mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
724 lines
20 KiB
C
724 lines
20 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_transport_hid
|
||
|
* @{
|
||
|
* @file
|
||
|
*
|
||
|
* @author Nils Ollrogge <nils.ollrogge@fu-berlin.de>
|
||
|
* @}
|
||
|
*/
|
||
|
|
||
|
#include <string.h>
|
||
|
|
||
|
#include "xtimer.h"
|
||
|
#include "usb/usbus.h"
|
||
|
#include "usb/usbus/hid_io.h"
|
||
|
|
||
|
#include "fido2/ctap.h"
|
||
|
#include "fido2/ctap/transport/hid/ctap_hid.h"
|
||
|
#include "fido2/ctap/ctap_utils.h"
|
||
|
|
||
|
#define ENABLE_DEBUG (0)
|
||
|
#include "debug.h"
|
||
|
|
||
|
/**
|
||
|
* @brief CTAP HID report descriptor
|
||
|
*
|
||
|
* CTAP specification (version 20190130) section 8.1.8.2
|
||
|
*/
|
||
|
const uint8_t _hid_report_desc[] = {
|
||
|
0x06, 0xD0, 0xF1, /**< HID_UsagePage ( FIDO_USAGE_PAGE ) */
|
||
|
0x09, 0x01, /**< HID_Usage ( FIDO_USAGE_CTAPHID ) */
|
||
|
0xA1, 0x01, /**< HID_Collection ( HID_Application ) */
|
||
|
0x09, 0x20, /**< HID_Usage ( FIDO_USAGE_DATA_IN ) */
|
||
|
0x15, 0x00, /**< HID_LogicalMin ( 0 ) */
|
||
|
0x26, 0xFF, 0x00, /**< HID_LogicalMaxS ( 0xff ) */
|
||
|
0x75, 0x08, /**< HID_ReportSize ( 8 ) */
|
||
|
0x95, 0x40, /**< HID_ReportCount ( HID_INPUT_REPORT_BYTES ) */
|
||
|
0x81, 0x02, /**< HID_Input ( HID_Data | HID_Absolute | HID_Variable ) */
|
||
|
0x09, 0x21, /**< HID_Usage ( FIDO_USAGE_DATA_OUT ) */
|
||
|
0x15, 0x00, /**< HID_LogicalMin ( 0 ) */
|
||
|
0x26, 0xFF, 0x00, /**< HID_LogicalMaxS ( 0xff ) */
|
||
|
0x75, 0x08, /**< HID_ReportSize ( 8 ) */
|
||
|
0x95, 0x40, /**< HID_ReportCount ( HID_OUTPUT_REPORT_BYTES ) */
|
||
|
0x91, 0x02, /**< HID_Output ( HID_Data | HID_Absolute | HID_Variable ) */
|
||
|
0xC0, /**< HID_EndCollection */
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* @brief CTAP_HID buffer struct
|
||
|
*
|
||
|
*/
|
||
|
typedef struct {
|
||
|
uint32_t cid; /**< channel identifier */
|
||
|
uint8_t cmd; /**< CTAP_HID command */
|
||
|
uint8_t buffer[CTAP_HID_BUFFER_SIZE]; /**< data buffer */
|
||
|
uint16_t offset; /**< current offset into data buffer */
|
||
|
int16_t seq; /**< current sequence number */
|
||
|
uint16_t bcnt; /**< expected amount of bytes to be received */
|
||
|
uint8_t err; /**< error type if error */
|
||
|
bool is_locked; /**< buffer is locked by transaction */
|
||
|
bool should_cancel; /**< flag if current transaction should be cancelled */
|
||
|
} ctap_hid_state_t;
|
||
|
|
||
|
/**
|
||
|
* @brief Serialize data and transmit it via USB HID layer
|
||
|
*/
|
||
|
static void _ctap_hid_write(uint8_t cmd, uint32_t cid, const void *_data, size_t size);
|
||
|
|
||
|
/**
|
||
|
* @brief CTAPHID_CBOR command
|
||
|
*
|
||
|
* CTAP specification (version 20190130) section 8.1.9.1.2
|
||
|
*/
|
||
|
static void _handle_cbor_packet(uint8_t cmd, uint32_t cid, uint8_t *buf, uint16_t bcnt);
|
||
|
|
||
|
/**
|
||
|
* @brief CTAPHID_INIT command
|
||
|
*
|
||
|
* CTAP specification (version 20190130) section 8.1.9.1.3
|
||
|
*/
|
||
|
static uint32_t _handle_init_packet(uint32_t cid, uint16_t bcnt,
|
||
|
const uint8_t *nonce);
|
||
|
|
||
|
/**
|
||
|
* @brief CTAPHID_WINK command
|
||
|
*
|
||
|
* CTAP specification (version 20190130) section 8.1.9.2.1
|
||
|
*/
|
||
|
static void _wink(uint32_t cid, uint8_t cmd);
|
||
|
|
||
|
/**
|
||
|
* @brief Encode response to CTAPHID_INIT command
|
||
|
*/
|
||
|
static void _send_init_response(uint32_t cid_old, uint32_t cid_new,
|
||
|
const uint8_t *nonce);
|
||
|
|
||
|
/**
|
||
|
* @brief Clear the CTAP packet buffer
|
||
|
*/
|
||
|
static void _clear_ctap_buffer(void);
|
||
|
|
||
|
/**
|
||
|
* @brief Buffer packet belonging to currently processed transaction
|
||
|
*/
|
||
|
static uint8_t _buffer_pkt(const ctap_hid_pkt_t *pkt);
|
||
|
|
||
|
/**
|
||
|
* @brief Send error code to cid
|
||
|
*/
|
||
|
static void _send_error_response(uint32_t cid, uint8_t err);
|
||
|
|
||
|
/**
|
||
|
* @brief Refresh the last_used timestamp for this cid
|
||
|
*/
|
||
|
static int8_t _refresh_cid(uint32_t cid);
|
||
|
|
||
|
/**
|
||
|
* @brief Allocate a new logical channel
|
||
|
*/
|
||
|
static int8_t _add_cid(uint32_t cid);
|
||
|
|
||
|
/**
|
||
|
* @brief Delete logical channel
|
||
|
*/
|
||
|
static int8_t _delete_cid(uint32_t cid);
|
||
|
|
||
|
/**
|
||
|
* @brief Check if a logical channel with cid exists
|
||
|
*/
|
||
|
static bool _cid_exists(uint32_t cid);
|
||
|
|
||
|
/**
|
||
|
* @brief Parse packet length from pkt
|
||
|
*/
|
||
|
static inline uint16_t _get_packet_len(const ctap_hid_pkt_t *pkt);
|
||
|
|
||
|
/**
|
||
|
* @brief Process CTAPHID transaction
|
||
|
*/
|
||
|
static void _process_transaction(event_t *arg);
|
||
|
|
||
|
/**
|
||
|
* @brief Check if packet is an initialization packet
|
||
|
*/
|
||
|
static inline bool _is_init_type_pkt(const ctap_hid_pkt_t *pkt);
|
||
|
|
||
|
/* usbus functionality */
|
||
|
|
||
|
/**
|
||
|
* @brief USB stack
|
||
|
*/
|
||
|
static char _usb_stack[USBUS_STACKSIZE];
|
||
|
|
||
|
/**
|
||
|
* @brief USBUS context
|
||
|
*/
|
||
|
static usbus_t _usbus;
|
||
|
|
||
|
/**
|
||
|
* @brief Indicate if authenticator is busy processing a transactions
|
||
|
*
|
||
|
* Transactions are atomic, therefore only 1 transaction can be processed at
|
||
|
* once
|
||
|
*/
|
||
|
static bool _is_busy = false;
|
||
|
|
||
|
/**
|
||
|
* @brief State for handling transactions
|
||
|
*/
|
||
|
static ctap_hid_state_t _state;
|
||
|
|
||
|
/**
|
||
|
* @brief Logical CTAPHID channels
|
||
|
*/
|
||
|
static ctap_hid_cid_t g_cids[CTAP_HID_CIDS_MAX];
|
||
|
|
||
|
/**
|
||
|
* @brief Incremental channel ids
|
||
|
*
|
||
|
* channel id 0 is reserved
|
||
|
*/
|
||
|
static uint32_t _cid = 1;
|
||
|
|
||
|
/**
|
||
|
* @brief CTAP transport layer event queue
|
||
|
*/
|
||
|
static event_queue_t *_queue;
|
||
|
|
||
|
/**
|
||
|
* @brief CTAPHID event
|
||
|
*/
|
||
|
static event_t _ctap_hid_event = { .handler = _process_transaction };
|
||
|
|
||
|
/**
|
||
|
* @brief USBUS context
|
||
|
*/
|
||
|
static usbus_t _usbus;
|
||
|
|
||
|
static void _usb_cb(void *arg)
|
||
|
{
|
||
|
(void)arg;
|
||
|
|
||
|
uint8_t buffer[CONFIG_USBUS_HID_INTERRUPT_EP_SIZE];
|
||
|
int read;
|
||
|
|
||
|
read = usb_hid_io_read(buffer, CONFIG_USBUS_HID_INTERRUPT_EP_SIZE);
|
||
|
|
||
|
if (read == CONFIG_USBUS_HID_INTERRUPT_EP_SIZE) {
|
||
|
fido2_ctap_transport_hid_handle_packet(buffer);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void fido2_ctap_transport_hid_init(event_queue_t *queue)
|
||
|
{
|
||
|
_queue = queue;
|
||
|
usbdev_t *usbdev = usbdev_get_ctx(0);
|
||
|
|
||
|
assert(usbdev);
|
||
|
usbus_init(&_usbus, usbdev);
|
||
|
usb_hid_io_init(&_usbus, _hid_report_desc, sizeof(_hid_report_desc));
|
||
|
usb_hid_io_set_rx_cb(_usb_cb, NULL);
|
||
|
usbus_create(_usb_stack, sizeof(_usb_stack), USBUS_PRIO, USBUS_TNAME, &_usbus);
|
||
|
}
|
||
|
|
||
|
void fido2_ctap_transport_hid_handle_packet(void *pkt_raw)
|
||
|
{
|
||
|
ctap_hid_pkt_t *pkt = (ctap_hid_pkt_t *)pkt_raw;
|
||
|
uint32_t cid = pkt->cid;
|
||
|
uint8_t status = CTAP_HID_BUFFER_STATUS_BUFFERING;
|
||
|
|
||
|
if (cid == 0x00) {
|
||
|
/* cid = 0x00 always invalid */
|
||
|
_send_error_response(cid, CTAP_HID_ERR_INVALID_CHANNEL);
|
||
|
return;
|
||
|
}
|
||
|
else if (_is_busy) {
|
||
|
if (_state.cid == cid) {
|
||
|
/* CTAP specification (version 20190130) section 8.1.5.3 */
|
||
|
if (_is_init_type_pkt(pkt)) {
|
||
|
if (pkt->init.cmd == CTAP_HID_COMMAND_INIT) {
|
||
|
/* abort */
|
||
|
_clear_ctap_buffer();
|
||
|
status = _buffer_pkt(pkt);
|
||
|
}
|
||
|
else if (_state.is_locked && pkt->init.cmd ==
|
||
|
CTAP_HID_COMMAND_CANCEL) {
|
||
|
_state.should_cancel = true;
|
||
|
}
|
||
|
/* random init type pkt. invalid sequence of pkts */
|
||
|
else {
|
||
|
_send_error_response(cid, CTAP_HID_ERR_INVALID_SEQ);
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
/* packet for this cid is currently being worked */
|
||
|
else if (_state.is_locked) {
|
||
|
_send_error_response(cid, CTAP_HID_ERR_CHANNEL_BUSY);
|
||
|
return;
|
||
|
}
|
||
|
else {
|
||
|
/* buffer cont packets */
|
||
|
status = _buffer_pkt(pkt);
|
||
|
}
|
||
|
}
|
||
|
/* transactions are atomic. Deny all other cids if busy with one cid */
|
||
|
else {
|
||
|
_send_error_response(cid, CTAP_HID_ERR_CHANNEL_BUSY);
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
/* first init packet received starts a transaction */
|
||
|
if (_is_init_type_pkt(pkt)) {
|
||
|
_is_busy = true;
|
||
|
status = _buffer_pkt(pkt);
|
||
|
}
|
||
|
/* ignore rest */
|
||
|
}
|
||
|
|
||
|
if (status == CTAP_HID_BUFFER_STATUS_ERROR) {
|
||
|
_send_error_response(cid, _state.err);
|
||
|
_delete_cid(cid);
|
||
|
_clear_ctap_buffer();
|
||
|
_is_busy = false;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
/* pkt->init.bcnt bytes have been received. Transaction can now be processed */
|
||
|
if (status == CTAP_HID_BUFFER_STATUS_DONE) {
|
||
|
_state.is_locked = 1;
|
||
|
event_post(_queue, &_ctap_hid_event);
|
||
|
_is_busy = false;
|
||
|
}
|
||
|
else {
|
||
|
/* refresh timestamp of cid that is being buffered */
|
||
|
_refresh_cid(_state.cid);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static uint8_t _buffer_pkt(const ctap_hid_pkt_t *pkt)
|
||
|
{
|
||
|
if (_is_init_type_pkt(pkt)) {
|
||
|
/**
|
||
|
* broadcast cid only allowed for CTAP_HID_COMMAND_INIT
|
||
|
*/
|
||
|
if (pkt->cid == CTAP_HID_BROADCAST_CID &&
|
||
|
pkt->init.cmd != CTAP_HID_COMMAND_INIT) {
|
||
|
_send_error_response(pkt->cid, CTAP_HID_ERR_INVALID_CHANNEL);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* received CTAP_HID_COMMAND_CANCEL while buffering packet.
|
||
|
* Cancel request.
|
||
|
*/
|
||
|
if (pkt->init.cmd == CTAP_HID_COMMAND_CANCEL && !_state.is_locked &&
|
||
|
pkt->cid == _state.cid) {
|
||
|
|
||
|
_state.err = CTAP2_ERR_KEEPALIVE_CANCEL;
|
||
|
return CTAP_HID_BUFFER_STATUS_ERROR;
|
||
|
}
|
||
|
|
||
|
_state.bcnt = _get_packet_len(pkt);
|
||
|
|
||
|
/* check for init transaction size described in CTAP specification
|
||
|
(version 20190130) section 8.1.9.1.3 */
|
||
|
if (pkt->init.cmd == CTAP_HID_COMMAND_INIT && _state.bcnt != 8) {
|
||
|
_state.err = CTAP_HID_ERR_INVALID_LEN;
|
||
|
return CTAP_HID_BUFFER_STATUS_ERROR;
|
||
|
}
|
||
|
|
||
|
/* don't allow transactions bigger than max buffer size */
|
||
|
if (_state.bcnt > CTAP_HID_BUFFER_SIZE) {
|
||
|
_state.err = CTAP_HID_ERR_INVALID_LEN;
|
||
|
return CTAP_HID_BUFFER_STATUS_ERROR;
|
||
|
}
|
||
|
|
||
|
uint16_t size = (_state.bcnt < CTAP_HID_INIT_PAYLOAD_SIZE) ?
|
||
|
_state.bcnt : CTAP_HID_INIT_PAYLOAD_SIZE;
|
||
|
_state.cmd = pkt->init.cmd;
|
||
|
_state.cid = pkt->cid;
|
||
|
_state.seq = -1;
|
||
|
memcpy(_state.buffer, pkt->init.payload, size);
|
||
|
_state.offset = size;
|
||
|
}
|
||
|
else {
|
||
|
int left = _state.bcnt - _state.offset;
|
||
|
int diff = left - CTAP_HID_CONT_PAYLOAD_SIZE;
|
||
|
_state.seq++;
|
||
|
|
||
|
/* seqs have to increase sequentially */
|
||
|
if (pkt->cont.seq != _state.seq) {
|
||
|
_state.err = CTAP_HID_ERR_INVALID_SEQ;
|
||
|
return CTAP_HID_BUFFER_STATUS_ERROR;
|
||
|
}
|
||
|
|
||
|
/* check for potential buffer overflow */
|
||
|
if (_state.offset + CTAP_HID_CONT_PAYLOAD_SIZE > CTAP_HID_BUFFER_SIZE) {
|
||
|
_state.err = CTAP_HID_ERR_INVALID_LEN;
|
||
|
return CTAP_HID_BUFFER_STATUS_ERROR;
|
||
|
}
|
||
|
|
||
|
if (diff <= 0) {
|
||
|
memcpy(_state.buffer + _state.offset, pkt->cont.payload, left);
|
||
|
_state.offset += left;
|
||
|
}
|
||
|
else {
|
||
|
memcpy(_state.buffer + _state.offset, pkt->cont.payload,
|
||
|
CTAP_HID_CONT_PAYLOAD_SIZE);
|
||
|
_state.offset += CTAP_HID_CONT_PAYLOAD_SIZE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return _state.offset == _state.bcnt ?
|
||
|
CTAP_HID_BUFFER_STATUS_DONE : CTAP_HID_BUFFER_STATUS_BUFFERING;
|
||
|
}
|
||
|
|
||
|
static void _process_transaction(event_t *arg)
|
||
|
{
|
||
|
(void)arg;
|
||
|
uint8_t *buf = (uint8_t *)&_state.buffer;
|
||
|
uint32_t cid = _state.cid;
|
||
|
uint16_t bcnt = _state.bcnt;
|
||
|
uint8_t cmd = _state.cmd;
|
||
|
|
||
|
if (cmd == CTAP_HID_COMMAND_INIT) {
|
||
|
_handle_init_packet(cid, bcnt, buf);
|
||
|
}
|
||
|
else {
|
||
|
/* readding deleted cid */
|
||
|
if (!_cid_exists(cid) && _add_cid(cid) == -1) {
|
||
|
_send_error_response(cid, CTAP_HID_ERR_CHANNEL_BUSY);
|
||
|
}
|
||
|
else {
|
||
|
switch (cmd) {
|
||
|
case CTAP_HID_COMMAND_MSG:
|
||
|
/* not implemented as of now */
|
||
|
DEBUG("CTAP_HID: MSG COMMAND \n");
|
||
|
_send_error_response(cid, CTAP_HID_ERR_INVALID_CMD);
|
||
|
break;
|
||
|
case CTAP_HID_COMMAND_CBOR:
|
||
|
DEBUG("CTAP_HID: CBOR COMMAND \n");
|
||
|
_handle_cbor_packet(cmd, cid, buf, bcnt);
|
||
|
break;
|
||
|
case CTAP_HID_COMMAND_WINK:
|
||
|
DEBUG("CTAP_HID: wink \n");
|
||
|
_wink(cid, cmd);
|
||
|
break;
|
||
|
case CTAP_HID_COMMAND_PING:
|
||
|
DEBUG("CTAP_HID: PING \n");
|
||
|
_ctap_hid_write(cmd, cid, buf, bcnt);
|
||
|
break;
|
||
|
case CTAP_HID_COMMAND_CANCEL:
|
||
|
/*
|
||
|
* no transaction is currently being processed,
|
||
|
* no reason to send cancel
|
||
|
*/
|
||
|
break;
|
||
|
default:
|
||
|
_send_error_response(cid, CTAP_HID_ERR_INVALID_CMD);
|
||
|
DEBUG("Ctaphid: unknown command %u \n", cmd);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* transaction done, cleanup */
|
||
|
_clear_ctap_buffer();
|
||
|
}
|
||
|
|
||
|
static uint32_t _handle_init_packet(uint32_t cid, uint16_t bcnt,
|
||
|
const uint8_t *nonce)
|
||
|
{
|
||
|
uint32_t cid_new = 0;
|
||
|
|
||
|
/* cid 0 is reserved */
|
||
|
if (cid == 0) {
|
||
|
_send_error_response(cid, CTAP_HID_ERR_INVALID_CHANNEL);
|
||
|
return 0;
|
||
|
}
|
||
|
/* check for len described in standard */
|
||
|
if (bcnt != 8) {
|
||
|
_send_error_response(cid, CTAP_HID_ERR_INVALID_LEN);
|
||
|
return 0;
|
||
|
}
|
||
|
/* create new channel */
|
||
|
if (cid == CTAP_HID_BROADCAST_CID) {
|
||
|
cid_new = _cid++;
|
||
|
|
||
|
if (_add_cid(cid_new) == -1) {
|
||
|
_send_error_response(cid, CTAP_HID_ERR_CHANNEL_BUSY);
|
||
|
return 0;
|
||
|
}
|
||
|
_send_init_response(cid, cid_new, nonce);
|
||
|
}
|
||
|
/* synchronize channel */
|
||
|
else {
|
||
|
cid_new = cid;
|
||
|
if (!_cid_exists(cid)) {
|
||
|
if (_add_cid(cid) == -1) {
|
||
|
/* reached cid limit */
|
||
|
_send_error_response(cid, CTAP_HID_ERR_CHANNEL_BUSY);
|
||
|
return 0;
|
||
|
}
|
||
|
}
|
||
|
_send_init_response(cid, cid, nonce);
|
||
|
}
|
||
|
|
||
|
return cid_new;
|
||
|
}
|
||
|
|
||
|
static void _handle_cbor_packet(uint8_t cmd, uint32_t cid, uint8_t *buf, uint16_t bcnt)
|
||
|
{
|
||
|
ctap_resp_t resp;
|
||
|
uint8_t err;
|
||
|
size_t size;
|
||
|
|
||
|
if (bcnt == 0) {
|
||
|
err = CTAP_HID_ERR_INVALID_LEN;
|
||
|
cmd = CTAP_HID_COMMAND_ERROR;
|
||
|
_ctap_hid_write(cmd, cid, &err, sizeof(err));
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
memset(&resp, 0, sizeof(ctap_resp_t));
|
||
|
|
||
|
ctap_req_t req;
|
||
|
|
||
|
req.method = *buf;
|
||
|
req.buf = buf + 1;
|
||
|
req.len = bcnt - 1;
|
||
|
|
||
|
size = fido2_ctap_handle_request(&req, &resp);
|
||
|
|
||
|
/* transaction done, clear should_cancel flag */
|
||
|
_state.should_cancel = false;
|
||
|
|
||
|
if (resp.status == CTAP2_OK && size > 0) {
|
||
|
/* status + data */
|
||
|
_ctap_hid_write(cmd, cid, &resp, size + sizeof(resp.status));
|
||
|
}
|
||
|
else {
|
||
|
/* status only */
|
||
|
_ctap_hid_write(cmd, cid, &resp.status, sizeof(resp.status));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static inline bool _is_init_type_pkt(const ctap_hid_pkt_t *pkt)
|
||
|
{
|
||
|
return ((pkt->init.cmd & CTAP_HID_INIT_PACKET) == CTAP_HID_INIT_PACKET);
|
||
|
}
|
||
|
|
||
|
static void _clear_ctap_buffer(void)
|
||
|
{
|
||
|
memset(&_state, 0, sizeof(_state));
|
||
|
}
|
||
|
|
||
|
bool fido2_ctap_transport_hid_should_cancel(void)
|
||
|
{
|
||
|
return _state.should_cancel;
|
||
|
}
|
||
|
|
||
|
void fido2_ctap_transport_hid_check_timeouts(void)
|
||
|
{
|
||
|
uint64_t now = xtimer_now_usec64();
|
||
|
|
||
|
for (uint8_t i = 0; i < CTAP_HID_CIDS_MAX; i++) {
|
||
|
/* transaction timed out because cont packets didn't arrive in time */
|
||
|
if (_is_busy && g_cids[i].taken &&
|
||
|
(now - g_cids[i].last_used) >= CTAP_HID_TRANSACTION_TIMEOUT &&
|
||
|
_state.cid == g_cids[i].cid && !_state.is_locked) {
|
||
|
|
||
|
_send_error_response(g_cids[i].cid, CTAP_HID_ERR_MSG_TIMEOUT);
|
||
|
_delete_cid(g_cids[i].cid);
|
||
|
_clear_ctap_buffer();
|
||
|
|
||
|
_is_busy = false;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static int8_t _add_cid(uint32_t cid)
|
||
|
{
|
||
|
uint64_t oldest = xtimer_now_usec64();
|
||
|
int8_t index_oldest = -1;
|
||
|
|
||
|
for (int i = 0; i < CTAP_HID_CIDS_MAX; i++) {
|
||
|
if (!g_cids[i].taken) {
|
||
|
g_cids[i].taken = true;
|
||
|
g_cids[i].cid = cid;
|
||
|
g_cids[i].last_used = xtimer_now_usec64();
|
||
|
|
||
|
return CTAP_HID_OK;
|
||
|
}
|
||
|
|
||
|
if (g_cids[i].last_used < oldest) {
|
||
|
oldest = g_cids[i].last_used;
|
||
|
index_oldest = i;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* remove oldest cid to make place for a new one */
|
||
|
if (index_oldest > -1) {
|
||
|
g_cids[index_oldest].taken = true;
|
||
|
g_cids[index_oldest].cid = cid;
|
||
|
g_cids[index_oldest].last_used = xtimer_now_usec64();
|
||
|
return CTAP_HID_OK;
|
||
|
}
|
||
|
|
||
|
return CTAP_HID_ERR_OTHER;
|
||
|
}
|
||
|
|
||
|
static int8_t _refresh_cid(uint32_t cid)
|
||
|
{
|
||
|
for (int i = 0; i < CTAP_HID_CIDS_MAX; i++) {
|
||
|
if (g_cids[i].cid == cid) {
|
||
|
g_cids[i].last_used = xtimer_now_usec64();
|
||
|
return CTAP_HID_OK;
|
||
|
}
|
||
|
}
|
||
|
return CTAP_HID_ERR_OTHER;
|
||
|
}
|
||
|
|
||
|
static int8_t _delete_cid(uint32_t cid)
|
||
|
{
|
||
|
for (int i = 0; i < CTAP_HID_CIDS_MAX; i++) {
|
||
|
if (g_cids[i].cid == cid) {
|
||
|
g_cids[i].taken = false;
|
||
|
g_cids[i].cid = 0;
|
||
|
|
||
|
return CTAP_HID_OK;
|
||
|
}
|
||
|
}
|
||
|
return CTAP_HID_ERR_OTHER;
|
||
|
}
|
||
|
|
||
|
static bool _cid_exists(uint32_t cid)
|
||
|
{
|
||
|
for (int i = 0; i < CTAP_HID_CIDS_MAX; i++) {
|
||
|
if (g_cids[i].cid == cid) {
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
static inline uint16_t _get_packet_len(const ctap_hid_pkt_t *pkt)
|
||
|
{
|
||
|
return (uint16_t)((pkt->init.bcnth << 8) | pkt->init.bcntl);
|
||
|
}
|
||
|
|
||
|
static void _wink(uint32_t cid, uint8_t cmd)
|
||
|
{
|
||
|
#if !IS_ACTIVE(CONFIG_FIDO2_CTAP_DISABLE_LED)
|
||
|
uint32_t delay = CTAP_HID_WINK_DELAY;
|
||
|
for (int i = 1; i <= 8; i++) {
|
||
|
#ifdef LED0_TOGGLE
|
||
|
LED0_TOGGLE;
|
||
|
xtimer_msleep(delay);
|
||
|
#endif
|
||
|
#ifdef LED1_TOGGLE
|
||
|
LED1_TOGGLE;
|
||
|
xtimer_msleep(delay);
|
||
|
#endif
|
||
|
#ifdef LED2_TOGGLE
|
||
|
LED2_TOGGLE;
|
||
|
xtimer_msleep(delay);
|
||
|
#endif
|
||
|
#ifdef LED3_TOGGLE
|
||
|
LED3_TOGGLE;
|
||
|
xtimer_msleep(delay);
|
||
|
#endif
|
||
|
delay /= 2;
|
||
|
}
|
||
|
#endif /* CONFIG_FIDO2_CTAP_DISABLE_LED */
|
||
|
|
||
|
_ctap_hid_write(cmd, cid, NULL, 0);
|
||
|
}
|
||
|
|
||
|
static void _send_error_response(uint32_t cid, uint8_t err)
|
||
|
{
|
||
|
DEBUG("ctap_trans_hid err resp: %02x \n", err);
|
||
|
_ctap_hid_write(CTAP_HID_COMMAND_ERROR, cid, &err, sizeof(err));
|
||
|
}
|
||
|
|
||
|
static void _send_init_response(uint32_t cid_old, uint32_t cid_new,
|
||
|
const uint8_t *nonce)
|
||
|
{
|
||
|
ctap_hid_init_resp_t resp;
|
||
|
|
||
|
memset(&resp, 0, sizeof(ctap_hid_init_resp_t));
|
||
|
|
||
|
resp.cid = cid_new;
|
||
|
resp.protocol_version = CTAP_HID_PROTOCOL_VERSION;
|
||
|
resp.version_major = 0;
|
||
|
resp.version_minor = 0;
|
||
|
resp.build_version = 0;
|
||
|
memcpy(resp.nonce, nonce, sizeof(resp.nonce));
|
||
|
|
||
|
uint8_t cmd = (CTAP_HID_INIT_PACKET | CTAP_HID_COMMAND_INIT);
|
||
|
|
||
|
resp.capabilities = CTAP_HID_CAPABILITY_CBOR | CTAP_HID_CAPABILITY_WINK
|
||
|
| CTAP_HID_CAPABILITY_NMSG;
|
||
|
|
||
|
_ctap_hid_write(cmd, cid_old, &resp, sizeof(ctap_hid_init_resp_t));
|
||
|
}
|
||
|
|
||
|
void _ctap_hid_write(uint8_t cmd, uint32_t cid, const void *_data, size_t len)
|
||
|
{
|
||
|
const uint8_t *data = (uint8_t *)_data;
|
||
|
uint8_t buf[CONFIG_USBUS_HID_INTERRUPT_EP_SIZE] = { 0 };
|
||
|
uint16_t bytes_written = 0;
|
||
|
uint8_t seq = 0;
|
||
|
uint8_t offset = 0;
|
||
|
|
||
|
memcpy(buf, &cid, sizeof(cid));
|
||
|
offset += sizeof(cid);
|
||
|
buf[offset++] = cmd;
|
||
|
/* high part of payload length first */
|
||
|
buf[offset++] = (len & 0xff00) >> 8;
|
||
|
buf[offset++] = (len & 0xff) >> 0;
|
||
|
|
||
|
if (data == NULL) {
|
||
|
usb_hid_io_write(buf, CONFIG_USBUS_HID_INTERRUPT_EP_SIZE);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
for (size_t i = 0; i < len; i++) {
|
||
|
if (offset == 0) {
|
||
|
memcpy(buf, &cid, sizeof(cid));
|
||
|
offset += sizeof(cid);
|
||
|
|
||
|
/* initialization packet */
|
||
|
if (bytes_written == 0) {
|
||
|
buf[offset++] = cmd;
|
||
|
buf[offset++] = (len & 0xff00) >> 8;
|
||
|
buf[offset++] = (len & 0xff) >> 0;
|
||
|
}
|
||
|
/* continuation packet */
|
||
|
else {
|
||
|
buf[offset++] = seq++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
buf[offset++] = data[i];
|
||
|
bytes_written++;
|
||
|
|
||
|
if (offset == CONFIG_USBUS_HID_INTERRUPT_EP_SIZE) {
|
||
|
usb_hid_io_write(buf, CONFIG_USBUS_HID_INTERRUPT_EP_SIZE);
|
||
|
offset = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (offset > 0) {
|
||
|
memset(buf + offset, 0, CONFIG_USBUS_HID_INTERRUPT_EP_SIZE - offset);
|
||
|
usb_hid_io_write(buf, CONFIG_USBUS_HID_INTERRUPT_EP_SIZE);
|
||
|
}
|
||
|
}
|