Add files via upload

This commit is contained in:
Token2
2024-05-24 11:46:48 +02:00
committed by GitHub
parent 976966cc30
commit 04bcffc0d2
71 changed files with 23969 additions and 0 deletions

156
src/CMakeLists.txt Normal file
View File

@@ -0,0 +1,156 @@
# 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)
configure_file(libfido2.pc.in libfido2.pc @ONLY)
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/libfido2.pc"
DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig")

216
src/aes256.c Normal file
View File

@@ -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);
}

1170
src/assert.c Normal file

File diff suppressed because it is too large Load Diff

107
src/authkey.c Normal file
View File

@@ -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));
}

903
src/bio.c Normal file
View File

@@ -0,0 +1,903 @@
/*
* 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 uint8_t
bio_get_cmd(const fido_dev_t *dev)
{
if (dev->flags & (FIDO_DEV_BIO_SET|FIDO_DEV_BIO_UNSET))
return (CTAP_CBOR_BIO_ENROLL);
return (CTAP_CBOR_BIO_ENROLL_PRE);
}
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 = bio_get_cmd(dev);
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);
}

134
src/blob.c Normal file
View File

@@ -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;
}

42
src/blob.h Normal file
View File

@@ -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 */

34
src/buf.c Normal file
View File

@@ -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);
}

1796
src/cbor.c Normal file

File diff suppressed because it is too large Load Diff

168
src/compress.c Normal file
View File

@@ -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);
}

235
src/config.c Normal file
View File

@@ -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;
}

1316
src/cred.c Normal file

File diff suppressed because it is too large Load Diff

834
src/credman.c Normal file
View File

@@ -0,0 +1,834 @@
/*
* 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(&param_cbor, 0, sizeof(param_cbor));
if (body == NULL)
return (fido_blob_set(hmac_data, &cmd, sizeof(cmd)));
switch (cmd) {
case CMD_RK_BEGIN:
n = 1;
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 uint8_t
credman_get_cmd(const fido_dev_t *dev)
{
if (dev->flags & FIDO_DEV_CREDMAN)
return (CTAP_CBOR_CRED_MGMT);
return (CTAP_CBOR_CRED_MGMT_PRE);
}
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 = credman_get_cmd(dev);
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);
}

606
src/dev.c Normal file
View File

@@ -0,0 +1,606 @@
/*
* 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) {
if (val[i])
dev->flags |= FIDO_DEV_CREDMAN;
} else if (strcmp(ptr[i], "credentialMgmtPreview") == 0) {
if (val[i])
dev->flags |= FIDO_DEV_CREDMAN_PRE;
} 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;
} else if (strcmp(ptr[i], "bioEnroll") == 0) {
dev->flags |= val[i] ?
FIDO_DEV_BIO_SET : FIDO_DEV_BIO_UNSET;
}
}
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|FIDO_DEV_CREDMAN_PRE));
}
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);
}

27
src/diff_exports.sh Normal file
View File

@@ -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}

208
src/ecdh.c Normal file
View File

@@ -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;
}

232
src/eddsa.c Normal file
View File

@@ -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) && LIBRESSL_VERSION_NUMBER < 0x3070000f
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);
}

137
src/err.c Normal file
View File

@@ -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";
}
}

541
src/es256.c Normal file
View File

@@ -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);
}

296
src/es384.c Normal file
View File

@@ -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);
}

272
src/export.gnu Normal file
View File

@@ -0,0 +1,272 @@
{
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_authdata_raw_len;
fido_assert_authdata_raw_ptr;
fido_assert_blob_len;
fido_assert_blob_ptr;
fido_assert_clientdata_hash_len;
fido_assert_clientdata_hash_ptr;
fido_assert_count;
fido_assert_empty_allow_list;
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_set_winhello_appid;
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_empty_exclude_list;
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_attobj;
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_list_count;
fido_cred_x5c_list_len;
fido_cred_x5c_list_ptr;
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:
*;
};

267
src/export.llvm Normal file
View File

@@ -0,0 +1,267 @@
_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_authdata_raw_len
_fido_assert_authdata_raw_ptr
_fido_assert_blob_len
_fido_assert_blob_ptr
_fido_assert_clientdata_hash_len
_fido_assert_clientdata_hash_ptr
_fido_assert_count
_fido_assert_empty_allow_list
_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_set_winhello_appid
_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_empty_exclude_list
_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_attobj
_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_list_count
_fido_cred_x5c_list_len
_fido_cred_x5c_list_ptr
_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

268
src/export.msvc Normal file
View File

@@ -0,0 +1,268 @@
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_authdata_raw_len
fido_assert_authdata_raw_ptr
fido_assert_blob_len
fido_assert_blob_ptr
fido_assert_clientdata_hash_len
fido_assert_clientdata_hash_ptr
fido_assert_count
fido_assert_empty_allow_list
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_set_winhello_appid
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_empty_exclude_list
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_attobj
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_list_count
fido_cred_x5c_list_len
fido_cred_x5c_list_ptr
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

280
src/extern.h Normal file
View File

@@ -0,0 +1,280 @@
/*
* 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_attobj(const cbor_item_t *, fido_cred_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 0x0001
#define FIDO_DEV_PIN_UNSET 0x0002
#define FIDO_DEV_CRED_PROT 0x0004
#define FIDO_DEV_CREDMAN 0x0008
#define FIDO_DEV_PIN_PROTOCOL1 0x0010
#define FIDO_DEV_PIN_PROTOCOL2 0x0020
#define FIDO_DEV_UV_SET 0x0040
#define FIDO_DEV_UV_UNSET 0x0080
#define FIDO_DEV_TOKEN_PERMS 0x0100
#define FIDO_DEV_WINHELLO 0x0200
#define FIDO_DEV_CREDMAN_PRE 0x0400
#define FIDO_DEV_BIO_SET 0x0800
#define FIDO_DEV_BIO_UNSET 0x1000
/* 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 */

