1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00
RIOT/sys/fido2/ctap/ctap.c

1718 lines
45 KiB
C

/*
* Copyright (C) 2021 Freie Universität Berlin
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @ingroup fido2_ctap
* @{
* @file
*
* @author Nils Ollrogge <nils.ollrogge@fu-berlin.de>
* @}
*/
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include "byteorder.h"
#include "errno.h"
#include "fmt.h"
#include "string_utils.h"
#include "ztimer.h"
#include "fido2/ctap/transport/ctap_transport.h"
#include "fido2/ctap.h"
#include "fido2/ctap/ctap_utils.h"
#include "fido2/ctap/ctap_cbor.h"
#include "fido2/ctap/ctap_mem.h"
#if IS_USED(MODULE_FIDO2_CTAP_TRANSPORT_HID)
#include "fido2/ctap/transport/hid/ctap_hid.h"
#endif
#define ENABLE_DEBUG (0)
#include "debug.h"
/**
* @brief CTAP get_assertion state
*/
typedef struct {
ctap_resident_key_t rks[CTAP_MAX_EXCLUDE_LIST_SIZE]; /**< eligible resident keys found */
uint8_t count; /**< number of rks found */
uint8_t cred_counter; /**< amount of creds sent to host */
uint32_t timer; /**< time gap between get_next_assertion calls in milliseconds */
bool uv; /**< indicate if user verified */
bool up; /**< indicate if user present */
uint8_t client_data_hash[SHA256_DIGEST_LENGTH]; /**< SHA-256 hash of JSON serialized client data */
} ctap_get_assertion_state_t;
/*** CTAP methods ***/
/**
* @brief MakeCredential method
*
* CTAP specification (version 20190130) section 5.1
*/
static int _make_credential(ctap_req_t *req_raw);
/**
* @brief GetAssertion method
*
* CTAP specification (version 20190130) section 5.2
*/
static int _get_assertion(ctap_req_t *req_raw);
/**
* @brief GetNextAssertion method
*
* CTAP specification (version 20190130) section 5.3
*/
static int _get_next_assertion(void);
/**
* @brief GetInfo method
*
* CTAP specification (version 20190130) section 5.4
*/
static int _get_info(void);
/**
* @brief ClientPIN method
*
* CTAP specification (version 20190130) section 5.5
*/
static int _client_pin(ctap_req_t *req_raw);
/**
* @brief Reset method
*
* CTAP specification (version 20190130) section 5.6
*/
static int _reset(void);
/*** CTAP clientPIN functions ***/
/**
* @brief ClientPIN getRetries method
*
* CTAP specification (version 20190130) section 5.5.3
*/
static int _get_retries(void);
/**
* @brief ClientPIN getKeyAgreement method
*
* CTAP specification (version 20190130) section 5.5.4
*/
static int _get_key_agreement(void);
/**
* @brief ClientPIN setPIN method
*
* CTAP specification (version 20190130) section 5.5.5
*/
static int _set_pin(ctap_client_pin_req_t *req);
/**
* @brief ClientPIN changePIN method
*
* CTAP specification (version 20190130) section 5.5.6
*/
static int _change_pin(ctap_client_pin_req_t *req);
/**
* @brief ClientPIN getPINToken method
*
* CTAP specification (version 20190130) section 5.5.7
*/
static int _get_pin_token(ctap_client_pin_req_t *req);
/*** helper functions ***/
/**
* @brief Initialize authData of attestation object
*
* This function generates the public key pair used by the credential
*/
static int _make_auth_data_attest(ctap_make_credential_req_t *req,
ctap_auth_data_t *auth_data,
ctap_resident_key_t *k, bool uv, bool up,
bool rk);
/**
* @brief Initialize authData of assertion object
*/
static int _make_auth_data_assert(uint8_t *rp_id, size_t rp_id_len,
ctap_auth_data_header_t *auth_data, bool uv,
bool up, uint32_t sign_count);
/**
* @brief Initialize authData of next assertion object
*/
static int _make_auth_data_next_assert(uint8_t *rp_id_hash,
ctap_auth_data_header_t *auth_data,
bool uv, bool up, uint32_t sign_count);
/**
* @brief Find most recent rk matching rp_id_hash and present in allow_list
*/
static int _find_matching_rks(ctap_resident_key_t *rks, size_t rks_len,
ctap_cred_desc_alt_t *allow_list,
size_t allow_list_len,
uint8_t *rp_id, size_t rp_id_len);
/**
* @brief Decrypt credential that is stored by relying party
*
* This function is used when the credential is stored by the
* relying party in encrypted form.
* See Webauthn specification (version 20190304) section 4 (Credential ID)
* for more details.
*/
static int _ctap_decrypt_rk(ctap_resident_key_t *rk, ctap_cred_id_t *id);
/**
* @brief Save PIN to authenticator state and write the updated state to flash
*/
static int _save_pin(uint8_t *pin, size_t len);
/**
* @brief Check if PIN protocol version is supported
*/
static inline bool _pin_protocol_supported(uint8_t version);
/**
* @brief Decrement PIN attempts and indicate if authenticator is locked
*
* Should the decrease of PIN attempts cross one of the two thresholds
* ( @ref CTAP_PIN_MAX_ATTS_BOOT, @ref CTAP_PIN_MAX_ATTS ) this methods returns
* an error code indicating the locking status.
*/
static int _decrement_pin_attempts(void);
/**
* @brief Reset PIN attempts to its starting values
*/
static int _reset_pin_attempts(void);
/**
* @brief Verify pinAuth sent by platform
*
* pinAuth is verified by comparing it to the first 16 bytes of
* HMAC-SHA-256(pinToken, clientDataHash)
*/
static int _verify_pin_auth(uint8_t *auth, uint8_t *hash, size_t len);
/**
* @brief Check if authenticator is boot locked
*
* Authenticator needs to be rebooted in order to be used again.
*/
static inline bool _is_boot_locked(void);
/**
* @brief Check if authenticator is completely locked
*
* Authenticator needs to be reset, deleting all stored credentials, in order
* to be used again.
*/
static inline bool _is_locked(void);
/**
* @brief State of authenticator
*/
static ctap_state_t _state;
/**
* @brief Assertion state
*
* The assertion state is needed to keep state between the GetAssertion and
* GetNextAssertion methods.
*/
static ctap_get_assertion_state_t _assert_state;
/**
* @brief pinToken
*
* The pinToken is used to reduce the cost of the PIN mechanism and increase
* its security.
*
* See CTAP specification (version 20190130) section 5.5.2 for more details
*/
static uint8_t _pin_token[CTAP_PIN_TOKEN_SZ];
/**
* @brief remaining PIN attempts until authenticator is boot locked
*/
static int _rem_pin_att_boot = CTAP_PIN_MAX_ATTS_BOOT;
ctap_status_code_t fido2_ctap_init(void)
{
int ret;
ret = fido2_ctap_mem_init();
if (ret != CTAP2_OK) {
return ret;
}
ret = fido2_ctap_mem_read_state_from_flash(&_state);
if (ret != CTAP2_OK) {
return ret;
}
/* first startup of the device */
if (_state.initialized_marker != CTAP_INITIALIZED_MARKER) {
ret = _reset();
if (ret != CTAP2_OK) {
return ret;
}
}
if (!IS_ACTIVE(CONFIG_FIDO2_CTAP_DISABLE_UP)) {
ret = fido2_ctap_utils_init_gpio_pin(CTAP_UP_BUTTON, CTAP_UP_BUTTON_MODE,
CTAP_UP_BUTTON_FLANK);
if (ret != CTAP2_OK) {
return ret;
}
}
ret = fido2_ctap_crypto_init();
if (ret != CTAP2_OK) {
return ret;
}
/* initialize pin_token */
ret = fido2_ctap_crypto_prng(_pin_token, sizeof(_pin_token));
if (ret != CTAP2_OK) {
return ret;
}
DEBUG("fido2_ctap: initialization successful \n");
return CTAP2_OK;
}
ctap_status_code_t fido2_ctap_handle_request(ctap_req_t *req, ctap_resp_t *resp)
{
assert(req);
assert(resp);
switch (req->method) {
case CTAP_GET_INFO:
DEBUG("fido2_ctap: get_info req \n");
fido2_ctap_get_info(resp);
break;
case CTAP_MAKE_CREDENTIAL:
DEBUG("fido2_ctap: make_credential req \n");
fido2_ctap_make_credential(req, resp);
break;
case CTAP_GET_ASSERTION:
DEBUG("fido2_ctap: get_assertion req \n");
fido2_ctap_get_assertion(req, resp);
break;
case CTAP_GET_NEXT_ASSERTION:
DEBUG("fido2_ctap: get_next_assertion req \n");
fido2_ctap_get_next_assertion(resp);
break;
case CTAP_CLIENT_PIN:
DEBUG("fido2_ctap: client_pin req \n");
fido2_ctap_client_pin(req, resp);
break;
case CTAP_RESET:
DEBUG("fido2_ctap: reset req \n");
fido2_ctap_reset(resp);
break;
default:
DEBUG("fido2_ctap: unknown req: %u \n", req->method);
resp->status = CTAP1_ERR_INVALID_COMMAND;
resp->len = 0x0;
break;
}
DEBUG("Resp status %d \n", resp->status);
return resp->status;
}
ctap_state_t *fido2_ctap_get_state(void)
{
return &_state;
}
ctap_status_code_t fido2_ctap_get_info(ctap_resp_t *resp)
{
assert(resp);
fido2_ctap_cbor_init_encoder(resp->data, sizeof(resp->data));
resp->status = _get_info();
resp->len = fido2_ctap_cbor_get_buffer_size(resp->data);
return resp->status;
}
ctap_status_code_t fido2_ctap_make_credential(ctap_req_t *req, ctap_resp_t *resp)
{
assert(req);
assert(resp);
fido2_ctap_cbor_init_encoder(resp->data, sizeof(resp->data));
resp->status = _make_credential(req);
resp->len = fido2_ctap_cbor_get_buffer_size(resp->data);
return resp->status;
}
ctap_status_code_t fido2_ctap_get_assertion(ctap_req_t *req, ctap_resp_t *resp)
{
assert(req);
assert(resp);
fido2_ctap_cbor_init_encoder(resp->data, sizeof(resp->data));
resp->status = _get_assertion(req);
resp->len = fido2_ctap_cbor_get_buffer_size(resp->data);
return resp->status;
}
ctap_status_code_t fido2_ctap_get_next_assertion(ctap_resp_t *resp)
{
assert(resp);
fido2_ctap_cbor_init_encoder(resp->data, sizeof(resp->data));
resp->status = _get_next_assertion();
resp->len = fido2_ctap_cbor_get_buffer_size(resp->data);
return resp->status;
}
ctap_status_code_t fido2_ctap_client_pin(ctap_req_t *req, ctap_resp_t *resp)
{
assert(req);
assert(resp);
fido2_ctap_cbor_init_encoder(resp->data, sizeof(resp->data));
resp->status = _client_pin(req);
resp->len = fido2_ctap_cbor_get_buffer_size(resp->data);
return resp->status;
}
ctap_status_code_t fido2_ctap_reset(ctap_resp_t *resp)
{
assert(resp);
resp->status = _reset();
resp->len = 0x0;
return resp->status;
}
static uint32_t get_id(void)
{
return _state.id_cnt++;
}
static int _reset(void)
{
int ret = fido2_ctap_mem_erase_flash();
if (ret != CTAP2_OK) {
return ret;
}
_state.initialized_marker = CTAP_INITIALIZED_MARKER;
_state.rem_pin_att = CTAP_PIN_MAX_ATTS;
_state.pin_is_set = false;
_state.rk_amount_stored = 0;
_state.id_cnt = 0;
_rem_pin_att_boot = CTAP_PIN_MAX_ATTS_BOOT;
/* invalidate AES CCM key */
explicit_bzero(_state.cred_key, sizeof(_state.cred_key));
_state.cred_key_is_initialized = false;
_state.config.options |= CTAP_INFO_OPTIONS_FLAG_PLAT;
_state.config.options |= CTAP_INFO_OPTIONS_FLAG_RK;
_state.config.options |= CTAP_INFO_OPTIONS_FLAG_CLIENT_PIN;
_state.config.options |= CTAP_INFO_OPTIONS_FLAG_UP;
uint8_t aaguid[CTAP_AAGUID_SIZE];
fmt_hex_bytes(aaguid, CTAP_AAGUID);
memcpy(_state.config.aaguid, aaguid, sizeof(_state.config.aaguid));
return fido2_ctap_mem_write_state_to_flash(&_state);
}
static int _make_credential(ctap_req_t *req_raw)
{
int ret;
bool uv = false;
bool up = false;
bool rk = false;
ctap_make_credential_req_t req = { 0 };
ctap_auth_data_t auth_data = { 0 };
ctap_resident_key_t k = { 0 };
if (_is_locked()) {
ret = CTAP2_ERR_PIN_BLOCKED;
goto done;
}
if (_is_boot_locked()) {
ret = CTAP2_ERR_PIN_AUTH_BLOCKED;
goto done;
}
/* set default values for options */
req.options.rk = false;
req.options.uv = false;
req.options.up = -1;
ret = fido2_ctap_cbor_parse_make_credential_req(&req, req_raw->buf, req_raw->len);
if (ret != CTAP2_OK) {
goto done;
}
/* true => authenticator is instructed to store credential on device */
rk = req.options.rk;
if (req.exclude_list_len > 0) {
if (_find_matching_rks(_assert_state.rks, CTAP_MAX_EXCLUDE_LIST_SIZE,
req.exclude_list, req.exclude_list_len, req.rp.id,
req.rp.id_len) > 0x0) {
if (!IS_ACTIVE(CONFIG_FIDO2_CTAP_DISABLE_UP)) {
fido2_ctap_utils_user_presence_test();
}
ret = CTAP2_ERR_CREDENTIAL_EXCLUDED;
goto done;
}
}
/**
* The user presence (up) check is mandatory for the MakeCredential method.
* For the MakeCredential method the up key of the options dictionary,
* which is part of the MakeCredential request, is not defined.
* Therefore setting it is invalid for this method.
*/
if (req.options.up != -1) {
ret = CTAP2_ERR_INVALID_OPTION;
goto done;
}
if (fido2_ctap_pin_is_set() && req.pin_auth_present) {
/* CTAP specification (version 20190130) section 5.5.8.1 */
if (req.pin_auth_len == 0) {
if (!IS_ACTIVE(CONFIG_FIDO2_CTAP_DISABLE_UP)) {
fido2_ctap_utils_user_presence_test();
}
ret = CTAP2_ERR_PIN_INVALID;
goto done;
}
ret = _verify_pin_auth(req.pin_auth, req.client_data_hash,
sizeof(req.client_data_hash));
if (ret != CTAP2_OK) {
goto done;
}
uv = true;
}
/* CTAP specification (version 20190130) section 5.5.8.1 */
else if (!fido2_ctap_pin_is_set() && req.pin_auth_present
&& req.pin_auth_len == 0) {
if (!IS_ACTIVE(CONFIG_FIDO2_CTAP_DISABLE_UP)) {
fido2_ctap_utils_user_presence_test();
}
ret = CTAP2_ERR_PIN_NOT_SET;
goto done;
}
else if (fido2_ctap_pin_is_set() && !req.pin_auth_present) {
ret = CTAP2_ERR_PIN_REQUIRED;
goto done;
}
if (req.pin_auth_present && !_pin_protocol_supported(req.pin_protocol)) {
ret = CTAP2_ERR_PIN_AUTH_INVALID;
goto done;
}
/* last moment where transaction can be cancelled */
#if IS_USED(MODULE_FIDO2_CTAP_TRANSPORT_HID)
if (fido2_ctap_transport_hid_should_cancel()) {
ret = CTAP2_ERR_KEEPALIVE_CANCEL;
goto done;
}
#endif
/* user presence test to create a new credential */
if (IS_ACTIVE(CONFIG_FIDO2_CTAP_DISABLE_UP)) {
up = true;
}
else {
if (fido2_ctap_utils_user_presence_test() == CTAP2_OK) {
up = true;
}
}
ret = _make_auth_data_attest(&req, &auth_data, &k, uv, up, rk);
if (ret != CTAP2_OK) {
goto done;
}
ret = fido2_ctap_cbor_encode_attestation_object(&auth_data, req.client_data_hash, &k);
if (ret != CTAP2_OK) {
goto done;
}
/* if created credential is a resident credential, save it to flash */
if (rk) {
ret = fido2_ctap_mem_write_rk_to_flash(&k);
if (ret != CTAP2_OK) {
goto done;
}
}
ret = CTAP2_OK;
done:
/* clear rk to remove private key from memory */
explicit_bzero(&k, sizeof(k));
return ret;
}
static int _get_assertion(ctap_req_t *req_raw)
{
int ret;
bool uv = false;
bool up = false;
ctap_get_assertion_req_t req = { 0 };
ctap_auth_data_header_t auth_data = { 0 };
ctap_resident_key_t *rk = NULL;
if (_is_locked()) {
ret = CTAP2_ERR_PIN_BLOCKED;
goto done;
}
if (_is_boot_locked()) {
ret = CTAP2_ERR_PIN_AUTH_BLOCKED;
goto done;
}
memset(&_assert_state, 0, sizeof(ctap_get_assertion_state_t));
/* set default values for options */
req.options.up = true;
req.options.uv = false;
ret = fido2_ctap_cbor_parse_get_assertion_req(&req, req_raw->buf, req_raw->len);
if (ret != CTAP2_OK) {
goto done;
}
/* find eligible credentials */
_assert_state.count = _find_matching_rks(_assert_state.rks,
CTAP_MAX_EXCLUDE_LIST_SIZE,
req.allow_list,
req.allow_list_len, req.rp_id,
req.rp_id_len);
if (fido2_ctap_pin_is_set() && req.pin_auth_present) {
/* CTAP specification (version 20190130) section 5.5.8.2 */
if (req.pin_auth_len == 0) {
if (!IS_ACTIVE(CONFIG_FIDO2_CTAP_DISABLE_UP)) {
fido2_ctap_utils_user_presence_test();
}
ret = CTAP2_ERR_PIN_INVALID;
goto done;
}
ret = _verify_pin_auth(req.pin_auth, req.client_data_hash,
sizeof(req.client_data_hash));
if (ret != CTAP2_OK) {
goto done;
}
uv = true;
_assert_state.uv = true;
}
/* CTAP specification (version 20190130) section 5.5.8.2 */
else if (!fido2_ctap_pin_is_set() && req.pin_auth_present
&& req.pin_auth_len == 0) {
if (!IS_ACTIVE(CONFIG_FIDO2_CTAP_DISABLE_UP)) {
fido2_ctap_utils_user_presence_test();
}
ret = CTAP2_ERR_PIN_NOT_SET;
goto done;
}
else if (fido2_ctap_pin_is_set() && !req.pin_auth_present) {
uv = false;
_assert_state.uv = false;
}
if (req.pin_auth_present && !_pin_protocol_supported(req.pin_protocol)) {
ret = CTAP2_ERR_PIN_AUTH_INVALID;
goto done;
}
if (req.options.up) {
if (IS_ACTIVE(CONFIG_FIDO2_CTAP_DISABLE_UP)) {
up = true;
_assert_state.up = true;
}
else {
if (fido2_ctap_utils_user_presence_test() == CTAP2_OK) {
up = true;
_assert_state.up = true;
}
else {
ret = CTAP2_ERR_OPERATION_DENIED;
goto done;
}
}
}
if (req.options.uv) {
ret = CTAP2_ERR_UNSUPPORTED_OPTION;
goto done;
}
if (_assert_state.count == 0) {
ret = CTAP2_ERR_NO_CREDENTIALS;
goto done;
}
memcpy(_assert_state.client_data_hash, req.client_data_hash,
SHA256_DIGEST_LENGTH);
/* most recently created eligible rk found */
rk = &_assert_state.rks[_assert_state.cred_counter++];
/* last moment where transaction can be cancelled */
#if IS_USED(MODULE_FIDO2_CTAP_TRANSPORT_HID)
if (fido2_ctap_transport_hid_should_cancel()) {
ret = CTAP2_ERR_KEEPALIVE_CANCEL;
goto done;
}
#endif
ret = _make_auth_data_assert(req.rp_id, req.rp_id_len, &auth_data, uv,
up,
rk->sign_count);
if (ret != CTAP2_OK) {
goto done;
}
ret = fido2_ctap_cbor_encode_assertion_object(&auth_data,
req.client_data_hash, rk,
_assert_state.count);
if (ret != CTAP2_OK) {
goto done;
}
rk->sign_count++;
/**
* if has_nonce is false, the credential is a resident credential and
* therefore needs to be saved on the device.
*/
if (!rk->cred_desc.has_nonce) {
ret = fido2_ctap_mem_write_rk_to_flash(rk);
if (ret != CTAP2_OK) {
goto done;
}
}
/* save current time for get_next_assertion timeout */
_assert_state.timer = ztimer_now(ZTIMER_MSEC);
ret = CTAP2_OK;
done:
/* clear rk to remove private key from memory */
if (rk) {
explicit_bzero(rk, sizeof(*rk));
}
return ret;
}
static int _get_next_assertion(void)
{
int ret;
uint32_t now;
ctap_resident_key_t *rk = NULL;
ctap_auth_data_header_t auth_data = { 0 };
if (_is_locked()) {
ret = CTAP2_ERR_PIN_BLOCKED;
goto done;
}
if (_is_boot_locked()) {
ret = CTAP2_ERR_PIN_AUTH_BLOCKED;
goto done;
}
/* no current valid assertion req pending */
if (_assert_state.timer == 0) {
ret = CTAP2_ERR_NOT_ALLOWED;
goto done;
}
if (_assert_state.cred_counter >= _assert_state.count) {
ret = CTAP2_ERR_NOT_ALLOWED;
goto done;
}
now = ztimer_now(ZTIMER_MSEC);
if (now - _assert_state.timer > CTAP_GET_NEXT_ASSERTION_TIMEOUT) {
memset(&_assert_state, 0, sizeof(_assert_state));
ret = CTAP2_ERR_NOT_ALLOWED;
goto done;
}
/* next eligible rk */
rk = &_assert_state.rks[_assert_state.cred_counter];
_assert_state.cred_counter++;
ret = _make_auth_data_next_assert(rk->rp_id_hash, &auth_data,
_assert_state.uv, _assert_state.up,
rk->sign_count);
if (ret != CTAP2_OK) {
goto done;
}
/* cred count set to 0 because omitted when get_next_assertion */
ret = fido2_ctap_cbor_encode_assertion_object(&auth_data,
_assert_state.client_data_hash, rk,
0);
if (ret != CTAP2_OK) {
goto done;
}
/* restart timer */
_assert_state.timer = ztimer_now(ZTIMER_MSEC);
rk->sign_count++;
/**
* if has_nonce is false, the credential is a resident credential and
* therefore needs to be saved on the device.
*/
if (!rk->cred_desc.has_nonce) {
ret = fido2_ctap_mem_write_rk_to_flash(rk);
if (ret != CTAP2_OK) {
goto done;
}
}
ret = CTAP2_OK;
done:
/* clear rk to remove private key from memory */
if (rk) {
explicit_bzero(rk, sizeof(*rk));
}
return ret;
}
static int _get_info(void)
{
ctap_info_t info = { 0 };
info.versions |= CTAP_VERSION_FLAG_FIDO;
info.options = _state.config.options;
info.max_msg_size = CTAP_MAX_MSG_SIZE;
info.pin_protocol = CTAP_PIN_PROT_VER;
info.pin_is_set = fido2_ctap_pin_is_set();
memcpy(info.aaguid, _state.config.aaguid, CTAP_AAGUID_SIZE);
return fido2_ctap_cbor_encode_info(&info);
}
static int _client_pin(ctap_req_t *req_raw)
{
int ret;
ctap_client_pin_req_t req = { 0 };
ret = fido2_ctap_cbor_parse_client_pin_req(&req, req_raw->buf, req_raw->len);
if (ret != CTAP2_OK) {
DEBUG("fido2_ctap: client_pin - error parsing request: %d \n", ret);
return ret;
}
/* common error handling */
if (req.sub_command != CTAP_PIN_GET_RETRIES) {
if (_is_locked()) {
return CTAP2_ERR_PIN_BLOCKED;
}
if (_is_boot_locked()) {
return CTAP2_ERR_PIN_AUTH_BLOCKED;
}
}
if (req.pin_protocol != CTAP_PIN_PROT_VER) {
return CTAP1_ERR_OTHER;
}
switch (req.sub_command) {
case CTAP_PIN_GET_RETRIES:
ret = _get_retries();
break;
case CTAP_PIN_GET_KEY_AGREEMENT:
ret = _get_key_agreement();
break;
case CTAP_PIN_SET_PIN:
ret = _set_pin(&req);
break;
case CTAP_PIN_CHANGE_PIN:
ret = _change_pin(&req);
break;
case CTAP_PIN_GET_PIN_TOKEN:
ret = _get_pin_token(&req);
break;
default:
ret = CTAP1_ERR_OTHER;
DEBUG("fido2_crap: clientpin - subcommand unknown %u \n",
req.sub_command);
}
return ret;
}
static int _get_retries(void)
{
return fido2_ctap_cbor_encode_retries(_state.rem_pin_att);
}
static int _get_key_agreement(void)
{
int ret;
ctap_public_key_cose_t key = { 0 };
/* generate key agreement key */
ret =
fido2_ctap_crypto_gen_keypair(&_state.ag_key.pub, _state.ag_key.priv,
sizeof(_state.ag_key.priv));
if (ret != CTAP2_OK) {
return ret;
}
memcpy(key.pubkey.x, _state.ag_key.pub.x, CTAP_CRYPTO_KEY_SIZE);
memcpy(key.pubkey.y, _state.ag_key.pub.y, CTAP_CRYPTO_KEY_SIZE);
key.alg_type = CTAP_COSE_ALG_ECDH_ES_HKDF_256;
key.cred_type = CTAP_PUB_KEY_CRED_PUB_KEY;
key.crv = CTAP_COSE_KEY_CRV_P256;
key.kty = CTAP_COSE_KEY_KTY_EC2;
return fido2_ctap_cbor_encode_key_agreement(&key);
}
static int _set_pin(ctap_client_pin_req_t *req)
{
uint8_t shared_key[SHA256_DIGEST_LENGTH] = { 0 };
uint8_t shared_secret[CTAP_CRYPTO_KEY_SIZE] = { 0 };
uint8_t hmac[SHA256_DIGEST_LENGTH] = { 0 };
uint8_t new_pin_dec[CTAP_PIN_MAX_SIZE] = { 0 };
size_t sz;
int ret;
if (!req->new_pin_enc_size || !req->pin_auth_present ||
!req->key_agreement_present) {
ret = CTAP2_ERR_MISSING_PARAMETER;
goto done;
}
if (fido2_ctap_pin_is_set()) {
ret = CTAP2_ERR_PIN_AUTH_INVALID;
goto done;
}
if (req->new_pin_enc_size < CTAP_PIN_ENC_MIN_SIZE) {
ret = CTAP1_ERR_OTHER;
goto done;
}
ret = fido2_ctap_crypto_ecdh(shared_secret, sizeof(shared_secret),
&req->key_agreement.pubkey, _state.ag_key.priv,
sizeof(_state.ag_key.priv));
if (ret != CTAP2_OK) {
goto done;
}
/* sha256 of shared secret ((abG).x) to obtain shared key */
ret = fido2_ctap_crypto_sha256(shared_secret, sizeof(shared_secret), shared_key);
if (ret != CTAP2_OK) {
goto done;
}
ret = fido2_ctap_crypto_hmac_sha256(shared_key, sizeof(shared_key), req->new_pin_enc,
req->new_pin_enc_size, hmac);
if (ret != CTAP2_OK) {
goto done;
}
if (memcmp(hmac, req->pin_auth, CTAP_PIN_AUTH_SZ) != 0) {
DEBUG("fido2_ctap: set pin - hmac and pin_auth differ \n");
ret = CTAP2_ERR_PIN_AUTH_INVALID;
goto done;
}
sz = sizeof(new_pin_dec);
ret = fido2_ctap_crypto_aes_dec(new_pin_dec, &sz, req->new_pin_enc,
req->new_pin_enc_size, shared_key,
sizeof(shared_key));
if (ret != CTAP2_OK) {
DEBUG("fido2_ctap: set pin - error while decrypting PIN \n");
goto done;
}
/* last moment where transaction can be cancelled */
#if IS_USED(MODULE_FIDO2_CTAP_TRANSPORT_HID)
if (fido2_ctap_transport_hid_should_cancel()) {
ret = CTAP2_ERR_KEEPALIVE_CANCEL;
goto done;
}
#endif
sz = fmt_strnlen((char *)new_pin_dec, CTAP_PIN_MAX_SIZE + 1);
if (sz < CTAP_PIN_MIN_SIZE || sz > CTAP_PIN_MAX_SIZE) {
ret = CTAP2_ERR_PIN_POLICY_VIOLATION;
goto done;
}
ret = _save_pin(new_pin_dec, sz);
if (ret != CTAP2_OK) {
return ret;
}
ret = _reset_pin_attempts();
if (ret != CTAP2_OK) {
return ret;
}
ret = CTAP2_OK;
done:
/* clear key agreement key */
explicit_bzero(&_state.ag_key, sizeof(_state.ag_key));
return ret;
}
static int _change_pin(ctap_client_pin_req_t *req)
{
int ret;
size_t sz;
hmac_context_t ctx;
uint8_t shared_key[CTAP_CRYPTO_KEY_SIZE] = { 0 };
uint8_t shared_secret[CTAP_CRYPTO_KEY_SIZE] = { 0 };
uint8_t pin_hash_dec[CTAP_PIN_TOKEN_SZ] = { 0 };
uint8_t hmac[SHA256_DIGEST_LENGTH] = { 0 };
uint8_t new_pin_dec[CTAP_PIN_MAX_SIZE] = { 0 };
if (!req->pin_auth_present || !req->key_agreement_present ||
!req->pin_hash_enc_present) {
ret = CTAP2_ERR_MISSING_PARAMETER;
goto done;
}
if (!fido2_ctap_pin_is_set()) {
ret = CTAP2_ERR_PIN_NOT_SET;
goto done;
}
if (req->new_pin_enc_size < CTAP_PIN_ENC_MIN_SIZE) {
ret = CTAP1_ERR_OTHER;
goto done;
}
/* derive shared secret */
ret = fido2_ctap_crypto_ecdh(shared_secret, sizeof(shared_secret),
&req->key_agreement.pubkey, _state.ag_key.priv,
sizeof(_state.ag_key.priv));
if (ret != CTAP2_OK) {
goto done;
}
/* sha256 of shared secret ((abG).x) to obtain shared key */
ret = fido2_ctap_crypto_sha256(shared_secret, sizeof(shared_secret), shared_key);
if (ret != CTAP2_OK) {
goto done;
}
ret = fido2_ctap_crypto_hmac_sha256_init(&ctx, shared_key, sizeof(shared_key));
if (ret != CTAP2_OK) {
goto done;
}
ret = fido2_ctap_crypto_hmac_sha256_update(&ctx, req->new_pin_enc, req->new_pin_enc_size);
if (ret != CTAP2_OK) {
goto done;
}
ret = fido2_ctap_crypto_hmac_sha256_update(&ctx, req->pin_hash_enc, sizeof(req->pin_hash_enc));
if (ret != CTAP2_OK) {
goto done;
}
ret = fido2_ctap_crypto_hmac_sha256_final(&ctx, hmac);
if (ret != CTAP2_OK) {
goto done;
}
/* verify pinAuth by comparing first 16 bytes of HMAC-SHA-256*/
if (memcmp(hmac, req->pin_auth, CTAP_PIN_AUTH_SZ) != 0) {
DEBUG("fido2_ctap: pin hmac and pin_auth differ \n");
ret = CTAP2_ERR_PIN_AUTH_INVALID;
goto done;
}
sz = sizeof(pin_hash_dec);
/* decrypt pinHashEnc */
ret = fido2_ctap_crypto_aes_dec(pin_hash_dec, &sz, req->pin_hash_enc,
sizeof(req->pin_hash_enc), shared_key,
sizeof(shared_key));
if (ret != CTAP2_OK) {
DEBUG("fido2_ctap: set pin - error while decrypting pin hash \n");
goto done;
}
/* last moment where transaction can be cancelled */
#if IS_USED(MODULE_FIDO2_CTAP_TRANSPORT_HID)
if (fido2_ctap_transport_hid_should_cancel()) {
ret = CTAP2_ERR_KEEPALIVE_CANCEL;
goto done;
}
#endif
/* verify decrypted pinHash against LEFT(SHA-256(curPin), 16) */
if (memcmp(pin_hash_dec, _state.pin_hash, CTAP_PIN_TOKEN_SZ) != 0) {
DEBUG("fido2_ctap: _get_pin_token - invalid pin \n");
/* reset key agreement key */
ret =
fido2_ctap_crypto_gen_keypair(&_state.ag_key.pub, _state.ag_key.priv,
sizeof(_state.ag_key.priv));
if (ret != CTAP2_OK) {
goto done;
}
ret = fido2_ctap_mem_write_state_to_flash(&_state);
if (ret != CTAP2_OK) {
goto done;
}
ret = _decrement_pin_attempts();
if (ret != CTAP2_OK) {
goto done;
}
ret = CTAP2_ERR_PIN_INVALID;
goto done;
}
ret = _reset_pin_attempts();
if (ret != CTAP2_OK) {
goto done;
}
sz = sizeof(new_pin_dec);
/* decrypt newPinEnc to obtain newPin */
ret = fido2_ctap_crypto_aes_dec(new_pin_dec, &sz, req->new_pin_enc,
req->new_pin_enc_size, shared_key,
sizeof(shared_key));
if (ret != CTAP2_OK) {
DEBUG("fido2_ctap: set pin - error while decrypting new PIN \n");
goto done;
}
sz = fmt_strnlen((char *)new_pin_dec, CTAP_PIN_MAX_SIZE + 1);
if (sz < CTAP_PIN_MIN_SIZE || sz > CTAP_PIN_MAX_SIZE) {
ret = CTAP2_ERR_PIN_POLICY_VIOLATION;
goto done;
}
ret = _save_pin(new_pin_dec, sz);
if (ret != CTAP2_OK) {
goto done;
}
ret = CTAP2_OK;
done:
/* clear key agreement key */
explicit_bzero(&_state.ag_key, sizeof(_state.ag_key));
return ret;
}
static int _get_pin_token(ctap_client_pin_req_t *req)
{
uint8_t shared_key[SHA256_DIGEST_LENGTH] = { 0 };
uint8_t shared_secret[CTAP_CRYPTO_KEY_SIZE] = { 0 };
uint8_t pin_hash_dec[CTAP_PIN_TOKEN_SZ] = { 0 };
uint8_t pin_token_enc[CTAP_PIN_TOKEN_SZ] = { 0 };
int ret;
size_t sz;
if (!fido2_ctap_pin_is_set()) {
ret = CTAP2_ERR_PIN_NOT_SET;
goto done;
}
if (!req->key_agreement_present || !req->pin_hash_enc_present) {
ret = CTAP2_ERR_MISSING_PARAMETER;
goto done;
}
ret = fido2_ctap_crypto_ecdh(shared_secret, sizeof(shared_secret),
&req->key_agreement.pubkey, _state.ag_key.priv,
sizeof(_state.ag_key.priv));
if (ret != CTAP2_OK) {
goto done;
}
/* last moment where transaction can be cancelled */
#if IS_USED(MODULE_FIDO2_CTAP_TRANSPORT_HID)
if (fido2_ctap_transport_hid_should_cancel()) {
ret = CTAP2_ERR_KEEPALIVE_CANCEL;
goto done;
}
#endif
/* sha256 of shared secret ((abG).x) to obtain shared key */
ret = fido2_ctap_crypto_sha256(shared_secret, sizeof(shared_secret), shared_key);
if (ret != CTAP2_OK) {
goto done;
}
sz = sizeof(pin_hash_dec);
ret = fido2_ctap_crypto_aes_dec(pin_hash_dec, &sz, req->pin_hash_enc,
sizeof(req->pin_hash_enc), shared_key,
sizeof(shared_key));
if (ret != CTAP2_OK) {
DEBUG("set pin: error while decrypting pin hash \n");
goto done;
}
if (memcmp(pin_hash_dec, _state.pin_hash, sizeof(_state.pin_hash)) != 0) {
DEBUG("fido2_ctap: _get_pin_token - invalid pin \n");
/* reset key agreement key */
ret =
fido2_ctap_crypto_gen_keypair(&_state.ag_key.pub, _state.ag_key.priv,
sizeof(_state.ag_key.priv));
if (ret != CTAP2_OK) {
goto done;
}
ret = _decrement_pin_attempts();
if (ret != CTAP2_OK) {
goto done;
}
ret = CTAP2_ERR_PIN_INVALID;
goto done;
}
ret = _reset_pin_attempts();
if (ret != CTAP2_OK) {
goto done;
}
sz = sizeof(pin_token_enc);
ret = fido2_ctap_crypto_aes_enc(pin_token_enc, &sz, _pin_token,
sizeof(_pin_token), shared_key,
sizeof(shared_key));
if (ret != CTAP2_OK) {
DEBUG("get pin token: error encrypting pin token \n");
goto done;
}
ret = fido2_ctap_cbor_encode_pin_token(pin_token_enc,
sizeof(pin_token_enc));
done:
/* clear key agreement key */
explicit_bzero(&_state.ag_key, sizeof(_state.ag_key));
return ret;
}
static int _save_pin(uint8_t *pin, size_t len)
{
uint8_t buf[SHA256_DIGEST_LENGTH] = { 0 };
int ret;
/* store LEFT(SHA-256(newPin), 16) */
ret = fido2_ctap_crypto_sha256(pin, len, buf);
if (ret != CTAP2_OK) {
return ret;
}
memcpy(_state.pin_hash, buf, sizeof(_state.pin_hash));
_state.pin_is_set = true;
return fido2_ctap_mem_write_state_to_flash(&_state);
}
bool fido2_ctap_cred_params_supported(uint8_t cred_type, int32_t alg_type)
{
if (cred_type == CTAP_PUB_KEY_CRED_PUB_KEY) {
if (alg_type == CTAP_COSE_ALG_ES256) {
return true;
}
}
return false;
}
static inline bool _pin_protocol_supported(uint8_t version)
{
return version == CTAP_PIN_PROT_VER;
}
bool fido2_ctap_pin_is_set(void)
{
return _state.pin_is_set;
}
static inline bool _is_locked(void)
{
return _state.rem_pin_att <= 0;
}
static inline bool _is_boot_locked(void)
{
return _rem_pin_att_boot <= 0;
}
static int _decrement_pin_attempts(void)
{
int ret;
if (_state.rem_pin_att > 0) {
_state.rem_pin_att--;
}
if (_rem_pin_att_boot > 0) {
_rem_pin_att_boot--;
}
ret = fido2_ctap_mem_write_state_to_flash(&_state);
if (ret != CTAP2_OK) {
return ret;
}
if (_is_locked()) {
return CTAP2_ERR_PIN_BLOCKED;
}
if (_is_boot_locked()) {
return CTAP2_ERR_PIN_AUTH_BLOCKED;
}
return CTAP2_OK;
}
static int _reset_pin_attempts(void)
{
_state.rem_pin_att = CTAP_PIN_MAX_ATTS;
_rem_pin_att_boot = CTAP_PIN_MAX_ATTS_BOOT;
return fido2_ctap_mem_write_state_to_flash(&_state);
}
static int _verify_pin_auth(uint8_t *auth, uint8_t *hash, size_t len)
{
int ret;
uint8_t hmac[SHA256_DIGEST_LENGTH] = { 0 };
ret = fido2_ctap_crypto_hmac_sha256(_pin_token, sizeof(_pin_token), hash, len, hmac);
if (ret != CTAP2_OK) {
return ret;
}
if (memcmp(auth, hmac, CTAP_PIN_AUTH_SZ) != 0) {
return CTAP2_ERR_PIN_AUTH_INVALID;
}
return CTAP2_OK;
}
static int _find_matching_rks(ctap_resident_key_t *rks, size_t rks_len,
ctap_cred_desc_alt_t *allow_list,
size_t allow_list_len, uint8_t *rp_id,
size_t rp_id_len)
{
uint8_t index = 0;
uint8_t rp_id_hash[SHA256_DIGEST_LENGTH] = { 0 };
int ret;
ret = fido2_ctap_crypto_sha256(rp_id, rp_id_len, rp_id_hash);
if (ret != CTAP2_OK) {
return ret;
}
/* no rks stored, try decrypt only */
if (_state.rk_amount_stored == 0) {
for (uint16_t i = 0; i < allow_list_len; i++) {
ret = _ctap_decrypt_rk(&rks[index], &allow_list[i].cred_id);
if (ret == CTAP2_OK) {
if (memcmp(rks[index].rp_id_hash, rp_id_hash,
SHA256_DIGEST_LENGTH) == 0) {
index++;
}
}
}
}
ctap_resident_key_t rk = { 0 };
uint32_t addr = 0x0;
while (fido2_ctap_mem_read_rk_from_flash(&rk, rp_id_hash, &addr) == CTAP2_OK) {
if (allow_list_len == 0) {
memcpy(&rks[index], &rk, sizeof(rk));
index++;
}
for (size_t i = 0; i < allow_list_len; i++) {
/* if allow list is present, check that cred_id is in list */
if (memcmp(allow_list[i].cred_id.id, rk.cred_desc.cred_id,
sizeof(rk.cred_desc.cred_id)) == 0) {
memcpy(&rks[index], &rk, sizeof(rk));
index++;
break;
}
else {
/* no match with stored key, try to decrypt */
ret = _ctap_decrypt_rk(&rks[index],
&allow_list[i].cred_id);
if (ret == CTAP2_OK) {
if (memcmp(rks[index].rp_id_hash, rk.rp_id_hash,
SHA256_DIGEST_LENGTH) == 0) {
index++;
break;
}
}
}
}
if (index >= rks_len) {
break;
}
}
/**
* Sort in descending order based on id. Credential with the
* highest (most recent) id will be first in list.
*/
if (index > 0) {
qsort(rks, index, sizeof(ctap_resident_key_t), fido2_ctap_utils_cred_cmp);
}
return index;
}
static int _make_auth_data_assert(uint8_t *rp_id, size_t rp_id_len,
ctap_auth_data_header_t *auth_data, bool uv,
bool up, uint32_t sign_count)
{
int ret;
ret = fido2_ctap_crypto_sha256(rp_id, rp_id_len, auth_data->rp_id_hash);
if (ret != CTAP2_OK) {
return ret;
}
auth_data->sign_count = htonl(sign_count);
if (up) {
auth_data->flags |= CTAP_AUTH_DATA_FLAG_UP;
}
if (uv) {
auth_data->flags |= CTAP_AUTH_DATA_FLAG_UV;
}
return CTAP2_OK;
}
static int _make_auth_data_next_assert(uint8_t *rp_id_hash,
ctap_auth_data_header_t *auth_data,
bool uv, bool up, uint32_t sign_count)
{
memcpy(auth_data->rp_id_hash, rp_id_hash, sizeof(auth_data->rp_id_hash));
auth_data->sign_count = htonl(sign_count);
if (up) {
auth_data->flags |= CTAP_AUTH_DATA_FLAG_UP;
}
if (uv) {
auth_data->flags |= CTAP_AUTH_DATA_FLAG_UV;
}
return CTAP2_OK;
}
static int _make_auth_data_attest(ctap_make_credential_req_t *req,
ctap_auth_data_t *auth_data,
ctap_resident_key_t *k,
bool uv, bool up, bool rk)
{
int ret;
/* device aaguid */
uint8_t aaguid[] = { CTAP_AAGUID };
ctap_auth_data_header_t *auth_header = &auth_data->header;
ctap_attested_cred_data_t *cred_data = &auth_data->attested_cred_data;
ctap_attested_cred_data_header_t *cred_header = &cred_data->header;
ctap_rp_ent_t *rp = &req->rp;
ctap_user_ent_t *user = &req->user;
memset(k, 0, sizeof(*k));
memset(auth_data, 0, sizeof(*auth_data));
ret = fido2_ctap_crypto_sha256(rp->id, rp->id_len, auth_header->rp_id_hash);
if (ret != CTAP2_OK) {
return ret;
}
/* set flag indicating that attested credential data included */
auth_header->flags |= CTAP_AUTH_DATA_FLAG_AT;
if (up) {
auth_header->flags |= CTAP_AUTH_DATA_FLAG_UP;
}
if (uv) {
auth_header->flags |= CTAP_AUTH_DATA_FLAG_UV;
}
auth_header->sign_count = 0;
memcpy(cred_header->aaguid, aaguid, sizeof(cred_header->aaguid));
ret =
fido2_ctap_crypto_gen_keypair(&cred_data->key.pubkey, k->priv_key,
sizeof(_state.ag_key.priv));
if (ret != CTAP2_OK) {
return ret;
}
cred_data->key.alg_type = req->alg_type;
cred_data->key.cred_type = req->cred_type;
cred_data->key.crv = CTAP_COSE_KEY_CRV_P256;
cred_data->key.kty = CTAP_COSE_KEY_KTY_EC2;
/* init key */
k->cred_desc.cred_type = req->cred_type;
k->user_id_len = user->id_len;
k->id = get_id();
memcpy(k->user_id, user->id, user->id_len);
memcpy(k->rp_id_hash, auth_header->rp_id_hash, SHA256_DIGEST_LENGTH);
if (rk) {
/* generate credential id as 16 random bytes */
ret = fido2_ctap_crypto_prng(cred_header->cred_id.id,
CTAP_CREDENTIAL_ID_SIZE);
if (ret != CTAP2_OK) {
return ret;
}
memcpy(k->cred_desc.cred_id, cred_header->cred_id.id,
sizeof(k->cred_desc.cred_id));
cred_header->cred_len_h = (CTAP_CREDENTIAL_ID_SIZE & 0xff00) >> 8;
cred_header->cred_len_l = CTAP_CREDENTIAL_ID_SIZE & 0x00ff;
}
else {
/* generate credential id by encrypting resident key */
uint8_t nonce[CTAP_AES_CCM_NONCE_SIZE];
ret = fido2_ctap_crypto_prng(nonce, sizeof(nonce));
if (ret != CTAP2_OK) {
return ret;
}
ret = fido2_ctap_encrypt_rk(k, nonce, sizeof(nonce),
&cred_header->cred_id);
if (ret != CTAP2_OK) {
return ret;
}
cred_header->cred_len_h = (sizeof(cred_header->cred_id) & 0xff00) >> 8;
cred_header->cred_len_l = sizeof(cred_header->cred_id) & 0x00ff;
}
return CTAP2_OK;
}
int fido2_ctap_encrypt_rk(ctap_resident_key_t *rk, uint8_t *nonce,
size_t nonce_len, ctap_cred_id_t *id)
{
assert(rk);
assert(nonce);
assert(id);
int ret;
/**
* If not initialized, create a new AES_CCM key to be able to encrypt a
* credential when it is not a resident credential and therefore
* will be stored by the relying party.
*/
if (!_state.cred_key_is_initialized) {
ret = fido2_ctap_crypto_prng(_state.cred_key, sizeof(_state.cred_key));
if (ret != CTAP2_OK) {
return ret;
}
_state.cred_key_is_initialized = true;
ret = fido2_ctap_mem_write_state_to_flash(&_state);
if (ret != CTAP2_OK) {
return ret;
}
}
ret = fido2_ctap_crypto_aes_ccm_enc((uint8_t *)id, sizeof(id),
(uint8_t *)rk,
CTAP_CREDENTIAL_ID_ENC_SIZE,
NULL, 0,
CCM_MAC_MAX_LEN, CTAP_AES_CCM_L,
nonce, nonce_len,
_state.cred_key,
sizeof(_state.cred_key));
if (ret != CTAP2_OK) {
return ret;
}
memcpy(id->nonce, nonce, CTAP_AES_CCM_NONCE_SIZE);
return CTAP2_OK;
}
static int _ctap_decrypt_rk(ctap_resident_key_t *rk, ctap_cred_id_t *id)
{
int ret;
ret = fido2_ctap_crypto_aes_ccm_dec((uint8_t *)rk, sizeof(*rk),
(uint8_t *)id,
sizeof(id->id) + sizeof(id->mac),
NULL, 0,
CCM_MAC_MAX_LEN, CTAP_AES_CCM_L,
id->nonce, sizeof(id->nonce),
_state.cred_key,
sizeof(_state.cred_key));
if (ret != CTAP2_OK) {
return ret;
}
/* store nonce in key to be able to later encrypt again */
memcpy(rk->cred_desc.nonce, id->nonce, CTAP_AES_CCM_NONCE_SIZE);
rk->cred_desc.has_nonce = true;
return CTAP2_OK;
}
int fido2_ctap_get_sig(const uint8_t *auth_data, size_t auth_data_len,
const uint8_t *client_data_hash,
const ctap_resident_key_t *rk,
uint8_t *sig, size_t *sig_len)
{
assert(auth_data);
assert(client_data_hash);
assert(rk);
assert(sig);
assert(sig_len);
int ret;
sha256_context_t ctx;
uint8_t hash[SHA256_DIGEST_LENGTH];
ret = fido2_ctap_crypto_sha256_init(&ctx);
if (ret != CTAP2_OK) {
return ret;
}
ret = fido2_ctap_crypto_sha256_update(&ctx, auth_data, auth_data_len);
if (ret != CTAP2_OK) {
return ret;
}
ret = fido2_ctap_crypto_sha256_update(&ctx, client_data_hash, SHA256_DIGEST_LENGTH);
if (ret != CTAP2_OK) {
return ret;
}
ret = fido2_ctap_crypto_sha256_final(&ctx, hash);
if (ret != CTAP2_OK) {
return ret;
}
return fido2_ctap_crypto_get_sig(hash, sizeof(hash), sig, sig_len,
rk->priv_key,
sizeof(rk->priv_key));
}