diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 09:23:06 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 09:23:06 +0000 |
commit | cd13e2506379761d3464eae917eb1881876a7aa1 (patch) | |
tree | 54d32c05b0d6396408ff0d599d771fb9e7cdd60c /src | |
parent | Initial commit. (diff) | |
download | libfido2-cd13e2506379761d3464eae917eb1881876a7aa1.tar.xz libfido2-cd13e2506379761d3464eae917eb1881876a7aa1.zip |
Adding upstream version 1.12.0.upstream/1.12.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src')
-rw-r--r-- | src/CMakeLists.txt | 158 | ||||
-rw-r--r-- | src/aes256.c | 216 | ||||
-rw-r--r-- | src/assert.c | 1097 | ||||
-rw-r--r-- | src/authkey.c | 107 | ||||
-rw-r--r-- | src/bio.c | 894 | ||||
-rw-r--r-- | src/blob.c | 134 | ||||
-rw-r--r-- | src/blob.h | 42 | ||||
-rw-r--r-- | src/buf.c | 34 | ||||
-rw-r--r-- | src/cbor.c | 1705 | ||||
-rw-r--r-- | src/compress.c | 168 | ||||
-rw-r--r-- | src/config.c | 235 | ||||
-rw-r--r-- | src/cred.c | 1222 | ||||
-rw-r--r-- | src/credman.c | 825 | ||||
-rw-r--r-- | src/dev.c | 601 | ||||
-rwxr-xr-x | src/diff_exports.sh | 27 | ||||
-rw-r--r-- | src/ecdh.c | 208 | ||||
-rw-r--r-- | src/eddsa.c | 232 | ||||
-rw-r--r-- | src/err.c | 137 | ||||
-rw-r--r-- | src/es256.c | 541 | ||||
-rw-r--r-- | src/es384.c | 296 | ||||
-rw-r--r-- | src/export.gnu | 263 | ||||
-rw-r--r-- | src/export.llvm | 258 | ||||
-rw-r--r-- | src/export.msvc | 259 | ||||
-rw-r--r-- | src/extern.h | 276 | ||||
-rw-r--r-- | src/fallthrough.h | 21 | ||||
-rw-r--r-- | src/fido.h | 272 | ||||
-rw-r--r-- | src/fido/bio.h | 133 | ||||
-rw-r--r-- | src/fido/config.h | 58 | ||||
-rw-r--r-- | src/fido/credman.h | 113 | ||||
-rw-r--r-- | src/fido/eddsa.h | 71 | ||||
-rw-r--r-- | src/fido/err.h | 106 | ||||
-rw-r--r-- | src/fido/es256.h | 71 | ||||
-rw-r--r-- | src/fido/es384.h | 59 | ||||
-rw-r--r-- | src/fido/param.h | 160 | ||||
-rw-r--r-- | src/fido/rs256.h | 59 | ||||
-rw-r--r-- | src/fido/types.h | 335 | ||||
-rw-r--r-- | src/hid.c | 222 | ||||
-rw-r--r-- | src/hid_freebsd.c | 337 | ||||
-rw-r--r-- | src/hid_hidapi.c | 269 | ||||
-rw-r--r-- | src/hid_linux.c | 391 | ||||
-rw-r--r-- | src/hid_netbsd.c | 339 | ||||
-rw-r--r-- | src/hid_openbsd.c | 280 | ||||
-rw-r--r-- | src/hid_osx.c | 597 | ||||
-rw-r--r-- | src/hid_unix.c | 76 | ||||
-rw-r--r-- | src/hid_win.c | 571 | ||||
-rw-r--r-- | src/info.c | 647 | ||||
-rw-r--r-- | src/io.c | 356 | ||||
-rw-r--r-- | src/iso7816.c | 65 | ||||
-rw-r--r-- | src/iso7816.h | 49 | ||||
-rw-r--r-- | src/largeblob.c | 902 | ||||
-rw-r--r-- | src/libfido2.pc.in | 12 | ||||
-rw-r--r-- | src/log.c | 122 | ||||
-rw-r--r-- | src/netlink.c | 785 | ||||
-rw-r--r-- | src/netlink.h | 45 | ||||
-rw-r--r-- | src/nfc.c | 350 | ||||
-rw-r--r-- | src/nfc_linux.c | 356 | ||||
-rw-r--r-- | src/packed.h | 23 | ||||
-rw-r--r-- | src/pcsc.c | 394 | ||||
-rw-r--r-- | src/pin.c | 723 | ||||
-rw-r--r-- | src/random.c | 83 | ||||
-rw-r--r-- | src/reset.c | 46 | ||||
-rw-r--r-- | src/rs1.c | 100 | ||||
-rw-r--r-- | src/rs256.c | 316 | ||||
-rw-r--r-- | src/time.c | 75 | ||||
-rw-r--r-- | src/touch.c | 109 | ||||
-rw-r--r-- | src/tpm.c | 391 | ||||
-rw-r--r-- | src/types.c | 91 | ||||
-rw-r--r-- | src/u2f.c | 959 | ||||
-rw-r--r-- | src/util.c | 31 | ||||
-rw-r--r-- | src/webauthn.h | 990 | ||||
-rw-r--r-- | src/winhello.c | 1036 |
71 files changed, 23531 insertions, 0 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..73493b1 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,158 @@ +# Copyright (c) 2018-2022 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. +# SPDX-License-Identifier: BSD-2-Clause + +add_definitions(-D_FIDO_INTERNAL) + +list(APPEND FIDO_SOURCES + aes256.c + assert.c + authkey.c + bio.c + blob.c + buf.c + cbor.c + compress.c + config.c + cred.c + credman.c + dev.c + ecdh.c + eddsa.c + err.c + es256.c + es384.c + hid.c + info.c + io.c + iso7816.c + largeblob.c + log.c + pin.c + random.c + reset.c + rs1.c + rs256.c + time.c + touch.c + tpm.c + types.c + u2f.c + util.c +) + +if(FUZZ) + list(APPEND FIDO_SOURCES ../fuzz/clock.c) + list(APPEND FIDO_SOURCES ../fuzz/pcsc.c) + list(APPEND FIDO_SOURCES ../fuzz/prng.c) + list(APPEND FIDO_SOURCES ../fuzz/udev.c) + list(APPEND FIDO_SOURCES ../fuzz/uniform_random.c) + list(APPEND FIDO_SOURCES ../fuzz/wrap.c) +endif() + +if(NFC_LINUX) + list(APPEND FIDO_SOURCES netlink.c nfc.c nfc_linux.c) +endif() + +if(USE_PCSC) + list(APPEND FIDO_SOURCES nfc.c pcsc.c) +endif() + +if(USE_HIDAPI) + list(APPEND FIDO_SOURCES hid_hidapi.c) + if(NOT WIN32 AND NOT APPLE) + list(APPEND FIDO_SOURCES hid_unix.c) + endif() +elseif(WIN32) + list(APPEND FIDO_SOURCES hid_win.c) + if(USE_WINHELLO) + list(APPEND FIDO_SOURCES winhello.c) + endif() +elseif(APPLE) + list(APPEND FIDO_SOURCES hid_osx.c) +elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux") + list(APPEND FIDO_SOURCES hid_linux.c hid_unix.c) +elseif(CMAKE_SYSTEM_NAME STREQUAL "NetBSD") + list(APPEND FIDO_SOURCES hid_netbsd.c hid_unix.c) +elseif(CMAKE_SYSTEM_NAME STREQUAL "OpenBSD") + list(APPEND FIDO_SOURCES hid_openbsd.c hid_unix.c) +elseif(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" OR + CMAKE_SYSTEM_NAME STREQUAL "MidnightBSD") + list(APPEND FIDO_SOURCES hid_freebsd.c hid_unix.c) +else() + message(FATAL_ERROR "please define a hid backend for your platform") +endif() + +if(NOT MSVC) + set_source_files_properties(${FIDO_SOURCES} + PROPERTIES COMPILE_FLAGS "${EXTRA_CFLAGS}") +endif() + +list(APPEND COMPAT_SOURCES + ../openbsd-compat/bsd-asprintf.c + ../openbsd-compat/bsd-getpagesize.c + ../openbsd-compat/clock_gettime.c + ../openbsd-compat/endian_win32.c + ../openbsd-compat/explicit_bzero.c + ../openbsd-compat/explicit_bzero_win32.c + ../openbsd-compat/freezero.c + ../openbsd-compat/recallocarray.c + ../openbsd-compat/strlcat.c + ../openbsd-compat/timingsafe_bcmp.c +) + +if(WIN32) + list(APPEND BASE_LIBRARIES wsock32 ws2_32 bcrypt setupapi hid) + if(USE_PCSC) + list(APPEND BASE_LIBRARIES winscard) + endif() +elseif(APPLE) + list(APPEND BASE_LIBRARIES "-framework CoreFoundation" + "-framework IOKit") + if(USE_PCSC) + list(APPEND BASE_LIBRARIES "-framework PCSC") + endif() +endif() + +list(APPEND TARGET_LIBRARIES + ${CBOR_LIBRARIES} + ${CRYPTO_LIBRARIES} + ${UDEV_LIBRARIES} + ${BASE_LIBRARIES} + ${HIDAPI_LIBRARIES} + ${ZLIB_LIBRARIES} + ${PCSC_LIBRARIES} +) + +# static library +if(BUILD_STATIC_LIBS) + add_library(fido2 STATIC ${FIDO_SOURCES} ${COMPAT_SOURCES}) + if(WIN32 AND NOT MINGW) + set_target_properties(fido2 PROPERTIES OUTPUT_NAME fido2_static) + endif() + target_link_libraries(fido2 ${TARGET_LIBRARIES}) + install(TARGETS fido2 ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) +endif() + +# dynamic library +if(BUILD_SHARED_LIBS) + add_library(fido2_shared SHARED ${FIDO_SOURCES} ${COMPAT_SOURCES}) + set_target_properties(fido2_shared PROPERTIES OUTPUT_NAME fido2 + VERSION ${FIDO_VERSION} SOVERSION ${FIDO_MAJOR}) + target_link_libraries(fido2_shared ${TARGET_LIBRARIES}) + install(TARGETS fido2_shared + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) +endif() + +install(FILES fido.h DESTINATION include) +install(DIRECTORY fido DESTINATION include) + +if(NOT MSVC) + configure_file(libfido2.pc.in libfido2.pc @ONLY) + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/libfido2.pc" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig") +endif() diff --git a/src/aes256.c b/src/aes256.c new file mode 100644 index 0000000..dcf716d --- /dev/null +++ b/src/aes256.c @@ -0,0 +1,216 @@ +/* + * Copyright (c) 2021 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. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "fido.h" + +static int +aes256_cbc(const fido_blob_t *key, const u_char *iv, const fido_blob_t *in, + fido_blob_t *out, int encrypt) +{ + EVP_CIPHER_CTX *ctx = NULL; + const EVP_CIPHER *cipher; + int ok = -1; + + memset(out, 0, sizeof(*out)); + + if (key->len != 32) { + fido_log_debug("%s: invalid key len %zu", __func__, key->len); + goto fail; + } + if (in->len > UINT_MAX || in->len % 16 || in->len == 0) { + fido_log_debug("%s: invalid input len %zu", __func__, in->len); + goto fail; + } + out->len = in->len; + if ((out->ptr = calloc(1, out->len)) == NULL) { + fido_log_debug("%s: calloc", __func__); + goto fail; + } + if ((ctx = EVP_CIPHER_CTX_new()) == NULL || + (cipher = EVP_aes_256_cbc()) == NULL) { + fido_log_debug("%s: EVP_CIPHER_CTX_new", __func__); + goto fail; + } + if (EVP_CipherInit(ctx, cipher, key->ptr, iv, encrypt) == 0 || + EVP_Cipher(ctx, out->ptr, in->ptr, (u_int)out->len) < 0) { + fido_log_debug("%s: EVP_Cipher", __func__); + goto fail; + } + + ok = 0; +fail: + if (ctx != NULL) + EVP_CIPHER_CTX_free(ctx); + if (ok < 0) + fido_blob_reset(out); + + return ok; +} + +static int +aes256_cbc_proto1(const fido_blob_t *key, const fido_blob_t *in, + fido_blob_t *out, int encrypt) +{ + u_char iv[16]; + + memset(&iv, 0, sizeof(iv)); + + return aes256_cbc(key, iv, in, out, encrypt); +} + +static int +aes256_cbc_fips(const fido_blob_t *secret, const fido_blob_t *in, + fido_blob_t *out, int encrypt) +{ + fido_blob_t key, cin, cout; + u_char iv[16]; + + memset(out, 0, sizeof(*out)); + + if (secret->len != 64) { + fido_log_debug("%s: invalid secret len %zu", __func__, + secret->len); + return -1; + } + if (in->len < sizeof(iv)) { + fido_log_debug("%s: invalid input len %zu", __func__, in->len); + return -1; + } + if (encrypt) { + if (fido_get_random(iv, sizeof(iv)) < 0) { + fido_log_debug("%s: fido_get_random", __func__); + return -1; + } + cin = *in; + } else { + memcpy(iv, in->ptr, sizeof(iv)); + cin.ptr = in->ptr + sizeof(iv); + cin.len = in->len - sizeof(iv); + } + key.ptr = secret->ptr + 32; + key.len = secret->len - 32; + if (aes256_cbc(&key, iv, &cin, &cout, encrypt) < 0) + return -1; + if (encrypt) { + if (cout.len > SIZE_MAX - sizeof(iv) || + (out->ptr = calloc(1, sizeof(iv) + cout.len)) == NULL) { + fido_blob_reset(&cout); + return -1; + } + out->len = sizeof(iv) + cout.len; + memcpy(out->ptr, iv, sizeof(iv)); + memcpy(out->ptr + sizeof(iv), cout.ptr, cout.len); + fido_blob_reset(&cout); + } else + *out = cout; + + return 0; +} + +static int +aes256_gcm(const fido_blob_t *key, const fido_blob_t *nonce, + const fido_blob_t *aad, const fido_blob_t *in, fido_blob_t *out, + int encrypt) +{ + EVP_CIPHER_CTX *ctx = NULL; + const EVP_CIPHER *cipher; + size_t textlen; + int ok = -1; + + memset(out, 0, sizeof(*out)); + + if (nonce->len != 12 || key->len != 32 || aad->len > UINT_MAX) { + fido_log_debug("%s: invalid params %zu, %zu, %zu", __func__, + nonce->len, key->len, aad->len); + goto fail; + } + if (in->len > UINT_MAX || in->len > SIZE_MAX - 16 || in->len < 16) { + fido_log_debug("%s: invalid input len %zu", __func__, in->len); + goto fail; + } + /* add tag to (on encrypt) or trim tag from the output (on decrypt) */ + out->len = encrypt ? in->len + 16 : in->len - 16; + if ((out->ptr = calloc(1, out->len)) == NULL) { + fido_log_debug("%s: calloc", __func__); + goto fail; + } + if ((ctx = EVP_CIPHER_CTX_new()) == NULL || + (cipher = EVP_aes_256_gcm()) == NULL) { + fido_log_debug("%s: EVP_CIPHER_CTX_new", __func__); + goto fail; + } + if (EVP_CipherInit(ctx, cipher, key->ptr, nonce->ptr, encrypt) == 0) { + fido_log_debug("%s: EVP_CipherInit", __func__); + goto fail; + } + + if (encrypt) + textlen = in->len; + else { + textlen = in->len - 16; + /* point openssl at the mac tag */ + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, 16, + in->ptr + in->len - 16) == 0) { + fido_log_debug("%s: EVP_CIPHER_CTX_ctrl", __func__); + goto fail; + } + } + /* the last EVP_Cipher() will either compute or verify the mac tag */ + if (EVP_Cipher(ctx, NULL, aad->ptr, (u_int)aad->len) < 0 || + EVP_Cipher(ctx, out->ptr, in->ptr, (u_int)textlen) < 0 || + EVP_Cipher(ctx, NULL, NULL, 0) < 0) { + fido_log_debug("%s: EVP_Cipher", __func__); + goto fail; + } + if (encrypt) { + /* append the mac tag */ + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, 16, + out->ptr + out->len - 16) == 0) { + fido_log_debug("%s: EVP_CIPHER_CTX_ctrl", __func__); + goto fail; + } + } + + ok = 0; +fail: + if (ctx != NULL) + EVP_CIPHER_CTX_free(ctx); + if (ok < 0) + fido_blob_reset(out); + + return ok; +} + +int +aes256_cbc_enc(const fido_dev_t *dev, const fido_blob_t *secret, + const fido_blob_t *in, fido_blob_t *out) +{ + return fido_dev_get_pin_protocol(dev) == 2 ? aes256_cbc_fips(secret, + in, out, 1) : aes256_cbc_proto1(secret, in, out, 1); +} + +int +aes256_cbc_dec(const fido_dev_t *dev, const fido_blob_t *secret, + const fido_blob_t *in, fido_blob_t *out) +{ + return fido_dev_get_pin_protocol(dev) == 2 ? aes256_cbc_fips(secret, + in, out, 0) : aes256_cbc_proto1(secret, in, out, 0); +} + +int +aes256_gcm_enc(const fido_blob_t *key, const fido_blob_t *nonce, + const fido_blob_t *aad, const fido_blob_t *in, fido_blob_t *out) +{ + return aes256_gcm(key, nonce, aad, in, out, 1); +} + +int +aes256_gcm_dec(const fido_blob_t *key, const fido_blob_t *nonce, + const fido_blob_t *aad, const fido_blob_t *in, fido_blob_t *out) +{ + return aes256_gcm(key, nonce, aad, in, out, 0); +} diff --git a/src/assert.c b/src/assert.c new file mode 100644 index 0000000..a690f46 --- /dev/null +++ b/src/assert.c @@ -0,0 +1,1097 @@ +/* + * Copyright (c) 2018-2022 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. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <openssl/sha.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) { + fido_log_debug("%s: cbor_type", __func__); + return (0); /* ignore */ + } + + if (cbor_decode_uint64(val, &n) < 0 || n > SIZE_MAX) { + fido_log_debug("%s: cbor_decode_uint64", __func__); + return (-1); + } + + if (assert->stmt_len != 0 || assert->stmt_cnt != 1 || + (size_t)n < assert->stmt_cnt) { + fido_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) { + fido_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) { + fido_log_debug("%s: cbor type", __func__); + return (0); /* ignore */ + } + + switch (cbor_get_uint8(key)) { + case 1: /* credential id */ + return (cbor_decode_cred_id(val, &stmt->id)); + case 2: /* authdata */ + return (cbor_decode_assert_authdata(val, &stmt->authdata_cbor, + &stmt->authdata, &stmt->authdata_ext)); + case 3: /* signature */ + return (fido_blob_decode(val, &stmt->sig)); + case 4: /* user attributes */ + return (cbor_decode_user(val, &stmt->user)); + case 7: /* large blob key */ + return (fido_blob_decode(val, &stmt->largeblob_key)); + default: /* ignore */ + fido_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, int *ms) +{ + fido_blob_t f; + fido_opt_t uv = assert->uv; + cbor_item_t *argv[7]; + const uint8_t cmd = CTAP_CBOR_ASSERT; + 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) { + fido_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) { + fido_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] = cbor_encode_pubkey_list(cl)) == NULL) { + fido_log_debug("%s: cbor_encode_pubkey_list", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + } + + if (assert->ext.mask) + if ((argv[3] = cbor_encode_assert_ext(dev, &assert->ext, ecdh, + pk)) == NULL) { + fido_log_debug("%s: cbor_encode_assert_ext", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + /* user verification */ + if (pin != NULL || (uv == FIDO_OPT_TRUE && + fido_dev_supports_permissions(dev))) { + if ((r = cbor_add_uv_params(dev, cmd, &assert->cdh, pk, ecdh, + pin, assert->rp_id, &argv[5], &argv[6], ms)) != FIDO_OK) { + fido_log_debug("%s: cbor_add_uv_params", __func__); + goto fail; + } + uv = FIDO_OPT_OMIT; + } + + /* options */ + if (assert->up != FIDO_OPT_OMIT || uv != FIDO_OPT_OMIT) + if ((argv[4] = cbor_encode_assert_opt(assert->up, uv)) == NULL) { + fido_log_debug("%s: cbor_encode_assert_opt", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + /* frame and transmit */ + if (cbor_build_frame(cmd, argv, nitems(argv), &f) < 0 || + fido_tx(dev, CTAP_CMD_CBOR, f.ptr, f.len, ms) < 0) { + fido_log_debug("%s: fido_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) +{ + unsigned char *msg; + int msglen; + int r; + + fido_assert_reset_rx(assert); + + if ((msg = malloc(FIDO_MAXMSG)) == NULL) { + r = FIDO_ERR_INTERNAL; + goto out; + } + + if ((msglen = fido_rx(dev, CTAP_CMD_CBOR, msg, FIDO_MAXMSG, ms)) < 0) { + fido_log_debug("%s: fido_rx", __func__); + r = FIDO_ERR_RX; + goto out; + } + + /* start with room for a single assertion */ + if ((assert->stmt = calloc(1, sizeof(fido_assert_stmt))) == NULL) { + r = FIDO_ERR_INTERNAL; + goto out; + } + assert->stmt_len = 0; + assert->stmt_cnt = 1; + + /* adjust as needed */ + if ((r = cbor_parse_reply(msg, (size_t)msglen, assert, + adjust_assert_count)) != FIDO_OK) { + fido_log_debug("%s: adjust_assert_count", __func__); + goto out; + } + + /* parse the first assertion */ + if ((r = cbor_parse_reply(msg, (size_t)msglen, &assert->stmt[0], + parse_assert_reply)) != FIDO_OK) { + fido_log_debug("%s: parse_assert_reply", __func__); + goto out; + } + assert->stmt_len = 1; + + r = FIDO_OK; +out: + freezero(msg, FIDO_MAXMSG); + + return (r); +} + +static int +fido_get_next_assert_tx(fido_dev_t *dev, int *ms) +{ + const unsigned char cbor[] = { CTAP_CBOR_NEXT_ASSERT }; + + if (fido_tx(dev, CTAP_CMD_CBOR, cbor, sizeof(cbor), ms) < 0) { + fido_log_debug("%s: fido_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) +{ + unsigned char *msg; + int msglen; + int r; + + if ((msg = malloc(FIDO_MAXMSG)) == NULL) { + r = FIDO_ERR_INTERNAL; + goto out; + } + + if ((msglen = fido_rx(dev, CTAP_CMD_CBOR, msg, FIDO_MAXMSG, ms)) < 0) { + fido_log_debug("%s: fido_rx", __func__); + r = FIDO_ERR_RX; + goto out; + } + + /* sanity check */ + if (assert->stmt_len >= assert->stmt_cnt) { + fido_log_debug("%s: stmt_len=%zu, stmt_cnt=%zu", __func__, + assert->stmt_len, assert->stmt_cnt); + r = FIDO_ERR_INTERNAL; + goto out; + } + + if ((r = cbor_parse_reply(msg, (size_t)msglen, + &assert->stmt[assert->stmt_len], parse_assert_reply)) != FIDO_OK) { + fido_log_debug("%s: parse_assert_reply", __func__); + goto out; + } + + r = FIDO_OK; +out: + freezero(msg, FIDO_MAXMSG); + + return (r); +} + +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, + ms)) != 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, ms)) != 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(const fido_dev_t *dev, 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->authdata_ext.hmac_secret_enc.ptr != NULL) { + if (aes256_cbc_dec(dev, key, + &stmt->authdata_ext.hmac_secret_enc, + &stmt->hmac_secret) < 0) { + fido_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 ms = dev->timeout_ms; + int r; + +#ifdef USE_WINHELLO + if (dev->flags & FIDO_DEV_WINHELLO) + return (fido_winhello_get_assert(dev, assert, pin, ms)); +#endif + + if (assert->rp_id == NULL || assert->cdh.ptr == NULL) { + fido_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.mask != 0) + return (FIDO_ERR_UNSUPPORTED_OPTION); + return (u2f_authenticate(dev, assert, &ms)); + } + + if (pin != NULL || (assert->uv == FIDO_OPT_TRUE && + fido_dev_supports_permissions(dev)) || + (assert->ext.mask & FIDO_EXT_HMAC_SECRET)) { + if ((r = fido_do_ecdh(dev, &pk, &ecdh, &ms)) != FIDO_OK) { + fido_log_debug("%s: fido_do_ecdh", __func__); + goto fail; + } + } + + r = fido_dev_get_assert_wait(dev, assert, pk, ecdh, pin, &ms); + if (r == FIDO_OK && (assert->ext.mask & FIDO_EXT_HMAC_SECRET)) + if (decrypt_hmac_secrets(dev, assert, ecdh) < 0) { + fido_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 +fido_check_flags(uint8_t flags, fido_opt_t up, fido_opt_t uv) +{ + fido_log_debug("%s: flags=%02x", __func__, flags); + fido_log_debug("%s: up=%d, uv=%d", __func__, up, uv); + + if (up == FIDO_OPT_TRUE && + (flags & CTAP_AUTHDATA_USER_PRESENT) == 0) { + fido_log_debug("%s: CTAP_AUTHDATA_USER_PRESENT", __func__); + return (-1); /* user not present */ + } + + if (uv == FIDO_OPT_TRUE && + (flags & CTAP_AUTHDATA_USER_VERIFIED) == 0) { + fido_log_debug("%s: CTAP_AUTHDATA_USER_VERIFIED", __func__); + return (-1); /* user not verified */ + } + + return (0); +} + +static int +check_extensions(int authdata_ext, int ext) +{ + /* XXX: largeBlobKey is not part of extensions map */ + ext &= ~FIDO_EXT_LARGEBLOB_KEY; + if (authdata_ext != ext) { + fido_log_debug("%s: authdata_ext=0x%x != ext=0x%x", __func__, + authdata_ext, ext); + return (-1); + } + + return (0); +} + +static int +get_es256_hash(fido_blob_t *dgst, const fido_blob_t *clientdata, + const fido_blob_t *authdata) +{ + const EVP_MD *md; + EVP_MD_CTX *ctx = NULL; + + if (dgst->len < SHA256_DIGEST_LENGTH || + (md = EVP_sha256()) == NULL || + (ctx = EVP_MD_CTX_new()) == NULL || + EVP_DigestInit_ex(ctx, md, NULL) != 1 || + EVP_DigestUpdate(ctx, authdata->ptr, authdata->len) != 1 || + EVP_DigestUpdate(ctx, clientdata->ptr, clientdata->len) != 1 || + EVP_DigestFinal_ex(ctx, dgst->ptr, NULL) != 1) { + EVP_MD_CTX_free(ctx); + return (-1); + } + dgst->len = SHA256_DIGEST_LENGTH; + + EVP_MD_CTX_free(ctx); + + return (0); +} + +static int +get_es384_hash(fido_blob_t *dgst, const fido_blob_t *clientdata, + const fido_blob_t *authdata) +{ + const EVP_MD *md; + EVP_MD_CTX *ctx = NULL; + + if (dgst->len < SHA384_DIGEST_LENGTH || + (md = EVP_sha384()) == NULL || + (ctx = EVP_MD_CTX_new()) == NULL || + EVP_DigestInit_ex(ctx, md, NULL) != 1 || + EVP_DigestUpdate(ctx, authdata->ptr, authdata->len) != 1 || + EVP_DigestUpdate(ctx, clientdata->ptr, clientdata->len) != 1 || + EVP_DigestFinal_ex(ctx, dgst->ptr, NULL) != 1) { + EVP_MD_CTX_free(ctx); + return (-1); + } + dgst->len = SHA384_DIGEST_LENGTH; + + EVP_MD_CTX_free(ctx); + + return (0); +} + +static int +get_eddsa_hash(fido_blob_t *dgst, const fido_blob_t *clientdata, + const fido_blob_t *authdata) +{ + if (SIZE_MAX - authdata->len < clientdata->len || + dgst->len < authdata->len + clientdata->len) + return (-1); + + memcpy(dgst->ptr, authdata->ptr, authdata->len); + memcpy(dgst->ptr + authdata->len, clientdata->ptr, clientdata->len); + dgst->len = authdata->len + clientdata->len; + + return (0); +} + +int +fido_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; + fido_blob_t authdata; + struct cbor_load_result cbor; + int ok = -1; + + fido_log_debug("%s: cose_alg=%d", __func__, cose_alg); + + if ((item = cbor_load(authdata_cbor->ptr, authdata_cbor->len, + &cbor)) == NULL || cbor_isa_bytestring(item) == false || + cbor_bytestring_is_definite(item) == false) { + fido_log_debug("%s: authdata", __func__); + goto fail; + } + authdata.ptr = cbor_bytestring_handle(item); + authdata.len = cbor_bytestring_length(item); + + switch (cose_alg) { + case COSE_ES256: + case COSE_RS256: + ok = get_es256_hash(dgst, clientdata, &authdata); + break; + case COSE_ES384: + ok = get_es384_hash(dgst, clientdata, &authdata); + break; + case COSE_EDDSA: + ok = get_eddsa_hash(dgst, clientdata, &authdata); + break; + default: + fido_log_debug("%s: unknown cose_alg", __func__); + break; + } +fail: + if (item != NULL) + cbor_decref(&item); + + return (ok); +} + +int +fido_assert_verify(const fido_assert_t *assert, size_t idx, int cose_alg, + const void *pk) +{ + unsigned char buf[1024]; /* XXX */ + 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) { + fido_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 (fido_check_flags(stmt->authdata.flags, assert->up, + assert->uv) < 0) { + fido_log_debug("%s: fido_check_flags", __func__); + r = FIDO_ERR_INVALID_PARAM; + goto out; + } + + if (check_extensions(stmt->authdata_ext.mask, assert->ext.mask) < 0) { + fido_log_debug("%s: check_extensions", __func__); + r = FIDO_ERR_INVALID_PARAM; + goto out; + } + + if (fido_check_rp_id(assert->rp_id, stmt->authdata.rp_id_hash) != 0) { + fido_log_debug("%s: fido_check_rp_id", __func__); + r = FIDO_ERR_INVALID_PARAM; + goto out; + } + + if (fido_get_signed_hash(cose_alg, &dgst, &assert->cdh, + &stmt->authdata_cbor) < 0) { + fido_log_debug("%s: fido_get_signed_hash", __func__); + r = FIDO_ERR_INTERNAL; + goto out; + } + + switch (cose_alg) { + case COSE_ES256: + ok = es256_pk_verify_sig(&dgst, pk, &stmt->sig); + break; + case COSE_ES384: + ok = es384_pk_verify_sig(&dgst, pk, &stmt->sig); + break; + case COSE_RS256: + ok = rs256_pk_verify_sig(&dgst, pk, &stmt->sig); + break; + case COSE_EDDSA: + ok = eddsa_pk_verify_sig(&dgst, pk, &stmt->sig); + break; + default: + fido_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(fido_assert_t *assert, const unsigned char *data, + size_t data_len) +{ + if (!fido_blob_is_empty(&assert->cdh) || + fido_blob_set(&assert->cd, data, data_len) < 0) { + return (FIDO_ERR_INVALID_ARGUMENT); + } + if (fido_sha256(&assert->cdh, data, data_len) < 0) { + fido_blob_reset(&assert->cd); + return (FIDO_ERR_INTERNAL); + } + + return (FIDO_OK); +} + +int +fido_assert_set_clientdata_hash(fido_assert_t *assert, + const unsigned char *hash, size_t hash_len) +{ + if (!fido_blob_is_empty(&assert->cd) || + 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->ext.hmac_salt, salt, salt_len) < 0) + return (FIDO_ERR_INVALID_ARGUMENT); + + return (FIDO_OK); +} + +int +fido_assert_set_hmac_secret(fido_assert_t *assert, size_t idx, + const unsigned char *secret, size_t secret_len) +{ + if (idx >= assert->stmt_len || (secret_len != 32 && secret_len != 64) || + fido_blob_set(&assert->stmt[idx].hmac_secret, secret, + secret_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) + assert->ext.mask = 0; + else { + if ((ext & FIDO_EXT_ASSERT_MASK) != ext) + return (FIDO_ERR_INVALID_ARGUMENT); + assert->ext.mask |= 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); + fido_blob_reset(&assert->cd); + fido_blob_reset(&assert->cdh); + fido_blob_reset(&assert->ext.hmac_salt); + fido_free_blob_array(&assert->allow_list); + memset(&assert->ext, 0, sizeof(assert->ext)); + memset(&assert->allow_list, 0, sizeof(assert->allow_list)); + assert->rp_id = NULL; + assert->up = FIDO_OPT_OMIT; + assert->uv = FIDO_OPT_OMIT; +} + +static void +fido_assert_reset_extattr(fido_assert_extattr_t *ext) +{ + fido_blob_reset(&ext->hmac_secret_enc); + fido_blob_reset(&ext->blob); + memset(ext, 0, sizeof(*ext)); +} + +void +fido_assert_reset_rx(fido_assert_t *assert) +{ + for (size_t i = 0; i < assert->stmt_cnt; i++) { + free(assert->stmt[i].user.icon); + free(assert->stmt[i].user.name); + free(assert->stmt[i].user.display_name); + fido_blob_reset(&assert->stmt[i].user.id); + fido_blob_reset(&assert->stmt[i].id); + fido_blob_reset(&assert->stmt[i].hmac_secret); + fido_blob_reset(&assert->stmt[i].authdata_cbor); + fido_blob_reset(&assert->stmt[i].largeblob_key); + fido_blob_reset(&assert->stmt[i].sig); + fido_assert_reset_extattr(&assert->stmt[i].authdata_ext); + 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); +} + +const unsigned char * +fido_assert_largeblob_key_ptr(const fido_assert_t *assert, size_t idx) +{ + if (idx >= assert->stmt_len) + return (NULL); + + return (assert->stmt[idx].largeblob_key.ptr); +} + +size_t +fido_assert_largeblob_key_len(const fido_assert_t *assert, size_t idx) +{ + if (idx >= assert->stmt_len) + return (0); + + return (assert->stmt[idx].largeblob_key.len); +} + +const unsigned char * +fido_assert_blob_ptr(const fido_assert_t *assert, size_t idx) +{ + if (idx >= assert->stmt_len) + return (NULL); + + return (assert->stmt[idx].authdata_ext.blob.ptr); +} + +size_t +fido_assert_blob_len(const fido_assert_t *assert, size_t idx) +{ + if (idx >= assert->stmt_len) + return (0); + + return (assert->stmt[idx].authdata_ext.blob.len); +} + +static void +fido_assert_clean_authdata(fido_assert_stmt *stmt) +{ + fido_blob_reset(&stmt->authdata_cbor); + fido_assert_reset_extattr(&stmt->authdata_ext); + memset(&stmt->authdata, 0, sizeof(stmt->authdata)); +} + +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) { + fido_log_debug("%s: cbor_load", __func__); + r = FIDO_ERR_INVALID_ARGUMENT; + goto fail; + } + + if (cbor_decode_assert_authdata(item, &stmt->authdata_cbor, + &stmt->authdata, &stmt->authdata_ext) < 0) { + fido_log_debug("%s: cbor_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) { + fido_log_debug("%s: cbor_build_bytestring", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + if (cbor_decode_assert_authdata(item, &stmt->authdata_cbor, + &stmt->authdata, &stmt->authdata_ext) < 0) { + fido_log_debug("%s: cbor_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_sig(fido_assert_t *a, size_t idx, const unsigned char *ptr, + size_t len) +{ + if (idx >= a->stmt_len || ptr == NULL || len == 0) + return (FIDO_ERR_INVALID_ARGUMENT); + if (fido_blob_set(&a->stmt[idx].sig, ptr, len) < 0) + return (FIDO_ERR_INTERNAL); + + 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) { + fido_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/src/authkey.c b/src/authkey.c new file mode 100644 index 0000000..761562b --- /dev/null +++ b/src/authkey.c @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2018-2022 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. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#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) { + fido_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, int *ms) +{ + fido_blob_t f; + cbor_item_t *argv[2]; + int r; + + fido_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_encode_pin_opt(dev)) == NULL || + (argv[1] = cbor_build_uint8(2)) == NULL) { + fido_log_debug("%s: cbor_build", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + /* frame and transmit */ + if (cbor_build_frame(CTAP_CBOR_CLIENT_PIN, argv, nitems(argv), + &f) < 0 || fido_tx(dev, CTAP_CMD_CBOR, f.ptr, f.len, ms) < 0) { + fido_log_debug("%s: fido_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) +{ + unsigned char *msg; + int msglen; + int r; + + fido_log_debug("%s: dev=%p, authkey=%p, ms=%d", __func__, (void *)dev, + (void *)authkey, *ms); + + memset(authkey, 0, sizeof(*authkey)); + + if ((msg = malloc(FIDO_MAXMSG)) == NULL) { + r = FIDO_ERR_INTERNAL; + goto out; + } + + if ((msglen = fido_rx(dev, CTAP_CMD_CBOR, msg, FIDO_MAXMSG, ms)) < 0) { + fido_log_debug("%s: fido_rx", __func__); + r = FIDO_ERR_RX; + goto out; + } + + r = cbor_parse_reply(msg, (size_t)msglen, authkey, parse_authkey); +out: + freezero(msg, FIDO_MAXMSG); + + return (r); +} + +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, ms)) != 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, int *ms) +{ + return (fido_dev_authkey_wait(dev, authkey, ms)); +} diff --git a/src/bio.c b/src/bio.c new file mode 100644 index 0000000..57db85f --- /dev/null +++ b/src/bio.c @@ -0,0 +1,894 @@ +/* + * Copyright (c) 2019-2022 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. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#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) { + fido_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)) { + fido_log_debug("%s: cbor_serialize_alloc", __func__); + goto fail; + } + + if ((hmac_data->ptr = malloc(cbor_len + sizeof(prefix))) == NULL) { + fido_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 subcmd, cbor_item_t **sub_argv, size_t sub_argc, + const char *pin, const fido_blob_t *token, int *ms) +{ + cbor_item_t *argv[5]; + es256_pk_t *pk = NULL; + fido_blob_t *ecdh = NULL; + fido_blob_t f; + fido_blob_t hmac; + const uint8_t cmd = CTAP_CBOR_BIO_ENROLL_PRE; + 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(subcmd)) == NULL) { + fido_log_debug("%s: cbor encode", __func__); + goto fail; + } + + /* subParams */ + if (pin || token) { + if (bio_prepare_hmac(subcmd, sub_argv, sub_argc, &argv[2], + &hmac) < 0) { + fido_log_debug("%s: bio_prepare_hmac", __func__); + goto fail; + } + } + + /* pinProtocol, pinAuth */ + if (pin) { + if ((r = fido_do_ecdh(dev, &pk, &ecdh, ms)) != FIDO_OK) { + fido_log_debug("%s: fido_do_ecdh", __func__); + goto fail; + } + if ((r = cbor_add_uv_params(dev, cmd, &hmac, pk, ecdh, pin, + NULL, &argv[4], &argv[3], ms)) != FIDO_OK) { + fido_log_debug("%s: cbor_add_uv_params", __func__); + goto fail; + } + } else if (token) { + if ((argv[3] = cbor_encode_pin_opt(dev)) == NULL || + (argv[4] = cbor_encode_pin_auth(dev, token, &hmac)) == NULL) { + fido_log_debug("%s: encode pin", __func__); + goto fail; + } + } + + /* framing and transmission */ + if (cbor_build_frame(cmd, argv, nitems(argv), &f) < 0 || + fido_tx(dev, CTAP_CMD_CBOR, f.ptr, f.len, ms) < 0) { + fido_log_debug("%s: fido_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); + t->name = NULL; + fido_blob_reset(&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) { + fido_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) { + fido_log_debug("%s: cbor type", __func__); + return (-1); + } + + if (ta->n_rx >= ta->n_alloc) { + fido_log_debug("%s: n_rx >= n_alloc", __func__); + return (-1); + } + + if (cbor_map_iter(item, &ta->ptr[ta->n_rx], decode_template) < 0) { + fido_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) { + fido_log_debug("%s: cbor type", __func__); + return (0); /* ignore */ + } + + if (cbor_isa_array(val) == false || + cbor_array_is_definite(val) == false) { + fido_log_debug("%s: cbor type", __func__); + return (-1); + } + + if (ta->ptr != NULL || ta->n_alloc != 0 || ta->n_rx != 0) { + fido_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) { + fido_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) +{ + unsigned char *msg; + int msglen; + int r; + + bio_reset_template_array(ta); + + if ((msg = malloc(FIDO_MAXMSG)) == NULL) { + r = FIDO_ERR_INTERNAL; + goto out; + } + + if ((msglen = fido_rx(dev, CTAP_CMD_CBOR, msg, FIDO_MAXMSG, ms)) < 0) { + fido_log_debug("%s: fido_rx", __func__); + r = FIDO_ERR_RX; + goto out; + } + + if ((r = cbor_parse_reply(msg, (size_t)msglen, ta, + bio_parse_template_array)) != FIDO_OK) { + fido_log_debug("%s: bio_parse_template_array" , __func__); + goto out; + } + + r = FIDO_OK; +out: + freezero(msg, FIDO_MAXMSG); + + return (r); +} + +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, ms)) != 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) +{ + int ms = dev->timeout_ms; + + if (pin == NULL) + return (FIDO_ERR_INVALID_ARGUMENT); + + return (bio_get_template_array_wait(dev, ta, pin, &ms)); +} + +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) { + fido_log_debug("%s: cbor encode", __func__); + goto fail; + } + + if ((r = bio_tx(dev, CMD_SET_NAME, argv, 2, pin, NULL, + ms)) != FIDO_OK || + (r = fido_rx_cbor_status(dev, ms)) != FIDO_OK) { + fido_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) +{ + int ms = dev->timeout_ms; + + if (pin == NULL || t->name == NULL) + return (FIDO_ERR_INVALID_ARGUMENT); + + return (bio_set_template_name_wait(dev, t, pin, &ms)); +} + +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) { + fido_log_debug("%s: cbor type", __func__); + return (0); /* ignore */ + } + + switch (cbor_get_uint8(key)) { + case 5: + if (cbor_decode_uint64(val, &x) < 0 || x > UINT8_MAX) { + fido_log_debug("%s: cbor_decode_uint64", __func__); + return (-1); + } + e->last_status = (uint8_t)x; + break; + case 6: + if (cbor_decode_uint64(val, &x) < 0 || x > UINT8_MAX) { + fido_log_debug("%s: cbor_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) { + fido_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) +{ + unsigned char *msg; + int msglen; + int r; + + bio_reset_template(t); + + e->remaining_samples = 0; + e->last_status = 0; + + if ((msg = malloc(FIDO_MAXMSG)) == NULL) { + r = FIDO_ERR_INTERNAL; + goto out; + } + + if ((msglen = fido_rx(dev, CTAP_CMD_CBOR, msg, FIDO_MAXMSG, ms)) < 0) { + fido_log_debug("%s: fido_rx", __func__); + r = FIDO_ERR_RX; + goto out; + } + + if ((r = cbor_parse_reply(msg, (size_t)msglen, e, + bio_parse_enroll_status)) != FIDO_OK) { + fido_log_debug("%s: bio_parse_enroll_status", __func__); + goto out; + } + + if ((r = cbor_parse_reply(msg, (size_t)msglen, &t->id, + bio_parse_template_id)) != FIDO_OK) { + fido_log_debug("%s: bio_parse_template_id", __func__); + goto out; + } + + r = FIDO_OK; +out: + freezero(msg, FIDO_MAXMSG); + + return (r); +} + +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_uint(timo_ms)) == NULL) { + fido_log_debug("%s: cbor encode", __func__); + goto fail; + } + + if ((r = bio_tx(dev, cmd, argv, 3, NULL, e->token, ms)) != FIDO_OK || + (r = bio_rx_enroll_begin(dev, t, e, ms)) != FIDO_OK) { + fido_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 ms = dev->timeout_ms; + 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, &ms)) != FIDO_OK) { + fido_log_debug("%s: fido_do_ecdh", __func__); + goto fail; + } + + if ((r = fido_dev_get_uv_token(dev, CTAP_CBOR_BIO_ENROLL_PRE, pin, ecdh, + pk, NULL, token, &ms)) != FIDO_OK) { + fido_log_debug("%s: fido_dev_get_uv_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, &ms)); +} + +static int +bio_rx_enroll_continue(fido_dev_t *dev, fido_bio_enroll_t *e, int *ms) +{ + unsigned char *msg; + int msglen; + int r; + + e->remaining_samples = 0; + e->last_status = 0; + + if ((msg = malloc(FIDO_MAXMSG)) == NULL) { + r = FIDO_ERR_INTERNAL; + goto out; + } + + if ((msglen = fido_rx(dev, CTAP_CMD_CBOR, msg, FIDO_MAXMSG, ms)) < 0) { + fido_log_debug("%s: fido_rx", __func__); + r = FIDO_ERR_RX; + goto out; + } + + if ((r = cbor_parse_reply(msg, (size_t)msglen, e, + bio_parse_enroll_status)) != FIDO_OK) { + fido_log_debug("%s: bio_parse_enroll_status", __func__); + goto out; + } + + r = FIDO_OK; +out: + freezero(msg, FIDO_MAXMSG); + + return (r); +} + +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_uint(timo_ms)) == NULL) { + fido_log_debug("%s: cbor encode", __func__); + goto fail; + } + + if ((r = bio_tx(dev, cmd, argv, 3, NULL, e->token, ms)) != FIDO_OK || + (r = bio_rx_enroll_continue(dev, e, ms)) != FIDO_OK) { + fido_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) +{ + int ms = dev->timeout_ms; + + if (e->token == NULL) + return (FIDO_ERR_INVALID_ARGUMENT); + + return (bio_enroll_continue_wait(dev, t, e, timo_ms, &ms)); +} + +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, ms)) != FIDO_OK || + (r = fido_rx_cbor_status(dev, ms)) != FIDO_OK) { + fido_log_debug("%s: tx/rx", __func__); + return (r); + } + + return (FIDO_OK); +} + +int +fido_bio_dev_enroll_cancel(fido_dev_t *dev) +{ + int ms = dev->timeout_ms; + + return (bio_enroll_cancel_wait(dev, &ms)); +} + +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) { + fido_log_debug("%s: cbor encode", __func__); + goto fail; + } + + if ((r = bio_tx(dev, cmd, argv, 1, pin, NULL, ms)) != FIDO_OK || + (r = fido_rx_cbor_status(dev, ms)) != FIDO_OK) { + fido_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) +{ + int ms = dev->timeout_ms; + + return (bio_enroll_remove_wait(dev, t, pin, &ms)); +} + +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) { + fido_log_debug("%s: cbor type", __func__); + return (0); /* ignore */ + } + + switch (cbor_get_uint8(key)) { + case 2: + if (cbor_decode_uint64(val, &x) < 0 || x > UINT8_MAX) { + fido_log_debug("%s: cbor_decode_uint64", __func__); + return (-1); + } + i->type = (uint8_t)x; + break; + case 3: + if (cbor_decode_uint64(val, &x) < 0 || x > UINT8_MAX) { + fido_log_debug("%s: cbor_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) +{ + unsigned char *msg; + int msglen; + int r; + + bio_reset_info(i); + + if ((msg = malloc(FIDO_MAXMSG)) == NULL) { + r = FIDO_ERR_INTERNAL; + goto out; + } + + if ((msglen = fido_rx(dev, CTAP_CMD_CBOR, msg, FIDO_MAXMSG, ms)) < 0) { + fido_log_debug("%s: fido_rx", __func__); + r = FIDO_ERR_RX; + goto out; + } + + if ((r = cbor_parse_reply(msg, (size_t)msglen, i, + bio_parse_info)) != FIDO_OK) { + fido_log_debug("%s: bio_parse_info" , __func__); + goto out; + } + + r = FIDO_OK; +out: + freezero(msg, FIDO_MAXMSG); + + return (r); +} + +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, + ms)) != FIDO_OK || + (r = bio_rx_info(dev, i, ms)) != FIDO_OK) { + fido_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) +{ + int ms = dev->timeout_ms; + + return (bio_get_info_wait(dev, i, &ms)); +} + +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) +{ + fido_blob_reset(&t->id); + + 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/src/blob.c b/src/blob.c new file mode 100644 index 0000000..b431f49 --- /dev/null +++ b/src/blob.c @@ -0,0 +1,134 @@ +/* + * 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. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "fido.h" + +fido_blob_t * +fido_blob_new(void) +{ + return calloc(1, sizeof(fido_blob_t)); +} + +void +fido_blob_reset(fido_blob_t *b) +{ + freezero(b->ptr, b->len); + explicit_bzero(b, sizeof(*b)); +} + +int +fido_blob_set(fido_blob_t *b, const u_char *ptr, size_t len) +{ + fido_blob_reset(b); + + if (ptr == NULL || len == 0) { + fido_log_debug("%s: ptr=%p, len=%zu", __func__, + (const void *)ptr, len); + return -1; + } + + if ((b->ptr = malloc(len)) == NULL) { + fido_log_debug("%s: malloc", __func__); + return -1; + } + + memcpy(b->ptr, ptr, len); + b->len = len; + + return 0; +} + +int +fido_blob_append(fido_blob_t *b, const u_char *ptr, size_t len) +{ + u_char *tmp; + + if (ptr == NULL || len == 0) { + fido_log_debug("%s: ptr=%p, len=%zu", __func__, + (const void *)ptr, len); + return -1; + } + if (SIZE_MAX - b->len < len) { + fido_log_debug("%s: overflow", __func__); + return -1; + } + if ((tmp = realloc(b->ptr, b->len + len)) == NULL) { + fido_log_debug("%s: realloc", __func__); + return -1; + } + b->ptr = tmp; + memcpy(&b->ptr[b->len], 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; + + fido_blob_reset(b); + free(b); + *bp = NULL; +} + +void +fido_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]; + freezero(b->ptr, b->len); + 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; +} + +int +fido_blob_serialise(fido_blob_t *b, const cbor_item_t *item) +{ + size_t alloc; + + if (!fido_blob_is_empty(b)) + return -1; + if ((b->len = cbor_serialize_alloc(item, &b->ptr, &alloc)) == 0) { + b->ptr = NULL; + return -1; + } + + return 0; +} diff --git a/src/blob.h b/src/blob.h new file mode 100644 index 0000000..7247185 --- /dev/null +++ b/src/blob.h @@ -0,0 +1,42 @@ +/* + * 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. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#ifndef _BLOB_H +#define _BLOB_H + +#include <cbor.h> +#include <stdlib.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +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 u_char *, size_t); +int fido_blob_append(fido_blob_t *, const u_char *, size_t); +void fido_blob_free(fido_blob_t **); +void fido_blob_reset(fido_blob_t *); +void fido_free_blob_array(fido_blob_array_t *); + +#ifdef __cplusplus +} /* extern "C" */ +#endif /* __cplusplus */ + +#endif /* !_BLOB_H */ diff --git a/src/buf.c b/src/buf.c new file mode 100644 index 0000000..42b6df1 --- /dev/null +++ b/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. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "fido.h" + +int +fido_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 +fido_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/src/cbor.c b/src/cbor.c new file mode 100644 index 0000000..ab99b34 --- /dev/null +++ b/src/cbor.c @@ -0,0 +1,1705 @@ +/* + * Copyright (c) 2018-2022 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. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <openssl/hmac.h> +#include <openssl/sha.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); + + fido_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); + fido_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); + } + + fido_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) { + fido_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) { + fido_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) { + fido_log_debug("%s: ctap_check_cbor", __func__); + return (-1); + } + if (f(v[i].key, v[i].value, arg) < 0) { + fido_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) { + fido_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) { + fido_log_debug("%s: iterator < 0 on i=%zu,%p", + __func__, i, (void *)v[i]); + return (-1); + } + + return (0); +} + +int +cbor_parse_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) { + fido_log_debug("%s: blob_len=%zu", __func__, blob_len); + r = FIDO_ERR_RX; + goto fail; + } + + if (blob[0] != FIDO_OK) { + fido_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) { + fido_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) { + fido_log_debug("%s: cbor type", __func__); + r = FIDO_ERR_RX_INVALID_CBOR; + goto fail; + } + + if (cbor_map_iter(item, arg, parser) < 0) { + fido_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) { + fido_log_debug("%s: dup", __func__); + return (-1); + } + + if (cbor_isa_bytestring(item) == false || + cbor_bytestring_is_definite(item) == false) { + fido_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) { + fido_log_debug("%s: dup", __func__); + return (-1); + } + + if (cbor_isa_string(item) == false || + cbor_string_is_definite(item) == false) { + fido_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) { + fido_log_debug("%s: cbor_build", __func__); + goto fail; + } + + if (!cbor_map_add(item, pair)) { + fido_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) { + fido_log_debug("%s: cbor_build", __func__); + goto fail; + } + + if (!cbor_map_add(item, pair)) { + fido_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) { + fido_log_debug("%s: cbor_build", __func__); + goto fail; + } + + if (!cbor_map_add(item, pair)) { + fido_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_uint8(cbor_item_t *item, const char *key, uint8_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_uint8(value)) == NULL) { + fido_log_debug("%s: cbor_build", __func__); + goto fail; + } + + if (!cbor_map_add(item, pair)) { + fido_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) { + fido_log_debug("%s: cbor_build", __func__); + goto fail; + } + + pair.value = arg; + + if (!cbor_map_add(item, pair)) { + fido_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, (uint8_t)(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) { + fido_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 * +cbor_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 * +cbor_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 * +cbor_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) { + fido_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 * +cbor_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 * +cbor_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 = cbor_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 * +cbor_encode_str_array(const fido_str_array_t *a) +{ + cbor_item_t *array = NULL; + cbor_item_t *entry = NULL; + + if ((array = cbor_new_definite_array(a->len)) == NULL) + goto fail; + + for (size_t i = 0; i < a->len; i++) { + if ((entry = cbor_build_string(a->ptr[i])) == NULL || + cbor_array_push(array, entry) == false) + goto fail; + cbor_decref(&entry); + } + + return (array); +fail: + if (entry != NULL) + cbor_decref(&entry); + if (array != NULL) + cbor_decref(&array); + + return (NULL); +} + +static int +cbor_encode_largeblob_key_ext(cbor_item_t *map) +{ + if (map == NULL || + cbor_add_bool(map, "largeBlobKey", FIDO_OPT_TRUE) < 0) + return (-1); + + return (0); +} + +cbor_item_t * +cbor_encode_cred_ext(const fido_cred_ext_t *ext, const fido_blob_t *blob) +{ + cbor_item_t *item = NULL; + size_t size = 0; + + if (ext->mask & FIDO_EXT_CRED_BLOB) + size++; + if (ext->mask & FIDO_EXT_HMAC_SECRET) + size++; + if (ext->mask & FIDO_EXT_CRED_PROTECT) + size++; + if (ext->mask & FIDO_EXT_LARGEBLOB_KEY) + size++; + if (ext->mask & FIDO_EXT_MINPINLEN) + size++; + + if (size == 0 || (item = cbor_new_definite_map(size)) == NULL) + return (NULL); + + if (ext->mask & FIDO_EXT_CRED_BLOB) { + if (cbor_add_bytestring(item, "credBlob", blob->ptr, + blob->len) < 0) { + cbor_decref(&item); + return (NULL); + } + } + if (ext->mask & FIDO_EXT_CRED_PROTECT) { + if (ext->prot < 0 || ext->prot > UINT8_MAX || + cbor_add_uint8(item, "credProtect", + (uint8_t)ext->prot) < 0) { + cbor_decref(&item); + return (NULL); + } + } + if (ext->mask & FIDO_EXT_HMAC_SECRET) { + if (cbor_add_bool(item, "hmac-secret", FIDO_OPT_TRUE) < 0) { + cbor_decref(&item); + return (NULL); + } + } + if (ext->mask & FIDO_EXT_LARGEBLOB_KEY) { + if (cbor_encode_largeblob_key_ext(item) < 0) { + cbor_decref(&item); + return (NULL); + } + } + if (ext->mask & FIDO_EXT_MINPINLEN) { + if (cbor_add_bool(item, "minPinLength", FIDO_OPT_TRUE) < 0) { + cbor_decref(&item); + return (NULL); + } + } + + return (item); +} + +cbor_item_t * +cbor_encode_cred_opt(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 * +cbor_encode_assert_opt(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 * +cbor_encode_pin_auth(const fido_dev_t *dev, const fido_blob_t *secret, + const fido_blob_t *data) +{ + const EVP_MD *md = NULL; + unsigned char dgst[SHA256_DIGEST_LENGTH]; + unsigned int dgst_len; + size_t outlen; + uint8_t prot; + fido_blob_t key; + + key.ptr = secret->ptr; + key.len = secret->len; + + if ((prot = fido_dev_get_pin_protocol(dev)) == 0) { + fido_log_debug("%s: fido_dev_get_pin_protocol", __func__); + return (NULL); + } + + /* select hmac portion of the shared secret */ + if (prot == CTAP_PIN_PROTOCOL2 && key.len > 32) + key.len = 32; + + if ((md = EVP_sha256()) == NULL || HMAC(md, key.ptr, + (int)key.len, data->ptr, data->len, dgst, + &dgst_len) == NULL || dgst_len != SHA256_DIGEST_LENGTH) + return (NULL); + + outlen = (prot == CTAP_PIN_PROTOCOL1) ? 16 : dgst_len; + + return (cbor_build_bytestring(dgst, outlen)); +} + +cbor_item_t * +cbor_encode_pin_opt(const fido_dev_t *dev) +{ + uint8_t prot; + + if ((prot = fido_dev_get_pin_protocol(dev)) == 0) { + fido_log_debug("%s: fido_dev_get_pin_protocol", __func__); + return (NULL); + } + + return (cbor_build_uint8(prot)); +} + +cbor_item_t * +cbor_encode_change_pin_auth(const fido_dev_t *dev, const fido_blob_t *secret, + const fido_blob_t *new_pin_enc, const fido_blob_t *pin_hash_enc) +{ + unsigned char dgst[SHA256_DIGEST_LENGTH]; + unsigned int dgst_len; + cbor_item_t *item = NULL; + const EVP_MD *md = NULL; + HMAC_CTX *ctx = NULL; + fido_blob_t key; + uint8_t prot; + size_t outlen; + + key.ptr = secret->ptr; + key.len = secret->len; + + if ((prot = fido_dev_get_pin_protocol(dev)) == 0) { + fido_log_debug("%s: fido_dev_get_pin_protocol", __func__); + goto fail; + } + + if (prot == CTAP_PIN_PROTOCOL2 && key.len > 32) + key.len = 32; + + 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, new_pin_enc->ptr, new_pin_enc->len) == 0 || + HMAC_Update(ctx, pin_hash_enc->ptr, pin_hash_enc->len) == 0 || + HMAC_Final(ctx, dgst, &dgst_len) == 0 || + dgst_len != SHA256_DIGEST_LENGTH) { + fido_log_debug("%s: HMAC", __func__); + goto fail; + } + + outlen = (prot == CTAP_PIN_PROTOCOL1) ? 16 : dgst_len; + + if ((item = cbor_build_bytestring(dgst, outlen)) == NULL) { + fido_log_debug("%s: cbor_build_bytestring", __func__); + goto fail; + } + +fail: + HMAC_CTX_free(ctx); + + return (item); +} + +static int +cbor_encode_hmac_secret_param(const fido_dev_t *dev, cbor_item_t *item, + const fido_blob_t *ecdh, const es256_pk_t *pk, const fido_blob_t *salt) +{ + cbor_item_t *param = NULL; + cbor_item_t *argv[4]; + struct cbor_pair pair; + fido_blob_t *enc = NULL; + uint8_t prot; + int r; + + memset(argv, 0, sizeof(argv)); + memset(&pair, 0, sizeof(pair)); + + if (item == NULL || ecdh == NULL || pk == NULL || salt->ptr == NULL) { + fido_log_debug("%s: ecdh=%p, pk=%p, salt->ptr=%p", __func__, + (const void *)ecdh, (const void *)pk, + (const void *)salt->ptr); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + if (salt->len != 32 && salt->len != 64) { + fido_log_debug("%s: salt->len=%zu", __func__, salt->len); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + if ((enc = fido_blob_new()) == NULL || + aes256_cbc_enc(dev, ecdh, salt, enc) < 0) { + fido_log_debug("%s: aes256_cbc_enc", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + if ((prot = fido_dev_get_pin_protocol(dev)) == 0) { + fido_log_debug("%s: fido_dev_get_pin_protocol", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + /* XXX not pin, but salt */ + if ((argv[0] = es256_pk_encode(pk, 1)) == NULL || + (argv[1] = fido_blob_encode(enc)) == NULL || + (argv[2] = cbor_encode_pin_auth(dev, ecdh, enc)) == NULL || + (prot != 1 && (argv[3] = cbor_build_uint8(prot)) == NULL)) { + fido_log_debug("%s: cbor encode", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + if ((param = cbor_flatten_vector(argv, nitems(argv))) == NULL) { + fido_log_debug("%s: cbor_flatten_vector", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + if ((pair.key = cbor_build_string("hmac-secret")) == NULL) { + fido_log_debug("%s: cbor_build", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + pair.value = param; + + if (!cbor_map_add(item, pair)) { + fido_log_debug("%s: cbor_map_add", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + r = FIDO_OK; + +fail: + cbor_vector_free(argv, nitems(argv)); + + if (param != NULL) + cbor_decref(¶m); + if (pair.key != NULL) + cbor_decref(&pair.key); + + fido_blob_free(&enc); + + return (r); +} + +cbor_item_t * +cbor_encode_assert_ext(fido_dev_t *dev, const fido_assert_ext_t *ext, + const fido_blob_t *ecdh, const es256_pk_t *pk) +{ + cbor_item_t *item = NULL; + size_t size = 0; + + if (ext->mask & FIDO_EXT_CRED_BLOB) + size++; + if (ext->mask & FIDO_EXT_HMAC_SECRET) + size++; + if (ext->mask & FIDO_EXT_LARGEBLOB_KEY) + size++; + if (size == 0 || (item = cbor_new_definite_map(size)) == NULL) + return (NULL); + + if (ext->mask & FIDO_EXT_CRED_BLOB) { + if (cbor_add_bool(item, "credBlob", FIDO_OPT_TRUE) < 0) { + cbor_decref(&item); + return (NULL); + } + } + if (ext->mask & FIDO_EXT_HMAC_SECRET) { + if (cbor_encode_hmac_secret_param(dev, item, ecdh, pk, + &ext->hmac_salt) < 0) { + cbor_decref(&item); + return (NULL); + } + } + if (ext->mask & FIDO_EXT_LARGEBLOB_KEY) { + if (cbor_encode_largeblob_key_ext(item) < 0) { + cbor_decref(&item); + return (NULL); + } + } + + return (item); +} + +int +cbor_decode_fmt(const cbor_item_t *item, char **fmt) +{ + char *type = NULL; + + if (cbor_string_copy(item, &type) < 0) { + fido_log_debug("%s: cbor_string_copy", __func__); + return (-1); + } + + if (strcmp(type, "packed") && strcmp(type, "fido-u2f") && + strcmp(type, "none") && strcmp(type, "tpm")) { + fido_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) { + fido_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) { + fido_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) { + fido_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) { + fido_log_debug("%s: invalid kty/crv", __func__); + return (-1); + } + break; + case COSE_ES384: + if (cose_key.kty != COSE_KTY_EC2 || + cose_key.crv != COSE_P384) { + fido_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) { + fido_log_debug("%s: invalid kty/crv", __func__); + return (-1); + } + break; + case COSE_RS256: + if (cose_key.kty != COSE_KTY_RSA) { + fido_log_debug("%s: invalid kty/crv", __func__); + return (-1); + } + break; + default: + fido_log_debug("%s: unknown alg %d", __func__, cose_key.alg); + + return (-1); + } + + *cose_alg = cose_key.alg; + + return (0); +} + +int +cbor_decode_pubkey(const cbor_item_t *item, int *type, void *key) +{ + if (get_cose_alg(item, type) < 0) { + fido_log_debug("%s: get_cose_alg", __func__); + return (-1); + } + + switch (*type) { + case COSE_ES256: + if (es256_pk_decode(item, key) < 0) { + fido_log_debug("%s: es256_pk_decode", __func__); + return (-1); + } + break; + case COSE_ES384: + if (es384_pk_decode(item, key) < 0) { + fido_log_debug("%s: es384_pk_decode", __func__); + return (-1); + } + break; + case COSE_RS256: + if (rs256_pk_decode(item, key) < 0) { + fido_log_debug("%s: rs256_pk_decode", __func__); + return (-1); + } + break; + case COSE_EDDSA: + if (eddsa_pk_decode(item, key) < 0) { + fido_log_debug("%s: eddsa_pk_decode", __func__); + return (-1); + } + break; + default: + fido_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; + + fido_log_xxd(*buf, *len, "%s", __func__); + + if (fido_buf_read(buf, len, &attcred->aaguid, + sizeof(attcred->aaguid)) < 0) { + fido_log_debug("%s: fido_buf_read aaguid", __func__); + return (-1); + } + + if (fido_buf_read(buf, len, &id_len, sizeof(id_len)) < 0) { + fido_log_debug("%s: fido_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); + + fido_log_debug("%s: attcred->id.len=%zu", __func__, attcred->id.len); + + if (fido_buf_read(buf, len, attcred->id.ptr, attcred->id.len) < 0) { + fido_log_debug("%s: fido_buf_read id", __func__); + return (-1); + } + + if ((item = cbor_load(*buf, *len, &cbor)) == NULL) { + fido_log_debug("%s: cbor_load", __func__); + goto fail; + } + + if (cbor_decode_pubkey(item, &attcred->type, &attcred->pubkey) < 0) { + fido_log_debug("%s: cbor_decode_pubkey", __func__); + goto fail; + } + + if (attcred->type != cose_alg) { + fido_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_cred_extension(const cbor_item_t *key, const cbor_item_t *val, void *arg) +{ + fido_cred_ext_t *authdata_ext = arg; + char *type = NULL; + int ok = -1; + + if (cbor_string_copy(key, &type) < 0) { + fido_log_debug("%s: cbor type", __func__); + ok = 0; /* ignore */ + goto out; + } + + if (strcmp(type, "hmac-secret") == 0) { + if (cbor_decode_bool(val, NULL) < 0) { + fido_log_debug("%s: cbor_decode_bool", __func__); + goto out; + } + if (cbor_ctrl_value(val) == CBOR_CTRL_TRUE) + authdata_ext->mask |= FIDO_EXT_HMAC_SECRET; + } else if (strcmp(type, "credProtect") == 0) { + if (cbor_isa_uint(val) == false || + cbor_int_get_width(val) != CBOR_INT_8) { + fido_log_debug("%s: cbor type", __func__); + goto out; + } + authdata_ext->mask |= FIDO_EXT_CRED_PROTECT; + authdata_ext->prot = cbor_get_uint8(val); + } else if (strcmp(type, "credBlob") == 0) { + if (cbor_decode_bool(val, NULL) < 0) { + fido_log_debug("%s: cbor_decode_bool", __func__); + goto out; + } + if (cbor_ctrl_value(val) == CBOR_CTRL_TRUE) + authdata_ext->mask |= FIDO_EXT_CRED_BLOB; + } else if (strcmp(type, "minPinLength") == 0) { + if (cbor_isa_uint(val) == false || + cbor_int_get_width(val) != CBOR_INT_8) { + fido_log_debug("%s: cbor type", __func__); + goto out; + } + authdata_ext->mask |= FIDO_EXT_MINPINLEN; + authdata_ext->minpinlen = cbor_get_uint8(val); + } + + ok = 0; +out: + free(type); + + return (ok); +} + +static int +decode_cred_extensions(const unsigned char **buf, size_t *len, + fido_cred_ext_t *authdata_ext) +{ + cbor_item_t *item = NULL; + struct cbor_load_result cbor; + int ok = -1; + + memset(authdata_ext, 0, sizeof(*authdata_ext)); + + fido_log_xxd(*buf, *len, "%s", __func__); + + if ((item = cbor_load(*buf, *len, &cbor)) == NULL) { + fido_log_debug("%s: cbor_load", __func__); + goto fail; + } + + if (cbor_isa_map(item) == false || + cbor_map_is_definite(item) == false || + cbor_map_iter(item, authdata_ext, decode_cred_extension) < 0) { + fido_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_assert_extension(const cbor_item_t *key, const cbor_item_t *val, + void *arg) +{ + fido_assert_extattr_t *authdata_ext = arg; + char *type = NULL; + int ok = -1; + + if (cbor_string_copy(key, &type) < 0) { + fido_log_debug("%s: cbor type", __func__); + ok = 0; /* ignore */ + goto out; + } + + if (strcmp(type, "hmac-secret") == 0) { + if (fido_blob_decode(val, &authdata_ext->hmac_secret_enc) < 0) { + fido_log_debug("%s: fido_blob_decode", __func__); + goto out; + } + authdata_ext->mask |= FIDO_EXT_HMAC_SECRET; + } else if (strcmp(type, "credBlob") == 0) { + if (fido_blob_decode(val, &authdata_ext->blob) < 0) { + fido_log_debug("%s: fido_blob_decode", __func__); + goto out; + } + authdata_ext->mask |= FIDO_EXT_CRED_BLOB; + } + + ok = 0; +out: + free(type); + + return (ok); +} + +static int +decode_assert_extensions(const unsigned char **buf, size_t *len, + fido_assert_extattr_t *authdata_ext) +{ + cbor_item_t *item = NULL; + struct cbor_load_result cbor; + int ok = -1; + + fido_log_xxd(*buf, *len, "%s", __func__); + + if ((item = cbor_load(*buf, *len, &cbor)) == NULL) { + fido_log_debug("%s: cbor_load", __func__); + goto fail; + } + + if (cbor_isa_map(item) == false || + cbor_map_is_definite(item) == false || + cbor_map_iter(item, authdata_ext, decode_assert_extension) < 0) { + fido_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 +cbor_decode_cred_authdata(const cbor_item_t *item, int cose_alg, + fido_blob_t *authdata_cbor, fido_authdata_t *authdata, + fido_attcred_t *attcred, fido_cred_ext_t *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) { + fido_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) { + fido_log_debug("%s: cbor_serialize_alloc", __func__); + return (-1); + } + + buf = cbor_bytestring_handle(item); + len = cbor_bytestring_length(item); + fido_log_xxd(buf, len, "%s", __func__); + + if (fido_buf_read(&buf, &len, authdata, sizeof(*authdata)) < 0) { + fido_log_debug("%s: fido_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_cred_extensions(&buf, &len, authdata_ext) < 0) + return (-1); + } + + /* XXX we should probably ensure that len == 0 at this point */ + + return (FIDO_OK); +} + +int +cbor_decode_assert_authdata(const cbor_item_t *item, fido_blob_t *authdata_cbor, + fido_authdata_t *authdata, fido_assert_extattr_t *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) { + fido_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) { + fido_log_debug("%s: cbor_serialize_alloc", __func__); + return (-1); + } + + buf = cbor_bytestring_handle(item); + len = cbor_bytestring_length(item); + + fido_log_debug("%s: buf=%p, len=%zu", __func__, (const void *)buf, len); + + if (fido_buf_read(&buf, &len, authdata, sizeof(*authdata)) < 0) { + fido_log_debug("%s: fido_buf_read", __func__); + return (-1); + } + + authdata->sigcount = be32toh(authdata->sigcount); + + if ((authdata->flags & CTAP_AUTHDATA_EXT_DATA) != 0) { + if (decode_assert_extensions(&buf, &len, authdata_ext) < 0) { + fido_log_debug("%s: decode_assert_extensions", + __func__); + return (-1); + } + } + + /* 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 (fido_blob_decode(item, x5c)); +} + +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) { + fido_log_debug("%s: cbor type", __func__); + ok = 0; /* ignore */ + goto out; + } + + if (!strcmp(name, "alg")) { + if (cbor_isa_negint(val) == false || + cbor_get_int(val) > UINT16_MAX) { + fido_log_debug("%s: alg", __func__); + goto out; + } + attstmt->alg = -(int)cbor_get_int(val) - 1; + if (attstmt->alg != COSE_ES256 && attstmt->alg != COSE_ES384 && + attstmt->alg != COSE_RS256 && attstmt->alg != COSE_EDDSA && + attstmt->alg != COSE_RS1) { + fido_log_debug("%s: unsupported attstmt->alg=%d", + __func__, attstmt->alg); + goto out; + } + } else if (!strcmp(name, "sig")) { + if (fido_blob_decode(val, &attstmt->sig) < 0) { + fido_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) { + fido_log_debug("%s: x5c", __func__); + goto out; + } + } else if (!strcmp(name, "certInfo")) { + if (fido_blob_decode(val, &attstmt->certinfo) < 0) { + fido_log_debug("%s: certinfo", __func__); + goto out; + } + } else if (!strcmp(name, "pubArea")) { + if (fido_blob_decode(val, &attstmt->pubarea) < 0) { + fido_log_debug("%s: pubarea", __func__); + goto out; + } + } + + ok = 0; +out: + free(name); + + return (ok); +} + +int +cbor_decode_attstmt(const cbor_item_t *item, fido_attstmt_t *attstmt) +{ + size_t alloc_len; + + if (cbor_isa_map(item) == false || + cbor_map_is_definite(item) == false || + cbor_map_iter(item, attstmt, decode_attstmt_entry) < 0) { + fido_log_debug("%s: cbor type", __func__); + return (-1); + } + + if (attstmt->cbor.ptr != NULL || + (attstmt->cbor.len = cbor_serialize_alloc(item, + &attstmt->cbor.ptr, &alloc_len)) == 0) { + fido_log_debug("%s: cbor_serialize_alloc", __func__); + return (-1); + } + + return (0); +} + +int +cbor_decode_uint64(const cbor_item_t *item, uint64_t *n) +{ + if (cbor_isa_uint(item) == false) { + fido_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) { + fido_log_debug("%s: cbor type", __func__); + ok = 0; /* ignore */ + goto out; + } + + if (!strcmp(name, "id")) + if (fido_blob_decode(val, id) < 0) { + fido_log_debug("%s: cbor_bytestring_copy", __func__); + goto out; + } + + ok = 0; +out: + free(name); + + return (ok); +} + +int +cbor_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) { + fido_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) { + fido_log_debug("%s: cbor type", __func__); + ok = 0; /* ignore */ + goto out; + } + + if (!strcmp(name, "icon")) { + if (cbor_string_copy(val, &user->icon) < 0) { + fido_log_debug("%s: icon", __func__); + goto out; + } + } else if (!strcmp(name, "name")) { + if (cbor_string_copy(val, &user->name) < 0) { + fido_log_debug("%s: name", __func__); + goto out; + } + } else if (!strcmp(name, "displayName")) { + if (cbor_string_copy(val, &user->display_name) < 0) { + fido_log_debug("%s: display_name", __func__); + goto out; + } + } else if (!strcmp(name, "id")) { + if (fido_blob_decode(val, &user->id) < 0) { + fido_log_debug("%s: id", __func__); + goto out; + } + } + + ok = 0; +out: + free(name); + + return (ok); +} + +int +cbor_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) { + fido_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) { + fido_log_debug("%s: cbor type", __func__); + ok = 0; /* ignore */ + goto out; + } + + if (!strcmp(name, "id")) { + if (cbor_string_copy(val, &rp->id) < 0) { + fido_log_debug("%s: id", __func__); + goto out; + } + } else if (!strcmp(name, "name")) { + if (cbor_string_copy(val, &rp->name) < 0) { + fido_log_debug("%s: name", __func__); + goto out; + } + } + + ok = 0; +out: + free(name); + + return (ok); +} + +int +cbor_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) { + fido_log_debug("%s: cbor type", __func__); + return (-1); + } + + return (0); +} + +int +cbor_decode_bool(const cbor_item_t *item, bool *v) +{ + if (cbor_isa_float_ctrl(item) == false || + cbor_float_get_width(item) != CBOR_FLOAT_0 || + cbor_is_bool(item) == false) { + fido_log_debug("%s: cbor type", __func__); + return (-1); + } + + if (v != NULL) + *v = cbor_ctrl_value(item) == CBOR_CTRL_TRUE; + + return (0); +} + +cbor_item_t * +cbor_build_uint(const uint64_t value) +{ + if (value <= UINT8_MAX) + return cbor_build_uint8((uint8_t)value); + else if (value <= UINT16_MAX) + return cbor_build_uint16((uint16_t)value); + else if (value <= UINT32_MAX) + return cbor_build_uint32((uint32_t)value); + + return cbor_build_uint64(value); +} + +int +cbor_array_append(cbor_item_t **array, cbor_item_t *item) +{ + cbor_item_t **v, *ret; + size_t n; + + if ((v = cbor_array_handle(*array)) == NULL || + (n = cbor_array_size(*array)) == SIZE_MAX || + (ret = cbor_new_definite_array(n + 1)) == NULL) + return -1; + for (size_t i = 0; i < n; i++) { + if (cbor_array_push(ret, v[i]) == 0) { + cbor_decref(&ret); + return -1; + } + } + if (cbor_array_push(ret, item) == 0) { + cbor_decref(&ret); + return -1; + } + cbor_decref(array); + *array = ret; + + return 0; +} + +int +cbor_array_drop(cbor_item_t **array, size_t idx) +{ + cbor_item_t **v, *ret; + size_t n; + + if ((v = cbor_array_handle(*array)) == NULL || + (n = cbor_array_size(*array)) == 0 || idx >= n || + (ret = cbor_new_definite_array(n - 1)) == NULL) + return -1; + for (size_t i = 0; i < n; i++) { + if (i != idx && cbor_array_push(ret, v[i]) == 0) { + cbor_decref(&ret); + return -1; + } + } + cbor_decref(array); + *array = ret; + + return 0; +} diff --git a/src/compress.c b/src/compress.c new file mode 100644 index 0000000..3be6fd5 --- /dev/null +++ b/src/compress.c @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2020-2022 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. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <zlib.h> +#include "fido.h" + +#define BOUND (1024UL * 1024UL) + +/* zlib inflate (raw + headers) */ +static int +rfc1950_inflate(fido_blob_t *out, const fido_blob_t *in, size_t origsiz) +{ + u_long ilen, olen; + int z; + + memset(out, 0, sizeof(*out)); + + if (in->len > ULONG_MAX || (ilen = (u_long)in->len) > BOUND || + origsiz > ULONG_MAX || (olen = (u_long)origsiz) > BOUND) { + fido_log_debug("%s: in->len=%zu, origsiz=%zu", __func__, + in->len, origsiz); + return FIDO_ERR_INVALID_ARGUMENT; + } + + if ((out->ptr = calloc(1, olen)) == NULL) + return FIDO_ERR_INTERNAL; + out->len = olen; + + if ((z = uncompress(out->ptr, &olen, in->ptr, ilen)) != Z_OK || + olen > SIZE_MAX || olen != out->len) { + fido_log_debug("%s: uncompress: %d, olen=%lu, out->len=%zu", + __func__, z, olen, out->len); + fido_blob_reset(out); + return FIDO_ERR_COMPRESS; + } + + return FIDO_OK; +} + +/* raw inflate */ +static int +rfc1951_inflate(fido_blob_t *out, const fido_blob_t *in, size_t origsiz) +{ + z_stream zs; + u_int ilen, olen; + int r, z; + + memset(&zs, 0, sizeof(zs)); + memset(out, 0, sizeof(*out)); + + if (in->len > UINT_MAX || (ilen = (u_int)in->len) > BOUND || + origsiz > UINT_MAX || (olen = (u_int)origsiz) > BOUND) { + fido_log_debug("%s: in->len=%zu, origsiz=%zu", __func__, + in->len, origsiz); + return FIDO_ERR_INVALID_ARGUMENT; + } + if ((z = inflateInit2(&zs, -MAX_WBITS)) != Z_OK) { + fido_log_debug("%s: inflateInit2: %d", __func__, z); + return FIDO_ERR_COMPRESS; + } + + if ((out->ptr = calloc(1, olen)) == NULL) { + r = FIDO_ERR_INTERNAL; + goto fail; + } + out->len = olen; + zs.next_in = in->ptr; + zs.avail_in = ilen; + zs.next_out = out->ptr; + zs.avail_out = olen; + + if ((z = inflate(&zs, Z_FINISH)) != Z_STREAM_END) { + fido_log_debug("%s: inflate: %d", __func__, z); + r = FIDO_ERR_COMPRESS; + goto fail; + } + if (zs.avail_out != 0) { + fido_log_debug("%s: %u != 0", __func__, zs.avail_out); + r = FIDO_ERR_COMPRESS; + goto fail; + } + + r = FIDO_OK; +fail: + if ((z = inflateEnd(&zs)) != Z_OK) { + fido_log_debug("%s: inflateEnd: %d", __func__, z); + r = FIDO_ERR_COMPRESS; + } + if (r != FIDO_OK) + fido_blob_reset(out); + + return r; +} + +/* raw deflate */ +static int +rfc1951_deflate(fido_blob_t *out, const fido_blob_t *in) +{ + z_stream zs; + u_int ilen, olen; + int r, z; + + memset(&zs, 0, sizeof(zs)); + memset(out, 0, sizeof(*out)); + + if (in->len > UINT_MAX || (ilen = (u_int)in->len) > BOUND) { + fido_log_debug("%s: in->len=%zu", __func__, in->len); + return FIDO_ERR_INVALID_ARGUMENT; + } + if ((z = deflateInit2(&zs, Z_DEFAULT_COMPRESSION, Z_DEFLATED, + -MAX_WBITS, 8, Z_DEFAULT_STRATEGY)) != Z_OK) { + fido_log_debug("%s: deflateInit2: %d", __func__, z); + return FIDO_ERR_COMPRESS; + } + + olen = BOUND; + if ((out->ptr = calloc(1, olen)) == NULL) { + r = FIDO_ERR_INTERNAL; + goto fail; + } + out->len = olen; + zs.next_in = in->ptr; + zs.avail_in = ilen; + zs.next_out = out->ptr; + zs.avail_out = olen; + + if ((z = deflate(&zs, Z_FINISH)) != Z_STREAM_END) { + fido_log_debug("%s: inflate: %d", __func__, z); + r = FIDO_ERR_COMPRESS; + goto fail; + } + if (zs.avail_out >= out->len) { + fido_log_debug("%s: %u > %zu", __func__, zs.avail_out, + out->len); + r = FIDO_ERR_COMPRESS; + goto fail; + } + out->len -= zs.avail_out; + + r = FIDO_OK; +fail: + if ((z = deflateEnd(&zs)) != Z_OK) { + fido_log_debug("%s: deflateEnd: %d", __func__, z); + r = FIDO_ERR_COMPRESS; + } + if (r != FIDO_OK) + fido_blob_reset(out); + + return r; +} + +int +fido_compress(fido_blob_t *out, const fido_blob_t *in) +{ + return rfc1951_deflate(out, in); +} + +int +fido_uncompress(fido_blob_t *out, const fido_blob_t *in, size_t origsiz) +{ + if (rfc1950_inflate(out, in, origsiz) == FIDO_OK) + return FIDO_OK; /* backwards compat with libfido2 < 1.11 */ + return rfc1951_inflate(out, in, origsiz); +} diff --git a/src/config.c b/src/config.c new file mode 100644 index 0000000..5302e11 --- /dev/null +++ b/src/config.c @@ -0,0 +1,235 @@ +/* + * Copyright (c) 2020-2022 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. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "fido.h" +#include "fido/config.h" +#include "fido/es256.h" + +#define CMD_ENABLE_ENTATTEST 0x01 +#define CMD_TOGGLE_ALWAYS_UV 0x02 +#define CMD_SET_PIN_MINLEN 0x03 + +static int +config_prepare_hmac(uint8_t subcmd, const cbor_item_t *item, fido_blob_t *hmac) +{ + uint8_t prefix[32 + 2 * sizeof(uint8_t)], cbor[128]; + size_t cbor_len = 0; + + memset(prefix, 0xff, sizeof(prefix)); + prefix[sizeof(prefix) - 2] = CTAP_CBOR_CONFIG; + prefix[sizeof(prefix) - 1] = subcmd; + + if (item != NULL) { + if ((cbor_len = cbor_serialize(item, cbor, sizeof(cbor))) == 0) { + fido_log_debug("%s: cbor_serialize", __func__); + return -1; + } + } + if ((hmac->ptr = malloc(cbor_len + sizeof(prefix))) == NULL) { + fido_log_debug("%s: malloc", __func__); + return -1; + } + memcpy(hmac->ptr, prefix, sizeof(prefix)); + memcpy(hmac->ptr + sizeof(prefix), cbor, cbor_len); + hmac->len = cbor_len + sizeof(prefix); + + return 0; +} + +static int +config_tx(fido_dev_t *dev, uint8_t subcmd, cbor_item_t **paramv, size_t paramc, + const char *pin, int *ms) +{ + cbor_item_t *argv[4]; + es256_pk_t *pk = NULL; + fido_blob_t *ecdh = NULL, f, hmac; + const uint8_t cmd = CTAP_CBOR_CONFIG; + 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(subcmd)) == NULL) { + fido_log_debug("%s: cbor encode", __func__); + goto fail; + } + + /* subCommandParams */ + if (paramc != 0 && + (argv[1] = cbor_flatten_vector(paramv, paramc)) == NULL) { + fido_log_debug("%s: cbor_flatten_vector", __func__); + goto fail; + } + + /* pinProtocol, pinAuth */ + if (pin != NULL || + (fido_dev_supports_permissions(dev) && fido_dev_has_uv(dev))) { + if (config_prepare_hmac(subcmd, argv[1], &hmac) < 0) { + fido_log_debug("%s: config_prepare_hmac", __func__); + goto fail; + } + if ((r = fido_do_ecdh(dev, &pk, &ecdh, ms)) != FIDO_OK) { + fido_log_debug("%s: fido_do_ecdh", __func__); + goto fail; + } + if ((r = cbor_add_uv_params(dev, cmd, &hmac, pk, ecdh, pin, + NULL, &argv[3], &argv[2], ms)) != FIDO_OK) { + fido_log_debug("%s: cbor_add_uv_params", __func__); + goto fail; + } + } + + /* framing and transmission */ + if (cbor_build_frame(cmd, argv, nitems(argv), &f) < 0 || + fido_tx(dev, CTAP_CMD_CBOR, f.ptr, f.len, ms) < 0) { + fido_log_debug("%s: fido_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 int +config_enable_entattest_wait(fido_dev_t *dev, const char *pin, int *ms) +{ + int r; + + if ((r = config_tx(dev, CMD_ENABLE_ENTATTEST, NULL, 0, pin, + ms)) != FIDO_OK) + return r; + + return fido_rx_cbor_status(dev, ms); +} + +int +fido_dev_enable_entattest(fido_dev_t *dev, const char *pin) +{ + int ms = dev->timeout_ms; + + return (config_enable_entattest_wait(dev, pin, &ms)); +} + +static int +config_toggle_always_uv_wait(fido_dev_t *dev, const char *pin, int *ms) +{ + int r; + + if ((r = config_tx(dev, CMD_TOGGLE_ALWAYS_UV, NULL, 0, pin, + ms)) != FIDO_OK) + return r; + + return (fido_rx_cbor_status(dev, ms)); +} + +int +fido_dev_toggle_always_uv(fido_dev_t *dev, const char *pin) +{ + int ms = dev->timeout_ms; + + return config_toggle_always_uv_wait(dev, pin, &ms); +} + +static int +config_pin_minlen_tx(fido_dev_t *dev, size_t len, bool force, + const fido_str_array_t *rpid, const char *pin, int *ms) +{ + cbor_item_t *argv[3]; + int r; + + memset(argv, 0, sizeof(argv)); + + if ((rpid == NULL && len == 0 && !force) || len > UINT8_MAX) { + r = FIDO_ERR_INVALID_ARGUMENT; + goto fail; + } + if (len && (argv[0] = cbor_build_uint8((uint8_t)len)) == NULL) { + fido_log_debug("%s: cbor_encode_uint8", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + if (rpid != NULL && (argv[1] = cbor_encode_str_array(rpid)) == NULL) { + fido_log_debug("%s: cbor_encode_str_array", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + if (force && (argv[2] = cbor_build_bool(true)) == NULL) { + fido_log_debug("%s: cbor_build_bool", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + if ((r = config_tx(dev, CMD_SET_PIN_MINLEN, argv, nitems(argv), + pin, ms)) != FIDO_OK) { + fido_log_debug("%s: config_tx", __func__); + goto fail; + } + +fail: + cbor_vector_free(argv, nitems(argv)); + + return r; +} + +static int +config_pin_minlen(fido_dev_t *dev, size_t len, bool force, + const fido_str_array_t *rpid, const char *pin, int *ms) +{ + int r; + + if ((r = config_pin_minlen_tx(dev, len, force, rpid, pin, + ms)) != FIDO_OK) + return r; + + return fido_rx_cbor_status(dev, ms); +} + +int +fido_dev_set_pin_minlen(fido_dev_t *dev, size_t len, const char *pin) +{ + int ms = dev->timeout_ms; + + return config_pin_minlen(dev, len, false, NULL, pin, &ms); +} + +int +fido_dev_force_pin_change(fido_dev_t *dev, const char *pin) +{ + int ms = dev->timeout_ms; + + return config_pin_minlen(dev, 0, true, NULL, pin, &ms); +} + +int +fido_dev_set_pin_minlen_rpid(fido_dev_t *dev, const char * const *rpid, + size_t n, const char *pin) +{ + fido_str_array_t sa; + int ms = dev->timeout_ms; + int r; + + memset(&sa, 0, sizeof(sa)); + if (fido_str_array_pack(&sa, rpid, n) < 0) { + fido_log_debug("%s: fido_str_array_pack", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + r = config_pin_minlen(dev, 0, false, &sa, pin, &ms); +fail: + fido_str_array_free(&sa); + + return r; +} diff --git a/src/cred.c b/src/cred.c new file mode 100644 index 0000000..188afe4 --- /dev/null +++ b/src/cred.c @@ -0,0 +1,1222 @@ +/* + * Copyright (c) 2018-2022 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. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <openssl/sha.h> +#include <openssl/x509.h> + +#include "fido.h" +#include "fido/es256.h" + +#ifndef FIDO_MAXMSG_CRED +#define FIDO_MAXMSG_CRED 4096 +#endif + +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) { + fido_log_debug("%s: cbor type", __func__); + return (0); /* ignore */ + } + + switch (cbor_get_uint8(key)) { + case 1: /* fmt */ + return (cbor_decode_fmt(val, &cred->fmt)); + case 2: /* authdata */ + if (fido_blob_decode(val, &cred->authdata_raw) < 0) { + fido_log_debug("%s: fido_blob_decode", __func__); + return (-1); + } + return (cbor_decode_cred_authdata(val, cred->type, + &cred->authdata_cbor, &cred->authdata, &cred->attcred, + &cred->authdata_ext)); + case 3: /* attestation statement */ + return (cbor_decode_attstmt(val, &cred->attstmt)); + case 5: /* large blob key */ + return (fido_blob_decode(val, &cred->largeblob_key)); + default: /* ignore */ + fido_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, + int *ms) +{ + fido_blob_t f; + fido_blob_t *ecdh = NULL; + fido_opt_t uv = cred->uv; + es256_pk_t *pk = NULL; + cbor_item_t *argv[9]; + const uint8_t cmd = CTAP_CBOR_MAKECRED; + int r; + + memset(&f, 0, sizeof(f)); + memset(argv, 0, sizeof(argv)); + + if (cred->cdh.ptr == NULL || cred->type == 0) { + fido_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] = cbor_encode_rp_entity(&cred->rp)) == NULL || + (argv[2] = cbor_encode_user_entity(&cred->user)) == NULL || + (argv[3] = cbor_encode_pubkey_param(cred->type)) == NULL) { + fido_log_debug("%s: cbor encode", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + /* excluded credentials */ + if (cred->excl.len) + if ((argv[4] = cbor_encode_pubkey_list(&cred->excl)) == NULL) { + fido_log_debug("%s: cbor_encode_pubkey_list", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + /* extensions */ + if (cred->ext.mask) + if ((argv[5] = cbor_encode_cred_ext(&cred->ext, + &cred->blob)) == NULL) { + fido_log_debug("%s: cbor_encode_cred_ext", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + /* user verification */ + if (pin != NULL || (uv == FIDO_OPT_TRUE && + fido_dev_supports_permissions(dev))) { + if ((r = fido_do_ecdh(dev, &pk, &ecdh, ms)) != FIDO_OK) { + fido_log_debug("%s: fido_do_ecdh", __func__); + goto fail; + } + if ((r = cbor_add_uv_params(dev, cmd, &cred->cdh, pk, ecdh, + pin, cred->rp.id, &argv[7], &argv[8], ms)) != FIDO_OK) { + fido_log_debug("%s: cbor_add_uv_params", __func__); + goto fail; + } + uv = FIDO_OPT_OMIT; + } + + /* options */ + if (cred->rk != FIDO_OPT_OMIT || uv != FIDO_OPT_OMIT) + if ((argv[6] = cbor_encode_cred_opt(cred->rk, uv)) == NULL) { + fido_log_debug("%s: cbor_encode_cred_opt", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + /* framing and transmission */ + if (cbor_build_frame(cmd, argv, nitems(argv), &f) < 0 || + fido_tx(dev, CTAP_CMD_CBOR, f.ptr, f.len, ms) < 0) { + fido_log_debug("%s: fido_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) +{ + unsigned char *reply; + int reply_len; + int r; + + fido_cred_reset_rx(cred); + + if ((reply = malloc(FIDO_MAXMSG_CRED)) == NULL) { + r = FIDO_ERR_INTERNAL; + goto fail; + } + + if ((reply_len = fido_rx(dev, CTAP_CMD_CBOR, reply, FIDO_MAXMSG_CRED, + ms)) < 0) { + fido_log_debug("%s: fido_rx", __func__); + r = FIDO_ERR_RX; + goto fail; + } + + if ((r = cbor_parse_reply(reply, (size_t)reply_len, cred, + parse_makecred_reply)) != FIDO_OK) { + fido_log_debug("%s: parse_makecred_reply", __func__); + goto fail; + } + + if (cred->fmt == NULL || fido_blob_is_empty(&cred->authdata_cbor) || + fido_blob_is_empty(&cred->attcred.id)) { + r = FIDO_ERR_INVALID_CBOR; + goto fail; + } + + r = FIDO_OK; +fail: + free(reply); + + if (r != FIDO_OK) + fido_cred_reset_rx(cred); + + return (r); +} + +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, ms)) != 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) +{ + int ms = dev->timeout_ms; + +#ifdef USE_WINHELLO + if (dev->flags & FIDO_DEV_WINHELLO) + return (fido_winhello_make_cred(dev, cred, pin, ms)); +#endif + if (fido_dev_is_fido2(dev) == false) { + if (pin != NULL || cred->rk == FIDO_OPT_TRUE || + cred->ext.mask != 0) + return (FIDO_ERR_UNSUPPORTED_OPTION); + return (u2f_register(dev, cred, &ms)); + } + + return (fido_dev_make_cred_wait(dev, cred, pin, &ms)); +} + +static int +check_extensions(const fido_cred_ext_t *authdata_ext, + const fido_cred_ext_t *ext) +{ + fido_cred_ext_t tmp; + + /* XXX: largeBlobKey is not part of the extensions map */ + memcpy(&tmp, ext, sizeof(tmp)); + tmp.mask &= ~FIDO_EXT_LARGEBLOB_KEY; + + return (timingsafe_bcmp(authdata_ext, &tmp, sizeof(*authdata_ext))); +} + +int +fido_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) { + fido_log_debug("%s: sha256", __func__); + return (-1); + } + + return (timingsafe_bcmp(expected_hash, obtained_hash, + SHA256_DIGEST_LENGTH)); +} + +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 */ + const EVP_MD *md = NULL; + EVP_MD_CTX *ctx = NULL; + int ok = -1; + + if (dgst->len < SHA256_DIGEST_LENGTH || + (md = EVP_sha256()) == NULL || + (ctx = EVP_MD_CTX_new()) == NULL || + EVP_DigestInit_ex(ctx, md, NULL) != 1 || + EVP_DigestUpdate(ctx, &zero, sizeof(zero)) != 1 || + EVP_DigestUpdate(ctx, rp_id, rp_id_len) != 1 || + EVP_DigestUpdate(ctx, clientdata->ptr, clientdata->len) != 1 || + EVP_DigestUpdate(ctx, id->ptr, id->len) != 1 || + EVP_DigestUpdate(ctx, &four, sizeof(four)) != 1 || + EVP_DigestUpdate(ctx, pk->x, sizeof(pk->x)) != 1 || + EVP_DigestUpdate(ctx, pk->y, sizeof(pk->y)) != 1 || + EVP_DigestFinal_ex(ctx, dgst->ptr, NULL) != 1) { + fido_log_debug("%s: sha256", __func__); + goto fail; + } + dgst->len = SHA256_DIGEST_LENGTH; + + ok = 0; +fail: + EVP_MD_CTX_free(ctx); + + return (ok); +} + +static int +verify_attstmt(const fido_blob_t *dgst, const fido_attstmt_t *attstmt) +{ + BIO *rawcert = NULL; + X509 *cert = NULL; + EVP_PKEY *pkey = NULL; + int ok = -1; + + /* openssl needs ints */ + if (attstmt->x5c.len > INT_MAX) { + fido_log_debug("%s: x5c.len=%zu", __func__, attstmt->x5c.len); + return (-1); + } + + /* fetch key from x509 */ + if ((rawcert = BIO_new_mem_buf(attstmt->x5c.ptr, + (int)attstmt->x5c.len)) == NULL || + (cert = d2i_X509_bio(rawcert, NULL)) == NULL || + (pkey = X509_get_pubkey(cert)) == NULL) { + fido_log_debug("%s: x509 key", __func__); + goto fail; + } + + switch (attstmt->alg) { + case COSE_UNSPEC: + case COSE_ES256: + ok = es256_verify_sig(dgst, pkey, &attstmt->sig); + break; + case COSE_ES384: + ok = es384_verify_sig(dgst, pkey, &attstmt->sig); + break; + case COSE_RS256: + ok = rs256_verify_sig(dgst, pkey, &attstmt->sig); + break; + case COSE_RS1: + ok = rs1_verify_sig(dgst, pkey, &attstmt->sig); + break; + case COSE_EDDSA: + ok = eddsa_verify_sig(dgst, pkey, &attstmt->sig); + break; + default: + fido_log_debug("%s: unknown alg %d", __func__, attstmt->alg); + break; + } + +fail: + BIO_free(rawcert); + X509_free(cert); + EVP_PKEY_free(pkey); + + return (ok); +} + +int +fido_cred_verify(const fido_cred_t *cred) +{ + unsigned char buf[1024]; /* XXX */ + fido_blob_t dgst; + int cose_alg; + 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) { + fido_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 (fido_check_rp_id(cred->rp.id, cred->authdata.rp_id_hash) != 0) { + fido_log_debug("%s: fido_check_rp_id", __func__); + r = FIDO_ERR_INVALID_PARAM; + goto out; + } + + if (fido_check_flags(cred->authdata.flags, FIDO_OPT_TRUE, + cred->uv) < 0) { + fido_log_debug("%s: fido_check_flags", __func__); + r = FIDO_ERR_INVALID_PARAM; + goto out; + } + + if (check_extensions(&cred->authdata_ext, &cred->ext) != 0) { + fido_log_debug("%s: check_extensions", __func__); + r = FIDO_ERR_INVALID_PARAM; + goto out; + } + + if ((cose_alg = cred->attstmt.alg) == COSE_UNSPEC) + cose_alg = COSE_ES256; /* backwards compat */ + + if (!strcmp(cred->fmt, "packed")) { + if (fido_get_signed_hash(cose_alg, &dgst, &cred->cdh, + &cred->authdata_cbor) < 0) { + fido_log_debug("%s: fido_get_signed_hash", __func__); + r = FIDO_ERR_INTERNAL; + goto out; + } + } else if (!strcmp(cred->fmt, "fido-u2f")) { + 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) { + fido_log_debug("%s: get_signed_hash_u2f", __func__); + r = FIDO_ERR_INTERNAL; + goto out; + } + } else if (!strcmp(cred->fmt, "tpm")) { + if (fido_get_signed_hash_tpm(&dgst, &cred->cdh, + &cred->authdata_raw, &cred->attstmt, &cred->attcred) < 0) { + fido_log_debug("%s: fido_get_signed_hash_tpm", __func__); + r = FIDO_ERR_INTERNAL; + goto out; + } + } else { + fido_log_debug("%s: unknown fmt %s", __func__, cred->fmt); + r = FIDO_ERR_INVALID_ARGUMENT; + goto out; + } + + if (verify_attstmt(&dgst, &cred->attstmt) < 0) { + fido_log_debug("%s: verify_attstmt", __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[1024]; /* XXX */ + 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) { + fido_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 (fido_check_rp_id(cred->rp.id, cred->authdata.rp_id_hash) != 0) { + fido_log_debug("%s: fido_check_rp_id", __func__); + r = FIDO_ERR_INVALID_PARAM; + goto out; + } + + if (fido_check_flags(cred->authdata.flags, FIDO_OPT_TRUE, + cred->uv) < 0) { + fido_log_debug("%s: fido_check_flags", __func__); + r = FIDO_ERR_INVALID_PARAM; + goto out; + } + + if (check_extensions(&cred->authdata_ext, &cred->ext) != 0) { + fido_log_debug("%s: check_extensions", __func__); + r = FIDO_ERR_INVALID_PARAM; + goto out; + } + + if (!strcmp(cred->fmt, "packed")) { + if (fido_get_signed_hash(cred->attcred.type, &dgst, &cred->cdh, + &cred->authdata_cbor) < 0) { + fido_log_debug("%s: fido_get_signed_hash", __func__); + r = FIDO_ERR_INTERNAL; + goto out; + } + } else if (!strcmp(cred->fmt, "fido-u2f")) { + 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) { + fido_log_debug("%s: get_signed_hash_u2f", __func__); + r = FIDO_ERR_INTERNAL; + goto out; + } + } else { + fido_log_debug("%s: unknown fmt %s", __func__, cred->fmt); + r = FIDO_ERR_INVALID_ARGUMENT; + goto out; + } + + switch (cred->attcred.type) { + case COSE_ES256: + ok = es256_pk_verify_sig(&dgst, &cred->attcred.pubkey.es256, + &cred->attstmt.sig); + break; + case COSE_ES384: + ok = es384_pk_verify_sig(&dgst, &cred->attcred.pubkey.es384, + &cred->attstmt.sig); + break; + case COSE_RS256: + ok = rs256_pk_verify_sig(&dgst, &cred->attcred.pubkey.rs256, + &cred->attstmt.sig); + break; + case COSE_EDDSA: + ok = eddsa_pk_verify_sig(&dgst, &cred->attcred.pubkey.eddsa, + &cred->attstmt.sig); + break; + default: + fido_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) +{ + fido_blob_reset(&cred->authdata_cbor); + fido_blob_reset(&cred->authdata_raw); + fido_blob_reset(&cred->attcred.id); + + memset(&cred->authdata_ext, 0, sizeof(cred->authdata_ext)); + memset(&cred->authdata, 0, sizeof(cred->authdata)); + memset(&cred->attcred, 0, sizeof(cred->attcred)); +} + +static void +fido_cred_clean_attstmt(fido_attstmt_t *attstmt) +{ + fido_blob_reset(&attstmt->certinfo); + fido_blob_reset(&attstmt->pubarea); + fido_blob_reset(&attstmt->cbor); + fido_blob_reset(&attstmt->x5c); + fido_blob_reset(&attstmt->sig); + + memset(attstmt, 0, sizeof(*attstmt)); +} + +void +fido_cred_reset_tx(fido_cred_t *cred) +{ + fido_blob_reset(&cred->cd); + fido_blob_reset(&cred->cdh); + fido_blob_reset(&cred->user.id); + fido_blob_reset(&cred->blob); + + free(cred->rp.id); + free(cred->rp.name); + free(cred->user.icon); + free(cred->user.name); + free(cred->user.display_name); + fido_free_blob_array(&cred->excl); + + memset(&cred->rp, 0, sizeof(cred->rp)); + memset(&cred->user, 0, sizeof(cred->user)); + memset(&cred->excl, 0, sizeof(cred->excl)); + memset(&cred->ext, 0, sizeof(cred->ext)); + + cred->type = 0; + cred->rk = FIDO_OPT_OMIT; + cred->uv = FIDO_OPT_OMIT; +} + +void +fido_cred_reset_rx(fido_cred_t *cred) +{ + free(cred->fmt); + cred->fmt = NULL; + fido_cred_clean_authdata(cred); + fido_cred_clean_attstmt(&cred->attstmt); + fido_blob_reset(&cred->largeblob_key); +} + +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_ERR_INVALID_ARGUMENT; + + fido_cred_clean_authdata(cred); + + if (ptr == NULL || len == 0) + goto fail; + + if ((item = cbor_load(ptr, len, &cbor)) == NULL) { + fido_log_debug("%s: cbor_load", __func__); + goto fail; + } + + if (fido_blob_decode(item, &cred->authdata_raw) < 0) { + fido_log_debug("%s: fido_blob_decode", __func__); + goto fail; + } + + if (cbor_decode_cred_authdata(item, cred->type, &cred->authdata_cbor, + &cred->authdata, &cred->attcred, &cred->authdata_ext) < 0) { + fido_log_debug("%s: cbor_decode_cred_authdata", __func__); + 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_ERR_INVALID_ARGUMENT; + + fido_cred_clean_authdata(cred); + + if (ptr == NULL || len == 0) + goto fail; + + if (fido_blob_set(&cred->authdata_raw, ptr, len) < 0) { + fido_log_debug("%s: fido_blob_set", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + if ((item = cbor_build_bytestring(ptr, len)) == NULL) { + fido_log_debug("%s: cbor_build_bytestring", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + if (cbor_decode_cred_authdata(item, cred->type, &cred->authdata_cbor, + &cred->authdata, &cred->attcred, &cred->authdata_ext) < 0) { + fido_log_debug("%s: cbor_decode_cred_authdata", __func__); + 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_id(fido_cred_t *cred, const unsigned char *ptr, size_t len) +{ + if (fido_blob_set(&cred->attcred.id, ptr, len) < 0) + return (FIDO_ERR_INVALID_ARGUMENT); + + return (FIDO_OK); +} + +int +fido_cred_set_x509(fido_cred_t *cred, const unsigned char *ptr, size_t len) +{ + if (fido_blob_set(&cred->attstmt.x5c, ptr, len) < 0) + return (FIDO_ERR_INVALID_ARGUMENT); + + return (FIDO_OK); +} + +int +fido_cred_set_sig(fido_cred_t *cred, const unsigned char *ptr, size_t len) +{ + if (fido_blob_set(&cred->attstmt.sig, ptr, len) < 0) + return (FIDO_ERR_INVALID_ARGUMENT); + + return (FIDO_OK); +} + +int +fido_cred_set_attstmt(fido_cred_t *cred, const unsigned char *ptr, size_t len) +{ + cbor_item_t *item = NULL; + struct cbor_load_result cbor; + int r = FIDO_ERR_INVALID_ARGUMENT; + + fido_cred_clean_attstmt(&cred->attstmt); + + if (ptr == NULL || len == 0) + goto fail; + + if ((item = cbor_load(ptr, len, &cbor)) == NULL) { + fido_log_debug("%s: cbor_load", __func__); + goto fail; + } + + if (cbor_decode_attstmt(item, &cred->attstmt) < 0) { + fido_log_debug("%s: cbor_decode_attstmt", __func__); + goto fail; + } + + r = FIDO_OK; +fail: + if (item != NULL) + cbor_decref(&item); + + if (r != FIDO_OK) + fido_cred_clean_attstmt(&cred->attstmt); + + return (r); +} + +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(fido_cred_t *cred, const unsigned char *data, + size_t data_len) +{ + if (!fido_blob_is_empty(&cred->cdh) || + fido_blob_set(&cred->cd, data, data_len) < 0) { + return (FIDO_ERR_INVALID_ARGUMENT); + } + if (fido_sha256(&cred->cdh, data, data_len) < 0) { + fido_blob_reset(&cred->cd); + return (FIDO_ERR_INTERNAL); + } + + return (FIDO_OK); +} + +int +fido_cred_set_clientdata_hash(fido_cred_t *cred, const unsigned char *hash, + size_t hash_len) +{ + if (!fido_blob_is_empty(&cred->cd) || + 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 && fido_blob_set(&up->id, user_id, user_id_len) < 0) + goto fail; + 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) + cred->ext.mask = 0; + else { + if ((ext & FIDO_EXT_CRED_MASK) != ext) + return (FIDO_ERR_INVALID_ARGUMENT); + cred->ext.mask |= 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_prot(fido_cred_t *cred, int prot) +{ + if (prot == 0) { + cred->ext.mask &= ~FIDO_EXT_CRED_PROTECT; + cred->ext.prot = 0; + } else { + if (prot != FIDO_CRED_PROT_UV_OPTIONAL && + prot != FIDO_CRED_PROT_UV_OPTIONAL_WITH_ID && + prot != FIDO_CRED_PROT_UV_REQUIRED) + return (FIDO_ERR_INVALID_ARGUMENT); + + cred->ext.mask |= FIDO_EXT_CRED_PROTECT; + cred->ext.prot = prot; + } + + return (FIDO_OK); +} + +int +fido_cred_set_pin_minlen(fido_cred_t *cred, size_t len) +{ + if (len == 0) + cred->ext.mask &= ~FIDO_EXT_MINPINLEN; + else + cred->ext.mask |= FIDO_EXT_MINPINLEN; + + cred->ext.minpinlen = len; + + return (FIDO_OK); +} + +int +fido_cred_set_blob(fido_cred_t *cred, const unsigned char *ptr, size_t len) +{ + if (ptr == NULL || len == 0) + return (FIDO_ERR_INVALID_ARGUMENT); + if (fido_blob_set(&cred->blob, ptr, len) < 0) + return (FIDO_ERR_INTERNAL); + + cred->ext.mask |= FIDO_EXT_CRED_BLOB; + + 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") && + strcmp(fmt, "none") && strcmp(fmt, "tpm")) + 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 (cred->type != 0) + return (FIDO_ERR_INVALID_ARGUMENT); + if (cose_alg != COSE_ES256 && cose_alg != COSE_ES384 && + cose_alg != COSE_RS256 && cose_alg != COSE_EDDSA) + 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); +} + +uint32_t +fido_cred_sigcount(const fido_cred_t *cred) +{ + return (cred->authdata.sigcount); +} + +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_authdata_raw_ptr(const fido_cred_t *cred) +{ + return (cred->authdata_raw.ptr); +} + +size_t +fido_cred_authdata_raw_len(const fido_cred_t *cred) +{ + return (cred->authdata_raw.len); +} + +const unsigned char * +fido_cred_attstmt_ptr(const fido_cred_t *cred) +{ + return (cred->attstmt.cbor.ptr); +} + +size_t +fido_cred_attstmt_len(const fido_cred_t *cred) +{ + return (cred->attstmt.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_ES384: + ptr = &cred->attcred.pubkey.es384; + 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_ES384: + len = sizeof(cred->attcred.pubkey.es384); + 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 unsigned char * +fido_cred_aaguid_ptr(const fido_cred_t *cred) +{ + return (cred->attcred.aaguid); +} + +size_t +fido_cred_aaguid_len(const fido_cred_t *cred) +{ + return (sizeof(cred->attcred.aaguid)); +} + +int +fido_cred_prot(const fido_cred_t *cred) +{ + return (cred->ext.prot); +} + +size_t +fido_cred_pin_minlen(const fido_cred_t *cred) +{ + return (cred->ext.minpinlen); +} + +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); +} + +const unsigned char * +fido_cred_largeblob_key_ptr(const fido_cred_t *cred) +{ + return (cred->largeblob_key.ptr); +} + +size_t +fido_cred_largeblob_key_len(const fido_cred_t *cred) +{ + return (cred->largeblob_key.len); +} diff --git a/src/credman.c b/src/credman.c new file mode 100644 index 0000000..c364242 --- /dev/null +++ b/src/credman.c @@ -0,0 +1,825 @@ +/* + * Copyright (c) 2019-2022 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. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <openssl/sha.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 +#define CMD_UPDATE_CRED 0x07 + +static int +credman_grow_array(void **ptr, size_t *n_alloc, const size_t *n_rx, size_t n, + size_t size) +{ + void *new_ptr; + +#ifdef FIDO_FUZZ + if (n > UINT8_MAX) { + fido_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) { + fido_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 void *body, cbor_item_t **param, + fido_blob_t *hmac_data) +{ + cbor_item_t *param_cbor[3]; + const fido_cred_t *cred; + size_t n; + int ok = -1; + + memset(¶m_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; + if ((param_cbor[0] = fido_blob_encode(body)) == NULL) { + fido_log_debug("%s: cbor encode", __func__); + goto fail; + } + break; + case CMD_DELETE_CRED: + n = 2; + if ((param_cbor[1] = cbor_encode_pubkey(body)) == NULL) { + fido_log_debug("%s: cbor encode", __func__); + goto fail; + } + break; + case CMD_UPDATE_CRED: + n = 3; + cred = body; + param_cbor[1] = cbor_encode_pubkey(&cred->attcred.id); + param_cbor[2] = cbor_encode_user_entity(&cred->user); + if (param_cbor[1] == NULL || param_cbor[2] == NULL) { + fido_log_debug("%s: cbor encode", __func__); + goto fail; + } + break; + default: + fido_log_debug("%s: unknown cmd=0x%02x", __func__, cmd); + return (-1); + } + + if ((*param = cbor_flatten_vector(param_cbor, n)) == NULL) { + fido_log_debug("%s: cbor_flatten_vector", __func__); + goto fail; + } + if (cbor_build_frame(cmd, param_cbor, n, hmac_data) < 0) { + fido_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 subcmd, const void *param, const char *pin, + const char *rp_id, fido_opt_t uv, int *ms) +{ + fido_blob_t f; + fido_blob_t *ecdh = NULL; + fido_blob_t hmac; + es256_pk_t *pk = NULL; + cbor_item_t *argv[4]; + const uint8_t cmd = CTAP_CBOR_CRED_MGMT_PRE; + int r = FIDO_ERR_INTERNAL; + + memset(&f, 0, sizeof(f)); + memset(&hmac, 0, sizeof(hmac)); + memset(&argv, 0, sizeof(argv)); + + if (fido_dev_is_fido2(dev) == false) { + fido_log_debug("%s: fido_dev_is_fido2", __func__); + r = FIDO_ERR_INVALID_COMMAND; + goto fail; + } + + /* subCommand */ + if ((argv[0] = cbor_build_uint8(subcmd)) == NULL) { + fido_log_debug("%s: cbor encode", __func__); + goto fail; + } + + /* pinProtocol, pinAuth */ + if (pin != NULL || uv == FIDO_OPT_TRUE) { + if (credman_prepare_hmac(subcmd, param, &argv[1], &hmac) < 0) { + fido_log_debug("%s: credman_prepare_hmac", __func__); + goto fail; + } + if ((r = fido_do_ecdh(dev, &pk, &ecdh, ms)) != FIDO_OK) { + fido_log_debug("%s: fido_do_ecdh", __func__); + goto fail; + } + if ((r = cbor_add_uv_params(dev, cmd, &hmac, pk, ecdh, pin, + rp_id, &argv[3], &argv[2], ms)) != FIDO_OK) { + fido_log_debug("%s: cbor_add_uv_params", __func__); + goto fail; + } + } + + /* framing and transmission */ + if (cbor_build_frame(cmd, argv, nitems(argv), &f) < 0 || + fido_tx(dev, CTAP_CMD_CBOR, f.ptr, f.len, ms) < 0) { + fido_log_debug("%s: fido_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) { + fido_log_debug("%s: cbor type", __func__); + return (0); /* ignore */ + } + + switch (cbor_get_uint8(key)) { + case 1: + return (cbor_decode_uint64(val, &metadata->rk_existing)); + case 2: + return (cbor_decode_uint64(val, &metadata->rk_remaining)); + default: + fido_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) +{ + unsigned char *msg; + int msglen; + int r; + + memset(metadata, 0, sizeof(*metadata)); + + if ((msg = malloc(FIDO_MAXMSG)) == NULL) { + r = FIDO_ERR_INTERNAL; + goto out; + } + + if ((msglen = fido_rx(dev, CTAP_CMD_CBOR, msg, FIDO_MAXMSG, ms)) < 0) { + fido_log_debug("%s: fido_rx", __func__); + r = FIDO_ERR_RX; + goto out; + } + + if ((r = cbor_parse_reply(msg, (size_t)msglen, metadata, + credman_parse_metadata)) != FIDO_OK) { + fido_log_debug("%s: credman_parse_metadata", __func__); + goto out; + } + + r = FIDO_OK; +out: + freezero(msg, FIDO_MAXMSG); + + return (r); +} + +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, NULL, + FIDO_OPT_TRUE, ms)) != 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) +{ + int ms = dev->timeout_ms; + + return (credman_get_metadata_wait(dev, metadata, pin, &ms)); +} + +static int +credman_parse_rk(const cbor_item_t *key, const cbor_item_t *val, void *arg) +{ + fido_cred_t *cred = arg; + uint64_t prot; + + if (cbor_isa_uint(key) == false || + cbor_int_get_width(key) != CBOR_INT_8) { + fido_log_debug("%s: cbor type", __func__); + return (0); /* ignore */ + } + + switch (cbor_get_uint8(key)) { + case 6: + return (cbor_decode_user(val, &cred->user)); + case 7: + return (cbor_decode_cred_id(val, &cred->attcred.id)); + case 8: + if (cbor_decode_pubkey(val, &cred->attcred.type, + &cred->attcred.pubkey) < 0) + return (-1); + cred->type = cred->attcred.type; /* XXX */ + return (0); + case 10: + if (cbor_decode_uint64(val, &prot) < 0 || prot > INT_MAX || + fido_cred_set_prot(cred, (int)prot) != FIDO_OK) + return (-1); + return (0); + case 11: + return (fido_blob_decode(val, &cred->largeblob_key)); + default: + fido_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) { + fido_log_debug("%s: cbor_type", __func__); + return (0); /* ignore */ + } + + if (cbor_decode_uint64(val, &n) < 0 || n > SIZE_MAX) { + fido_log_debug("%s: cbor_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) { + fido_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) +{ + unsigned char *msg; + int msglen; + int r; + + credman_reset_rk(rk); + + if ((msg = malloc(FIDO_MAXMSG)) == NULL) { + r = FIDO_ERR_INTERNAL; + goto out; + } + + if ((msglen = fido_rx(dev, CTAP_CMD_CBOR, msg, FIDO_MAXMSG, ms)) < 0) { + fido_log_debug("%s: fido_rx", __func__); + r = FIDO_ERR_RX; + goto out; + } + + /* adjust as needed */ + if ((r = cbor_parse_reply(msg, (size_t)msglen, rk, + credman_parse_rk_count)) != FIDO_OK) { + fido_log_debug("%s: credman_parse_rk_count", __func__); + goto out; + } + + if (rk->n_alloc == 0) { + fido_log_debug("%s: n_alloc=0", __func__); + r = FIDO_OK; + goto out; + } + + /* parse the first rk */ + if ((r = cbor_parse_reply(msg, (size_t)msglen, &rk->ptr[0], + credman_parse_rk)) != FIDO_OK) { + fido_log_debug("%s: credman_parse_rk", __func__); + goto out; + } + rk->n_rx = 1; + + r = FIDO_OK; +out: + freezero(msg, FIDO_MAXMSG); + + return (r); +} + +static int +credman_rx_next_rk(fido_dev_t *dev, fido_credman_rk_t *rk, int *ms) +{ + unsigned char *msg; + int msglen; + int r; + + if ((msg = malloc(FIDO_MAXMSG)) == NULL) { + r = FIDO_ERR_INTERNAL; + goto out; + } + + if ((msglen = fido_rx(dev, CTAP_CMD_CBOR, msg, FIDO_MAXMSG, ms)) < 0) { + fido_log_debug("%s: fido_rx", __func__); + r = FIDO_ERR_RX; + goto out; + } + + /* sanity check */ + if (rk->n_rx >= rk->n_alloc) { + fido_log_debug("%s: n_rx=%zu, n_alloc=%zu", __func__, rk->n_rx, + rk->n_alloc); + r = FIDO_ERR_INTERNAL; + goto out; + } + + if ((r = cbor_parse_reply(msg, (size_t)msglen, &rk->ptr[rk->n_rx], + credman_parse_rk)) != FIDO_OK) { + fido_log_debug("%s: credman_parse_rk", __func__); + goto out; + } + + r = FIDO_OK; +out: + freezero(msg, FIDO_MAXMSG); + + return (r); +} + +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) { + fido_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, rp_id, + FIDO_OPT_TRUE, ms)) != 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, NULL, + FIDO_OPT_FALSE, ms)) != 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) +{ + int ms = dev->timeout_ms; + + return (credman_get_rk_wait(dev, rp_id, rk, pin, &ms)); +} + +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, NULL, + FIDO_OPT_TRUE, ms)) != FIDO_OK || + (r = fido_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) +{ + int ms = dev->timeout_ms; + + return (credman_del_rk_wait(dev, cred_id, cred_id_len, pin, &ms)); +} + +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) { + fido_log_debug("%s: cbor type", __func__); + return (0); /* ignore */ + } + + switch (cbor_get_uint8(key)) { + case 3: + return (cbor_decode_rp_entity(val, &rp->rp_entity)); + case 4: + return (fido_blob_decode(val, &rp->rp_id_hash)); + default: + fido_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; + fido_blob_reset(&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) { + fido_log_debug("%s: cbor_type", __func__); + return (0); /* ignore */ + } + + if (cbor_decode_uint64(val, &n) < 0 || n > SIZE_MAX) { + fido_log_debug("%s: cbor_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) { + fido_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) +{ + unsigned char *msg; + int msglen; + int r; + + credman_reset_rp(rp); + + if ((msg = malloc(FIDO_MAXMSG)) == NULL) { + r = FIDO_ERR_INTERNAL; + goto out; + } + + if ((msglen = fido_rx(dev, CTAP_CMD_CBOR, msg, FIDO_MAXMSG, ms)) < 0) { + fido_log_debug("%s: fido_rx", __func__); + r = FIDO_ERR_RX; + goto out; + } + + /* adjust as needed */ + if ((r = cbor_parse_reply(msg, (size_t)msglen, rp, + credman_parse_rp_count)) != FIDO_OK) { + fido_log_debug("%s: credman_parse_rp_count", __func__); + goto out; + } + + if (rp->n_alloc == 0) { + fido_log_debug("%s: n_alloc=0", __func__); + r = FIDO_OK; + goto out; + } + + /* parse the first rp */ + if ((r = cbor_parse_reply(msg, (size_t)msglen, &rp->ptr[0], + credman_parse_rp)) != FIDO_OK) { + fido_log_debug("%s: credman_parse_rp", __func__); + goto out; + } + rp->n_rx = 1; + + r = FIDO_OK; +out: + freezero(msg, FIDO_MAXMSG); + + return (r); +} + +static int +credman_rx_next_rp(fido_dev_t *dev, fido_credman_rp_t *rp, int *ms) +{ + unsigned char *msg; + int msglen; + int r; + + if ((msg = malloc(FIDO_MAXMSG)) == NULL) { + r = FIDO_ERR_INTERNAL; + goto out; + } + + if ((msglen = fido_rx(dev, CTAP_CMD_CBOR, msg, FIDO_MAXMSG, ms)) < 0) { + fido_log_debug("%s: fido_rx", __func__); + r = FIDO_ERR_RX; + goto out; + } + + /* sanity check */ + if (rp->n_rx >= rp->n_alloc) { + fido_log_debug("%s: n_rx=%zu, n_alloc=%zu", __func__, rp->n_rx, + rp->n_alloc); + r = FIDO_ERR_INTERNAL; + goto out; + } + + if ((r = cbor_parse_reply(msg, (size_t)msglen, &rp->ptr[rp->n_rx], + credman_parse_rp)) != FIDO_OK) { + fido_log_debug("%s: credman_parse_rp", __func__); + goto out; + } + + r = FIDO_OK; +out: + freezero(msg, FIDO_MAXMSG); + + return (r); +} + +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, NULL, + FIDO_OPT_TRUE, ms)) != 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, NULL, + FIDO_OPT_FALSE, ms)) != 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) +{ + int ms = dev->timeout_ms; + + return (credman_get_rp_wait(dev, rp, pin, &ms)); +} + +static int +credman_set_dev_rk_wait(fido_dev_t *dev, fido_cred_t *cred, const char *pin, + int *ms) +{ + int r; + + if ((r = credman_tx(dev, CMD_UPDATE_CRED, cred, pin, NULL, + FIDO_OPT_TRUE, ms)) != FIDO_OK || + (r = fido_rx_cbor_status(dev, ms)) != FIDO_OK) + return (r); + + return (FIDO_OK); +} + +int +fido_credman_set_dev_rk(fido_dev_t *dev, fido_cred_t *cred, const char *pin) +{ + int ms = dev->timeout_ms; + + return (credman_set_dev_rk_wait(dev, cred, pin, &ms)); +} + +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/src/dev.c b/src/dev.c new file mode 100644 index 0000000..2d662a6 --- /dev/null +++ b/src/dev.c @@ -0,0 +1,601 @@ +/* + * Copyright (c) 2018-2022 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. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "fido.h" + +#ifndef TLS +#define TLS +#endif + +static TLS bool disable_u2f_fallback; + +#ifdef FIDO_FUZZ +static void +set_random_report_len(fido_dev_t *dev) +{ + dev->rx_len = CTAP_MIN_REPORT_LEN + + uniform_random(CTAP_MAX_REPORT_LEN - CTAP_MIN_REPORT_LEN + 1); + dev->tx_len = CTAP_MIN_REPORT_LEN + + uniform_random(CTAP_MAX_REPORT_LEN - CTAP_MIN_REPORT_LEN + 1); +} +#endif + +static void +fido_dev_set_extension_flags(fido_dev_t *dev, const fido_cbor_info_t *info) +{ + char * const *ptr = fido_cbor_info_extensions_ptr(info); + size_t len = fido_cbor_info_extensions_len(info); + + for (size_t i = 0; i < len; i++) + if (strcmp(ptr[i], "credProtect") == 0) + dev->flags |= FIDO_DEV_CRED_PROT; +} + +static void +fido_dev_set_option_flags(fido_dev_t *dev, const fido_cbor_info_t *info) +{ + char * const *ptr = fido_cbor_info_options_name_ptr(info); + const bool *val = fido_cbor_info_options_value_ptr(info); + size_t len = fido_cbor_info_options_len(info); + + for (size_t i = 0; i < len; i++) + if (strcmp(ptr[i], "clientPin") == 0) { + dev->flags |= val[i] ? + FIDO_DEV_PIN_SET : FIDO_DEV_PIN_UNSET; + } else if (strcmp(ptr[i], "credMgmt") == 0 || + strcmp(ptr[i], "credentialMgmtPreview") == 0) { + if (val[i]) + dev->flags |= FIDO_DEV_CREDMAN; + } else if (strcmp(ptr[i], "uv") == 0) { + dev->flags |= val[i] ? + FIDO_DEV_UV_SET : FIDO_DEV_UV_UNSET; + } else if (strcmp(ptr[i], "pinUvAuthToken") == 0) { + if (val[i]) + dev->flags |= FIDO_DEV_TOKEN_PERMS; + } +} + +static void +fido_dev_set_protocol_flags(fido_dev_t *dev, const fido_cbor_info_t *info) +{ + const uint8_t *ptr = fido_cbor_info_protocols_ptr(info); + size_t len = fido_cbor_info_protocols_len(info); + + for (size_t i = 0; i < len; i++) + switch (ptr[i]) { + case CTAP_PIN_PROTOCOL1: + dev->flags |= FIDO_DEV_PIN_PROTOCOL1; + break; + case CTAP_PIN_PROTOCOL2: + dev->flags |= FIDO_DEV_PIN_PROTOCOL2; + break; + default: + fido_log_debug("%s: unknown protocol %u", __func__, + ptr[i]); + break; + } +} + +static void +fido_dev_set_flags(fido_dev_t *dev, const fido_cbor_info_t *info) +{ + fido_dev_set_extension_flags(dev, info); + fido_dev_set_option_flags(dev, info); + fido_dev_set_protocol_flags(dev, info); +} + +static int +fido_dev_open_tx(fido_dev_t *dev, const char *path, int *ms) +{ + int r; + + if (dev->io_handle != NULL) { + fido_log_debug("%s: handle=%p", __func__, dev->io_handle); + return (FIDO_ERR_INVALID_ARGUMENT); + } + + if (dev->io.open == NULL || dev->io.close == NULL) { + fido_log_debug("%s: NULL open/close", __func__); + return (FIDO_ERR_INVALID_ARGUMENT); + } + + if (dev->cid != CTAP_CID_BROADCAST) { + fido_log_debug("%s: cid=0x%x", __func__, dev->cid); + return (FIDO_ERR_INVALID_ARGUMENT); + } + + if (fido_get_random(&dev->nonce, sizeof(dev->nonce)) < 0) { + fido_log_debug("%s: fido_get_random", __func__); + return (FIDO_ERR_INTERNAL); + } + + if ((dev->io_handle = dev->io.open(path)) == NULL) { + fido_log_debug("%s: dev->io.open", __func__); + return (FIDO_ERR_INTERNAL); + } + + if (dev->io_own) { + dev->rx_len = CTAP_MAX_REPORT_LEN; + dev->tx_len = CTAP_MAX_REPORT_LEN; + } else { + dev->rx_len = fido_hid_report_in_len(dev->io_handle); + dev->tx_len = fido_hid_report_out_len(dev->io_handle); + } + +#ifdef FIDO_FUZZ + set_random_report_len(dev); +#endif + + if (dev->rx_len < CTAP_MIN_REPORT_LEN || + dev->rx_len > CTAP_MAX_REPORT_LEN) { + fido_log_debug("%s: invalid rx_len %zu", __func__, dev->rx_len); + r = FIDO_ERR_RX; + goto fail; + } + + if (dev->tx_len < CTAP_MIN_REPORT_LEN || + dev->tx_len > CTAP_MAX_REPORT_LEN) { + fido_log_debug("%s: invalid tx_len %zu", __func__, dev->tx_len); + r = FIDO_ERR_TX; + goto fail; + } + + if (fido_tx(dev, CTAP_CMD_INIT, &dev->nonce, sizeof(dev->nonce), + ms) < 0) { + fido_log_debug("%s: fido_tx", __func__); + r = FIDO_ERR_TX; + goto fail; + } + + return (FIDO_OK); +fail: + dev->io.close(dev->io_handle); + dev->io_handle = NULL; + + return (r); +} + +static int +fido_dev_open_rx(fido_dev_t *dev, int *ms) +{ + fido_cbor_info_t *info = NULL; + int reply_len; + int r; + + if ((reply_len = fido_rx(dev, CTAP_CMD_INIT, &dev->attr, + sizeof(dev->attr), ms)) < 0) { + fido_log_debug("%s: fido_rx", __func__); + r = FIDO_ERR_RX; + goto fail; + } + +#ifdef FIDO_FUZZ + dev->attr.nonce = dev->nonce; +#endif + + if ((size_t)reply_len != sizeof(dev->attr) || + dev->attr.nonce != dev->nonce) { + fido_log_debug("%s: invalid nonce", __func__); + r = FIDO_ERR_RX; + goto fail; + } + + dev->flags = 0; + dev->cid = dev->attr.cid; + + if (fido_dev_is_fido2(dev)) { + if ((info = fido_cbor_info_new()) == NULL) { + fido_log_debug("%s: fido_cbor_info_new", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + if ((r = fido_dev_get_cbor_info_wait(dev, info, + ms)) != FIDO_OK) { + fido_log_debug("%s: fido_dev_cbor_info_wait: %d", + __func__, r); + if (disable_u2f_fallback) + goto fail; + fido_log_debug("%s: falling back to u2f", __func__); + fido_dev_force_u2f(dev); + } else { + fido_dev_set_flags(dev, info); + } + } + + if (fido_dev_is_fido2(dev) && info != NULL) { + dev->maxmsgsize = fido_cbor_info_maxmsgsiz(info); + fido_log_debug("%s: FIDO_MAXMSG=%d, maxmsgsiz=%lu", __func__, + FIDO_MAXMSG, (unsigned long)dev->maxmsgsize); + } + + r = FIDO_OK; +fail: + fido_cbor_info_free(&info); + + if (r != FIDO_OK) { + dev->io.close(dev->io_handle); + dev->io_handle = NULL; + } + + return (r); +} + +static int +fido_dev_open_wait(fido_dev_t *dev, const char *path, int *ms) +{ + int r; + +#ifdef USE_WINHELLO + if (strcmp(path, FIDO_WINHELLO_PATH) == 0) + return (fido_winhello_open(dev)); +#endif + if ((r = fido_dev_open_tx(dev, path, ms)) != FIDO_OK || + (r = fido_dev_open_rx(dev, ms)) != FIDO_OK) + return (r); + + return (FIDO_OK); +} + +static void +run_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen, + const char *type, int (*manifest)(fido_dev_info_t *, size_t, size_t *)) +{ + size_t ndevs = 0; + int r; + + if (*olen >= ilen) { + fido_log_debug("%s: skipping %s", __func__, type); + return; + } + if ((r = manifest(devlist + *olen, ilen - *olen, &ndevs)) != FIDO_OK) + fido_log_debug("%s: %s: 0x%x", __func__, type, r); + fido_log_debug("%s: found %zu %s device%s", __func__, ndevs, type, + ndevs == 1 ? "" : "s"); + *olen += ndevs; +} + +int +fido_dev_info_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen) +{ + *olen = 0; + + run_manifest(devlist, ilen, olen, "hid", fido_hid_manifest); +#ifdef USE_NFC + run_manifest(devlist, ilen, olen, "nfc", fido_nfc_manifest); +#endif +#ifdef USE_PCSC + run_manifest(devlist, ilen, olen, "pcsc", fido_pcsc_manifest); +#endif +#ifdef USE_WINHELLO + run_manifest(devlist, ilen, olen, "winhello", fido_winhello_manifest); +#endif + + return (FIDO_OK); +} + +int +fido_dev_open_with_info(fido_dev_t *dev) +{ + int ms = dev->timeout_ms; + + if (dev->path == NULL) + return (FIDO_ERR_INVALID_ARGUMENT); + + return (fido_dev_open_wait(dev, dev->path, &ms)); +} + +int +fido_dev_open(fido_dev_t *dev, const char *path) +{ + int ms = dev->timeout_ms; + +#ifdef USE_NFC + if (fido_is_nfc(path) && fido_dev_set_nfc(dev) < 0) { + fido_log_debug("%s: fido_dev_set_nfc", __func__); + return FIDO_ERR_INTERNAL; + } +#endif +#ifdef USE_PCSC + if (fido_is_pcsc(path) && fido_dev_set_pcsc(dev) < 0) { + fido_log_debug("%s: fido_dev_set_pcsc", __func__); + return FIDO_ERR_INTERNAL; + } +#endif + + return (fido_dev_open_wait(dev, path, &ms)); +} + +int +fido_dev_close(fido_dev_t *dev) +{ +#ifdef USE_WINHELLO + if (dev->flags & FIDO_DEV_WINHELLO) + return (fido_winhello_close(dev)); +#endif + if (dev->io_handle == NULL || dev->io.close == NULL) + return (FIDO_ERR_INVALID_ARGUMENT); + + dev->io.close(dev->io_handle); + dev->io_handle = NULL; + dev->cid = CTAP_CID_BROADCAST; + + return (FIDO_OK); +} + +int +fido_dev_set_sigmask(fido_dev_t *dev, const fido_sigset_t *sigmask) +{ + if (dev->io_handle == NULL || sigmask == NULL) + return (FIDO_ERR_INVALID_ARGUMENT); + +#ifdef USE_NFC + if (dev->transport.rx == fido_nfc_rx && dev->io.read == fido_nfc_read) + return (fido_nfc_set_sigmask(dev->io_handle, sigmask)); +#endif + if (dev->transport.rx == NULL && dev->io.read == fido_hid_read) + return (fido_hid_set_sigmask(dev->io_handle, sigmask)); + + return (FIDO_ERR_INVALID_ARGUMENT); +} + +int +fido_dev_cancel(fido_dev_t *dev) +{ + int ms = dev->timeout_ms; + +#ifdef USE_WINHELLO + if (dev->flags & FIDO_DEV_WINHELLO) + return (fido_winhello_cancel(dev)); +#endif + if (fido_dev_is_fido2(dev) == false) + return (FIDO_ERR_INVALID_ARGUMENT); + if (fido_tx(dev, CTAP_CMD_CANCEL, NULL, 0, &ms) < 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) { + fido_log_debug("%s: non-NULL handle", __func__); + return (FIDO_ERR_INVALID_ARGUMENT); + } + + if (io == NULL || io->open == NULL || io->close == NULL || + io->read == NULL || io->write == NULL) { + fido_log_debug("%s: NULL function", __func__); + return (FIDO_ERR_INVALID_ARGUMENT); + } + + dev->io = *io; + dev->io_own = true; + + return (FIDO_OK); +} + +int +fido_dev_set_transport_functions(fido_dev_t *dev, const fido_dev_transport_t *t) +{ + if (dev->io_handle != NULL) { + fido_log_debug("%s: non-NULL handle", __func__); + return (FIDO_ERR_INVALID_ARGUMENT); + } + + dev->transport = *t; + dev->io_own = true; + + return (FIDO_OK); +} + +void * +fido_dev_io_handle(const fido_dev_t *dev) +{ + + return (dev->io_handle); +} + +void +fido_init(int flags) +{ + if (flags & FIDO_DEBUG || getenv("FIDO_DEBUG") != NULL) + fido_log_init(); + + disable_u2f_fallback = (flags & FIDO_DISABLE_U2F_FALLBACK); +} + +fido_dev_t * +fido_dev_new(void) +{ + fido_dev_t *dev; + + if ((dev = calloc(1, sizeof(*dev))) == NULL) + return (NULL); + + dev->cid = CTAP_CID_BROADCAST; + dev->timeout_ms = -1; + dev->io = (fido_dev_io_t) { + &fido_hid_open, + &fido_hid_close, + &fido_hid_read, + &fido_hid_write, + }; + + return (dev); +} + +fido_dev_t * +fido_dev_new_with_info(const fido_dev_info_t *di) +{ + fido_dev_t *dev; + + if ((dev = calloc(1, sizeof(*dev))) == NULL) + return (NULL); + +#if 0 + if (di->io.open == NULL || di->io.close == NULL || + di->io.read == NULL || di->io.write == NULL) { + fido_log_debug("%s: NULL function", __func__); + fido_dev_free(&dev); + return (NULL); + } +#endif + + dev->io = di->io; + dev->io_own = di->transport.tx != NULL || di->transport.rx != NULL; + dev->transport = di->transport; + dev->cid = CTAP_CID_BROADCAST; + dev->timeout_ms = -1; + + if ((dev->path = strdup(di->path)) == NULL) { + fido_log_debug("%s: strdup", __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->path); + 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); +} + +bool +fido_dev_is_winhello(const fido_dev_t *dev) +{ + return (dev->flags & FIDO_DEV_WINHELLO); +} + +bool +fido_dev_supports_pin(const fido_dev_t *dev) +{ + return (dev->flags & (FIDO_DEV_PIN_SET|FIDO_DEV_PIN_UNSET)); +} + +bool +fido_dev_has_pin(const fido_dev_t *dev) +{ + return (dev->flags & FIDO_DEV_PIN_SET); +} + +bool +fido_dev_supports_cred_prot(const fido_dev_t *dev) +{ + return (dev->flags & FIDO_DEV_CRED_PROT); +} + +bool +fido_dev_supports_credman(const fido_dev_t *dev) +{ + return (dev->flags & FIDO_DEV_CREDMAN); +} + +bool +fido_dev_supports_uv(const fido_dev_t *dev) +{ + return (dev->flags & (FIDO_DEV_UV_SET|FIDO_DEV_UV_UNSET)); +} + +bool +fido_dev_has_uv(const fido_dev_t *dev) +{ + return (dev->flags & FIDO_DEV_UV_SET); +} + +bool +fido_dev_supports_permissions(const fido_dev_t *dev) +{ + return (dev->flags & FIDO_DEV_TOKEN_PERMS); +} + +void +fido_dev_force_u2f(fido_dev_t *dev) +{ + dev->attr.flags &= (uint8_t)~FIDO_CAP_CBOR; + dev->flags = 0; +} + +void +fido_dev_force_fido2(fido_dev_t *dev) +{ + dev->attr.flags |= FIDO_CAP_CBOR; +} + +uint8_t +fido_dev_get_pin_protocol(const fido_dev_t *dev) +{ + if (dev->flags & FIDO_DEV_PIN_PROTOCOL2) + return (CTAP_PIN_PROTOCOL2); + else if (dev->flags & FIDO_DEV_PIN_PROTOCOL1) + return (CTAP_PIN_PROTOCOL1); + + return (0); +} + +uint64_t +fido_dev_maxmsgsize(const fido_dev_t *dev) +{ + return (dev->maxmsgsize); +} + +int +fido_dev_set_timeout(fido_dev_t *dev, int ms) +{ + if (ms < -1) + return (FIDO_ERR_INVALID_ARGUMENT); + + dev->timeout_ms = ms; + + return (FIDO_OK); +} diff --git a/src/diff_exports.sh b/src/diff_exports.sh new file mode 100755 index 0000000..2e15cd0 --- /dev/null +++ b/src/diff_exports.sh @@ -0,0 +1,27 @@ +#!/bin/sh -u + +# 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. +# SPDX-License-Identifier: BSD-2-Clause + +for f in export.gnu export.llvm export.msvc; do + if [ ! -f "${f}" ]; then + exit 1 + fi +done + +TMPDIR="$(mktemp -d)" +GNU="${TMPDIR}/gnu" +LLVM="${TMPDIR}/llvm" +MSVC="${TMPDIR}/msvc" + +awk '/^[^*{}]+;$/' export.gnu | tr -d '\t;' | sort > "${GNU}" +sed 's/^_//' export.llvm | sort > "${LLVM}" +grep -v '^EXPORTS$' export.msvc | sort > "${MSVC}" +diff -u "${GNU}" "${LLVM}" && diff -u "${MSVC}" "${LLVM}" +ERROR=$? +rm "${GNU}" "${LLVM}" "${MSVC}" +rmdir "${TMPDIR}" + +exit ${ERROR} diff --git a/src/ecdh.c b/src/ecdh.c new file mode 100644 index 0000000..878f976 --- /dev/null +++ b/src/ecdh.c @@ -0,0 +1,208 @@ +/* + * Copyright (c) 2018-2021 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. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <openssl/evp.h> +#include <openssl/sha.h> +#if defined(LIBRESSL_VERSION_NUMBER) +#include <openssl/hkdf.h> +#else +#include <openssl/kdf.h> +#endif + +#include "fido.h" +#include "fido/es256.h" + +#if defined(LIBRESSL_VERSION_NUMBER) +static int +hkdf_sha256(uint8_t *key, const char *info, const fido_blob_t *secret) +{ + const EVP_MD *md; + uint8_t salt[32]; + + memset(salt, 0, sizeof(salt)); + if ((md = EVP_sha256()) == NULL || + HKDF(key, SHA256_DIGEST_LENGTH, md, secret->ptr, secret->len, salt, + sizeof(salt), (const uint8_t *)info, strlen(info)) != 1) + return -1; + + return 0; +} +#else +static int +hkdf_sha256(uint8_t *key, char *info, fido_blob_t *secret) +{ + const EVP_MD *const_md; + EVP_MD *md = NULL; + EVP_PKEY_CTX *ctx = NULL; + size_t keylen = SHA256_DIGEST_LENGTH; + uint8_t salt[32]; + int ok = -1; + + memset(salt, 0, sizeof(salt)); + if (secret->len > INT_MAX || strlen(info) > INT_MAX) { + fido_log_debug("%s: invalid param", __func__); + goto fail; + } + if ((const_md = EVP_sha256()) == NULL || + (md = EVP_MD_meth_dup(const_md)) == NULL || + (ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL)) == NULL) { + fido_log_debug("%s: init", __func__); + goto fail; + } + if (EVP_PKEY_derive_init(ctx) < 1 || + EVP_PKEY_CTX_set_hkdf_md(ctx, md) < 1 || + EVP_PKEY_CTX_set1_hkdf_salt(ctx, salt, sizeof(salt)) < 1 || + EVP_PKEY_CTX_set1_hkdf_key(ctx, secret->ptr, (int)secret->len) < 1 || + EVP_PKEY_CTX_add1_hkdf_info(ctx, (void *)info, (int)strlen(info)) < 1) { + fido_log_debug("%s: EVP_PKEY_CTX", __func__); + goto fail; + } + if (EVP_PKEY_derive(ctx, key, &keylen) < 1) { + fido_log_debug("%s: EVP_PKEY_derive", __func__); + goto fail; + } + + ok = 0; +fail: + if (md != NULL) + EVP_MD_meth_free(md); + if (ctx != NULL) + EVP_PKEY_CTX_free(ctx); + + return ok; +} +#endif /* defined(LIBRESSL_VERSION_NUMBER) */ + +static int +kdf(uint8_t prot, fido_blob_t *key, /* const */ fido_blob_t *secret) +{ + char hmac_info[] = "CTAP2 HMAC key"; /* const */ + char aes_info[] = "CTAP2 AES key"; /* const */ + + switch (prot) { + case CTAP_PIN_PROTOCOL1: + /* use sha256 on the resulting secret */ + key->len = SHA256_DIGEST_LENGTH; + if ((key->ptr = calloc(1, key->len)) == NULL || + SHA256(secret->ptr, secret->len, key->ptr) != key->ptr) { + fido_log_debug("%s: SHA256", __func__); + return -1; + } + break; + case CTAP_PIN_PROTOCOL2: + /* use two instances of hkdf-sha256 on the resulting secret */ + key->len = 2 * SHA256_DIGEST_LENGTH; + if ((key->ptr = calloc(1, key->len)) == NULL || + hkdf_sha256(key->ptr, hmac_info, secret) < 0 || + hkdf_sha256(key->ptr + SHA256_DIGEST_LENGTH, aes_info, + secret) < 0) { + fido_log_debug("%s: hkdf", __func__); + return -1; + } + break; + default: + fido_log_debug("%s: unknown pin protocol %u", __func__, prot); + return -1; + } + + return 0; +} + +static int +do_ecdh(const fido_dev_t *dev, 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; + if ((secret = fido_blob_new()) == NULL || + (*ecdh = fido_blob_new()) == NULL) + goto fail; + if ((pk_evp = es256_pk_to_EVP_PKEY(pk)) == NULL || + (sk_evp = es256_sk_to_EVP_PKEY(sk)) == NULL) { + fido_log_debug("%s: es256_to_EVP_PKEY", __func__); + goto fail; + } + 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) { + fido_log_debug("%s: EVP_PKEY_derive_init", __func__); + goto fail; + } + 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) { + fido_log_debug("%s: EVP_PKEY_derive", __func__); + goto fail; + } + if (kdf(fido_dev_get_pin_protocol(dev), *ecdh, secret) < 0) { + fido_log_debug("%s: kdf", __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, int *ms) +{ + es256_sk_t *sk = NULL; /* our private key */ + es256_pk_t *ak = NULL; /* authenticator's public key */ + int r; + + *pk = NULL; + *ecdh = NULL; + 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) { + fido_log_debug("%s: es256_derive_pk", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + if ((ak = es256_pk_new()) == NULL || + fido_dev_authkey(dev, ak, ms) != FIDO_OK) { + fido_log_debug("%s: fido_dev_authkey", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + if (do_ecdh(dev, sk, ak, ecdh) < 0) { + fido_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/src/eddsa.c b/src/eddsa.c new file mode 100644 index 0000000..d043f89 --- /dev/null +++ b/src/eddsa.c @@ -0,0 +1,232 @@ +/* + * Copyright (c) 2019-2021 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. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <openssl/bn.h> +#include <openssl/obj_mac.h> + +#include "fido.h" +#include "fido/eddsa.h" + +#if defined(LIBRESSL_VERSION_NUMBER) +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; + + fido_log_debug("%s: unimplemented", __func__); + + 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; + + fido_log_debug("%s: unimplemented", __func__); + + return (0); +} +#endif /* LIBRESSL_VERSION_NUMBER */ + +#if defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x3040000f +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; + + fido_log_debug("%s: unimplemented", __func__); + + return (0); +} +#endif /* LIBRESSL_VERSION_NUMBER < 0x3040000f */ + +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) { + fido_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) { + fido_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; + + freezero(pk, sizeof(*pk)); + *pkp = NULL; +} + +int +eddsa_pk_from_ptr(eddsa_pk_t *pk, const void *ptr, size_t len) +{ + EVP_PKEY *pkey; + + if (len < sizeof(*pk)) + return (FIDO_ERR_INVALID_ARGUMENT); + + memcpy(pk, ptr, sizeof(*pk)); + + if ((pkey = eddsa_pk_to_EVP_PKEY(pk)) == NULL) { + fido_log_debug("%s: eddsa_pk_to_EVP_PKEY", __func__); + return (FIDO_ERR_INVALID_ARGUMENT); + } + + EVP_PKEY_free(pkey); + + 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) + fido_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_base_id(pkey) != EVP_PKEY_ED25519) + return (FIDO_ERR_INVALID_ARGUMENT); + 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); +} + +int +eddsa_verify_sig(const fido_blob_t *dgst, EVP_PKEY *pkey, + const fido_blob_t *sig) +{ + EVP_MD_CTX *mdctx = NULL; + int ok = -1; + + if (EVP_PKEY_base_id(pkey) != EVP_PKEY_ED25519) { + fido_log_debug("%s: EVP_PKEY_base_id", __func__); + goto fail; + } + + /* EVP_DigestVerify needs ints */ + if (dgst->len > INT_MAX || sig->len > INT_MAX) { + fido_log_debug("%s: dgst->len=%zu, sig->len=%zu", __func__, + dgst->len, sig->len); + return (-1); + } + + if ((mdctx = EVP_MD_CTX_new()) == NULL) { + fido_log_debug("%s: EVP_MD_CTX_new", __func__); + goto fail; + } + + if (EVP_DigestVerifyInit(mdctx, NULL, NULL, NULL, pkey) != 1) { + fido_log_debug("%s: EVP_DigestVerifyInit", __func__); + goto fail; + } + + if (EVP_DigestVerify(mdctx, sig->ptr, sig->len, dgst->ptr, + dgst->len) != 1) { + fido_log_debug("%s: EVP_DigestVerify", __func__); + goto fail; + } + + ok = 0; +fail: + EVP_MD_CTX_free(mdctx); + + return (ok); +} + +int +eddsa_pk_verify_sig(const fido_blob_t *dgst, const eddsa_pk_t *pk, + const fido_blob_t *sig) +{ + EVP_PKEY *pkey; + int ok = -1; + + if ((pkey = eddsa_pk_to_EVP_PKEY(pk)) == NULL || + eddsa_verify_sig(dgst, pkey, sig) < 0) { + fido_log_debug("%s: eddsa_verify_sig", __func__); + goto fail; + } + + ok = 0; +fail: + EVP_PKEY_free(pkey); + + return (ok); +} diff --git a/src/err.c b/src/err.c new file mode 100644 index 0000000..3a6f3e0 --- /dev/null +++ b/src/err.c @@ -0,0 +1,137 @@ +/* + * 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. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#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_CBOR_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_FP_DATABASE_FULL: + return "FIDO_ERR_FP_DATABASE_FULL"; + case FIDO_ERR_LARGEBLOB_STORAGE_FULL: + return "FIDO_ERR_LARGEBLOB_STORAGE_FULL"; + 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_USER_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_KEY_STORE_FULL"; + case FIDO_ERR_NOT_BUSY: + return "FIDO_ERR_NOT_BUSY"; + case FIDO_ERR_NO_OPERATION_PENDING: + return "FIDO_ERR_NO_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_USER_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_PIN_AUTH_INVALID"; + case FIDO_ERR_PIN_AUTH_BLOCKED: + return "FIDO_ERR_PIN_AUTH_BLOCKED"; + case FIDO_ERR_PIN_NOT_SET: + return "FIDO_ERR_PIN_NOT_SET"; + case FIDO_ERR_PIN_REQUIRED: + return "FIDO_ERR_PIN_REQUIRED"; + case FIDO_ERR_PIN_POLICY_VIOLATION: + return "FIDO_ERR_PIN_POLICY_VIOLATION"; + case FIDO_ERR_PIN_TOKEN_EXPIRED: + return "FIDO_ERR_PIN_TOKEN_EXPIRED"; + case FIDO_ERR_REQUEST_TOO_LARGE: + return "FIDO_ERR_REQUEST_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_UV_BLOCKED: + return "FIDO_ERR_UV_BLOCKED"; + case FIDO_ERR_UV_INVALID: + return "FIDO_ERR_UV_INVALID"; + case FIDO_ERR_UNAUTHORIZED_PERM: + return "FIDO_ERR_UNAUTHORIZED_PERM"; + case FIDO_ERR_ERR_OTHER: + return "FIDO_ERR_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_NOTFOUND: + return "FIDO_ERR_NOTFOUND"; + case FIDO_ERR_COMPRESS: + return "FIDO_ERR_COMPRESS"; + case FIDO_ERR_INTERNAL: + return "FIDO_ERR_INTERNAL"; + default: + return "FIDO_ERR_UNKNOWN"; + } +} diff --git a/src/es256.c b/src/es256.c new file mode 100644 index 0000000..17efb0a --- /dev/null +++ b/src/es256.c @@ -0,0 +1,541 @@ +/* + * Copyright (c) 2018-2022 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. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <openssl/bn.h> +#include <openssl/ecdsa.h> +#include <openssl/obj_mac.h> + +#include "fido.h" +#include "fido/es256.h" + +#if OPENSSL_VERSION_NUMBER >= 0x30000000 +#define get0_EC_KEY(x) EVP_PKEY_get0_EC_KEY((x)) +#else +#define get0_EC_KEY(x) EVP_PKEY_get0((x)) +#endif + +static const int es256_nid = NID_X9_62_prime256v1; + +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) { + fido_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) { + fido_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((uint8_t)(-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; + + freezero(sk, sizeof(*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; + + freezero(pk, sizeof(*pk)); + *pkp = NULL; +} + +int +es256_pk_from_ptr(es256_pk_t *pk, const void *ptr, size_t len) +{ + const uint8_t *p = ptr; + EVP_PKEY *pkey; + + if (len < sizeof(*pk)) + return (FIDO_ERR_INVALID_ARGUMENT); + + if (len == sizeof(*pk) + 1 && *p == 0x04) + memcpy(pk, ++p, sizeof(*pk)); /* uncompressed format */ + else + memcpy(pk, ptr, sizeof(*pk)); /* libfido2 x||y format */ + + if ((pkey = es256_pk_to_EVP_PKEY(pk)) == NULL) { + fido_log_debug("%s: es256_pk_to_EVP_PKEY", __func__); + explicit_bzero(pk, sizeof(*pk)); + return (FIDO_ERR_INVALID_ARGUMENT); + } + + EVP_PKEY_free(pkey); + + 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; + 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, es256_nid) <= 0 || + EVP_PKEY_paramgen(pctx, &p) <= 0) { + fido_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) { + fido_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)) { + fido_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; + int ok = -1; + + if ((bnctx = BN_CTX_new()) == NULL) + goto fail; + + BN_CTX_start(bnctx); + + if ((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) { + fido_log_debug("%s: BN_bin2bn", __func__); + goto fail; + } + + if ((ec = EC_KEY_new_by_curve_name(es256_nid)) == NULL || + (g = EC_KEY_get0_group(ec)) == NULL) { + fido_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) { + fido_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) { + fido_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_end(bnctx); + 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 *bnctx = NULL; + BIGNUM *x = NULL; + BIGNUM *y = NULL; + const EC_POINT *q = NULL; + EC_GROUP *g = NULL; + size_t dx; + size_t dy; + int ok = FIDO_ERR_INTERNAL; + int nx; + int ny; + + if ((q = EC_KEY_get0_public_key(ec)) == NULL || + (g = EC_GROUP_new_by_curve_name(es256_nid)) == NULL || + (bnctx = BN_CTX_new()) == NULL) + goto fail; + + BN_CTX_start(bnctx); + + if ((x = BN_CTX_get(bnctx)) == NULL || + (y = BN_CTX_get(bnctx)) == NULL) + goto fail; + + if (EC_POINT_is_on_curve(g, q, bnctx) != 1) { + fido_log_debug("%s: EC_POINT_is_on_curve", __func__); + ok = FIDO_ERR_INVALID_ARGUMENT; + goto fail; + } + + if (EC_POINT_get_affine_coordinates_GFp(g, q, x, y, bnctx) == 0 || + (nx = BN_num_bytes(x)) < 0 || (size_t)nx > sizeof(pk->x) || + (ny = BN_num_bytes(y)) < 0 || (size_t)ny > sizeof(pk->y)) { + fido_log_debug("%s: EC_POINT_get_affine_coordinates_GFp", + __func__); + goto fail; + } + + dx = sizeof(pk->x) - (size_t)nx; + dy = sizeof(pk->y) - (size_t)ny; + + if ((nx = BN_bn2bin(x, pk->x + dx)) < 0 || (size_t)nx > sizeof(pk->x) || + (ny = BN_bn2bin(y, pk->y + dy)) < 0 || (size_t)ny > sizeof(pk->y)) { + fido_log_debug("%s: BN_bn2bin", __func__); + goto fail; + } + + ok = FIDO_OK; +fail: + EC_GROUP_free(g); + + if (bnctx != NULL) { + BN_CTX_end(bnctx); + BN_CTX_free(bnctx); + } + + return (ok); +} + +int +es256_pk_from_EVP_PKEY(es256_pk_t *pk, const EVP_PKEY *pkey) +{ + const EC_KEY *ec; + + if (EVP_PKEY_base_id(pkey) != EVP_PKEY_EC || + (ec = get0_EC_KEY(pkey)) == NULL) + return (FIDO_ERR_INVALID_ARGUMENT); + + return (es256_pk_from_EC_KEY(pk, ec)); +} + +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; + int ok = -1; + + if ((bnctx = BN_CTX_new()) == NULL) + goto fail; + + BN_CTX_start(bnctx); + + if ((d = BN_CTX_get(bnctx)) == NULL || + BN_bin2bn(k->d, sizeof(k->d), d) == NULL) { + fido_log_debug("%s: BN_bin2bn", __func__); + goto fail; + } + + if ((ec = EC_KEY_new_by_curve_name(es256_nid)) == NULL || + EC_KEY_set_private_key(ec, d) == 0) { + fido_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) { + fido_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_end(bnctx); + 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; + int ok = -1; + + if ((d = BN_bin2bn(sk->d, (int)sizeof(sk->d), NULL)) == NULL || + (ec = EC_KEY_new_by_curve_name(es256_nid)) == NULL || + (g = EC_KEY_get0_group(ec)) == NULL || + (q = EC_POINT_new(g)) == NULL) { + fido_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) { + fido_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); +} + +int +es256_verify_sig(const fido_blob_t *dgst, EVP_PKEY *pkey, + const fido_blob_t *sig) +{ + EVP_PKEY_CTX *pctx = NULL; + int ok = -1; + + if (EVP_PKEY_base_id(pkey) != EVP_PKEY_EC) { + fido_log_debug("%s: EVP_PKEY_base_id", __func__); + goto fail; + } + + if ((pctx = EVP_PKEY_CTX_new(pkey, NULL)) == NULL || + EVP_PKEY_verify_init(pctx) != 1 || + EVP_PKEY_verify(pctx, sig->ptr, sig->len, dgst->ptr, + dgst->len) != 1) { + fido_log_debug("%s: EVP_PKEY_verify", __func__); + goto fail; + } + + ok = 0; +fail: + EVP_PKEY_CTX_free(pctx); + + return (ok); +} + +int +es256_pk_verify_sig(const fido_blob_t *dgst, const es256_pk_t *pk, + const fido_blob_t *sig) +{ + EVP_PKEY *pkey; + int ok = -1; + + if ((pkey = es256_pk_to_EVP_PKEY(pk)) == NULL || + es256_verify_sig(dgst, pkey, sig) < 0) { + fido_log_debug("%s: es256_verify_sig", __func__); + goto fail; + } + + ok = 0; +fail: + EVP_PKEY_free(pkey); + + return (ok); +} diff --git a/src/es384.c b/src/es384.c new file mode 100644 index 0000000..013d285 --- /dev/null +++ b/src/es384.c @@ -0,0 +1,296 @@ +/* + * Copyright (c) 2022 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. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <openssl/bn.h> +#include <openssl/ecdsa.h> +#include <openssl/obj_mac.h> + +#include "fido.h" +#include "fido/es384.h" + +#if OPENSSL_VERSION_NUMBER >= 0x30000000 +#define get0_EC_KEY(x) EVP_PKEY_get0_EC_KEY((x)) +#else +#define get0_EC_KEY(x) EVP_PKEY_get0((x)) +#endif + +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) { + fido_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) +{ + es384_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 +es384_pk_decode(const cbor_item_t *item, es384_pk_t *k) +{ + if (cbor_isa_map(item) == false || + cbor_map_is_definite(item) == false || + cbor_map_iter(item, k, decode_pubkey_point) < 0) { + fido_log_debug("%s: cbor type", __func__); + return (-1); + } + + return (0); +} + +es384_pk_t * +es384_pk_new(void) +{ + return (calloc(1, sizeof(es384_pk_t))); +} + +void +es384_pk_free(es384_pk_t **pkp) +{ + es384_pk_t *pk; + + if (pkp == NULL || (pk = *pkp) == NULL) + return; + + freezero(pk, sizeof(*pk)); + *pkp = NULL; +} + +int +es384_pk_from_ptr(es384_pk_t *pk, const void *ptr, size_t len) +{ + const uint8_t *p = ptr; + EVP_PKEY *pkey; + + if (len < sizeof(*pk)) + return (FIDO_ERR_INVALID_ARGUMENT); + + if (len == sizeof(*pk) + 1 && *p == 0x04) + memcpy(pk, ++p, sizeof(*pk)); /* uncompressed format */ + else + memcpy(pk, ptr, sizeof(*pk)); /* libfido2 x||y format */ + + if ((pkey = es384_pk_to_EVP_PKEY(pk)) == NULL) { + fido_log_debug("%s: es384_pk_to_EVP_PKEY", __func__); + explicit_bzero(pk, sizeof(*pk)); + return (FIDO_ERR_INVALID_ARGUMENT); + } + + EVP_PKEY_free(pkey); + + return (FIDO_OK); +} + +EVP_PKEY * +es384_pk_to_EVP_PKEY(const es384_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; + int ok = -1; + + if ((bnctx = BN_CTX_new()) == NULL) + goto fail; + + BN_CTX_start(bnctx); + + if ((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) { + fido_log_debug("%s: BN_bin2bn", __func__); + goto fail; + } + + if ((ec = EC_KEY_new_by_curve_name(NID_secp384r1)) == NULL || + (g = EC_KEY_get0_group(ec)) == NULL) { + fido_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) { + fido_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) { + fido_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_end(bnctx); + 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 +es384_pk_from_EC_KEY(es384_pk_t *pk, const EC_KEY *ec) +{ + BN_CTX *bnctx = NULL; + BIGNUM *x = NULL; + BIGNUM *y = NULL; + const EC_POINT *q = NULL; + EC_GROUP *g = NULL; + size_t dx; + size_t dy; + int ok = FIDO_ERR_INTERNAL; + int nx; + int ny; + + if ((q = EC_KEY_get0_public_key(ec)) == NULL || + (g = EC_GROUP_new_by_curve_name(NID_secp384r1)) == NULL || + (bnctx = BN_CTX_new()) == NULL) + goto fail; + + BN_CTX_start(bnctx); + + if ((x = BN_CTX_get(bnctx)) == NULL || + (y = BN_CTX_get(bnctx)) == NULL) + goto fail; + + if (EC_POINT_is_on_curve(g, q, bnctx) != 1) { + fido_log_debug("%s: EC_POINT_is_on_curve", __func__); + ok = FIDO_ERR_INVALID_ARGUMENT; + goto fail; + } + + if (EC_POINT_get_affine_coordinates_GFp(g, q, x, y, bnctx) == 0 || + (nx = BN_num_bytes(x)) < 0 || (size_t)nx > sizeof(pk->x) || + (ny = BN_num_bytes(y)) < 0 || (size_t)ny > sizeof(pk->y)) { + fido_log_debug("%s: EC_POINT_get_affine_coordinates_GFp", + __func__); + goto fail; + } + + dx = sizeof(pk->x) - (size_t)nx; + dy = sizeof(pk->y) - (size_t)ny; + + if ((nx = BN_bn2bin(x, pk->x + dx)) < 0 || (size_t)nx > sizeof(pk->x) || + (ny = BN_bn2bin(y, pk->y + dy)) < 0 || (size_t)ny > sizeof(pk->y)) { + fido_log_debug("%s: BN_bn2bin", __func__); + goto fail; + } + + ok = FIDO_OK; +fail: + EC_GROUP_free(g); + + if (bnctx != NULL) { + BN_CTX_end(bnctx); + BN_CTX_free(bnctx); + } + + return (ok); +} + +int +es384_pk_from_EVP_PKEY(es384_pk_t *pk, const EVP_PKEY *pkey) +{ + const EC_KEY *ec; + + if (EVP_PKEY_base_id(pkey) != EVP_PKEY_EC || + (ec = get0_EC_KEY(pkey)) == NULL) + return (FIDO_ERR_INVALID_ARGUMENT); + + return (es384_pk_from_EC_KEY(pk, ec)); +} + +int +es384_verify_sig(const fido_blob_t *dgst, EVP_PKEY *pkey, + const fido_blob_t *sig) +{ + EVP_PKEY_CTX *pctx = NULL; + int ok = -1; + + if (EVP_PKEY_base_id(pkey) != EVP_PKEY_EC) { + fido_log_debug("%s: EVP_PKEY_base_id", __func__); + goto fail; + } + + if ((pctx = EVP_PKEY_CTX_new(pkey, NULL)) == NULL || + EVP_PKEY_verify_init(pctx) != 1 || + EVP_PKEY_verify(pctx, sig->ptr, sig->len, dgst->ptr, + dgst->len) != 1) { + fido_log_debug("%s: EVP_PKEY_verify", __func__); + goto fail; + } + + ok = 0; +fail: + EVP_PKEY_CTX_free(pctx); + + return (ok); +} + +int +es384_pk_verify_sig(const fido_blob_t *dgst, const es384_pk_t *pk, + const fido_blob_t *sig) +{ + EVP_PKEY *pkey; + int ok = -1; + + if ((pkey = es384_pk_to_EVP_PKEY(pk)) == NULL || + es384_verify_sig(dgst, pkey, sig) < 0) { + fido_log_debug("%s: es384_verify_sig", __func__); + goto fail; + } + + ok = 0; +fail: + EVP_PKEY_free(pkey); + + return (ok); +} diff --git a/src/export.gnu b/src/export.gnu new file mode 100644 index 0000000..6916837 --- /dev/null +++ b/src/export.gnu @@ -0,0 +1,263 @@ +{ + global: + 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_EVP_PKEY; + es256_pk_from_ptr; + es256_pk_new; + es256_pk_to_EVP_PKEY; + es384_pk_free; + es384_pk_from_EC_KEY; + es384_pk_from_EVP_PKEY; + es384_pk_from_ptr; + es384_pk_new; + es384_pk_to_EVP_PKEY; + fido_assert_allow_cred; + fido_assert_authdata_len; + fido_assert_authdata_ptr; + fido_assert_blob_len; + fido_assert_blob_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_largeblob_key_len; + fido_assert_largeblob_key_ptr; + fido_assert_new; + fido_assert_rp_id; + fido_assert_set_authdata; + fido_assert_set_authdata_raw; + fido_assert_set_clientdata; + fido_assert_set_clientdata_hash; + fido_assert_set_count; + fido_assert_set_extensions; + fido_assert_set_hmac_salt; + fido_assert_set_hmac_secret; + 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_algorithm_cose; + fido_cbor_info_algorithm_count; + fido_cbor_info_algorithm_type; + fido_cbor_info_certs_len; + fido_cbor_info_certs_name_ptr; + fido_cbor_info_certs_value_ptr; + fido_cbor_info_extensions_len; + fido_cbor_info_extensions_ptr; + fido_cbor_info_free; + fido_cbor_info_fwversion; + fido_cbor_info_maxcredbloblen; + fido_cbor_info_maxcredcntlst; + fido_cbor_info_maxcredidlen; + fido_cbor_info_maxlargeblob; + fido_cbor_info_maxmsgsiz; + fido_cbor_info_maxrpid_minpinlen; + fido_cbor_info_minpinlen; + fido_cbor_info_new; + fido_cbor_info_new_pin_required; + 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_rk_remaining; + fido_cbor_info_transports_len; + fido_cbor_info_transports_ptr; + fido_cbor_info_uv_attempts; + fido_cbor_info_uv_modality; + fido_cbor_info_versions_len; + fido_cbor_info_versions_ptr; + fido_cred_attstmt_len; + fido_cred_attstmt_ptr; + fido_cred_authdata_len; + fido_cred_authdata_ptr; + fido_cred_authdata_raw_len; + fido_cred_authdata_raw_ptr; + fido_cred_clientdata_hash_len; + fido_cred_clientdata_hash_ptr; + fido_cred_display_name; + fido_cred_exclude; + fido_cred_flags; + fido_cred_largeblob_key_len; + fido_cred_largeblob_key_ptr; + fido_cred_sigcount; + fido_cred_fmt; + fido_cred_free; + fido_cred_id_len; + fido_cred_id_ptr; + fido_cred_aaguid_len; + fido_cred_aaguid_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_credman_set_dev_rk; + fido_cred_new; + fido_cred_pin_minlen; + fido_cred_prot; + fido_cred_pubkey_len; + fido_cred_pubkey_ptr; + fido_cred_rp_id; + fido_cred_rp_name; + fido_cred_set_attstmt; + fido_cred_set_authdata; + fido_cred_set_authdata_raw; + fido_cred_set_blob; + fido_cred_set_clientdata; + fido_cred_set_clientdata_hash; + fido_cred_set_extensions; + fido_cred_set_fmt; + fido_cred_set_id; + fido_cred_set_options; + fido_cred_set_pin_minlen; + fido_cred_set_prot; + 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_enable_entattest; + fido_dev_flags; + fido_dev_force_fido2; + fido_dev_force_pin_change; + fido_dev_force_u2f; + fido_dev_free; + fido_dev_get_assert; + fido_dev_get_cbor_info; + fido_dev_get_retry_count; + fido_dev_get_uv_retry_count; + fido_dev_get_touch_begin; + fido_dev_get_touch_status; + fido_dev_has_pin; + fido_dev_has_uv; + 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_set; + fido_dev_info_vendor; + fido_dev_io_handle; + fido_dev_is_fido2; + fido_dev_is_winhello; + fido_dev_major; + fido_dev_make_cred; + fido_dev_minor; + fido_dev_new; + fido_dev_new_with_info; + fido_dev_open; + fido_dev_open_with_info; + fido_dev_protocol; + fido_dev_reset; + fido_dev_set_io_functions; + fido_dev_set_pin; + fido_dev_set_pin_minlen; + fido_dev_set_pin_minlen_rpid; + fido_dev_set_sigmask; + fido_dev_set_timeout; + fido_dev_set_transport_functions; + fido_dev_supports_cred_prot; + fido_dev_supports_credman; + fido_dev_supports_permissions; + fido_dev_supports_pin; + fido_dev_supports_uv; + fido_dev_toggle_always_uv; + fido_dev_largeblob_get; + fido_dev_largeblob_get_array; + fido_dev_largeblob_remove; + fido_dev_largeblob_set; + fido_dev_largeblob_set_array; + fido_init; + fido_set_log_handler; + fido_strerr; + rs256_pk_free; + rs256_pk_from_ptr; + rs256_pk_from_EVP_PKEY; + rs256_pk_from_RSA; + rs256_pk_new; + rs256_pk_to_EVP_PKEY; + local: + *; +}; diff --git a/src/export.llvm b/src/export.llvm new file mode 100644 index 0000000..bd21fb4 --- /dev/null +++ b/src/export.llvm @@ -0,0 +1,258 @@ +_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_EVP_PKEY +_es256_pk_from_ptr +_es256_pk_new +_es256_pk_to_EVP_PKEY +_es384_pk_free +_es384_pk_from_EC_KEY +_es384_pk_from_EVP_PKEY +_es384_pk_from_ptr +_es384_pk_new +_es384_pk_to_EVP_PKEY +_fido_assert_allow_cred +_fido_assert_authdata_len +_fido_assert_authdata_ptr +_fido_assert_blob_len +_fido_assert_blob_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_largeblob_key_len +_fido_assert_largeblob_key_ptr +_fido_assert_new +_fido_assert_rp_id +_fido_assert_set_authdata +_fido_assert_set_authdata_raw +_fido_assert_set_clientdata +_fido_assert_set_clientdata_hash +_fido_assert_set_count +_fido_assert_set_extensions +_fido_assert_set_hmac_salt +_fido_assert_set_hmac_secret +_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_algorithm_cose +_fido_cbor_info_algorithm_count +_fido_cbor_info_algorithm_type +_fido_cbor_info_certs_len +_fido_cbor_info_certs_name_ptr +_fido_cbor_info_certs_value_ptr +_fido_cbor_info_extensions_len +_fido_cbor_info_extensions_ptr +_fido_cbor_info_free +_fido_cbor_info_fwversion +_fido_cbor_info_maxcredbloblen +_fido_cbor_info_maxcredcntlst +_fido_cbor_info_maxcredidlen +_fido_cbor_info_maxlargeblob +_fido_cbor_info_maxmsgsiz +_fido_cbor_info_maxrpid_minpinlen +_fido_cbor_info_minpinlen +_fido_cbor_info_new +_fido_cbor_info_new_pin_required +_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_rk_remaining +_fido_cbor_info_transports_len +_fido_cbor_info_transports_ptr +_fido_cbor_info_uv_attempts +_fido_cbor_info_uv_modality +_fido_cbor_info_versions_len +_fido_cbor_info_versions_ptr +_fido_cred_attstmt_len +_fido_cred_attstmt_ptr +_fido_cred_authdata_len +_fido_cred_authdata_ptr +_fido_cred_authdata_raw_len +_fido_cred_authdata_raw_ptr +_fido_cred_clientdata_hash_len +_fido_cred_clientdata_hash_ptr +_fido_cred_display_name +_fido_cred_exclude +_fido_cred_flags +_fido_cred_largeblob_key_len +_fido_cred_largeblob_key_ptr +_fido_cred_sigcount +_fido_cred_fmt +_fido_cred_free +_fido_cred_id_len +_fido_cred_id_ptr +_fido_cred_aaguid_len +_fido_cred_aaguid_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_credman_set_dev_rk +_fido_cred_new +_fido_cred_pin_minlen +_fido_cred_prot +_fido_cred_pubkey_len +_fido_cred_pubkey_ptr +_fido_cred_rp_id +_fido_cred_rp_name +_fido_cred_set_attstmt +_fido_cred_set_authdata +_fido_cred_set_authdata_raw +_fido_cred_set_blob +_fido_cred_set_clientdata +_fido_cred_set_clientdata_hash +_fido_cred_set_extensions +_fido_cred_set_fmt +_fido_cred_set_id +_fido_cred_set_options +_fido_cred_set_pin_minlen +_fido_cred_set_prot +_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_enable_entattest +_fido_dev_flags +_fido_dev_force_fido2 +_fido_dev_force_pin_change +_fido_dev_force_u2f +_fido_dev_free +_fido_dev_get_assert +_fido_dev_get_cbor_info +_fido_dev_get_retry_count +_fido_dev_get_uv_retry_count +_fido_dev_get_touch_begin +_fido_dev_get_touch_status +_fido_dev_has_pin +_fido_dev_has_uv +_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_set +_fido_dev_info_vendor +_fido_dev_io_handle +_fido_dev_is_fido2 +_fido_dev_is_winhello +_fido_dev_major +_fido_dev_make_cred +_fido_dev_minor +_fido_dev_new +_fido_dev_new_with_info +_fido_dev_open +_fido_dev_open_with_info +_fido_dev_protocol +_fido_dev_reset +_fido_dev_set_io_functions +_fido_dev_set_pin +_fido_dev_set_pin_minlen +_fido_dev_set_pin_minlen_rpid +_fido_dev_set_sigmask +_fido_dev_set_timeout +_fido_dev_set_transport_functions +_fido_dev_supports_cred_prot +_fido_dev_supports_credman +_fido_dev_supports_permissions +_fido_dev_supports_pin +_fido_dev_supports_uv +_fido_dev_toggle_always_uv +_fido_dev_largeblob_get +_fido_dev_largeblob_get_array +_fido_dev_largeblob_remove +_fido_dev_largeblob_set +_fido_dev_largeblob_set_array +_fido_init +_fido_set_log_handler +_fido_strerr +_rs256_pk_free +_rs256_pk_from_ptr +_rs256_pk_from_EVP_PKEY +_rs256_pk_from_RSA +_rs256_pk_new +_rs256_pk_to_EVP_PKEY diff --git a/src/export.msvc b/src/export.msvc new file mode 100644 index 0000000..55147f9 --- /dev/null +++ b/src/export.msvc @@ -0,0 +1,259 @@ +EXPORTS +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_EVP_PKEY +es256_pk_from_ptr +es256_pk_new +es256_pk_to_EVP_PKEY +es384_pk_free +es384_pk_from_EC_KEY +es384_pk_from_EVP_PKEY +es384_pk_from_ptr +es384_pk_new +es384_pk_to_EVP_PKEY +fido_assert_allow_cred +fido_assert_authdata_len +fido_assert_authdata_ptr +fido_assert_blob_len +fido_assert_blob_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_largeblob_key_len +fido_assert_largeblob_key_ptr +fido_assert_new +fido_assert_rp_id +fido_assert_set_authdata +fido_assert_set_authdata_raw +fido_assert_set_clientdata +fido_assert_set_clientdata_hash +fido_assert_set_count +fido_assert_set_extensions +fido_assert_set_hmac_salt +fido_assert_set_hmac_secret +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_algorithm_cose +fido_cbor_info_algorithm_count +fido_cbor_info_algorithm_type +fido_cbor_info_certs_len +fido_cbor_info_certs_name_ptr +fido_cbor_info_certs_value_ptr +fido_cbor_info_extensions_len +fido_cbor_info_extensions_ptr +fido_cbor_info_free +fido_cbor_info_fwversion +fido_cbor_info_maxcredbloblen +fido_cbor_info_maxcredcntlst +fido_cbor_info_maxcredidlen +fido_cbor_info_maxlargeblob +fido_cbor_info_maxmsgsiz +fido_cbor_info_maxrpid_minpinlen +fido_cbor_info_minpinlen +fido_cbor_info_new +fido_cbor_info_new_pin_required +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_rk_remaining +fido_cbor_info_transports_len +fido_cbor_info_transports_ptr +fido_cbor_info_uv_attempts +fido_cbor_info_uv_modality +fido_cbor_info_versions_len +fido_cbor_info_versions_ptr +fido_cred_attstmt_len +fido_cred_attstmt_ptr +fido_cred_authdata_len +fido_cred_authdata_ptr +fido_cred_authdata_raw_len +fido_cred_authdata_raw_ptr +fido_cred_clientdata_hash_len +fido_cred_clientdata_hash_ptr +fido_cred_display_name +fido_cred_exclude +fido_cred_flags +fido_cred_largeblob_key_len +fido_cred_largeblob_key_ptr +fido_cred_sigcount +fido_cred_fmt +fido_cred_free +fido_cred_id_len +fido_cred_id_ptr +fido_cred_aaguid_len +fido_cred_aaguid_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_credman_set_dev_rk +fido_cred_new +fido_cred_pin_minlen +fido_cred_prot +fido_cred_pubkey_len +fido_cred_pubkey_ptr +fido_cred_rp_id +fido_cred_rp_name +fido_cred_set_attstmt +fido_cred_set_authdata +fido_cred_set_authdata_raw +fido_cred_set_blob +fido_cred_set_clientdata +fido_cred_set_clientdata_hash +fido_cred_set_extensions +fido_cred_set_fmt +fido_cred_set_id +fido_cred_set_options +fido_cred_set_pin_minlen +fido_cred_set_prot +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_enable_entattest +fido_dev_flags +fido_dev_force_fido2 +fido_dev_force_pin_change +fido_dev_force_u2f +fido_dev_free +fido_dev_get_assert +fido_dev_get_cbor_info +fido_dev_get_retry_count +fido_dev_get_uv_retry_count +fido_dev_get_touch_begin +fido_dev_get_touch_status +fido_dev_has_pin +fido_dev_has_uv +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_set +fido_dev_info_vendor +fido_dev_io_handle +fido_dev_is_fido2 +fido_dev_is_winhello +fido_dev_major +fido_dev_make_cred +fido_dev_minor +fido_dev_new +fido_dev_new_with_info +fido_dev_open +fido_dev_open_with_info +fido_dev_protocol +fido_dev_reset +fido_dev_set_io_functions +fido_dev_set_pin +fido_dev_set_pin_minlen +fido_dev_set_pin_minlen_rpid +fido_dev_set_sigmask +fido_dev_set_timeout +fido_dev_set_transport_functions +fido_dev_supports_cred_prot +fido_dev_supports_credman +fido_dev_supports_permissions +fido_dev_supports_pin +fido_dev_supports_uv +fido_dev_toggle_always_uv +fido_dev_largeblob_get +fido_dev_largeblob_get_array +fido_dev_largeblob_remove +fido_dev_largeblob_set +fido_dev_largeblob_set_array +fido_init +fido_set_log_handler +fido_strerr +rs256_pk_free +rs256_pk_from_ptr +rs256_pk_from_EVP_PKEY +rs256_pk_from_RSA +rs256_pk_new +rs256_pk_to_EVP_PKEY diff --git a/src/extern.h b/src/extern.h new file mode 100644 index 0000000..1bc95b2 --- /dev/null +++ b/src/extern.h @@ -0,0 +1,276 @@ +/* + * Copyright (c) 2018-2022 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. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#ifndef _EXTERN_H +#define _EXTERN_H + +#ifdef __MINGW32__ +#include <sys/types.h> +#endif + +#ifdef HAVE_SIGNAL_H +#include <signal.h> +#endif + +#include <stdint.h> + +#include "fido/types.h" +#include "blob.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* aes256 */ +int aes256_cbc_dec(const fido_dev_t *dev, const fido_blob_t *, + const fido_blob_t *, fido_blob_t *); +int aes256_cbc_enc(const fido_dev_t *dev, const fido_blob_t *, + const fido_blob_t *, fido_blob_t *); +int aes256_gcm_dec(const fido_blob_t *, const fido_blob_t *, + const fido_blob_t *, const fido_blob_t *, fido_blob_t *); +int aes256_gcm_enc(const fido_blob_t *, const fido_blob_t *, + const fido_blob_t *, const fido_blob_t *, fido_blob_t *); + +/* cbor encoding functions */ +cbor_item_t *cbor_build_uint(const uint64_t); +cbor_item_t *cbor_flatten_vector(cbor_item_t **, size_t); +cbor_item_t *cbor_encode_assert_opt(fido_opt_t, fido_opt_t); +cbor_item_t *cbor_encode_change_pin_auth(const fido_dev_t *, + const fido_blob_t *, const fido_blob_t *, const fido_blob_t *); +cbor_item_t *cbor_encode_cred_ext(const fido_cred_ext_t *, const fido_blob_t *); +cbor_item_t *cbor_encode_assert_ext(fido_dev_t *, + const fido_assert_ext_t *, const fido_blob_t *, const es256_pk_t *); +cbor_item_t *cbor_encode_cred_opt(fido_opt_t, fido_opt_t); +cbor_item_t *cbor_encode_pin_auth(const fido_dev_t *, const fido_blob_t *, + const fido_blob_t *); +cbor_item_t *cbor_encode_pin_opt(const fido_dev_t *); +cbor_item_t *cbor_encode_pubkey(const fido_blob_t *); +cbor_item_t *cbor_encode_pubkey_list(const fido_blob_array_t *); +cbor_item_t *cbor_encode_pubkey_param(int); +cbor_item_t *cbor_encode_rp_entity(const fido_rp_t *); +cbor_item_t *cbor_encode_str_array(const fido_str_array_t *); +cbor_item_t *cbor_encode_user_entity(const fido_user_t *); +cbor_item_t *es256_pk_encode(const es256_pk_t *, int); + +/* cbor decoding functions */ +int cbor_decode_attstmt(const cbor_item_t *, fido_attstmt_t *); +int cbor_decode_bool(const cbor_item_t *, bool *); +int cbor_decode_cred_authdata(const cbor_item_t *, int, fido_blob_t *, + fido_authdata_t *, fido_attcred_t *, fido_cred_ext_t *); +int cbor_decode_assert_authdata(const cbor_item_t *, fido_blob_t *, + fido_authdata_t *, fido_assert_extattr_t *); +int cbor_decode_cred_id(const cbor_item_t *, fido_blob_t *); +int cbor_decode_fmt(const cbor_item_t *, char **); +int cbor_decode_pubkey(const cbor_item_t *, int *, void *); +int cbor_decode_rp_entity(const cbor_item_t *, fido_rp_t *); +int cbor_decode_uint64(const cbor_item_t *, uint64_t *); +int cbor_decode_user(const cbor_item_t *, fido_user_t *); +int es256_pk_decode(const cbor_item_t *, es256_pk_t *); +int es384_pk_decode(const cbor_item_t *, es384_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 cbor_parse_reply(const unsigned char *, size_t, void *, + int(*)(const cbor_item_t *, const cbor_item_t *, void *)); +int cbor_add_uv_params(fido_dev_t *, uint8_t, const fido_blob_t *, + const es256_pk_t *, const fido_blob_t *, const char *, const char *, + cbor_item_t **, cbor_item_t **, int *); +void cbor_vector_free(cbor_item_t **, size_t); +int cbor_array_append(cbor_item_t **, cbor_item_t *); +int cbor_array_drop(cbor_item_t **, size_t); + +/* deflate */ +int fido_compress(fido_blob_t *, const fido_blob_t *); +int fido_uncompress(fido_blob_t *, const fido_blob_t *, size_t); + +#ifndef nitems +#define nitems(_a) (sizeof((_a)) / sizeof((_a)[0])) +#endif + +/* buf */ +int fido_buf_read(const unsigned char **, size_t *, void *, size_t); +int fido_buf_write(unsigned char **, size_t *, const void *, size_t); + +/* hid i/o */ +void *fido_hid_open(const char *); +void fido_hid_close(void *); +int fido_hid_read(void *, unsigned char *, size_t, int); +int fido_hid_write(void *, const unsigned char *, size_t); +int fido_hid_get_usage(const uint8_t *, size_t, uint32_t *); +int fido_hid_get_report_len(const uint8_t *, size_t, size_t *, size_t *); +int fido_hid_unix_open(const char *); +int fido_hid_unix_wait(int, int, const fido_sigset_t *); +int fido_hid_set_sigmask(void *, const fido_sigset_t *); +size_t fido_hid_report_in_len(void *); +size_t fido_hid_report_out_len(void *); + +/* nfc i/o */ +bool fido_is_nfc(const char *); +bool nfc_is_fido(const char *); +void *fido_nfc_open(const char *); +void fido_nfc_close(void *); +int fido_nfc_read(void *, unsigned char *, size_t, int); +int fido_nfc_write(void *, const unsigned char *, size_t); +int fido_nfc_rx(fido_dev_t *, uint8_t, unsigned char *, size_t, int); +int fido_nfc_tx(fido_dev_t *, uint8_t, const unsigned char *, size_t); +int fido_nfc_set_sigmask(void *, const fido_sigset_t *); +int fido_dev_set_nfc(fido_dev_t *); + +/* pcsc i/o */ +bool fido_is_pcsc(const char *); +void *fido_pcsc_open(const char *); +void fido_pcsc_close(void *); +int fido_pcsc_read(void *, unsigned char *, size_t, int); +int fido_pcsc_write(void *, const unsigned char *, size_t); +int fido_pcsc_rx(fido_dev_t *, uint8_t, unsigned char *, size_t, int); +int fido_pcsc_tx(fido_dev_t *, uint8_t, const unsigned char *, size_t); +int fido_dev_set_pcsc(fido_dev_t *); + +/* windows hello */ +int fido_winhello_manifest(fido_dev_info_t *, size_t, size_t *); +int fido_winhello_open(fido_dev_t *); +int fido_winhello_close(fido_dev_t *); +int fido_winhello_cancel(fido_dev_t *); +int fido_winhello_get_assert(fido_dev_t *, fido_assert_t *, const char *, int); +int fido_winhello_get_cbor_info(fido_dev_t *, fido_cbor_info_t *); +int fido_winhello_make_cred(fido_dev_t *, fido_cred_t *, const char *, int); + +/* generic i/o */ +int fido_rx_cbor_status(fido_dev_t *, int *); +int fido_rx(fido_dev_t *, uint8_t, void *, size_t, int *); +int fido_tx(fido_dev_t *, uint8_t, const void *, size_t, int *); + +/* log */ +#ifdef FIDO_NO_DIAGNOSTIC +#define fido_log_init(...) do { /* nothing */ } while (0) +#define fido_log_debug(...) do { /* nothing */ } while (0) +#define fido_log_xxd(...) do { /* nothing */ } while (0) +#define fido_log_error(...) do { /* nothing */ } while (0) +#else +#ifdef __GNUC__ +void fido_log_init(void); +void fido_log_debug(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); +void fido_log_xxd(const void *, size_t, const char *, ...) + __attribute__((__format__ (printf, 3, 4))); +void fido_log_error(int, const char *, ...) + __attribute__((__format__ (printf, 2, 3))); +#else +void fido_log_init(void); +void fido_log_debug(const char *, ...); +void fido_log_xxd(const void *, size_t, const char *, ...); +void fido_log_error(int, const char *, ...); +#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 *); +int u2f_get_touch_begin(fido_dev_t *, int *); +int u2f_get_touch_status(fido_dev_t *, int *, int *); + +/* unexposed fido ops */ +uint8_t fido_dev_get_pin_protocol(const fido_dev_t *); +int fido_dev_authkey(fido_dev_t *, es256_pk_t *, int *); +int fido_dev_get_cbor_info_wait(fido_dev_t *, fido_cbor_info_t *, int *); +int fido_dev_get_uv_token(fido_dev_t *, uint8_t, const char *, + const fido_blob_t *, const es256_pk_t *, const char *, fido_blob_t *, + int *); +uint64_t fido_dev_maxmsgsize(const fido_dev_t *); +int fido_do_ecdh(fido_dev_t *, es256_pk_t **, fido_blob_t **, int *); + +/* types */ +void fido_algo_array_free(fido_algo_array_t *); +void fido_byte_array_free(fido_byte_array_t *); +void fido_cert_array_free(fido_cert_array_t *); +void fido_opt_array_free(fido_opt_array_t *); +void fido_str_array_free(fido_str_array_t *); +void fido_algo_free(fido_algo_t *); +int fido_str_array_pack(fido_str_array_t *, const char * const *, size_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 *); +void fido_cbor_info_reset(fido_cbor_info_t *); +int fido_blob_serialise(fido_blob_t *, const cbor_item_t *); +int fido_check_flags(uint8_t, fido_opt_t, fido_opt_t); +int fido_check_rp_id(const char *, const unsigned char *); +int fido_get_random(void *, size_t); +int fido_sha256(fido_blob_t *, const u_char *, size_t); +int fido_time_now(struct timespec *); +int fido_time_delta(const struct timespec *, int *); +int fido_to_uint64(const char *, int, uint64_t *); + +/* crypto */ +int es256_verify_sig(const fido_blob_t *, EVP_PKEY *, const fido_blob_t *); +int es384_verify_sig(const fido_blob_t *, EVP_PKEY *, const fido_blob_t *); +int rs256_verify_sig(const fido_blob_t *, EVP_PKEY *, const fido_blob_t *); +int eddsa_verify_sig(const fido_blob_t *, EVP_PKEY *, const fido_blob_t *); +int rs1_verify_sig(const fido_blob_t *, EVP_PKEY *, const fido_blob_t *); +int es256_pk_verify_sig(const fido_blob_t *, const es256_pk_t *, + const fido_blob_t *); +int es384_pk_verify_sig(const fido_blob_t *, const es384_pk_t *, + const fido_blob_t *); +int rs256_pk_verify_sig(const fido_blob_t *, const rs256_pk_t *, + const fido_blob_t *); +int eddsa_pk_verify_sig(const fido_blob_t *, const eddsa_pk_t *, + const fido_blob_t *); +int fido_get_signed_hash(int, fido_blob_t *, const fido_blob_t *, + const fido_blob_t *); +int fido_get_signed_hash_tpm(fido_blob_t *, const fido_blob_t *, + const fido_blob_t *, const fido_attstmt_t *, const fido_attcred_t *); + +/* device manifest functions */ +int fido_hid_manifest(fido_dev_info_t *, size_t, size_t *); +int fido_nfc_manifest(fido_dev_info_t *, size_t, size_t *); +int fido_pcsc_manifest(fido_dev_info_t *, size_t, size_t *); + +/* fuzzing instrumentation */ +#ifdef FIDO_FUZZ +uint32_t uniform_random(uint32_t); +#endif + +/* internal device capability flags */ +#define FIDO_DEV_PIN_SET 0x001 +#define FIDO_DEV_PIN_UNSET 0x002 +#define FIDO_DEV_CRED_PROT 0x004 +#define FIDO_DEV_CREDMAN 0x008 +#define FIDO_DEV_PIN_PROTOCOL1 0x010 +#define FIDO_DEV_PIN_PROTOCOL2 0x020 +#define FIDO_DEV_UV_SET 0x040 +#define FIDO_DEV_UV_UNSET 0x080 +#define FIDO_DEV_TOKEN_PERMS 0x100 +#define FIDO_DEV_WINHELLO 0x200 + +/* miscellanea */ +#define FIDO_DUMMY_CLIENTDATA "" +#define FIDO_DUMMY_RP_ID "localhost" +#define FIDO_DUMMY_USER_NAME "dummy" +#define FIDO_DUMMY_USER_ID 1 +#define FIDO_WINHELLO_PATH "windows://hello" +#define FIDO_NFC_PREFIX "nfc:" +#define FIDO_PCSC_PREFIX "pcsc:" + +#ifdef __cplusplus +} /* extern "C" */ +#endif /* __cplusplus */ + +#endif /* !_EXTERN_H */ diff --git a/src/fallthrough.h b/src/fallthrough.h new file mode 100644 index 0000000..bdfd30f --- /dev/null +++ b/src/fallthrough.h @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2022 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. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#ifndef _FALLTHROUGH_H +#define _FALLTHROUGH_H + +#if defined(__GNUC__) +#if __has_attribute(fallthrough) +#define FALLTHROUGH __attribute__((fallthrough)); +#endif +#endif /* __GNUC__ */ + +#ifndef FALLTHROUGH +#define FALLTHROUGH /* FALLTHROUGH */ +#endif + +#endif /* !_FALLTHROUGH_H */ diff --git a/src/fido.h b/src/fido.h new file mode 100644 index 0000000..ce7da16 --- /dev/null +++ b/src/fido.h @@ -0,0 +1,272 @@ +/* + * Copyright (c) 2018-2022 Yubico AB. All rights reserved. + * SPDX-License-Identifier: BSD-2-Clause + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _FIDO_H +#define _FIDO_H + +#include <openssl/ec.h> +#include <openssl/evp.h> + +#include <stdbool.h> +#include <stdint.h> +#include <stdlib.h> + +#ifdef _FIDO_INTERNAL +#include <sys/types.h> + +#include <cbor.h> +#include <limits.h> + +#include "../openbsd-compat/openbsd-compat.h" +#include "blob.h" +#include "iso7816.h" +#include "extern.h" +#endif + +#include "fido/err.h" +#include "fido/param.h" +#include "fido/types.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +fido_assert_t *fido_assert_new(void); +fido_cred_t *fido_cred_new(void); +fido_dev_t *fido_dev_new(void); +fido_dev_t *fido_dev_new_with_info(const fido_dev_info_t *); +fido_dev_info_t *fido_dev_info_new(size_t); +fido_cbor_info_t *fido_cbor_info_new(void); +void *fido_dev_io_handle(const fido_dev_t *); + +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 +#define FIDO_DISABLE_U2F_FALLBACK 0x02 + +void fido_init(int); +void fido_set_log_handler(fido_log_handler_t *); + +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_largeblob_key_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); +const unsigned char *fido_assert_blob_ptr(const fido_assert_t *, size_t); + +char **fido_cbor_info_certs_name_ptr(const fido_cbor_info_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_transports_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_cbor_info_algorithm_type(const fido_cbor_info_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 uint64_t *fido_cbor_info_certs_value_ptr(const fido_cbor_info_t *); +const unsigned char *fido_cbor_info_aaguid_ptr(const fido_cbor_info_t *); +const unsigned char *fido_cred_aaguid_ptr(const fido_cred_t *); +const unsigned char *fido_cred_attstmt_ptr(const fido_cred_t *); +const unsigned char *fido_cred_authdata_ptr(const fido_cred_t *); +const unsigned char *fido_cred_authdata_raw_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_largeblob_key_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_user_id_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(fido_assert_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_hmac_secret(fido_assert_t *, size_t, const unsigned char *, + size_t); +int fido_assert_set_options(fido_assert_t *, bool, bool); +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_cbor_info_algorithm_cose(const fido_cbor_info_t *, size_t); +int fido_cred_exclude(fido_cred_t *, const unsigned char *, size_t); +int fido_cred_prot(const fido_cred_t *); +int fido_cred_set_attstmt(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_blob(fido_cred_t *, const unsigned char *, size_t); +int fido_cred_set_clientdata(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_id(fido_cred_t *, const unsigned char *, size_t); +int fido_cred_set_options(fido_cred_t *, bool, bool); +int fido_cred_set_pin_minlen(fido_cred_t *, size_t); +int fido_cred_set_prot(fido_cred_t *, int); +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 *); +#ifdef _FIDO_SIGSET_DEFINED +int fido_dev_set_sigmask(fido_dev_t *, const fido_sigset_t *); +#endif +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_get_uv_retry_count(fido_dev_t *, int *); +int fido_dev_get_touch_begin(fido_dev_t *); +int fido_dev_get_touch_status(fido_dev_t *, int *, int); +int fido_dev_info_manifest(fido_dev_info_t *, size_t, size_t *); +int fido_dev_info_set(fido_dev_info_t *, size_t, const char *, const char *, + const char *, const fido_dev_io_t *, const fido_dev_transport_t *); +int fido_dev_make_cred(fido_dev_t *, fido_cred_t *, const char *); +int fido_dev_open_with_info(fido_dev_t *); +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 *); +int fido_dev_set_transport_functions(fido_dev_t *, const fido_dev_transport_t *); +int fido_dev_set_timeout(fido_dev_t *, int); + +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_largeblob_key_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_assert_blob_len(const fido_assert_t *, size_t); +size_t fido_cbor_info_aaguid_len(const fido_cbor_info_t *); +size_t fido_cbor_info_algorithm_count(const fido_cbor_info_t *); +size_t fido_cbor_info_certs_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_transports_len(const fido_cbor_info_t *); +size_t fido_cbor_info_versions_len(const fido_cbor_info_t *); +size_t fido_cred_aaguid_len(const fido_cred_t *); +size_t fido_cred_attstmt_len(const fido_cred_t *); +size_t fido_cred_authdata_len(const fido_cred_t *); +size_t fido_cred_authdata_raw_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_largeblob_key_len(const fido_cred_t *); +size_t fido_cred_pin_minlen(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_user_id_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 *); +uint32_t fido_cred_sigcount(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_fwversion(const fido_cbor_info_t *); +uint64_t fido_cbor_info_maxcredbloblen(const fido_cbor_info_t *); +uint64_t fido_cbor_info_maxcredcntlst(const fido_cbor_info_t *); +uint64_t fido_cbor_info_maxcredidlen(const fido_cbor_info_t *); +uint64_t fido_cbor_info_maxlargeblob(const fido_cbor_info_t *); +uint64_t fido_cbor_info_maxmsgsiz(const fido_cbor_info_t *); +uint64_t fido_cbor_info_maxrpid_minpinlen(const fido_cbor_info_t *); +uint64_t fido_cbor_info_minpinlen(const fido_cbor_info_t *); +uint64_t fido_cbor_info_uv_attempts(const fido_cbor_info_t *); +uint64_t fido_cbor_info_uv_modality(const fido_cbor_info_t *); +int64_t fido_cbor_info_rk_remaining(const fido_cbor_info_t *); + +bool fido_dev_has_pin(const fido_dev_t *); +bool fido_dev_has_uv(const fido_dev_t *); +bool fido_dev_is_fido2(const fido_dev_t *); +bool fido_dev_is_winhello(const fido_dev_t *); +bool fido_dev_supports_credman(const fido_dev_t *); +bool fido_dev_supports_cred_prot(const fido_dev_t *); +bool fido_dev_supports_permissions(const fido_dev_t *); +bool fido_dev_supports_pin(const fido_dev_t *); +bool fido_dev_supports_uv(const fido_dev_t *); +bool fido_cbor_info_new_pin_required(const fido_cbor_info_t *); + +int fido_dev_largeblob_get(fido_dev_t *, const unsigned char *, size_t, + unsigned char **, size_t *); +int fido_dev_largeblob_set(fido_dev_t *, const unsigned char *, size_t, + const unsigned char *, size_t, const char *); +int fido_dev_largeblob_remove(fido_dev_t *, const unsigned char *, size_t, + const char *); +int fido_dev_largeblob_get_array(fido_dev_t *, unsigned char **, size_t *); +int fido_dev_largeblob_set_array(fido_dev_t *, const unsigned char *, size_t, + const char *); + +#ifdef __cplusplus +} /* extern "C" */ +#endif /* __cplusplus */ + +#endif /* !_FIDO_H */ diff --git a/src/fido/bio.h b/src/fido/bio.h new file mode 100644 index 0000000..f5039e0 --- /dev/null +++ b/src/fido/bio.h @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2019 Yubico AB. All rights reserved. + * SPDX-License-Identifier: BSD-2-Clause + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _FIDO_BIO_H +#define _FIDO_BIO_H + +#include <stdint.h> +#include <stdlib.h> + +#ifdef _FIDO_INTERNAL +#include "blob.h" +#include "fido/err.h" +#include "fido/param.h" +#include "fido/types.h" +#else +#include <fido.h> +#include <fido/err.h> +#include <fido/param.h> +#endif + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#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 **); + +#ifdef __cplusplus +} /* extern "C" */ +#endif /* __cplusplus */ + +#endif /* !_FIDO_BIO_H */ diff --git a/src/fido/config.h b/src/fido/config.h new file mode 100644 index 0000000..cba286f --- /dev/null +++ b/src/fido/config.h @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2020 Yubico AB. All rights reserved. + * SPDX-License-Identifier: BSD-2-Clause + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _FIDO_CONFIG_H +#define _FIDO_CONFIG_H + +#ifdef _FIDO_INTERNAL +#include "blob.h" +#include "fido/err.h" +#include "fido/param.h" +#include "fido/types.h" +#else +#include <fido.h> +#include <fido/err.h> +#include <fido/param.h> +#endif + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +int fido_dev_enable_entattest(fido_dev_t *, const char *); +int fido_dev_force_pin_change(fido_dev_t *, const char *); +int fido_dev_toggle_always_uv(fido_dev_t *, const char *); +int fido_dev_set_pin_minlen(fido_dev_t *, size_t, const char *); +int fido_dev_set_pin_minlen_rpid(fido_dev_t *, const char * const *, size_t, + const char *); + +#ifdef __cplusplus +} /* extern "C" */ +#endif /* __cplusplus */ + +#endif /* !_FIDO_CONFIG_H */ diff --git a/src/fido/credman.h b/src/fido/credman.h new file mode 100644 index 0000000..9f9dff1 --- /dev/null +++ b/src/fido/credman.h @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2019-2021 Yubico AB. All rights reserved. + * SPDX-License-Identifier: BSD-2-Clause + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _FIDO_CREDMAN_H +#define _FIDO_CREDMAN_H + +#include <stdint.h> +#include <stdlib.h> + +#ifdef _FIDO_INTERNAL +#include "blob.h" +#include "fido/err.h" +#include "fido/param.h" +#include "fido/types.h" +#else +#include <fido.h> +#include <fido/err.h> +#include <fido/param.h> +#endif + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#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 *); +int fido_credman_set_dev_rk(fido_dev_t *, fido_cred_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 **); + +#ifdef __cplusplus +} /* extern "C" */ +#endif /* __cplusplus */ + +#endif /* !_FIDO_CREDMAN_H */ diff --git a/src/fido/eddsa.h b/src/fido/eddsa.h new file mode 100644 index 0000000..7981a6f --- /dev/null +++ b/src/fido/eddsa.h @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2019 Yubico AB. All rights reserved. + * SPDX-License-Identifier: BSD-2-Clause + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _FIDO_EDDSA_H +#define _FIDO_EDDSA_H + +#include <openssl/ec.h> + +#include <stdint.h> +#include <stdlib.h> + +#ifdef _FIDO_INTERNAL +#include "types.h" +#else +#include <fido.h> +#endif + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +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) +#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 */ + +#endif /* _FIDO_INTERNAL */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif /* __cplusplus */ + +#endif /* !_FIDO_EDDSA_H */ diff --git a/src/fido/err.h b/src/fido/err.h new file mode 100644 index 0000000..7db25f2 --- /dev/null +++ b/src/fido/err.h @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2018 Yubico AB. All rights reserved. + * SPDX-License-Identifier: BSD-2-Clause + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#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_FP_DATABASE_FULL 0x17 +#define FIDO_ERR_LARGEBLOB_STORAGE_FULL 0x18 +#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_UV_BLOCKED 0x3c +#define FIDO_ERR_UV_INVALID 0x3f +#define FIDO_ERR_UNAUTHORIZED_PERM 0x40 +#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 +#define FIDO_ERR_NOTFOUND -10 +#define FIDO_ERR_COMPRESS -11 + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +const char *fido_strerr(int); + +#ifdef __cplusplus +} /* extern "C" */ +#endif /* __cplusplus */ + +#endif /* _FIDO_ERR_H */ diff --git a/src/fido/es256.h b/src/fido/es256.h new file mode 100644 index 0000000..0450de2 --- /dev/null +++ b/src/fido/es256.h @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2018-2021 Yubico AB. All rights reserved. + * SPDX-License-Identifier: BSD-2-Clause + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _FIDO_ES256_H +#define _FIDO_ES256_H + +#include <openssl/ec.h> + +#include <stdint.h> +#include <stdlib.h> + +#ifdef _FIDO_INTERNAL +#include "types.h" +#else +#include <fido.h> +#endif + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +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_EVP_PKEY(es256_pk_t *, const EVP_PKEY *); +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 + +#ifdef __cplusplus +} /* extern "C" */ +#endif /* __cplusplus */ + +#endif /* !_FIDO_ES256_H */ diff --git a/src/fido/es384.h b/src/fido/es384.h new file mode 100644 index 0000000..b4b4ca7 --- /dev/null +++ b/src/fido/es384.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2022 Yubico AB. All rights reserved. + * SPDX-License-Identifier: BSD-2-Clause + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _FIDO_ES384_H +#define _FIDO_ES384_H + +#include <openssl/ec.h> + +#include <stdint.h> +#include <stdlib.h> + +#ifdef _FIDO_INTERNAL +#include "types.h" +#else +#include <fido.h> +#endif + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +es384_pk_t *es384_pk_new(void); +void es384_pk_free(es384_pk_t **); +EVP_PKEY *es384_pk_to_EVP_PKEY(const es384_pk_t *); + +int es384_pk_from_EC_KEY(es384_pk_t *, const EC_KEY *); +int es384_pk_from_EVP_PKEY(es384_pk_t *, const EVP_PKEY *); +int es384_pk_from_ptr(es384_pk_t *, const void *, size_t); + +#ifdef __cplusplus +} /* extern "C" */ +#endif /* __cplusplus */ + +#endif /* !_FIDO_ES384_H */ diff --git a/src/fido/param.h b/src/fido/param.h new file mode 100644 index 0000000..511370b --- /dev/null +++ b/src/fido/param.h @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2018-2022 Yubico AB. All rights reserved. + * SPDX-License-Identifier: BSD-2-Clause + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#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_LARGEBLOB 0x0c +#define CTAP_CBOR_CONFIG 0x0d +#define CTAP_CBOR_BIO_ENROLL_PRE 0x40 +#define CTAP_CBOR_CRED_MGMT_PRE 0x41 + +/* Supported CTAP PIN/UV Auth Protocols. */ +#define CTAP_PIN_PROTOCOL1 1 +#define CTAP_PIN_PROTOCOL2 2 + +/* 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 SW1_MORE_DATA 0x61 +#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 + +#define CTAP_INIT_HEADER_LEN 7 +#define CTAP_CONT_HEADER_LEN 5 + +/* Maximum length of a CTAP HID report in bytes. */ +#define CTAP_MAX_REPORT_LEN 64 + +/* Minimum length of a CTAP HID report in bytes. */ +#define CTAP_MIN_REPORT_LEN (CTAP_INIT_HEADER_LEN + 1) + +/* Randomness device on UNIX-like platforms. */ +#ifndef FIDO_RANDOM_DEV +#define FIDO_RANDOM_DEV "/dev/urandom" +#endif + +/* Maximum message size in bytes. */ +#ifndef FIDO_MAXMSG +#define FIDO_MAXMSG 2048 +#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_UNSPEC 0 +#define COSE_ES256 -7 +#define COSE_EDDSA -8 +#define COSE_ECDH_ES256 -25 +#define COSE_ES384 -35 +#define COSE_RS256 -257 +#define COSE_RS1 -65535 + +/* 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_P384 2 +#define COSE_ED25519 6 + +/* Supported extensions. */ +#define FIDO_EXT_HMAC_SECRET 0x01 +#define FIDO_EXT_CRED_PROTECT 0x02 +#define FIDO_EXT_LARGEBLOB_KEY 0x04 +#define FIDO_EXT_CRED_BLOB 0x08 +#define FIDO_EXT_MINPINLEN 0x10 + +/* Supported credential protection policies. */ +#define FIDO_CRED_PROT_UV_OPTIONAL 0x01 +#define FIDO_CRED_PROT_UV_OPTIONAL_WITH_ID 0x02 +#define FIDO_CRED_PROT_UV_REQUIRED 0x03 + +#ifdef _FIDO_INTERNAL +#define FIDO_EXT_ASSERT_MASK (FIDO_EXT_HMAC_SECRET|FIDO_EXT_LARGEBLOB_KEY| \ + FIDO_EXT_CRED_BLOB) +#define FIDO_EXT_CRED_MASK (FIDO_EXT_HMAC_SECRET|FIDO_EXT_CRED_PROTECT| \ + FIDO_EXT_LARGEBLOB_KEY|FIDO_EXT_CRED_BLOB| \ + FIDO_EXT_MINPINLEN) +#endif /* _FIDO_INTERNAL */ + +/* Recognised UV modes. */ +#define FIDO_UV_MODE_TUP 0x0001 /* internal test of user presence */ +#define FIDO_UV_MODE_FP 0x0002 /* internal fingerprint check */ +#define FIDO_UV_MODE_PIN 0x0004 /* internal pin check */ +#define FIDO_UV_MODE_VOICE 0x0008 /* internal voice recognition */ +#define FIDO_UV_MODE_FACE 0x0010 /* internal face recognition */ +#define FIDO_UV_MODE_LOCATION 0x0020 /* internal location check */ +#define FIDO_UV_MODE_EYE 0x0040 /* internal eyeprint check */ +#define FIDO_UV_MODE_DRAWN 0x0080 /* internal drawn pattern check */ +#define FIDO_UV_MODE_HAND 0x0100 /* internal handprint verification */ +#define FIDO_UV_MODE_NONE 0x0200 /* TUP/UV not required */ +#define FIDO_UV_MODE_ALL 0x0400 /* all supported UV modes required */ +#define FIDO_UV_MODE_EXT_PIN 0x0800 /* external pin verification */ +#define FIDO_UV_MODE_EXT_DRAWN 0x1000 /* external drawn pattern check */ + +#endif /* !_FIDO_PARAM_H */ diff --git a/src/fido/rs256.h b/src/fido/rs256.h new file mode 100644 index 0000000..6f8c781 --- /dev/null +++ b/src/fido/rs256.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2018-2021 Yubico AB. All rights reserved. + * SPDX-License-Identifier: BSD-2-Clause + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _FIDO_RS256_H +#define _FIDO_RS256_H + +#include <openssl/rsa.h> + +#include <stdint.h> +#include <stdlib.h> + +#ifdef _FIDO_INTERNAL +#include "types.h" +#else +#include <fido.h> +#endif + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +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_EVP_PKEY(rs256_pk_t *, const EVP_PKEY *); +int rs256_pk_from_RSA(rs256_pk_t *, const RSA *); +int rs256_pk_from_ptr(rs256_pk_t *, const void *, size_t); + +#ifdef __cplusplus +} /* extern "C" */ +#endif /* __cplusplus */ + +#endif /* !_FIDO_RS256_H */ diff --git a/src/fido/types.h b/src/fido/types.h new file mode 100644 index 0000000..cfb4c7a --- /dev/null +++ b/src/fido/types.h @@ -0,0 +1,335 @@ +/* + * Copyright (c) 2018-2022 Yubico AB. All rights reserved. + * SPDX-License-Identifier: BSD-2-Clause + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _FIDO_TYPES_H +#define _FIDO_TYPES_H + +#ifdef __MINGW32__ +#include <sys/types.h> +#endif + +#include <signal.h> +#include <stddef.h> +#include <stdint.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +struct fido_dev; + +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 int fido_dev_rx_t(struct fido_dev *, uint8_t, unsigned char *, size_t, int); +typedef int fido_dev_tx_t(struct fido_dev *, uint8_t, 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 struct fido_dev_transport { + fido_dev_rx_t *rx; + fido_dev_tx_t *tx; +} fido_dev_transport_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; + +typedef void fido_log_handler_t(const char *); + +#undef _FIDO_SIGSET_DEFINED +#define _FIDO_SIGSET_DEFINED +#ifdef _WIN32 +typedef int fido_sigset_t; +#elif defined(SIG_BLOCK) +typedef sigset_t fido_sigset_t; +#else +#undef _FIDO_SIGSET_DEFINED +#endif + +#ifdef _FIDO_INTERNAL +#include "packed.h" +#include "blob.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 ES384 (ECDSA over P-384 with SHA-384) public key */ +typedef struct es384_pk { + unsigned char x[48]; + unsigned char y[48]; +} es384_pk_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; + es384_pk_t es384; + rs256_pk_t rs256; + eddsa_pk_t eddsa; + } pubkey; +} fido_attcred_t; + +typedef struct fido_attstmt { + fido_blob_t certinfo; /* tpm attestation TPMS_ATTEST structure */ + fido_blob_t pubarea; /* tpm attestation TPMT_PUBLIC structure */ + fido_blob_t cbor; /* cbor-encoded attestation statement */ + fido_blob_t x5c; /* attestation certificate */ + fido_blob_t sig; /* attestation signature */ + int alg; /* attestation algorithm (cose) */ +} 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_ext { + int mask; /* enabled extensions */ + int prot; /* protection policy */ + size_t minpinlen; /* minimum pin length */ +} fido_cred_ext_t; + +typedef struct fido_cred { + fido_blob_t cd; /* client data */ + 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 */ + fido_cred_ext_t ext; /* extensions */ + int type; /* cose algorithm */ + char *fmt; /* credential format */ + fido_cred_ext_t authdata_ext; /* decoded extensions */ + fido_blob_t authdata_cbor; /* cbor-encoded payload */ + fido_blob_t authdata_raw; /* cbor-decoded 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_blob_t largeblob_key; /* decoded large blob key */ + fido_blob_t blob; /* CTAP 2.1 credBlob */ +} fido_cred_t; + +typedef struct fido_assert_extattr { + int mask; /* decoded extensions */ + fido_blob_t hmac_secret_enc; /* hmac secret, encrypted */ + fido_blob_t blob; /* decoded CTAP 2.1 credBlob */ +} fido_assert_extattr_t; + +typedef struct _fido_assert_stmt { + fido_blob_t id; /* credential id */ + fido_user_t user; /* user attributes */ + fido_blob_t hmac_secret; /* hmac secret */ + fido_assert_extattr_t 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_blob_t largeblob_key; /* decoded large blob key */ +} fido_assert_stmt; + +typedef struct fido_assert_ext { + int mask; /* enabled extensions */ + fido_blob_t hmac_salt; /* optional hmac-secret salt */ +} fido_assert_ext_t; + +typedef struct fido_assert { + char *rp_id; /* relying party id */ + fido_blob_t cd; /* client data */ + fido_blob_t cdh; /* client data hash */ + fido_blob_array_t allow_list; /* list of allowed credentials */ + fido_opt_t up; /* user presence */ + fido_opt_t uv; /* user verification */ + fido_assert_ext_t 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_algo { + char *type; + int cose; +} fido_algo_t; + +typedef struct fido_algo_array { + fido_algo_t *ptr; + size_t len; +} fido_algo_array_t; + +typedef struct fido_cert_array { + char **name; + uint64_t *value; + size_t len; +} fido_cert_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 */ + fido_str_array_t transports; /* list of supported transports */ + 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_algo_array_t algorithms; /* list of supported algorithms */ + uint64_t maxcredcntlst; /* max credentials in list */ + uint64_t maxcredidlen; /* max credential ID length */ + uint64_t fwversion; /* firmware version */ + uint64_t maxcredbloblen; /* max credBlob length */ + uint64_t maxlargeblob; /* max largeBlob array length */ + uint64_t maxrpid_minlen; /* max rpid in set_pin_minlen_rpid */ + uint64_t minpinlen; /* min pin len enforced */ + uint64_t uv_attempts; /* platform uv attempts */ + uint64_t uv_modality; /* bitmask of supported uv types */ + int64_t rk_remaining; /* remaining resident credentials */ + bool new_pin_reqd; /* new pin required */ + fido_cert_array_t certs; /* associated certifications */ +} 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_io_t io; /* i/o functions */ + fido_dev_transport_t transport; /* transport functions */ +} 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 */ + char *path; /* device path */ + void *io_handle; /* abstract i/o handle */ + fido_dev_io_t io; /* i/o functions */ + bool io_own; /* device has own io/transport */ + size_t rx_len; /* length of HID input reports */ + size_t tx_len; /* length of HID output reports */ + int flags; /* internal flags; see FIDO_DEV_* */ + fido_dev_transport_t transport; /* transport functions */ + uint64_t maxmsgsize; /* max message size */ + int timeout_ms; /* read timeout in ms */ +} fido_dev_t; + +#else +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 es384_pk es384_pk_t; +typedef struct rs256_pk rs256_pk_t; +typedef struct eddsa_pk eddsa_pk_t; +#endif /* _FIDO_INTERNAL */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif /* __cplusplus */ + +#endif /* !_FIDO_TYPES_H */ diff --git a/src/hid.c b/src/hid.c new file mode 100644 index 0000000..662bd44 --- /dev/null +++ b/src/hid.c @@ -0,0 +1,222 @@ +/* + * 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. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "fido.h" + +static int +get_key_len(uint8_t tag, uint8_t *key, size_t *key_len) +{ + *key = tag & 0xfc; + if ((*key & 0xf0) == 0xf0) { + fido_log_debug("%s: *key=0x%02x", __func__, *key); + return (-1); + } + + *key_len = tag & 0x3; + if (*key_len == 3) { + *key_len = 4; + } + + return (0); +} + +static int +get_key_val(const void *body, size_t key_len, uint32_t *val) +{ + const uint8_t *ptr = body; + + switch (key_len) { + case 0: + *val = 0; + break; + case 1: + *val = ptr[0]; + break; + case 2: + *val = (uint32_t)((ptr[1] << 8) | ptr[0]); + break; + default: + fido_log_debug("%s: key_len=%zu", __func__, key_len); + return (-1); + } + + return (0); +} + +int +fido_hid_get_usage(const uint8_t *report_ptr, size_t report_len, + uint32_t *usage_page) +{ + const uint8_t *ptr = report_ptr; + size_t len = report_len; + + while (len > 0) { + const uint8_t tag = ptr[0]; + ptr++; + len--; + + uint8_t key; + size_t key_len; + uint32_t key_val; + + if (get_key_len(tag, &key, &key_len) < 0 || key_len > len || + get_key_val(ptr, key_len, &key_val) < 0) { + return (-1); + } + + if (key == 0x4) { + *usage_page = key_val; + } + + ptr += key_len; + len -= key_len; + } + + return (0); +} + +int +fido_hid_get_report_len(const uint8_t *report_ptr, size_t report_len, + size_t *report_in_len, size_t *report_out_len) +{ + const uint8_t *ptr = report_ptr; + size_t len = report_len; + uint32_t report_size = 0; + + while (len > 0) { + const uint8_t tag = ptr[0]; + ptr++; + len--; + + uint8_t key; + size_t key_len; + uint32_t key_val; + + if (get_key_len(tag, &key, &key_len) < 0 || key_len > len || + get_key_val(ptr, key_len, &key_val) < 0) { + return (-1); + } + + if (key == 0x94) { + report_size = key_val; + } else if (key == 0x80) { + *report_in_len = (size_t)report_size; + } else if (key == 0x90) { + *report_out_len = (size_t)report_size; + } + + ptr += key_len; + len -= key_len; + } + + return (0); +} + +fido_dev_info_t * +fido_dev_info_new(size_t n) +{ + return (calloc(n, sizeof(fido_dev_info_t))); +} + +static void +fido_dev_info_reset(fido_dev_info_t *di) +{ + free(di->path); + free(di->manufacturer); + free(di->product); + memset(di, 0, sizeof(*di)); +} + +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++) + fido_dev_info_reset(&devlist[i]); + + 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]); +} + +int +fido_dev_info_set(fido_dev_info_t *devlist, size_t i, + const char *path, const char *manufacturer, const char *product, + const fido_dev_io_t *io, const fido_dev_transport_t *transport) +{ + char *path_copy = NULL, *manu_copy = NULL, *prod_copy = NULL; + int r; + + if (path == NULL || manufacturer == NULL || product == NULL || + io == NULL) { + r = FIDO_ERR_INVALID_ARGUMENT; + goto out; + } + + if ((path_copy = strdup(path)) == NULL || + (manu_copy = strdup(manufacturer)) == NULL || + (prod_copy = strdup(product)) == NULL) { + r = FIDO_ERR_INTERNAL; + goto out; + } + + fido_dev_info_reset(&devlist[i]); + devlist[i].path = path_copy; + devlist[i].manufacturer = manu_copy; + devlist[i].product = prod_copy; + devlist[i].io = *io; + if (transport) + devlist[i].transport = *transport; + r = FIDO_OK; +out: + if (r != FIDO_OK) { + free(prod_copy); + free(manu_copy); + free(path_copy); + } + return (r); +} + +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/src/hid_freebsd.c b/src/hid_freebsd.c new file mode 100644 index 0000000..2bbe80b --- /dev/null +++ b/src/hid_freebsd.c @@ -0,0 +1,337 @@ +/* + * Copyright (c) 2020-2022 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. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <sys/param.h> + +#include <dev/usb/usb_ioctl.h> +#include <dev/usb/usbhid.h> +#if __FreeBSD_version >= 1300500 +#include <dev/hid/hidraw.h> +#define USE_HIDRAW /* see usbhid(4) and hidraw(4) on FreeBSD 13+ */ +#endif + +#include <errno.h> +#include <unistd.h> + +#include "fido.h" + +#if defined(__MidnightBSD__) +#define UHID_VENDOR "MidnightBSD" +#else +#define UHID_VENDOR "FreeBSD" +#endif + +#define MAX_UHID 64 + +struct hid_freebsd { + int fd; + size_t report_in_len; + size_t report_out_len; + sigset_t sigmask; + const sigset_t *sigmaskp; +}; + +static bool +is_fido(int fd) +{ + char buf[64]; + struct usb_gen_descriptor ugd; + uint32_t usage_page = 0; + + memset(&buf, 0, sizeof(buf)); + memset(&ugd, 0, sizeof(ugd)); + + ugd.ugd_report_type = UHID_FEATURE_REPORT; + ugd.ugd_data = buf; + ugd.ugd_maxlen = sizeof(buf); + + if (ioctl(fd, IOCTL_REQ(USB_GET_REPORT_DESC), &ugd) == -1) { + fido_log_error(errno, "%s: ioctl", __func__); + return (false); + } + if (ugd.ugd_actlen > sizeof(buf) || fido_hid_get_usage(ugd.ugd_data, + ugd.ugd_actlen, &usage_page) < 0) { + fido_log_debug("%s: fido_hid_get_usage", __func__); + return (false); + } + + return (usage_page == 0xf1d0); +} + +#ifdef USE_HIDRAW +static int +copy_info_hidraw(fido_dev_info_t *di, const char *path) +{ + int fd = -1; + int ok = -1; + struct usb_device_info udi; + struct hidraw_devinfo devinfo; + char rawname[129]; + + memset(di, 0, sizeof(*di)); + memset(&udi, 0, sizeof(udi)); + memset(&devinfo, 0, sizeof(devinfo)); + memset(rawname, 0, sizeof(rawname)); + + if ((fd = fido_hid_unix_open(path)) == -1 || is_fido(fd) == 0) + goto fail; + + if (ioctl(fd, IOCTL_REQ(USB_GET_DEVICEINFO), &udi) == -1) { + if (ioctl(fd, IOCTL_REQ(HIDIOCGRAWINFO), &devinfo) == -1 || + ioctl(fd, IOCTL_REQ(HIDIOCGRAWNAME(128)), rawname) == -1 || + (di->path = strdup(path)) == NULL || + (di->manufacturer = strdup(UHID_VENDOR)) == NULL || + (di->product = strdup(rawname)) == NULL) + goto fail; + di->vendor_id = devinfo.vendor; + di->product_id = devinfo.product; + } else { + if ((di->path = strdup(path)) == NULL || + (di->manufacturer = strdup(udi.udi_vendor)) == NULL || + (di->product = strdup(udi.udi_product)) == NULL) + goto fail; + di->vendor_id = (int16_t)udi.udi_vendorNo; + di->product_id = (int16_t)udi.udi_productNo; + } + + ok = 0; +fail: + if (fd != -1 && close(fd) == -1) + fido_log_error(errno, "%s: close %s", __func__, path); + + if (ok < 0) { + free(di->path); + free(di->manufacturer); + free(di->product); + explicit_bzero(di, sizeof(*di)); + } + + return (ok); +} +#endif /* USE_HIDRAW */ + +static int +copy_info_uhid(fido_dev_info_t *di, const char *path) +{ + int fd = -1; + int ok = -1; + struct usb_device_info udi; + + memset(di, 0, sizeof(*di)); + memset(&udi, 0, sizeof(udi)); + + if ((fd = fido_hid_unix_open(path)) == -1 || is_fido(fd) == 0) + goto fail; + + if (ioctl(fd, IOCTL_REQ(USB_GET_DEVICEINFO), &udi) == -1) { + fido_log_error(errno, "%s: ioctl", __func__); + strlcpy(udi.udi_vendor, UHID_VENDOR, sizeof(udi.udi_vendor)); + strlcpy(udi.udi_product, "uhid(4)", sizeof(udi.udi_product)); + udi.udi_vendorNo = 0x0b5d; /* stolen from PCI_VENDOR_OPENBSD */ + } + + if ((di->path = strdup(path)) == NULL || + (di->manufacturer = strdup(udi.udi_vendor)) == NULL || + (di->product = strdup(udi.udi_product)) == NULL) + goto fail; + di->vendor_id = (int16_t)udi.udi_vendorNo; + di->product_id = (int16_t)udi.udi_productNo; + + ok = 0; +fail: + if (fd != -1 && close(fd) == -1) + fido_log_error(errno, "%s: close %s", __func__, path); + + if (ok < 0) { + free(di->path); + free(di->manufacturer); + free(di->product); + explicit_bzero(di, sizeof(*di)); + } + + return (ok); +} + +int +fido_hid_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen) +{ + char path[64]; + size_t i; + + if (ilen == 0) + return (FIDO_OK); /* nothing to do */ + + if (devlist == NULL || olen == NULL) + return (FIDO_ERR_INVALID_ARGUMENT); + + *olen = 0; + +#ifdef USE_HIDRAW + for (i = 0; i < MAX_UHID && *olen < ilen; i++) { + snprintf(path, sizeof(path), "/dev/hidraw%zu", i); + if (copy_info_hidraw(&devlist[*olen], path) == 0) { + devlist[*olen].io = (fido_dev_io_t) { + fido_hid_open, + fido_hid_close, + fido_hid_read, + fido_hid_write, + }; + ++(*olen); + } + } + /* hidraw(4) is preferred over uhid(4) */ + if (*olen != 0) + return (FIDO_OK); +#endif /* USE_HIDRAW */ + + for (i = 0; i < MAX_UHID && *olen < ilen; i++) { + snprintf(path, sizeof(path), "/dev/uhid%zu", i); + if (copy_info_uhid(&devlist[*olen], path) == 0) { + devlist[*olen].io = (fido_dev_io_t) { + fido_hid_open, + fido_hid_close, + fido_hid_read, + fido_hid_write, + }; + ++(*olen); + } + } + + return (FIDO_OK); +} + +void * +fido_hid_open(const char *path) +{ + char buf[64]; + struct hid_freebsd *ctx; + struct usb_gen_descriptor ugd; + int r; + + memset(&buf, 0, sizeof(buf)); + memset(&ugd, 0, sizeof(ugd)); + + if ((ctx = calloc(1, sizeof(*ctx))) == NULL) + return (NULL); + + if ((ctx->fd = fido_hid_unix_open(path)) == -1) { + free(ctx); + return (NULL); + } + + ugd.ugd_report_type = UHID_FEATURE_REPORT; + ugd.ugd_data = buf; + ugd.ugd_maxlen = sizeof(buf); + + /* + * N.B. if ctx->fd is an hidraw(4) device, the ioctl() below puts it in + * uhid(4) compat mode, which we need to keep fido_hid_write() as-is. + */ + if ((r = ioctl(ctx->fd, IOCTL_REQ(USB_GET_REPORT_DESC), &ugd) == -1) || + ugd.ugd_actlen > sizeof(buf) || + fido_hid_get_report_len(ugd.ugd_data, ugd.ugd_actlen, + &ctx->report_in_len, &ctx->report_out_len) < 0) { + if (r == -1) + fido_log_error(errno, "%s: ioctl", __func__); + fido_log_debug("%s: using default report sizes", __func__); + ctx->report_in_len = CTAP_MAX_REPORT_LEN; + ctx->report_out_len = CTAP_MAX_REPORT_LEN; + } + + return (ctx); +} + +void +fido_hid_close(void *handle) +{ + struct hid_freebsd *ctx = handle; + + if (close(ctx->fd) == -1) + fido_log_error(errno, "%s: close", __func__); + + free(ctx); +} + +int +fido_hid_set_sigmask(void *handle, const fido_sigset_t *sigmask) +{ + struct hid_freebsd *ctx = handle; + + ctx->sigmask = *sigmask; + ctx->sigmaskp = &ctx->sigmask; + + return (FIDO_OK); +} + +int +fido_hid_read(void *handle, unsigned char *buf, size_t len, int ms) +{ + struct hid_freebsd *ctx = handle; + ssize_t r; + + if (len != ctx->report_in_len) { + fido_log_debug("%s: len %zu", __func__, len); + return (-1); + } + + if (fido_hid_unix_wait(ctx->fd, ms, ctx->sigmaskp) < 0) { + fido_log_debug("%s: fd not ready", __func__); + return (-1); + } + + if ((r = read(ctx->fd, buf, len)) == -1) { + fido_log_error(errno, "%s: read", __func__); + return (-1); + } + + if (r < 0 || (size_t)r != len) { + fido_log_debug("%s: %zd != %zu", __func__, r, len); + return (-1); + } + + return ((int)r); +} + +int +fido_hid_write(void *handle, const unsigned char *buf, size_t len) +{ + struct hid_freebsd *ctx = handle; + ssize_t r; + + if (len != ctx->report_out_len + 1) { + fido_log_debug("%s: len %zu", __func__, len); + return (-1); + } + + if ((r = write(ctx->fd, buf + 1, len - 1)) == -1) { + fido_log_error(errno, "%s: write", __func__); + return (-1); + } + + if (r < 0 || (size_t)r != len - 1) { + fido_log_debug("%s: %zd != %zu", __func__, r, len - 1); + return (-1); + } + + return ((int)len); +} + +size_t +fido_hid_report_in_len(void *handle) +{ + struct hid_freebsd *ctx = handle; + + return (ctx->report_in_len); +} + +size_t +fido_hid_report_out_len(void *handle) +{ + struct hid_freebsd *ctx = handle; + + return (ctx->report_out_len); +} diff --git a/src/hid_hidapi.c b/src/hid_hidapi.c new file mode 100644 index 0000000..fed6f69 --- /dev/null +++ b/src/hid_hidapi.c @@ -0,0 +1,269 @@ +/* + * 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. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#ifdef __linux__ +#include <sys/ioctl.h> +#include <linux/hidraw.h> +#include <linux/input.h> +#include <fcntl.h> +#endif + +#include <errno.h> +#include <hidapi.h> +#include <wchar.h> + +#include "fido.h" + +struct hid_hidapi { + void *handle; + size_t report_in_len; + size_t report_out_len; +}; + +static size_t +fido_wcslen(const wchar_t *wcs) +{ + size_t l = 0; + while (*wcs++ != L'\0') + l++; + return l; +} + +static char * +wcs_to_cs(const wchar_t *wcs) +{ + char *cs; + size_t i; + + if (wcs == NULL || (cs = calloc(fido_wcslen(wcs) + 1, 1)) == NULL) + return NULL; + + for (i = 0; i < fido_wcslen(wcs); i++) { + if (wcs[i] >= 128) { + /* give up on parsing non-ASCII text */ + free(cs); + return strdup("hidapi device"); + } + cs[i] = (char)wcs[i]; + } + + return cs; +} + +static int +copy_info(fido_dev_info_t *di, const struct hid_device_info *d) +{ + memset(di, 0, sizeof(*di)); + + if (d->path != NULL) + di->path = strdup(d->path); + else + di->path = strdup(""); + + if (d->manufacturer_string != NULL) + di->manufacturer = wcs_to_cs(d->manufacturer_string); + else + di->manufacturer = strdup(""); + + if (d->product_string != NULL) + di->product = wcs_to_cs(d->product_string); + else + di->product = strdup(""); + + if (di->path == NULL || + di->manufacturer == NULL || + di->product == NULL) { + free(di->path); + free(di->manufacturer); + free(di->product); + explicit_bzero(di, sizeof(*di)); + return -1; + } + + di->product_id = (int16_t)d->product_id; + di->vendor_id = (int16_t)d->vendor_id; + di->io = (fido_dev_io_t) { + &fido_hid_open, + &fido_hid_close, + &fido_hid_read, + &fido_hid_write, + }; + + return 0; +} + +#ifdef __linux__ +static int +get_report_descriptor(const char *path, struct hidraw_report_descriptor *hrd) +{ + int fd; + int s = -1; + int ok = -1; + + if ((fd = fido_hid_unix_open(path)) == -1) { + fido_log_debug("%s: fido_hid_unix_open", __func__); + return -1; + } + + if (ioctl(fd, IOCTL_REQ(HIDIOCGRDESCSIZE), &s) < 0 || s < 0 || + (unsigned)s > HID_MAX_DESCRIPTOR_SIZE) { + fido_log_error(errno, "%s: ioctl HIDIOCGRDESCSIZE", __func__); + goto fail; + } + + hrd->size = (unsigned)s; + + if (ioctl(fd, IOCTL_REQ(HIDIOCGRDESC), hrd) < 0) { + fido_log_error(errno, "%s: ioctl HIDIOCGRDESC", __func__); + goto fail; + } + + ok = 0; +fail: + if (fd != -1) + close(fd); + + return ok; +} + +static bool +is_fido(const struct hid_device_info *hdi) +{ + uint32_t usage_page = 0; + struct hidraw_report_descriptor *hrd; + + if ((hrd = calloc(1, sizeof(*hrd))) == NULL || + get_report_descriptor(hdi->path, hrd) < 0 || + fido_hid_get_usage(hrd->value, hrd->size, &usage_page) < 0) + usage_page = 0; + + free(hrd); + + return usage_page == 0xf1d0; +} +#elif defined(_WIN32) || defined(__APPLE__) +static bool +is_fido(const struct hid_device_info *hdi) +{ + return hdi->usage_page == 0xf1d0; +} +#else +static bool +is_fido(const struct hid_device_info *hdi) +{ + (void)hdi; + fido_log_debug("%s: assuming FIDO HID", __func__); + return true; +} +#endif + +void * +fido_hid_open(const char *path) +{ + struct hid_hidapi *ctx; + + if ((ctx = calloc(1, sizeof(*ctx))) == NULL) { + return (NULL); + } + + if ((ctx->handle = hid_open_path(path)) == NULL) { + free(ctx); + return (NULL); + } + + ctx->report_in_len = ctx->report_out_len = CTAP_MAX_REPORT_LEN; + + return ctx; +} + +void +fido_hid_close(void *handle) +{ + struct hid_hidapi *ctx = handle; + + hid_close(ctx->handle); + free(ctx); +} + +int +fido_hid_set_sigmask(void *handle, const fido_sigset_t *sigmask) +{ + (void)handle; + (void)sigmask; + + return (FIDO_ERR_INTERNAL); +} + +int +fido_hid_read(void *handle, unsigned char *buf, size_t len, int ms) +{ + struct hid_hidapi *ctx = handle; + + if (len != ctx->report_in_len) { + fido_log_debug("%s: len %zu", __func__, len); + return -1; + } + + return hid_read_timeout(ctx->handle, buf, len, ms); +} + +int +fido_hid_write(void *handle, const unsigned char *buf, size_t len) +{ + struct hid_hidapi *ctx = handle; + + if (len != ctx->report_out_len + 1) { + fido_log_debug("%s: len %zu", __func__, len); + return -1; + } + + return hid_write(ctx->handle, buf, len); +} + +int +fido_hid_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen) +{ + struct hid_device_info *hdi; + + *olen = 0; + + if (ilen == 0) + return FIDO_OK; /* nothing to do */ + if (devlist == NULL) + return FIDO_ERR_INVALID_ARGUMENT; + if ((hdi = hid_enumerate(0, 0)) == NULL) + return FIDO_OK; /* nothing to do */ + + for (struct hid_device_info *d = hdi; d != NULL; d = d->next) { + if (is_fido(d) == false) + continue; + if (copy_info(&devlist[*olen], d) == 0) { + if (++(*olen) == ilen) + break; + } + } + + hid_free_enumeration(hdi); + + return FIDO_OK; +} + +size_t +fido_hid_report_in_len(void *handle) +{ + struct hid_hidapi *ctx = handle; + + return (ctx->report_in_len); +} + +size_t +fido_hid_report_out_len(void *handle) +{ + struct hid_hidapi *ctx = handle; + + return (ctx->report_out_len); +} diff --git a/src/hid_linux.c b/src/hid_linux.c new file mode 100644 index 0000000..841a95b --- /dev/null +++ b/src/hid_linux.c @@ -0,0 +1,391 @@ +/* + * Copyright (c) 2019-2022 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. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <sys/types.h> +#include <sys/file.h> +#include <sys/ioctl.h> + +#include <linux/hidraw.h> +#include <linux/input.h> + +#include <errno.h> +#include <libudev.h> +#include <time.h> +#include <unistd.h> + +#include "fido.h" + +struct hid_linux { + int fd; + size_t report_in_len; + size_t report_out_len; + sigset_t sigmask; + const sigset_t *sigmaskp; +}; + +static int +get_report_descriptor(int fd, struct hidraw_report_descriptor *hrd) +{ + int s = -1; + + if (ioctl(fd, IOCTL_REQ(HIDIOCGRDESCSIZE), &s) == -1) { + fido_log_error(errno, "%s: ioctl HIDIOCGRDESCSIZE", __func__); + return (-1); + } + + if (s < 0 || (unsigned)s > HID_MAX_DESCRIPTOR_SIZE) { + fido_log_debug("%s: HIDIOCGRDESCSIZE %d", __func__, s); + return (-1); + } + + hrd->size = (unsigned)s; + + if (ioctl(fd, IOCTL_REQ(HIDIOCGRDESC), hrd) == -1) { + fido_log_error(errno, "%s: ioctl HIDIOCGRDESC", __func__); + return (-1); + } + + return (0); +} + +static bool +is_fido(const char *path) +{ + int fd = -1; + uint32_t usage_page = 0; + struct hidraw_report_descriptor *hrd = NULL; + + if ((hrd = calloc(1, sizeof(*hrd))) == NULL || + (fd = fido_hid_unix_open(path)) == -1) + goto out; + if (get_report_descriptor(fd, hrd) < 0 || + fido_hid_get_usage(hrd->value, hrd->size, &usage_page) < 0) + usage_page = 0; + +out: + free(hrd); + + if (fd != -1 && close(fd) == -1) + fido_log_error(errno, "%s: close", __func__); + + return (usage_page == 0xf1d0); +} + +static int +parse_uevent(const char *uevent, int *bus, int16_t *vendor_id, + int16_t *product_id) +{ + char *cp; + char *p; + char *s; + int ok = -1; + short unsigned int x; + short unsigned int y; + short unsigned int z; + + if ((s = cp = strdup(uevent)) == NULL) + return (-1); + + while ((p = strsep(&cp, "\n")) != NULL && *p != '\0') { + if (strncmp(p, "HID_ID=", 7) == 0) { + if (sscanf(p + 7, "%hx:%hx:%hx", &x, &y, &z) == 3) { + *bus = (int)x; + *vendor_id = (int16_t)y; + *product_id = (int16_t)z; + ok = 0; + break; + } + } + } + + free(s); + + return (ok); +} + +static char * +get_parent_attr(struct udev_device *dev, const char *subsystem, + const char *devtype, const char *attr) +{ + struct udev_device *parent; + const char *value; + + if ((parent = udev_device_get_parent_with_subsystem_devtype(dev, + subsystem, devtype)) == NULL || (value = + udev_device_get_sysattr_value(parent, attr)) == NULL) + return (NULL); + + return (strdup(value)); +} + +static char * +get_usb_attr(struct udev_device *dev, const char *attr) +{ + return (get_parent_attr(dev, "usb", "usb_device", attr)); +} + +static int +copy_info(fido_dev_info_t *di, struct udev *udev, + struct udev_list_entry *udev_entry) +{ + const char *name; + const char *path; + char *uevent = NULL; + struct udev_device *dev = NULL; + int bus = 0; + int ok = -1; + + memset(di, 0, sizeof(*di)); + + if ((name = udev_list_entry_get_name(udev_entry)) == NULL || + (dev = udev_device_new_from_syspath(udev, name)) == NULL || + (path = udev_device_get_devnode(dev)) == NULL || + is_fido(path) == 0) + goto fail; + + if ((uevent = get_parent_attr(dev, "hid", NULL, "uevent")) == NULL || + parse_uevent(uevent, &bus, &di->vendor_id, &di->product_id) < 0) { + fido_log_debug("%s: uevent", __func__); + goto fail; + } + +#ifndef FIDO_HID_ANY + if (bus != BUS_USB) { + fido_log_debug("%s: bus", __func__); + goto fail; + } +#endif + + di->path = strdup(path); + if ((di->manufacturer = get_usb_attr(dev, "manufacturer")) == NULL) + di->manufacturer = strdup(""); + if ((di->product = get_usb_attr(dev, "product")) == NULL) + di->product = strdup(""); + if (di->path == NULL || di->manufacturer == NULL || di->product == NULL) + goto fail; + + ok = 0; +fail: + if (dev != NULL) + udev_device_unref(dev); + + free(uevent); + + if (ok < 0) { + free(di->path); + free(di->manufacturer); + free(di->product); + explicit_bzero(di, sizeof(*di)); + } + + return (ok); +} + +int +fido_hid_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen) +{ + struct udev *udev = NULL; + struct udev_enumerate *udev_enum = NULL; + struct udev_list_entry *udev_list; + struct udev_list_entry *udev_entry; + int r = FIDO_ERR_INTERNAL; + + *olen = 0; + + if (ilen == 0) + return (FIDO_OK); /* nothing to do */ + + if (devlist == NULL) + return (FIDO_ERR_INVALID_ARGUMENT); + + if ((udev = udev_new()) == NULL || + (udev_enum = udev_enumerate_new(udev)) == NULL) + goto fail; + + if (udev_enumerate_add_match_subsystem(udev_enum, "hidraw") < 0 || + udev_enumerate_scan_devices(udev_enum) < 0) + goto fail; + + if ((udev_list = udev_enumerate_get_list_entry(udev_enum)) == NULL) { + r = FIDO_OK; /* zero hidraw devices */ + goto fail; + } + + udev_list_entry_foreach(udev_entry, udev_list) { + if (copy_info(&devlist[*olen], udev, udev_entry) == 0) { + devlist[*olen].io = (fido_dev_io_t) { + fido_hid_open, + fido_hid_close, + fido_hid_read, + fido_hid_write, + }; + if (++(*olen) == ilen) + break; + } + } + + r = FIDO_OK; +fail: + if (udev_enum != NULL) + udev_enumerate_unref(udev_enum); + if (udev != NULL) + udev_unref(udev); + + return (r); +} + +void * +fido_hid_open(const char *path) +{ + struct hid_linux *ctx; + struct hidraw_report_descriptor *hrd; + struct timespec tv_pause; + long interval_ms, retries = 0; + bool looped; + +retry: + looped = false; + + if ((ctx = calloc(1, sizeof(*ctx))) == NULL || + (ctx->fd = fido_hid_unix_open(path)) == -1) { + free(ctx); + return (NULL); + } + + while (flock(ctx->fd, LOCK_EX|LOCK_NB) == -1) { + if (errno != EWOULDBLOCK) { + fido_log_error(errno, "%s: flock", __func__); + fido_hid_close(ctx); + return (NULL); + } + looped = true; + if (retries++ >= 20) { + fido_log_debug("%s: flock timeout", __func__); + fido_hid_close(ctx); + return (NULL); + } + interval_ms = retries * 100000000L; + tv_pause.tv_sec = interval_ms / 1000000000L; + tv_pause.tv_nsec = interval_ms % 1000000000L; + if (nanosleep(&tv_pause, NULL) == -1) { + fido_log_error(errno, "%s: nanosleep", __func__); + fido_hid_close(ctx); + return (NULL); + } + } + + if (looped) { + fido_log_debug("%s: retrying", __func__); + fido_hid_close(ctx); + goto retry; + } + + if ((hrd = calloc(1, sizeof(*hrd))) == NULL || + get_report_descriptor(ctx->fd, hrd) < 0 || + fido_hid_get_report_len(hrd->value, hrd->size, &ctx->report_in_len, + &ctx->report_out_len) < 0 || ctx->report_in_len == 0 || + ctx->report_out_len == 0) { + fido_log_debug("%s: using default report sizes", __func__); + ctx->report_in_len = CTAP_MAX_REPORT_LEN; + ctx->report_out_len = CTAP_MAX_REPORT_LEN; + } + + free(hrd); + + return (ctx); +} + +void +fido_hid_close(void *handle) +{ + struct hid_linux *ctx = handle; + + if (close(ctx->fd) == -1) + fido_log_error(errno, "%s: close", __func__); + + free(ctx); +} + +int +fido_hid_set_sigmask(void *handle, const fido_sigset_t *sigmask) +{ + struct hid_linux *ctx = handle; + + ctx->sigmask = *sigmask; + ctx->sigmaskp = &ctx->sigmask; + + return (FIDO_OK); +} + +int +fido_hid_read(void *handle, unsigned char *buf, size_t len, int ms) +{ + struct hid_linux *ctx = handle; + ssize_t r; + + if (len != ctx->report_in_len) { + fido_log_debug("%s: len %zu", __func__, len); + return (-1); + } + + if (fido_hid_unix_wait(ctx->fd, ms, ctx->sigmaskp) < 0) { + fido_log_debug("%s: fd not ready", __func__); + return (-1); + } + + if ((r = read(ctx->fd, buf, len)) == -1) { + fido_log_error(errno, "%s: read", __func__); + return (-1); + } + + if (r < 0 || (size_t)r != len) { + fido_log_debug("%s: %zd != %zu", __func__, r, len); + return (-1); + } + + return ((int)r); +} + +int +fido_hid_write(void *handle, const unsigned char *buf, size_t len) +{ + struct hid_linux *ctx = handle; + ssize_t r; + + if (len != ctx->report_out_len + 1) { + fido_log_debug("%s: len %zu", __func__, len); + return (-1); + } + + if ((r = write(ctx->fd, buf, len)) == -1) { + fido_log_error(errno, "%s: write", __func__); + return (-1); + } + + if (r < 0 || (size_t)r != len) { + fido_log_debug("%s: %zd != %zu", __func__, r, len); + return (-1); + } + + return ((int)r); +} + +size_t +fido_hid_report_in_len(void *handle) +{ + struct hid_linux *ctx = handle; + + return (ctx->report_in_len); +} + +size_t +fido_hid_report_out_len(void *handle) +{ + struct hid_linux *ctx = handle; + + return (ctx->report_out_len); +} diff --git a/src/hid_netbsd.c b/src/hid_netbsd.c new file mode 100644 index 0000000..d5b9fad --- /dev/null +++ b/src/hid_netbsd.c @@ -0,0 +1,339 @@ +/* + * Copyright (c) 2020 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. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <sys/types.h> +#include <sys/ioctl.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbhid.h> + +#include <errno.h> +#include <poll.h> +#include <signal.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "fido.h" + +#define MAX_UHID 64 + +struct hid_netbsd { + int fd; + size_t report_in_len; + size_t report_out_len; + sigset_t sigmask; + const sigset_t *sigmaskp; +}; + +/* Hack to make this work with newer kernels even if /usr/include is old. */ +#if __NetBSD_Version__ < 901000000 /* 9.1 */ +#define USB_HID_GET_RAW _IOR('h', 1, int) +#define USB_HID_SET_RAW _IOW('h', 2, int) +#endif + +static bool +is_fido(int fd) +{ + struct usb_ctl_report_desc ucrd; + uint32_t usage_page = 0; + int raw = 1; + + memset(&ucrd, 0, sizeof(ucrd)); + + if (ioctl(fd, IOCTL_REQ(USB_GET_REPORT_DESC), &ucrd) == -1) { + fido_log_error(errno, "%s: ioctl", __func__); + return (false); + } + + if (ucrd.ucrd_size < 0 || + (size_t)ucrd.ucrd_size > sizeof(ucrd.ucrd_data) || + fido_hid_get_usage(ucrd.ucrd_data, (size_t)ucrd.ucrd_size, + &usage_page) < 0) { + fido_log_debug("%s: fido_hid_get_usage", __func__); + return (false); + } + + if (usage_page != 0xf1d0) + return (false); + + /* + * This step is not strictly necessary -- NetBSD puts fido + * devices into raw mode automatically by default, but in + * principle that might change, and this serves as a test to + * verify that we're running on a kernel with support for raw + * mode at all so we don't get confused issuing writes that try + * to set the report descriptor rather than transfer data on + * the output interrupt pipe as we need. + */ + if (ioctl(fd, IOCTL_REQ(USB_HID_SET_RAW), &raw) == -1) { + fido_log_error(errno, "%s: unable to set raw", __func__); + return (false); + } + + return (true); +} + +static int +copy_info(fido_dev_info_t *di, const char *path) +{ + int fd = -1; + int ok = -1; + struct usb_device_info udi; + + memset(di, 0, sizeof(*di)); + memset(&udi, 0, sizeof(udi)); + + if ((fd = fido_hid_unix_open(path)) == -1 || is_fido(fd) == 0) + goto fail; + + if (ioctl(fd, IOCTL_REQ(USB_GET_DEVICEINFO), &udi) == -1) { + fido_log_error(errno, "%s: ioctl", __func__); + goto fail; + } + + if ((di->path = strdup(path)) == NULL || + (di->manufacturer = strdup(udi.udi_vendor)) == NULL || + (di->product = strdup(udi.udi_product)) == NULL) + goto fail; + + di->vendor_id = (int16_t)udi.udi_vendorNo; + di->product_id = (int16_t)udi.udi_productNo; + + ok = 0; +fail: + if (fd != -1 && close(fd) == -1) + fido_log_error(errno, "%s: close", __func__); + + if (ok < 0) { + free(di->path); + free(di->manufacturer); + free(di->product); + explicit_bzero(di, sizeof(*di)); + } + + return (ok); +} + +int +fido_hid_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen) +{ + char path[64]; + size_t i; + + *olen = 0; + + 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 (copy_info(&devlist[*olen], path) == 0) { + devlist[*olen].io = (fido_dev_io_t) { + fido_hid_open, + fido_hid_close, + fido_hid_read, + fido_hid_write, + }; + ++(*olen); + } + } + + return (FIDO_OK); +} + +/* + * Workaround for NetBSD (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_netbsd *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; + fido_log_debug("%s: send ping %d", __func__, i); + if (fido_hid_write(ctx, data, ctx->report_out_len + 1) == -1) + return -1; + fido_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) { + fido_log_error(errno, "%s: poll", __func__); + return -1; + } else if (n == 0) { + fido_log_debug("%s: timed out", __func__); + continue; + } + if (fido_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. + */ + fido_log_xxd(data, ctx->report_out_len, "%s: got reply", + __func__); + return 0; + } + fido_log_debug("%s: no response", __func__); + return -1; +} + +void * +fido_hid_open(const char *path) +{ + struct hid_netbsd *ctx; + struct usb_ctl_report_desc ucrd; + int r; + + memset(&ucrd, 0, sizeof(ucrd)); + + if ((ctx = calloc(1, sizeof(*ctx))) == NULL || + (ctx->fd = fido_hid_unix_open(path)) == -1) { + free(ctx); + return (NULL); + } + + if ((r = ioctl(ctx->fd, IOCTL_REQ(USB_GET_REPORT_DESC), &ucrd)) == -1 || + ucrd.ucrd_size < 0 || + (size_t)ucrd.ucrd_size > sizeof(ucrd.ucrd_data) || + fido_hid_get_report_len(ucrd.ucrd_data, (size_t)ucrd.ucrd_size, + &ctx->report_in_len, &ctx->report_out_len) < 0) { + if (r == -1) + fido_log_error(errno, "%s: ioctl", __func__); + fido_log_debug("%s: using default report sizes", __func__); + ctx->report_in_len = CTAP_MAX_REPORT_LEN; + ctx->report_out_len = CTAP_MAX_REPORT_LEN; + } + + /* + * NetBSD 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 (!is_fido(ctx->fd) || terrible_ping_kludge(ctx) != 0) { + fido_hid_close(ctx); + return NULL; + } + + return (ctx); +} + +void +fido_hid_close(void *handle) +{ + struct hid_netbsd *ctx = handle; + + if (close(ctx->fd) == -1) + fido_log_error(errno, "%s: close", __func__); + + free(ctx); +} + +int +fido_hid_set_sigmask(void *handle, const fido_sigset_t *sigmask) +{ + struct hid_netbsd *ctx = handle; + + ctx->sigmask = *sigmask; + ctx->sigmaskp = &ctx->sigmask; + + return (FIDO_OK); +} + +int +fido_hid_read(void *handle, unsigned char *buf, size_t len, int ms) +{ + struct hid_netbsd *ctx = handle; + ssize_t r; + + if (len != ctx->report_in_len) { + fido_log_debug("%s: len %zu", __func__, len); + return (-1); + } + + if (fido_hid_unix_wait(ctx->fd, ms, ctx->sigmaskp) < 0) { + fido_log_debug("%s: fd not ready", __func__); + return (-1); + } + + if ((r = read(ctx->fd, buf, len)) == -1) { + fido_log_error(errno, "%s: read", __func__); + return (-1); + } + + if (r < 0 || (size_t)r != len) { + fido_log_error(errno, "%s: %zd != %zu", __func__, r, len); + return (-1); + } + + return ((int)r); +} + +int +fido_hid_write(void *handle, const unsigned char *buf, size_t len) +{ + struct hid_netbsd *ctx = handle; + ssize_t r; + + if (len != ctx->report_out_len + 1) { + fido_log_debug("%s: len %zu", __func__, len); + return (-1); + } + + if ((r = write(ctx->fd, buf + 1, len - 1)) == -1) { + fido_log_error(errno, "%s: write", __func__); + return (-1); + } + + if (r < 0 || (size_t)r != len - 1) { + fido_log_error(errno, "%s: %zd != %zu", __func__, r, len - 1); + return (-1); + } + + return ((int)len); +} + +size_t +fido_hid_report_in_len(void *handle) +{ + struct hid_netbsd *ctx = handle; + + return (ctx->report_in_len); +} + +size_t +fido_hid_report_out_len(void *handle) +{ + struct hid_netbsd *ctx = handle; + + return (ctx->report_out_len); +} diff --git a/src/hid_openbsd.c b/src/hid_openbsd.c new file mode 100644 index 0000000..2d08aca --- /dev/null +++ b/src/hid_openbsd.c @@ -0,0 +1,280 @@ +/* + * 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. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <sys/types.h> + +#include <sys/ioctl.h> +#include <dev/usb/usb.h> + +#include <errno.h> +#include <fcntl.h> +#include <poll.h> +#include <signal.h> +#include <unistd.h> + +#include "fido.h" + +#define MAX_UHID 64 + +struct hid_openbsd { + int fd; + size_t report_in_len; + size_t report_out_len; + sigset_t sigmask; + const sigset_t *sigmaskp; +}; + +static int +copy_info(fido_dev_info_t *di, const char *path) +{ + int fd = -1, ok = -1; + struct usb_device_info udi; + + memset(di, 0, sizeof(*di)); + memset(&udi, 0, sizeof(udi)); + + if ((fd = fido_hid_unix_open(path)) == -1) + goto fail; + if (ioctl(fd, IOCTL_REQ(USB_GET_DEVICEINFO), &udi) == -1) { + fido_log_error(errno, "%s: ioctl %s", __func__, path); + goto fail; + } + + fido_log_debug("%s: %s: bus = 0x%02x, addr = 0x%02x", __func__, path, + udi.udi_bus, udi.udi_addr); + fido_log_debug("%s: %s: vendor = \"%s\", product = \"%s\"", __func__, + path, udi.udi_vendor, udi.udi_product); + fido_log_debug("%s: %s: productNo = 0x%04x, vendorNo = 0x%04x, " + "releaseNo = 0x%04x", __func__, path, udi.udi_productNo, + udi.udi_vendorNo, udi.udi_releaseNo); + + if ((di->path = strdup(path)) == NULL || + (di->manufacturer = strdup(udi.udi_vendor)) == NULL || + (di->product = strdup(udi.udi_product)) == NULL) + goto fail; + + di->vendor_id = (int16_t)udi.udi_vendorNo; + di->product_id = (int16_t)udi.udi_productNo; + + ok = 0; +fail: + if (fd != -1 && close(fd) == -1) + fido_log_error(errno, "%s: close %s", __func__, path); + + if (ok < 0) { + free(di->path); + free(di->manufacturer); + free(di->product); + explicit_bzero(di, sizeof(*di)); + } + + return (ok); +} + +int +fido_hid_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen) +{ + size_t i; + char path[64]; + + 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/fido/%zu", i); + if (copy_info(&devlist[*olen], path) == 0) { + devlist[*olen].io = (fido_dev_io_t) { + fido_hid_open, + fido_hid_close, + fido_hid_read, + fido_hid_write, + }; + ++(*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; + fido_log_debug("%s: send ping %d", __func__, i); + if (fido_hid_write(ctx, data, ctx->report_out_len + 1) == -1) + return -1; + fido_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) { + fido_log_error(errno, "%s: poll", __func__); + return -1; + } else if (n == 0) { + fido_log_debug("%s: timed out", __func__); + continue; + } + if (fido_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. + */ + fido_log_xxd(data, ctx->report_out_len, "%s: got reply", + __func__); + return 0; + } + fido_log_debug("%s: no response", __func__); + return -1; +} + +void * +fido_hid_open(const char *path) +{ + struct hid_openbsd *ret = NULL; + + if ((ret = calloc(1, sizeof(*ret))) == NULL || + (ret->fd = fido_hid_unix_open(path)) == -1) { + free(ret); + return (NULL); + } + ret->report_in_len = ret->report_out_len = CTAP_MAX_REPORT_LEN; + fido_log_debug("%s: inlen = %zu outlen = %zu", __func__, + 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) { + fido_hid_close(ret); + return NULL; + } + + return (ret); +} + +void +fido_hid_close(void *handle) +{ + struct hid_openbsd *ctx = (struct hid_openbsd *)handle; + + if (close(ctx->fd) == -1) + fido_log_error(errno, "%s: close", __func__); + + free(ctx); +} + +int +fido_hid_set_sigmask(void *handle, const fido_sigset_t *sigmask) +{ + struct hid_openbsd *ctx = handle; + + ctx->sigmask = *sigmask; + ctx->sigmaskp = &ctx->sigmask; + + return (FIDO_OK); +} + +int +fido_hid_read(void *handle, unsigned char *buf, size_t len, int ms) +{ + struct hid_openbsd *ctx = (struct hid_openbsd *)handle; + ssize_t r; + + if (len != ctx->report_in_len) { + fido_log_debug("%s: invalid len: got %zu, want %zu", __func__, + len, ctx->report_in_len); + return (-1); + } + + if (fido_hid_unix_wait(ctx->fd, ms, ctx->sigmaskp) < 0) { + fido_log_debug("%s: fd not ready", __func__); + return (-1); + } + + if ((r = read(ctx->fd, buf, len)) == -1) { + fido_log_error(errno, "%s: read", __func__); + return (-1); + } + + if (r < 0 || (size_t)r != len) { + fido_log_debug("%s: %zd != %zu", __func__, r, len); + return (-1); + } + + return ((int)len); +} + +int +fido_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) { + fido_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) { + fido_log_error(errno, "%s: write", __func__); + return (-1); + } + + if (r < 0 || (size_t)r != len - 1) { + fido_log_debug("%s: %zd != %zu", __func__, r, len - 1); + return (-1); + } + + return ((int)len); +} + +size_t +fido_hid_report_in_len(void *handle) +{ + struct hid_openbsd *ctx = handle; + + return (ctx->report_in_len); +} + +size_t +fido_hid_report_out_len(void *handle) +{ + struct hid_openbsd *ctx = handle; + + return (ctx->report_out_len); +} diff --git a/src/hid_osx.c b/src/hid_osx.c new file mode 100644 index 0000000..9309762 --- /dev/null +++ b/src/hid_osx.c @@ -0,0 +1,597 @@ +/* + * Copyright (c) 2019-2022 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. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <sys/types.h> + +#include <errno.h> +#include <fcntl.h> +#include <signal.h> +#include <unistd.h> + +#include <Availability.h> +#include <CoreFoundation/CoreFoundation.h> +#include <IOKit/IOKitLib.h> +#include <IOKit/hid/IOHIDKeys.h> +#include <IOKit/hid/IOHIDManager.h> + +#include "fido.h" + +#if __MAC_OS_X_VERSION_MIN_REQUIRED < 120000 +#define kIOMainPortDefault kIOMasterPortDefault +#endif + +#define IOREG "ioreg://" + +struct hid_osx { + IOHIDDeviceRef ref; + CFStringRef loop_id; + int report_pipe[2]; + size_t report_in_len; + size_t report_out_len; + unsigned char report[CTAP_MAX_REPORT_LEN]; +}; + +static int +get_int32(IOHIDDeviceRef dev, CFStringRef key, int32_t *v) +{ + CFTypeRef ref; + + if ((ref = IOHIDDeviceGetProperty(dev, key)) == NULL || + CFGetTypeID(ref) != CFNumberGetTypeID()) { + fido_log_debug("%s: IOHIDDeviceGetProperty", __func__); + return (-1); + } + + if (CFNumberGetType(ref) != kCFNumberSInt32Type && + CFNumberGetType(ref) != kCFNumberSInt64Type) { + fido_log_debug("%s: CFNumberGetType", __func__); + return (-1); + } + + if (CFNumberGetValue(ref, kCFNumberSInt32Type, v) == false) { + fido_log_debug("%s: CFNumberGetValue", __func__); + return (-1); + } + + return (0); +} + +static int +get_utf8(IOHIDDeviceRef dev, CFStringRef key, void *buf, size_t len) +{ + CFTypeRef ref; + + memset(buf, 0, len); + + if ((ref = IOHIDDeviceGetProperty(dev, key)) == NULL || + CFGetTypeID(ref) != CFStringGetTypeID()) { + fido_log_debug("%s: IOHIDDeviceGetProperty", __func__); + return (-1); + } + + if (CFStringGetCString(ref, buf, (long)len, + kCFStringEncodingUTF8) == false) { + fido_log_debug("%s: CFStringGetCString", __func__); + return (-1); + } + + return (0); +} + +static int +get_report_len(IOHIDDeviceRef dev, int dir, size_t *report_len) +{ + CFStringRef key; + int32_t v; + + if (dir == 0) + key = CFSTR(kIOHIDMaxInputReportSizeKey); + else + key = CFSTR(kIOHIDMaxOutputReportSizeKey); + + if (get_int32(dev, key, &v) < 0) { + fido_log_debug("%s: get_int32/%d", __func__, dir); + return (-1); + } + + if ((*report_len = (size_t)v) > CTAP_MAX_REPORT_LEN) { + fido_log_debug("%s: report_len=%zu", __func__, *report_len); + return (-1); + } + + return (0); +} + +static int +get_id(IOHIDDeviceRef dev, int16_t *vendor_id, int16_t *product_id) +{ + int32_t vendor; + int32_t product; + + if (get_int32(dev, CFSTR(kIOHIDVendorIDKey), &vendor) < 0 || + vendor > UINT16_MAX) { + fido_log_debug("%s: get_int32 vendor", __func__); + return (-1); + } + + if (get_int32(dev, CFSTR(kIOHIDProductIDKey), &product) < 0 || + product > UINT16_MAX) { + fido_log_debug("%s: get_int32 product", __func__); + return (-1); + } + + *vendor_id = (int16_t)vendor; + *product_id = (int16_t)product; + + return (0); +} + +static int +get_str(IOHIDDeviceRef dev, char **manufacturer, char **product) +{ + char buf[512]; + int ok = -1; + + *manufacturer = NULL; + *product = NULL; + + if (get_utf8(dev, CFSTR(kIOHIDManufacturerKey), buf, sizeof(buf)) < 0) + *manufacturer = strdup(""); + else + *manufacturer = strdup(buf); + + if (get_utf8(dev, CFSTR(kIOHIDProductKey), buf, sizeof(buf)) < 0) + *product = strdup(""); + else + *product = strdup(buf); + + if (*manufacturer == NULL || *product == NULL) { + fido_log_debug("%s: strdup", __func__); + goto fail; + } + + ok = 0; +fail: + if (ok < 0) { + free(*manufacturer); + free(*product); + *manufacturer = NULL; + *product = NULL; + } + + return (ok); +} + +static char * +get_path(IOHIDDeviceRef dev) +{ + io_service_t s; + uint64_t id; + char *path; + + if ((s = IOHIDDeviceGetService(dev)) == MACH_PORT_NULL) { + fido_log_debug("%s: IOHIDDeviceGetService", __func__); + return (NULL); + } + + if (IORegistryEntryGetRegistryEntryID(s, &id) != KERN_SUCCESS) { + fido_log_debug("%s: IORegistryEntryGetRegistryEntryID", + __func__); + return (NULL); + } + + if (asprintf(&path, "%s%llu", IOREG, (unsigned long long)id) == -1) { + fido_log_error(errno, "%s: asprintf", __func__); + return (NULL); + } + + return (path); +} + +static bool +is_fido(IOHIDDeviceRef dev) +{ + char buf[32]; + uint32_t usage_page; + + if (get_int32(dev, CFSTR(kIOHIDPrimaryUsagePageKey), + (int32_t *)&usage_page) < 0 || usage_page != 0xf1d0) + return (false); + + if (get_utf8(dev, CFSTR(kIOHIDTransportKey), buf, sizeof(buf)) < 0) { + fido_log_debug("%s: get_utf8 transport", __func__); + return (false); + } + +#ifndef FIDO_HID_ANY + if (strcasecmp(buf, "usb") != 0) { + fido_log_debug("%s: transport", __func__); + return (false); + } +#endif + + return (true); +} + +static int +copy_info(fido_dev_info_t *di, IOHIDDeviceRef dev) +{ + memset(di, 0, sizeof(*di)); + + if (is_fido(dev) == false) + return (-1); + + if (get_id(dev, &di->vendor_id, &di->product_id) < 0 || + get_str(dev, &di->manufacturer, &di->product) < 0 || + (di->path = get_path(dev)) == NULL) { + free(di->path); + free(di->manufacturer); + free(di->product); + explicit_bzero(di, sizeof(*di)); + return (-1); + } + + return (0); +} + +int +fido_hid_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen) +{ + IOHIDManagerRef manager = NULL; + CFSetRef devset = NULL; + size_t devcnt; + CFIndex n; + IOHIDDeviceRef *devs = NULL; + int r = FIDO_ERR_INTERNAL; + + *olen = 0; + + if (ilen == 0) + return (FIDO_OK); /* nothing to do */ + + if (devlist == NULL) + return (FIDO_ERR_INVALID_ARGUMENT); + + if ((manager = IOHIDManagerCreate(kCFAllocatorDefault, + kIOHIDManagerOptionNone)) == NULL) { + fido_log_debug("%s: IOHIDManagerCreate", __func__); + goto fail; + } + + IOHIDManagerSetDeviceMatching(manager, NULL); + + if ((devset = IOHIDManagerCopyDevices(manager)) == NULL) { + fido_log_debug("%s: IOHIDManagerCopyDevices", __func__); + goto fail; + } + + if ((n = CFSetGetCount(devset)) < 0) { + fido_log_debug("%s: CFSetGetCount", __func__); + goto fail; + } + + devcnt = (size_t)n; + + if ((devs = calloc(devcnt, sizeof(*devs))) == NULL) { + fido_log_debug("%s: calloc", __func__); + goto fail; + } + + CFSetGetValues(devset, (void *)devs); + + for (size_t i = 0; i < devcnt; i++) { + if (copy_info(&devlist[*olen], devs[i]) == 0) { + devlist[*olen].io = (fido_dev_io_t) { + fido_hid_open, + fido_hid_close, + fido_hid_read, + fido_hid_write, + }; + if (++(*olen) == ilen) + break; + } + } + + r = FIDO_OK; +fail: + if (manager != NULL) + CFRelease(manager); + if (devset != NULL) + CFRelease(devset); + + free(devs); + + return (r); +} + +static void +report_callback(void *context, IOReturn result, void *dev, IOHIDReportType type, + uint32_t id, uint8_t *ptr, CFIndex len) +{ + struct hid_osx *ctx = context; + ssize_t r; + + (void)dev; + + if (result != kIOReturnSuccess || type != kIOHIDReportTypeInput || + id != 0 || len < 0 || (size_t)len != ctx->report_in_len) { + fido_log_debug("%s: io error", __func__); + return; + } + + if ((r = write(ctx->report_pipe[1], ptr, (size_t)len)) == -1) { + fido_log_error(errno, "%s: write", __func__); + return; + } + + if (r < 0 || (size_t)r != (size_t)len) { + fido_log_debug("%s: %zd != %zu", __func__, r, (size_t)len); + return; + } +} + +static void +removal_callback(void *context, IOReturn result, void *sender) +{ + (void)context; + (void)result; + (void)sender; + + CFRunLoopStop(CFRunLoopGetCurrent()); +} + +static int +set_nonblock(int fd) +{ + int flags; + + if ((flags = fcntl(fd, F_GETFL)) == -1) { + fido_log_error(errno, "%s: fcntl F_GETFL", __func__); + return (-1); + } + + if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) { + fido_log_error(errno, "%s: fcntl F_SETFL", __func__); + return (-1); + } + + return (0); +} + +static int +disable_sigpipe(int fd) +{ + int disabled = 1; + + if (fcntl(fd, F_SETNOSIGPIPE, &disabled) == -1) { + fido_log_error(errno, "%s: fcntl F_SETNOSIGPIPE", __func__); + return (-1); + } + + return (0); +} + +static io_registry_entry_t +get_ioreg_entry(const char *path) +{ + uint64_t id; + + if (strncmp(path, IOREG, strlen(IOREG)) != 0) + return (IORegistryEntryFromPath(kIOMainPortDefault, path)); + + if (fido_to_uint64(path + strlen(IOREG), 10, &id) == -1) { + fido_log_debug("%s: fido_to_uint64", __func__); + return (MACH_PORT_NULL); + } + + return (IOServiceGetMatchingService(kIOMainPortDefault, + IORegistryEntryIDMatching(id))); +} + +void * +fido_hid_open(const char *path) +{ + struct hid_osx *ctx; + io_registry_entry_t entry = MACH_PORT_NULL; + char loop_id[32]; + int ok = -1; + int r; + + if ((ctx = calloc(1, sizeof(*ctx))) == NULL) { + fido_log_debug("%s: calloc", __func__); + goto fail; + } + + ctx->report_pipe[0] = -1; + ctx->report_pipe[1] = -1; + + if (pipe(ctx->report_pipe) == -1) { + fido_log_error(errno, "%s: pipe", __func__); + goto fail; + } + + if (set_nonblock(ctx->report_pipe[0]) < 0 || + set_nonblock(ctx->report_pipe[1]) < 0) { + fido_log_debug("%s: set_nonblock", __func__); + goto fail; + } + + if (disable_sigpipe(ctx->report_pipe[1]) < 0) { + fido_log_debug("%s: disable_sigpipe", __func__); + goto fail; + } + + if ((entry = get_ioreg_entry(path)) == MACH_PORT_NULL) { + fido_log_debug("%s: get_ioreg_entry: %s", __func__, path); + goto fail; + } + + if ((ctx->ref = IOHIDDeviceCreate(kCFAllocatorDefault, + entry)) == NULL) { + fido_log_debug("%s: IOHIDDeviceCreate", __func__); + goto fail; + } + + if (get_report_len(ctx->ref, 0, &ctx->report_in_len) < 0 || + get_report_len(ctx->ref, 1, &ctx->report_out_len) < 0) { + fido_log_debug("%s: get_report_len", __func__); + goto fail; + } + + if (ctx->report_in_len > sizeof(ctx->report)) { + fido_log_debug("%s: report_in_len=%zu", __func__, + ctx->report_in_len); + goto fail; + } + + if (IOHIDDeviceOpen(ctx->ref, + kIOHIDOptionsTypeSeizeDevice) != kIOReturnSuccess) { + fido_log_debug("%s: IOHIDDeviceOpen", __func__); + goto fail; + } + + if ((r = snprintf(loop_id, sizeof(loop_id), "fido2-%p", + (void *)ctx->ref)) < 0 || (size_t)r >= sizeof(loop_id)) { + fido_log_debug("%s: snprintf", __func__); + goto fail; + } + + if ((ctx->loop_id = CFStringCreateWithCString(NULL, loop_id, + kCFStringEncodingASCII)) == NULL) { + fido_log_debug("%s: CFStringCreateWithCString", __func__); + goto fail; + } + + IOHIDDeviceRegisterInputReportCallback(ctx->ref, ctx->report, + (long)ctx->report_in_len, &report_callback, ctx); + IOHIDDeviceRegisterRemovalCallback(ctx->ref, &removal_callback, ctx); + + ok = 0; +fail: + if (entry != MACH_PORT_NULL) + IOObjectRelease(entry); + + if (ok < 0 && ctx != NULL) { + if (ctx->ref != NULL) + CFRelease(ctx->ref); + if (ctx->loop_id != NULL) + CFRelease(ctx->loop_id); + if (ctx->report_pipe[0] != -1) + close(ctx->report_pipe[0]); + if (ctx->report_pipe[1] != -1) + close(ctx->report_pipe[1]); + free(ctx); + ctx = NULL; + } + + return (ctx); +} + +void +fido_hid_close(void *handle) +{ + struct hid_osx *ctx = handle; + + IOHIDDeviceRegisterInputReportCallback(ctx->ref, ctx->report, + (long)ctx->report_in_len, NULL, ctx); + IOHIDDeviceRegisterRemovalCallback(ctx->ref, NULL, ctx); + + if (IOHIDDeviceClose(ctx->ref, + kIOHIDOptionsTypeSeizeDevice) != kIOReturnSuccess) + fido_log_debug("%s: IOHIDDeviceClose", __func__); + + CFRelease(ctx->ref); + CFRelease(ctx->loop_id); + + explicit_bzero(ctx->report, sizeof(ctx->report)); + close(ctx->report_pipe[0]); + close(ctx->report_pipe[1]); + + free(ctx); +} + +int +fido_hid_set_sigmask(void *handle, const fido_sigset_t *sigmask) +{ + (void)handle; + (void)sigmask; + + return (FIDO_ERR_INTERNAL); +} + +int +fido_hid_read(void *handle, unsigned char *buf, size_t len, int ms) +{ + struct hid_osx *ctx = handle; + ssize_t r; + + explicit_bzero(buf, len); + explicit_bzero(ctx->report, sizeof(ctx->report)); + + if (len != ctx->report_in_len || len > sizeof(ctx->report)) { + fido_log_debug("%s: len %zu", __func__, len); + return (-1); + } + + IOHIDDeviceScheduleWithRunLoop(ctx->ref, CFRunLoopGetCurrent(), + ctx->loop_id); + + if (ms == -1) + ms = 5000; /* wait 5 seconds by default */ + + CFRunLoopRunInMode(ctx->loop_id, (double)ms/1000.0, true); + + IOHIDDeviceUnscheduleFromRunLoop(ctx->ref, CFRunLoopGetCurrent(), + ctx->loop_id); + + if ((r = read(ctx->report_pipe[0], buf, len)) == -1) { + fido_log_error(errno, "%s: read", __func__); + return (-1); + } + + if (r < 0 || (size_t)r != len) { + fido_log_debug("%s: %zd != %zu", __func__, r, len); + return (-1); + } + + return ((int)len); +} + +int +fido_hid_write(void *handle, const unsigned char *buf, size_t len) +{ + struct hid_osx *ctx = handle; + + if (len != ctx->report_out_len + 1 || len > LONG_MAX) { + fido_log_debug("%s: len %zu", __func__, len); + return (-1); + } + + if (IOHIDDeviceSetReport(ctx->ref, kIOHIDReportTypeOutput, 0, buf + 1, + (long)(len - 1)) != kIOReturnSuccess) { + fido_log_debug("%s: IOHIDDeviceSetReport", __func__); + return (-1); + } + + return ((int)len); +} + +size_t +fido_hid_report_in_len(void *handle) +{ + struct hid_osx *ctx = handle; + + return (ctx->report_in_len); +} + +size_t +fido_hid_report_out_len(void *handle) +{ + struct hid_osx *ctx = handle; + + return (ctx->report_out_len); +} diff --git a/src/hid_unix.c b/src/hid_unix.c new file mode 100644 index 0000000..e53882d --- /dev/null +++ b/src/hid_unix.c @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2020 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. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <sys/stat.h> + +#include <errno.h> +#include <fcntl.h> +#include <poll.h> +#include <unistd.h> + +#include "fido.h" + +#ifdef __NetBSD__ +#define ppoll pollts +#endif + +int +fido_hid_unix_open(const char *path) +{ + int fd; + struct stat st; + + if ((fd = open(path, O_RDWR)) == -1) { + if (errno != ENOENT && errno != ENXIO) + fido_log_error(errno, "%s: open %s", __func__, path); + return (-1); + } + + if (fstat(fd, &st) == -1) { + fido_log_error(errno, "%s: fstat %s", __func__, path); + if (close(fd) == -1) + fido_log_error(errno, "%s: close", __func__); + return (-1); + } + + if (S_ISCHR(st.st_mode) == 0) { + fido_log_debug("%s: S_ISCHR %s", __func__, path); + if (close(fd) == -1) + fido_log_error(errno, "%s: close", __func__); + return (-1); + } + + return (fd); +} + +int +fido_hid_unix_wait(int fd, int ms, const fido_sigset_t *sigmask) +{ + struct timespec ts; + struct pollfd pfd; + int r; + + memset(&pfd, 0, sizeof(pfd)); + pfd.events = POLLIN; + pfd.fd = fd; + +#ifdef FIDO_FUZZ + return (0); +#endif + if (ms > -1) { + ts.tv_sec = ms / 1000; + ts.tv_nsec = (ms % 1000) * 1000000; + } + + if ((r = ppoll(&pfd, 1, ms > -1 ? &ts : NULL, sigmask)) < 1) { + if (r == -1) + fido_log_error(errno, "%s: ppoll", __func__); + return (-1); + } + + return (0); +} diff --git a/src/hid_win.c b/src/hid_win.c new file mode 100644 index 0000000..bc98a17 --- /dev/null +++ b/src/hid_win.c @@ -0,0 +1,571 @@ +/* + * Copyright (c) 2019-2022 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. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <sys/types.h> + +#include <fcntl.h> +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#include <windows.h> +#include <setupapi.h> +#include <initguid.h> +#include <devpkey.h> +#include <devpropdef.h> +#include <hidclass.h> +#include <hidsdi.h> +#include <wchar.h> + +#include "fido.h" + +#if defined(__MINGW32__) && __MINGW64_VERSION_MAJOR < 6 +WINSETUPAPI WINBOOL WINAPI SetupDiGetDevicePropertyW(HDEVINFO, + PSP_DEVINFO_DATA, const DEVPROPKEY *, DEVPROPTYPE *, PBYTE, + DWORD, PDWORD, DWORD); +#endif + +#if defined(__MINGW32__) && __MINGW64_VERSION_MAJOR < 8 +DEFINE_DEVPROPKEY(DEVPKEY_Device_Parent, 0x4340a6c5, 0x93fa, 0x4706, 0x97, + 0x2c, 0x7b, 0x64, 0x80, 0x08, 0xa5, 0xa7, 8); +#endif + +struct hid_win { + HANDLE dev; + OVERLAPPED overlap; + int report_pending; + size_t report_in_len; + size_t report_out_len; + unsigned char report[1 + CTAP_MAX_REPORT_LEN]; +}; + +static bool +is_fido(HANDLE dev) +{ + PHIDP_PREPARSED_DATA data = NULL; + HIDP_CAPS caps; + int fido = 0; + + if (HidD_GetPreparsedData(dev, &data) == false) { + fido_log_debug("%s: HidD_GetPreparsedData", __func__); + goto fail; + } + + if (HidP_GetCaps(data, &caps) != HIDP_STATUS_SUCCESS) { + fido_log_debug("%s: HidP_GetCaps", __func__); + goto fail; + } + + fido = (uint16_t)caps.UsagePage == 0xf1d0; +fail: + if (data != NULL) + HidD_FreePreparsedData(data); + + return (fido); +} + +static int +get_report_len(HANDLE dev, int dir, size_t *report_len) +{ + PHIDP_PREPARSED_DATA data = NULL; + HIDP_CAPS caps; + USHORT v; + int ok = -1; + + if (HidD_GetPreparsedData(dev, &data) == false) { + fido_log_debug("%s: HidD_GetPreparsedData/%d", __func__, dir); + goto fail; + } + + if (HidP_GetCaps(data, &caps) != HIDP_STATUS_SUCCESS) { + fido_log_debug("%s: HidP_GetCaps/%d", __func__, dir); + goto fail; + } + + if (dir == 0) + v = caps.InputReportByteLength; + else + v = caps.OutputReportByteLength; + + if ((*report_len = (size_t)v) == 0) { + fido_log_debug("%s: report_len == 0", __func__); + goto fail; + } + + ok = 0; +fail: + if (data != NULL) + HidD_FreePreparsedData(data); + + return (ok); +} + +static int +get_id(HANDLE dev, int16_t *vendor_id, int16_t *product_id) +{ + HIDD_ATTRIBUTES attr; + + attr.Size = sizeof(attr); + + if (HidD_GetAttributes(dev, &attr) == false) { + fido_log_debug("%s: HidD_GetAttributes", __func__); + return (-1); + } + + *vendor_id = (int16_t)attr.VendorID; + *product_id = (int16_t)attr.ProductID; + + return (0); +} + +static int +get_manufacturer(HANDLE dev, char **manufacturer) +{ + wchar_t buf[512]; + int utf8_len; + int ok = -1; + + *manufacturer = NULL; + + if (HidD_GetManufacturerString(dev, &buf, sizeof(buf)) == false) { + fido_log_debug("%s: HidD_GetManufacturerString", __func__); + goto fail; + } + + if ((utf8_len = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, buf, + -1, NULL, 0, NULL, NULL)) <= 0 || utf8_len > 128) { + fido_log_debug("%s: WideCharToMultiByte", __func__); + goto fail; + } + + if ((*manufacturer = malloc((size_t)utf8_len)) == NULL) { + fido_log_debug("%s: malloc", __func__); + goto fail; + } + + if (WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, buf, -1, + *manufacturer, utf8_len, NULL, NULL) != utf8_len) { + fido_log_debug("%s: WideCharToMultiByte", __func__); + goto fail; + } + + ok = 0; +fail: + if (ok < 0) { + free(*manufacturer); + *manufacturer = NULL; + } + + return (ok); +} + +static int +get_product(HANDLE dev, char **product) +{ + wchar_t buf[512]; + int utf8_len; + int ok = -1; + + *product = NULL; + + if (HidD_GetProductString(dev, &buf, sizeof(buf)) == false) { + fido_log_debug("%s: HidD_GetProductString", __func__); + goto fail; + } + + if ((utf8_len = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, buf, + -1, NULL, 0, NULL, NULL)) <= 0 || utf8_len > 128) { + fido_log_debug("%s: WideCharToMultiByte", __func__); + goto fail; + } + + if ((*product = malloc((size_t)utf8_len)) == NULL) { + fido_log_debug("%s: malloc", __func__); + goto fail; + } + + if (WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, buf, -1, + *product, utf8_len, NULL, NULL) != utf8_len) { + fido_log_debug("%s: WideCharToMultiByte", __func__); + goto fail; + } + + ok = 0; +fail: + if (ok < 0) { + free(*product); + *product = NULL; + } + + return (ok); +} + +static char * +get_path(HDEVINFO devinfo, SP_DEVICE_INTERFACE_DATA *ifdata) +{ + SP_DEVICE_INTERFACE_DETAIL_DATA_A *ifdetail = NULL; + char *path = NULL; + DWORD len = 0; + + /* + * "Get the required buffer size. Call SetupDiGetDeviceInterfaceDetail + * with a NULL DeviceInterfaceDetailData pointer, a + * DeviceInterfaceDetailDataSize of zero, and a valid RequiredSize + * variable. In response to such a call, this function returns the + * required buffer size at RequiredSize and fails with GetLastError + * returning ERROR_INSUFFICIENT_BUFFER." + */ + if (SetupDiGetDeviceInterfaceDetailA(devinfo, ifdata, NULL, 0, &len, + NULL) != false || GetLastError() != ERROR_INSUFFICIENT_BUFFER) { + fido_log_debug("%s: SetupDiGetDeviceInterfaceDetailA 1", + __func__); + goto fail; + } + + if ((ifdetail = malloc(len)) == NULL) { + fido_log_debug("%s: malloc", __func__); + goto fail; + } + + ifdetail->cbSize = sizeof(*ifdetail); + + if (SetupDiGetDeviceInterfaceDetailA(devinfo, ifdata, ifdetail, len, + NULL, NULL) == false) { + fido_log_debug("%s: SetupDiGetDeviceInterfaceDetailA 2", + __func__); + goto fail; + } + + if ((path = strdup(ifdetail->DevicePath)) == NULL) { + fido_log_debug("%s: strdup", __func__); + goto fail; + } + +fail: + free(ifdetail); + + return (path); +} + +#ifndef FIDO_HID_ANY +static bool +hid_ok(HDEVINFO devinfo, DWORD idx) +{ + SP_DEVINFO_DATA devinfo_data; + wchar_t *parent = NULL; + DWORD parent_type = DEVPROP_TYPE_STRING; + DWORD len = 0; + bool ok = false; + + memset(&devinfo_data, 0, sizeof(devinfo_data)); + devinfo_data.cbSize = sizeof(devinfo_data); + + if (SetupDiEnumDeviceInfo(devinfo, idx, &devinfo_data) == false) { + fido_log_debug("%s: SetupDiEnumDeviceInfo", __func__); + goto fail; + } + + if (SetupDiGetDevicePropertyW(devinfo, &devinfo_data, + &DEVPKEY_Device_Parent, &parent_type, NULL, 0, &len, 0) != false || + GetLastError() != ERROR_INSUFFICIENT_BUFFER) { + fido_log_debug("%s: SetupDiGetDevicePropertyW 1", __func__); + goto fail; + } + + if ((parent = malloc(len)) == NULL) { + fido_log_debug("%s: malloc", __func__); + goto fail; + } + + if (SetupDiGetDevicePropertyW(devinfo, &devinfo_data, + &DEVPKEY_Device_Parent, &parent_type, (PBYTE)parent, len, NULL, + 0) == false) { + fido_log_debug("%s: SetupDiGetDevicePropertyW 2", __func__); + goto fail; + } + + ok = wcsncmp(parent, L"USB\\", 4) == 0; +fail: + free(parent); + + return (ok); +} +#endif + +static int +copy_info(fido_dev_info_t *di, HDEVINFO devinfo, DWORD idx, + SP_DEVICE_INTERFACE_DATA *ifdata) +{ + HANDLE dev = INVALID_HANDLE_VALUE; + int ok = -1; + + memset(di, 0, sizeof(*di)); + + if ((di->path = get_path(devinfo, ifdata)) == NULL) { + fido_log_debug("%s: get_path", __func__); + goto fail; + } + + fido_log_debug("%s: path=%s", __func__, di->path); + +#ifndef FIDO_HID_ANY + if (hid_ok(devinfo, idx) == false) { + fido_log_debug("%s: hid_ok", __func__); + goto fail; + } +#endif + + dev = CreateFileA(di->path, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (dev == INVALID_HANDLE_VALUE) { + fido_log_debug("%s: CreateFileA", __func__); + goto fail; + } + + if (is_fido(dev) == false) { + fido_log_debug("%s: is_fido", __func__); + goto fail; + } + + if (get_id(dev, &di->vendor_id, &di->product_id) < 0) { + fido_log_debug("%s: get_id", __func__); + goto fail; + } + + if (get_manufacturer(dev, &di->manufacturer) < 0) { + fido_log_debug("%s: get_manufacturer", __func__); + di->manufacturer = strdup(""); + } + + if (get_product(dev, &di->product) < 0) { + fido_log_debug("%s: get_product", __func__); + di->product = strdup(""); + } + + if (di->manufacturer == NULL || di->product == NULL) { + fido_log_debug("%s: manufacturer/product", __func__); + goto fail; + } + + ok = 0; +fail: + if (dev != INVALID_HANDLE_VALUE) + CloseHandle(dev); + + if (ok < 0) { + free(di->path); + free(di->manufacturer); + free(di->product); + explicit_bzero(di, sizeof(*di)); + } + + return (ok); +} + +int +fido_hid_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen) +{ + GUID hid_guid = GUID_DEVINTERFACE_HID; + HDEVINFO devinfo = INVALID_HANDLE_VALUE; + SP_DEVICE_INTERFACE_DATA ifdata; + DWORD idx; + int r = FIDO_ERR_INTERNAL; + + *olen = 0; + + if (ilen == 0) + return (FIDO_OK); /* nothing to do */ + if (devlist == NULL) + return (FIDO_ERR_INVALID_ARGUMENT); + + if ((devinfo = SetupDiGetClassDevsA(&hid_guid, NULL, NULL, + DIGCF_DEVICEINTERFACE | DIGCF_PRESENT)) == INVALID_HANDLE_VALUE) { + fido_log_debug("%s: SetupDiGetClassDevsA", __func__); + goto fail; + } + + ifdata.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); + + for (idx = 0; SetupDiEnumDeviceInterfaces(devinfo, NULL, &hid_guid, + idx, &ifdata) == true; idx++) { + if (copy_info(&devlist[*olen], devinfo, idx, &ifdata) == 0) { + devlist[*olen].io = (fido_dev_io_t) { + fido_hid_open, + fido_hid_close, + fido_hid_read, + fido_hid_write, + }; + if (++(*olen) == ilen) + break; + } + } + + r = FIDO_OK; +fail: + if (devinfo != INVALID_HANDLE_VALUE) + SetupDiDestroyDeviceInfoList(devinfo); + + return (r); +} + +void * +fido_hid_open(const char *path) +{ + struct hid_win *ctx; + + if ((ctx = calloc(1, sizeof(*ctx))) == NULL) + return (NULL); + + ctx->dev = CreateFileA(path, GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, + FILE_FLAG_OVERLAPPED, NULL); + + if (ctx->dev == INVALID_HANDLE_VALUE) { + free(ctx); + return (NULL); + } + + if ((ctx->overlap.hEvent = CreateEventA(NULL, FALSE, FALSE, + NULL)) == NULL) { + fido_log_debug("%s: CreateEventA", __func__); + fido_hid_close(ctx); + return (NULL); + } + + if (get_report_len(ctx->dev, 0, &ctx->report_in_len) < 0 || + get_report_len(ctx->dev, 1, &ctx->report_out_len) < 0) { + fido_log_debug("%s: get_report_len", __func__); + fido_hid_close(ctx); + return (NULL); + } + + return (ctx); +} + +void +fido_hid_close(void *handle) +{ + struct hid_win *ctx = handle; + + if (ctx->overlap.hEvent != NULL) { + if (ctx->report_pending) { + fido_log_debug("%s: report_pending", __func__); + if (CancelIoEx(ctx->dev, &ctx->overlap) == 0) + fido_log_debug("%s CancelIoEx: 0x%lx", + __func__, (u_long)GetLastError()); + } + CloseHandle(ctx->overlap.hEvent); + } + + explicit_bzero(ctx->report, sizeof(ctx->report)); + CloseHandle(ctx->dev); + free(ctx); +} + +int +fido_hid_set_sigmask(void *handle, const fido_sigset_t *sigmask) +{ + (void)handle; + (void)sigmask; + + return (FIDO_ERR_INTERNAL); +} + +int +fido_hid_read(void *handle, unsigned char *buf, size_t len, int ms) +{ + struct hid_win *ctx = handle; + DWORD n; + + if (len != ctx->report_in_len - 1 || len > sizeof(ctx->report) - 1) { + fido_log_debug("%s: len %zu", __func__, len); + return (-1); + } + + if (ctx->report_pending == 0) { + memset(&ctx->report, 0, sizeof(ctx->report)); + ResetEvent(ctx->overlap.hEvent); + if (ReadFile(ctx->dev, ctx->report, (DWORD)(len + 1), &n, + &ctx->overlap) == 0 && GetLastError() != ERROR_IO_PENDING) { + CancelIo(ctx->dev); + fido_log_debug("%s: ReadFile", __func__); + return (-1); + } + ctx->report_pending = 1; + } + + if (ms > -1 && WaitForSingleObject(ctx->overlap.hEvent, + (DWORD)ms) != WAIT_OBJECT_0) + return (0); + + ctx->report_pending = 0; + + if (GetOverlappedResult(ctx->dev, &ctx->overlap, &n, TRUE) == 0) { + fido_log_debug("%s: GetOverlappedResult", __func__); + return (-1); + } + + if (n != len + 1) { + fido_log_debug("%s: expected %zu, got %zu", __func__, + len + 1, (size_t)n); + return (-1); + } + + memcpy(buf, ctx->report + 1, len); + explicit_bzero(ctx->report, sizeof(ctx->report)); + + return ((int)len); +} + +int +fido_hid_write(void *handle, const unsigned char *buf, size_t len) +{ + struct hid_win *ctx = handle; + OVERLAPPED overlap; + DWORD n; + + memset(&overlap, 0, sizeof(overlap)); + + if (len != ctx->report_out_len) { + fido_log_debug("%s: len %zu", __func__, len); + return (-1); + } + + if (WriteFile(ctx->dev, buf, (DWORD)len, NULL, &overlap) == 0 && + GetLastError() != ERROR_IO_PENDING) { + fido_log_debug("%s: WriteFile", __func__); + return (-1); + } + + if (GetOverlappedResult(ctx->dev, &overlap, &n, TRUE) == 0) { + fido_log_debug("%s: GetOverlappedResult", __func__); + return (-1); + } + + if (n != len) { + fido_log_debug("%s: expected %zu, got %zu", __func__, len, + (size_t)n); + return (-1); + } + + return ((int)len); +} + +size_t +fido_hid_report_in_len(void *handle) +{ + struct hid_win *ctx = handle; + + return (ctx->report_in_len - 1); +} + +size_t +fido_hid_report_out_len(void *handle) +{ + struct hid_win *ctx = handle; + + return (ctx->report_out_len - 1); +} diff --git a/src/info.c b/src/info.c new file mode 100644 index 0000000..cd30828 --- /dev/null +++ b/src/info.c @@ -0,0 +1,647 @@ +/* + * Copyright (c) 2018-2022 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. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "fido.h" + +static int +decode_string(const cbor_item_t *item, void *arg) +{ + fido_str_array_t *a = arg; + const size_t i = a->len; + + /* keep ptr[x] and len consistent */ + if (cbor_string_copy(item, &a->ptr[i]) < 0) { + fido_log_debug("%s: cbor_string_copy", __func__); + return (-1); + } + + a->len++; + + return (0); +} + +static int +decode_string_array(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) { + fido_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_string) < 0) { + fido_log_debug("%s: decode_string", __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) { + fido_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_decode_bool(val, NULL) < 0) { + fido_log_debug("%s: cbor_decode_bool", __func__); + return (0); /* ignore */ + } + + if (cbor_string_copy(key, &o->name[i]) < 0) { + fido_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) { + fido_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) { + fido_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) { + fido_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) { + fido_log_debug("%s: decode_protocol", __func__); + return (-1); + } + + return (0); +} + +static int +decode_algorithm_entry(const cbor_item_t *key, const cbor_item_t *val, + void *arg) +{ + fido_algo_t *alg = arg; + char *name = NULL; + int ok = -1; + + if (cbor_string_copy(key, &name) < 0) { + fido_log_debug("%s: cbor type", __func__); + ok = 0; /* ignore */ + goto out; + } + + if (!strcmp(name, "alg")) { + if (cbor_isa_negint(val) == false || + cbor_get_int(val) > INT_MAX || alg->cose != 0) { + fido_log_debug("%s: alg", __func__); + goto out; + } + alg->cose = -(int)cbor_get_int(val) - 1; + } else if (!strcmp(name, "type")) { + if (cbor_string_copy(val, &alg->type) < 0) { + fido_log_debug("%s: type", __func__); + goto out; + } + } + + ok = 0; +out: + free(name); + + return (ok); +} + +static int +decode_algorithm(const cbor_item_t *item, void *arg) +{ + fido_algo_array_t *aa = arg; + const size_t i = aa->len; + + if (cbor_isa_map(item) == false || + cbor_map_is_definite(item) == false) { + fido_log_debug("%s: cbor type", __func__); + return (-1); + } + + memset(&aa->ptr[i], 0, sizeof(aa->ptr[i])); + + if (cbor_map_iter(item, &aa->ptr[i], decode_algorithm_entry) < 0) { + fido_log_debug("%s: decode_algorithm_entry", __func__); + fido_algo_free(&aa->ptr[i]); + return (-1); + } + + /* keep ptr[x] and len consistent */ + aa->len++; + + return (0); +} + +static int +decode_algorithms(const cbor_item_t *item, fido_algo_array_t *aa) +{ + aa->ptr = NULL; + aa->len = 0; + + if (cbor_isa_array(item) == false || + cbor_array_is_definite(item) == false) { + fido_log_debug("%s: cbor type", __func__); + return (-1); + } + + aa->ptr = calloc(cbor_array_size(item), sizeof(fido_algo_t)); + if (aa->ptr == NULL) + return (-1); + + if (cbor_array_iter(item, aa, decode_algorithm) < 0) { + fido_log_debug("%s: decode_algorithm", __func__); + return (-1); + } + + return (0); +} + +static int +decode_cert(const cbor_item_t *key, const cbor_item_t *val, void *arg) +{ + fido_cert_array_t *c = arg; + const size_t i = c->len; + + if (cbor_is_int(val) == false) { + fido_log_debug("%s: cbor_is_int", __func__); + return (0); /* ignore */ + } + + if (cbor_string_copy(key, &c->name[i]) < 0) { + fido_log_debug("%s: cbor_string_copy", __func__); + return (0); /* ignore */ + } + + /* keep name/value and len consistent */ + c->value[i] = cbor_get_int(val); + c->len++; + + return (0); +} + +static int +decode_certs(const cbor_item_t *item, fido_cert_array_t *c) +{ + c->name = NULL; + c->value = NULL; + c->len = 0; + + if (cbor_isa_map(item) == false || + cbor_map_is_definite(item) == false) { + fido_log_debug("%s: cbor type", __func__); + return (-1); + } + + c->name = calloc(cbor_map_size(item), sizeof(char *)); + c->value = calloc(cbor_map_size(item), sizeof(uint64_t)); + if (c->name == NULL || c->value == NULL) + return (-1); + + return (cbor_map_iter(item, c, decode_cert)); +} + +static int +parse_reply_element(const cbor_item_t *key, const cbor_item_t *val, void *arg) +{ + fido_cbor_info_t *ci = arg; + uint64_t x; + + if (cbor_isa_uint(key) == false || + cbor_int_get_width(key) != CBOR_INT_8) { + fido_log_debug("%s: cbor type", __func__); + return (0); /* ignore */ + } + + switch (cbor_get_uint8(key)) { + case 1: /* versions */ + return (decode_string_array(val, &ci->versions)); + case 2: /* extensions */ + return (decode_string_array(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 (cbor_decode_uint64(val, &ci->maxmsgsiz)); + case 6: /* pinProtocols */ + return (decode_protocols(val, &ci->protocols)); + case 7: /* maxCredentialCountInList */ + return (cbor_decode_uint64(val, &ci->maxcredcntlst)); + case 8: /* maxCredentialIdLength */ + return (cbor_decode_uint64(val, &ci->maxcredidlen)); + case 9: /* transports */ + return (decode_string_array(val, &ci->transports)); + case 10: /* algorithms */ + return (decode_algorithms(val, &ci->algorithms)); + case 11: /* maxSerializedLargeBlobArray */ + return (cbor_decode_uint64(val, &ci->maxlargeblob)); + case 12: /* forcePINChange */ + return (cbor_decode_bool(val, &ci->new_pin_reqd)); + case 13: /* minPINLength */ + return (cbor_decode_uint64(val, &ci->minpinlen)); + case 14: /* fwVersion */ + return (cbor_decode_uint64(val, &ci->fwversion)); + case 15: /* maxCredBlobLen */ + return (cbor_decode_uint64(val, &ci->maxcredbloblen)); + case 16: /* maxRPIDsForSetMinPINLength */ + return (cbor_decode_uint64(val, &ci->maxrpid_minlen)); + case 17: /* preferredPlatformUvAttempts */ + return (cbor_decode_uint64(val, &ci->uv_attempts)); + case 18: /* uvModality */ + return (cbor_decode_uint64(val, &ci->uv_modality)); + case 19: /* certifications */ + return (decode_certs(val, &ci->certs)); + case 20: /* remainingDiscoverableCredentials */ + if (cbor_decode_uint64(val, &x) < 0 || x > INT64_MAX) { + fido_log_debug("%s: cbor_decode_uint64", __func__); + return (-1); + } + ci->rk_remaining = (int64_t)x; + return (0); + default: /* ignore */ + fido_log_debug("%s: cbor type: 0x%02x", __func__, cbor_get_uint8(key)); + return (0); + } +} + +static int +fido_dev_get_cbor_info_tx(fido_dev_t *dev, int *ms) +{ + const unsigned char cbor[] = { CTAP_CBOR_GETINFO }; + + fido_log_debug("%s: dev=%p", __func__, (void *)dev); + + if (fido_tx(dev, CTAP_CMD_CBOR, cbor, sizeof(cbor), ms) < 0) { + fido_log_debug("%s: fido_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) +{ + unsigned char *msg; + int msglen; + int r; + + fido_log_debug("%s: dev=%p, ci=%p, ms=%d", __func__, (void *)dev, + (void *)ci, *ms); + + fido_cbor_info_reset(ci); + + if ((msg = malloc(FIDO_MAXMSG)) == NULL) { + r = FIDO_ERR_INTERNAL; + goto out; + } + + if ((msglen = fido_rx(dev, CTAP_CMD_CBOR, msg, FIDO_MAXMSG, ms)) < 0) { + fido_log_debug("%s: fido_rx", __func__); + r = FIDO_ERR_RX; + goto out; + } + + r = cbor_parse_reply(msg, (size_t)msglen, ci, parse_reply_element); +out: + freezero(msg, FIDO_MAXMSG); + + return (r); +} + +int +fido_dev_get_cbor_info_wait(fido_dev_t *dev, fido_cbor_info_t *ci, int *ms) +{ + int r; + +#ifdef USE_WINHELLO + if (dev->flags & FIDO_DEV_WINHELLO) + return (fido_winhello_get_cbor_info(dev, ci)); +#endif + if ((r = fido_dev_get_cbor_info_tx(dev, ms)) != 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) +{ + int ms = dev->timeout_ms; + + return (fido_dev_get_cbor_info_wait(dev, ci, &ms)); +} + +/* + * get/set functions for fido_cbor_info_t; always at the end of the file + */ + +fido_cbor_info_t * +fido_cbor_info_new(void) +{ + fido_cbor_info_t *ci; + + if ((ci = calloc(1, sizeof(fido_cbor_info_t))) == NULL) + return (NULL); + + fido_cbor_info_reset(ci); + + return (ci); +} + +void +fido_cbor_info_reset(fido_cbor_info_t *ci) +{ + fido_str_array_free(&ci->versions); + fido_str_array_free(&ci->extensions); + fido_str_array_free(&ci->transports); + fido_opt_array_free(&ci->options); + fido_byte_array_free(&ci->protocols); + fido_algo_array_free(&ci->algorithms); + fido_cert_array_free(&ci->certs); + ci->rk_remaining = -1; +} + +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; + fido_cbor_info_reset(ci); + 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); +} + +char ** +fido_cbor_info_transports_ptr(const fido_cbor_info_t *ci) +{ + return (ci->transports.ptr); +} + +size_t +fido_cbor_info_transports_len(const fido_cbor_info_t *ci) +{ + return (ci->transports.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_maxcredbloblen(const fido_cbor_info_t *ci) +{ + return (ci->maxcredbloblen); +} + +uint64_t +fido_cbor_info_maxmsgsiz(const fido_cbor_info_t *ci) +{ + return (ci->maxmsgsiz); +} + +uint64_t +fido_cbor_info_maxcredcntlst(const fido_cbor_info_t *ci) +{ + return (ci->maxcredcntlst); +} + +uint64_t +fido_cbor_info_maxcredidlen(const fido_cbor_info_t *ci) +{ + return (ci->maxcredidlen); +} + +uint64_t +fido_cbor_info_maxlargeblob(const fido_cbor_info_t *ci) +{ + return (ci->maxlargeblob); +} + +uint64_t +fido_cbor_info_fwversion(const fido_cbor_info_t *ci) +{ + return (ci->fwversion); +} + +uint64_t +fido_cbor_info_minpinlen(const fido_cbor_info_t *ci) +{ + return (ci->minpinlen); +} + +uint64_t +fido_cbor_info_maxrpid_minpinlen(const fido_cbor_info_t *ci) +{ + return (ci->maxrpid_minlen); +} + +uint64_t +fido_cbor_info_uv_attempts(const fido_cbor_info_t *ci) +{ + return (ci->uv_attempts); +} + +uint64_t +fido_cbor_info_uv_modality(const fido_cbor_info_t *ci) +{ + return (ci->uv_modality); +} + +int64_t +fido_cbor_info_rk_remaining(const fido_cbor_info_t *ci) +{ + return (ci->rk_remaining); +} + +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); +} + +size_t +fido_cbor_info_algorithm_count(const fido_cbor_info_t *ci) +{ + return (ci->algorithms.len); +} + +const char * +fido_cbor_info_algorithm_type(const fido_cbor_info_t *ci, size_t idx) +{ + if (idx >= ci->algorithms.len) + return (NULL); + + return (ci->algorithms.ptr[idx].type); +} + +int +fido_cbor_info_algorithm_cose(const fido_cbor_info_t *ci, size_t idx) +{ + if (idx >= ci->algorithms.len) + return (0); + + return (ci->algorithms.ptr[idx].cose); +} + +bool +fido_cbor_info_new_pin_required(const fido_cbor_info_t *ci) +{ + return (ci->new_pin_reqd); +} + +char ** +fido_cbor_info_certs_name_ptr(const fido_cbor_info_t *ci) +{ + return (ci->certs.name); +} + +const uint64_t * +fido_cbor_info_certs_value_ptr(const fido_cbor_info_t *ci) +{ + return (ci->certs.value); +} + +size_t +fido_cbor_info_certs_len(const fido_cbor_info_t *ci) +{ + return (ci->certs.len); +} diff --git a/src/io.c b/src/io.c new file mode 100644 index 0000000..a9715b5 --- /dev/null +++ b/src/io.c @@ -0,0 +1,356 @@ +/* + * Copyright (c) 2018-2022 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. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#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_MAX_REPORT_LEN - CTAP_INIT_HEADER_LEN]; + } init; + struct { + uint8_t seq; + uint8_t data[CTAP_MAX_REPORT_LEN - CTAP_CONT_HEADER_LEN]; + } cont; + } body; +}) + +#ifndef MIN +#define MIN(x, y) ((x) > (y) ? (y) : (x)) +#endif + +static int +tx_pkt(fido_dev_t *d, const void *pkt, size_t len, int *ms) +{ + struct timespec ts; + int n; + + if (fido_time_now(&ts) != 0) + return (-1); + + n = d->io.write(d->io_handle, pkt, len); + + if (fido_time_delta(&ts, ms) != 0) + return (-1); + + return (n); +} + +static int +tx_empty(fido_dev_t *d, uint8_t cmd, int *ms) +{ + struct frame *fp; + unsigned char pkt[sizeof(*fp) + 1]; + const size_t len = d->tx_len + 1; + int n; + + memset(&pkt, 0, sizeof(pkt)); + fp = (struct frame *)(pkt + 1); + fp->cid = d->cid; + fp->body.init.cmd = CTAP_FRAME_INIT | cmd; + + if (len > sizeof(pkt) || (n = tx_pkt(d, pkt, len, ms)) < 0 || + (size_t)n != len) + return (-1); + + return (0); +} + +static size_t +tx_preamble(fido_dev_t *d, uint8_t cmd, const void *buf, size_t count, int *ms) +{ + struct frame *fp; + unsigned char pkt[sizeof(*fp) + 1]; + const size_t len = d->tx_len + 1; + int n; + + if (d->tx_len - CTAP_INIT_HEADER_LEN > sizeof(fp->body.init.data)) + return (0); + + memset(&pkt, 0, sizeof(pkt)); + fp = (struct frame *)(pkt + 1); + fp->cid = d->cid; + fp->body.init.cmd = CTAP_FRAME_INIT | cmd; + fp->body.init.bcnth = (count >> 8) & 0xff; + fp->body.init.bcntl = count & 0xff; + count = MIN(count, d->tx_len - CTAP_INIT_HEADER_LEN); + memcpy(&fp->body.init.data, buf, count); + + if (len > sizeof(pkt) || (n = tx_pkt(d, pkt, len, ms)) < 0 || + (size_t)n != len) + return (0); + + return (count); +} + +static size_t +tx_frame(fido_dev_t *d, uint8_t seq, const void *buf, size_t count, int *ms) +{ + struct frame *fp; + unsigned char pkt[sizeof(*fp) + 1]; + const size_t len = d->tx_len + 1; + int n; + + if (d->tx_len - CTAP_CONT_HEADER_LEN > sizeof(fp->body.cont.data)) + return (0); + + memset(&pkt, 0, sizeof(pkt)); + fp = (struct frame *)(pkt + 1); + fp->cid = d->cid; + fp->body.cont.seq = seq; + count = MIN(count, d->tx_len - CTAP_CONT_HEADER_LEN); + memcpy(&fp->body.cont.data, buf, count); + + if (len > sizeof(pkt) || (n = tx_pkt(d, pkt, len, ms)) < 0 || + (size_t)n != len) + return (0); + + return (count); +} + +static int +tx(fido_dev_t *d, uint8_t cmd, const unsigned char *buf, size_t count, int *ms) +{ + size_t n, sent; + + if ((sent = tx_preamble(d, cmd, buf, count, ms)) == 0) { + fido_log_debug("%s: tx_preamble", __func__); + return (-1); + } + + for (uint8_t seq = 0; sent < count; sent += n) { + if (seq & 0x80) { + fido_log_debug("%s: seq & 0x80", __func__); + return (-1); + } + if ((n = tx_frame(d, seq++, buf + sent, count - sent, + ms)) == 0) { + fido_log_debug("%s: tx_frame", __func__); + return (-1); + } + } + + return (0); +} + +static int +transport_tx(fido_dev_t *d, uint8_t cmd, const void *buf, size_t count, int *ms) +{ + struct timespec ts; + int n; + + if (fido_time_now(&ts) != 0) + return (-1); + + n = d->transport.tx(d, cmd, buf, count); + + if (fido_time_delta(&ts, ms) != 0) + return (-1); + + return (n); +} + +int +fido_tx(fido_dev_t *d, uint8_t cmd, const void *buf, size_t count, int *ms) +{ + fido_log_debug("%s: dev=%p, cmd=0x%02x", __func__, (void *)d, cmd); + fido_log_xxd(buf, count, "%s", __func__); + + if (d->transport.tx != NULL) + return (transport_tx(d, cmd, buf, count, ms)); + if (d->io_handle == NULL || d->io.write == NULL || count > UINT16_MAX) { + fido_log_debug("%s: invalid argument", __func__); + return (-1); + } + + return (count == 0 ? tx_empty(d, cmd, ms) : tx(d, cmd, buf, count, ms)); +} + +static int +rx_frame(fido_dev_t *d, struct frame *fp, int *ms) +{ + struct timespec ts; + int n; + + memset(fp, 0, sizeof(*fp)); + + if (fido_time_now(&ts) != 0) + return (-1); + + if (d->rx_len > sizeof(*fp) || (n = d->io.read(d->io_handle, + (unsigned char *)fp, d->rx_len, *ms)) < 0 || (size_t)n != d->rx_len) + return (-1); + + return (fido_time_delta(&ts, ms)); +} + +static int +rx_preamble(fido_dev_t *d, uint8_t cmd, 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->cid == d->cid && + fp->body.init.cmd == (CTAP_FRAME_INIT | CTAP_KEEPALIVE))); + + if (d->rx_len > sizeof(*fp)) + return (-1); + + fido_log_xxd(fp, d->rx_len, "%s", __func__); +#ifdef FIDO_FUZZ + fp->body.init.cmd = (CTAP_FRAME_INIT | cmd); +#endif + + if (fp->cid != d->cid || fp->body.init.cmd != (CTAP_FRAME_INIT | cmd)) { + fido_log_debug("%s: cid (0x%x, 0x%x), cmd (0x%02x, 0x%02x)", + __func__, fp->cid, d->cid, fp->body.init.cmd, cmd); + return (-1); + } + + return (0); +} + +static int +rx(fido_dev_t *d, uint8_t cmd, unsigned char *buf, size_t count, int *ms) +{ + struct frame f; + size_t r, payload_len, init_data_len, cont_data_len; + + if (d->rx_len <= CTAP_INIT_HEADER_LEN || + d->rx_len <= CTAP_CONT_HEADER_LEN) + return (-1); + + init_data_len = d->rx_len - CTAP_INIT_HEADER_LEN; + cont_data_len = d->rx_len - CTAP_CONT_HEADER_LEN; + + if (init_data_len > sizeof(f.body.init.data) || + cont_data_len > sizeof(f.body.cont.data)) + return (-1); + + if (rx_preamble(d, cmd, &f, ms) < 0) { + fido_log_debug("%s: rx_preamble", __func__); + return (-1); + } + + payload_len = (size_t)((f.body.init.bcnth << 8) | f.body.init.bcntl); + fido_log_debug("%s: payload_len=%zu", __func__, payload_len); + + if (count < payload_len) { + fido_log_debug("%s: count < payload_len", __func__); + return (-1); + } + + if (payload_len < init_data_len) { + memcpy(buf, f.body.init.data, payload_len); + return ((int)payload_len); + } + + memcpy(buf, f.body.init.data, init_data_len); + r = init_data_len; + + for (int seq = 0; r < payload_len; seq++) { + if (rx_frame(d, &f, ms) < 0) { + fido_log_debug("%s: rx_frame", __func__); + return (-1); + } + + fido_log_xxd(&f, d->rx_len, "%s", __func__); +#ifdef FIDO_FUZZ + f.cid = d->cid; + f.body.cont.seq = (uint8_t)seq; +#endif + + if (f.cid != d->cid || f.body.cont.seq != seq) { + fido_log_debug("%s: cid (0x%x, 0x%x), seq (%d, %d)", + __func__, f.cid, d->cid, f.body.cont.seq, seq); + return (-1); + } + + if (payload_len - r > cont_data_len) { + memcpy(buf + r, f.body.cont.data, cont_data_len); + r += cont_data_len; + } else { + memcpy(buf + r, f.body.cont.data, payload_len - r); + r += payload_len - r; /* break */ + } + } + + return ((int)r); +} + +static int +transport_rx(fido_dev_t *d, uint8_t cmd, void *buf, size_t count, int *ms) +{ + struct timespec ts; + int n; + + if (fido_time_now(&ts) != 0) + return (-1); + + n = d->transport.rx(d, cmd, buf, count, *ms); + + if (fido_time_delta(&ts, ms) != 0) + return (-1); + + return (n); +} + +int +fido_rx(fido_dev_t *d, uint8_t cmd, void *buf, size_t count, int *ms) +{ + int n; + + fido_log_debug("%s: dev=%p, cmd=0x%02x, ms=%d", __func__, (void *)d, + cmd, *ms); + + if (d->transport.rx != NULL) + return (transport_rx(d, cmd, buf, count, ms)); + if (d->io_handle == NULL || d->io.read == NULL || count > UINT16_MAX) { + fido_log_debug("%s: invalid argument", __func__); + return (-1); + } + if ((n = rx(d, cmd, buf, count, ms)) >= 0) + fido_log_xxd(buf, (size_t)n, "%s", __func__); + + return (n); +} + +int +fido_rx_cbor_status(fido_dev_t *d, int *ms) +{ + unsigned char *msg; + int msglen; + int r; + + if ((msg = malloc(FIDO_MAXMSG)) == NULL) { + r = FIDO_ERR_INTERNAL; + goto out; + } + + if ((msglen = fido_rx(d, CTAP_CMD_CBOR, msg, FIDO_MAXMSG, ms)) < 0 || + (size_t)msglen < 1) { + fido_log_debug("%s: fido_rx", __func__); + r = FIDO_ERR_RX; + goto out; + } + + r = msg[0]; +out: + freezero(msg, FIDO_MAXMSG); + + return (r); +} diff --git a/src/iso7816.c b/src/iso7816.c new file mode 100644 index 0000000..5bba106 --- /dev/null +++ b/src/iso7816.c @@ -0,0 +1,65 @@ +/* + * 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. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "fido.h" + +iso7816_apdu_t * +iso7816_new(uint8_t cla, 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 + 2; /* le1 le2 */ + 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.cla = cla; + apdu->header.ins = ins; + apdu->header.p1 = p1; + apdu->header.lc2 = (uint8_t)((payload_len >> 8) & 0xff); + apdu->header.lc3 = (uint8_t)(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; + freezero(apdu, apdu->alloc_len); + *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)(apdu->payload_len - 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 - offsetof(iso7816_apdu_t, header) - + (sizeof(iso7816_apdu_t) - offsetof(iso7816_apdu_t, payload)); +} diff --git a/src/iso7816.h b/src/iso7816.h new file mode 100644 index 0000000..7545719 --- /dev/null +++ b/src/iso7816.h @@ -0,0 +1,49 @@ +/* + * 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. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#ifndef _ISO7816_H +#define _ISO7816_H + +#include <stdint.h> +#include <stdlib.h> + +#include "packed.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +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; +}) + +typedef struct iso7816_apdu { + size_t alloc_len; + uint16_t payload_len; + uint8_t *payload_ptr; + iso7816_header_t header; + uint8_t payload[]; +} iso7816_apdu_t; + +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, uint8_t, uint16_t); +size_t iso7816_len(const iso7816_apdu_t *); +void iso7816_free(iso7816_apdu_t **); + +#ifdef __cplusplus +} /* extern "C" */ +#endif /* __cplusplus */ + +#endif /* !_ISO7816_H */ diff --git a/src/largeblob.c b/src/largeblob.c new file mode 100644 index 0000000..c1f2e62 --- /dev/null +++ b/src/largeblob.c @@ -0,0 +1,902 @@ +/* + * Copyright (c) 2020-2022 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. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <openssl/sha.h> + +#include "fido.h" +#include "fido/es256.h" + +#define LARGEBLOB_DIGEST_LENGTH 16 +#define LARGEBLOB_NONCE_LENGTH 12 +#define LARGEBLOB_TAG_LENGTH 16 + +typedef struct largeblob { + size_t origsiz; + fido_blob_t ciphertext; + fido_blob_t nonce; +} largeblob_t; + +static largeblob_t * +largeblob_new(void) +{ + return calloc(1, sizeof(largeblob_t)); +} + +static void +largeblob_reset(largeblob_t *blob) +{ + fido_blob_reset(&blob->ciphertext); + fido_blob_reset(&blob->nonce); + blob->origsiz = 0; +} + +static void +largeblob_free(largeblob_t **blob_ptr) +{ + largeblob_t *blob; + + if (blob_ptr == NULL || (blob = *blob_ptr) == NULL) + return; + largeblob_reset(blob); + free(blob); + *blob_ptr = NULL; +} + +static int +largeblob_aad(fido_blob_t *aad, uint64_t size) +{ + uint8_t buf[4 + sizeof(uint64_t)]; + + buf[0] = 0x62; /* b */ + buf[1] = 0x6c; /* l */ + buf[2] = 0x6f; /* o */ + buf[3] = 0x62; /* b */ + size = htole64(size); + memcpy(&buf[4], &size, sizeof(uint64_t)); + + return fido_blob_set(aad, buf, sizeof(buf)); +} + +static fido_blob_t * +largeblob_decrypt(const largeblob_t *blob, const fido_blob_t *key) +{ + fido_blob_t *plaintext = NULL, *aad = NULL; + int ok = -1; + + if ((plaintext = fido_blob_new()) == NULL || + (aad = fido_blob_new()) == NULL) { + fido_log_debug("%s: fido_blob_new", __func__); + goto fail; + } + if (largeblob_aad(aad, blob->origsiz) < 0) { + fido_log_debug("%s: largeblob_aad", __func__); + goto fail; + } + if (aes256_gcm_dec(key, &blob->nonce, aad, &blob->ciphertext, + plaintext) < 0) { + fido_log_debug("%s: aes256_gcm_dec", __func__); + goto fail; + } + + ok = 0; +fail: + fido_blob_free(&aad); + + if (ok < 0) + fido_blob_free(&plaintext); + + return plaintext; +} + +static int +largeblob_get_nonce(largeblob_t *blob) +{ + uint8_t buf[LARGEBLOB_NONCE_LENGTH]; + int ok = -1; + + if (fido_get_random(buf, sizeof(buf)) < 0) { + fido_log_debug("%s: fido_get_random", __func__); + goto fail; + } + if (fido_blob_set(&blob->nonce, buf, sizeof(buf)) < 0) { + fido_log_debug("%s: fido_blob_set", __func__); + goto fail; + } + + ok = 0; +fail: + explicit_bzero(buf, sizeof(buf)); + + return ok; +} + +static int +largeblob_seal(largeblob_t *blob, const fido_blob_t *body, + const fido_blob_t *key) +{ + fido_blob_t *plaintext = NULL, *aad = NULL; + int ok = -1; + + if ((plaintext = fido_blob_new()) == NULL || + (aad = fido_blob_new()) == NULL) { + fido_log_debug("%s: fido_blob_new", __func__); + goto fail; + } + if (fido_compress(plaintext, body) != FIDO_OK) { + fido_log_debug("%s: fido_compress", __func__); + goto fail; + } + if (largeblob_aad(aad, body->len) < 0) { + fido_log_debug("%s: largeblob_aad", __func__); + goto fail; + } + if (largeblob_get_nonce(blob) < 0) { + fido_log_debug("%s: largeblob_get_nonce", __func__); + goto fail; + } + if (aes256_gcm_enc(key, &blob->nonce, aad, plaintext, + &blob->ciphertext) < 0) { + fido_log_debug("%s: aes256_gcm_enc", __func__); + goto fail; + } + blob->origsiz = body->len; + + ok = 0; +fail: + fido_blob_free(&plaintext); + fido_blob_free(&aad); + + return ok; +} + +static int +largeblob_get_tx(fido_dev_t *dev, size_t offset, size_t count, int *ms) +{ + fido_blob_t f; + cbor_item_t *argv[3]; + int r; + + memset(argv, 0, sizeof(argv)); + memset(&f, 0, sizeof(f)); + + if ((argv[0] = cbor_build_uint(count)) == NULL || + (argv[2] = cbor_build_uint(offset)) == NULL) { + fido_log_debug("%s: cbor encode", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + if (cbor_build_frame(CTAP_CBOR_LARGEBLOB, argv, nitems(argv), &f) < 0 || + fido_tx(dev, CTAP_CMD_CBOR, f.ptr, f.len, ms) < 0) { + fido_log_debug("%s: fido_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 +parse_largeblob_reply(const cbor_item_t *key, const cbor_item_t *val, + void *arg) +{ + if (cbor_isa_uint(key) == false || + cbor_int_get_width(key) != CBOR_INT_8 || + cbor_get_uint8(key) != 1) { + fido_log_debug("%s: cbor type", __func__); + return 0; /* ignore */ + } + + return fido_blob_decode(val, arg); +} + +static int +largeblob_get_rx(fido_dev_t *dev, fido_blob_t **chunk, int *ms) +{ + unsigned char *msg; + int msglen, r; + + *chunk = NULL; + if ((msg = malloc(FIDO_MAXMSG)) == NULL) { + r = FIDO_ERR_INTERNAL; + goto out; + } + if ((msglen = fido_rx(dev, CTAP_CMD_CBOR, msg, FIDO_MAXMSG, ms)) < 0) { + fido_log_debug("%s: fido_rx", __func__); + r = FIDO_ERR_RX; + goto out; + } + if ((*chunk = fido_blob_new()) == NULL) { + fido_log_debug("%s: fido_blob_new", __func__); + r = FIDO_ERR_INTERNAL; + goto out; + } + if ((r = cbor_parse_reply(msg, (size_t)msglen, *chunk, + parse_largeblob_reply)) != FIDO_OK) { + fido_log_debug("%s: parse_largeblob_reply", __func__); + goto out; + } + + r = FIDO_OK; +out: + if (r != FIDO_OK) + fido_blob_free(chunk); + + freezero(msg, FIDO_MAXMSG); + + return r; +} + +static cbor_item_t * +largeblob_array_load(const uint8_t *ptr, size_t len) +{ + struct cbor_load_result cbor; + cbor_item_t *item; + + if (len < LARGEBLOB_DIGEST_LENGTH) { + fido_log_debug("%s: len", __func__); + return NULL; + } + len -= LARGEBLOB_DIGEST_LENGTH; + if ((item = cbor_load(ptr, len, &cbor)) == NULL) { + fido_log_debug("%s: cbor_load", __func__); + return NULL; + } + if (!cbor_isa_array(item) || !cbor_array_is_definite(item)) { + fido_log_debug("%s: cbor type", __func__); + cbor_decref(&item); + return NULL; + } + + return item; +} + +static size_t +get_chunklen(fido_dev_t *dev) +{ + uint64_t maxchunklen; + + if ((maxchunklen = fido_dev_maxmsgsize(dev)) > SIZE_MAX) + maxchunklen = SIZE_MAX; + if (maxchunklen > FIDO_MAXMSG) + maxchunklen = FIDO_MAXMSG; + maxchunklen = maxchunklen > 64 ? maxchunklen - 64 : 0; + + return (size_t)maxchunklen; +} + +static int +largeblob_do_decode(const cbor_item_t *key, const cbor_item_t *val, void *arg) +{ + largeblob_t *blob = arg; + uint64_t origsiz; + + if (cbor_isa_uint(key) == false || + cbor_int_get_width(key) != CBOR_INT_8) { + fido_log_debug("%s: cbor type", __func__); + return 0; /* ignore */ + } + + switch (cbor_get_uint8(key)) { + case 1: /* ciphertext */ + if (fido_blob_decode(val, &blob->ciphertext) < 0 || + blob->ciphertext.len < LARGEBLOB_TAG_LENGTH) + return -1; + return 0; + case 2: /* nonce */ + if (fido_blob_decode(val, &blob->nonce) < 0 || + blob->nonce.len != LARGEBLOB_NONCE_LENGTH) + return -1; + return 0; + case 3: /* origSize */ + if (!cbor_isa_uint(val) || + (origsiz = cbor_get_int(val)) > SIZE_MAX) + return -1; + blob->origsiz = (size_t)origsiz; + return 0; + default: /* ignore */ + fido_log_debug("%s: cbor type", __func__); + return 0; + } +} + +static int +largeblob_decode(largeblob_t *blob, const cbor_item_t *item) +{ + if (!cbor_isa_map(item) || !cbor_map_is_definite(item)) { + fido_log_debug("%s: cbor type", __func__); + return -1; + } + if (cbor_map_iter(item, blob, largeblob_do_decode) < 0) { + fido_log_debug("%s: cbor_map_iter", __func__); + return -1; + } + if (fido_blob_is_empty(&blob->ciphertext) || + fido_blob_is_empty(&blob->nonce) || blob->origsiz == 0) { + fido_log_debug("%s: incomplete blob", __func__); + return -1; + } + + return 0; +} + +static cbor_item_t * +largeblob_encode(const fido_blob_t *body, const fido_blob_t *key) +{ + largeblob_t *blob; + cbor_item_t *argv[3], *item = NULL; + + memset(argv, 0, sizeof(argv)); + if ((blob = largeblob_new()) == NULL || + largeblob_seal(blob, body, key) < 0) { + fido_log_debug("%s: largeblob_seal", __func__); + goto fail; + } + if ((argv[0] = fido_blob_encode(&blob->ciphertext)) == NULL || + (argv[1] = fido_blob_encode(&blob->nonce)) == NULL || + (argv[2] = cbor_build_uint(blob->origsiz)) == NULL) { + fido_log_debug("%s: cbor encode", __func__); + goto fail; + } + item = cbor_flatten_vector(argv, nitems(argv)); +fail: + cbor_vector_free(argv, nitems(argv)); + largeblob_free(&blob); + + return item; +} + +static int +largeblob_array_lookup(fido_blob_t *out, size_t *idx, const cbor_item_t *item, + const fido_blob_t *key) +{ + cbor_item_t **v; + fido_blob_t *plaintext = NULL; + largeblob_t blob; + int r; + + memset(&blob, 0, sizeof(blob)); + if (idx != NULL) + *idx = 0; + if ((v = cbor_array_handle(item)) == NULL) + return FIDO_ERR_INVALID_ARGUMENT; + for (size_t i = 0; i < cbor_array_size(item); i++) { + if (largeblob_decode(&blob, v[i]) < 0 || + (plaintext = largeblob_decrypt(&blob, key)) == NULL) { + fido_log_debug("%s: largeblob_decode", __func__); + largeblob_reset(&blob); + continue; + } + if (idx != NULL) + *idx = i; + break; + } + if (plaintext == NULL) { + fido_log_debug("%s: not found", __func__); + return FIDO_ERR_NOTFOUND; + } + if (out != NULL) + r = fido_uncompress(out, plaintext, blob.origsiz); + else + r = FIDO_OK; + + fido_blob_free(&plaintext); + largeblob_reset(&blob); + + return r; +} + +static int +largeblob_array_digest(u_char out[LARGEBLOB_DIGEST_LENGTH], const u_char *data, + size_t len) +{ + u_char dgst[SHA256_DIGEST_LENGTH]; + + if (data == NULL || len == 0) + return -1; + if (SHA256(data, len, dgst) != dgst) + return -1; + memcpy(out, dgst, LARGEBLOB_DIGEST_LENGTH); + + return 0; +} + +static int +largeblob_array_check(const fido_blob_t *array) +{ + u_char expected_hash[LARGEBLOB_DIGEST_LENGTH]; + size_t body_len; + + fido_log_xxd(array->ptr, array->len, __func__); + if (array->len < sizeof(expected_hash)) { + fido_log_debug("%s: len %zu", __func__, array->len); + return -1; + } + body_len = array->len - sizeof(expected_hash); + if (largeblob_array_digest(expected_hash, array->ptr, body_len) < 0) { + fido_log_debug("%s: largeblob_array_digest", __func__); + return -1; + } + + return timingsafe_bcmp(expected_hash, array->ptr + body_len, + sizeof(expected_hash)); +} + +static int +largeblob_get_array(fido_dev_t *dev, cbor_item_t **item, int *ms) +{ + fido_blob_t *array, *chunk = NULL; + size_t n; + int r; + + *item = NULL; + if ((n = get_chunklen(dev)) == 0) + return FIDO_ERR_INVALID_ARGUMENT; + if ((array = fido_blob_new()) == NULL) + return FIDO_ERR_INTERNAL; + do { + fido_blob_free(&chunk); + if ((r = largeblob_get_tx(dev, array->len, n, ms)) != FIDO_OK || + (r = largeblob_get_rx(dev, &chunk, ms)) != FIDO_OK) { + fido_log_debug("%s: largeblob_get_wait %zu/%zu", + __func__, array->len, n); + goto fail; + } + if (fido_blob_append(array, chunk->ptr, chunk->len) < 0) { + fido_log_debug("%s: fido_blob_append", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + } while (chunk->len == n); + + if (largeblob_array_check(array) != 0) + *item = cbor_new_definite_array(0); /* per spec */ + else + *item = largeblob_array_load(array->ptr, array->len); + if (*item == NULL) + r = FIDO_ERR_INTERNAL; + else + r = FIDO_OK; +fail: + fido_blob_free(&array); + fido_blob_free(&chunk); + + return r; +} + +static int +prepare_hmac(size_t offset, const u_char *data, size_t len, fido_blob_t *hmac) +{ + uint8_t buf[32 + 2 + sizeof(uint32_t) + SHA256_DIGEST_LENGTH]; + uint32_t u32_offset; + + if (data == NULL || len == 0) { + fido_log_debug("%s: invalid data=%p, len=%zu", __func__, + (const void *)data, len); + return -1; + } + if (offset > UINT32_MAX) { + fido_log_debug("%s: invalid offset=%zu", __func__, offset); + return -1; + } + + memset(buf, 0xff, 32); + buf[32] = CTAP_CBOR_LARGEBLOB; + buf[33] = 0x00; + u32_offset = htole32((uint32_t)offset); + memcpy(&buf[34], &u32_offset, sizeof(uint32_t)); + if (SHA256(data, len, &buf[38]) != &buf[38]) { + fido_log_debug("%s: SHA256", __func__); + return -1; + } + + return fido_blob_set(hmac, buf, sizeof(buf)); +} + +static int +largeblob_set_tx(fido_dev_t *dev, const fido_blob_t *token, const u_char *chunk, + size_t chunk_len, size_t offset, size_t totalsiz, int *ms) +{ + fido_blob_t *hmac = NULL, f; + cbor_item_t *argv[6]; + int r; + + memset(argv, 0, sizeof(argv)); + memset(&f, 0, sizeof(f)); + + if ((argv[1] = cbor_build_bytestring(chunk, chunk_len)) == NULL || + (argv[2] = cbor_build_uint(offset)) == NULL || + (offset == 0 && (argv[3] = cbor_build_uint(totalsiz)) == NULL)) { + fido_log_debug("%s: cbor encode", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + if (token != NULL) { + if ((hmac = fido_blob_new()) == NULL || + prepare_hmac(offset, chunk, chunk_len, hmac) < 0 || + (argv[4] = cbor_encode_pin_auth(dev, token, hmac)) == NULL || + (argv[5] = cbor_encode_pin_opt(dev)) == NULL) { + fido_log_debug("%s: cbor_encode_pin_auth", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + } + if (cbor_build_frame(CTAP_CBOR_LARGEBLOB, argv, nitems(argv), &f) < 0 || + fido_tx(dev, CTAP_CMD_CBOR, f.ptr, f.len, ms) < 0) { + fido_log_debug("%s: fido_tx", __func__); + r = FIDO_ERR_TX; + goto fail; + } + + r = FIDO_OK; +fail: + cbor_vector_free(argv, nitems(argv)); + fido_blob_free(&hmac); + free(f.ptr); + + return r; +} + +static int +largeblob_get_uv_token(fido_dev_t *dev, const char *pin, fido_blob_t **token, + int *ms) +{ + es256_pk_t *pk = NULL; + fido_blob_t *ecdh = NULL; + int r; + + if ((*token = fido_blob_new()) == NULL) + return FIDO_ERR_INTERNAL; + if ((r = fido_do_ecdh(dev, &pk, &ecdh, ms)) != FIDO_OK) { + fido_log_debug("%s: fido_do_ecdh", __func__); + goto fail; + } + if ((r = fido_dev_get_uv_token(dev, CTAP_CBOR_LARGEBLOB, pin, ecdh, pk, + NULL, *token, ms)) != FIDO_OK) { + fido_log_debug("%s: fido_dev_get_uv_token", __func__); + goto fail; + } + + r = FIDO_OK; +fail: + if (r != FIDO_OK) + fido_blob_free(token); + + fido_blob_free(&ecdh); + es256_pk_free(&pk); + + return r; +} + +static int +largeblob_set_array(fido_dev_t *dev, const cbor_item_t *item, const char *pin, + int *ms) +{ + unsigned char dgst[SHA256_DIGEST_LENGTH]; + fido_blob_t cbor, *token = NULL; + size_t chunklen, maxchunklen, totalsize; + int r; + + memset(&cbor, 0, sizeof(cbor)); + + if ((maxchunklen = get_chunklen(dev)) == 0) { + fido_log_debug("%s: maxchunklen=%zu", __func__, maxchunklen); + r = FIDO_ERR_INVALID_ARGUMENT; + goto fail; + } + if (!cbor_isa_array(item) || !cbor_array_is_definite(item)) { + fido_log_debug("%s: cbor type", __func__); + r = FIDO_ERR_INVALID_ARGUMENT; + goto fail; + } + if ((fido_blob_serialise(&cbor, item)) < 0) { + fido_log_debug("%s: fido_blob_serialise", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + if (cbor.len > SIZE_MAX - sizeof(dgst)) { + fido_log_debug("%s: cbor.len=%zu", __func__, cbor.len); + r = FIDO_ERR_INVALID_ARGUMENT; + goto fail; + } + if (SHA256(cbor.ptr, cbor.len, dgst) != dgst) { + fido_log_debug("%s: SHA256", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + totalsize = cbor.len + sizeof(dgst) - 16; /* the first 16 bytes only */ + if (pin != NULL || fido_dev_supports_permissions(dev)) { + if ((r = largeblob_get_uv_token(dev, pin, &token, + ms)) != FIDO_OK) { + fido_log_debug("%s: largeblob_get_uv_token", __func__); + goto fail; + } + } + for (size_t offset = 0; offset < cbor.len; offset += chunklen) { + if ((chunklen = cbor.len - offset) > maxchunklen) + chunklen = maxchunklen; + if ((r = largeblob_set_tx(dev, token, cbor.ptr + offset, + chunklen, offset, totalsize, ms)) != FIDO_OK || + (r = fido_rx_cbor_status(dev, ms)) != FIDO_OK) { + fido_log_debug("%s: body", __func__); + goto fail; + } + } + if ((r = largeblob_set_tx(dev, token, dgst, sizeof(dgst) - 16, cbor.len, + totalsize, ms)) != FIDO_OK || + (r = fido_rx_cbor_status(dev, ms)) != FIDO_OK) { + fido_log_debug("%s: dgst", __func__); + goto fail; + } + + r = FIDO_OK; +fail: + fido_blob_free(&token); + fido_blob_reset(&cbor); + + return r; +} + +static int +largeblob_add(fido_dev_t *dev, const fido_blob_t *key, cbor_item_t *item, + const char *pin, int *ms) +{ + cbor_item_t *array = NULL; + size_t idx; + int r; + + if ((r = largeblob_get_array(dev, &array, ms)) != FIDO_OK) { + fido_log_debug("%s: largeblob_get_array", __func__); + goto fail; + } + + switch (r = largeblob_array_lookup(NULL, &idx, array, key)) { + case FIDO_OK: + if (!cbor_array_replace(array, idx, item)) { + r = FIDO_ERR_INTERNAL; + goto fail; + } + break; + case FIDO_ERR_NOTFOUND: + if (cbor_array_append(&array, item) < 0) { + r = FIDO_ERR_INTERNAL; + goto fail; + } + break; + default: + fido_log_debug("%s: largeblob_array_lookup", __func__); + goto fail; + } + + if ((r = largeblob_set_array(dev, array, pin, ms)) != FIDO_OK) { + fido_log_debug("%s: largeblob_set_array", __func__); + goto fail; + } + + r = FIDO_OK; +fail: + if (array != NULL) + cbor_decref(&array); + + return r; +} + +static int +largeblob_drop(fido_dev_t *dev, const fido_blob_t *key, const char *pin, + int *ms) +{ + cbor_item_t *array = NULL; + size_t idx; + int r; + + if ((r = largeblob_get_array(dev, &array, ms)) != FIDO_OK) { + fido_log_debug("%s: largeblob_get_array", __func__); + goto fail; + } + if ((r = largeblob_array_lookup(NULL, &idx, array, key)) != FIDO_OK) { + fido_log_debug("%s: largeblob_array_lookup", __func__); + goto fail; + } + if (cbor_array_drop(&array, idx) < 0) { + fido_log_debug("%s: cbor_array_drop", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + if ((r = largeblob_set_array(dev, array, pin, ms)) != FIDO_OK) { + fido_log_debug("%s: largeblob_set_array", __func__); + goto fail; + } + + r = FIDO_OK; +fail: + if (array != NULL) + cbor_decref(&array); + + return r; +} + +int +fido_dev_largeblob_get(fido_dev_t *dev, const unsigned char *key_ptr, + size_t key_len, unsigned char **blob_ptr, size_t *blob_len) +{ + cbor_item_t *item = NULL; + fido_blob_t key, body; + int ms = dev->timeout_ms; + int r; + + memset(&key, 0, sizeof(key)); + memset(&body, 0, sizeof(body)); + + if (key_len != 32) { + fido_log_debug("%s: invalid key len %zu", __func__, key_len); + return FIDO_ERR_INVALID_ARGUMENT; + } + if (blob_ptr == NULL || blob_len == NULL) { + fido_log_debug("%s: invalid blob_ptr=%p, blob_len=%p", __func__, + (const void *)blob_ptr, (const void *)blob_len); + return FIDO_ERR_INVALID_ARGUMENT; + } + *blob_ptr = NULL; + *blob_len = 0; + if (fido_blob_set(&key, key_ptr, key_len) < 0) { + fido_log_debug("%s: fido_blob_set", __func__); + return FIDO_ERR_INTERNAL; + } + if ((r = largeblob_get_array(dev, &item, &ms)) != FIDO_OK) { + fido_log_debug("%s: largeblob_get_array", __func__); + goto fail; + } + if ((r = largeblob_array_lookup(&body, NULL, item, &key)) != FIDO_OK) + fido_log_debug("%s: largeblob_array_lookup", __func__); + else { + *blob_ptr = body.ptr; + *blob_len = body.len; + } +fail: + if (item != NULL) + cbor_decref(&item); + + fido_blob_reset(&key); + + return r; +} + +int +fido_dev_largeblob_set(fido_dev_t *dev, const unsigned char *key_ptr, + size_t key_len, const unsigned char *blob_ptr, size_t blob_len, + const char *pin) +{ + cbor_item_t *item = NULL; + fido_blob_t key, body; + int ms = dev->timeout_ms; + int r; + + memset(&key, 0, sizeof(key)); + memset(&body, 0, sizeof(body)); + + if (key_len != 32) { + fido_log_debug("%s: invalid key len %zu", __func__, key_len); + return FIDO_ERR_INVALID_ARGUMENT; + } + if (blob_ptr == NULL || blob_len == 0) { + fido_log_debug("%s: invalid blob_ptr=%p, blob_len=%zu", __func__, + (const void *)blob_ptr, blob_len); + return FIDO_ERR_INVALID_ARGUMENT; + } + if (fido_blob_set(&key, key_ptr, key_len) < 0 || + fido_blob_set(&body, blob_ptr, blob_len) < 0) { + fido_log_debug("%s: fido_blob_set", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + if ((item = largeblob_encode(&body, &key)) == NULL) { + fido_log_debug("%s: largeblob_encode", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + if ((r = largeblob_add(dev, &key, item, pin, &ms)) != FIDO_OK) + fido_log_debug("%s: largeblob_add", __func__); +fail: + if (item != NULL) + cbor_decref(&item); + + fido_blob_reset(&key); + fido_blob_reset(&body); + + return r; +} + +int +fido_dev_largeblob_remove(fido_dev_t *dev, const unsigned char *key_ptr, + size_t key_len, const char *pin) +{ + fido_blob_t key; + int ms = dev->timeout_ms; + int r; + + memset(&key, 0, sizeof(key)); + + if (key_len != 32) { + fido_log_debug("%s: invalid key len %zu", __func__, key_len); + return FIDO_ERR_INVALID_ARGUMENT; + } + if (fido_blob_set(&key, key_ptr, key_len) < 0) { + fido_log_debug("%s: fido_blob_set", __func__); + return FIDO_ERR_INTERNAL; + } + if ((r = largeblob_drop(dev, &key, pin, &ms)) != FIDO_OK) + fido_log_debug("%s: largeblob_drop", __func__); + + fido_blob_reset(&key); + + return r; +} + +int +fido_dev_largeblob_get_array(fido_dev_t *dev, unsigned char **cbor_ptr, + size_t *cbor_len) +{ + cbor_item_t *item = NULL; + fido_blob_t cbor; + int ms = dev->timeout_ms; + int r; + + memset(&cbor, 0, sizeof(cbor)); + + if (cbor_ptr == NULL || cbor_len == NULL) { + fido_log_debug("%s: invalid cbor_ptr=%p, cbor_len=%p", __func__, + (const void *)cbor_ptr, (const void *)cbor_len); + return FIDO_ERR_INVALID_ARGUMENT; + } + *cbor_ptr = NULL; + *cbor_len = 0; + if ((r = largeblob_get_array(dev, &item, &ms)) != FIDO_OK) { + fido_log_debug("%s: largeblob_get_array", __func__); + return r; + } + if (fido_blob_serialise(&cbor, item) < 0) { + fido_log_debug("%s: fido_blob_serialise", __func__); + r = FIDO_ERR_INTERNAL; + } else { + *cbor_ptr = cbor.ptr; + *cbor_len = cbor.len; + } + + cbor_decref(&item); + + return r; +} + +int +fido_dev_largeblob_set_array(fido_dev_t *dev, const unsigned char *cbor_ptr, + size_t cbor_len, const char *pin) +{ + cbor_item_t *item = NULL; + struct cbor_load_result cbor_result; + int ms = dev->timeout_ms; + int r; + + if (cbor_ptr == NULL || cbor_len == 0) { + fido_log_debug("%s: invalid cbor_ptr=%p, cbor_len=%zu", __func__, + (const void *)cbor_ptr, cbor_len); + return FIDO_ERR_INVALID_ARGUMENT; + } + if ((item = cbor_load(cbor_ptr, cbor_len, &cbor_result)) == NULL) { + fido_log_debug("%s: cbor_load", __func__); + return FIDO_ERR_INVALID_ARGUMENT; + } + if ((r = largeblob_set_array(dev, item, pin, &ms)) != FIDO_OK) + fido_log_debug("%s: largeblob_set_array", __func__); + + cbor_decref(&item); + + return r; +} diff --git a/src/libfido2.pc.in b/src/libfido2.pc.in new file mode 100644 index 0000000..03d0606 --- /dev/null +++ b/src/libfido2.pc.in @@ -0,0 +1,12 @@ +prefix=@CMAKE_INSTALL_PREFIX@ +exec_prefix=${prefix} +libdir=${prefix}/@CMAKE_INSTALL_LIBDIR@ +includedir=${prefix}/include + +Name: @PROJECT_NAME@ +Description: A FIDO2 library +URL: https://github.com/yubico/libfido2 +Version: @FIDO_VERSION@ +Requires: libcrypto +Libs: -L${libdir} -lfido2 +Cflags: -I${includedir} diff --git a/src/log.c b/src/log.c new file mode 100644 index 0000000..e54f8fc --- /dev/null +++ b/src/log.c @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2018-2021 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. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#undef _GNU_SOURCE /* XSI strerror_r() */ + +#include <stdarg.h> +#include <stdio.h> + +#include "fido.h" + +#ifndef FIDO_NO_DIAGNOSTIC + +#define XXDLEN 32 +#define XXDROW 128 +#define LINELEN 256 + +#ifndef TLS +#define TLS +#endif + +static TLS int logging; +static TLS fido_log_handler_t *log_handler; + +static void +log_on_stderr(const char *str) +{ + fprintf(stderr, "%s", str); +} + +static void +do_log(const char *suffix, const char *fmt, va_list args) +{ + char line[LINELEN], body[LINELEN]; + + vsnprintf(body, sizeof(body), fmt, args); + + if (suffix != NULL) + snprintf(line, sizeof(line), "%.180s: %.70s\n", body, suffix); + else + snprintf(line, sizeof(line), "%.180s\n", body); + + log_handler(line); +} + +void +fido_log_init(void) +{ + logging = 1; + log_handler = log_on_stderr; +} + +void +fido_log_debug(const char *fmt, ...) +{ + va_list args; + + if (!logging || log_handler == NULL) + return; + + va_start(args, fmt); + do_log(NULL, fmt, args); + va_end(args); +} + +void +fido_log_xxd(const void *buf, size_t count, const char *fmt, ...) +{ + const uint8_t *ptr = buf; + char row[XXDROW], xxd[XXDLEN]; + va_list args; + + if (!logging || log_handler == NULL) + return; + + snprintf(row, sizeof(row), "buf=%p, len=%zu", buf, count); + va_start(args, fmt); + do_log(row, fmt, args); + va_end(args); + *row = '\0'; + + for (size_t i = 0; i < count; i++) { + *xxd = '\0'; + if (i % 16 == 0) + snprintf(xxd, sizeof(xxd), "%04zu: %02x", i, *ptr++); + else + snprintf(xxd, sizeof(xxd), " %02x", *ptr++); + strlcat(row, xxd, sizeof(row)); + if (i % 16 == 15 || i == count - 1) { + fido_log_debug("%s", row); + *row = '\0'; + } + } +} + +void +fido_log_error(int errnum, const char *fmt, ...) +{ + char errstr[LINELEN]; + va_list args; + + if (!logging || log_handler == NULL) + return; + if (strerror_r(errnum, errstr, sizeof(errstr)) != 0) + snprintf(errstr, sizeof(errstr), "error %d", errnum); + + va_start(args, fmt); + do_log(errstr, fmt, args); + va_end(args); +} + +void +fido_set_log_handler(fido_log_handler_t *handler) +{ + if (handler != NULL) + log_handler = handler; +} + +#endif /* !FIDO_NO_DIAGNOSTIC */ diff --git a/src/netlink.c b/src/netlink.c new file mode 100644 index 0000000..2a9216c --- /dev/null +++ b/src/netlink.c @@ -0,0 +1,785 @@ +/* + * Copyright (c) 2020 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. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <sys/socket.h> + +#include <linux/genetlink.h> +#include <linux/netlink.h> +#include <linux/nfc.h> + +#include <errno.h> +#include <limits.h> + +#include "fido.h" +#include "netlink.h" + +#ifdef FIDO_FUZZ +static ssize_t (*fuzz_read)(int, void *, size_t); +static ssize_t (*fuzz_write)(int, const void *, size_t); +# define READ fuzz_read +# define WRITE fuzz_write +#else +# define READ read +# define WRITE write +#endif + +#ifndef SOL_NETLINK +#define SOL_NETLINK 270 +#endif + +#define NETLINK_POLL_MS 100 + +/* XXX avoid signed NLA_ALIGNTO */ +#undef NLA_HDRLEN +#define NLA_HDRLEN NLMSG_ALIGN(sizeof(struct nlattr)) + +typedef struct nlmsgbuf { + size_t siz; /* alloc size */ + size_t len; /* of payload */ + unsigned char *ptr; /* in payload */ + union { + struct nlmsghdr nlmsg; + char buf[NLMSG_HDRLEN]; /* align */ + } u; + unsigned char payload[]; +} nlmsgbuf_t; + +typedef struct genlmsgbuf { + union { + struct genlmsghdr genl; + char buf[GENL_HDRLEN]; /* align */ + } u; +} genlmsgbuf_t; + +typedef struct nlamsgbuf { + size_t siz; /* alloc size */ + size_t len; /* of payload */ + unsigned char *ptr; /* in payload */ + union { + struct nlattr nla; + char buf[NLA_HDRLEN]; /* align */ + } u; + unsigned char payload[]; +} nlamsgbuf_t; + +typedef struct nl_family { + uint16_t id; + uint32_t mcastgrp; +} nl_family_t; + +typedef struct nl_poll { + uint32_t dev; + unsigned int eventcnt; +} nl_poll_t; + +typedef struct nl_target { + int found; + uint32_t *value; +} nl_target_t; + +static const void * +nlmsg_ptr(const nlmsgbuf_t *m) +{ + return (&m->u.nlmsg); +} + +static size_t +nlmsg_len(const nlmsgbuf_t *m) +{ + return (m->u.nlmsg.nlmsg_len); +} + +static uint16_t +nlmsg_type(const nlmsgbuf_t *m) +{ + return (m->u.nlmsg.nlmsg_type); +} + +static nlmsgbuf_t * +nlmsg_new(uint16_t type, uint16_t flags, size_t len) +{ + nlmsgbuf_t *m; + size_t siz; + + if (len > SIZE_MAX - sizeof(*m) || + (siz = sizeof(*m) + len) > UINT16_MAX || + (m = calloc(1, siz)) == NULL) + return (NULL); + + m->siz = siz; + m->len = len; + m->ptr = m->payload; + m->u.nlmsg.nlmsg_type = type; + m->u.nlmsg.nlmsg_flags = NLM_F_REQUEST | flags; + m->u.nlmsg.nlmsg_len = NLMSG_HDRLEN; + + return (m); +} + +static nlamsgbuf_t * +nla_from_buf(const unsigned char **ptr, size_t *len) +{ + nlamsgbuf_t h, *a; + size_t nlalen, skip; + + if (*len < sizeof(h.u)) + return (NULL); + + memset(&h, 0, sizeof(h)); + memcpy(&h.u, *ptr, sizeof(h.u)); + + if ((nlalen = h.u.nla.nla_len) < sizeof(h.u) || nlalen > *len || + nlalen - sizeof(h.u) > UINT16_MAX || + nlalen > SIZE_MAX - sizeof(*a) || + (skip = NLMSG_ALIGN(nlalen)) > *len || + (a = calloc(1, sizeof(*a) + nlalen - sizeof(h.u))) == NULL) + return (NULL); + + memcpy(&a->u, *ptr, nlalen); + a->siz = sizeof(*a) + nlalen - sizeof(h.u); + a->ptr = a->payload; + a->len = nlalen - sizeof(h.u); + *ptr += skip; + *len -= skip; + + return (a); +} + +static nlamsgbuf_t * +nla_getattr(nlamsgbuf_t *a) +{ + return (nla_from_buf((void *)&a->ptr, &a->len)); +} + +static uint16_t +nla_type(const nlamsgbuf_t *a) +{ + return (a->u.nla.nla_type); +} + +static nlamsgbuf_t * +nlmsg_getattr(nlmsgbuf_t *m) +{ + return (nla_from_buf((void *)&m->ptr, &m->len)); +} + +static int +nla_read(nlamsgbuf_t *a, void *buf, size_t cnt) +{ + if (cnt > a->u.nla.nla_len || + fido_buf_read((void *)&a->ptr, &a->len, buf, cnt) < 0) + return (-1); + + a->u.nla.nla_len = (uint16_t)(a->u.nla.nla_len - cnt); + + return (0); +} + +static nlmsgbuf_t * +nlmsg_from_buf(const unsigned char **ptr, size_t *len) +{ + nlmsgbuf_t h, *m; + size_t msglen, skip; + + if (*len < sizeof(h.u)) + return (NULL); + + memset(&h, 0, sizeof(h)); + memcpy(&h.u, *ptr, sizeof(h.u)); + + if ((msglen = h.u.nlmsg.nlmsg_len) < sizeof(h.u) || msglen > *len || + msglen - sizeof(h.u) > UINT16_MAX || + (skip = NLMSG_ALIGN(msglen)) > *len || + (m = nlmsg_new(0, 0, msglen - sizeof(h.u))) == NULL) + return (NULL); + + memcpy(&m->u, *ptr, msglen); + *ptr += skip; + *len -= skip; + + return (m); +} + +static int +nlmsg_read(nlmsgbuf_t *m, void *buf, size_t cnt) +{ + if (cnt > m->u.nlmsg.nlmsg_len || + fido_buf_read((void *)&m->ptr, &m->len, buf, cnt) < 0) + return (-1); + + m->u.nlmsg.nlmsg_len = (uint32_t)(m->u.nlmsg.nlmsg_len - cnt); + + return (0); +} + +static int +nlmsg_write(nlmsgbuf_t *m, const void *buf, size_t cnt) +{ + if (cnt > UINT32_MAX - m->u.nlmsg.nlmsg_len || + fido_buf_write(&m->ptr, &m->len, buf, cnt) < 0) + return (-1); + + m->u.nlmsg.nlmsg_len = (uint32_t)(m->u.nlmsg.nlmsg_len + cnt); + + return (0); +} + +static int +nlmsg_set_genl(nlmsgbuf_t *m, uint8_t cmd) +{ + genlmsgbuf_t g; + + memset(&g, 0, sizeof(g)); + g.u.genl.cmd = cmd; + g.u.genl.version = NFC_GENL_VERSION; + + return (nlmsg_write(m, &g, sizeof(g))); +} + +static int +nlmsg_get_genl(nlmsgbuf_t *m, uint8_t cmd) +{ + genlmsgbuf_t g; + + memset(&g, 0, sizeof(g)); + + if (nlmsg_read(m, &g, sizeof(g)) < 0 || g.u.genl.cmd != cmd) + return (-1); + + return (0); +} + +static int +nlmsg_get_status(nlmsgbuf_t *m) +{ + int status; + + if (nlmsg_read(m, &status, sizeof(status)) < 0 || status == INT_MIN) + return (-1); + if (status < 0) + status = -status; + + return (status); +} + +static int +nlmsg_setattr(nlmsgbuf_t *m, uint16_t type, const void *ptr, size_t len) +{ + int r; + char *padding; + size_t skip; + nlamsgbuf_t a; + + if ((skip = NLMSG_ALIGN(len)) > UINT16_MAX - sizeof(a.u) || + skip < len || (padding = calloc(1, skip - len)) == NULL) + return (-1); + + memset(&a, 0, sizeof(a)); + a.u.nla.nla_type = type; + a.u.nla.nla_len = (uint16_t)(len + sizeof(a.u)); + r = nlmsg_write(m, &a.u, sizeof(a.u)) < 0 || + nlmsg_write(m, ptr, len) < 0 || + nlmsg_write(m, padding, skip - len) < 0 ? -1 : 0; + + free(padding); + + return (r); +} + +static int +nlmsg_set_u16(nlmsgbuf_t *m, uint16_t type, uint16_t val) +{ + return (nlmsg_setattr(m, type, &val, sizeof(val))); +} + +static int +nlmsg_set_u32(nlmsgbuf_t *m, uint16_t type, uint32_t val) +{ + return (nlmsg_setattr(m, type, &val, sizeof(val))); +} + +static int +nlmsg_set_str(nlmsgbuf_t *m, uint16_t type, const char *val) +{ + return (nlmsg_setattr(m, type, val, strlen(val) + 1)); +} + +static int +nla_get_u16(nlamsgbuf_t *a, uint16_t *v) +{ + return (nla_read(a, v, sizeof(*v))); +} + +static int +nla_get_u32(nlamsgbuf_t *a, uint32_t *v) +{ + return (nla_read(a, v, sizeof(*v))); +} + +static char * +nla_get_str(nlamsgbuf_t *a) +{ + size_t n; + char *s = NULL; + + if ((n = a->len) < 1 || a->ptr[n - 1] != '\0' || + (s = calloc(1, n)) == NULL || nla_read(a, s, n) < 0) { + free(s); + return (NULL); + } + s[n - 1] = '\0'; + + return (s); +} + +static int +nlmsg_tx(int fd, const nlmsgbuf_t *m) +{ + ssize_t r; + + if ((r = WRITE(fd, nlmsg_ptr(m), nlmsg_len(m))) == -1) { + fido_log_error(errno, "%s: write", __func__); + return (-1); + } + if (r < 0 || (size_t)r != nlmsg_len(m)) { + fido_log_debug("%s: %zd != %zu", __func__, r, nlmsg_len(m)); + return (-1); + } + fido_log_xxd(nlmsg_ptr(m), nlmsg_len(m), "%s", __func__); + + return (0); +} + +static ssize_t +nlmsg_rx(int fd, unsigned char *ptr, size_t len, int ms) +{ + ssize_t r; + + if (len > SSIZE_MAX) { + fido_log_debug("%s: len", __func__); + return (-1); + } + if (fido_hid_unix_wait(fd, ms, NULL) < 0) { + fido_log_debug("%s: fido_hid_unix_wait", __func__); + return (-1); + } + if ((r = READ(fd, ptr, len)) == -1) { + fido_log_error(errno, "%s: read %zd", __func__, r); + return (-1); + } + fido_log_xxd(ptr, (size_t)r, "%s", __func__); + + return (r); +} + +static int +nlmsg_iter(nlmsgbuf_t *m, void *arg, int (*parser)(nlamsgbuf_t *, void *)) +{ + nlamsgbuf_t *a; + int r; + + while ((a = nlmsg_getattr(m)) != NULL) { + r = parser(a, arg); + free(a); + if (r < 0) { + fido_log_debug("%s: parser", __func__); + return (-1); + } + } + + return (0); +} + +static int +nla_iter(nlamsgbuf_t *g, void *arg, int (*parser)(nlamsgbuf_t *, void *)) +{ + nlamsgbuf_t *a; + int r; + + while ((a = nla_getattr(g)) != NULL) { + r = parser(a, arg); + free(a); + if (r < 0) { + fido_log_debug("%s: parser", __func__); + return (-1); + } + } + + return (0); +} + +static int +nl_parse_reply(const uint8_t *blob, size_t blob_len, uint16_t msg_type, + uint8_t genl_cmd, void *arg, int (*parser)(nlamsgbuf_t *, void *)) +{ + nlmsgbuf_t *m; + int r; + + while (blob_len) { + if ((m = nlmsg_from_buf(&blob, &blob_len)) == NULL) { + fido_log_debug("%s: nlmsg", __func__); + return (-1); + } + if (nlmsg_type(m) == NLMSG_ERROR) { + r = nlmsg_get_status(m); + free(m); + return (r); + } + if (nlmsg_type(m) != msg_type || + nlmsg_get_genl(m, genl_cmd) < 0) { + fido_log_debug("%s: skipping", __func__); + free(m); + continue; + } + if (parser != NULL && nlmsg_iter(m, arg, parser) < 0) { + fido_log_debug("%s: nlmsg_iter", __func__); + free(m); + return (-1); + } + free(m); + } + + return (0); +} + +static int +parse_mcastgrp(nlamsgbuf_t *a, void *arg) +{ + nl_family_t *family = arg; + char *name; + + switch (nla_type(a)) { + case CTRL_ATTR_MCAST_GRP_NAME: + if ((name = nla_get_str(a)) == NULL || + strcmp(name, NFC_GENL_MCAST_EVENT_NAME) != 0) { + free(name); + return (-1); /* XXX skip? */ + } + free(name); + return (0); + case CTRL_ATTR_MCAST_GRP_ID: + if (family->mcastgrp) + break; + if (nla_get_u32(a, &family->mcastgrp) < 0) { + fido_log_debug("%s: group", __func__); + return (-1); + } + return (0); + } + + fido_log_debug("%s: ignoring nla 0x%x", __func__, nla_type(a)); + + return (0); +} + +static int +parse_mcastgrps(nlamsgbuf_t *a, void *arg) +{ + return (nla_iter(a, arg, parse_mcastgrp)); +} + +static int +parse_family(nlamsgbuf_t *a, void *arg) +{ + nl_family_t *family = arg; + + switch (nla_type(a)) { + case CTRL_ATTR_FAMILY_ID: + if (family->id) + break; + if (nla_get_u16(a, &family->id) < 0) { + fido_log_debug("%s: id", __func__); + return (-1); + } + return (0); + case CTRL_ATTR_MCAST_GROUPS: + return (nla_iter(a, family, parse_mcastgrps)); + } + + fido_log_debug("%s: ignoring nla 0x%x", __func__, nla_type(a)); + + return (0); +} + +static int +nl_get_nfc_family(int fd, uint16_t *type, uint32_t *mcastgrp) +{ + nlmsgbuf_t *m; + uint8_t reply[512]; + nl_family_t family; + ssize_t r; + int ok; + + if ((m = nlmsg_new(GENL_ID_CTRL, 0, 64)) == NULL || + nlmsg_set_genl(m, CTRL_CMD_GETFAMILY) < 0 || + nlmsg_set_u16(m, CTRL_ATTR_FAMILY_ID, GENL_ID_CTRL) < 0 || + nlmsg_set_str(m, CTRL_ATTR_FAMILY_NAME, NFC_GENL_NAME) < 0 || + nlmsg_tx(fd, m) < 0) { + free(m); + return (-1); + } + free(m); + memset(&family, 0, sizeof(family)); + if ((r = nlmsg_rx(fd, reply, sizeof(reply), -1)) < 0) { + fido_log_debug("%s: nlmsg_rx", __func__); + return (-1); + } + if ((ok = nl_parse_reply(reply, (size_t)r, GENL_ID_CTRL, + CTRL_CMD_NEWFAMILY, &family, parse_family)) != 0) { + fido_log_debug("%s: nl_parse_reply: %d", __func__, ok); + return (-1); + } + if (family.id == 0 || family.mcastgrp == 0) { + fido_log_debug("%s: missing attr", __func__); + return (-1); + } + *type = family.id; + *mcastgrp = family.mcastgrp; + + return (0); +} + +static int +parse_target(nlamsgbuf_t *a, void *arg) +{ + nl_target_t *t = arg; + + if (t->found || nla_type(a) != NFC_ATTR_TARGET_INDEX) { + fido_log_debug("%s: ignoring nla 0x%x", __func__, nla_type(a)); + return (0); + } + if (nla_get_u32(a, t->value) < 0) { + fido_log_debug("%s: target", __func__); + return (-1); + } + t->found = 1; + + return (0); +} + +int +fido_nl_power_nfc(fido_nl_t *nl, uint32_t dev) +{ + nlmsgbuf_t *m; + uint8_t reply[512]; + ssize_t r; + int ok; + + if ((m = nlmsg_new(nl->nfc_type, NLM_F_ACK, 64)) == NULL || + nlmsg_set_genl(m, NFC_CMD_DEV_UP) < 0 || + nlmsg_set_u32(m, NFC_ATTR_DEVICE_INDEX, dev) < 0 || + nlmsg_tx(nl->fd, m) < 0) { + free(m); + return (-1); + } + free(m); + if ((r = nlmsg_rx(nl->fd, reply, sizeof(reply), -1)) < 0) { + fido_log_debug("%s: nlmsg_rx", __func__); + return (-1); + } + if ((ok = nl_parse_reply(reply, (size_t)r, nl->nfc_type, + NFC_CMD_DEV_UP, NULL, NULL)) != 0 && ok != EALREADY) { + fido_log_debug("%s: nl_parse_reply: %d", __func__, ok); + return (-1); + } + + return (0); +} + +static int +nl_nfc_poll(fido_nl_t *nl, uint32_t dev) +{ + nlmsgbuf_t *m; + uint8_t reply[512]; + ssize_t r; + int ok; + + if ((m = nlmsg_new(nl->nfc_type, NLM_F_ACK, 64)) == NULL || + nlmsg_set_genl(m, NFC_CMD_START_POLL) < 0 || + nlmsg_set_u32(m, NFC_ATTR_DEVICE_INDEX, dev) < 0 || + nlmsg_set_u32(m, NFC_ATTR_PROTOCOLS, NFC_PROTO_ISO14443_MASK) < 0 || + nlmsg_tx(nl->fd, m) < 0) { + free(m); + return (-1); + } + free(m); + if ((r = nlmsg_rx(nl->fd, reply, sizeof(reply), -1)) < 0) { + fido_log_debug("%s: nlmsg_rx", __func__); + return (-1); + } + if ((ok = nl_parse_reply(reply, (size_t)r, nl->nfc_type, + NFC_CMD_START_POLL, NULL, NULL)) != 0) { + fido_log_debug("%s: nl_parse_reply: %d", __func__, ok); + return (-1); + } + + return (0); +} + +static int +nl_dump_nfc_target(fido_nl_t *nl, uint32_t dev, uint32_t *target, int ms) +{ + nlmsgbuf_t *m; + nl_target_t t; + uint8_t reply[512]; + ssize_t r; + int ok; + + if ((m = nlmsg_new(nl->nfc_type, NLM_F_DUMP, 64)) == NULL || + nlmsg_set_genl(m, NFC_CMD_GET_TARGET) < 0 || + nlmsg_set_u32(m, NFC_ATTR_DEVICE_INDEX, dev) < 0 || + nlmsg_tx(nl->fd, m) < 0) { + free(m); + return (-1); + } + free(m); + if ((r = nlmsg_rx(nl->fd, reply, sizeof(reply), ms)) < 0) { + fido_log_debug("%s: nlmsg_rx", __func__); + return (-1); + } + memset(&t, 0, sizeof(t)); + t.value = target; + if ((ok = nl_parse_reply(reply, (size_t)r, nl->nfc_type, + NFC_CMD_GET_TARGET, &t, parse_target)) != 0) { + fido_log_debug("%s: nl_parse_reply: %d", __func__, ok); + return (-1); + } + if (!t.found) { + fido_log_debug("%s: target not found", __func__); + return (-1); + } + + return (0); +} + +static int +parse_nfc_event(nlamsgbuf_t *a, void *arg) +{ + nl_poll_t *ctx = arg; + uint32_t dev; + + if (nla_type(a) != NFC_ATTR_DEVICE_INDEX) { + fido_log_debug("%s: ignoring nla 0x%x", __func__, nla_type(a)); + return (0); + } + if (nla_get_u32(a, &dev) < 0) { + fido_log_debug("%s: dev", __func__); + return (-1); + } + if (dev == ctx->dev) + ctx->eventcnt++; + else + fido_log_debug("%s: ignoring dev 0x%x", __func__, dev); + + return (0); +} + +int +fido_nl_get_nfc_target(fido_nl_t *nl, uint32_t dev, uint32_t *target) +{ + uint8_t reply[512]; + nl_poll_t ctx; + ssize_t r; + int ok; + + if (nl_nfc_poll(nl, dev) < 0) { + fido_log_debug("%s: nl_nfc_poll", __func__); + return (-1); + } +#ifndef FIDO_FUZZ + if (setsockopt(nl->fd, SOL_NETLINK, NETLINK_ADD_MEMBERSHIP, + &nl->nfc_mcastgrp, sizeof(nl->nfc_mcastgrp)) == -1) { + fido_log_error(errno, "%s: setsockopt add", __func__); + return (-1); + } +#endif + r = nlmsg_rx(nl->fd, reply, sizeof(reply), NETLINK_POLL_MS); +#ifndef FIDO_FUZZ + if (setsockopt(nl->fd, SOL_NETLINK, NETLINK_DROP_MEMBERSHIP, + &nl->nfc_mcastgrp, sizeof(nl->nfc_mcastgrp)) == -1) { + fido_log_error(errno, "%s: setsockopt drop", __func__); + return (-1); + } +#endif + if (r < 0) { + fido_log_debug("%s: nlmsg_rx", __func__); + return (-1); + } + memset(&ctx, 0, sizeof(ctx)); + ctx.dev = dev; + if ((ok = nl_parse_reply(reply, (size_t)r, nl->nfc_type, + NFC_EVENT_TARGETS_FOUND, &ctx, parse_nfc_event)) != 0) { + fido_log_debug("%s: nl_parse_reply: %d", __func__, ok); + return (-1); + } + if (ctx.eventcnt == 0) { + fido_log_debug("%s: dev 0x%x not observed", __func__, dev); + return (-1); + } + if (nl_dump_nfc_target(nl, dev, target, -1) < 0) { + fido_log_debug("%s: nl_dump_nfc_target", __func__); + return (-1); + } + + return (0); +} + +void +fido_nl_free(fido_nl_t **nlp) +{ + fido_nl_t *nl; + + if (nlp == NULL || (nl = *nlp) == NULL) + return; + if (nl->fd != -1 && close(nl->fd) == -1) + fido_log_error(errno, "%s: close", __func__); + + free(nl); + *nlp = NULL; +} + +fido_nl_t * +fido_nl_new(void) +{ + fido_nl_t *nl; + int ok = -1; + + if ((nl = calloc(1, sizeof(*nl))) == NULL) + return (NULL); + if ((nl->fd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, + NETLINK_GENERIC)) == -1) { + fido_log_error(errno, "%s: socket", __func__); + goto fail; + } + nl->saddr.nl_family = AF_NETLINK; + if (bind(nl->fd, (struct sockaddr *)&nl->saddr, + sizeof(nl->saddr)) == -1) { + fido_log_error(errno, "%s: bind", __func__); + goto fail; + } + if (nl_get_nfc_family(nl->fd, &nl->nfc_type, &nl->nfc_mcastgrp) < 0) { + fido_log_debug("%s: nl_get_nfc_family", __func__); + goto fail; + } + + ok = 0; +fail: + if (ok < 0) + fido_nl_free(&nl); + + return (nl); +} + +#ifdef FIDO_FUZZ +void +set_netlink_io_functions(ssize_t (*read_f)(int, void *, size_t), + ssize_t (*write_f)(int, const void *, size_t)) +{ + fuzz_read = read_f; + fuzz_write = write_f; +} +#endif diff --git a/src/netlink.h b/src/netlink.h new file mode 100644 index 0000000..c600b52 --- /dev/null +++ b/src/netlink.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2020 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. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#ifndef _FIDO_NETLINK_H +#define _FIDO_NETLINK_H + +#include <sys/socket.h> + +#include <linux/genetlink.h> +#include <linux/netlink.h> +#include <linux/nfc.h> + +#include <stdlib.h> +#include <stdint.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +typedef struct fido_nl { + int fd; + uint16_t nfc_type; + uint32_t nfc_mcastgrp; + struct sockaddr_nl saddr; +} fido_nl_t; + +fido_nl_t *fido_nl_new(void); +void fido_nl_free(struct fido_nl **); +int fido_nl_power_nfc(struct fido_nl *, uint32_t); +int fido_nl_get_nfc_target(struct fido_nl *, uint32_t , uint32_t *); + +#ifdef FIDO_FUZZ +void set_netlink_io_functions(ssize_t (*)(int, void *, size_t), + ssize_t (*)(int, const void *, size_t)); +#endif + +#ifdef __cplusplus +} /* extern "C" */ +#endif /* __cplusplus */ + +#endif /* !_FIDO_NETLINK_H */ diff --git a/src/nfc.c b/src/nfc.c new file mode 100644 index 0000000..2e97d5f --- /dev/null +++ b/src/nfc.c @@ -0,0 +1,350 @@ +/* + * Copyright (c) 2020-2022 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. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <stdio.h> +#include <string.h> + +#include "fido.h" +#include "fido/param.h" +#include "iso7816.h" + +#define TX_CHUNK_SIZE 240 + +static const uint8_t aid[] = { 0xa0, 0x00, 0x00, 0x06, 0x47, 0x2f, 0x00, 0x01 }; +static const uint8_t v_u2f[] = { 'U', '2', 'F', '_', 'V', '2' }; +static const uint8_t v_fido[] = { 'F', 'I', 'D', 'O', '_', '2', '_', '0' }; + +static int +tx_short_apdu(fido_dev_t *d, const iso7816_header_t *h, const uint8_t *payload, + uint8_t payload_len, uint8_t cla_flags) +{ + uint8_t apdu[5 + UINT8_MAX + 1]; + uint8_t sw[2]; + size_t apdu_len; + int ok = -1; + + memset(&apdu, 0, sizeof(apdu)); + apdu[0] = h->cla | cla_flags; + apdu[1] = h->ins; + apdu[2] = h->p1; + apdu[3] = h->p2; + apdu[4] = payload_len; + memcpy(&apdu[5], payload, payload_len); + apdu_len = (size_t)(5 + payload_len + 1); + + if (d->io.write(d->io_handle, apdu, apdu_len) < 0) { + fido_log_debug("%s: write", __func__); + goto fail; + } + + if (cla_flags & 0x10) { + if (d->io.read(d->io_handle, sw, sizeof(sw), -1) != 2) { + fido_log_debug("%s: read", __func__); + goto fail; + } + if ((sw[0] << 8 | sw[1]) != SW_NO_ERROR) { + fido_log_debug("%s: unexpected sw", __func__); + goto fail; + } + } + + ok = 0; +fail: + explicit_bzero(apdu, sizeof(apdu)); + + return ok; +} + +static int +nfc_do_tx(fido_dev_t *d, const uint8_t *apdu_ptr, size_t apdu_len) +{ + iso7816_header_t h; + + if (fido_buf_read(&apdu_ptr, &apdu_len, &h, sizeof(h)) < 0) { + fido_log_debug("%s: header", __func__); + return -1; + } + if (apdu_len < 2) { + fido_log_debug("%s: apdu_len %zu", __func__, apdu_len); + return -1; + } + + apdu_len -= 2; /* trim le1 le2 */ + + while (apdu_len > TX_CHUNK_SIZE) { + if (tx_short_apdu(d, &h, apdu_ptr, TX_CHUNK_SIZE, 0x10) < 0) { + fido_log_debug("%s: chain", __func__); + return -1; + } + apdu_ptr += TX_CHUNK_SIZE; + apdu_len -= TX_CHUNK_SIZE; + } + + if (tx_short_apdu(d, &h, apdu_ptr, (uint8_t)apdu_len, 0) < 0) { + fido_log_debug("%s: tx_short_apdu", __func__); + return -1; + } + + return 0; +} + +int +fido_nfc_tx(fido_dev_t *d, uint8_t cmd, const unsigned char *buf, size_t count) +{ + iso7816_apdu_t *apdu = NULL; + const uint8_t *ptr; + size_t len; + int ok = -1; + + switch (cmd) { + case CTAP_CMD_INIT: /* select */ + if ((apdu = iso7816_new(0, 0xa4, 0x04, sizeof(aid))) == NULL || + iso7816_add(apdu, aid, sizeof(aid)) < 0) { + fido_log_debug("%s: iso7816", __func__); + goto fail; + } + break; + case CTAP_CMD_CBOR: /* wrap cbor */ + if (count > UINT16_MAX || (apdu = iso7816_new(0x80, 0x10, 0x00, + (uint16_t)count)) == NULL || + iso7816_add(apdu, buf, count) < 0) { + fido_log_debug("%s: iso7816", __func__); + goto fail; + } + break; + case CTAP_CMD_MSG: /* already an apdu */ + break; + default: + fido_log_debug("%s: cmd=%02x", __func__, cmd); + goto fail; + } + + if (apdu != NULL) { + ptr = iso7816_ptr(apdu); + len = iso7816_len(apdu); + } else { + ptr = buf; + len = count; + } + + if (nfc_do_tx(d, ptr, len) < 0) { + fido_log_debug("%s: nfc_do_tx", __func__); + goto fail; + } + + ok = 0; +fail: + iso7816_free(&apdu); + + return ok; +} + +static int +rx_init(fido_dev_t *d, unsigned char *buf, size_t count, int ms) +{ + fido_ctap_info_t *attr = (fido_ctap_info_t *)buf; + uint8_t f[64]; + int n; + + if (count != sizeof(*attr)) { + fido_log_debug("%s: count=%zu", __func__, count); + return -1; + } + + memset(attr, 0, sizeof(*attr)); + + if ((n = d->io.read(d->io_handle, f, sizeof(f), ms)) < 2 || + (f[n - 2] << 8 | f[n - 1]) != SW_NO_ERROR) { + fido_log_debug("%s: read", __func__); + return -1; + } + + n -= 2; + + if (n == sizeof(v_u2f) && memcmp(f, v_u2f, sizeof(v_u2f)) == 0) + attr->flags = FIDO_CAP_CBOR; + else if (n == sizeof(v_fido) && memcmp(f, v_fido, sizeof(v_fido)) == 0) + attr->flags = FIDO_CAP_CBOR | FIDO_CAP_NMSG; + else { + fido_log_debug("%s: unknown version string", __func__); +#ifdef FIDO_FUZZ + attr->flags = FIDO_CAP_CBOR | FIDO_CAP_NMSG; +#else + return -1; +#endif + } + + memcpy(&attr->nonce, &d->nonce, sizeof(attr->nonce)); /* XXX */ + + return (int)count; +} + +static int +tx_get_response(fido_dev_t *d, uint8_t count) +{ + uint8_t apdu[5]; + + memset(apdu, 0, sizeof(apdu)); + apdu[1] = 0xc0; /* GET_RESPONSE */ + apdu[4] = count; + + if (d->io.write(d->io_handle, apdu, sizeof(apdu)) < 0) { + fido_log_debug("%s: write", __func__); + return -1; + } + + return 0; +} + +static int +rx_apdu(fido_dev_t *d, uint8_t sw[2], unsigned char **buf, size_t *count, int *ms) +{ + uint8_t f[256 + 2]; + struct timespec ts; + int n, ok = -1; + + if (fido_time_now(&ts) != 0) + goto fail; + + if ((n = d->io.read(d->io_handle, f, sizeof(f), *ms)) < 2) { + fido_log_debug("%s: read", __func__); + goto fail; + } + + if (fido_time_delta(&ts, ms) != 0) + goto fail; + + if (fido_buf_write(buf, count, f, (size_t)(n - 2)) < 0) { + fido_log_debug("%s: fido_buf_write", __func__); + goto fail; + } + + memcpy(sw, f + n - 2, 2); + + ok = 0; +fail: + explicit_bzero(f, sizeof(f)); + + return ok; +} + +static int +rx_msg(fido_dev_t *d, unsigned char *buf, size_t count, int ms) +{ + uint8_t sw[2]; + const size_t bufsiz = count; + + if (rx_apdu(d, sw, &buf, &count, &ms) < 0) { + fido_log_debug("%s: preamble", __func__); + return -1; + } + + while (sw[0] == SW1_MORE_DATA) + if (tx_get_response(d, sw[1]) < 0 || + rx_apdu(d, sw, &buf, &count, &ms) < 0) { + fido_log_debug("%s: chain", __func__); + return -1; + } + + if (fido_buf_write(&buf, &count, sw, sizeof(sw)) < 0) { + fido_log_debug("%s: sw", __func__); + return -1; + } + + if (bufsiz - count > INT_MAX) { + fido_log_debug("%s: bufsiz", __func__); + return -1; + } + + return (int)(bufsiz - count); +} + +static int +rx_cbor(fido_dev_t *d, unsigned char *buf, size_t count, int ms) +{ + int r; + + if ((r = rx_msg(d, buf, count, ms)) < 2) + return -1; + + return r - 2; +} + +int +fido_nfc_rx(fido_dev_t *d, uint8_t cmd, unsigned char *buf, size_t count, int ms) +{ + switch (cmd) { + case CTAP_CMD_INIT: + return rx_init(d, buf, count, ms); + case CTAP_CMD_CBOR: + return rx_cbor(d, buf, count, ms); + case CTAP_CMD_MSG: + return rx_msg(d, buf, count, ms); + default: + fido_log_debug("%s: cmd=%02x", __func__, cmd); + return -1; + } +} + +bool +nfc_is_fido(const char *path) +{ + bool fido = false; + fido_dev_t *d; + int r; + + if ((d = fido_dev_new()) == NULL) { + fido_log_debug("%s: fido_dev_new", __func__); + goto fail; + } + /* fido_dev_open selects the fido applet */ + if ((r = fido_dev_open(d, path)) != FIDO_OK) { + fido_log_debug("%s: fido_dev_open: 0x%x", __func__, r); + goto fail; + } + if ((r = fido_dev_close(d)) != FIDO_OK) { + fido_log_debug("%s: fido_dev_close: 0x%x", __func__, r); + goto fail; + + } + + fido = true; +fail: + fido_dev_free(&d); + + return fido; +} + +#ifdef USE_NFC +bool +fido_is_nfc(const char *path) +{ + return strncmp(path, FIDO_NFC_PREFIX, strlen(FIDO_NFC_PREFIX)) == 0; +} + +int +fido_dev_set_nfc(fido_dev_t *d) +{ + if (d->io_handle != NULL) { + fido_log_debug("%s: device open", __func__); + return -1; + } + d->io_own = true; + d->io = (fido_dev_io_t) { + fido_nfc_open, + fido_nfc_close, + fido_nfc_read, + fido_nfc_write, + }; + d->transport = (fido_dev_transport_t) { + fido_nfc_rx, + fido_nfc_tx, + }; + + return 0; +} +#endif /* USE_NFC */ diff --git a/src/nfc_linux.c b/src/nfc_linux.c new file mode 100644 index 0000000..4b69eb1 --- /dev/null +++ b/src/nfc_linux.c @@ -0,0 +1,356 @@ +/* + * Copyright (c) 2020-2022 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. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <sys/types.h> +#include <sys/uio.h> +#include <sys/socket.h> + +#include <linux/nfc.h> + +#include <errno.h> +#include <libudev.h> +#include <signal.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +#include "fido.h" +#include "fido/param.h" +#include "netlink.h" +#include "iso7816.h" + +struct nfc_linux { + int fd; + uint32_t dev; + uint32_t target; + sigset_t sigmask; + const sigset_t *sigmaskp; + struct fido_nl *nl; +}; + +static char * +get_parent_attr(struct udev_device *dev, const char *subsystem, + const char *devtype, const char *attr) +{ + struct udev_device *parent; + const char *value; + + if ((parent = udev_device_get_parent_with_subsystem_devtype(dev, + subsystem, devtype)) == NULL || (value = + udev_device_get_sysattr_value(parent, attr)) == NULL) + return NULL; + + return strdup(value); +} + +static char * +get_usb_attr(struct udev_device *dev, const char *attr) +{ + return get_parent_attr(dev, "usb", "usb_device", attr); +} + +static int +copy_info(fido_dev_info_t *di, struct udev *udev, + struct udev_list_entry *udev_entry) +{ + const char *name; + char *str; + struct udev_device *dev = NULL; + uint64_t id; + int ok = -1; + + memset(di, 0, sizeof(*di)); + + if ((name = udev_list_entry_get_name(udev_entry)) == NULL || + (dev = udev_device_new_from_syspath(udev, name)) == NULL) + goto fail; + if (asprintf(&di->path, "%s/%s", FIDO_NFC_PREFIX, name) == -1) { + di->path = NULL; + goto fail; + } + if (nfc_is_fido(di->path) == false) { + fido_log_debug("%s: nfc_is_fido: %s", __func__, di->path); + goto fail; + } + if ((di->manufacturer = get_usb_attr(dev, "manufacturer")) == NULL) + di->manufacturer = strdup(""); + if ((di->product = get_usb_attr(dev, "product")) == NULL) + di->product = strdup(""); + if (di->manufacturer == NULL || di->product == NULL) + goto fail; + /* XXX assumes USB for vendor/product info */ + if ((str = get_usb_attr(dev, "idVendor")) != NULL && + fido_to_uint64(str, 16, &id) == 0 && id <= UINT16_MAX) + di->vendor_id = (int16_t)id; + free(str); + if ((str = get_usb_attr(dev, "idProduct")) != NULL && + fido_to_uint64(str, 16, &id) == 0 && id <= UINT16_MAX) + di->product_id = (int16_t)id; + free(str); + + ok = 0; +fail: + if (dev != NULL) + udev_device_unref(dev); + + if (ok < 0) { + free(di->path); + free(di->manufacturer); + free(di->product); + explicit_bzero(di, sizeof(*di)); + } + + return ok; +} + +static int +sysnum_from_syspath(const char *path) +{ + struct udev *udev = NULL; + struct udev_device *dev = NULL; + const char *str; + uint64_t idx64; + int idx = -1; + + if ((udev = udev_new()) != NULL && + (dev = udev_device_new_from_syspath(udev, path)) != NULL && + (str = udev_device_get_sysnum(dev)) != NULL && + fido_to_uint64(str, 10, &idx64) == 0 && idx64 < INT_MAX) + idx = (int)idx64; + + if (dev != NULL) + udev_device_unref(dev); + if (udev != NULL) + udev_unref(udev); + + return idx; +} + +int +fido_nfc_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen) +{ + struct udev *udev = NULL; + struct udev_enumerate *udev_enum = NULL; + struct udev_list_entry *udev_list; + struct udev_list_entry *udev_entry; + int r = FIDO_ERR_INTERNAL; + + *olen = 0; + + if (ilen == 0) + return FIDO_OK; + + if (devlist == NULL) + return FIDO_ERR_INVALID_ARGUMENT; + + if ((udev = udev_new()) == NULL || + (udev_enum = udev_enumerate_new(udev)) == NULL) + goto fail; + + if (udev_enumerate_add_match_subsystem(udev_enum, "nfc") < 0 || + udev_enumerate_scan_devices(udev_enum) < 0) + goto fail; + + if ((udev_list = udev_enumerate_get_list_entry(udev_enum)) == NULL) { + r = FIDO_OK; /* zero nfc devices */ + goto fail; + } + + udev_list_entry_foreach(udev_entry, udev_list) { + if (copy_info(&devlist[*olen], udev, udev_entry) == 0) { + devlist[*olen].io = (fido_dev_io_t) { + fido_nfc_open, + fido_nfc_close, + fido_nfc_read, + fido_nfc_write, + }; + devlist[*olen].transport = (fido_dev_transport_t) { + fido_nfc_rx, + fido_nfc_tx, + }; + if (++(*olen) == ilen) + break; + } + } + + r = FIDO_OK; +fail: + if (udev_enum != NULL) + udev_enumerate_unref(udev_enum); + if (udev != NULL) + udev_unref(udev); + + return r; +} + +static int +nfc_target_connect(struct nfc_linux *ctx) +{ + struct sockaddr_nfc sa; + + memset(&sa, 0, sizeof(sa)); + sa.sa_family = AF_NFC; + sa.dev_idx = ctx->dev; + sa.target_idx = ctx->target; + sa.nfc_protocol = NFC_PROTO_ISO14443; + + if ((ctx->fd = socket(AF_NFC, SOCK_SEQPACKET | SOCK_CLOEXEC, + NFC_SOCKPROTO_RAW)) == -1) { + fido_log_error(errno, "%s: socket", __func__); + return -1; + } + if (connect(ctx->fd, (struct sockaddr *)&sa, sizeof(sa)) == -1) { + fido_log_error(errno, "%s: connect", __func__); + if (close(ctx->fd) == -1) + fido_log_error(errno, "%s: close", __func__); + ctx->fd = -1; + return -1; + } + + return 0; +} + +static void +nfc_free(struct nfc_linux **ctx_p) +{ + struct nfc_linux *ctx; + + if (ctx_p == NULL || (ctx = *ctx_p) == NULL) + return; + if (ctx->fd != -1 && close(ctx->fd) == -1) + fido_log_error(errno, "%s: close", __func__); + if (ctx->nl != NULL) + fido_nl_free(&ctx->nl); + + free(ctx); + *ctx_p = NULL; +} + +static struct nfc_linux * +nfc_new(uint32_t dev) +{ + struct nfc_linux *ctx; + + if ((ctx = calloc(1, sizeof(*ctx))) == NULL || + (ctx->nl = fido_nl_new()) == NULL) { + nfc_free(&ctx); + return NULL; + } + + ctx->fd = -1; + ctx->dev = dev; + + return ctx; +} + +void * +fido_nfc_open(const char *path) +{ + struct nfc_linux *ctx = NULL; + int idx; + + if (strncmp(path, FIDO_NFC_PREFIX, strlen(FIDO_NFC_PREFIX)) != 0) { + fido_log_debug("%s: bad prefix", __func__); + goto fail; + } + if ((idx = sysnum_from_syspath(path + strlen(FIDO_NFC_PREFIX))) < 0 || + (ctx = nfc_new((uint32_t)idx)) == NULL) { + fido_log_debug("%s: nfc_new", __func__); + goto fail; + } + if (fido_nl_power_nfc(ctx->nl, ctx->dev) < 0 || + fido_nl_get_nfc_target(ctx->nl, ctx->dev, &ctx->target) < 0 || + nfc_target_connect(ctx) < 0) { + fido_log_debug("%s: netlink", __func__); + goto fail; + } + + return ctx; +fail: + nfc_free(&ctx); + return NULL; +} + +void +fido_nfc_close(void *handle) +{ + struct nfc_linux *ctx = handle; + + nfc_free(&ctx); +} + +int +fido_nfc_set_sigmask(void *handle, const fido_sigset_t *sigmask) +{ + struct nfc_linux *ctx = handle; + + ctx->sigmask = *sigmask; + ctx->sigmaskp = &ctx->sigmask; + + return FIDO_OK; +} + +int +fido_nfc_read(void *handle, unsigned char *buf, size_t len, int ms) +{ + struct nfc_linux *ctx = handle; + struct iovec iov[2]; + uint8_t preamble; + ssize_t r; + + memset(&iov, 0, sizeof(iov)); + iov[0].iov_base = &preamble; + iov[0].iov_len = sizeof(preamble); + iov[1].iov_base = buf; + iov[1].iov_len = len; + + if (fido_hid_unix_wait(ctx->fd, ms, ctx->sigmaskp) < 0) { + fido_log_debug("%s: fido_hid_unix_wait", __func__); + return -1; + } + if ((r = readv(ctx->fd, iov, nitems(iov))) == -1) { + fido_log_error(errno, "%s: read", __func__); + return -1; + } + if (r < 1) { + fido_log_debug("%s: %zd < 1", __func__, r); + return -1; + } + if (preamble != 0x00) { + fido_log_debug("%s: preamble", __func__); + return -1; + } + + r--; + fido_log_xxd(buf, (size_t)r, "%s", __func__); + + return (int)r; +} + +int +fido_nfc_write(void *handle, const unsigned char *buf, size_t len) +{ + struct nfc_linux *ctx = handle; + ssize_t r; + + fido_log_xxd(buf, len, "%s", __func__); + + if (len > INT_MAX) { + fido_log_debug("%s: len", __func__); + return -1; + } + if ((r = write(ctx->fd, buf, len)) == -1) { + fido_log_error(errno, "%s: write", __func__); + return -1; + } + if (r < 0 || (size_t)r != len) { + fido_log_debug("%s: %zd != %zu", __func__, r, len); + return -1; + } + + return (int)r; +} diff --git a/src/packed.h b/src/packed.h new file mode 100644 index 0000000..5f53ae5 --- /dev/null +++ b/src/packed.h @@ -0,0 +1,23 @@ +/* + * 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. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#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/src/pcsc.c b/src/pcsc.c new file mode 100644 index 0000000..d7bd6c6 --- /dev/null +++ b/src/pcsc.c @@ -0,0 +1,394 @@ +/* + * Copyright (c) 2022 Micro Focus or one of its affiliates. + * Copyright (c) 2022 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. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#if __APPLE__ +#include <PCSC/wintypes.h> +#include <PCSC/winscard.h> +#else +#include <winscard.h> +#endif /* __APPLE__ */ + +#include <errno.h> + +#include "fido.h" +#include "fido/param.h" +#include "iso7816.h" + +#if defined(_WIN32) && !defined(__MINGW32__) +#define SCardConnect SCardConnectA +#define SCardListReaders SCardListReadersA +#endif + +#ifndef SCARD_PROTOCOL_Tx +#define SCARD_PROTOCOL_Tx SCARD_PROTOCOL_ANY +#endif + +#define BUFSIZE 1024 /* in bytes; passed to SCardListReaders() */ +#define APDULEN 264 /* 261 rounded up to the nearest multiple of 8 */ +#define READERS 8 /* maximum number of readers */ + +struct pcsc { + SCARDCONTEXT ctx; + SCARDHANDLE h; + SCARD_IO_REQUEST req; + uint8_t rx_buf[APDULEN]; + size_t rx_len; +}; + +static LONG +list_readers(SCARDCONTEXT ctx, char **buf) +{ + LONG s; + DWORD len; + + len = BUFSIZE; + if ((*buf = calloc(1, len)) == NULL) + goto fail; + if ((s = SCardListReaders(ctx, NULL, *buf, &len)) != SCARD_S_SUCCESS) { + fido_log_debug("%s: SCardListReaders 0x%lx", __func__, (long)s); + goto fail; + } + /* sanity check "multi-string" */ + if (len > BUFSIZE || len < 2) { + fido_log_debug("%s: bogus len=%u", __func__, (unsigned)len); + goto fail; + } + if ((*buf)[len - 1] != 0 || (*buf)[len - 2] != '\0') { + fido_log_debug("%s: bogus buf", __func__); + goto fail; + } + return (LONG)SCARD_S_SUCCESS; +fail: + free(*buf); + *buf = NULL; + + return (LONG)SCARD_E_NO_READERS_AVAILABLE; +} + +static char * +get_reader(SCARDCONTEXT ctx, const char *path) +{ + char *reader = NULL, *buf = NULL; + const char prefix[] = FIDO_PCSC_PREFIX "//slot"; + uint64_t n; + + if (path == NULL) + goto out; + if (strncmp(path, prefix, strlen(prefix)) != 0 || + fido_to_uint64(path + strlen(prefix), 10, &n) < 0 || + n > READERS - 1) { + fido_log_debug("%s: invalid path %s", __func__, path); + goto out; + } + if (list_readers(ctx, &buf) != SCARD_S_SUCCESS) { + fido_log_debug("%s: list_readers", __func__); + goto out; + } + for (const char *name = buf; *name != 0; name += strlen(name) + 1) { + if (n == 0) { + reader = strdup(name); + goto out; + } + n--; + } + fido_log_debug("%s: failed to find reader %s", __func__, path); +out: + free(buf); + + return reader; +} + +static int +prepare_io_request(DWORD prot, SCARD_IO_REQUEST *req) +{ + switch (prot) { + case SCARD_PROTOCOL_T0: + req->dwProtocol = SCARD_PCI_T0->dwProtocol; + req->cbPciLength = SCARD_PCI_T0->cbPciLength; + break; + case SCARD_PROTOCOL_T1: + req->dwProtocol = SCARD_PCI_T1->dwProtocol; + req->cbPciLength = SCARD_PCI_T1->cbPciLength; + break; + default: + fido_log_debug("%s: unknown protocol %lu", __func__, + (u_long)prot); + return -1; + } + + return 0; +} + +static int +copy_info(fido_dev_info_t *di, SCARDCONTEXT ctx, const char *reader, size_t idx) +{ + SCARDHANDLE h = 0; + SCARD_IO_REQUEST req; + DWORD prot = 0; + LONG s; + int ok = -1; + + memset(di, 0, sizeof(*di)); + memset(&req, 0, sizeof(req)); + + if ((s = SCardConnect(ctx, reader, SCARD_SHARE_SHARED, + SCARD_PROTOCOL_Tx, &h, &prot)) != SCARD_S_SUCCESS) { + fido_log_debug("%s: SCardConnect 0x%lx", __func__, (long)s); + goto fail; + } + if (prepare_io_request(prot, &req) < 0) { + fido_log_debug("%s: prepare_io_request", __func__); + goto fail; + } + if (asprintf(&di->path, "%s//slot%zu", FIDO_PCSC_PREFIX, idx) == -1) { + di->path = NULL; + fido_log_debug("%s: asprintf", __func__); + goto fail; + } + if (nfc_is_fido(di->path) == false) { + fido_log_debug("%s: nfc_is_fido: %s", __func__, di->path); + goto fail; + } + if ((di->manufacturer = strdup("PC/SC")) == NULL || + (di->product = strdup(reader)) == NULL) + goto fail; + + ok = 0; +fail: + if (h != 0) + SCardDisconnect(h, SCARD_LEAVE_CARD); + if (ok < 0) { + free(di->path); + free(di->manufacturer); + free(di->product); + explicit_bzero(di, sizeof(*di)); + } + + return ok; +} + +int +fido_pcsc_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen) +{ + SCARDCONTEXT ctx = 0; + char *buf = NULL; + LONG s; + size_t idx = 0; + int r = FIDO_ERR_INTERNAL; + + *olen = 0; + + if (ilen == 0) + return FIDO_OK; + if (devlist == NULL) + return FIDO_ERR_INVALID_ARGUMENT; + + if ((s = SCardEstablishContext(SCARD_SCOPE_SYSTEM, NULL, NULL, + &ctx)) != SCARD_S_SUCCESS || ctx == 0) { + fido_log_debug("%s: SCardEstablishContext 0x%lx", __func__, + (long)s); + if (s == (LONG)SCARD_E_NO_SERVICE || + s == (LONG)SCARD_E_NO_SMARTCARD) + r = FIDO_OK; /* suppress error */ + goto out; + } + if ((s = list_readers(ctx, &buf)) != SCARD_S_SUCCESS) { + fido_log_debug("%s: list_readers 0x%lx", __func__, (long)s); + if (s == (LONG)SCARD_E_NO_READERS_AVAILABLE) + r = FIDO_OK; /* suppress error */ + goto out; + } + + for (const char *name = buf; *name != 0; name += strlen(name) + 1) { + if (idx == READERS) { + fido_log_debug("%s: stopping at %zu readers", __func__, + idx); + r = FIDO_OK; + goto out; + } + if (copy_info(&devlist[*olen], ctx, name, idx++) == 0) { + devlist[*olen].io = (fido_dev_io_t) { + fido_pcsc_open, + fido_pcsc_close, + fido_pcsc_read, + fido_pcsc_write, + }; + devlist[*olen].transport = (fido_dev_transport_t) { + fido_pcsc_rx, + fido_pcsc_tx, + }; + if (++(*olen) == ilen) + break; + } + } + + r = FIDO_OK; +out: + free(buf); + if (ctx != 0) + SCardReleaseContext(ctx); + + return r; +} + +void * +fido_pcsc_open(const char *path) +{ + char *reader = NULL; + struct pcsc *dev = NULL; + SCARDCONTEXT ctx = 0; + SCARDHANDLE h = 0; + SCARD_IO_REQUEST req; + DWORD prot = 0; + LONG s; + + memset(&req, 0, sizeof(req)); + + if ((s = SCardEstablishContext(SCARD_SCOPE_SYSTEM, NULL, NULL, + &ctx)) != SCARD_S_SUCCESS || ctx == 0) { + fido_log_debug("%s: SCardEstablishContext 0x%lx", __func__, + (long)s); + goto fail; + + } + if ((reader = get_reader(ctx, path)) == NULL) { + fido_log_debug("%s: get_reader(%s)", __func__, path); + goto fail; + } + if ((s = SCardConnect(ctx, reader, SCARD_SHARE_SHARED, + SCARD_PROTOCOL_Tx, &h, &prot)) != SCARD_S_SUCCESS) { + fido_log_debug("%s: SCardConnect 0x%lx", __func__, (long)s); + goto fail; + } + if (prepare_io_request(prot, &req) < 0) { + fido_log_debug("%s: prepare_io_request", __func__); + goto fail; + } + if ((dev = calloc(1, sizeof(*dev))) == NULL) + goto fail; + + dev->ctx = ctx; + dev->h = h; + dev->req = req; + ctx = 0; + h = 0; +fail: + if (h != 0) + SCardDisconnect(h, SCARD_LEAVE_CARD); + if (ctx != 0) + SCardReleaseContext(ctx); + free(reader); + + return dev; +} + +void +fido_pcsc_close(void *handle) +{ + struct pcsc *dev = handle; + + if (dev->h != 0) + SCardDisconnect(dev->h, SCARD_LEAVE_CARD); + if (dev->ctx != 0) + SCardReleaseContext(dev->ctx); + + explicit_bzero(dev->rx_buf, sizeof(dev->rx_buf)); + free(dev); +} + +int +fido_pcsc_read(void *handle, unsigned char *buf, size_t len, int ms) +{ + struct pcsc *dev = handle; + int r; + + (void)ms; + if (dev->rx_len == 0 || dev->rx_len > len || + dev->rx_len > sizeof(dev->rx_buf)) { + fido_log_debug("%s: rx_len", __func__); + return -1; + } + fido_log_xxd(dev->rx_buf, dev->rx_len, "%s: reading", __func__); + memcpy(buf, dev->rx_buf, dev->rx_len); + explicit_bzero(dev->rx_buf, sizeof(dev->rx_buf)); + r = (int)dev->rx_len; + dev->rx_len = 0; + + return r; +} + +int +fido_pcsc_write(void *handle, const unsigned char *buf, size_t len) +{ + struct pcsc *dev = handle; + DWORD n; + LONG s; + + if (len > INT_MAX) { + fido_log_debug("%s: len", __func__); + return -1; + } + + explicit_bzero(dev->rx_buf, sizeof(dev->rx_buf)); + dev->rx_len = 0; + n = (DWORD)sizeof(dev->rx_buf); + + fido_log_xxd(buf, len, "%s: writing", __func__); + + if ((s = SCardTransmit(dev->h, &dev->req, buf, (DWORD)len, NULL, + dev->rx_buf, &n)) != SCARD_S_SUCCESS) { + fido_log_debug("%s: SCardTransmit 0x%lx", __func__, (long)s); + explicit_bzero(dev->rx_buf, sizeof(dev->rx_buf)); + return -1; + } + dev->rx_len = (size_t)n; + + fido_log_xxd(dev->rx_buf, dev->rx_len, "%s: read", __func__); + + return (int)len; +} + +int +fido_pcsc_tx(fido_dev_t *d, uint8_t cmd, const u_char *buf, size_t count) +{ + return fido_nfc_tx(d, cmd, buf, count); +} + +int +fido_pcsc_rx(fido_dev_t *d, uint8_t cmd, u_char *buf, size_t count, int ms) +{ + return fido_nfc_rx(d, cmd, buf, count, ms); +} + +bool +fido_is_pcsc(const char *path) +{ + return strncmp(path, FIDO_PCSC_PREFIX, strlen(FIDO_PCSC_PREFIX)) == 0; +} + +int +fido_dev_set_pcsc(fido_dev_t *d) +{ + if (d->io_handle != NULL) { + fido_log_debug("%s: device open", __func__); + return -1; + } + d->io_own = true; + d->io = (fido_dev_io_t) { + fido_pcsc_open, + fido_pcsc_close, + fido_pcsc_read, + fido_pcsc_write, + }; + d->transport = (fido_dev_transport_t) { + fido_pcsc_rx, + fido_pcsc_tx, + }; + + return 0; +} diff --git a/src/pin.c b/src/pin.c new file mode 100644 index 0000000..c3dd927 --- /dev/null +++ b/src/pin.c @@ -0,0 +1,723 @@ +/* + * Copyright (c) 2018-2022 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. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <openssl/sha.h> +#include "fido.h" +#include "fido/es256.h" + +#define CTAP21_UV_TOKEN_PERM_MAKECRED 0x01 +#define CTAP21_UV_TOKEN_PERM_ASSERT 0x02 +#define CTAP21_UV_TOKEN_PERM_CRED_MGMT 0x04 +#define CTAP21_UV_TOKEN_PERM_BIO 0x08 +#define CTAP21_UV_TOKEN_PERM_LARGEBLOB 0x10 +#define CTAP21_UV_TOKEN_PERM_CONFIG 0x20 + +int +fido_sha256(fido_blob_t *digest, const u_char *data, size_t data_len) +{ + 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) { + fido_blob_reset(digest); + return (-1); + } + + return (0); +} + +static int +pin_sha256_enc(const fido_dev_t *dev, const fido_blob_t *shared, + const fido_blob_t *pin, fido_blob_t **out) +{ + fido_blob_t *ph = NULL; + int r; + + if ((*out = fido_blob_new()) == NULL || + (ph = fido_blob_new()) == NULL) { + r = FIDO_ERR_INTERNAL; + goto fail; + } + + if (fido_sha256(ph, pin->ptr, pin->len) < 0 || ph->len < 16) { + fido_log_debug("%s: SHA256", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + ph->len = 16; /* first 16 bytes */ + + if (aes256_cbc_enc(dev, shared, ph, *out) < 0) { + fido_log_debug("%s: aes256_cbc_enc", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + r = FIDO_OK; +fail: + fido_blob_free(&ph); + + return (r); +} + +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 > 63) { + fido_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 + 63U) & ~63U; + 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 +pin_pad64_enc(const fido_dev_t *dev, const fido_blob_t *shared, + const char *pin, fido_blob_t **out) +{ + fido_blob_t *ppin = NULL; + int r; + + if ((r = pad64(pin, &ppin)) != FIDO_OK) { + fido_log_debug("%s: pad64", __func__); + goto fail; + } + + if ((*out = fido_blob_new()) == NULL) { + r = FIDO_ERR_INTERNAL; + goto fail; + } + + if (aes256_cbc_enc(dev, shared, ppin, *out) < 0) { + fido_log_debug("%s: aes256_cbc_enc", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + r = FIDO_OK; +fail: + fido_blob_free(&ppin); + + return (r); +} + +static cbor_item_t * +encode_uv_permission(uint8_t cmd) +{ + switch (cmd) { + case CTAP_CBOR_ASSERT: + return (cbor_build_uint8(CTAP21_UV_TOKEN_PERM_ASSERT)); + case CTAP_CBOR_BIO_ENROLL_PRE: + return (cbor_build_uint8(CTAP21_UV_TOKEN_PERM_BIO)); + case CTAP_CBOR_CONFIG: + return (cbor_build_uint8(CTAP21_UV_TOKEN_PERM_CONFIG)); + case CTAP_CBOR_MAKECRED: + return (cbor_build_uint8(CTAP21_UV_TOKEN_PERM_MAKECRED)); + case CTAP_CBOR_CRED_MGMT_PRE: + return (cbor_build_uint8(CTAP21_UV_TOKEN_PERM_CRED_MGMT)); + case CTAP_CBOR_LARGEBLOB: + return (cbor_build_uint8(CTAP21_UV_TOKEN_PERM_LARGEBLOB)); + default: + fido_log_debug("%s: cmd 0x%02x", __func__, cmd); + return (NULL); + } +} + +static int +ctap20_uv_token_tx(fido_dev_t *dev, const char *pin, const fido_blob_t *ecdh, + const es256_pk_t *pk, int *ms) +{ + fido_blob_t f; + fido_blob_t *p = NULL; + fido_blob_t *phe = NULL; + cbor_item_t *argv[6]; + int r; + + memset(&f, 0, sizeof(f)); + memset(argv, 0, sizeof(argv)); + + if (pin == NULL) { + fido_log_debug("%s: NULL pin", __func__); + r = FIDO_ERR_PIN_REQUIRED; + goto fail; + } + + if ((p = fido_blob_new()) == NULL || fido_blob_set(p, + (const unsigned char *)pin, strlen(pin)) < 0) { + fido_log_debug("%s: fido_blob_set", __func__); + r = FIDO_ERR_INVALID_ARGUMENT; + goto fail; + } + + if ((r = pin_sha256_enc(dev, ecdh, p, &phe)) != FIDO_OK) { + fido_log_debug("%s: pin_sha256_enc", __func__); + goto fail; + } + + if ((argv[0] = cbor_encode_pin_opt(dev)) == NULL || + (argv[1] = cbor_build_uint8(5)) == NULL || + (argv[2] = es256_pk_encode(pk, 1)) == NULL || + (argv[5] = fido_blob_encode(phe)) == NULL) { + fido_log_debug("%s: cbor encode", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + if (cbor_build_frame(CTAP_CBOR_CLIENT_PIN, argv, nitems(argv), + &f) < 0 || fido_tx(dev, CTAP_CMD_CBOR, f.ptr, f.len, ms) < 0) { + fido_log_debug("%s: fido_tx", __func__); + r = FIDO_ERR_TX; + goto fail; + } + + r = FIDO_OK; +fail: + cbor_vector_free(argv, nitems(argv)); + fido_blob_free(&p); + fido_blob_free(&phe); + free(f.ptr); + + return (r); +} + +static int +ctap21_uv_token_tx(fido_dev_t *dev, const char *pin, const fido_blob_t *ecdh, + const es256_pk_t *pk, uint8_t cmd, const char *rpid, int *ms) +{ + fido_blob_t f; + fido_blob_t *p = NULL; + fido_blob_t *phe = NULL; + cbor_item_t *argv[10]; + uint8_t subcmd; + int r; + + memset(&f, 0, sizeof(f)); + memset(argv, 0, sizeof(argv)); + + if (pin != NULL) { + if ((p = fido_blob_new()) == NULL || fido_blob_set(p, + (const unsigned char *)pin, strlen(pin)) < 0) { + fido_log_debug("%s: fido_blob_set", __func__); + r = FIDO_ERR_INVALID_ARGUMENT; + goto fail; + } + if ((r = pin_sha256_enc(dev, ecdh, p, &phe)) != FIDO_OK) { + fido_log_debug("%s: pin_sha256_enc", __func__); + goto fail; + } + subcmd = 9; /* getPinUvAuthTokenUsingPinWithPermissions */ + } else { + if (fido_dev_has_uv(dev) == false) { + fido_log_debug("%s: fido_dev_has_uv", __func__); + r = FIDO_ERR_PIN_REQUIRED; + goto fail; + } + subcmd = 6; /* getPinUvAuthTokenUsingUvWithPermissions */ + } + + if ((argv[0] = cbor_encode_pin_opt(dev)) == NULL || + (argv[1] = cbor_build_uint8(subcmd)) == NULL || + (argv[2] = es256_pk_encode(pk, 1)) == NULL || + (phe != NULL && (argv[5] = fido_blob_encode(phe)) == NULL) || + (argv[8] = encode_uv_permission(cmd)) == NULL || + (rpid != NULL && (argv[9] = cbor_build_string(rpid)) == NULL)) { + fido_log_debug("%s: cbor encode", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + if (cbor_build_frame(CTAP_CBOR_CLIENT_PIN, argv, nitems(argv), + &f) < 0 || fido_tx(dev, CTAP_CMD_CBOR, f.ptr, f.len, ms) < 0) { + fido_log_debug("%s: fido_tx", __func__); + r = FIDO_ERR_TX; + goto fail; + } + + r = FIDO_OK; +fail: + cbor_vector_free(argv, nitems(argv)); + fido_blob_free(&p); + fido_blob_free(&phe); + free(f.ptr); + + return (r); +} + +static int +parse_uv_token(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) { + fido_log_debug("%s: cbor type", __func__); + return (0); /* ignore */ + } + + return (fido_blob_decode(val, token)); +} + +static int +uv_token_rx(fido_dev_t *dev, const fido_blob_t *ecdh, fido_blob_t *token, + int *ms) +{ + fido_blob_t *aes_token = NULL; + unsigned char *msg = NULL; + int msglen; + int r; + + if ((aes_token = fido_blob_new()) == NULL) { + r = FIDO_ERR_INTERNAL; + goto fail; + } + + if ((msg = malloc(FIDO_MAXMSG)) == NULL) { + r = FIDO_ERR_INTERNAL; + goto fail; + } + + if ((msglen = fido_rx(dev, CTAP_CMD_CBOR, msg, FIDO_MAXMSG, ms)) < 0) { + fido_log_debug("%s: fido_rx", __func__); + r = FIDO_ERR_RX; + goto fail; + } + + if ((r = cbor_parse_reply(msg, (size_t)msglen, aes_token, + parse_uv_token)) != FIDO_OK) { + fido_log_debug("%s: parse_uv_token", __func__); + goto fail; + } + + if (aes256_cbc_dec(dev, ecdh, aes_token, token) < 0) { + fido_log_debug("%s: aes256_cbc_dec", __func__); + r = FIDO_ERR_RX; + goto fail; + } + + r = FIDO_OK; +fail: + fido_blob_free(&aes_token); + freezero(msg, FIDO_MAXMSG); + + return (r); +} + +static int +uv_token_wait(fido_dev_t *dev, uint8_t cmd, const char *pin, + const fido_blob_t *ecdh, const es256_pk_t *pk, const char *rpid, + fido_blob_t *token, int *ms) +{ + int r; + + if (ecdh == NULL || pk == NULL) + return (FIDO_ERR_INVALID_ARGUMENT); + if (fido_dev_supports_permissions(dev)) + r = ctap21_uv_token_tx(dev, pin, ecdh, pk, cmd, rpid, ms); + else + r = ctap20_uv_token_tx(dev, pin, ecdh, pk, ms); + if (r != FIDO_OK) + return (r); + + return (uv_token_rx(dev, ecdh, token, ms)); +} + +int +fido_dev_get_uv_token(fido_dev_t *dev, uint8_t cmd, const char *pin, + const fido_blob_t *ecdh, const es256_pk_t *pk, const char *rpid, + fido_blob_t *token, int *ms) +{ + return (uv_token_wait(dev, cmd, pin, ecdh, pk, rpid, token, ms)); +} + +static int +fido_dev_change_pin_tx(fido_dev_t *dev, const char *pin, const char *oldpin, + int *ms) +{ + fido_blob_t f; + fido_blob_t *ppine = NULL; + fido_blob_t *ecdh = NULL; + fido_blob_t *opin = NULL; + fido_blob_t *opinhe = 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) { + fido_log_debug("%s: fido_blob_set", __func__); + r = FIDO_ERR_INVALID_ARGUMENT; + goto fail; + } + + if ((r = fido_do_ecdh(dev, &pk, &ecdh, ms)) != FIDO_OK) { + fido_log_debug("%s: fido_do_ecdh", __func__); + goto fail; + } + + /* pad and encrypt new pin */ + if ((r = pin_pad64_enc(dev, ecdh, pin, &ppine)) != FIDO_OK) { + fido_log_debug("%s: pin_pad64_enc", __func__); + goto fail; + } + + /* hash and encrypt old pin */ + if ((r = pin_sha256_enc(dev, ecdh, opin, &opinhe)) != FIDO_OK) { + fido_log_debug("%s: pin_sha256_enc", __func__); + goto fail; + } + + if ((argv[0] = cbor_encode_pin_opt(dev)) == NULL || + (argv[1] = cbor_build_uint8(4)) == NULL || + (argv[2] = es256_pk_encode(pk, 1)) == NULL || + (argv[3] = cbor_encode_change_pin_auth(dev, ecdh, ppine, opinhe)) == NULL || + (argv[4] = fido_blob_encode(ppine)) == NULL || + (argv[5] = fido_blob_encode(opinhe)) == NULL) { + fido_log_debug("%s: cbor encode", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + if (cbor_build_frame(CTAP_CBOR_CLIENT_PIN, argv, nitems(argv), + &f) < 0 || fido_tx(dev, CTAP_CMD_CBOR, f.ptr, f.len, ms) < 0) { + fido_log_debug("%s: fido_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(&ppine); + fido_blob_free(&ecdh); + fido_blob_free(&opin); + fido_blob_free(&opinhe); + free(f.ptr); + + return (r); + +} + +static int +fido_dev_set_pin_tx(fido_dev_t *dev, const char *pin, int *ms) +{ + fido_blob_t f; + fido_blob_t *ppine = 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 = fido_do_ecdh(dev, &pk, &ecdh, ms)) != FIDO_OK) { + fido_log_debug("%s: fido_do_ecdh", __func__); + goto fail; + } + + if ((r = pin_pad64_enc(dev, ecdh, pin, &ppine)) != FIDO_OK) { + fido_log_debug("%s: pin_pad64_enc", __func__); + goto fail; + } + + if ((argv[0] = cbor_encode_pin_opt(dev)) == NULL || + (argv[1] = cbor_build_uint8(3)) == NULL || + (argv[2] = es256_pk_encode(pk, 1)) == NULL || + (argv[3] = cbor_encode_pin_auth(dev, ecdh, ppine)) == NULL || + (argv[4] = fido_blob_encode(ppine)) == NULL) { + fido_log_debug("%s: cbor encode", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + if (cbor_build_frame(CTAP_CBOR_CLIENT_PIN, argv, nitems(argv), + &f) < 0 || fido_tx(dev, CTAP_CMD_CBOR, f.ptr, f.len, ms) < 0) { + fido_log_debug("%s: fido_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(&ppine); + 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, + ms)) != FIDO_OK) { + fido_log_debug("%s: fido_dev_change_pin_tx", __func__); + return (r); + } + } else { + if ((r = fido_dev_set_pin_tx(dev, pin, ms)) != FIDO_OK) { + fido_log_debug("%s: fido_dev_set_pin_tx", __func__); + return (r); + } + } + + if ((r = fido_rx_cbor_status(dev, ms)) != FIDO_OK) { + fido_log_debug("%s: fido_rx_cbor_status", __func__); + return (r); + } + + if (dev->flags & FIDO_DEV_PIN_UNSET) { + dev->flags &= ~FIDO_DEV_PIN_UNSET; + dev->flags |= FIDO_DEV_PIN_SET; + } + + return (FIDO_OK); +} + +int +fido_dev_set_pin(fido_dev_t *dev, const char *pin, const char *oldpin) +{ + int ms = dev->timeout_ms; + + return (fido_dev_set_pin_wait(dev, pin, oldpin, &ms)); +} + +static int +parse_retry_count(const uint8_t keyval, 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) != keyval) { + fido_log_debug("%s: cbor type", __func__); + return (0); /* ignore */ + } + + if (cbor_decode_uint64(val, &n) < 0 || n > INT_MAX) { + fido_log_debug("%s: cbor_decode_uint64", __func__); + return (-1); + } + + *retries = (int)n; + + return (0); +} + +static int +parse_pin_retry_count(const cbor_item_t *key, const cbor_item_t *val, void *arg) +{ + return (parse_retry_count(3, key, val, arg)); +} + +static int +parse_uv_retry_count(const cbor_item_t *key, const cbor_item_t *val, void *arg) +{ + return (parse_retry_count(5, key, val, arg)); +} + +static int +fido_dev_get_retry_count_tx(fido_dev_t *dev, uint8_t subcmd, int *ms) +{ + 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(subcmd)) == NULL) { + r = FIDO_ERR_INTERNAL; + goto fail; + } + + if (cbor_build_frame(CTAP_CBOR_CLIENT_PIN, argv, nitems(argv), + &f) < 0 || fido_tx(dev, CTAP_CMD_CBOR, f.ptr, f.len, ms) < 0) { + fido_log_debug("%s: fido_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_pin_retry_count_rx(fido_dev_t *dev, int *retries, int *ms) +{ + unsigned char *msg; + int msglen; + int r; + + *retries = 0; + + if ((msg = malloc(FIDO_MAXMSG)) == NULL) { + r = FIDO_ERR_INTERNAL; + goto fail; + } + + if ((msglen = fido_rx(dev, CTAP_CMD_CBOR, msg, FIDO_MAXMSG, ms)) < 0) { + fido_log_debug("%s: fido_rx", __func__); + r = FIDO_ERR_RX; + goto fail; + } + + if ((r = cbor_parse_reply(msg, (size_t)msglen, retries, + parse_pin_retry_count)) != FIDO_OK) { + fido_log_debug("%s: parse_pin_retry_count", __func__); + goto fail; + } + + r = FIDO_OK; +fail: + freezero(msg, FIDO_MAXMSG); + + return (r); +} + +static int +fido_dev_get_pin_retry_count_wait(fido_dev_t *dev, int *retries, int *ms) +{ + int r; + + if ((r = fido_dev_get_retry_count_tx(dev, 1, ms)) != FIDO_OK || + (r = fido_dev_get_pin_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) +{ + int ms = dev->timeout_ms; + + return (fido_dev_get_pin_retry_count_wait(dev, retries, &ms)); +} + +static int +fido_dev_get_uv_retry_count_rx(fido_dev_t *dev, int *retries, int *ms) +{ + unsigned char *msg; + int msglen; + int r; + + *retries = 0; + + if ((msg = malloc(FIDO_MAXMSG)) == NULL) { + r = FIDO_ERR_INTERNAL; + goto fail; + } + + if ((msglen = fido_rx(dev, CTAP_CMD_CBOR, msg, FIDO_MAXMSG, ms)) < 0) { + fido_log_debug("%s: fido_rx", __func__); + r = FIDO_ERR_RX; + goto fail; + } + + if ((r = cbor_parse_reply(msg, (size_t)msglen, retries, + parse_uv_retry_count)) != FIDO_OK) { + fido_log_debug("%s: parse_uv_retry_count", __func__); + goto fail; + } + + r = FIDO_OK; +fail: + freezero(msg, FIDO_MAXMSG); + + return (r); +} + +static int +fido_dev_get_uv_retry_count_wait(fido_dev_t *dev, int *retries, int *ms) +{ + int r; + + if ((r = fido_dev_get_retry_count_tx(dev, 7, ms)) != FIDO_OK || + (r = fido_dev_get_uv_retry_count_rx(dev, retries, ms)) != FIDO_OK) + return (r); + + return (FIDO_OK); +} + +int +fido_dev_get_uv_retry_count(fido_dev_t *dev, int *retries) +{ + int ms = dev->timeout_ms; + + return (fido_dev_get_uv_retry_count_wait(dev, retries, &ms)); +} + +int +cbor_add_uv_params(fido_dev_t *dev, uint8_t cmd, const fido_blob_t *hmac_data, + const es256_pk_t *pk, const fido_blob_t *ecdh, const char *pin, + const char *rpid, cbor_item_t **auth, cbor_item_t **opt, int *ms) +{ + fido_blob_t *token = NULL; + int r; + + if ((token = fido_blob_new()) == NULL) { + r = FIDO_ERR_INTERNAL; + goto fail; + } + + if ((r = fido_dev_get_uv_token(dev, cmd, pin, ecdh, pk, rpid, + token, ms)) != FIDO_OK) { + fido_log_debug("%s: fido_dev_get_uv_token", __func__); + goto fail; + } + + if ((*auth = cbor_encode_pin_auth(dev, token, hmac_data)) == NULL || + (*opt = cbor_encode_pin_opt(dev)) == NULL) { + fido_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/src/random.c b/src/random.c new file mode 100644 index 0000000..9688d35 --- /dev/null +++ b/src/random.c @@ -0,0 +1,83 @@ +/* + * 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. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <sys/types.h> +#include <sys/stat.h> +#ifdef HAVE_SYS_RANDOM_H +#include <sys/random.h> +#endif + +#include <fcntl.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> + +int +fido_get_random(void *buf, size_t len) +{ + NTSTATUS status; + + status = BCryptGenRandom(NULL, buf, (ULONG)len, + BCRYPT_USE_SYSTEM_PREFERRED_RNG); + + if (!NT_SUCCESS(status)) + return (-1); + + return (0); +} +#elif defined(HAVE_ARC4RANDOM_BUF) +int +fido_get_random(void *buf, size_t len) +{ + arc4random_buf(buf, len); + return (0); +} +#elif defined(HAVE_GETRANDOM) +int +fido_get_random(void *buf, size_t len) +{ + ssize_t r; + + if ((r = getrandom(buf, len, 0)) < 0 || (size_t)r != len) + return (-1); + + return (0); +} +#elif defined(HAVE_DEV_URANDOM) +int +fido_get_random(void *buf, size_t len) +{ + int fd = -1; + int ok = -1; + ssize_t r; + + if ((fd = open(FIDO_RANDOM_DEV, O_RDONLY)) < 0) + goto fail; + if ((r = read(fd, buf, len)) < 0 || (size_t)r != len) + goto fail; + + ok = 0; +fail: + if (fd != -1) + close(fd); + + return (ok); +} +#else +#error "please provide an implementation of fido_get_random() for your platform" +#endif /* _WIN32 */ diff --git a/src/reset.c b/src/reset.c new file mode 100644 index 0000000..4e09dbb --- /dev/null +++ b/src/reset.c @@ -0,0 +1,46 @@ +/* + * 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. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "fido.h" + +static int +fido_dev_reset_tx(fido_dev_t *dev, int *ms) +{ + const unsigned char cbor[] = { CTAP_CBOR_RESET }; + + if (fido_tx(dev, CTAP_CMD_CBOR, cbor, sizeof(cbor), ms) < 0) { + fido_log_debug("%s: fido_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, ms)) != FIDO_OK || + (r = fido_rx_cbor_status(dev, ms)) != FIDO_OK) + return (r); + + if (dev->flags & FIDO_DEV_PIN_SET) { + dev->flags &= ~FIDO_DEV_PIN_SET; + dev->flags |= FIDO_DEV_PIN_UNSET; + } + + return (FIDO_OK); +} + +int +fido_dev_reset(fido_dev_t *dev) +{ + int ms = dev->timeout_ms; + + return (fido_dev_reset_wait(dev, &ms)); +} diff --git a/src/rs1.c b/src/rs1.c new file mode 100644 index 0000000..03636b5 --- /dev/null +++ b/src/rs1.c @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2021 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. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <openssl/rsa.h> +#include <openssl/obj_mac.h> + +#include "fido.h" + +#if defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x3050200fL +static EVP_MD * +rs1_get_EVP_MD(void) +{ + const EVP_MD *from; + EVP_MD *to = NULL; + + if ((from = EVP_sha1()) != NULL && (to = malloc(sizeof(*to))) != NULL) + memcpy(to, from, sizeof(*to)); + + return (to); +} + +static void +rs1_free_EVP_MD(EVP_MD *md) +{ + freezero(md, sizeof(*md)); +} +#elif OPENSSL_VERSION_NUMBER >= 0x30000000 +static EVP_MD * +rs1_get_EVP_MD(void) +{ + return (EVP_MD_fetch(NULL, "SHA-1", NULL)); +} + +static void +rs1_free_EVP_MD(EVP_MD *md) +{ + EVP_MD_free(md); +} +#else +static EVP_MD * +rs1_get_EVP_MD(void) +{ + const EVP_MD *md; + + if ((md = EVP_sha1()) == NULL) + return (NULL); + + return (EVP_MD_meth_dup(md)); +} + +static void +rs1_free_EVP_MD(EVP_MD *md) +{ + EVP_MD_meth_free(md); +} +#endif /* LIBRESSL_VERSION_NUMBER */ + +int +rs1_verify_sig(const fido_blob_t *dgst, EVP_PKEY *pkey, + const fido_blob_t *sig) +{ + EVP_PKEY_CTX *pctx = NULL; + EVP_MD *md = NULL; + int ok = -1; + + if (EVP_PKEY_base_id(pkey) != EVP_PKEY_RSA) { + fido_log_debug("%s: EVP_PKEY_base_id", __func__); + goto fail; + } + + if ((md = rs1_get_EVP_MD()) == NULL) { + fido_log_debug("%s: rs1_get_EVP_MD", __func__); + goto fail; + } + + if ((pctx = EVP_PKEY_CTX_new(pkey, NULL)) == NULL || + EVP_PKEY_verify_init(pctx) != 1 || + EVP_PKEY_CTX_set_rsa_padding(pctx, RSA_PKCS1_PADDING) != 1 || + EVP_PKEY_CTX_set_signature_md(pctx, md) != 1) { + fido_log_debug("%s: EVP_PKEY_CTX", __func__); + goto fail; + } + + if (EVP_PKEY_verify(pctx, sig->ptr, sig->len, dgst->ptr, + dgst->len) != 1) { + fido_log_debug("%s: EVP_PKEY_verify", __func__); + goto fail; + } + + ok = 0; +fail: + EVP_PKEY_CTX_free(pctx); + rs1_free_EVP_MD(md); + + return (ok); +} diff --git a/src/rs256.c b/src/rs256.c new file mode 100644 index 0000000..59ceb94 --- /dev/null +++ b/src/rs256.c @@ -0,0 +1,316 @@ +/* + * Copyright (c) 2018-2022 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. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <openssl/bn.h> +#include <openssl/rsa.h> +#include <openssl/obj_mac.h> + +#include "fido.h" +#include "fido/rs256.h" + +#if OPENSSL_VERSION_NUMBER >= 0x30000000 +#define get0_RSA(x) EVP_PKEY_get0_RSA((x)) +#else +#define get0_RSA(x) EVP_PKEY_get0((x)) +#endif + +#if defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x3050200fL +static EVP_MD * +rs256_get_EVP_MD(void) +{ + const EVP_MD *from; + EVP_MD *to = NULL; + + if ((from = EVP_sha256()) != NULL && (to = malloc(sizeof(*to))) != NULL) + memcpy(to, from, sizeof(*to)); + + return (to); +} + +static void +rs256_free_EVP_MD(EVP_MD *md) +{ + freezero(md, sizeof(*md)); +} +#elif OPENSSL_VERSION_NUMBER >= 0x30000000 +static EVP_MD * +rs256_get_EVP_MD(void) +{ + return (EVP_MD_fetch(NULL, "SHA2-256", NULL)); +} + +static void +rs256_free_EVP_MD(EVP_MD *md) +{ + EVP_MD_free(md); +} +#else +static EVP_MD * +rs256_get_EVP_MD(void) +{ + const EVP_MD *md; + + if ((md = EVP_sha256()) == NULL) + return (NULL); + + return (EVP_MD_meth_dup(md)); +} + +static void +rs256_free_EVP_MD(EVP_MD *md) +{ + EVP_MD_meth_free(md); +} +#endif /* LIBRESSL_VERSION_NUMBER */ + +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) { + fido_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) { + fido_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; + + freezero(pk, sizeof(*pk)); + *pkp = NULL; +} + +int +rs256_pk_from_ptr(rs256_pk_t *pk, const void *ptr, size_t len) +{ + EVP_PKEY *pkey; + + if (len < sizeof(*pk)) + return (FIDO_ERR_INVALID_ARGUMENT); + + memcpy(pk, ptr, sizeof(*pk)); + + if ((pkey = rs256_pk_to_EVP_PKEY(pk)) == NULL) { + fido_log_debug("%s: rs256_pk_to_EVP_PKEY", __func__); + return (FIDO_ERR_INVALID_ARGUMENT); + } + + EVP_PKEY_free(pkey); + + 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) { + fido_log_debug("%s: BN_bin2bn", __func__); + goto fail; + } + + if ((rsa = RSA_new()) == NULL || RSA_set0_key(rsa, n, e, NULL) == 0) { + fido_log_debug("%s: RSA_set0_key", __func__); + goto fail; + } + + /* at this point, n and e belong to rsa */ + n = NULL; + e = NULL; + + if (RSA_bits(rsa) != 2048) { + fido_log_debug("%s: invalid key length", __func__); + goto fail; + } + + if ((pkey = EVP_PKEY_new()) == NULL || + EVP_PKEY_assign_RSA(pkey, rsa) == 0) { + fido_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) { + fido_log_debug("%s: invalid key length", __func__); + return (FIDO_ERR_INVALID_ARGUMENT); + } + + RSA_get0_key(rsa, &n, &e, &d); + + if (n == NULL || e == NULL) { + fido_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)) { + fido_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)) { + fido_log_debug("%s: BN_bn2bin", __func__); + return (FIDO_ERR_INTERNAL); + } + + return (FIDO_OK); +} + +int +rs256_pk_from_EVP_PKEY(rs256_pk_t *pk, const EVP_PKEY *pkey) +{ + const RSA *rsa; + + if (EVP_PKEY_base_id(pkey) != EVP_PKEY_RSA || + (rsa = get0_RSA(pkey)) == NULL) + return (FIDO_ERR_INVALID_ARGUMENT); + + return (rs256_pk_from_RSA(pk, rsa)); +} + +int +rs256_verify_sig(const fido_blob_t *dgst, EVP_PKEY *pkey, + const fido_blob_t *sig) +{ + EVP_PKEY_CTX *pctx = NULL; + EVP_MD *md = NULL; + int ok = -1; + + if (EVP_PKEY_base_id(pkey) != EVP_PKEY_RSA) { + fido_log_debug("%s: EVP_PKEY_base_id", __func__); + goto fail; + } + + if ((md = rs256_get_EVP_MD()) == NULL) { + fido_log_debug("%s: rs256_get_EVP_MD", __func__); + goto fail; + } + + if ((pctx = EVP_PKEY_CTX_new(pkey, NULL)) == NULL || + EVP_PKEY_verify_init(pctx) != 1 || + EVP_PKEY_CTX_set_rsa_padding(pctx, RSA_PKCS1_PADDING) != 1 || + EVP_PKEY_CTX_set_signature_md(pctx, md) != 1) { + fido_log_debug("%s: EVP_PKEY_CTX", __func__); + goto fail; + } + + if (EVP_PKEY_verify(pctx, sig->ptr, sig->len, dgst->ptr, + dgst->len) != 1) { + fido_log_debug("%s: EVP_PKEY_verify", __func__); + goto fail; + } + + ok = 0; +fail: + EVP_PKEY_CTX_free(pctx); + rs256_free_EVP_MD(md); + + return (ok); +} + +int +rs256_pk_verify_sig(const fido_blob_t *dgst, const rs256_pk_t *pk, + const fido_blob_t *sig) +{ + EVP_PKEY *pkey; + int ok = -1; + + if ((pkey = rs256_pk_to_EVP_PKEY(pk)) == NULL || + rs256_verify_sig(dgst, pkey, sig) < 0) { + fido_log_debug("%s: rs256_verify_sig", __func__); + goto fail; + } + + ok = 0; +fail: + EVP_PKEY_free(pkey); + + return (ok); +} diff --git a/src/time.c b/src/time.c new file mode 100644 index 0000000..fd0e4e3 --- /dev/null +++ b/src/time.c @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2021 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. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <errno.h> +#include "fido.h" + +static int +timespec_to_ms(const struct timespec *ts) +{ + int64_t x, y; + + if (ts->tv_sec < 0 || ts->tv_nsec < 0 || + ts->tv_nsec >= 1000000000LL) + return -1; + + if ((uint64_t)ts->tv_sec >= INT64_MAX / 1000LL) + return -1; + + x = ts->tv_sec * 1000LL; + y = ts->tv_nsec / 1000000LL; + + if (INT64_MAX - x < y || x + y > INT_MAX) + return -1; + + return (int)(x + y); +} + +int +fido_time_now(struct timespec *ts_now) +{ + if (clock_gettime(CLOCK_MONOTONIC, ts_now) != 0) { + fido_log_error(errno, "%s: clock_gettime", __func__); + return -1; + } + + return 0; +} + +int +fido_time_delta(const struct timespec *ts_start, int *ms_remain) +{ + struct timespec ts_end, ts_delta; + int ms; + + if (*ms_remain < 0) + return 0; + + if (clock_gettime(CLOCK_MONOTONIC, &ts_end) != 0) { + fido_log_error(errno, "%s: clock_gettime", __func__); + return -1; + } + + if (timespeccmp(&ts_end, ts_start, <)) { + fido_log_debug("%s: timespeccmp", __func__); + return -1; + } + + timespecsub(&ts_end, ts_start, &ts_delta); + + if ((ms = timespec_to_ms(&ts_delta)) < 0) { + fido_log_debug("%s: timespec_to_ms", __func__); + return -1; + } + + if (ms > *ms_remain) + ms = *ms_remain; + + *ms_remain -= ms; + + return 0; +} diff --git a/src/touch.c b/src/touch.c new file mode 100644 index 0000000..6844e2c --- /dev/null +++ b/src/touch.c @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2018-2022 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. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <openssl/sha.h> +#include "fido.h" + +int +fido_dev_get_touch_begin(fido_dev_t *dev) +{ + fido_blob_t f; + cbor_item_t *argv[9]; + const char *clientdata = FIDO_DUMMY_CLIENTDATA; + const uint8_t user_id = FIDO_DUMMY_USER_ID; + unsigned char cdh[SHA256_DIGEST_LENGTH]; + fido_rp_t rp; + fido_user_t user; + int ms = dev->timeout_ms; + int r = FIDO_ERR_INTERNAL; + + memset(&f, 0, sizeof(f)); + memset(argv, 0, sizeof(argv)); + memset(cdh, 0, sizeof(cdh)); + memset(&rp, 0, sizeof(rp)); + memset(&user, 0, sizeof(user)); + + if (fido_dev_is_fido2(dev) == false) + return (u2f_get_touch_begin(dev, &ms)); + + if (SHA256((const void *)clientdata, strlen(clientdata), cdh) != cdh) { + fido_log_debug("%s: sha256", __func__); + return (FIDO_ERR_INTERNAL); + } + + if ((rp.id = strdup(FIDO_DUMMY_RP_ID)) == NULL || + (user.name = strdup(FIDO_DUMMY_USER_NAME)) == NULL) { + fido_log_debug("%s: strdup", __func__); + goto fail; + } + + if (fido_blob_set(&user.id, &user_id, sizeof(user_id)) < 0) { + fido_log_debug("%s: fido_blob_set", __func__); + goto fail; + } + + if ((argv[0] = cbor_build_bytestring(cdh, sizeof(cdh))) == NULL || + (argv[1] = cbor_encode_rp_entity(&rp)) == NULL || + (argv[2] = cbor_encode_user_entity(&user)) == NULL || + (argv[3] = cbor_encode_pubkey_param(COSE_ES256)) == NULL) { + fido_log_debug("%s: cbor encode", __func__); + goto fail; + } + + if (fido_dev_supports_pin(dev)) { + if ((argv[7] = cbor_new_definite_bytestring()) == NULL || + (argv[8] = cbor_encode_pin_opt(dev)) == NULL) { + fido_log_debug("%s: cbor encode", __func__); + goto fail; + } + } + + if (cbor_build_frame(CTAP_CBOR_MAKECRED, argv, nitems(argv), &f) < 0 || + fido_tx(dev, CTAP_CMD_CBOR, f.ptr, f.len, &ms) < 0) { + fido_log_debug("%s: fido_tx", __func__); + r = FIDO_ERR_TX; + goto fail; + } + + r = FIDO_OK; +fail: + cbor_vector_free(argv, nitems(argv)); + free(f.ptr); + free(rp.id); + free(user.name); + free(user.id.ptr); + + return (r); +} + +int +fido_dev_get_touch_status(fido_dev_t *dev, int *touched, int ms) +{ + int r; + + *touched = 0; + + if (fido_dev_is_fido2(dev) == false) + return (u2f_get_touch_status(dev, touched, &ms)); + + switch ((r = fido_rx_cbor_status(dev, &ms))) { + case FIDO_ERR_PIN_AUTH_INVALID: + case FIDO_ERR_PIN_INVALID: + case FIDO_ERR_PIN_NOT_SET: + case FIDO_ERR_SUCCESS: + *touched = 1; + break; + case FIDO_ERR_RX: + /* ignore */ + break; + default: + fido_log_debug("%s: fido_rx_cbor_status", __func__); + return (r); + } + + return (FIDO_OK); +} diff --git a/src/tpm.c b/src/tpm.c new file mode 100644 index 0000000..69c48c3 --- /dev/null +++ b/src/tpm.c @@ -0,0 +1,391 @@ +/* + * Copyright (c) 2021 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. + * SPDX-License-Identifier: BSD-2-Clause + */ + +/* + * Trusted Platform Module (TPM) 2.0 attestation support. Documentation + * references are relative to revision 01.38 of the TPM 2.0 specification. + */ + +#include <openssl/sha.h> + +#include "packed.h" +#include "fido.h" + +/* Part 1, 4.89: TPM_GENERATED_VALUE */ +#define TPM_MAGIC 0xff544347 + +/* Part 2, 6.3: TPM_ALG_ID */ +#define TPM_ALG_RSA 0x0001 +#define TPM_ALG_SHA256 0x000b +#define TPM_ALG_NULL 0x0010 +#define TPM_ALG_ECC 0x0023 + +/* Part 2, 6.4: TPM_ECC_CURVE */ +#define TPM_ECC_P256 0x0003 + +/* Part 2, 6.9: TPM_ST_ATTEST_CERTIFY */ +#define TPM_ST_CERTIFY 0x8017 + +/* Part 2, 8.3: TPMA_OBJECT */ +#define TPMA_RESERVED 0xfff8f309 /* reserved bits; must be zero */ +#define TPMA_FIXED 0x00000002 /* object has fixed hierarchy */ +#define TPMA_CLEAR 0x00000004 /* object persists */ +#define TPMA_FIXED_P 0x00000010 /* object has fixed parent */ +#define TPMA_SENSITIVE 0x00000020 /* data originates within tpm */ +#define TPMA_SIGN 0x00040000 /* object may sign */ + +/* Part 2, 10.4.2: TPM2B_DIGEST */ +PACKED_TYPE(tpm_sha256_digest_t, +struct tpm_sha256_digest { + uint16_t size; /* sizeof(body) */ + uint8_t body[32]; +}) + +/* Part 2, 10.4.3: TPM2B_DATA */ +PACKED_TYPE(tpm_sha1_data_t, +struct tpm_sha1_data { + uint16_t size; /* sizeof(body */ + uint8_t body[20]; +}) + +/* Part 2, 10.5.3: TPM2B_NAME */ +PACKED_TYPE(tpm_sha256_name_t, +struct tpm_sha256_name { + uint16_t size; /* sizeof(alg) + sizeof(body) */ + uint16_t alg; /* TPM_ALG_SHA256 */ + uint8_t body[32]; +}) + +/* Part 2, 10.11.1: TPMS_CLOCK_INFO */ +PACKED_TYPE(tpm_clock_info_t, +struct tpm_clock_info { + uint64_t timestamp_ms; + uint32_t reset_count; /* obfuscated by tpm */ + uint32_t restart_count; /* obfuscated by tpm */ + uint8_t safe; /* 1 if timestamp_ms is current */ +}) + +/* Part 2, 10.12.8 TPMS_ATTEST */ +PACKED_TYPE(tpm_sha1_attest_t, +struct tpm_sha1_attest { + uint32_t magic; /* TPM_MAGIC */ + uint16_t type; /* TPM_ST_ATTEST_CERTIFY */ + tpm_sha256_name_t signer; /* full tpm path of signing key */ + tpm_sha1_data_t data; /* signed sha1 */ + tpm_clock_info_t clock; + uint64_t fwversion; /* obfuscated by tpm */ + tpm_sha256_name_t name; /* sha256 of tpm_rs256_pubarea_t */ + tpm_sha256_name_t qual_name; /* full tpm path of attested key */ +}) + +/* Part 2, 11.2.4.5: TPM2B_PUBLIC_KEY_RSA */ +PACKED_TYPE(tpm_rs256_key_t, +struct tpm_rs256_key { + uint16_t size; /* sizeof(body) */ + uint8_t body[256]; +}) + +/* Part 2, 11.2.5.1: TPM2B_ECC_PARAMETER */ +PACKED_TYPE(tpm_es256_coord_t, +struct tpm_es256_coord { + uint16_t size; /* sizeof(body) */ + uint8_t body[32]; +}) + +/* Part 2, 11.2.5.2: TPMS_ECC_POINT */ +PACKED_TYPE(tpm_es256_point_t, +struct tpm_es256_point { + tpm_es256_coord_t x; + tpm_es256_coord_t y; +}) + +/* Part 2, 12.2.3.5: TPMS_RSA_PARMS */ +PACKED_TYPE(tpm_rs256_param_t, +struct tpm_rs256_param { + uint16_t symmetric; /* TPM_ALG_NULL */ + uint16_t scheme; /* TPM_ALG_NULL */ + uint16_t keybits; /* 2048 */ + uint32_t exponent; /* zero (meaning 2^16 + 1) */ +}) + +/* Part 2, 12.2.3.6: TPMS_ECC_PARMS */ +PACKED_TYPE(tpm_es256_param_t, +struct tpm_es256_param { + uint16_t symmetric; /* TPM_ALG_NULL */ + uint16_t scheme; /* TPM_ALG_NULL */ + uint16_t curve_id; /* TPM_ECC_P256 */ + uint16_t kdf; /* TPM_ALG_NULL */ +}) + +/* Part 2, 12.2.4: TPMT_PUBLIC */ +PACKED_TYPE(tpm_rs256_pubarea_t, +struct tpm_rs256_pubarea { + uint16_t alg; /* TPM_ALG_RSA */ + uint16_t hash; /* TPM_ALG_SHA256 */ + uint32_t attr; + tpm_sha256_digest_t policy; /* must be present? */ + tpm_rs256_param_t param; + tpm_rs256_key_t key; +}) + +/* Part 2, 12.2.4: TPMT_PUBLIC */ +PACKED_TYPE(tpm_es256_pubarea_t, +struct tpm_es256_pubarea { + uint16_t alg; /* TPM_ALG_ECC */ + uint16_t hash; /* TPM_ALG_SHA256 */ + uint32_t attr; + tpm_sha256_digest_t policy; /* must be present? */ + tpm_es256_param_t param; + tpm_es256_point_t point; +}) + +static int +get_signed_sha1(tpm_sha1_data_t *dgst, const fido_blob_t *authdata, + const fido_blob_t *clientdata) +{ + const EVP_MD *md = NULL; + EVP_MD_CTX *ctx = NULL; + int ok = -1; + + if ((dgst->size = sizeof(dgst->body)) != SHA_DIGEST_LENGTH || + (md = EVP_sha1()) == NULL || + (ctx = EVP_MD_CTX_new()) == NULL || + EVP_DigestInit_ex(ctx, md, NULL) != 1 || + EVP_DigestUpdate(ctx, authdata->ptr, authdata->len) != 1 || + EVP_DigestUpdate(ctx, clientdata->ptr, clientdata->len) != 1 || + EVP_DigestFinal_ex(ctx, dgst->body, NULL) != 1) { + fido_log_debug("%s: sha1", __func__); + goto fail; + } + + ok = 0; +fail: + EVP_MD_CTX_free(ctx); + + return (ok); +} + +static int +get_signed_name(tpm_sha256_name_t *name, const fido_blob_t *pubarea) +{ + name->alg = TPM_ALG_SHA256; + name->size = sizeof(name->alg) + sizeof(name->body); + if (sizeof(name->body) != SHA256_DIGEST_LENGTH || + SHA256(pubarea->ptr, pubarea->len, name->body) != name->body) { + fido_log_debug("%s: sha256", __func__); + return -1; + } + + return 0; +} + +static void +bswap_rs256_pubarea(tpm_rs256_pubarea_t *x) +{ + x->alg = htobe16(x->alg); + x->hash = htobe16(x->hash); + x->attr = htobe32(x->attr); + x->policy.size = htobe16(x->policy.size); + x->param.symmetric = htobe16(x->param.symmetric); + x->param.scheme = htobe16(x->param.scheme); + x->param.keybits = htobe16(x->param.keybits); + x->key.size = htobe16(x->key.size); +} + +static void +bswap_es256_pubarea(tpm_es256_pubarea_t *x) +{ + x->alg = htobe16(x->alg); + x->hash = htobe16(x->hash); + x->attr = htobe32(x->attr); + x->policy.size = htobe16(x->policy.size); + x->param.symmetric = htobe16(x->param.symmetric); + x->param.scheme = htobe16(x->param.scheme); + x->param.curve_id = htobe16(x->param.curve_id); + x->param.kdf = htobe16(x->param.kdf); + x->point.x.size = htobe16(x->point.x.size); + x->point.y.size = htobe16(x->point.y.size); +} + +static void +bswap_sha1_certinfo(tpm_sha1_attest_t *x) +{ + x->magic = htobe32(x->magic); + x->type = htobe16(x->type); + x->signer.size = htobe16(x->signer.size); + x->data.size = htobe16(x->data.size); + x->name.alg = htobe16(x->name.alg); + x->name.size = htobe16(x->name.size); +} + +static int +check_rs256_pubarea(const fido_blob_t *buf, const rs256_pk_t *pk) +{ + const tpm_rs256_pubarea_t *actual; + tpm_rs256_pubarea_t expected; + int ok; + + if (buf->len != sizeof(*actual)) { + fido_log_debug("%s: buf->len=%zu", __func__, buf->len); + return -1; + } + actual = (const void *)buf->ptr; + + memset(&expected, 0, sizeof(expected)); + expected.alg = TPM_ALG_RSA; + expected.hash = TPM_ALG_SHA256; + expected.attr = be32toh(actual->attr); + expected.attr &= ~(TPMA_RESERVED|TPMA_CLEAR); + expected.attr |= (TPMA_FIXED|TPMA_FIXED_P|TPMA_SENSITIVE|TPMA_SIGN); + expected.policy = actual->policy; + expected.policy.size = sizeof(expected.policy.body); + expected.param.symmetric = TPM_ALG_NULL; + expected.param.scheme = TPM_ALG_NULL; + expected.param.keybits = 2048; + expected.param.exponent = 0; /* meaning 2^16+1 */ + expected.key.size = sizeof(expected.key.body); + memcpy(&expected.key.body, &pk->n, sizeof(expected.key.body)); + bswap_rs256_pubarea(&expected); + + ok = timingsafe_bcmp(&expected, actual, sizeof(expected)); + explicit_bzero(&expected, sizeof(expected)); + + return ok != 0 ? -1 : 0; +} + +static int +check_es256_pubarea(const fido_blob_t *buf, const es256_pk_t *pk) +{ + const tpm_es256_pubarea_t *actual; + tpm_es256_pubarea_t expected; + int ok; + + if (buf->len != sizeof(*actual)) { + fido_log_debug("%s: buf->len=%zu", __func__, buf->len); + return -1; + } + actual = (const void *)buf->ptr; + + memset(&expected, 0, sizeof(expected)); + expected.alg = TPM_ALG_ECC; + expected.hash = TPM_ALG_SHA256; + expected.attr = be32toh(actual->attr); + expected.attr &= ~(TPMA_RESERVED|TPMA_CLEAR); + expected.attr |= (TPMA_FIXED|TPMA_FIXED_P|TPMA_SENSITIVE|TPMA_SIGN); + expected.policy = actual->policy; + expected.policy.size = sizeof(expected.policy.body); + expected.param.symmetric = TPM_ALG_NULL; + expected.param.scheme = TPM_ALG_NULL; /* TCG Alg. Registry, 5.2.4 */ + expected.param.curve_id = TPM_ECC_P256; + expected.param.kdf = TPM_ALG_NULL; + expected.point.x.size = sizeof(expected.point.x.body); + expected.point.y.size = sizeof(expected.point.y.body); + memcpy(&expected.point.x.body, &pk->x, sizeof(expected.point.x.body)); + memcpy(&expected.point.y.body, &pk->y, sizeof(expected.point.y.body)); + bswap_es256_pubarea(&expected); + + ok = timingsafe_bcmp(&expected, actual, sizeof(expected)); + explicit_bzero(&expected, sizeof(expected)); + + return ok != 0 ? -1 : 0; +} + +static int +check_sha1_certinfo(const fido_blob_t *buf, const fido_blob_t *clientdata_hash, + const fido_blob_t *authdata_raw, const fido_blob_t *pubarea) +{ + const tpm_sha1_attest_t *actual; + tpm_sha1_attest_t expected; + tpm_sha1_data_t signed_data; + tpm_sha256_name_t signed_name; + int ok = -1; + + memset(&signed_data, 0, sizeof(signed_data)); + memset(&signed_name, 0, sizeof(signed_name)); + + if (get_signed_sha1(&signed_data, authdata_raw, clientdata_hash) < 0 || + get_signed_name(&signed_name, pubarea) < 0) { + fido_log_debug("%s: get_signed_sha1/name", __func__); + goto fail; + } + if (buf->len != sizeof(*actual)) { + fido_log_debug("%s: buf->len=%zu", __func__, buf->len); + goto fail; + } + actual = (const void *)buf->ptr; + + memset(&expected, 0, sizeof(expected)); + expected.magic = TPM_MAGIC; + expected.type = TPM_ST_CERTIFY; + expected.signer = actual->signer; + expected.signer.size = sizeof(expected.signer.alg) + + sizeof(expected.signer.body); + expected.data = signed_data; + expected.clock = actual->clock; + expected.clock.safe = 1; + expected.fwversion = actual->fwversion; + expected.name = signed_name; + expected.qual_name = actual->qual_name; + bswap_sha1_certinfo(&expected); + + ok = timingsafe_bcmp(&expected, actual, sizeof(expected)); +fail: + explicit_bzero(&expected, sizeof(expected)); + explicit_bzero(&signed_data, sizeof(signed_data)); + explicit_bzero(&signed_name, sizeof(signed_name)); + + return ok != 0 ? -1 : 0; +} + +int +fido_get_signed_hash_tpm(fido_blob_t *dgst, const fido_blob_t *clientdata_hash, + const fido_blob_t *authdata_raw, const fido_attstmt_t *attstmt, + const fido_attcred_t *attcred) +{ + const fido_blob_t *pubarea = &attstmt->pubarea; + const fido_blob_t *certinfo = &attstmt->certinfo; + + if (attstmt->alg != COSE_RS1) { + fido_log_debug("%s: unsupported alg %d", __func__, + attstmt->alg); + return -1; + } + + switch (attcred->type) { + case COSE_ES256: + if (check_es256_pubarea(pubarea, &attcred->pubkey.es256) < 0) { + fido_log_debug("%s: check_es256_pubarea", __func__); + return -1; + } + break; + case COSE_RS256: + if (check_rs256_pubarea(pubarea, &attcred->pubkey.rs256) < 0) { + fido_log_debug("%s: check_rs256_pubarea", __func__); + return -1; + } + break; + default: + fido_log_debug("%s: unsupported type %d", __func__, + attcred->type); + return -1; + } + + if (check_sha1_certinfo(certinfo, clientdata_hash, authdata_raw, + pubarea) < 0) { + fido_log_debug("%s: check_sha1_certinfo", __func__); + return -1; + } + + if (dgst->len < SHA_DIGEST_LENGTH || + SHA1(certinfo->ptr, certinfo->len, dgst->ptr) != dgst->ptr) { + fido_log_debug("%s: sha1", __func__); + return -1; + } + dgst->len = SHA_DIGEST_LENGTH; + + return 0; +} diff --git a/src/types.c b/src/types.c new file mode 100644 index 0000000..f31f8da --- /dev/null +++ b/src/types.c @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2018-2022 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. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "fido.h" + +void +fido_str_array_free(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; +} + +void +fido_opt_array_free(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; + oa->len = 0; +} + +void +fido_byte_array_free(fido_byte_array_t *ba) +{ + free(ba->ptr); + + ba->ptr = NULL; + ba->len = 0; +} + +void +fido_algo_free(fido_algo_t *a) +{ + free(a->type); + a->type = NULL; + a->cose = 0; +} + +void +fido_algo_array_free(fido_algo_array_t *aa) +{ + for (size_t i = 0; i < aa->len; i++) + fido_algo_free(&aa->ptr[i]); + + free(aa->ptr); + aa->ptr = NULL; + aa->len = 0; +} + +void +fido_cert_array_free(fido_cert_array_t *ca) +{ + for (size_t i = 0; i < ca->len; i++) + free(ca->name[i]); + + free(ca->name); + free(ca->value); + ca->name = NULL; + ca->value = NULL; + ca->len = 0; +} + +int +fido_str_array_pack(fido_str_array_t *sa, const char * const *v, size_t n) +{ + if ((sa->ptr = calloc(n, sizeof(char *))) == NULL) { + fido_log_debug("%s: calloc", __func__); + return -1; + } + for (size_t i = 0; i < n; i++) { + if ((sa->ptr[i] = strdup(v[i])) == NULL) { + fido_log_debug("%s: strdup", __func__); + return -1; + } + sa->len++; + } + + return 0; +} diff --git a/src/u2f.c b/src/u2f.c new file mode 100644 index 0000000..b1f7bce --- /dev/null +++ b/src/u2f.c @@ -0,0 +1,959 @@ +/* + * Copyright (c) 2018-2022 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. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <openssl/sha.h> +#include <openssl/x509.h> + +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#include <errno.h> + +#include "fido.h" +#include "fido/es256.h" +#include "fallthrough.h" + +#define U2F_PACE_MS (100) + +#if defined(_MSC_VER) +static int +usleep(unsigned int usec) +{ + Sleep(usec / 1000); + + return (0); +} +#endif + +static int +delay_ms(unsigned int ms, int *ms_remain) +{ + if (*ms_remain > -1 && (unsigned int)*ms_remain < ms) + ms = (unsigned int)*ms_remain; + + if (ms > UINT_MAX / 1000) { + fido_log_debug("%s: ms=%u", __func__, ms); + return (-1); + } + + if (usleep(ms * 1000) < 0) { + fido_log_error(errno, "%s: usleep", __func__); + return (-1); + } + + if (*ms_remain > -1) + *ms_remain -= (int)ms; + + return (0); +} + +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 || + fido_buf_read(buf, len, sig->ptr, sig->len) < 0) { + fido_log_debug("%s: fido_buf_read", __func__); + fido_blob_reset(sig); + 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) { + fido_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) { + fido_log_debug("%s: d2i_X509", __func__); + goto fail; + } + + /* read accordingly */ + if ((x5c->ptr = calloc(1, x5c->len)) == NULL || + fido_buf_read(buf, len, x5c->ptr, x5c->len) < 0) { + fido_log_debug("%s: fido_buf_read", __func__); + goto fail; + } + + ok = 0; +fail: + if (cert != NULL) + X509_free(cert); + + if (ok < 0) + fido_blob_reset(x5c); + + 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) { + fido_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) { + fido_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) { + fido_log_debug("%s: cbor_serialize_alloc", __func__); + cbor_decref(&item); + return (-1); + } + + cbor_decref(&item); + + return (0); +} + +/* TODO: use u2f_get_touch_begin & u2f_get_touch_status instead */ +static int +send_dummy_register(fido_dev_t *dev, int *ms) +{ + iso7816_apdu_t *apdu = NULL; + unsigned char *reply = NULL; + unsigned char challenge[SHA256_DIGEST_LENGTH]; + unsigned char application[SHA256_DIGEST_LENGTH]; + int r; + + /* dummy challenge & application */ + memset(&challenge, 0xff, sizeof(challenge)); + memset(&application, 0xff, sizeof(application)); + + if ((apdu = iso7816_new(0, U2F_CMD_REGISTER, 0, 2 * + SHA256_DIGEST_LENGTH)) == NULL || + iso7816_add(apdu, &challenge, sizeof(challenge)) < 0 || + iso7816_add(apdu, &application, sizeof(application)) < 0) { + fido_log_debug("%s: iso7816", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + if ((reply = malloc(FIDO_MAXMSG)) == NULL) { + fido_log_debug("%s: malloc", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + do { + if (fido_tx(dev, CTAP_CMD_MSG, iso7816_ptr(apdu), + iso7816_len(apdu), ms) < 0) { + fido_log_debug("%s: fido_tx", __func__); + r = FIDO_ERR_TX; + goto fail; + } + if (fido_rx(dev, CTAP_CMD_MSG, reply, FIDO_MAXMSG, ms) < 2) { + fido_log_debug("%s: fido_rx", __func__); + r = FIDO_ERR_RX; + goto fail; + } + if (delay_ms(U2F_PACE_MS, ms) != 0) { + fido_log_debug("%s: delay_ms", __func__); + r = FIDO_ERR_RX; + goto fail; + } + } while (((reply[0] << 8) | reply[1]) == SW_CONDITIONS_NOT_SATISFIED); + + r = FIDO_OK; +fail: + iso7816_free(&apdu); + freezero(reply, FIDO_MAXMSG); + + return (r); +} + +static int +key_lookup(fido_dev_t *dev, const char *rp_id, const fido_blob_t *key_id, + int *found, int *ms) +{ + iso7816_apdu_t *apdu = NULL; + unsigned char *reply = NULL; + unsigned char challenge[SHA256_DIGEST_LENGTH]; + unsigned char rp_id_hash[SHA256_DIGEST_LENGTH]; + uint8_t key_id_len; + int r; + + if (key_id->len > UINT8_MAX || rp_id == NULL) { + fido_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) { + fido_log_debug("%s: sha256", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + key_id_len = (uint8_t)key_id->len; + + if ((apdu = iso7816_new(0, U2F_CMD_AUTH, U2F_AUTH_CHECK, (uint16_t)(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) { + fido_log_debug("%s: iso7816", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + if ((reply = malloc(FIDO_MAXMSG)) == NULL) { + fido_log_debug("%s: malloc", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + if (fido_tx(dev, CTAP_CMD_MSG, iso7816_ptr(apdu), + iso7816_len(apdu), ms) < 0) { + fido_log_debug("%s: fido_tx", __func__); + r = FIDO_ERR_TX; + goto fail; + } + if (fido_rx(dev, CTAP_CMD_MSG, reply, FIDO_MAXMSG, ms) != 2) { + fido_log_debug("%s: fido_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); + freezero(reply, FIDO_MAXMSG); + + 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) { + fido_log_debug("%s: unexpected sw", __func__); + return (FIDO_ERR_RX); + } + + len -= 2; + + if (fido_buf_read(&reply, &len, &flags, sizeof(flags)) < 0 || + fido_buf_read(&reply, &len, &sigcount, sizeof(sigcount)) < 0) { + fido_log_debug("%s: fido_buf_read", __func__); + return (FIDO_ERR_RX); + } + + if (sig_get(sig, &reply, &len) < 0) { + fido_log_debug("%s: sig_get", __func__); + return (FIDO_ERR_RX); + } + + if (authdata_fake(rp_id, flags, sigcount, ad) < 0) { + fido_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) +{ + iso7816_apdu_t *apdu = NULL; + unsigned char *reply = NULL; + unsigned char rp_id_hash[SHA256_DIGEST_LENGTH]; + 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) { + fido_log_debug("%s: sha256", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + key_id_len = (uint8_t)key_id->len; + + if ((apdu = iso7816_new(0, U2F_CMD_AUTH, U2F_AUTH_SIGN, (uint16_t)(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) { + fido_log_debug("%s: iso7816", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + if ((reply = malloc(FIDO_MAXMSG)) == NULL) { + fido_log_debug("%s: malloc", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + do { + if (fido_tx(dev, CTAP_CMD_MSG, iso7816_ptr(apdu), + iso7816_len(apdu), ms) < 0) { + fido_log_debug("%s: fido_tx", __func__); + r = FIDO_ERR_TX; + goto fail; + } + if ((reply_len = fido_rx(dev, CTAP_CMD_MSG, reply, + FIDO_MAXMSG, ms)) < 2) { + fido_log_debug("%s: fido_rx", __func__); + r = FIDO_ERR_RX; + goto fail; + } + if (delay_ms(U2F_PACE_MS, ms) != 0) { + fido_log_debug("%s: delay_ms", __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) { + fido_log_debug("%s: parse_auth_reply", __func__); + goto fail; + } + +fail: + iso7816_free(&apdu); + freezero(reply, FIDO_MAXMSG); + + 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) { + fido_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) { + fido_log_debug("%s: es256_pk_set", __func__); + goto fail; + } + + if ((pk_cbor = es256_pk_encode(pk, 0)) == NULL) { + fido_log_debug("%s: es256_pk_encode", __func__); + goto fail; + } + + if ((cbor_blob->len = cbor_serialize_alloc(pk_cbor, &cbor_blob->ptr, + &alloc_len)) != 77) { + fido_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_attstmt(int cose_alg, const fido_blob_t *x5c, + const fido_blob_t *sig, fido_blob_t *out) +{ + cbor_item_t *item = NULL; + cbor_item_t *x5c_cbor = NULL; + const uint8_t alg_cbor = (uint8_t)(-cose_alg - 1); + struct cbor_pair kv[3]; + size_t alloc_len; + int ok = -1; + + memset(&kv, 0, sizeof(kv)); + memset(out, 0, sizeof(*out)); + + if ((item = cbor_new_definite_map(3)) == NULL) { + fido_log_debug("%s: cbor_new_definite_map", __func__); + goto fail; + } + + if ((kv[0].key = cbor_build_string("alg")) == NULL || + (kv[0].value = cbor_build_negint8(alg_cbor)) == NULL || + !cbor_map_add(item, kv[0])) { + fido_log_debug("%s: alg", __func__); + goto fail; + } + + if ((kv[1].key = cbor_build_string("sig")) == NULL || + (kv[1].value = fido_blob_encode(sig)) == NULL || + !cbor_map_add(item, kv[1])) { + fido_log_debug("%s: sig", __func__); + goto fail; + } + + if ((kv[2].key = cbor_build_string("x5c")) == NULL || + (kv[2].value = cbor_new_definite_array(1)) == NULL || + (x5c_cbor = fido_blob_encode(x5c)) == NULL || + !cbor_array_push(kv[2].value, x5c_cbor) || + !cbor_map_add(item, kv[2])) { + fido_log_debug("%s: x5c", __func__); + goto fail; + } + + if ((out->len = cbor_serialize_alloc(item, &out->ptr, + &alloc_len)) == 0) { + fido_log_debug("%s: cbor_serialize_alloc", __func__); + goto fail; + } + + ok = 0; +fail: + if (item != NULL) + cbor_decref(&item); + if (x5c_cbor != NULL) + cbor_decref(&x5c_cbor); + + for (size_t i = 0; i < nitems(kv); i++) { + if (kv[i].key) + cbor_decref(&kv[i].key); + if (kv[i].value) + cbor_decref(&kv[i].value); + } + + 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) { + fido_log_debug("%s: NULL rp_id", __func__); + goto fail; + } + + if (cbor_blob_from_ec_point(pubkey, pubkey_len, &pk_blob) < 0) { + fido_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) { + fido_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 = htobe16(kh_len); + + len = authdata_blob.len = sizeof(authdata) + sizeof(attcred_raw) + + kh_len + pk_blob.len; + ptr = authdata_blob.ptr = calloc(1, authdata_blob.len); + + fido_log_debug("%s: ptr=%p, len=%zu", __func__, (void *)ptr, len); + + if (authdata_blob.ptr == NULL) + goto fail; + + if (fido_buf_write(&ptr, &len, &authdata, sizeof(authdata)) < 0 || + fido_buf_write(&ptr, &len, &attcred_raw, sizeof(attcred_raw)) < 0 || + fido_buf_write(&ptr, &len, kh, kh_len) < 0 || + fido_buf_write(&ptr, &len, pk_blob.ptr, pk_blob.len) < 0) { + fido_log_debug("%s: fido_buf_write", __func__); + goto fail; + } + + if ((authdata_cbor = fido_blob_encode(&authdata_blob)) == NULL) { + fido_log_debug("%s: fido_blob_encode", __func__); + goto fail; + } + + if ((out->len = cbor_serialize_alloc(authdata_cbor, &out->ptr, + &alloc_len)) == 0) { + fido_log_debug("%s: cbor_serialize_alloc", __func__); + goto fail; + } + + ok = 0; +fail: + if (authdata_cbor) + cbor_decref(&authdata_cbor); + + fido_blob_reset(&pk_blob); + fido_blob_reset(&authdata_blob); + + 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; + fido_blob_t stmt; + 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)); + memset(&stmt, 0, sizeof(stmt)); + r = FIDO_ERR_RX; + + /* status word */ + if (len < 2 || ((reply[len - 2] << 8) | reply[len - 1]) != SW_NO_ERROR) { + fido_log_debug("%s: unexpected sw", __func__); + goto fail; + } + + len -= 2; + + /* reserved byte */ + if (fido_buf_read(&reply, &len, &dummy, sizeof(dummy)) < 0 || + dummy != 0x05) { + fido_log_debug("%s: reserved byte", __func__); + goto fail; + } + + /* pubkey + key handle */ + if (fido_buf_read(&reply, &len, &pubkey, sizeof(pubkey)) < 0 || + fido_buf_read(&reply, &len, &kh_len, sizeof(kh_len)) < 0 || + (kh = calloc(1, kh_len)) == NULL || + fido_buf_read(&reply, &len, kh, kh_len) < 0) { + fido_log_debug("%s: fido_buf_read", __func__); + goto fail; + } + + /* x5c + sig */ + if (x5c_get(&x5c, &reply, &len) < 0 || + sig_get(&sig, &reply, &len) < 0) { + fido_log_debug("%s: x5c || sig", __func__); + goto fail; + } + + /* attstmt */ + if (encode_cred_attstmt(COSE_ES256, &x5c, &sig, &stmt) < 0) { + fido_log_debug("%s: encode_cred_attstmt", __func__); + goto fail; + } + + /* authdata */ + if (encode_cred_authdata(cred->rp.id, kh, kh_len, pubkey, + sizeof(pubkey), &ad) < 0) { + fido_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_attstmt(cred, stmt.ptr, stmt.len) != FIDO_OK) { + fido_log_debug("%s: fido_cred_set", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + r = FIDO_OK; +fail: + freezero(kh, kh_len); + fido_blob_reset(&x5c); + fido_blob_reset(&sig); + fido_blob_reset(&ad); + fido_blob_reset(&stmt); + + return (r); +} + +int +u2f_register(fido_dev_t *dev, fido_cred_t *cred, int *ms) +{ + iso7816_apdu_t *apdu = NULL; + unsigned char rp_id_hash[SHA256_DIGEST_LENGTH]; + unsigned char *reply = NULL; + int reply_len; + int found; + int r; + + if (cred->rk == FIDO_OPT_TRUE || cred->uv == FIDO_OPT_TRUE) { + fido_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) { + fido_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) { + fido_log_debug("%s: key_lookup", __func__); + return (r); + } + if (found) { + if ((r = send_dummy_register(dev, ms)) != FIDO_OK) { + fido_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) { + fido_log_debug("%s: sha256", __func__); + return (FIDO_ERR_INTERNAL); + } + + if ((apdu = iso7816_new(0, 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) { + fido_log_debug("%s: iso7816", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + if ((reply = malloc(FIDO_MAXMSG)) == NULL) { + fido_log_debug("%s: malloc", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + do { + if (fido_tx(dev, CTAP_CMD_MSG, iso7816_ptr(apdu), + iso7816_len(apdu), ms) < 0) { + fido_log_debug("%s: fido_tx", __func__); + r = FIDO_ERR_TX; + goto fail; + } + if ((reply_len = fido_rx(dev, CTAP_CMD_MSG, reply, + FIDO_MAXMSG, ms)) < 2) { + fido_log_debug("%s: fido_rx", __func__); + r = FIDO_ERR_RX; + goto fail; + } + if (delay_ms(U2F_PACE_MS, ms) != 0) { + fido_log_debug("%s: delay_ms", __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) { + fido_log_debug("%s: parse_register_reply", __func__); + goto fail; + } +fail: + iso7816_free(&apdu); + freezero(reply, FIDO_MAXMSG); + + 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) { + fido_log_debug("%s: key_lookup", __func__); + goto fail; + } + + if (!found) { + fido_log_debug("%s: not found", __func__); + r = FIDO_ERR_CREDENTIAL_EXCLUDED; + goto fail; + } + + if (fido_blob_set(&fa->stmt[idx].id, key_id->ptr, key_id->len) < 0) { + fido_log_debug("%s: fido_blob_set", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + if (fa->up == FIDO_OPT_FALSE) { + fido_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) { + fido_log_debug("%s: do_auth", __func__); + goto fail; + } + + if (fido_assert_set_authdata(fa, idx, ad.ptr, ad.len) != FIDO_OK || + fido_assert_set_sig(fa, idx, sig.ptr, sig.len) != FIDO_OK) { + fido_log_debug("%s: fido_assert_set", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + r = FIDO_OK; +fail: + fido_blob_reset(&sig); + fido_blob_reset(&ad); + + return (r); +} + +int +u2f_authenticate(fido_dev_t *dev, fido_assert_t *fa, int *ms) +{ + size_t nfound = 0; + size_t nauth_ok = 0; + int r; + + if (fa->uv == FIDO_OPT_TRUE || fa->allow_list.ptr == NULL) { + fido_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) { + fido_log_debug("%s: fido_assert_set_count", __func__); + return (r); + } + + for (size_t i = 0; i < fa->allow_list.len; i++) { + switch ((r = u2f_authenticate_single(dev, + &fa->allow_list.ptr[i], fa, nfound, ms))) { + case FIDO_OK: + nauth_ok++; + FALLTHROUGH + case FIDO_ERR_USER_PRESENCE_REQUIRED: + nfound++; + break; + default: + if (r != FIDO_ERR_CREDENTIAL_EXCLUDED) { + fido_log_debug("%s: u2f_authenticate_single", + __func__); + return (r); + } + /* ignore credentials that don't exist */ + } + } + + fa->stmt_len = nfound; + + if (nfound == 0) + return (FIDO_ERR_NO_CREDENTIALS); + if (nauth_ok == 0) + return (FIDO_ERR_USER_PRESENCE_REQUIRED); + + return (FIDO_OK); +} + +int +u2f_get_touch_begin(fido_dev_t *dev, int *ms) +{ + iso7816_apdu_t *apdu = NULL; + const char *clientdata = FIDO_DUMMY_CLIENTDATA; + const char *rp_id = FIDO_DUMMY_RP_ID; + unsigned char *reply = NULL; + unsigned char clientdata_hash[SHA256_DIGEST_LENGTH]; + unsigned char rp_id_hash[SHA256_DIGEST_LENGTH]; + int r; + + memset(&clientdata_hash, 0, sizeof(clientdata_hash)); + memset(&rp_id_hash, 0, sizeof(rp_id_hash)); + + if (SHA256((const void *)clientdata, strlen(clientdata), + clientdata_hash) != clientdata_hash || SHA256((const void *)rp_id, + strlen(rp_id), rp_id_hash) != rp_id_hash) { + fido_log_debug("%s: sha256", __func__); + return (FIDO_ERR_INTERNAL); + } + + if ((apdu = iso7816_new(0, U2F_CMD_REGISTER, 0, 2 * + SHA256_DIGEST_LENGTH)) == NULL || + iso7816_add(apdu, clientdata_hash, sizeof(clientdata_hash)) < 0 || + iso7816_add(apdu, rp_id_hash, sizeof(rp_id_hash)) < 0) { + fido_log_debug("%s: iso7816", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + if ((reply = malloc(FIDO_MAXMSG)) == NULL) { + fido_log_debug("%s: malloc", __func__); + r = FIDO_ERR_INTERNAL; + goto fail; + } + + if (dev->attr.flags & FIDO_CAP_WINK) { + fido_tx(dev, CTAP_CMD_WINK, NULL, 0, ms); + fido_rx(dev, CTAP_CMD_WINK, reply, FIDO_MAXMSG, ms); + } + + if (fido_tx(dev, CTAP_CMD_MSG, iso7816_ptr(apdu), + iso7816_len(apdu), ms) < 0) { + fido_log_debug("%s: fido_tx", __func__); + r = FIDO_ERR_TX; + goto fail; + } + + r = FIDO_OK; +fail: + iso7816_free(&apdu); + freezero(reply, FIDO_MAXMSG); + + return (r); +} + +int +u2f_get_touch_status(fido_dev_t *dev, int *touched, int *ms) +{ + unsigned char *reply; + int reply_len; + int r; + + if ((reply = malloc(FIDO_MAXMSG)) == NULL) { + fido_log_debug("%s: malloc", __func__); + r = FIDO_ERR_INTERNAL; + goto out; + } + + if ((reply_len = fido_rx(dev, CTAP_CMD_MSG, reply, FIDO_MAXMSG, + ms)) < 2) { + fido_log_debug("%s: fido_rx", __func__); + r = FIDO_OK; /* ignore */ + goto out; + } + + switch ((reply[reply_len - 2] << 8) | reply[reply_len - 1]) { + case SW_CONDITIONS_NOT_SATISFIED: + if ((r = u2f_get_touch_begin(dev, ms)) != FIDO_OK) { + fido_log_debug("%s: u2f_get_touch_begin", __func__); + goto out; + } + *touched = 0; + break; + case SW_NO_ERROR: + *touched = 1; + break; + default: + fido_log_debug("%s: unexpected sw", __func__); + r = FIDO_ERR_RX; + goto out; + } + + r = FIDO_OK; +out: + freezero(reply, FIDO_MAXMSG); + + return (r); +} diff --git a/src/util.c b/src/util.c new file mode 100644 index 0000000..25281bb --- /dev/null +++ b/src/util.c @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2022 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. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <errno.h> +#include <stdint.h> +#include <stdlib.h> + +#include "fido.h" + +int +fido_to_uint64(const char *str, int base, uint64_t *out) +{ + char *ep; + unsigned long long ull; + + errno = 0; + ull = strtoull(str, &ep, base); + if (str == ep || *ep != '\0') + return -1; + else if (ull == ULLONG_MAX && errno == ERANGE) + return -1; + else if (ull > UINT64_MAX) + return -1; + *out = (uint64_t)ull; + + return 0; +} diff --git a/src/webauthn.h b/src/webauthn.h new file mode 100644 index 0000000..e64236a --- /dev/null +++ b/src/webauthn.h @@ -0,0 +1,990 @@ +// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+#ifndef __WEBAUTHN_H_
+#define __WEBAUTHN_H_
+
+#pragma once
+
+#include <winapifamily.h>
+
+#ifdef _MSC_VER
+#pragma region Desktop Family or OneCore Family
+#endif
+#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP | WINAPI_PARTITION_SYSTEM)
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef WINAPI
+#define WINAPI __stdcall
+#endif
+
+#ifndef INITGUID
+#define INITGUID
+#include <guiddef.h>
+#undef INITGUID
+#else
+#include <guiddef.h>
+#endif
+
+//+------------------------------------------------------------------------------------------
+// API Version Information.
+// Caller should check for WebAuthNGetApiVersionNumber to check the presence of relevant APIs
+// and features for their usage.
+//-------------------------------------------------------------------------------------------
+
+#define WEBAUTHN_API_VERSION_1 1
+// WEBAUTHN_API_VERSION_1 : Baseline Version
+// Data Structures and their sub versions:
+// - WEBAUTHN_RP_ENTITY_INFORMATION : 1
+// - WEBAUTHN_USER_ENTITY_INFORMATION : 1
+// - WEBAUTHN_CLIENT_DATA : 1
+// - WEBAUTHN_COSE_CREDENTIAL_PARAMETER : 1
+// - WEBAUTHN_COSE_CREDENTIAL_PARAMETERS : Not Applicable
+// - WEBAUTHN_CREDENTIAL : 1
+// - WEBAUTHN_CREDENTIALS : Not Applicable
+// - WEBAUTHN_CREDENTIAL_EX : 1
+// - WEBAUTHN_CREDENTIAL_LIST : Not Applicable
+// - WEBAUTHN_EXTENSION : Not Applicable
+// - WEBAUTHN_EXTENSIONS : Not Applicable
+// - WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS : 3
+// - WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS : 4
+// - WEBAUTHN_COMMON_ATTESTATION : 1
+// - WEBAUTHN_CREDENTIAL_ATTESTATION : 3
+// - WEBAUTHN_ASSERTION : 1
+// Extensions:
+// - WEBAUTHN_EXTENSIONS_IDENTIFIER_HMAC_SECRET
+// APIs:
+// - WebAuthNGetApiVersionNumber
+// - WebAuthNIsUserVerifyingPlatformAuthenticatorAvailable
+// - WebAuthNAuthenticatorMakeCredential
+// - WebAuthNAuthenticatorGetAssertion
+// - WebAuthNFreeCredentialAttestation
+// - WebAuthNFreeAssertion
+// - WebAuthNGetCancellationId
+// - WebAuthNCancelCurrentOperation
+// - WebAuthNGetErrorName
+// - WebAuthNGetW3CExceptionDOMError
+
+#define WEBAUTHN_API_VERSION_2 2
+// WEBAUTHN_API_VERSION_2 : Delta From WEBAUTHN_API_VERSION_1
+// Added Extensions:
+// - WEBAUTHN_EXTENSIONS_IDENTIFIER_CRED_PROTECT
+//
+
+#define WEBAUTHN_API_VERSION_3 3
+// WEBAUTHN_API_VERSION_3 : Delta From WEBAUTHN_API_VERSION_2
+// Data Structures and their sub versions:
+// - WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS : 4
+// - WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS : 5
+// - WEBAUTHN_CREDENTIAL_ATTESTATION : 4
+// - WEBAUTHN_ASSERTION : 2
+// Added Extensions:
+// - WEBAUTHN_EXTENSIONS_IDENTIFIER_CRED_BLOB
+// - WEBAUTHN_EXTENSIONS_IDENTIFIER_MIN_PIN_LENGTH
+//
+
+#define WEBAUTHN_API_VERSION_4 4
+// WEBAUTHN_API_VERSION_4 : Delta From WEBAUTHN_API_VERSION_3
+// Data Structures and their sub versions:
+// - WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS : 5
+// - WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS : 6
+// - WEBAUTHN_ASSERTION : 3
+// APIs:
+// - WebAuthNGetPlatformCredentialList
+// - WebAuthNFreePlatformCredentialList
+//
+
+#define WEBAUTHN_API_CURRENT_VERSION WEBAUTHN_API_VERSION_4
+
+//+------------------------------------------------------------------------------------------
+// Information about an RP Entity
+//-------------------------------------------------------------------------------------------
+
+#define WEBAUTHN_RP_ENTITY_INFORMATION_CURRENT_VERSION 1
+
+typedef struct _WEBAUTHN_RP_ENTITY_INFORMATION {
+ // Version of this structure, to allow for modifications in the future.
+ // This field is required and should be set to CURRENT_VERSION above.
+ DWORD dwVersion;
+
+ // Identifier for the RP. This field is required.
+ PCWSTR pwszId;
+
+ // Contains the friendly name of the Relying Party, such as "Acme Corporation", "Widgets Inc" or "Awesome Site".
+ // This field is required.
+ PCWSTR pwszName;
+
+ // Optional URL pointing to RP's logo.
+ PCWSTR pwszIcon;
+} WEBAUTHN_RP_ENTITY_INFORMATION, *PWEBAUTHN_RP_ENTITY_INFORMATION;
+typedef const WEBAUTHN_RP_ENTITY_INFORMATION *PCWEBAUTHN_RP_ENTITY_INFORMATION;
+
+//+------------------------------------------------------------------------------------------
+// Information about an User Entity
+//-------------------------------------------------------------------------------------------
+#define WEBAUTHN_MAX_USER_ID_LENGTH 64
+
+#define WEBAUTHN_USER_ENTITY_INFORMATION_CURRENT_VERSION 1
+
+typedef struct _WEBAUTHN_USER_ENTITY_INFORMATION {
+ // Version of this structure, to allow for modifications in the future.
+ // This field is required and should be set to CURRENT_VERSION above.
+ DWORD dwVersion;
+
+ // Identifier for the User. This field is required.
+ DWORD cbId;
+ _Field_size_bytes_(cbId)
+ PBYTE pbId;
+
+ // Contains a detailed name for this account, such as "john.p.smith@example.com".
+ PCWSTR pwszName;
+
+ // Optional URL that can be used to retrieve an image containing the user's current avatar,
+ // or a data URI that contains the image data.
+ PCWSTR pwszIcon;
+
+ // For User: Contains the friendly name associated with the user account by the Relying Party, such as "John P. Smith".
+ PCWSTR pwszDisplayName;
+} WEBAUTHN_USER_ENTITY_INFORMATION, *PWEBAUTHN_USER_ENTITY_INFORMATION;
+typedef const WEBAUTHN_USER_ENTITY_INFORMATION *PCWEBAUTHN_USER_ENTITY_INFORMATION;
+
+//+------------------------------------------------------------------------------------------
+// Information about client data.
+//-------------------------------------------------------------------------------------------
+
+#define WEBAUTHN_HASH_ALGORITHM_SHA_256 L"SHA-256"
+#define WEBAUTHN_HASH_ALGORITHM_SHA_384 L"SHA-384"
+#define WEBAUTHN_HASH_ALGORITHM_SHA_512 L"SHA-512"
+
+#define WEBAUTHN_CLIENT_DATA_CURRENT_VERSION 1
+
+typedef struct _WEBAUTHN_CLIENT_DATA {
+ // Version of this structure, to allow for modifications in the future.
+ // This field is required and should be set to CURRENT_VERSION above.
+ DWORD dwVersion;
+
+ // Size of the pbClientDataJSON field.
+ DWORD cbClientDataJSON;
+ // UTF-8 encoded JSON serialization of the client data.
+ _Field_size_bytes_(cbClientDataJSON)
+ PBYTE pbClientDataJSON;
+
+ // Hash algorithm ID used to hash the pbClientDataJSON field.
+ LPCWSTR pwszHashAlgId;
+} WEBAUTHN_CLIENT_DATA, *PWEBAUTHN_CLIENT_DATA;
+typedef const WEBAUTHN_CLIENT_DATA *PCWEBAUTHN_CLIENT_DATA;
+
+//+------------------------------------------------------------------------------------------
+// Information about credential parameters.
+//-------------------------------------------------------------------------------------------
+
+#define WEBAUTHN_CREDENTIAL_TYPE_PUBLIC_KEY L"public-key"
+
+#define WEBAUTHN_COSE_ALGORITHM_ECDSA_P256_WITH_SHA256 -7
+#define WEBAUTHN_COSE_ALGORITHM_ECDSA_P384_WITH_SHA384 -35
+#define WEBAUTHN_COSE_ALGORITHM_ECDSA_P521_WITH_SHA512 -36
+
+#define WEBAUTHN_COSE_ALGORITHM_RSASSA_PKCS1_V1_5_WITH_SHA256 -257
+#define WEBAUTHN_COSE_ALGORITHM_RSASSA_PKCS1_V1_5_WITH_SHA384 -258
+#define WEBAUTHN_COSE_ALGORITHM_RSASSA_PKCS1_V1_5_WITH_SHA512 -259
+
+#define WEBAUTHN_COSE_ALGORITHM_RSA_PSS_WITH_SHA256 -37
+#define WEBAUTHN_COSE_ALGORITHM_RSA_PSS_WITH_SHA384 -38
+#define WEBAUTHN_COSE_ALGORITHM_RSA_PSS_WITH_SHA512 -39
+
+#define WEBAUTHN_COSE_CREDENTIAL_PARAMETER_CURRENT_VERSION 1
+
+typedef struct _WEBAUTHN_COSE_CREDENTIAL_PARAMETER {
+ // Version of this structure, to allow for modifications in the future.
+ DWORD dwVersion;
+
+ // Well-known credential type specifying a credential to create.
+ LPCWSTR pwszCredentialType;
+
+ // Well-known COSE algorithm specifying the algorithm to use for the credential.
+ LONG lAlg;
+} WEBAUTHN_COSE_CREDENTIAL_PARAMETER, *PWEBAUTHN_COSE_CREDENTIAL_PARAMETER;
+typedef const WEBAUTHN_COSE_CREDENTIAL_PARAMETER *PCWEBAUTHN_COSE_CREDENTIAL_PARAMETER;
+
+typedef struct _WEBAUTHN_COSE_CREDENTIAL_PARAMETERS {
+ DWORD cCredentialParameters;
+ _Field_size_(cCredentialParameters)
+ PWEBAUTHN_COSE_CREDENTIAL_PARAMETER pCredentialParameters;
+} WEBAUTHN_COSE_CREDENTIAL_PARAMETERS, *PWEBAUTHN_COSE_CREDENTIAL_PARAMETERS;
+typedef const WEBAUTHN_COSE_CREDENTIAL_PARAMETERS *PCWEBAUTHN_COSE_CREDENTIAL_PARAMETERS;
+
+//+------------------------------------------------------------------------------------------
+// Information about credential.
+//-------------------------------------------------------------------------------------------
+#define WEBAUTHN_CREDENTIAL_CURRENT_VERSION 1
+
+typedef struct _WEBAUTHN_CREDENTIAL {
+ // Version of this structure, to allow for modifications in the future.
+ DWORD dwVersion;
+
+ // Size of pbID.
+ DWORD cbId;
+ // Unique ID for this particular credential.
+ _Field_size_bytes_(cbId)
+ PBYTE pbId;
+
+ // Well-known credential type specifying what this particular credential is.
+ LPCWSTR pwszCredentialType;
+} WEBAUTHN_CREDENTIAL, *PWEBAUTHN_CREDENTIAL;
+typedef const WEBAUTHN_CREDENTIAL *PCWEBAUTHN_CREDENTIAL;
+
+typedef struct _WEBAUTHN_CREDENTIALS {
+ DWORD cCredentials;
+ _Field_size_(cCredentials)
+ PWEBAUTHN_CREDENTIAL pCredentials;
+} WEBAUTHN_CREDENTIALS, *PWEBAUTHN_CREDENTIALS;
+typedef const WEBAUTHN_CREDENTIALS *PCWEBAUTHN_CREDENTIALS;
+
+//+------------------------------------------------------------------------------------------
+// Information about credential with extra information, such as, dwTransports
+//-------------------------------------------------------------------------------------------
+
+#define WEBAUTHN_CTAP_TRANSPORT_USB 0x00000001
+#define WEBAUTHN_CTAP_TRANSPORT_NFC 0x00000002
+#define WEBAUTHN_CTAP_TRANSPORT_BLE 0x00000004
+#define WEBAUTHN_CTAP_TRANSPORT_TEST 0x00000008
+#define WEBAUTHN_CTAP_TRANSPORT_INTERNAL 0x00000010
+#define WEBAUTHN_CTAP_TRANSPORT_FLAGS_MASK 0x0000001F
+
+#define WEBAUTHN_CREDENTIAL_EX_CURRENT_VERSION 1
+
+typedef struct _WEBAUTHN_CREDENTIAL_EX {
+ // Version of this structure, to allow for modifications in the future.
+ DWORD dwVersion;
+
+ // Size of pbID.
+ DWORD cbId;
+ // Unique ID for this particular credential.
+ _Field_size_bytes_(cbId)
+ PBYTE pbId;
+
+ // Well-known credential type specifying what this particular credential is.
+ LPCWSTR pwszCredentialType;
+
+ // Transports. 0 implies no transport restrictions.
+ DWORD dwTransports;
+} WEBAUTHN_CREDENTIAL_EX, *PWEBAUTHN_CREDENTIAL_EX;
+typedef const WEBAUTHN_CREDENTIAL_EX *PCWEBAUTHN_CREDENTIAL_EX;
+
+//+------------------------------------------------------------------------------------------
+// Information about credential list with extra information
+//-------------------------------------------------------------------------------------------
+
+typedef struct _WEBAUTHN_CREDENTIAL_LIST {
+ DWORD cCredentials;
+ _Field_size_(cCredentials)
+ PWEBAUTHN_CREDENTIAL_EX *ppCredentials;
+} WEBAUTHN_CREDENTIAL_LIST, *PWEBAUTHN_CREDENTIAL_LIST;
+typedef const WEBAUTHN_CREDENTIAL_LIST *PCWEBAUTHN_CREDENTIAL_LIST;
+
+//+------------------------------------------------------------------------------------------
+// Credential Information for WebAuthNGetPlatformCredentialList API
+//-------------------------------------------------------------------------------------------
+
+#define WEBAUTHN_CREDENTIAL_DETAILS_VERSION_1 1
+#define WEBAUTHN_CREDENTIAL_DETAILS_CURRENT_VERSION WEBAUTHN_CREDENTIAL_DETAILS_VERSION_1
+
+typedef struct _WEBAUTHN_CREDENTIAL_DETAILS {
+ // Version of this structure, to allow for modifications in the future.
+ DWORD dwVersion;
+
+ // Size of pbCredentialID.
+ DWORD cbCredentialID;
+ _Field_size_bytes_(cbCredentialID)
+ PBYTE pbCredentialID;
+
+ // RP Info
+ PWEBAUTHN_RP_ENTITY_INFORMATION pRpInformation;
+
+ // User Info
+ PWEBAUTHN_USER_ENTITY_INFORMATION pUserInformation;
+} WEBAUTHN_CREDENTIAL_DETAILS, *PWEBAUTHN_CREDENTIAL_DETAILS;
+typedef const WEBAUTHN_CREDENTIAL_DETAILS *PCWEBAUTHN_CREDENTIAL_DETAILS;
+
+typedef struct _WEBAUTHN_CREDENTIAL_DETAILS_LIST {
+ DWORD cCredentialDetails;
+ _Field_size_(cCredentialDetails)
+ PWEBAUTHN_CREDENTIAL_DETAILS *ppCredentialDetails;
+} WEBAUTHN_CREDENTIAL_DETAILS_LIST, *PWEBAUTHN_CREDENTIAL_DETAILS_LIST;
+typedef const WEBAUTHN_CREDENTIAL_DETAILS_LIST *PCWEBAUTHN_CREDENTIAL_DETAILS_LIST;
+
+#define WEBAUTHN_GET_CREDENTIALS_OPTIONS_VERSION_1 1
+#define WEBAUTHN_GET_CREDENTIALS_OPTIONS_CURRENT_VERSION WEBAUTHN_GET_CREDENTIALS_OPTIONS_VERSION_1
+
+typedef struct _WEBAUTHN_GET_CREDENTIALS_OPTIONS {
+ // Version of this structure, to allow for modifications in the future.
+ DWORD dwVersion;
+
+ // RPID
+ LPCWSTR pwszRpId;
+
+ // Optional. BrowserInPrivate Mode. Defaulting to FALSE.
+ BOOL bBrowserInPrivateMode;
+} WEBAUTHN_GET_CREDENTIALS_OPTIONS, *PWEBAUTHN_GET_CREDENTIALS_OPTIONS;
+typedef const WEBAUTHN_GET_CREDENTIALS_OPTIONS *PCWEBAUTHN_GET_CREDENTIALS_OPTIONS;
+
+//+------------------------------------------------------------------------------------------
+// PRF values.
+//-------------------------------------------------------------------------------------------
+
+#define WEBAUTHN_CTAP_ONE_HMAC_SECRET_LENGTH 32
+
+// SALT values below by default are converted into RAW Hmac-Secret values as per PRF extension.
+// - SHA-256(UTF8Encode("WebAuthn PRF") || 0x00 || Value)
+//
+// Set WEBAUTHN_CTAP_HMAC_SECRET_VALUES_FLAG in dwFlags in WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS,
+// if caller wants to provide RAW Hmac-Secret SALT values directly. In that case,
+// values if provided MUST be of WEBAUTHN_CTAP_ONE_HMAC_SECRET_LENGTH size.
+
+typedef struct _WEBAUTHN_HMAC_SECRET_SALT {
+ // Size of pbFirst.
+ DWORD cbFirst;
+ _Field_size_bytes_(cbFirst)
+ PBYTE pbFirst; // Required
+
+ // Size of pbSecond.
+ DWORD cbSecond;
+ _Field_size_bytes_(cbSecond)
+ PBYTE pbSecond;
+} WEBAUTHN_HMAC_SECRET_SALT, *PWEBAUTHN_HMAC_SECRET_SALT;
+typedef const WEBAUTHN_HMAC_SECRET_SALT *PCWEBAUTHN_HMAC_SECRET_SALT;
+
+typedef struct _WEBAUTHN_CRED_WITH_HMAC_SECRET_SALT {
+ // Size of pbCredID.
+ DWORD cbCredID;
+ _Field_size_bytes_(cbCredID)
+ PBYTE pbCredID; // Required
+
+ // PRF Values for above credential
+ PWEBAUTHN_HMAC_SECRET_SALT pHmacSecretSalt; // Required
+} WEBAUTHN_CRED_WITH_HMAC_SECRET_SALT, *PWEBAUTHN_CRED_WITH_HMAC_SECRET_SALT;
+typedef const WEBAUTHN_CRED_WITH_HMAC_SECRET_SALT *PCWEBAUTHN_CRED_WITH_HMAC_SECRET_SALT;
+
+typedef struct _WEBAUTHN_HMAC_SECRET_SALT_VALUES {
+ PWEBAUTHN_HMAC_SECRET_SALT pGlobalHmacSalt;
+
+ DWORD cCredWithHmacSecretSaltList;
+ _Field_size_(cCredWithHmacSecretSaltList)
+ PWEBAUTHN_CRED_WITH_HMAC_SECRET_SALT pCredWithHmacSecretSaltList;
+} WEBAUTHN_HMAC_SECRET_SALT_VALUES, *PWEBAUTHN_HMAC_SECRET_SALT_VALUES;
+typedef const WEBAUTHN_HMAC_SECRET_SALT_VALUES *PCWEBAUTHN_HMAC_SECRET_SALT_VALUES;
+
+//+------------------------------------------------------------------------------------------
+// Hmac-Secret extension
+//-------------------------------------------------------------------------------------------
+
+#define WEBAUTHN_EXTENSIONS_IDENTIFIER_HMAC_SECRET L"hmac-secret"
+// Below type definitions is for WEBAUTHN_EXTENSIONS_IDENTIFIER_HMAC_SECRET
+// MakeCredential Input Type: BOOL.
+// - pvExtension must point to a BOOL with the value TRUE.
+// - cbExtension must contain the sizeof(BOOL).
+// MakeCredential Output Type: BOOL.
+// - pvExtension will point to a BOOL with the value TRUE if credential
+// was successfully created with HMAC_SECRET.
+// - cbExtension will contain the sizeof(BOOL).
+// GetAssertion Input Type: Not Supported
+// GetAssertion Output Type: Not Supported
+
+//+------------------------------------------------------------------------------------------
+// credProtect extension
+//-------------------------------------------------------------------------------------------
+
+#define WEBAUTHN_USER_VERIFICATION_ANY 0
+#define WEBAUTHN_USER_VERIFICATION_OPTIONAL 1
+#define WEBAUTHN_USER_VERIFICATION_OPTIONAL_WITH_CREDENTIAL_ID_LIST 2
+#define WEBAUTHN_USER_VERIFICATION_REQUIRED 3
+
+typedef struct _WEBAUTHN_CRED_PROTECT_EXTENSION_IN {
+ // One of the above WEBAUTHN_USER_VERIFICATION_* values
+ DWORD dwCredProtect;
+ // Set the following to TRUE to require authenticator support for the credProtect extension
+ BOOL bRequireCredProtect;
+} WEBAUTHN_CRED_PROTECT_EXTENSION_IN, *PWEBAUTHN_CRED_PROTECT_EXTENSION_IN;
+typedef const WEBAUTHN_CRED_PROTECT_EXTENSION_IN *PCWEBAUTHN_CRED_PROTECT_EXTENSION_IN;
+
+
+#define WEBAUTHN_EXTENSIONS_IDENTIFIER_CRED_PROTECT L"credProtect"
+// Below type definitions is for WEBAUTHN_EXTENSIONS_IDENTIFIER_CRED_PROTECT
+// MakeCredential Input Type: WEBAUTHN_CRED_PROTECT_EXTENSION_IN.
+// - pvExtension must point to a WEBAUTHN_CRED_PROTECT_EXTENSION_IN struct
+// - cbExtension will contain the sizeof(WEBAUTHN_CRED_PROTECT_EXTENSION_IN).
+// MakeCredential Output Type: DWORD.
+// - pvExtension will point to a DWORD with one of the above WEBAUTHN_USER_VERIFICATION_* values
+// if credential was successfully created with CRED_PROTECT.
+// - cbExtension will contain the sizeof(DWORD).
+// GetAssertion Input Type: Not Supported
+// GetAssertion Output Type: Not Supported
+
+//+------------------------------------------------------------------------------------------
+// credBlob extension
+//-------------------------------------------------------------------------------------------
+
+typedef struct _WEBAUTHN_CRED_BLOB_EXTENSION {
+ // Size of pbCredBlob.
+ DWORD cbCredBlob;
+ _Field_size_bytes_(cbCredBlob)
+ PBYTE pbCredBlob;
+} WEBAUTHN_CRED_BLOB_EXTENSION, *PWEBAUTHN_CRED_BLOB_EXTENSION;
+typedef const WEBAUTHN_CRED_BLOB_EXTENSION *PCWEBAUTHN_CRED_BLOB_EXTENSION;
+
+
+#define WEBAUTHN_EXTENSIONS_IDENTIFIER_CRED_BLOB L"credBlob"
+// Below type definitions is for WEBAUTHN_EXTENSIONS_IDENTIFIER_CRED_BLOB
+// MakeCredential Input Type: WEBAUTHN_CRED_BLOB_EXTENSION.
+// - pvExtension must point to a WEBAUTHN_CRED_BLOB_EXTENSION struct
+// - cbExtension must contain the sizeof(WEBAUTHN_CRED_BLOB_EXTENSION).
+// MakeCredential Output Type: BOOL.
+// - pvExtension will point to a BOOL with the value TRUE if credBlob was successfully created
+// - cbExtension will contain the sizeof(BOOL).
+// GetAssertion Input Type: BOOL.
+// - pvExtension must point to a BOOL with the value TRUE to request the credBlob.
+// - cbExtension must contain the sizeof(BOOL).
+// GetAssertion Output Type: WEBAUTHN_CRED_BLOB_EXTENSION.
+// - pvExtension will point to a WEBAUTHN_CRED_BLOB_EXTENSION struct if the authenticator
+// returns the credBlob in the signed extensions
+// - cbExtension will contain the sizeof(WEBAUTHN_CRED_BLOB_EXTENSION).
+
+//+------------------------------------------------------------------------------------------
+// minPinLength extension
+//-------------------------------------------------------------------------------------------
+
+#define WEBAUTHN_EXTENSIONS_IDENTIFIER_MIN_PIN_LENGTH L"minPinLength"
+// Below type definitions is for WEBAUTHN_EXTENSIONS_IDENTIFIER_MIN_PIN_LENGTH
+// MakeCredential Input Type: BOOL.
+// - pvExtension must point to a BOOL with the value TRUE to request the minPinLength.
+// - cbExtension must contain the sizeof(BOOL).
+// MakeCredential Output Type: DWORD.
+// - pvExtension will point to a DWORD with the minimum pin length if returned by the authenticator
+// - cbExtension will contain the sizeof(DWORD).
+// GetAssertion Input Type: Not Supported
+// GetAssertion Output Type: Not Supported
+
+//+------------------------------------------------------------------------------------------
+// Information about Extensions.
+//-------------------------------------------------------------------------------------------
+typedef struct _WEBAUTHN_EXTENSION {
+ LPCWSTR pwszExtensionIdentifier;
+ DWORD cbExtension;
+ PVOID pvExtension;
+} WEBAUTHN_EXTENSION, *PWEBAUTHN_EXTENSION;
+typedef const WEBAUTHN_EXTENSION *PCWEBAUTHN_EXTENSION;
+
+typedef struct _WEBAUTHN_EXTENSIONS {
+ DWORD cExtensions;
+ _Field_size_(cExtensions)
+ PWEBAUTHN_EXTENSION pExtensions;
+} WEBAUTHN_EXTENSIONS, *PWEBAUTHN_EXTENSIONS;
+typedef const WEBAUTHN_EXTENSIONS *PCWEBAUTHN_EXTENSIONS;
+
+//+------------------------------------------------------------------------------------------
+// Options.
+//-------------------------------------------------------------------------------------------
+
+#define WEBAUTHN_AUTHENTICATOR_ATTACHMENT_ANY 0
+#define WEBAUTHN_AUTHENTICATOR_ATTACHMENT_PLATFORM 1
+#define WEBAUTHN_AUTHENTICATOR_ATTACHMENT_CROSS_PLATFORM 2
+#define WEBAUTHN_AUTHENTICATOR_ATTACHMENT_CROSS_PLATFORM_U2F_V2 3
+
+#define WEBAUTHN_USER_VERIFICATION_REQUIREMENT_ANY 0
+#define WEBAUTHN_USER_VERIFICATION_REQUIREMENT_REQUIRED 1
+#define WEBAUTHN_USER_VERIFICATION_REQUIREMENT_PREFERRED 2
+#define WEBAUTHN_USER_VERIFICATION_REQUIREMENT_DISCOURAGED 3
+
+#define WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_ANY 0
+#define WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_NONE 1
+#define WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_INDIRECT 2
+#define WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_DIRECT 3
+
+#define WEBAUTHN_ENTERPRISE_ATTESTATION_NONE 0
+#define WEBAUTHN_ENTERPRISE_ATTESTATION_VENDOR_FACILITATED 1
+#define WEBAUTHN_ENTERPRISE_ATTESTATION_PLATFORM_MANAGED 2
+
+#define WEBAUTHN_LARGE_BLOB_SUPPORT_NONE 0
+#define WEBAUTHN_LARGE_BLOB_SUPPORT_REQUIRED 1
+#define WEBAUTHN_LARGE_BLOB_SUPPORT_PREFERRED 2
+
+#define WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_1 1
+#define WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_2 2
+#define WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_3 3
+#define WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_4 4
+#define WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_5 5
+#define WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_CURRENT_VERSION WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_5
+
+typedef struct _WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS {
+ // Version of this structure, to allow for modifications in the future.
+ DWORD dwVersion;
+
+ // Time that the operation is expected to complete within.
+ // This is used as guidance, and can be overridden by the platform.
+ DWORD dwTimeoutMilliseconds;
+
+ // Credentials used for exclusion.
+ WEBAUTHN_CREDENTIALS CredentialList;
+
+ // Optional extensions to parse when performing the operation.
+ WEBAUTHN_EXTENSIONS Extensions;
+
+ // Optional. Platform vs Cross-Platform Authenticators.
+ DWORD dwAuthenticatorAttachment;
+
+ // Optional. Require key to be resident or not. Defaulting to FALSE.
+ BOOL bRequireResidentKey;
+
+ // User Verification Requirement.
+ DWORD dwUserVerificationRequirement;
+
+ // Attestation Conveyance Preference.
+ DWORD dwAttestationConveyancePreference;
+
+ // Reserved for future Use
+ DWORD dwFlags;
+
+ //
+ // The following fields have been added in WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_2
+ //
+
+ // Cancellation Id - Optional - See WebAuthNGetCancellationId
+ GUID *pCancellationId;
+
+ //
+ // The following fields have been added in WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_3
+ //
+
+ // Exclude Credential List. If present, "CredentialList" will be ignored.
+ PWEBAUTHN_CREDENTIAL_LIST pExcludeCredentialList;
+
+ //
+ // The following fields have been added in WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_4
+ //
+
+ // Enterprise Attestation
+ DWORD dwEnterpriseAttestation;
+
+ // Large Blob Support: none, required or preferred
+ //
+ // NTE_INVALID_PARAMETER when large blob required or preferred and
+ // bRequireResidentKey isn't set to TRUE
+ DWORD dwLargeBlobSupport;
+
+ // Optional. Prefer key to be resident. Defaulting to FALSE. When TRUE,
+ // overrides the above bRequireResidentKey.
+ BOOL bPreferResidentKey;
+
+ //
+ // The following fields have been added in WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_5
+ //
+
+ // Optional. BrowserInPrivate Mode. Defaulting to FALSE.
+ BOOL bBrowserInPrivateMode;
+
+} WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS, *PWEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS;
+typedef const WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS *PCWEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS;
+
+#define WEBAUTHN_CRED_LARGE_BLOB_OPERATION_NONE 0
+#define WEBAUTHN_CRED_LARGE_BLOB_OPERATION_GET 1
+#define WEBAUTHN_CRED_LARGE_BLOB_OPERATION_SET 2
+#define WEBAUTHN_CRED_LARGE_BLOB_OPERATION_DELETE 3
+
+#define WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_1 1
+#define WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_2 2
+#define WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_3 3
+#define WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_4 4
+#define WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_5 5
+#define WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_6 6
+#define WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_CURRENT_VERSION WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_6
+
+/*
+ Information about flags.
+*/
+
+#define WEBAUTHN_AUTHENTICATOR_HMAC_SECRET_VALUES_FLAG 0x00100000
+
+typedef struct _WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS {
+ // Version of this structure, to allow for modifications in the future.
+ DWORD dwVersion;
+
+ // Time that the operation is expected to complete within.
+ // This is used as guidance, and can be overridden by the platform.
+ DWORD dwTimeoutMilliseconds;
+
+ // Allowed Credentials List.
+ WEBAUTHN_CREDENTIALS CredentialList;
+
+ // Optional extensions to parse when performing the operation.
+ WEBAUTHN_EXTENSIONS Extensions;
+
+ // Optional. Platform vs Cross-Platform Authenticators.
+ DWORD dwAuthenticatorAttachment;
+
+ // User Verification Requirement.
+ DWORD dwUserVerificationRequirement;
+
+ // Flags
+ DWORD dwFlags;
+
+ //
+ // The following fields have been added in WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_2
+ //
+
+ // Optional identifier for the U2F AppId. Converted to UTF8 before being hashed. Not lower cased.
+ PCWSTR pwszU2fAppId;
+
+ // If the following is non-NULL, then, set to TRUE if the above pwszU2fAppid was used instead of
+ // PCWSTR pwszRpId;
+ BOOL *pbU2fAppId;
+
+ //
+ // The following fields have been added in WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_3
+ //
+
+ // Cancellation Id - Optional - See WebAuthNGetCancellationId
+ GUID *pCancellationId;
+
+ //
+ // The following fields have been added in WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_4
+ //
+
+ // Allow Credential List. If present, "CredentialList" will be ignored.
+ PWEBAUTHN_CREDENTIAL_LIST pAllowCredentialList;
+
+ //
+ // The following fields have been added in WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_5
+ //
+
+ DWORD dwCredLargeBlobOperation;
+
+ // Size of pbCredLargeBlob
+ DWORD cbCredLargeBlob;
+ _Field_size_bytes_(cbCredLargeBlob)
+ PBYTE pbCredLargeBlob;
+
+ //
+ // The following fields have been added in WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_6
+ //
+
+ // PRF values which will be converted into HMAC-SECRET values according to WebAuthn Spec.
+ PWEBAUTHN_HMAC_SECRET_SALT_VALUES pHmacSecretSaltValues;
+
+ // Optional. BrowserInPrivate Mode. Defaulting to FALSE.
+ BOOL bBrowserInPrivateMode;
+
+} WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS, *PWEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS;
+typedef const WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS *PCWEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS;
+
+
+//+------------------------------------------------------------------------------------------
+// Attestation Info.
+//
+//-------------------------------------------------------------------------------------------
+#define WEBAUTHN_ATTESTATION_DECODE_NONE 0
+#define WEBAUTHN_ATTESTATION_DECODE_COMMON 1
+// WEBAUTHN_ATTESTATION_DECODE_COMMON supports format types
+// L"packed"
+// L"fido-u2f"
+
+#define WEBAUTHN_ATTESTATION_VER_TPM_2_0 L"2.0"
+
+typedef struct _WEBAUTHN_X5C {
+ // Length of X.509 encoded certificate
+ DWORD cbData;
+ // X.509 encoded certificate bytes
+ _Field_size_bytes_(cbData)
+ PBYTE pbData;
+} WEBAUTHN_X5C, *PWEBAUTHN_X5C;
+
+// Supports either Self or Full Basic Attestation
+
+// Note, new fields will be added to the following data structure to
+// support additional attestation format types, such as, TPM.
+// When fields are added, the dwVersion will be incremented.
+//
+// Therefore, your code must make the following check:
+// "if (dwVersion >= WEBAUTHN_COMMON_ATTESTATION_CURRENT_VERSION)"
+
+#define WEBAUTHN_COMMON_ATTESTATION_CURRENT_VERSION 1
+
+typedef struct _WEBAUTHN_COMMON_ATTESTATION {
+ // Version of this structure, to allow for modifications in the future.
+ DWORD dwVersion;
+
+ // Hash and Padding Algorithm
+ //
+ // The following won't be set for "fido-u2f" which assumes "ES256".
+ PCWSTR pwszAlg;
+ LONG lAlg; // COSE algorithm
+
+ // Signature that was generated for this attestation.
+ DWORD cbSignature;
+ _Field_size_bytes_(cbSignature)
+ PBYTE pbSignature;
+
+ // Following is set for Full Basic Attestation. If not, set then, this is Self Attestation.
+ // Array of X.509 DER encoded certificates. The first certificate is the signer, leaf certificate.
+ DWORD cX5c;
+ _Field_size_(cX5c)
+ PWEBAUTHN_X5C pX5c;
+
+ // Following are also set for tpm
+ PCWSTR pwszVer; // L"2.0"
+ DWORD cbCertInfo;
+ _Field_size_bytes_(cbCertInfo)
+ PBYTE pbCertInfo;
+ DWORD cbPubArea;
+ _Field_size_bytes_(cbPubArea)
+ PBYTE pbPubArea;
+} WEBAUTHN_COMMON_ATTESTATION, *PWEBAUTHN_COMMON_ATTESTATION;
+typedef const WEBAUTHN_COMMON_ATTESTATION *PCWEBAUTHN_COMMON_ATTESTATION;
+
+#define WEBAUTHN_ATTESTATION_TYPE_PACKED L"packed"
+#define WEBAUTHN_ATTESTATION_TYPE_U2F L"fido-u2f"
+#define WEBAUTHN_ATTESTATION_TYPE_TPM L"tpm"
+#define WEBAUTHN_ATTESTATION_TYPE_NONE L"none"
+
+#define WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_1 1
+#define WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_2 2
+#define WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_3 3
+#define WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_4 4
+#define WEBAUTHN_CREDENTIAL_ATTESTATION_CURRENT_VERSION WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_4
+
+typedef struct _WEBAUTHN_CREDENTIAL_ATTESTATION {
+ // Version of this structure, to allow for modifications in the future.
+ DWORD dwVersion;
+
+ // Attestation format type
+ PCWSTR pwszFormatType;
+
+ // Size of cbAuthenticatorData.
+ DWORD cbAuthenticatorData;
+ // Authenticator data that was created for this credential.
+ _Field_size_bytes_(cbAuthenticatorData)
+ PBYTE pbAuthenticatorData;
+
+ // Size of CBOR encoded attestation information
+ //0 => encoded as CBOR null value.
+ DWORD cbAttestation;
+ //Encoded CBOR attestation information
+ _Field_size_bytes_(cbAttestation)
+ PBYTE pbAttestation;
+
+ DWORD dwAttestationDecodeType;
+ // Following depends on the dwAttestationDecodeType
+ // WEBAUTHN_ATTESTATION_DECODE_NONE
+ // NULL - not able to decode the CBOR attestation information
+ // WEBAUTHN_ATTESTATION_DECODE_COMMON
+ // PWEBAUTHN_COMMON_ATTESTATION;
+ PVOID pvAttestationDecode;
+
+ // The CBOR encoded Attestation Object to be returned to the RP.
+ DWORD cbAttestationObject;
+ _Field_size_bytes_(cbAttestationObject)
+ PBYTE pbAttestationObject;
+
+ // The CredentialId bytes extracted from the Authenticator Data.
+ // Used by Edge to return to the RP.
+ DWORD cbCredentialId;
+ _Field_size_bytes_(cbCredentialId)
+ PBYTE pbCredentialId;
+
+ //
+ // Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_2
+ //
+
+ WEBAUTHN_EXTENSIONS Extensions;
+
+ //
+ // Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_3
+ //
+
+ // One of the WEBAUTHN_CTAP_TRANSPORT_* bits will be set corresponding to
+ // the transport that was used.
+ DWORD dwUsedTransport;
+
+ //
+ // Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_4
+ //
+
+ BOOL bEpAtt;
+ BOOL bLargeBlobSupported;
+ BOOL bResidentKey;
+
+} WEBAUTHN_CREDENTIAL_ATTESTATION, *PWEBAUTHN_CREDENTIAL_ATTESTATION;
+typedef const WEBAUTHN_CREDENTIAL_ATTESTATION *PCWEBAUTHN_CREDENTIAL_ATTESTATION;
+
+
+//+------------------------------------------------------------------------------------------
+// authenticatorGetAssertion output.
+//-------------------------------------------------------------------------------------------
+
+#define WEBAUTHN_CRED_LARGE_BLOB_STATUS_NONE 0
+#define WEBAUTHN_CRED_LARGE_BLOB_STATUS_SUCCESS 1
+#define WEBAUTHN_CRED_LARGE_BLOB_STATUS_NOT_SUPPORTED 2
+#define WEBAUTHN_CRED_LARGE_BLOB_STATUS_INVALID_DATA 3
+#define WEBAUTHN_CRED_LARGE_BLOB_STATUS_INVALID_PARAMETER 4
+#define WEBAUTHN_CRED_LARGE_BLOB_STATUS_NOT_FOUND 5
+#define WEBAUTHN_CRED_LARGE_BLOB_STATUS_MULTIPLE_CREDENTIALS 6
+#define WEBAUTHN_CRED_LARGE_BLOB_STATUS_LACK_OF_SPACE 7
+#define WEBAUTHN_CRED_LARGE_BLOB_STATUS_PLATFORM_ERROR 8
+#define WEBAUTHN_CRED_LARGE_BLOB_STATUS_AUTHENTICATOR_ERROR 9
+
+#define WEBAUTHN_ASSERTION_VERSION_1 1
+#define WEBAUTHN_ASSERTION_VERSION_2 2
+#define WEBAUTHN_ASSERTION_VERSION_3 3
+#define WEBAUTHN_ASSERTION_CURRENT_VERSION WEBAUTHN_ASSERTION_VERSION_3
+
+typedef struct _WEBAUTHN_ASSERTION {
+ // Version of this structure, to allow for modifications in the future.
+ DWORD dwVersion;
+
+ // Size of cbAuthenticatorData.
+ DWORD cbAuthenticatorData;
+ // Authenticator data that was created for this assertion.
+ _Field_size_bytes_(cbAuthenticatorData)
+ PBYTE pbAuthenticatorData;
+
+ // Size of pbSignature.
+ DWORD cbSignature;
+ // Signature that was generated for this assertion.
+ _Field_size_bytes_(cbSignature)
+ PBYTE pbSignature;
+
+ // Credential that was used for this assertion.
+ WEBAUTHN_CREDENTIAL Credential;
+
+ // Size of User Id
+ DWORD cbUserId;
+ // UserId
+ _Field_size_bytes_(cbUserId)
+ PBYTE pbUserId;
+
+ //
+ // Following fields have been added in WEBAUTHN_ASSERTION_VERSION_2
+ //
+
+ WEBAUTHN_EXTENSIONS Extensions;
+
+ // Size of pbCredLargeBlob
+ DWORD cbCredLargeBlob;
+ _Field_size_bytes_(cbCredLargeBlob)
+ PBYTE pbCredLargeBlob;
+
+ DWORD dwCredLargeBlobStatus;
+
+ //
+ // Following fields have been added in WEBAUTHN_ASSERTION_VERSION_3
+ //
+
+ PWEBAUTHN_HMAC_SECRET_SALT pHmacSecret;
+
+} WEBAUTHN_ASSERTION, *PWEBAUTHN_ASSERTION;
+typedef const WEBAUTHN_ASSERTION *PCWEBAUTHN_ASSERTION;
+
+//+------------------------------------------------------------------------------------------
+// APIs.
+//-------------------------------------------------------------------------------------------
+
+DWORD
+WINAPI
+WebAuthNGetApiVersionNumber();
+
+HRESULT
+WINAPI
+WebAuthNIsUserVerifyingPlatformAuthenticatorAvailable(
+ _Out_ BOOL *pbIsUserVerifyingPlatformAuthenticatorAvailable);
+
+
+HRESULT
+WINAPI
+WebAuthNAuthenticatorMakeCredential(
+ _In_ HWND hWnd,
+ _In_ PCWEBAUTHN_RP_ENTITY_INFORMATION pRpInformation,
+ _In_ PCWEBAUTHN_USER_ENTITY_INFORMATION pUserInformation,
+ _In_ PCWEBAUTHN_COSE_CREDENTIAL_PARAMETERS pPubKeyCredParams,
+ _In_ PCWEBAUTHN_CLIENT_DATA pWebAuthNClientData,
+ _In_opt_ PCWEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS pWebAuthNMakeCredentialOptions,
+ _Outptr_result_maybenull_ PWEBAUTHN_CREDENTIAL_ATTESTATION *ppWebAuthNCredentialAttestation);
+
+
+HRESULT
+WINAPI
+WebAuthNAuthenticatorGetAssertion(
+ _In_ HWND hWnd,
+ _In_ LPCWSTR pwszRpId,
+ _In_ PCWEBAUTHN_CLIENT_DATA pWebAuthNClientData,
+ _In_opt_ PCWEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS pWebAuthNGetAssertionOptions,
+ _Outptr_result_maybenull_ PWEBAUTHN_ASSERTION *ppWebAuthNAssertion);
+
+void
+WINAPI
+WebAuthNFreeCredentialAttestation(
+ _In_opt_ PWEBAUTHN_CREDENTIAL_ATTESTATION pWebAuthNCredentialAttestation);
+
+void
+WINAPI
+WebAuthNFreeAssertion(
+ _In_ PWEBAUTHN_ASSERTION pWebAuthNAssertion);
+
+HRESULT
+WINAPI
+WebAuthNGetCancellationId(
+ _Out_ GUID* pCancellationId);
+
+HRESULT
+WINAPI
+WebAuthNCancelCurrentOperation(
+ _In_ const GUID* pCancellationId);
+
+HRESULT
+WINAPI
+WebAuthNGetPlatformCredentialList(
+ _In_ PCWEBAUTHN_GET_CREDENTIALS_OPTIONS pGetCredentialsOptions,
+ _Outptr_result_maybenull_ PWEBAUTHN_CREDENTIAL_DETAILS_LIST *ppCredentialDetailsList);
+
+void
+WINAPI
+WebAuthNFreePlatformCredentialList(
+ _In_ PWEBAUTHN_CREDENTIAL_DETAILS_LIST pCredentialDetailsList);
+
+//
+// Returns the following Error Names:
+// L"Success" - S_OK
+// L"InvalidStateError" - NTE_EXISTS
+// L"ConstraintError" - HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED),
+// NTE_NOT_SUPPORTED,
+// NTE_TOKEN_KEYSET_STORAGE_FULL
+// L"NotSupportedError" - NTE_INVALID_PARAMETER
+// L"NotAllowedError" - NTE_DEVICE_NOT_FOUND,
+// NTE_NOT_FOUND,
+// HRESULT_FROM_WIN32(ERROR_CANCELLED),
+// NTE_USER_CANCELLED,
+// HRESULT_FROM_WIN32(ERROR_TIMEOUT)
+// L"UnknownError" - All other hr values
+//
+PCWSTR
+WINAPI
+WebAuthNGetErrorName(
+ _In_ HRESULT hr);
+
+HRESULT
+WINAPI
+WebAuthNGetW3CExceptionDOMError(
+ _In_ HRESULT hr);
+
+
+#ifdef __cplusplus
+} // Balance extern "C" above
+#endif
+
+#endif // WINAPI_FAMILY_PARTITION
+#ifdef _MSC_VER
+#pragma endregion
+#endif
+
+#endif // __WEBAUTHN_H_
diff --git a/src/winhello.c b/src/winhello.c new file mode 100644 index 0000000..e0453cb --- /dev/null +++ b/src/winhello.c @@ -0,0 +1,1036 @@ +/* + * Copyright (c) 2021-2022 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. + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <sys/types.h> + +#include <stdlib.h> +#include <windows.h> + +#include "fido.h" +#include "webauthn.h" + +#ifndef NTE_INVALID_PARAMETER +#define NTE_INVALID_PARAMETER _HRESULT_TYPEDEF_(0x80090027) +#endif +#ifndef NTE_NOT_SUPPORTED +#define NTE_NOT_SUPPORTED _HRESULT_TYPEDEF_(0x80090029) +#endif +#ifndef NTE_DEVICE_NOT_FOUND +#define NTE_DEVICE_NOT_FOUND _HRESULT_TYPEDEF_(0x80090035) +#endif + +#define MAXCHARS 128 +#define MAXCREDS 128 +#define MAXMSEC 6000 * 1000 +#define VENDORID 0x045e +#define PRODID 0x0001 + +struct winhello_assert { + WEBAUTHN_CLIENT_DATA cd; + WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS opt; + WEBAUTHN_ASSERTION *assert; + wchar_t *rp_id; +}; + +struct winhello_cred { + WEBAUTHN_RP_ENTITY_INFORMATION rp; + WEBAUTHN_USER_ENTITY_INFORMATION user; + WEBAUTHN_COSE_CREDENTIAL_PARAMETER alg; + WEBAUTHN_COSE_CREDENTIAL_PARAMETERS cose; + WEBAUTHN_CLIENT_DATA cd; + WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS opt; + WEBAUTHN_CREDENTIAL_ATTESTATION *att; + wchar_t *rp_id; + wchar_t *rp_name; + wchar_t *user_name; + wchar_t *user_icon; + wchar_t *display_name; +}; + +typedef DWORD WINAPI webauthn_get_api_version_t(void); +typedef PCWSTR WINAPI webauthn_strerr_t(HRESULT); +typedef HRESULT WINAPI webauthn_get_assert_t(HWND, LPCWSTR, + PCWEBAUTHN_CLIENT_DATA, + PCWEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS, + PWEBAUTHN_ASSERTION *); +typedef HRESULT WINAPI webauthn_make_cred_t(HWND, + PCWEBAUTHN_RP_ENTITY_INFORMATION, + PCWEBAUTHN_USER_ENTITY_INFORMATION, + PCWEBAUTHN_COSE_CREDENTIAL_PARAMETERS, + PCWEBAUTHN_CLIENT_DATA, + PCWEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS, + PWEBAUTHN_CREDENTIAL_ATTESTATION *); +typedef void WINAPI webauthn_free_assert_t(PWEBAUTHN_ASSERTION); +typedef void WINAPI webauthn_free_attest_t(PWEBAUTHN_CREDENTIAL_ATTESTATION); + +static TLS BOOL webauthn_loaded; +static TLS HMODULE webauthn_handle; +static TLS webauthn_get_api_version_t *webauthn_get_api_version; +static TLS webauthn_strerr_t *webauthn_strerr; +static TLS webauthn_get_assert_t *webauthn_get_assert; +static TLS webauthn_make_cred_t *webauthn_make_cred; +static TLS webauthn_free_assert_t *webauthn_free_assert; +static TLS webauthn_free_attest_t *webauthn_free_attest; + +static int +webauthn_load(void) +{ + DWORD n = 1; + + if (webauthn_loaded || webauthn_handle != NULL) { + fido_log_debug("%s: already loaded", __func__); + return -1; + } + if ((webauthn_handle = LoadLibrary("webauthn.dll")) == NULL) { + fido_log_debug("%s: LoadLibrary", __func__); + return -1; + } + + if ((webauthn_get_api_version = + (webauthn_get_api_version_t *)GetProcAddress(webauthn_handle, + "WebAuthNGetApiVersionNumber")) == NULL) { + fido_log_debug("%s: WebAuthNGetApiVersionNumber", __func__); + /* WebAuthNGetApiVersionNumber might not exist */ + } + if (webauthn_get_api_version != NULL && + (n = webauthn_get_api_version()) < 1) { + fido_log_debug("%s: unsupported api %lu", __func__, (u_long)n); + goto fail; + } + fido_log_debug("%s: api version %lu", __func__, (u_long)n); + if ((webauthn_strerr = + (webauthn_strerr_t *)GetProcAddress(webauthn_handle, + "WebAuthNGetErrorName")) == NULL) { + fido_log_debug("%s: WebAuthNGetErrorName", __func__); + goto fail; + } + if ((webauthn_get_assert = + (webauthn_get_assert_t *)GetProcAddress(webauthn_handle, + "WebAuthNAuthenticatorGetAssertion")) == NULL) { + fido_log_debug("%s: WebAuthNAuthenticatorGetAssertion", + __func__); + goto fail; + } + if ((webauthn_make_cred = + (webauthn_make_cred_t *)GetProcAddress(webauthn_handle, + "WebAuthNAuthenticatorMakeCredential")) == NULL) { + fido_log_debug("%s: WebAuthNAuthenticatorMakeCredential", + __func__); + goto fail; + } + if ((webauthn_free_assert = + (webauthn_free_assert_t *)GetProcAddress(webauthn_handle, + "WebAuthNFreeAssertion")) == NULL) { + fido_log_debug("%s: WebAuthNFreeAssertion", __func__); + goto fail; + } + if ((webauthn_free_attest = + (webauthn_free_attest_t *)GetProcAddress(webauthn_handle, + "WebAuthNFreeCredentialAttestation")) == NULL) { + fido_log_debug("%s: WebAuthNFreeCredentialAttestation", + __func__); + goto fail; + } + + webauthn_loaded = true; + + return 0; +fail: + fido_log_debug("%s: GetProcAddress", __func__); + webauthn_get_api_version = NULL; + webauthn_strerr = NULL; + webauthn_get_assert = NULL; + webauthn_make_cred = NULL; + webauthn_free_assert = NULL; + webauthn_free_attest = NULL; + FreeLibrary(webauthn_handle); + webauthn_handle = NULL; + + return -1; +} + +static wchar_t * +to_utf16(const char *utf8) +{ + int nch; + wchar_t *utf16; + + if (utf8 == NULL) { + fido_log_debug("%s: NULL", __func__); + return NULL; + } + if ((nch = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, NULL, 0)) < 1 || + (size_t)nch > MAXCHARS) { + fido_log_debug("%s: MultiByteToWideChar %d", __func__, nch); + return NULL; + } + if ((utf16 = calloc((size_t)nch, sizeof(*utf16))) == NULL) { + fido_log_debug("%s: calloc", __func__); + return NULL; + } + if (MultiByteToWideChar(CP_UTF8, 0, utf8, -1, utf16, nch) != nch) { + fido_log_debug("%s: MultiByteToWideChar", __func__); + free(utf16); + return NULL; + } + + return utf16; +} + +static int +to_fido(HRESULT hr) +{ + switch (hr) { + case NTE_NOT_SUPPORTED: + return FIDO_ERR_UNSUPPORTED_OPTION; + case NTE_INVALID_PARAMETER: + return FIDO_ERR_INVALID_PARAMETER; + case NTE_TOKEN_KEYSET_STORAGE_FULL: + return FIDO_ERR_KEY_STORE_FULL; + case NTE_DEVICE_NOT_FOUND: + case NTE_NOT_FOUND: + return FIDO_ERR_NOT_ALLOWED; + default: + fido_log_debug("%s: hr=0x%lx", __func__, (u_long)hr); + return FIDO_ERR_INTERNAL; + } +} + +static int +pack_cd(WEBAUTHN_CLIENT_DATA *out, const fido_blob_t *in) +{ + if (in->ptr == NULL) { + fido_log_debug("%s: NULL", __func__); + return -1; + } + if (in->len > ULONG_MAX) { + fido_log_debug("%s: in->len=%zu", __func__, in->len); + return -1; + } + out->dwVersion = WEBAUTHN_CLIENT_DATA_CURRENT_VERSION; + out->cbClientDataJSON = (DWORD)in->len; + out->pbClientDataJSON = in->ptr; + out->pwszHashAlgId = WEBAUTHN_HASH_ALGORITHM_SHA_256; + + return 0; +} + +static int +pack_credlist(WEBAUTHN_CREDENTIALS *out, const fido_blob_array_t *in) +{ + WEBAUTHN_CREDENTIAL *c; + + if (in->len == 0) { + return 0; /* nothing to do */ + } + if (in->len > MAXCREDS) { + fido_log_debug("%s: in->len=%zu", __func__, in->len); + return -1; + } + if ((out->pCredentials = calloc(in->len, sizeof(*c))) == NULL) { + fido_log_debug("%s: calloc", __func__); + return -1; + } + out->cCredentials = (DWORD)in->len; + for (size_t i = 0; i < in->len; i++) { + if (in->ptr[i].len > ULONG_MAX) { + fido_log_debug("%s: %zu", __func__, in->ptr[i].len); + return -1; + } + c = &out->pCredentials[i]; + c->dwVersion = WEBAUTHN_CREDENTIAL_CURRENT_VERSION; + c->cbId = (DWORD)in->ptr[i].len; + c->pbId = in->ptr[i].ptr; + c->pwszCredentialType = WEBAUTHN_CREDENTIAL_TYPE_PUBLIC_KEY; + } + + return 0; +} + +static int +set_cred_uv(DWORD *out, fido_opt_t uv, const char *pin) +{ + if (pin) { + *out = WEBAUTHN_USER_VERIFICATION_REQUIREMENT_REQUIRED; + return 0; + } + + switch (uv) { + case FIDO_OPT_OMIT: + case FIDO_OPT_FALSE: + *out = WEBAUTHN_USER_VERIFICATION_REQUIREMENT_DISCOURAGED; + break; + case FIDO_OPT_TRUE: + *out = WEBAUTHN_USER_VERIFICATION_REQUIREMENT_REQUIRED; + break; + } + + return 0; +} + +static int +set_assert_uv(DWORD *out, fido_opt_t uv, const char *pin) +{ + if (pin) { + *out = WEBAUTHN_USER_VERIFICATION_REQUIREMENT_REQUIRED; + return 0; + } + + switch (uv) { + case FIDO_OPT_OMIT: + *out = WEBAUTHN_USER_VERIFICATION_REQUIREMENT_PREFERRED; + break; + case FIDO_OPT_FALSE: + *out = WEBAUTHN_USER_VERIFICATION_REQUIREMENT_DISCOURAGED; + break; + case FIDO_OPT_TRUE: + *out = WEBAUTHN_USER_VERIFICATION_REQUIREMENT_REQUIRED; + break; + } + + return 0; +} + +static int +pack_rp(wchar_t **id, wchar_t **name, WEBAUTHN_RP_ENTITY_INFORMATION *out, + const fido_rp_t *in) +{ + /* keep non-const copies of pwsz* for free() */ + out->dwVersion = WEBAUTHN_RP_ENTITY_INFORMATION_CURRENT_VERSION; + if ((out->pwszId = *id = to_utf16(in->id)) == NULL) { + fido_log_debug("%s: id", __func__); + return -1; + } + if (in->name && (out->pwszName = *name = to_utf16(in->name)) == NULL) { + fido_log_debug("%s: name", __func__); + return -1; + } + return 0; +} + +static int +pack_user(wchar_t **name, wchar_t **icon, wchar_t **display_name, + WEBAUTHN_USER_ENTITY_INFORMATION *out, const fido_user_t *in) +{ + if (in->id.ptr == NULL || in->id.len > ULONG_MAX) { + fido_log_debug("%s: id", __func__); + return -1; + } + out->dwVersion = WEBAUTHN_USER_ENTITY_INFORMATION_CURRENT_VERSION; + out->cbId = (DWORD)in->id.len; + out->pbId = in->id.ptr; + /* keep non-const copies of pwsz* for free() */ + if (in->name != NULL) { + if ((out->pwszName = *name = to_utf16(in->name)) == NULL) { + fido_log_debug("%s: name", __func__); + return -1; + } + } + if (in->icon != NULL) { + if ((out->pwszIcon = *icon = to_utf16(in->icon)) == NULL) { + fido_log_debug("%s: icon", __func__); + return -1; + } + } + if (in->display_name != NULL) { + if ((out->pwszDisplayName = *display_name = + to_utf16(in->display_name)) == NULL) { + fido_log_debug("%s: display_name", __func__); + return -1; + } + } + + return 0; +} + +static int +pack_cose(WEBAUTHN_COSE_CREDENTIAL_PARAMETER *alg, + WEBAUTHN_COSE_CREDENTIAL_PARAMETERS *cose, int type) +{ + switch (type) { + case COSE_ES256: + alg->lAlg = WEBAUTHN_COSE_ALGORITHM_ECDSA_P256_WITH_SHA256; + break; + case COSE_ES384: + alg->lAlg = WEBAUTHN_COSE_ALGORITHM_ECDSA_P384_WITH_SHA384; + break; + case COSE_EDDSA: + alg->lAlg = -8; /* XXX */; + break; + case COSE_RS256: + alg->lAlg = WEBAUTHN_COSE_ALGORITHM_RSASSA_PKCS1_V1_5_WITH_SHA256; + break; + default: + fido_log_debug("%s: type %d", __func__, type); + return -1; + } + alg->dwVersion = WEBAUTHN_COSE_CREDENTIAL_PARAMETER_CURRENT_VERSION; + alg->pwszCredentialType = WEBAUTHN_CREDENTIAL_TYPE_PUBLIC_KEY; + cose->cCredentialParameters = 1; + cose->pCredentialParameters = alg; + + return 0; +} + +static int +pack_cred_ext(WEBAUTHN_EXTENSIONS *out, const fido_cred_ext_t *in) +{ + WEBAUTHN_EXTENSION *e; + WEBAUTHN_CRED_PROTECT_EXTENSION_IN *p; + BOOL *b; + size_t n = 0, i = 0; + + if (in->mask == 0) { + return 0; /* nothing to do */ + } + if (in->mask & ~(FIDO_EXT_HMAC_SECRET | FIDO_EXT_CRED_PROTECT)) { + fido_log_debug("%s: mask 0x%x", __func__, in->mask); + return -1; + } + if (in->mask & FIDO_EXT_HMAC_SECRET) + n++; + if (in->mask & FIDO_EXT_CRED_PROTECT) + n++; + if ((out->pExtensions = calloc(n, sizeof(*e))) == NULL) { + fido_log_debug("%s: calloc", __func__); + return -1; + } + out->cExtensions = (DWORD)n; + if (in->mask & FIDO_EXT_HMAC_SECRET) { + if ((b = calloc(1, sizeof(*b))) == NULL) { + fido_log_debug("%s: calloc", __func__); + return -1; + } + *b = true; + e = &out->pExtensions[i]; + e->pwszExtensionIdentifier = + WEBAUTHN_EXTENSIONS_IDENTIFIER_HMAC_SECRET; + e->pvExtension = b; + e->cbExtension = sizeof(*b); + i++; + } + if (in->mask & FIDO_EXT_CRED_PROTECT) { + if ((p = calloc(1, sizeof(*p))) == NULL) { + fido_log_debug("%s: calloc", __func__); + return -1; + } + p->dwCredProtect = (DWORD)in->prot; + p->bRequireCredProtect = true; + e = &out->pExtensions[i]; + e->pwszExtensionIdentifier = + WEBAUTHN_EXTENSIONS_IDENTIFIER_CRED_PROTECT; + e->pvExtension = p; + e->cbExtension = sizeof(*p); + i++; + } + + return 0; +} + +static int +pack_assert_ext(WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS *out, + const fido_assert_ext_t *in) +{ + WEBAUTHN_HMAC_SECRET_SALT_VALUES *v; + WEBAUTHN_HMAC_SECRET_SALT *s; + + if (in->mask == 0) { + return 0; /* nothing to do */ + } + if (in->mask != FIDO_EXT_HMAC_SECRET) { + fido_log_debug("%s: mask 0x%x", __func__, in->mask); + return -1; + } + if (in->hmac_salt.ptr == NULL || + in->hmac_salt.len != WEBAUTHN_CTAP_ONE_HMAC_SECRET_LENGTH) { + fido_log_debug("%s: salt %p/%zu", __func__, + (const void *)in->hmac_salt.ptr, in->hmac_salt.len); + return -1; + } + if ((v = calloc(1, sizeof(*v))) == NULL || + (s = calloc(1, sizeof(*s))) == NULL) { + free(v); + fido_log_debug("%s: calloc", __func__); + return -1; + } + s->cbFirst = (DWORD)in->hmac_salt.len; + s->pbFirst = in->hmac_salt.ptr; + v->pGlobalHmacSalt = s; + out->pHmacSecretSaltValues = v; + out->dwFlags |= WEBAUTHN_AUTHENTICATOR_HMAC_SECRET_VALUES_FLAG; + out->dwVersion = WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_6; + + return 0; +} + +static int +unpack_assert_authdata(fido_assert_t *assert, const WEBAUTHN_ASSERTION *wa) +{ + int r; + + if (wa->cbAuthenticatorData > SIZE_MAX) { + fido_log_debug("%s: cbAuthenticatorData", __func__); + return -1; + } + if ((r = fido_assert_set_authdata_raw(assert, 0, wa->pbAuthenticatorData, + (size_t)wa->cbAuthenticatorData)) != FIDO_OK) { + fido_log_debug("%s: fido_assert_set_authdata_raw: %s", __func__, + fido_strerr(r)); + return -1; + } + + return 0; +} + +static int +unpack_assert_sig(fido_assert_t *assert, const WEBAUTHN_ASSERTION *wa) +{ + int r; + + if (wa->cbSignature > SIZE_MAX) { + fido_log_debug("%s: cbSignature", __func__); + return -1; + } + if ((r = fido_assert_set_sig(assert, 0, wa->pbSignature, + (size_t)wa->cbSignature)) != FIDO_OK) { + fido_log_debug("%s: fido_assert_set_sig: %s", __func__, + fido_strerr(r)); + return -1; + } + + return 0; +} + +static int +unpack_cred_id(fido_assert_t *assert, const WEBAUTHN_ASSERTION *wa) +{ + if (wa->Credential.cbId > SIZE_MAX) { + fido_log_debug("%s: Credential.cbId", __func__); + return -1; + } + if (fido_blob_set(&assert->stmt[0].id, wa->Credential.pbId, + (size_t)wa->Credential.cbId) < 0) { + fido_log_debug("%s: fido_blob_set", __func__); + return -1; + } + + return 0; +} + +static int +unpack_user_id(fido_assert_t *assert, const WEBAUTHN_ASSERTION *wa) +{ + if (wa->cbUserId == 0) + return 0; /* user id absent */ + if (wa->cbUserId > SIZE_MAX) { + fido_log_debug("%s: cbUserId", __func__); + return -1; + } + if (fido_blob_set(&assert->stmt[0].user.id, wa->pbUserId, + (size_t)wa->cbUserId) < 0) { + fido_log_debug("%s: fido_blob_set", __func__); + return -1; + } + + return 0; +} + +static int +unpack_hmac_secret(fido_assert_t *assert, const WEBAUTHN_ASSERTION *wa) +{ + if (wa->dwVersion != WEBAUTHN_ASSERTION_VERSION_3) { + fido_log_debug("%s: dwVersion %u", __func__, + (unsigned)wa->dwVersion); + return 0; /* proceed without hmac-secret */ + } + if (wa->pHmacSecret == NULL || + wa->pHmacSecret->cbFirst == 0 || + wa->pHmacSecret->cbFirst > SIZE_MAX || + wa->pHmacSecret->pbFirst == NULL) { + fido_log_debug("%s: hmac-secret absent", __func__); + return 0; /* proceed without hmac-secret */ + } + if (wa->pHmacSecret->cbSecond != 0 || + wa->pHmacSecret->pbSecond != NULL) { + fido_log_debug("%s: 64-byte hmac-secret", __func__); + return 0; /* proceed without hmac-secret */ + } + if (!fido_blob_is_empty(&assert->stmt[0].hmac_secret)) { + fido_log_debug("%s: fido_blob_is_empty", __func__); + return -1; + } + if (fido_blob_set(&assert->stmt[0].hmac_secret, + wa->pHmacSecret->pbFirst, (size_t)wa->pHmacSecret->cbFirst) < 0) { + fido_log_debug("%s: fido_blob_set", __func__); + return -1; + } + + return 0; +} + +static int +translate_fido_assert(struct winhello_assert *ctx, const fido_assert_t *assert, + const char *pin, int ms) +{ + WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS *opt; + + /* not supported by webauthn.h */ + if (assert->up == FIDO_OPT_FALSE) { + fido_log_debug("%s: up %d", __func__, assert->up); + return FIDO_ERR_UNSUPPORTED_OPTION; + } + if ((ctx->rp_id = to_utf16(assert->rp_id)) == NULL) { + fido_log_debug("%s: rp_id", __func__); + return FIDO_ERR_INTERNAL; + } + if (pack_cd(&ctx->cd, &assert->cd) < 0) { + fido_log_debug("%s: pack_cd", __func__); + return FIDO_ERR_INTERNAL; + } + /* options */ + opt = &ctx->opt; + opt->dwVersion = WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS_VERSION_1; + opt->dwTimeoutMilliseconds = ms < 0 ? MAXMSEC : (DWORD)ms; + if (pack_credlist(&opt->CredentialList, &assert->allow_list) < 0) { + fido_log_debug("%s: pack_credlist", __func__); + return FIDO_ERR_INTERNAL; + } + if (pack_assert_ext(opt, &assert->ext) < 0) { + fido_log_debug("%s: pack_assert_ext", __func__); + return FIDO_ERR_UNSUPPORTED_EXTENSION; + } + if (set_assert_uv(&opt->dwUserVerificationRequirement, assert->uv, + pin) < 0) { + fido_log_debug("%s: set_assert_uv", __func__); + return FIDO_ERR_INTERNAL; + } + + return FIDO_OK; +} + +static int +translate_winhello_assert(fido_assert_t *assert, const WEBAUTHN_ASSERTION *wa) +{ + int r; + + if (assert->stmt_len > 0) { + fido_log_debug("%s: stmt_len=%zu", __func__, assert->stmt_len); + return FIDO_ERR_INTERNAL; + } + if ((r = fido_assert_set_count(assert, 1)) != FIDO_OK) { + fido_log_debug("%s: fido_assert_set_count: %s", __func__, + fido_strerr(r)); + return FIDO_ERR_INTERNAL; + } + if (unpack_assert_authdata(assert, wa) < 0) { + fido_log_debug("%s: unpack_assert_authdata", __func__); + return FIDO_ERR_INTERNAL; + } + if (unpack_assert_sig(assert, wa) < 0) { + fido_log_debug("%s: unpack_assert_sig", __func__); + return FIDO_ERR_INTERNAL; + } + if (unpack_cred_id(assert, wa) < 0) { + fido_log_debug("%s: unpack_cred_id", __func__); + return FIDO_ERR_INTERNAL; + } + if (unpack_user_id(assert, wa) < 0) { + fido_log_debug("%s: unpack_user_id", __func__); + return FIDO_ERR_INTERNAL; + } + if (assert->ext.mask & FIDO_EXT_HMAC_SECRET && + unpack_hmac_secret(assert, wa) < 0) { + fido_log_debug("%s: unpack_hmac_secret", __func__); + return FIDO_ERR_INTERNAL; + } + + return FIDO_OK; +} + +static int +translate_fido_cred(struct winhello_cred *ctx, const fido_cred_t *cred, + const char *pin, int ms) +{ + WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS *opt; + + if (pack_rp(&ctx->rp_id, &ctx->rp_name, &ctx->rp, &cred->rp) < 0) { + fido_log_debug("%s: pack_rp", __func__); + return FIDO_ERR_INTERNAL; + } + if (pack_user(&ctx->user_name, &ctx->user_icon, &ctx->display_name, + &ctx->user, &cred->user) < 0) { + fido_log_debug("%s: pack_user", __func__); + return FIDO_ERR_INTERNAL; + } + if (pack_cose(&ctx->alg, &ctx->cose, cred->type) < 0) { + fido_log_debug("%s: pack_cose", __func__); + return FIDO_ERR_INTERNAL; + } + if (pack_cd(&ctx->cd, &cred->cd) < 0) { + fido_log_debug("%s: pack_cd", __func__); + return FIDO_ERR_INTERNAL; + } + /* options */ + opt = &ctx->opt; + opt->dwVersion = WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS_VERSION_1; + opt->dwTimeoutMilliseconds = ms < 0 ? MAXMSEC : (DWORD)ms; + opt->dwAttestationConveyancePreference = + WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_DIRECT; + if (pack_credlist(&opt->CredentialList, &cred->excl) < 0) { + fido_log_debug("%s: pack_credlist", __func__); + return FIDO_ERR_INTERNAL; + } + if (pack_cred_ext(&opt->Extensions, &cred->ext) < 0) { + fido_log_debug("%s: pack_cred_ext", __func__); + return FIDO_ERR_UNSUPPORTED_EXTENSION; + } + if (set_cred_uv(&opt->dwUserVerificationRequirement, (cred->ext.mask & + FIDO_EXT_CRED_PROTECT) ? FIDO_OPT_TRUE : cred->uv, pin) < 0) { + fido_log_debug("%s: set_cred_uv", __func__); + return FIDO_ERR_INTERNAL; + } + if (cred->rk == FIDO_OPT_TRUE) { + opt->bRequireResidentKey = true; + } + + return FIDO_OK; +} + +static int +decode_attobj(const cbor_item_t *key, const cbor_item_t *val, void *arg) +{ + fido_cred_t *cred = arg; + char *name = NULL; + int ok = -1; + + if (cbor_string_copy(key, &name) < 0) { + fido_log_debug("%s: cbor type", __func__); + ok = 0; /* ignore */ + goto fail; + } + + if (!strcmp(name, "fmt")) { + if (cbor_decode_fmt(val, &cred->fmt) < 0) { + fido_log_debug("%s: cbor_decode_fmt", __func__); + goto fail; + } + } else if (!strcmp(name, "attStmt")) { + if (cbor_decode_attstmt(val, &cred->attstmt) < 0) { + fido_log_debug("%s: cbor_decode_attstmt", __func__); + goto fail; + } + } else if (!strcmp(name, "authData")) { + if (fido_blob_decode(val, &cred->authdata_raw) < 0) { + fido_log_debug("%s: fido_blob_decode", __func__); + goto fail; + } + if (cbor_decode_cred_authdata(val, cred->type, + &cred->authdata_cbor, &cred->authdata, &cred->attcred, + &cred->authdata_ext) < 0) { + fido_log_debug("%s: cbor_decode_cred_authdata", + __func__); + goto fail; + } + } + + ok = 0; +fail: + free(name); + + return (ok); +} + +static int +translate_winhello_cred(fido_cred_t *cred, + const WEBAUTHN_CREDENTIAL_ATTESTATION *att) +{ + cbor_item_t *item = NULL; + struct cbor_load_result cbor; + int r = FIDO_ERR_INTERNAL; + + if (att->pbAttestationObject == NULL || + att->cbAttestationObject > SIZE_MAX) { + fido_log_debug("%s: pbAttestationObject", __func__); + goto fail; + } + if ((item = cbor_load(att->pbAttestationObject, + (size_t)att->cbAttestationObject, &cbor)) == NULL) { + fido_log_debug("%s: cbor_load", __func__); + goto fail; + } + if (cbor_isa_map(item) == false || + cbor_map_is_definite(item) == false || + cbor_map_iter(item, cred, decode_attobj) < 0) { + fido_log_debug("%s: cbor type", __func__); + goto fail; + } + + r = FIDO_OK; +fail: + if (item != NULL) + cbor_decref(&item); + + return r; +} + +static int +winhello_get_assert(HWND w, struct winhello_assert *ctx) +{ + HRESULT hr; + int r = FIDO_OK; + + if ((hr = webauthn_get_assert(w, ctx->rp_id, &ctx->cd, &ctx->opt, + &ctx->assert)) != S_OK) { + r = to_fido(hr); + fido_log_debug("%s: %ls -> %s", __func__, webauthn_strerr(hr), + fido_strerr(r)); + } + + return r; +} + +static int +winhello_make_cred(HWND w, struct winhello_cred *ctx) +{ + HRESULT hr; + int r = FIDO_OK; + + if ((hr = webauthn_make_cred(w, &ctx->rp, &ctx->user, &ctx->cose, + &ctx->cd, &ctx->opt, &ctx->att)) != S_OK) { + r = to_fido(hr); + fido_log_debug("%s: %ls -> %s", __func__, webauthn_strerr(hr), + fido_strerr(r)); + } + + return r; +} + +static void +winhello_assert_free(struct winhello_assert *ctx) +{ + if (ctx == NULL) + return; + if (ctx->assert != NULL) + webauthn_free_assert(ctx->assert); + + free(ctx->rp_id); + free(ctx->opt.CredentialList.pCredentials); + if (ctx->opt.pHmacSecretSaltValues != NULL) + free(ctx->opt.pHmacSecretSaltValues->pGlobalHmacSalt); + free(ctx->opt.pHmacSecretSaltValues); + free(ctx); +} + +static void +winhello_cred_free(struct winhello_cred *ctx) +{ + if (ctx == NULL) + return; + if (ctx->att != NULL) + webauthn_free_attest(ctx->att); + + free(ctx->rp_id); + free(ctx->rp_name); + free(ctx->user_name); + free(ctx->user_icon); + free(ctx->display_name); + free(ctx->opt.CredentialList.pCredentials); + for (size_t i = 0; i < ctx->opt.Extensions.cExtensions; i++) { + WEBAUTHN_EXTENSION *e; + e = &ctx->opt.Extensions.pExtensions[i]; + free(e->pvExtension); + } + free(ctx->opt.Extensions.pExtensions); + free(ctx); +} + +int +fido_winhello_manifest(fido_dev_info_t *devlist, size_t ilen, size_t *olen) +{ + fido_dev_info_t *di; + + if (ilen == 0) { + return FIDO_OK; + } + if (devlist == NULL) { + return FIDO_ERR_INVALID_ARGUMENT; + } + if (!webauthn_loaded && webauthn_load() < 0) { + fido_log_debug("%s: webauthn_load", __func__); + return FIDO_OK; /* not an error */ + } + + di = &devlist[*olen]; + memset(di, 0, sizeof(*di)); + di->path = strdup(FIDO_WINHELLO_PATH); + di->manufacturer = strdup("Microsoft Corporation"); + di->product = strdup("Windows Hello"); + di->vendor_id = VENDORID; + di->product_id = PRODID; + if (di->path == NULL || di->manufacturer == NULL || + di->product == NULL) { + free(di->path); + free(di->manufacturer); + free(di->product); + explicit_bzero(di, sizeof(*di)); + return FIDO_ERR_INTERNAL; + } + ++(*olen); + + return FIDO_OK; +} + +int +fido_winhello_open(fido_dev_t *dev) +{ + if (!webauthn_loaded && webauthn_load() < 0) { + fido_log_debug("%s: webauthn_load", __func__); + return FIDO_ERR_INTERNAL; + } + if (dev->flags != 0) + return FIDO_ERR_INVALID_ARGUMENT; + dev->attr.flags = FIDO_CAP_CBOR | FIDO_CAP_WINK; + dev->flags = FIDO_DEV_WINHELLO | FIDO_DEV_CRED_PROT | FIDO_DEV_PIN_SET; + + return FIDO_OK; +} + +int +fido_winhello_close(fido_dev_t *dev) +{ + memset(dev, 0, sizeof(*dev)); + + return FIDO_OK; +} + +int +fido_winhello_cancel(fido_dev_t *dev) +{ + (void)dev; + + return FIDO_ERR_INTERNAL; +} + +int +fido_winhello_get_assert(fido_dev_t *dev, fido_assert_t *assert, + const char *pin, int ms) +{ + HWND w; + struct winhello_assert *ctx; + int r = FIDO_ERR_INTERNAL; + + (void)dev; + + fido_assert_reset_rx(assert); + + if ((ctx = calloc(1, sizeof(*ctx))) == NULL) { + fido_log_debug("%s: calloc", __func__); + goto fail; + } + if ((w = GetForegroundWindow()) == NULL) { + fido_log_debug("%s: GetForegroundWindow", __func__); + if ((w = GetTopWindow(NULL)) == NULL) { + fido_log_debug("%s: GetTopWindow", __func__); + goto fail; + } + } + if ((r = translate_fido_assert(ctx, assert, pin, ms)) != FIDO_OK) { + fido_log_debug("%s: translate_fido_assert", __func__); + goto fail; + } + if ((r = winhello_get_assert(w, ctx)) != FIDO_OK) { + fido_log_debug("%s: winhello_get_assert", __func__); + goto fail; + } + if ((r = translate_winhello_assert(assert, ctx->assert)) != FIDO_OK) { + fido_log_debug("%s: translate_winhello_assert", __func__); + goto fail; + } + +fail: + winhello_assert_free(ctx); + + return r; +} + +int +fido_winhello_get_cbor_info(fido_dev_t *dev, fido_cbor_info_t *ci) +{ + const char *v[3] = { "U2F_V2", "FIDO_2_0", "FIDO_2_1_PRE" }; + const char *e[2] = { "credProtect", "hmac-secret" }; + const char *t[2] = { "nfc", "usb" }; + const char *o[4] = { "rk", "up", "uv", "plat" }; + + (void)dev; + + fido_cbor_info_reset(ci); + + if (fido_str_array_pack(&ci->versions, v, nitems(v)) < 0 || + fido_str_array_pack(&ci->extensions, e, nitems(e)) < 0 || + fido_str_array_pack(&ci->transports, t, nitems(t)) < 0) { + fido_log_debug("%s: fido_str_array_pack", __func__); + return FIDO_ERR_INTERNAL; + } + if ((ci->options.name = calloc(nitems(o), sizeof(char *))) == NULL || + (ci->options.value = calloc(nitems(o), sizeof(bool))) == NULL) { + fido_log_debug("%s: calloc", __func__); + return FIDO_ERR_INTERNAL; + } + for (size_t i = 0; i < nitems(o); i++) { + if ((ci->options.name[i] = strdup(o[i])) == NULL) { + fido_log_debug("%s: strdup", __func__); + return FIDO_ERR_INTERNAL; + } + ci->options.value[i] = true; + ci->options.len++; + } + + return FIDO_OK; +} + +int +fido_winhello_make_cred(fido_dev_t *dev, fido_cred_t *cred, const char *pin, + int ms) +{ + HWND w; + struct winhello_cred *ctx; + int r = FIDO_ERR_INTERNAL; + + (void)dev; + + fido_cred_reset_rx(cred); + + if ((ctx = calloc(1, sizeof(*ctx))) == NULL) { + fido_log_debug("%s: calloc", __func__); + goto fail; + } + if ((w = GetForegroundWindow()) == NULL) { + fido_log_debug("%s: GetForegroundWindow", __func__); + if ((w = GetTopWindow(NULL)) == NULL) { + fido_log_debug("%s: GetTopWindow", __func__); + goto fail; + } + } + if ((r = translate_fido_cred(ctx, cred, pin, ms)) != FIDO_OK) { + fido_log_debug("%s: translate_fido_cred", __func__); + goto fail; + } + if ((r = winhello_make_cred(w, ctx)) != FIDO_OK) { + fido_log_debug("%s: winhello_make_cred", __func__); + goto fail; + } + if ((r = translate_winhello_cred(cred, ctx->att)) != FIDO_OK) { + fido_log_debug("%s: translate_winhello_cred", __func__); + goto fail; + } + + r = FIDO_OK; +fail: + winhello_cred_free(ctx); + + return r; +} |