21
src/fallthrough.h Normal file
View File

@@ -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 */

282
src/fido.h Normal file
View File

@@ -0,0 +1,282 @@
/*
* 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_authdata_raw_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 *);
const unsigned char *fido_cred_x5c_list_ptr(const fido_cred_t *, size_t);
int fido_assert_allow_cred(fido_assert_t *, const unsigned char *, size_t);
int fido_assert_empty_allow_list(fido_assert_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_set_winhello_appid(fido_assert_t *, const char *);
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_empty_exclude_list(fido_cred_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_attobj(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_authdata_raw_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 *);
size_t fido_cred_x5c_list_count(const fido_cred_t *);
size_t fido_cred_x5c_list_len(const fido_cred_t *, size_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 */

133
src/fido/bio.h Normal file
View File

@@ -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 */

58
src/fido/config.h Normal file
View File

@@ -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 */

113
src/fido/credman.h Normal file
View File

@@ -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 */

71
src/fido/eddsa.h Normal file
View File

@@ -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) && LIBRESSL_VERSION_NUMBER < 0x3070000f
#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 */

106
src/fido/err.h Normal file
View File

@@ -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 */

71
src/fido/es256.h Normal file
View File

@@ -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 */

59
src/fido/es384.h Normal file
View File

@@ -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 */

162
src/fido/param.h Normal file
View File

@@ -0,0 +1,162 @@
/*
* 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_BIO_ENROLL 0x09
#define CTAP_CBOR_CRED_MGMT 0x0a
#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 */

59
src/fido/rs256.h Normal file
View File

@@ -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 */

337
src/fido/types.h Normal file
View File

@@ -0,0 +1,337 @@
/*
* 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_array_t x5c; /* attestation certificate chain */
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_blob_t authdata_raw; /* raw authdata */
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 */
char *appid; /* winhello u2f appid */
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 */

222
src/hid.c Normal file
View File

@@ -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);
}

337
src/hid_freebsd.c Normal file
View File

@@ -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);
}

269
src/hid_hidapi.c Normal file
View File

@@ -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);
}

407
src/hid_linux.c Normal file
View File

@@ -0,0 +1,407 @@
/*
* Copyright (c) 2019-2024 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 **hid_name)
{
char *cp;
char *p;
char *s;
bool found_id = false;
bool found_name = false;
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 (!found_id && 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;
found_id = true;
}
} else if (!found_name && strncmp(p, "HID_NAME=", 9) == 0) {
if ((*hid_name = strdup(p + 9)) != NULL)
found_name = true;
}
}
free(s);
if (!found_name || !found_id)
return (-1);
return (0);
}
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;
char *hid_name = NULL;
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,
&hid_name) < 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);
di->manufacturer = get_usb_attr(dev, "manufacturer");
di->product = get_usb_attr(dev, "product");
if (di->manufacturer == NULL && di->product == NULL) {
di->product = hid_name; /* fallback */
hid_name = NULL;
}
if (di->manufacturer == NULL)
di->manufacturer = strdup("");
if (di->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);
free(hid_name);
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);
}

