From 9c42823cd2653269d1f0f399bfd4014b6a3dce6c Mon Sep 17 00:00:00 2001 From: Conor Patrick Date: Mon, 28 May 2018 14:14:59 -0400 Subject: [PATCH] allocate cids better and do proper timeouts in hid --- ctap.c | 6 + ctap.h | 3 + ctap_test.py | 103 ++++++++++++++++- ctaphid.c | 316 +++++++++++++++++++++++++++++---------------------- ctaphid.h | 1 + main.c | 11 +- udp_bridge.c | 32 +++++- udp_bridge.h | 2 +- usbhid.c | 14 ++- usbhid.h | 2 +- 10 files changed, 342 insertions(+), 148 deletions(-) diff --git a/ctap.c b/ctap.c index 2c0c5b2..6bdfd77 100644 --- a/ctap.c +++ b/ctap.c @@ -1260,11 +1260,17 @@ void ctap_reset_pin_attempts() _flash_tries = 8; } +void ctap_reset_state() +{ + memset(&getAssertionState, 0, sizeof(getAssertionState)); +} + void ctap_reset() { _flash_tries = 8; PIN_CODE_SET = 0; DEVICE_LOCKOUT = 0; + ctap_reset_state(); memset(PIN_CODE,0,sizeof(PIN_CODE)); memset(PIN_CODE_HASH,0,sizeof(PIN_CODE_HASH)); crypto_ecc256_make_key_pair(KEY_AGREEMENT_PUB, KEY_AGREEMENT_PRIV); diff --git a/ctap.h b/ctap.h index f747704..dd3ba91 100644 --- a/ctap.h +++ b/ctap.h @@ -244,6 +244,9 @@ int ctap_encode_der_sig(uint8_t * sigbuf, uint8_t * sigder); // Run ctap related power-up procedures (init pinToken, generate shared secret) void ctap_init(); +// Resets state between different accesses of different applications +void ctap_reset_state(); + void ctap_update_pin(uint8_t * pin, int len); uint8_t ctap_decrement_pin_attempts(); int8_t ctap_leftover_pin_attempts(); diff --git a/ctap_test.py b/ctap_test.py index 6c5a5ca..52e3be0 100644 --- a/ctap_test.py +++ b/ctap_test.py @@ -12,7 +12,7 @@ from fido2.utils import Timeout import sys,os from random import randint from binascii import hexlify -import array,struct +import array,struct,socket # Set up a FIDO 2 client using the origin https://example.com @@ -24,6 +24,19 @@ def ForceU2F(client,device): client._do_get_assertion = client._ctap1_get_assertion +class Packet(object): + def __init__(self,data): + self.data = data + + def ToWireFormat(self,): + return self.data + + @staticmethod + def FromWireFormat(pkt_size,data): + return Packet(data) + + + class Tester(): def __init__(self,): self.origin = 'https://examplo.org' @@ -35,15 +48,49 @@ class Tester(): self.dev = dev self.ctap = CTAP2(dev) + # consume timeout error + cmd,resp = self.recv_raw() def send_data(self, cmd, data): #print('<<', hexlify(data)) if type(data) != type(b''): - data = data.encode() + data = struct.pack('%dB' % len(data), *[ord(x) for x in data]) with Timeout(1.0) as event: return self.dev.call(cmd, data,event) + def send_raw(self, data, cid = None): + if cid is None: + cid = self.dev._dev.cid + if type(data) != type(b''): + data = struct.pack('%dB' % len(data), *[ord(x) for x in data]) + self.dev._dev.InternalSendPacket(Packet(cid + data)) + + def cid(self,): + return self.dev._dev.cid + + def recv_raw(self,): + cmd,payload = self.dev._dev.InternalRecv() + return cmd, payload + + def check_error(self,data,err=None): + assert(len(data) == 1) + if err is None: + if data[0] != 0: + raise CtapError(data[0]) + elif data[0] != err: + raise ValueError('Unexpected error: %02x' % data[0]) + + def test_hid(self,): + print('Test idle') + try: + cmd,resp = self.recv_raw() + except socket.timeout: + print('Pass: Idle') + + print('Test init') + r = self.send_data(CTAPHID.INIT, '\x11\x11\x11\x11\x11\x11\x11\x11') + pingdata = os.urandom(100) try: r = self.send_data(CTAPHID.PING, pingdata) @@ -62,6 +109,8 @@ class Tester(): print('7609 byte Ping failed:', e) print('PASS: 7609 byte ping') + print('Test non-active cid') + try: r = self.send_data(CTAPHID.WINK, '') except CtapError as e: @@ -90,15 +139,59 @@ class Tester(): print('PASS: no data msg') try: - r = self.send_data(CTAPHID.INIT, '') + r = self.send_data(CTAPHID.INIT, '\x11\x22\x33\x44\x55\x66\x77\x88') except CtapError as e: - print('resync fail: ', e) - return + raise RuntimeError('resync fail: ', e) print('PASS: resync') + try: + r = self.send_data(0x66, '') + raise RuntimeError('Invalid command did not return error') + except CtapError as e: + assert(e.code == CtapError.ERR.INVALID_COMMAND) + print('PASS: invalid HID command') + print('Sending packet with too large of a length.') + self.send_raw('\x80\x1d\xba\x00') + cmd,resp = self.recv_raw() + self.check_error(resp, CtapError.ERR.INVALID_LENGTH) + print('PASS: invalid length') + print('Sending packets that skip a sequence number.') + self.send_raw('\x81\x10\x00') + self.send_raw('\x00') + self.send_raw('\x01') + self.send_raw('\x02') + # skip 3 + self.send_raw('\x04') + cmd,resp = self.recv_raw() + self.check_error(resp, CtapError.ERR.INVALID_SEQ) + print('PASS: invalid sequence') + + print('Resync and send ping') + try: + r = self.send_data(CTAPHID.INIT, '\x11\x22\x33\x44\x55\x66\x77\x88') + pingdata = os.urandom(100) + r = self.send_data(CTAPHID.PING, pingdata) + if (r != pingdata): + raise ValueError('Ping data not echo\'d') + except CtapError as e: + raise RuntimeError('resync fail: ', e) + print('PASS: resync and ping') + + print('Send ping and abort it') + self.send_raw('\x81\x10\x00') + self.send_raw('\x00') + self.send_raw('\x01') + try: + r = self.send_data(CTAPHID.INIT, '\x11\x22\x33\x44\x55\x66\x77\x88') + except CtapError as e: + raise RuntimeError('resync fail: ', e) + print('PASS: interrupt ping with resync') + + + print('Send ping and abort it') if __name__ == '__main__': t = Tester() diff --git a/ctaphid.c b/ctaphid.c index 0b707dc..03d0e33 100644 --- a/ctaphid.c +++ b/ctaphid.c @@ -34,13 +34,22 @@ typedef struct uint8_t buf[HID_MESSAGE_SIZE]; } CTAPHID_WRITE_BUFFER; +struct CID +{ + uint32_t cid; + uint64_t last_used; + uint8_t busy; + uint8_t last_cmd; +}; + #define SUCESS 0 #define SEQUENCE_ERROR 1 static int state; +static struct CID CIDS[10]; +#define CID_MAX (sizeof(CIDS)/sizeof(struct CID)) -static int active_cid; static uint64_t active_cid_timestamp; static uint8_t ctap_buffer[CTAPHID_BUFFER_SIZE]; @@ -49,8 +58,6 @@ static uint16_t ctap_buffer_bcnt; static int ctap_buffer_offset; static int ctap_packet_seq; -static uint32_t _next_cid = 0; - static void buffer_reset(); #define CTAPHID_WRITE_INIT 0x01 @@ -63,28 +70,61 @@ static void ctaphid_write(CTAPHID_WRITE_BUFFER * wb, void * _data, int len); void ctaphid_init() { state = IDLE; - active_cid = 0; buffer_reset(); - active_cid_timestamp = millis(); -} - -static uint32_t set_next_cid(uint32_t cid) -{ - _next_cid = cid; + ctap_reset_state(); } static uint32_t get_new_cid() { static uint32_t cid = 1; - - if (_next_cid != 0) + do { - int tmp = _next_cid; - _next_cid = 0; - return tmp; - } + cid++; + }while(cid == 0 || cid == 0xffffffff); + return cid; +} - return cid++; +static int8_t add_cid(uint32_t cid) +{ + int i; + for(i = 0; i < CID_MAX-1; i++) + { + if (!CIDS[i].busy) + { + CIDS[i].cid = cid; + CIDS[i].busy = 1; + CIDS[i].last_used = millis(); + return 0; + } + } + return -1; +} + +static int8_t cid_exists(uint32_t cid) +{ + int i; + for(i = 0; i < CID_MAX-1; i++) + { + if (CIDS[i].cid == cid) + { + return 1; + } + } + return 0; +} + +static int8_t cid_refresh(uint32_t cid) +{ + int i; + for(i = 0; i < CID_MAX-1; i++) + { + if (CIDS[i].cid == cid) + { + CIDS[i].last_used = millis(); + return 0; + } + } + return -1; } static int is_broadcast(CTAPHID_PACKET * pkt) @@ -102,10 +142,6 @@ static int is_cont_pkt(CTAPHID_PACKET * pkt) return !(pkt->pkt.init.cmd & TYPE_INIT); } -static int is_active_cid(CTAPHID_PACKET * pkt) -{ - return (pkt->cid == active_cid); -} static int is_timed_out() { @@ -252,106 +288,148 @@ static void ctaphid_send_error(uint32_t cid, uint8_t error) ctaphid_write(&wb, NULL, 0); } +static void send_init_response(uint32_t oldcid, uint32_t newcid, uint8_t * nonce) +{ + CTAPHID_INIT_RESPONSE init_resp; + CTAPHID_WRITE_BUFFER wb; + ctaphid_write_buffer_init(&wb); + wb.cid = oldcid; + wb.cmd = CTAPHID_INIT; + wb.bcnt = 17; + + memmove(init_resp.nonce, nonce, 8); + init_resp.cid = newcid; + init_resp.protocol_version = 0;//? + init_resp.version_major = 0;//? + init_resp.version_minor = 0;//? + init_resp.build_version = 0;//? + init_resp.capabilities = CTAP_CAPABILITIES; + + ctaphid_write(&wb,&init_resp,sizeof(CTAPHID_INIT_RESPONSE)); + ctaphid_write(&wb,NULL,0); +} + + +void u2f_hid_check_timeouts() +{ + uint8_t i; + for(i = 0; i < CID_MAX; i++) + { + if (CIDS[i].busy && ((millis() - CIDS[i].last_used) >= 750)) + { + printf("TIMEOUT CID: %08x\n", CIDS[i].cid); + ctaphid_send_error(CIDS[i].cid, CTAP1_ERR_TIMEOUT); + memset(CIDS + i, 0, sizeof(struct CID)); + } + } + +} + + void ctaphid_handle_packet(uint8_t * pkt_raw) { CTAPHID_PACKET * pkt = (CTAPHID_PACKET *)(pkt_raw); printf("Recv packet\n"); - printf(" CID: %08x active(%08x)\n", pkt->cid, active_cid); + printf(" CID: %08x \n", pkt->cid); printf(" cmd: %02x\n", pkt->pkt.init.cmd); if (!is_cont_pkt(pkt)) printf(" length: %d\n", ctaphid_packet_len(pkt)); int ret; uint8_t status; uint32_t oldcid; - static CTAPHID_INIT_RESPONSE init_resp; + uint32_t newcid; static CTAPHID_WRITE_BUFFER wb; + uint32_t active_cid; CTAP_RESPONSE ctap_resp; -start_over: - - switch (state) + if (is_init_pkt(pkt)) { - case IDLE: - if (is_broadcast(pkt)) - { - printf("starting a new request\n"); - state = HANDLING_REQUEST; - buffer_packet(pkt); - } + if (ctaphid_packet_len(pkt) != 8) + { + printf("Error,invalid length field for init packet\n"); + ctaphid_send_error(pkt->cid, CTAP1_ERR_INVALID_LENGTH); + return; + } + if (pkt->cid == 0) + { + printf("Error, invalid cid 0\n"); + ctaphid_send_error(pkt->cid, CTAP1_ERR_INVALID_PARAMETER); + return; + } + + ctaphid_init(); + if (is_broadcast(pkt)) + { + // Check if any existing cids are busy first ? + printf("adding a new cid\n"); + oldcid = CTAPHID_BROADCAST_CID; + newcid = get_new_cid(); + ret = add_cid(newcid); + // handle init here + } + else + { + printf("synchronizing to cid\n"); + oldcid = pkt->cid; + newcid = pkt->cid; + if (cid_exists(newcid)) + ret = cid_refresh(newcid); else - { - printf("Error, unknown request\n"); - ctaphid_send_error(pkt->cid, ERR_INVALID_PAR); - return; - } - break; - case HANDLING_REQUEST: - if (is_active_cid(pkt)) - { - if (is_init_pkt(pkt)) - { - printf("received abort request from %08x\n", pkt->cid); - ctaphid_write_buffer_init(&wb); - - wb.cid = active_cid; - active_cid_timestamp = millis(); - - ctaphid_init(); - - active_cid = wb.cid; - wb.cmd = CTAPHID_INIT; - wb.bcnt = 0; - ctaphid_write(&wb, ctap_buffer, buffer_len()); - ctaphid_write(&wb, NULL, 0); - return; - } - else if (!is_cont_pkt(pkt) && buffer_status() == BUFFERING) - { - printf("Error, expecting cont packet\n"); - ctaphid_send_error(pkt->cid, ERR_INVALID_PAR); - return; - } - else if(!is_cont_pkt(pkt)) - { - if (ctaphid_packet_len(pkt) > CTAPHID_BUFFER_SIZE) - { - printf("Error, internal buffer not big enough\n"); - ctaphid_send_error(pkt->cid, CTAP1_ERR_INVALID_LENGTH); - return; - } - } - - active_cid_timestamp = millis(); - ret = buffer_packet(pkt); - if (ret == SEQUENCE_ERROR) - { - printf("Sequence error\n"); - ctaphid_send_error(pkt->cid, ERR_INVALID_SEQ); - return; - } - } - else if (is_timed_out()) - { - printf("dropping last channel -- timeout"); - oldcid = active_cid; - ctaphid_init(); - ctaphid_send_error(active_cid, ERR_MSG_TIMEOUT); - goto start_over; - } - else - { - ctaphid_send_error(pkt->cid, ERR_CHANNEL_BUSY); - printf("Too busy with current transaction\n"); - return; - } - break; - default: - printf("invalid state; abort\n"); - exit(1); + ret = add_cid(newcid); + } + if (ret == -1) + { + printf("Error, not enough memory for new CID. return BUSY.\n"); + ctaphid_send_error(pkt->cid, CTAP1_ERR_CHANNEL_BUSY); + return; + } + send_init_response(oldcid, newcid, pkt->pkt.init.payload); + return; } + else + { + // Check if matches existing CID + if (cid_exists(pkt->cid)) + { + if (! is_cont_pkt(pkt)) + { + if (ctaphid_packet_len(pkt) > CTAPHID_BUFFER_SIZE) + { + ctaphid_send_error(pkt->cid, CTAP1_ERR_INVALID_LENGTH); + return; + } + } + if (buffer_packet(pkt) == SEQUENCE_ERROR) + { + printf("Buffering sequence error\n"); + ctaphid_send_error(pkt->cid, CTAP1_ERR_INVALID_SEQ); + return; + } + ret = cid_refresh(pkt->cid); + if (ret != 0) + { + printf("Error, refresh cid failed\n"); + exit(1); + } + active_cid = pkt->cid; + } + else if (is_cont_pkt(pkt)) + { + printf("ignoring unwarranted cont packet\n"); + // Ignore + return; + } + else + { + printf("ignoring unwarranted init packet\n"); + // Ignore + return; + } + } + switch(buffer_status()) @@ -367,37 +445,8 @@ start_over: switch(buffer_cmd()) { case CTAPHID_INIT: - printf("CTAPHID_INIT\n"); - - if (buffer_len() != 8) - { - printf("Error,invalid length field for init packet\n"); - ctaphid_send_error(pkt->cid, CTAP1_ERR_INVALID_LENGTH); - return; - } - - active_cid = get_new_cid(); - - printf("cid: %08x\n",active_cid); - active_cid_timestamp = millis(); - - ctaphid_write_buffer_init(&wb); - wb.cid = CTAPHID_BROADCAST_CID; - wb.cmd = CTAPHID_INIT; - wb.bcnt = 17; - - memmove(init_resp.nonce, pkt->pkt.init.payload, 8); - init_resp.cid = active_cid; - init_resp.protocol_version = 0;//? - init_resp.version_major = 0;//? - init_resp.version_minor = 0;//? - init_resp.build_version = 0;//? - init_resp.capabilities = CTAP_CAPABILITIES; - - ctaphid_write(&wb,&init_resp,sizeof(CTAPHID_INIT_RESPONSE)); - ctaphid_write(&wb,NULL,0); - - + printf("CTAPHID_INIT, error this should already be handled\n"); + exit(1); break; case CTAPHID_PING: printf("CTAPHID_PING\n"); @@ -476,6 +525,7 @@ start_over: default: printf("error, unimplemented HID cmd: %02x\r\n", buffer_cmd()); + ctaphid_send_error(pkt->cid, CTAP1_ERR_INVALID_COMMAND); break; } diff --git a/ctaphid.h b/ctaphid.h index 4db3fc4..4ecd051 100644 --- a/ctaphid.h +++ b/ctaphid.h @@ -72,6 +72,7 @@ void ctaphid_init(); void ctaphid_handle_packet(uint8_t * pkt_raw); +void u2f_hid_check_timeouts(); #define ctaphid_packet_len(pkt) ((uint16_t)((pkt)->pkt.init.bcnth << 8) | ((pkt)->pkt.init.bcntl)) diff --git a/main.c b/main.c index c8affda..81c0ed3 100644 --- a/main.c +++ b/main.c @@ -50,11 +50,14 @@ int main(int argc, char * argv[]) while(1) { - usbhid_recv(hidmsg); - printf("%d>> ",count++); dump_hex(hidmsg,sizeof(hidmsg)); + if (usbhid_recv(hidmsg) > 0) + { + printf("%d>> ",count++); dump_hex(hidmsg,sizeof(hidmsg)); - ctaphid_handle_packet(hidmsg); - memset(hidmsg, 0, sizeof(hidmsg)); + ctaphid_handle_packet(hidmsg); + memset(hidmsg, 0, sizeof(hidmsg)); + } + u2f_hid_check_timeouts(); } diff --git a/udp_bridge.c b/udp_bridge.c index 603afb0..4d7a949 100644 --- a/udp_bridge.c +++ b/udp_bridge.c @@ -7,11 +7,13 @@ #include #include +#include #include #include #include #include #include +#include int udp_server() @@ -22,6 +24,16 @@ int udp_server() return 1; } + struct timeval read_timeout; + read_timeout.tv_sec = 0; + read_timeout.tv_usec = 10; + if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &read_timeout, sizeof(struct timeval)) != 0) + { + perror( "setsockopt" ); + exit(1); + } + /*fcntl(fd, F_SETFL, fcntl(fd,F_GETFL, 0)|O_NONBLOCK);*/ + struct sockaddr_in serveraddr; memset( &serveraddr, 0, sizeof(serveraddr) ); serveraddr.sin_family = AF_INET; @@ -35,13 +47,31 @@ int udp_server() return fd; } -void udp_recv(int fd, uint8_t * buf, int size) +int udp_recv(int fd, uint8_t * buf, int size) { + + fd_set input; + FD_ZERO(&input); + FD_SET(fd, &input); + struct timeval timeout; + timeout.tv_sec = 0; + timeout.tv_usec = 100000; + int n = select(fd + 1, &input, NULL, NULL, &timeout); + if (n == -1) { + perror("select\n"); + exit(1); + } else if (n == 0) + return 0; + if (!FD_ISSET(fd, &input)) + { + + } int length = recvfrom( fd, buf, size, 0, NULL, 0 ); if ( length < 0 ) { perror( "recvfrom failed" ); exit(1); } + return length; } diff --git a/udp_bridge.h b/udp_bridge.h index c6f8885..448f2ac 100644 --- a/udp_bridge.h +++ b/udp_bridge.h @@ -4,7 +4,7 @@ int udp_server(); // Recv from anyone -void udp_recv(int fd, uint8_t * buf, int size); +int udp_recv(int fd, uint8_t * buf, int size); // Send to 127.0.0.1:7112 void udp_send(int fd, uint8_t * buf, int size); diff --git a/usbhid.c b/usbhid.c index 45fe703..e5f561b 100644 --- a/usbhid.c +++ b/usbhid.c @@ -1,5 +1,7 @@ #include +#include +#include #include "usbhid.h" #include "udp_bridge.h" @@ -13,10 +15,16 @@ void usbhid_init() serverfd = udp_server(); } -// Receive 64 byte USB HID message -void usbhid_recv(uint8_t * msg) +// Receive 64 byte USB HID message, don't block, return size of packet, return 0 if nothing +int usbhid_recv(uint8_t * msg) { - udp_recv(serverfd, msg, HID_MESSAGE_SIZE); + int l = udp_recv(serverfd, msg, HID_MESSAGE_SIZE); + /*if (l && l != HID_MESSAGE_SIZE)*/ + /*{*/ + /*printf("Error, recv'd message of wrong size %d", l);*/ + /*exit(1);*/ + /*}*/ + return l; } diff --git a/usbhid.h b/usbhid.h index 1e97d8d..a0b5956 100644 --- a/usbhid.h +++ b/usbhid.h @@ -7,7 +7,7 @@ void usbhid_init(); -void usbhid_recv(uint8_t * msg); +int usbhid_recv(uint8_t * msg); void usbhid_send(uint8_t * msg);