kopia lustrzana https://github.com/conorpp/u2f-zero
486 wiersze
9.1 KiB
C
486 wiersze
9.1 KiB
C
/*
|
|
* u2f_hid.c
|
|
*
|
|
* Created on: Jan 26, 2016
|
|
* Author: pp
|
|
*/
|
|
#include "app.h"
|
|
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
|
|
#include "bsp.h"
|
|
#include "u2f_hid.h"
|
|
#include "u2f.h"
|
|
|
|
#ifndef U2F_HID_DISABLE
|
|
|
|
#define CID_MAX (sizeof(CIDS)/sizeof(uint32_t))
|
|
|
|
|
|
typedef enum
|
|
{
|
|
HID_BUSY=0,
|
|
HID_READY,
|
|
} HID_STATE;
|
|
|
|
struct CID
|
|
{
|
|
uint32_t cid;
|
|
uint32_t last_used;
|
|
};
|
|
|
|
static struct hid_layer_param
|
|
{
|
|
HID_STATE state;
|
|
uint32_t current_cid;
|
|
uint8_t current_cmd;
|
|
|
|
uint32_t last_buffered;
|
|
uint16_t bytes_buffered;
|
|
uint16_t req_len;
|
|
|
|
// number of payload bytes written in response
|
|
uint16_t bytes_written;
|
|
|
|
|
|
// total length of response in bytes
|
|
uint16_t res_len;
|
|
|
|
#define BUFFER_SIZE 200
|
|
uint8_t buffer[BUFFER_SIZE];
|
|
|
|
} hid_layer;
|
|
|
|
uint32_t _hid_lockt = 0;
|
|
uint32_t _hid_lock_cid = 0;
|
|
|
|
struct CID CIDS[5];
|
|
static uint8_t CID_NUM = 0;
|
|
|
|
static uint8_t _hid_pkt[HID_PACKET_SIZE];
|
|
static uint8_t _hid_offset = 0;
|
|
static uint8_t _hid_seq = 0;
|
|
static uint8_t _hid_in_session = 0;
|
|
|
|
#define u2f_hid_busy() (_hid_in_session)
|
|
|
|
#define MIN(a,b) ((a) < (b) ? (a):(b))
|
|
|
|
#define hid_is_locked() (_hid_lockt > get_ms())
|
|
#define hid_is_lock_cid(c) ((c) == _hid_lock_cid)
|
|
|
|
void u2f_hid_init()
|
|
{
|
|
memset(CIDS, 0, sizeof(CIDS));
|
|
memset(&hid_layer, 0, sizeof(hid_layer));
|
|
CID_NUM = 0;
|
|
_hid_offset = 0;
|
|
_hid_seq = 0;
|
|
_hid_in_session = 0;
|
|
}
|
|
|
|
void u2f_hid_set_len(uint16_t len)
|
|
{
|
|
hid_layer.res_len = len;
|
|
}
|
|
|
|
|
|
static void u2f_hid_reset_packet()
|
|
{
|
|
_hid_seq = 0;
|
|
_hid_offset = 0;
|
|
_hid_in_session = 0;
|
|
memset(&hid_layer, 0, sizeof(hid_layer));
|
|
memset(_hid_pkt, 0, HID_PACKET_SIZE);
|
|
}
|
|
|
|
// writes what has been buffered and clears memory
|
|
void u2f_hid_flush()
|
|
{
|
|
if (_hid_offset)
|
|
{
|
|
usb_write(_hid_pkt, HID_PACKET_SIZE);
|
|
}
|
|
u2f_hid_reset_packet();
|
|
}
|
|
|
|
|
|
void u2f_hid_writeback(uint8_t * payload, uint16_t len)
|
|
{
|
|
struct u2f_hid_msg * r = (struct u2f_hid_response *) _hid_pkt;
|
|
_hid_in_session = 1;
|
|
if (_hid_offset == 0)
|
|
{
|
|
r->cid = hid_layer.current_cid;
|
|
if (!_hid_seq)
|
|
{
|
|
r->pkt.init.cmd = hid_layer.current_cmd;
|
|
U2FHID_SET_LEN(r, hid_layer.res_len);
|
|
_hid_offset = 7;
|
|
}
|
|
else
|
|
{
|
|
r->pkt.cont.seq = _hid_seq - 1;
|
|
_hid_offset = 5;
|
|
if (_hid_seq-1 > 127)
|
|
{
|
|
set_app_error(ERROR_SEQ_EXCEEDED);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
while(len--)
|
|
{
|
|
_hid_pkt[_hid_offset++] = *payload++;
|
|
hid_layer.bytes_written++;
|
|
if (_hid_offset == HID_PACKET_SIZE)
|
|
{
|
|
_hid_offset = 0;
|
|
_hid_seq++;
|
|
|
|
usb_write(_hid_pkt, HID_PACKET_SIZE);
|
|
memset(_hid_pkt, 0, HID_PACKET_SIZE);
|
|
|
|
if (len)
|
|
{
|
|
u2f_hid_writeback(payload, len);
|
|
return;
|
|
}
|
|
else break;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
static uint8_t is_cid_free(struct CID* c)
|
|
{
|
|
return (c->cid == 0 || get_ms() - c->last_used > 1000);
|
|
}
|
|
|
|
static void refresh_cid(struct CID* c)
|
|
{
|
|
c->last_used = get_ms();
|
|
}
|
|
|
|
|
|
static uint32_t get_new_cid()
|
|
{
|
|
static uint32_t base = 0xcafebabe;
|
|
int i;
|
|
for(i = 0; i < CID_MAX; i++)
|
|
{
|
|
if (is_cid_free(CIDS+i))
|
|
{
|
|
goto newcid;
|
|
}
|
|
}
|
|
return 0;
|
|
newcid:
|
|
|
|
do
|
|
{
|
|
CIDS[i].cid = base + CID_NUM++;
|
|
}while(CIDS[i].cid == 0 || CIDS[i].cid == U2FHID_BROADCAST);
|
|
|
|
refresh_cid(CIDS+i);
|
|
|
|
return CIDS[i].cid;
|
|
}
|
|
|
|
static struct CID* get_cid(uint32_t cid)
|
|
{
|
|
uint8_t i;
|
|
for(i = 0; i < CID_MAX; i++)
|
|
{
|
|
if (CIDS[i].cid == cid)
|
|
{
|
|
return CIDS+i;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
static void del_cid(uint32_t cid)
|
|
{
|
|
uint8_t i;
|
|
for(i = 0; i < CID_MAX; i++)
|
|
{
|
|
if (CIDS[i].cid == cid)
|
|
{
|
|
CIDS[i].cid = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static void stamp_error(uint32_t cid, uint8_t err)
|
|
{
|
|
uint8_t errbuf[HID_PACKET_SIZE];
|
|
struct u2f_hid_msg * res = (struct u2f_hid_msg *)errbuf;
|
|
res->cid = cid;
|
|
res->pkt.init.cmd = U2FHID_ERROR;
|
|
res->pkt.init.payload[0] = err;
|
|
res->pkt.init.bcnth = 0;
|
|
res->pkt.init.bcntl = 1;
|
|
|
|
usb_write(errbuf, HID_PACKET_SIZE);
|
|
del_cid(cid);
|
|
}
|
|
|
|
static void start_buffering(struct u2f_hid_msg* req)
|
|
{
|
|
_hid_in_session = 1;
|
|
hid_layer.bytes_buffered = U2FHID_INIT_PAYLOAD_SIZE;
|
|
hid_layer.req_len = U2FHID_LEN(req);
|
|
memmove(hid_layer.buffer, req->pkt.init.payload, U2FHID_INIT_PAYLOAD_SIZE);
|
|
}
|
|
|
|
static int buffer_request(struct u2f_hid_msg* req)
|
|
{
|
|
if (hid_layer.bytes_buffered + U2FHID_CONT_PAYLOAD_SIZE > BUFFER_SIZE)
|
|
{
|
|
set_app_error(ERROR_HID_BUFFER_FULL);
|
|
stamp_error(req->cid, ERR_OTHER);
|
|
return -1;
|
|
}
|
|
memmove(hid_layer.buffer + hid_layer.bytes_buffered, req->pkt.cont.payload, U2FHID_CONT_PAYLOAD_SIZE);
|
|
hid_layer.bytes_buffered += U2FHID_CONT_PAYLOAD_SIZE;
|
|
}
|
|
|
|
static void hid_u2f_parse(struct u2f_hid_msg* req)
|
|
{
|
|
|
|
uint16_t len = 0;
|
|
uint8_t secs;
|
|
struct u2f_hid_init_response * init_res = appdata.tmp;
|
|
|
|
switch(hid_layer.current_cmd)
|
|
{
|
|
case U2FHID_INIT:
|
|
if (U2FHID_LEN(req) != 8)
|
|
{
|
|
stamp_error(hid_layer.current_cid, ERR_INVALID_LEN);
|
|
goto fail;
|
|
}
|
|
u2f_hid_set_len(17);
|
|
|
|
if (hid_layer.current_cid == 0)
|
|
{
|
|
u2f_prints("out of cid's\r\n");
|
|
set_app_error(ERROR_OUT_OF_CIDS);
|
|
goto fail;
|
|
}
|
|
|
|
init_res->cid = get_new_cid();
|
|
init_res->version_id = 1;
|
|
init_res->version_major = 1;
|
|
init_res->version_minor = 0;
|
|
init_res->version_build = 0;
|
|
init_res->cflags = 0;
|
|
|
|
// write back the same data nonce
|
|
u2f_hid_writeback(req->pkt.init.payload, 8);
|
|
u2f_hid_writeback(init_res, 9);
|
|
u2f_hid_flush();
|
|
hid_layer.current_cid = init_res->cid;
|
|
|
|
break;
|
|
case U2FHID_MSG:
|
|
|
|
if (U2FHID_LEN(req) < 4)
|
|
{
|
|
stamp_error(hid_layer.current_cid, ERR_INVALID_LEN);
|
|
u2f_prints("invalid len msg\r\n");
|
|
goto fail;
|
|
}
|
|
// buffer 2 payloads (120 bytes) to get full U2F message
|
|
// assuming key handle is < 45 bytes
|
|
// 7 bytes for apdu header
|
|
// 7 + 66 bytes + key handle for authenticate message
|
|
// 7 + 64 for register message
|
|
if (hid_layer.bytes_buffered == 0)
|
|
{
|
|
start_buffering(req);
|
|
if (hid_layer.bytes_buffered >= U2FHID_LEN(req))
|
|
{
|
|
u2f_request((struct u2f_request_apdu *)hid_layer.buffer);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
buffer_request(req);
|
|
if (hid_layer.bytes_buffered >= hid_layer.req_len)
|
|
{
|
|
u2f_request((struct u2f_request_apdu *)hid_layer.buffer);
|
|
}
|
|
}
|
|
|
|
|
|
break;
|
|
case U2FHID_PING:
|
|
|
|
//u2f_prints("U2F PING\r\n");
|
|
|
|
if (!u2f_hid_busy())
|
|
{
|
|
u2f_hid_set_len(U2FHID_LEN(req));
|
|
u2f_hid_writeback(req->pkt.init.payload, MIN(hid_layer.res_len, U2FHID_INIT_PAYLOAD_SIZE));
|
|
}
|
|
else
|
|
{
|
|
u2f_hid_writeback(req->pkt.cont.payload, MIN(hid_layer.res_len - hid_layer.bytes_written, U2FHID_CONT_PAYLOAD_SIZE));
|
|
}
|
|
|
|
if (hid_layer.res_len == hid_layer.bytes_written) u2f_hid_flush();
|
|
break;
|
|
|
|
case U2FHID_WINK:
|
|
if (U2FHID_LEN(req) != 0)
|
|
{
|
|
// this one is safe
|
|
stamp_error(hid_layer.current_cid, ERR_INVALID_LEN);
|
|
}
|
|
u2f_hid_set_len(0);
|
|
u2f_hid_writeback(NULL, 0);
|
|
u2f_hid_flush();
|
|
app_wink(U2F_COLOR_WINK);
|
|
break;
|
|
case U2FHID_LOCK:
|
|
|
|
secs = req->pkt.init.payload[0];
|
|
if (secs > 10)
|
|
{
|
|
stamp_error(hid_layer.current_cid, ERR_INVALID_PAR);
|
|
}
|
|
else
|
|
{
|
|
_hid_lock_cid = hid_layer.current_cid;
|
|
_hid_lockt = get_ms() + 1000 * secs;
|
|
u2f_hid_set_len(0);
|
|
u2f_hid_writeback(NULL, 0);
|
|
u2f_hid_flush();
|
|
}
|
|
|
|
break;
|
|
default:
|
|
set_app_error(ERROR_HID_INVALID_CMD);
|
|
stamp_error(hid_layer.current_cid, ERR_INVALID_CMD);
|
|
}
|
|
|
|
return;
|
|
|
|
fail:
|
|
u2f_prints("U2F HID FAIL\r\n");
|
|
return;
|
|
}
|
|
|
|
|
|
void u2f_hid_request(struct u2f_hid_msg* req)
|
|
{
|
|
uint8_t* payload = req->pkt.init.payload;
|
|
static int8_t last_seq = -1;
|
|
struct CID* cid = get_cid(req->cid);
|
|
|
|
|
|
if (cid != NULL)
|
|
{
|
|
refresh_cid(cid);
|
|
}
|
|
else if (req->cid == U2FHID_BROADCAST)
|
|
{
|
|
|
|
}
|
|
else
|
|
{
|
|
// Ignore CID's we did not allocate.
|
|
//u2f_printlx("ignoring pkt ",1,req->cid);
|
|
return;
|
|
}
|
|
|
|
// ignore if we locked to a different cid
|
|
if(hid_is_locked())
|
|
{
|
|
if (!hid_is_lock_cid(cid))
|
|
{
|
|
stamp_error(hid_layer.current_cid, ERR_CHANNEL_BUSY);
|
|
return;
|
|
}
|
|
}
|
|
|
|
hid_layer.state = (u2f_hid_busy()) ? HID_BUSY : HID_READY;
|
|
|
|
switch(hid_layer.state)
|
|
{
|
|
case HID_READY:
|
|
if (req->pkt.init.cmd & TYPE_INIT)
|
|
{
|
|
if (U2FHID_LEN(req) > U2FHID_MAX_PAYLOAD_SIZE)
|
|
{
|
|
//u2f_prints("length too big\r\n");
|
|
stamp_error(req->cid, ERR_INVALID_LEN);
|
|
return;
|
|
}
|
|
u2f_hid_reset_packet();
|
|
hid_layer.current_cid = req->cid;
|
|
hid_layer.current_cmd = req->pkt.init.cmd;
|
|
hid_layer.last_buffered = get_ms();
|
|
last_seq = -1;
|
|
|
|
}
|
|
else
|
|
{
|
|
stamp_error(req->cid, ERR_INVALID_CMD);
|
|
u2f_prints("ERR_INVALID_CMD\r\n");
|
|
return;
|
|
}
|
|
|
|
break;
|
|
case HID_BUSY:
|
|
// buffer long requests
|
|
if (req->cid == hid_layer.current_cid)
|
|
{
|
|
if (req->pkt.init.cmd & TYPE_INIT)
|
|
{
|
|
u2f_hid_reset_packet();
|
|
u2f_hid_request(req);
|
|
return;
|
|
}
|
|
|
|
hid_layer.last_buffered = get_ms();
|
|
|
|
// verify packets arrive in ascending order
|
|
if (last_seq + 1 != req->pkt.cont.seq)
|
|
{
|
|
u2f_hid_reset_packet();
|
|
stamp_error(hid_layer.current_cid, ERR_INVALID_SEQ);
|
|
return;
|
|
}
|
|
last_seq = req->pkt.cont.seq;
|
|
|
|
}
|
|
else if (U2FHID_TIMEOUT(&hid_layer))
|
|
{
|
|
// return timeout error for old channel and run again for new channel
|
|
//u2f_prints("timeout, switching\r\n");
|
|
hid_layer.state = HID_READY;
|
|
u2f_hid_reset_packet();
|
|
stamp_error(hid_layer.current_cid, ERR_MSG_TIMEOUT);
|
|
u2f_hid_request(req);
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
// Current application may not be interrupted
|
|
stamp_error(req->cid, ERR_CHANNEL_BUSY);
|
|
return;
|
|
}
|
|
break;
|
|
|
|
}
|
|
|
|
hid_u2f_parse(req);
|
|
return;
|
|
}
|
|
|
|
#endif
|