339
src/hid_netbsd.c Normal file
View File

@@ -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;
if (devlist == NULL || olen == NULL)
return (FIDO_ERR_INVALID_ARGUMENT);
*olen = 0;
if (ilen == 0)
return (FIDO_OK); /* nothing to do */
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);
}

280
src/hid_openbsd.c Normal file
View File

@@ -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);
}

611
src/hid_osx.c Normal file
View File

@@ -0,0 +1,611 @@
/*
* Copyright (c) 2019-2023 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);
}
static void
schedule_io_loop(struct hid_osx *ctx, int ms)
{
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);
}
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);
}
/* check for pending frame */
if ((r = read(ctx->report_pipe[0], buf, len)) == -1) {
if (errno != EAGAIN && errno != EWOULDBLOCK) {
fido_log_error(errno, "%s: read", __func__);
return (-1);
}
schedule_io_loop(ctx, ms);
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);
}

76
src/hid_unix.c Normal file
View File

@@ -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);
}

571
src/hid_win.c Normal file
View File

@@ -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);
}

647
src/info.c Normal file
View File

@@ -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);
}

356
src/io.c Normal file
View File

@@ -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);
}

65
src/iso7816.c Normal file
View File

@@ -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));
}

49
src/iso7816.h Normal file
View File

@@ -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 */

902
src/largeblob.c Normal file
View File

@@ -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;
}

12
src/libfido2.pc.in Normal file
View File

@@ -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}

122
src/log.c Normal file
View File

@@ -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 */

785
src/netlink.c Normal file
View File

@@ -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

45
src/netlink.h Normal file
View File

@@ -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 */

350
src/nfc.c Normal file
View File

@@ -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 */

356
src/nfc_linux.c Normal file
View File

@@ -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;
}

23
src/packed.h Normal file
View File

@@ -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 */

394
src/pcsc.c Normal file
View File

@@ -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;
}

725
src/pin.c Normal file
View File

@@ -0,0 +1,725 @@
/*
* 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:
case CTAP_CBOR_BIO_ENROLL:
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:
case CTAP_CBOR_CRED_MGMT:
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);
}

83
src/random.c Normal file
View File

@@ -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 */

46
src/reset.c Normal file
View File

@@ -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));
}

65
src/rs1.c Normal file
View File

@@ -0,0 +1,65 @@
/*
* 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(__GNUC__)
#define PRAGMA(s) _Pragma(s)
#else
#define PRAGMA(s)
#endif
static EVP_MD *
rs1_get_EVP_MD(void)
{
PRAGMA("GCC diagnostic push")
PRAGMA("GCC diagnostic ignored \"-Wcast-qual\"")
return ((EVP_MD *)EVP_sha1());
PRAGMA("GCC diagnostic pop")
}
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);
return (ok);
}

281
src/rs256.c Normal file
View File

@@ -0,0 +1,281 @@
/*
* 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(__GNUC__)
#define PRAGMA(s) _Pragma(s)
#else
#define PRAGMA(s)
#endif
static EVP_MD *
rs256_get_EVP_MD(void)
{
PRAGMA("GCC diagnostic push")
PRAGMA("GCC diagnostic ignored \"-Wcast-qual\"")
return ((EVP_MD *)EVP_sha256());
PRAGMA("GCC diagnostic pop")
}
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);
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);
}

75
src/time.c Normal file
View File

@@ -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;
}

109
src/touch.c Normal file
View File

@@ -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);
}

391
src/tpm.c Normal file
View File

@@ -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;
}

91
src/types.c Normal file
View File

@@ -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;
}

959
src/u2f.c Normal file
View File

@@ -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);
}

31
src/util.c Normal file
View File

@@ -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;
}

1149
src/webauthn.h Normal file

File diff suppressed because it is too large Load Diff

1029
src/winhello.c Normal file

File diff suppressed because it is too large Load Diff