/* SSSD Helper child to commmunicate with passkey devices Authors: Iker Pedrosa Copyright (C) 2022 Red Hat This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include #include #include #include "util/crypto/sss_crypto.h" #include "util/debug.h" #include "util/util.h" #include "util/crypto/sss_crypto.h" #include "passkey_child.h" #if OPENSSL_VERSION_NUMBER >= 0x30000000 #define get_id(x) EVP_PKEY_get_base_id((x)) #else #define get_id(x) EVP_PKEY_base_id((x)) #endif /* OPENSSL_VERSION_NUMBER */ errno_t cose_str_to_int(const char *type, int *out) { if (strcasecmp(type, "es256") == 0) { *out = COSE_ES256; } else if (strcasecmp(type, "rs256") == 0) { *out = COSE_RS256; } else if (strcasecmp(type, "eddsa") == 0) { *out = COSE_EDDSA; } else { *out = 0; return ERR_INVALID_CRED_TYPE; } return EOK; } static errno_t cred_type_str_to_enum(const char *type, enum credential_type *out) { if (strcasecmp(type, "server-side") == 0) { *out = CRED_SERVER_SIDE; } else if (strcasecmp(type, "discoverable") == 0) { *out = CRED_DISCOVERABLE; } else { *out = 0; return ERR_INVALID_CRED_TYPE; } return EOK; } static errno_t parse_public_keys_and_handlers(TALLOC_CTX *mem_ctx, const char *public_keys, const char *key_handles, struct passkey_data *_data) { TALLOC_CTX *tmp_ctx = NULL; char **pk_list = NULL; char **kh_list = NULL; int pk_num = 0; int kh_num = 0; errno_t ret; tmp_ctx = talloc_new(NULL); if (tmp_ctx == NULL) { ERROR("talloc_new() failed\n"); return ENOMEM; } ret = split_on_separator(tmp_ctx, public_keys, ',', true, true, &pk_list, &pk_num); if (ret != EOK && _data->action == ACTION_AUTHENTICATE) { ERROR("Incorrectly formatted public keys.\n"); goto done; } ret = split_on_separator(tmp_ctx, key_handles, ',', true, true, &kh_list, &kh_num); if (ret != EOK) { ERROR("Incorrectly formatted public keys.\n"); goto done; } if (_data->action == ACTION_AUTHENTICATE && pk_num != kh_num) { ERROR("The number of public keys and key handles don't match.\n"); goto done; } _data->public_key_list = talloc_steal(mem_ctx, pk_list); _data->key_handle_list = talloc_steal(mem_ctx, kh_list); _data->public_key_size = pk_num; _data->key_handle_size = kh_num; done: talloc_free(tmp_ctx); return ret; } errno_t parse_arguments(TALLOC_CTX *mem_ctx, int argc, const char *argv[], struct passkey_data *data) { int opt; int dumpable = 1; int debug_fd = -1; char *user_verification = NULL; char *public_keys = NULL; char *key_handles = NULL; const char *opt_logger = NULL; const char *type = NULL; const char *cred_type = NULL; poptContext pc; errno_t ret; /* Set defaults */ data->action = ACTION_NONE; data->shortname = NULL; data->domain = NULL; data->public_key_list = NULL; data->key_handle_list = NULL; data->public_key_size = 0; data->key_handle_size = 0; data->crypto_challenge = NULL; data->auth_data = NULL; data->signature = NULL; data->type = COSE_ES256; data->user_verification = FIDO_OPT_OMIT; data->cred_type = CRED_SERVER_SIDE; data->user_id = NULL; data->mapping_file = NULL; data->quiet = false; data->debug_libfido2 = false; struct poptOption long_options[] = { POPT_AUTOHELP SSSD_DEBUG_OPTS {"dumpable", 0, POPT_ARG_INT, &dumpable, 0, _("Allow core dumps"), NULL }, {"debug-fd", 0, POPT_ARG_INT, &debug_fd, 0, _("An open file descriptor for the debug logs"), NULL}, SSSD_LOGGER_OPTS {"register", 0, POPT_ARG_NONE, NULL, 'r', _("Register a passkey for a user"), NULL }, {"authenticate", 0, POPT_ARG_NONE, NULL, 'a', _("Authenticate a user with a passkey"), NULL }, {"get-assert", 0, POPT_ARG_NONE, NULL, 'g', _("Obtain assertion data"), NULL }, {"verify-assert", 0, POPT_ARG_NONE, NULL, 'v', _("Verify assertion data"), NULL }, {"username", 0, POPT_ARG_STRING, &data->shortname, 0, _("Shortname"), NULL }, {"domain", 0, POPT_ARG_STRING, &data->domain, 0, _("Domain"), NULL}, {"public-key", 0, POPT_ARG_STRING, &public_keys, 0, _("Public key"), NULL }, {"key-handle", 0, POPT_ARG_STRING, &key_handles, 0, _("Key handle"), NULL}, {"cryptographic-challenge", 0, POPT_ARG_STRING, &data->crypto_challenge, 0, _("Cryptographic challenge"), NULL}, {"auth-data", 0, POPT_ARG_STRING, &data->auth_data, 0, _("Authenticator data"), NULL}, {"signature", 0, POPT_ARG_STRING, &data->signature, 0, _("Signature"), NULL}, {"type", 0, POPT_ARG_STRING, &type, 0, _("COSE type to use"), "es256|rs256|eddsa"}, {"user-verification", 0, POPT_ARG_STRING, &user_verification, 0, _("Require user-verification"), "true|false"}, {"cred-type", 0, POPT_ARG_STRING, &cred_type, 0, _("Credential type"), "server-side|discoverable"}, {"output-file", 0, POPT_ARG_STRING, &data->mapping_file, 0, _("Write key mapping data to file"), NULL}, {"quiet", 0, POPT_ARG_NONE, NULL, 'q', _("Supress prompts"), NULL}, {"debug-libfido2", 0, POPT_ARG_NONE, NULL, 'd', _("Enable debug in libfido2 library"), NULL}, SSSD_LOGGER_OPTS POPT_TABLEEND }; /* Set debug level to invalid value so we can decide if -d 0 was used. */ debug_level = SSSDBG_INVALID; pc = poptGetContext(argv[0], argc, argv, long_options, 0); while((opt = poptGetNextOpt(pc)) != -1) { switch(opt) { case 'r': if (data->action != ACTION_NONE && data->action != ACTION_REGISTER) { fprintf(stderr, "\nActions are mutually exclusive and should" \ " be used only once.\n\n"); poptPrintUsage(pc, stderr, 0); ret = EINVAL; goto done; } data->action = ACTION_REGISTER; break; case 'a': if (data->action != ACTION_NONE && data->action != ACTION_AUTHENTICATE) { fprintf(stderr, "\nActions are mutually exclusive and should" \ " be used only once.\n\n"); poptPrintUsage(pc, stderr, 0); ret = EINVAL; goto done; } data->action = ACTION_AUTHENTICATE; break; case 'g': if (data->action != ACTION_NONE && data->action != ACTION_GET_ASSERT) { fprintf(stderr, "\nActions are mutually exclusive and should" \ " be used only once.\n\n"); poptPrintUsage(pc, stderr, 0); ret = EINVAL; goto done; } data->action = ACTION_GET_ASSERT; break; case 'v': if (data->action != ACTION_NONE && data->action != ACTION_VERIFY_ASSERT) { fprintf(stderr, "\nActions are mutually exclusive and should" \ " be used only once.\n\n"); poptPrintUsage(pc, stderr, 0); ret = EINVAL; goto done; } data->action = ACTION_VERIFY_ASSERT; break; case 'q': data->quiet = true; break; case 'd': data->debug_libfido2 = true; break; default: fprintf(stderr, "\nInvalid option %s: %s\n\n", poptBadOption(pc, 0), poptStrerror(opt)); poptPrintUsage(pc, stderr, 0); ret = EINVAL; goto done; } } poptFreeContext(pc); prctl(PR_SET_DUMPABLE, (dumpable == 0) ? 0 : 1); if (user_verification != NULL) { if (strcmp(user_verification, "true") == 0) { data->user_verification = FIDO_OPT_TRUE; } else if (strcmp(user_verification, "false") == 0) { data->user_verification = FIDO_OPT_FALSE; } else if (user_verification != NULL) { ERROR("[%s] is not a valid user-verification value.\n", user_verification); ret = EINVAL; goto done; } } if (type != NULL) { ret = cose_str_to_int(type, &data->type); if (ret != EOK) { ERROR("[%s] is not a valid COSE type (es256, rs256 or eddsa).\n", type); goto done; } } if (public_keys != NULL || key_handles != NULL) { ret = parse_public_keys_and_handlers(mem_ctx, public_keys, key_handles, data); if (ret != EOK) { goto done; } } if (cred_type != NULL) { ret = cred_type_str_to_enum(cred_type, &data->cred_type); if (ret != EOK) { ERROR("[%s] is not a valid credential type (server-side or" " discoverable).\n", cred_type); goto done; } } debug_prg_name = talloc_asprintf(NULL, "passkey_child[%d]", getpid()); if (debug_prg_name == NULL) { ERROR("talloc_asprintf failed.\n"); ret = ENOMEM; goto done; } if (debug_fd != -1) { opt_logger = sss_logger_str[FILES_LOGGER]; ret = set_debug_file_from_fd(debug_fd); if (ret != EOK) { opt_logger = sss_logger_str[STDERR_LOGGER]; ERROR("set_debug_file_from_fd failed.\n"); } } DEBUG_INIT(debug_level, opt_logger); ret = EOK; done: return ret; } errno_t check_arguments(const struct passkey_data *data) { errno_t ret = EOK; DEBUG(SSSDBG_TRACE_FUNC, "Argument values after parsing\n"); DEBUG(SSSDBG_TRACE_FUNC, "action: %d\n", data->action); DEBUG(SSSDBG_TRACE_FUNC, "shortname: %s, domain: %s\n", data->shortname, data->domain); DEBUG(SSSDBG_TRACE_FUNC, "Number of key handles %d\n", data->key_handle_size); for (int i = 0; i < data->key_handle_size; i++) { DEBUG(SSSDBG_TRACE_FUNC, "key %d, key_handle: %s\n", i + 1, data->key_handle_list[i]); } DEBUG(SSSDBG_TRACE_FUNC, "Number of public keys %d\n", data->public_key_size); for (int i = 0; i < data->public_key_size; i++) { DEBUG(SSSDBG_TRACE_FUNC, "key %d, public_key: %s\n", i + 1, data->public_key_list[i]); } DEBUG(SSSDBG_TRACE_FUNC, "cryptographic-challenge: %s\n", data->crypto_challenge); DEBUG(SSSDBG_TRACE_FUNC, "auth-data: %s\n", data->auth_data); DEBUG(SSSDBG_TRACE_FUNC, "signature: %s\n", data->signature); DEBUG(SSSDBG_TRACE_FUNC, "type: %d\n", data->type); DEBUG(SSSDBG_TRACE_FUNC, "user_verification: %d\n", data->user_verification); DEBUG(SSSDBG_TRACE_FUNC, "cred_type: %d\n", data->cred_type); DEBUG(SSSDBG_TRACE_FUNC, "Mapping file: %s\n", data->mapping_file); DEBUG(SSSDBG_TRACE_FUNC, "debug_libfido2: %d\n", data->debug_libfido2); if (data->action == ACTION_NONE) { DEBUG(SSSDBG_OP_FAILURE, "No action set.\n"); ret = ERR_INPUT_PARSE; goto done; } if (data->action == ACTION_REGISTER && (data->shortname == NULL || data->domain == NULL)) { DEBUG(SSSDBG_OP_FAILURE, "Too few arguments for register action.\n"); ret = ERR_INPUT_PARSE; goto done; } if (data->action == ACTION_AUTHENTICATE && (data->domain == NULL || data->public_key_list == NULL || data->key_handle_list == NULL)) { DEBUG(SSSDBG_OP_FAILURE, "Too few arguments for authenticate action.\n"); ret = ERR_INPUT_PARSE; goto done; } if (data->action == ACTION_GET_ASSERT && (data->domain == NULL || data->key_handle_list == NULL || data->crypto_challenge == NULL)) { DEBUG(SSSDBG_OP_FAILURE, "Too few arguments for get-assert action.\n"); ret = ERR_INPUT_PARSE; goto done; } if (data->action == ACTION_VERIFY_ASSERT && (data->domain == NULL || data->public_key_list == NULL || data->key_handle_list == NULL || data->crypto_challenge == NULL || data->auth_data == NULL || data->signature == NULL)) { DEBUG(SSSDBG_OP_FAILURE, "Too few arguments for verify-assert action.\n"); ret = ERR_INPUT_PARSE; goto done; } done: return ret; } errno_t register_key(struct passkey_data *data) { TALLOC_CTX *tmp_ctx = NULL; fido_cred_t *cred = NULL; fido_dev_t *dev = NULL; fido_dev_info_t *dev_list = NULL; size_t dev_number = 0; errno_t ret; tmp_ctx = talloc_new(NULL); if (tmp_ctx == NULL) { DEBUG(SSSDBG_OP_FAILURE, "talloc_new() failed.\n"); return ENOMEM; } data->user_id = talloc_array(tmp_ctx, unsigned char, USER_ID_SIZE); if (data->user_id == NULL) { DEBUG(SSSDBG_OP_FAILURE, "talloc_array() failed.\n"); ret = ENOMEM; goto done; } cred = fido_cred_new(); if (cred == NULL) { DEBUG(SSSDBG_OP_FAILURE, "fido_cred_new failed.\n"); ret = ENOMEM; goto done; } dev_list = fido_dev_info_new(DEVLIST_SIZE); if (dev_list == NULL) { DEBUG(SSSDBG_OP_FAILURE, "fido_dev_info_new failed.\n"); ret = ENOMEM; goto done; } ret = list_devices(dev_list, &dev_number); if (ret != EOK) { goto done; } ret = select_device(data->action, dev_list, dev_number, NULL, &dev); if (ret != EOK) { goto done; } ret = prepare_credentials(data, dev, cred); if (ret != EOK) { goto done; } ret = generate_credentials(data, dev, cred); if (ret != EOK) { ERROR("A problem occurred while generating the credentials.\n"); goto done; } ret = verify_credentials(cred); if (ret != EOK) { goto done; } ret = print_credentials(data, cred); if (ret != EOK) { goto done; } ret = EOK; done: fido_cred_free(&cred); fido_dev_info_free(&dev_list, dev_number); if (dev != NULL) { fido_dev_close(dev); } fido_dev_free(&dev); talloc_free(tmp_ctx); return ret; } errno_t public_key_to_base64(TALLOC_CTX *mem_ctx, const struct passkey_data *data, const unsigned char *public_key, size_t pk_len, char **_pem_key) { EVP_PKEY *evp_pkey = NULL; unsigned char *pub = NULL; char *pem_key = NULL; unsigned long err; errno_t ret; if (_pem_key == NULL) { ret = EINVAL; goto done; } switch (data->type) { case COSE_ES256: ret = es256_pubkey_to_evp_pkey(mem_ctx, public_key, pk_len, &evp_pkey); break; case COSE_RS256: ret = rs256_pubkey_to_evp_pkey(mem_ctx, public_key, pk_len, &evp_pkey); break; case COSE_EDDSA: ret = eddsa_pubkey_to_evp_pkey(mem_ctx, public_key, pk_len, &evp_pkey); break; default: DEBUG(SSSDBG_OP_FAILURE, "Invalid key type.\n"); ret = EINVAL; break; } if (ret != EOK) { goto done; } ret = i2d_PUBKEY(evp_pkey, &pub); if (ret < 1) { err = ERR_get_error(); DEBUG(SSSDBG_OP_FAILURE, "i2d_PUBKEY failed [%lu][%s].\n", err, ERR_error_string(err, NULL)); ret = EIO; goto done; } pem_key = sss_base64_encode(mem_ctx, pub, ret); if (pem_key == NULL) { DEBUG(SSSDBG_OP_FAILURE, "sss_base64_encode failed.\n"); ret = ENOMEM; goto done; } *_pem_key = pem_key; ret = EOK; done: free(pub); if (evp_pkey != NULL) { EVP_PKEY_free(evp_pkey); } return ret; } errno_t select_authenticator(struct passkey_data *data, fido_dev_t **_dev, fido_assert_t **_assert, int *_index) { fido_dev_info_t *dev_list = NULL; fido_dev_t *dev = NULL; size_t dev_list_len = 0; fido_assert_t *assert = NULL; int index = 0; errno_t ret; dev_list = fido_dev_info_new(DEVLIST_SIZE); if (dev_list == NULL) { DEBUG(SSSDBG_OP_FAILURE, "fido_dev_info_new failed.\n"); ret = ENOMEM; goto done; } DEBUG(SSSDBG_TRACE_FUNC, "Checking for devices.\n"); ret = list_devices(dev_list, &dev_list_len); if (ret != EOK) { goto done; } DEBUG(SSSDBG_TRACE_FUNC, "%d key handles provided.\n", data->key_handle_size); while (index < data->key_handle_size) { DEBUG(SSSDBG_TRACE_FUNC, "Preparing assert request data with key handle %d.\n", index + 1); assert = fido_assert_new(); if (assert == NULL) { DEBUG(SSSDBG_OP_FAILURE, "fido_assert_new failed.\n"); ret = ENOMEM; goto done; } DEBUG(SSSDBG_TRACE_FUNC, "Preparing assert request data.\n"); ret = prepare_assert(data, index, assert); if (ret != FIDO_OK) { goto done; } DEBUG(SSSDBG_TRACE_FUNC, "Selecting device.\n"); ret = select_device(data->action, dev_list, dev_list_len, assert, &dev); if (ret == EOK) { /* Key handle found in device */ break; } if (dev != NULL) { fido_dev_close(dev); } fido_dev_free(&dev); fido_assert_free(&assert); index++; } *_dev = dev; *_assert = assert; *_index = index; done: if (ret != EOK) { fido_assert_free(&assert); } fido_dev_info_free(&dev_list, dev_list_len); return ret; } errno_t public_key_to_libfido2(const char *pem_public_key, struct pk_data_t *_pk_data) { TALLOC_CTX *tmp_ctx = NULL; const unsigned char *public_key = NULL; size_t pk_len; const EVP_PKEY *evp_pkey = NULL; int base_id; unsigned long err; errno_t ret; tmp_ctx = talloc_new(NULL); if (tmp_ctx == NULL) { ret = ENOMEM; goto done; } public_key = sss_base64_decode(tmp_ctx, pem_public_key, &pk_len); if (public_key == NULL) { DEBUG(SSSDBG_OP_FAILURE, "Failed to decode public key.\n"); ret = ENOMEM; goto done; } evp_pkey = d2i_PUBKEY(NULL, &public_key, pk_len); if (evp_pkey == NULL) { err = ERR_get_error(); DEBUG(SSSDBG_OP_FAILURE, "d2i_pubkey failed [%lu][%s].\n", err, ERR_error_string(err, NULL)); ret = EIO; goto done; } base_id = get_id(evp_pkey); if (base_id == EVP_PKEY_EC) { _pk_data->type = COSE_ES256; ret = evp_pkey_to_es256_pubkey(evp_pkey, _pk_data); } else if (base_id == EVP_PKEY_RSA) { _pk_data->type = COSE_RS256; ret = evp_pkey_to_rs256_pubkey(evp_pkey, _pk_data); } else if (base_id == EVP_PKEY_ED25519) { _pk_data->type = COSE_EDDSA; ret = evp_pkey_to_eddsa_pubkey(evp_pkey, _pk_data); } else { DEBUG(SSSDBG_OP_FAILURE, "Unrecognized key type.\n"); ret = EINVAL; } if (ret != EOK) { goto done; } ret = EOK; done: if (evp_pkey != NULL) { EVP_PKEY_free(discard_const(evp_pkey)); } talloc_free(tmp_ctx); return ret; } errno_t authenticate(struct passkey_data *data) { TALLOC_CTX *tmp_ctx = NULL; fido_assert_t *assert = NULL; fido_dev_t *dev = NULL; struct pk_data_t pk_data = { 0 }; int index; errno_t ret; tmp_ctx = talloc_new(NULL); if (tmp_ctx == NULL) { ERROR("talloc_new() failed\n"); return ENOMEM; } ret = select_authenticator(data, &dev, &assert, &index); if (ret != EOK) { goto done; } DEBUG(SSSDBG_TRACE_FUNC, "Comparing the device and policy options.\n"); ret = get_device_options(dev, data); if (ret != FIDO_OK) { goto done; } DEBUG(SSSDBG_TRACE_FUNC, "Resetting assert options.\n"); ret = set_assert_options(FIDO_OPT_TRUE, data->user_verification, assert); if (ret != FIDO_OK) { DEBUG(SSSDBG_OP_FAILURE, "Failed to reset assert options.\n"); goto done; } DEBUG(SSSDBG_TRACE_FUNC, "Resetting assert client data.\n"); ret = set_assert_client_data_hash(data, assert); if (ret != FIDO_OK) { DEBUG(SSSDBG_OP_FAILURE, "Failed to reset client data hash.\n"); goto done; } DEBUG(SSSDBG_TRACE_FUNC, "Decoding public key.\n"); ret = public_key_to_libfido2(data->public_key_list[index], &pk_data); if (ret != FIDO_OK) { goto done; } DEBUG(SSSDBG_TRACE_FUNC, "Getting assert.\n"); ret = request_assert(data, dev, assert); if (ret != FIDO_OK) { goto done; } DEBUG(SSSDBG_TRACE_FUNC, "Verifying assert.\n"); ret = verify_assert(&pk_data, assert); if (ret != FIDO_OK) { goto done; } ret = FIDO_OK; done: reset_public_key(&pk_data); if (dev != NULL) { fido_dev_close(dev); } fido_dev_free(&dev); fido_assert_free(&assert); talloc_free(tmp_ctx); return ret; } errno_t get_assert_data(struct passkey_data *data) { TALLOC_CTX *tmp_ctx = NULL; fido_dev_t *dev = NULL; fido_assert_t *assert = NULL; const char *auth_data = NULL; const char *signature = NULL; int index; errno_t ret; tmp_ctx = talloc_new(NULL); if (tmp_ctx == NULL) { DEBUG(SSSDBG_OP_FAILURE, "talloc_new() failed.\n"); return ENOMEM; } ret = select_authenticator(data, &dev, &assert, &index); if (ret != EOK) { goto done; } DEBUG(SSSDBG_TRACE_FUNC, "Comparing the device and policy options.\n"); ret = get_device_options(dev, data); if (ret != FIDO_OK) { goto done; } DEBUG(SSSDBG_TRACE_FUNC, "Resetting assert options.\n"); ret = set_assert_options(FIDO_OPT_TRUE, data->user_verification, assert); if (ret != FIDO_OK) { DEBUG(SSSDBG_OP_FAILURE, "Failed to reset assert options.\n"); goto done; } DEBUG(SSSDBG_TRACE_FUNC, "Getting assert.\n"); ret = request_assert(data, dev, assert); if (ret != FIDO_OK) { goto done; } DEBUG(SSSDBG_TRACE_FUNC, "Getting authentication data and signature.\n"); ret = get_assert_auth_data_signature(tmp_ctx, assert, &auth_data, &signature); if (ret != EOK) { goto done; } print_assert_data(data->key_handle_list[index], data->crypto_challenge, auth_data, signature); done: if (dev != NULL) { fido_dev_close(dev); } fido_dev_free(&dev); fido_assert_free(&assert); talloc_free(tmp_ctx); return ret; } errno_t verify_assert_data(struct passkey_data *data) { fido_assert_t *assert = NULL; struct pk_data_t pk_data = { 0 }; errno_t ret; assert = fido_assert_new(); if (assert == NULL) { DEBUG(SSSDBG_OP_FAILURE, "fido_assert_new failed.\n"); ret = ENOMEM; goto done; } DEBUG(SSSDBG_TRACE_FUNC, "Preparing assert data.\n"); ret = prepare_assert(data, 0, assert); if (ret != FIDO_OK) { goto done; } DEBUG(SSSDBG_TRACE_FUNC, "Preparing assert authenticator data and signature.\n"); ret = set_assert_auth_data_signature(data, assert); if (ret != FIDO_OK) { goto done; } DEBUG(SSSDBG_TRACE_FUNC, "Decoding public key.\n"); ret = public_key_to_libfido2(data->public_key_list[0], &pk_data); if (ret != FIDO_OK) { goto done; } DEBUG(SSSDBG_TRACE_FUNC, "Verifying assert.\n"); ret = verify_assert(&pk_data, assert); if (ret != FIDO_OK) { goto done; } ret = FIDO_OK; done: reset_public_key(&pk_data); fido_assert_free(&assert); return ret; }