From 9328cad7f191de23c0891600d9a82c3b966762f0 Mon Sep 17 00:00:00 2001 From: Mark Bryars Date: Tue, 3 Nov 2015 00:08:18 +0100 Subject: [PATCH] Add U2F support --- firmware/Makefile | 1 + firmware/protob/storage.pb.c | 3 +- firmware/protob/storage.pb.h | 11 +- firmware/storage.c | 12 + firmware/storage.h | 2 + firmware/u2f.c | 725 +++++++++++++++++++++++++++++++++++ firmware/u2f.h | 58 +++ firmware/u2f/u2f.h | 141 +++++++ firmware/u2f/u2f_hid.h | 140 +++++++ firmware/u2f/u2f_keys.h | 46 +++ firmware/usb.c | 116 +++++- trezor-common | 2 +- 12 files changed, 1245 insertions(+), 12 deletions(-) create mode 100644 firmware/u2f.c create mode 100644 firmware/u2f.h create mode 100644 firmware/u2f/u2f.h create mode 100644 firmware/u2f/u2f_hid.h create mode 100644 firmware/u2f/u2f_keys.h diff --git a/firmware/Makefile b/firmware/Makefile index fa4aa3e16..a321a7792 100644 --- a/firmware/Makefile +++ b/firmware/Makefile @@ -3,6 +3,7 @@ APPVER = 1.0.0 NAME = trezor OBJS += usb.o +OBJS += u2f.o OBJS += messages.o OBJS += storage.o OBJS += trezor.o diff --git a/firmware/protob/storage.pb.c b/firmware/protob/storage.pb.c index 68da897f2..67f1619a8 100644 --- a/firmware/protob/storage.pb.c +++ b/firmware/protob/storage.pb.c @@ -5,7 +5,7 @@ -const pb_field_t Storage_fields[11] = { +const pb_field_t Storage_fields[12] = { PB_FIELD2( 1, UINT32 , REQUIRED, STATIC , FIRST, Storage, version, version, 0), PB_FIELD2( 2, MESSAGE , OPTIONAL, STATIC , OTHER, Storage, node, version, &HDNodeType_fields), PB_FIELD2( 3, STRING , OPTIONAL, STATIC , OTHER, Storage, mnemonic, node, 0), @@ -16,6 +16,7 @@ const pb_field_t Storage_fields[11] = { PB_FIELD2( 8, STRING , OPTIONAL, STATIC , OTHER, Storage, label, language, 0), PB_FIELD2( 9, BOOL , OPTIONAL, STATIC , OTHER, Storage, imported, label, 0), PB_FIELD2( 10, BYTES , OPTIONAL, STATIC , OTHER, Storage, homescreen, imported, 0), + PB_FIELD2( 11, UINT32 , OPTIONAL, STATIC , OTHER, Storage, u2f_counter, homescreen, 0), PB_LAST_FIELD }; diff --git a/firmware/protob/storage.pb.h b/firmware/protob/storage.pb.h index 2f51923fd..c5d942eb9 100644 --- a/firmware/protob/storage.pb.h +++ b/firmware/protob/storage.pb.h @@ -37,13 +37,15 @@ typedef struct _Storage { bool imported; bool has_homescreen; Storage_homescreen_t homescreen; + bool has_u2f_counter; + uint32_t u2f_counter; } Storage; /* Default values for struct fields */ /* Initializer values for message structs */ -#define Storage_init_default {0, false, HDNodeType_init_default, false, "", false, 0, false, 0, false, "", false, "", false, "", false, 0, false, {0, {0}}} -#define Storage_init_zero {0, false, HDNodeType_init_zero, false, "", false, 0, false, 0, false, "", false, "", false, "", false, 0, false, {0, {0}}} +#define Storage_init_default {0, false, HDNodeType_init_default, false, "", false, 0, false, 0, false, "", false, "", false, "", false, 0, false, {0, {0}}, false, 0} +#define Storage_init_zero {0, false, HDNodeType_init_zero, false, "", false, 0, false, 0, false, "", false, "", false, "", false, 0, false, {0, {0}}, false, 0} /* Field tags (for use in manual encoding/decoding) */ #define Storage_version_tag 1 @@ -56,12 +58,13 @@ typedef struct _Storage { #define Storage_label_tag 8 #define Storage_imported_tag 9 #define Storage_homescreen_tag 10 +#define Storage_u2f_counter_tag 11 /* Struct field encoding specification for nanopb */ -extern const pb_field_t Storage_fields[11]; +extern const pb_field_t Storage_fields[12]; /* Maximum encoded size of messages (where known) */ -#define Storage_size (1359 + HDNodeType_size) +#define Storage_size (1365 + HDNodeType_size) #ifdef __cplusplus } /* extern "C" */ diff --git a/firmware/storage.c b/firmware/storage.c index d5f5bc9f9..f28bce589 100644 --- a/firmware/storage.c +++ b/firmware/storage.c @@ -401,3 +401,15 @@ bool storage_isInitialized(void) { return storage.has_node || storage.has_mnemonic; } + +uint32_t storage_nextU2FCounter(void) +{ + if(!storage.has_u2f_counter) { + storage.has_u2f_counter = true; + storage.u2f_counter = 1; + } else { + storage.u2f_counter++; + } + storage_commit(); + return storage.u2f_counter; +} diff --git a/firmware/storage.h b/firmware/storage.h index b1041a8b9..793c8c4ea 100644 --- a/firmware/storage.h +++ b/firmware/storage.h @@ -58,6 +58,8 @@ void storage_resetPinFails(void); void storage_increasePinFails(void); uint32_t storage_getPinFails(void); +uint32_t storage_nextU2FCounter(void); + bool storage_isInitialized(void); extern Storage storage; diff --git a/firmware/u2f.c b/firmware/u2f.c new file mode 100644 index 000000000..09f2ca810 --- /dev/null +++ b/firmware/u2f.c @@ -0,0 +1,725 @@ +/* + * This file is part of the TREZOR project. + * + * Copyright (C) 2015 Mark Bryars + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + + +#include +#include + +#include "debug.h" +#include "storage.h" +#include "bip32.h" +#include "layout2.h" +#include "usb.h" +#include "buttons.h" +#include "trezor.h" +#include "nist256p1.h" +#include "rng.h" + +#include "u2f/u2f.h" +#include "u2f/u2f_hid.h" +#include "u2f/u2f_keys.h" +#include "u2f.h" + +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) + +// About 1/2 Second according to values used in protect.c +#define U2F_TIMEOUT 840000/2 +#define U2F_OUT_PKT_BUFFER_LEN 128 + +// Initialise without a cid +static uint32_t cid = CID_BROADCAST; + +// Circular Output buffer +static uint32_t u2f_out_start = 0; +static uint32_t u2f_out_end = 0; +static uint8_t u2f_out_packets[U2F_OUT_PKT_BUFFER_LEN][HID_RPT_SIZE]; + +#define U2F_PUBKEY_LEN 65 +#define KEY_HANDLE_LEN 64 + +// Auth/Register request state machine +typedef enum { + INIT = 0, + BTN_NO = 1, + BTN_YES = 2, + AUTH = 10, + AUTH_FAIL = 11, + AUTH_PASS = 12, + REG = 20, + REG_FAIL = 21, + REG_PASS = 22 +} U2F_STATE; + +static U2F_STATE last_req_state = INIT; + +typedef struct { + uint8_t reserved; + uint8_t appId[U2F_APPID_SIZE]; + uint8_t chal[U2F_CHAL_SIZE]; + uint8_t keyHandle[KEY_HANDLE_LEN]; + uint8_t pubKey[U2F_PUBKEY_LEN]; +} U2F_REGISTER_SIG_STR; + +typedef struct { + uint8_t appId[U2F_APPID_SIZE]; + uint8_t flags; + uint8_t ctr[4]; + uint8_t chal[U2F_CHAL_SIZE]; +} U2F_AUTHENTICATE_SIG_STR; + +uint8_t buttonState(void) +{ + buttonUpdate(); + + if ((button.NoDown > 10) || button.NoUp) + return BTN_NO; + if ((button.YesDown > 10) || button.YesUp) + return BTN_YES; + return 0; +} + +void int2hex(uint8_t *dst, const uint32_t i) +{ + dst[0] = '0' + ((i >> 28) & 0x0F); + dst[1] = '0' + ((i >> 24) & 0x0F); + dst[2] = '0' + ((i >> 20) & 0x0F); + dst[3] = '0' + ((i >> 16) & 0x0F); + dst[4] = '0' + ((i >> 12) & 0x0F); + dst[5] = '0' + ((i >> 8) & 0x0F); + dst[6] = '0' + ((i >> 4) & 0x0F); + dst[7] = '0' + (i & 0x0F); + dst[8] = '\0'; + + int t = 0; + for (; t < 8; t++) { + if (dst[t] > '9') + dst[t] += 7; // 'A'-'9'+1 + } +} + +char *debugInt(const uint32_t i) +{ + static uint8_t n = 0; + static uint8_t id[8][9]; + int2hex(id[n], i); + debugLog(0, "", (const char *)id[n]); + char *ret = (char *)id[n]; + n = (n + 1) % 8; + return ret; +} + +static uint32_t dialog_timeout = 0; + +void LayoutHomeAfterTimeout(void) +{ + static bool timeoutLock = false; + + if (timeoutLock || dialog_timeout == 0) + return; // Dialog has cleared or already in loop + + timeoutLock = true; + U2F_STATE rs = last_req_state; + U2F_STATE bs = INIT; + while (dialog_timeout-- && rs == last_req_state && bs == 0) { + usbPoll(); // may trigger new request + bs = buttonState(); + } + timeoutLock = false; + + if (rs != last_req_state) + return; // Reset by new request don't clear screen + + if (dialog_timeout == 0) { + last_req_state += BTN_NO; // Timeout is like button no + } + else { + last_req_state += bs; + dialog_timeout = 0; + } + + layoutHome(); +} + +uint32_t next_cid(void) +{ + // extremely unlikely but hey + do { + cid = random32(); + } while (cid == 0 || cid == CID_BROADCAST); + return cid; +} + +void u2fhid_read(const U2FHID_FRAME *f) +{ + static uint8_t seq, cmd; + static uint32_t len; + static uint8_t *buf_ptr; + static uint8_t buf[7609]; + + if ((f->cid != CID_BROADCAST) && (f->cid != cid)) { + return; // Not for us + } + + if (f->type & TYPE_INIT) { + seq = 0; + buf_ptr = buf; + len = MSG_LEN(*f); + cmd = f->type; + memcpy(buf_ptr, f->init.data, sizeof(f->init.data)); + buf_ptr += sizeof(f->init.data); + } + else { + if (f->cont.seq == seq) { + seq++; + memcpy(buf_ptr, f->cont.data, sizeof(f->cont.data)); + buf_ptr += sizeof(f->cont.data); + } + else { + return send_u2fhid_error(ERR_INVALID_SEQ); + } + } + + // Broadcast is reserved for init + if (cid == CID_BROADCAST && cmd != U2FHID_INIT) + return; + + // Check length isnt bigger than spec max + if (len > sizeof(buf)) + return send_u2fhid_error(ERR_INVALID_LEN); + + // Do we need to wait for more data + if ((buf_ptr - buf) < (signed)len) { + // debugLog(0, "", "u2fhid_read wait"); + return; + } + + // We have all the data + switch (cmd) { + case U2FHID_PING: + u2fhid_ping(buf, len); + break; + case U2FHID_MSG: + u2fhid_msg((APDU *)buf, len); + break; + case U2FHID_LOCK: + u2fhid_lock(buf, len); + break; + case U2FHID_INIT: + u2fhid_init((const U2FHID_INIT_REQ *)buf); + break; + case U2FHID_WINK: + u2fhid_wink(buf, len); + break; + // case U2FHID_SYNC: + // u2fhid_sync(buf, len); + break; + default: + send_u2fhid_error(ERR_INVALID_CMD); + break; + } +} + +void u2fhid_ping(const uint8_t *buf, uint32_t len) +{ + debugLog(0, "", "u2fhid_ping"); + send_u2fhid_msg(U2FHID_PING, buf, len); +} + +void u2fhid_wink(const uint8_t *buf, uint32_t len) +{ + debugLog(0, "", "u2fhid_wink"); + (void)buf; + + if (len > 0) + return send_u2fhid_error(ERR_INVALID_LEN); + + if (dialog_timeout > 0) + dialog_timeout = U2F_TIMEOUT; + + U2FHID_FRAME f; + bzero(&f, sizeof(f)); + f.cid = cid; + f.init.cmd = U2FHID_WINK; + f.init.bcntl = 0; + queue_u2f_pkt(&f); +} + +void u2fhid_sync(const uint8_t *buf, uint32_t len) +{ + debugLog(0, "", "u2fhid_sync"); + (void)buf; + + if (len > 0) + return send_u2fhid_error(ERR_INVALID_LEN); + + // Abort things. + dialog_timeout = 0; +} + +void u2fhid_lock(const uint8_t *buf, uint32_t len) +{ + debugLog(0, "", "u2fhid_lock"); + (void)buf; + (void)len; + send_u2fhid_error(ERR_INVALID_CMD); +} + +void u2fhid_init(const U2FHID_INIT_REQ *init_req) +{ + debugLog(0, "", "u2fhid_init"); + U2FHID_FRAME f; + U2FHID_INIT_RESP *resp = (U2FHID_INIT_RESP *)f.init.data; + + bzero(&f, sizeof(f)); + f.cid = CID_BROADCAST; + f.init.cmd = U2FHID_INIT; + f.init.bcnth = 0; + f.init.bcntl = sizeof(U2FHID_INIT_RESP); + + memcpy(resp->nonce, init_req->nonce, sizeof(init_req->nonce)); + resp->cid = next_cid(); + resp->versionInterface = U2FHID_IF_VERSION; + resp->versionMajor = VERSION_MAJOR; + resp->versionMinor = VERSION_MINOR; + resp->versionBuild = VERSION_PATCH; + resp->capFlags = CAPFLAG_WINK; + + queue_u2f_pkt(&f); +} + +void queue_u2f_pkt(const U2FHID_FRAME *u2f_pkt) +{ + // debugLog(0, "", "u2f_write_pkt"); + uint32_t next = (u2f_out_end + 1) % U2F_OUT_PKT_BUFFER_LEN; + if (u2f_out_start == next) { + debugLog(0, "", "u2f_write_pkt full"); + return; // Buffer full :( + } + memcpy(u2f_out_packets[u2f_out_end], u2f_pkt, HID_RPT_SIZE); + u2f_out_end = next; +} + +uint8_t *u2f_out_data(void) +{ + if (u2f_out_start == u2f_out_end) + return NULL; // No data + // debugLog(0, "", "u2f_out_data"); + uint32_t t = u2f_out_start; + u2f_out_start = (u2f_out_start + 1) % U2F_OUT_PKT_BUFFER_LEN; + return u2f_out_packets[t]; +} + +void u2fhid_msg(const APDU *a, uint32_t len) +{ + static bool lock = false; + + if ((APDU_LEN(*a) + sizeof(APDU)) > len) { + debugLog(0, "", "BAD APDU LENGTH"); + debugInt(APDU_LEN(*a)); + debugInt(len); + return; + } + + // Very crude locking, incase another message comes in while we wait. This + // actually can probably be removed as no code inside calls usbPoll anymore + if (lock) + return send_u2fhid_error(ERR_CHANNEL_BUSY); + + lock = true; + + switch (a->ins) { + case U2F_REGISTER: + u2f_register(a); + break; + case U2F_AUTHENTICATE: + u2f_authenticate(a); + break; + case U2F_VERSION: + u2f_version(a); + break; + default: + debugLog(0, "", "u2f unknown cmd"); + send_u2f_error(U2F_SW_INS_NOT_SUPPORTED); + } + + lock = false; + + LayoutHomeAfterTimeout(); +} + +void send_u2fhid_msg(const uint8_t cmd, const uint8_t *data, const uint32_t len) +{ + U2FHID_FRAME f; + uint8_t *p = (uint8_t *)data; + uint32_t l = len; + uint32_t psz; + uint8_t seq = 0; + + // debugLog(0, "", "send_u2fhid_msg"); + + bzero(&f, sizeof(f)); + f.cid = cid; + f.init.cmd = cmd; + f.init.bcnth = len >> 8; + f.init.bcntl = len & 0xff; + + // Init packet + psz = MIN(sizeof(f.init.data), l); + memcpy(f.init.data, p, psz); + queue_u2f_pkt(&f); + l -= psz; + p += psz; + + // Cont packet(s) + for (; l > 0; l -= psz, p += psz) { + // debugLog(0, "", "send_u2fhid_msg con"); + bzero(&f.cont.data, sizeof(f.cont.data)); + f.cont.seq = seq++; + psz = MIN(sizeof(f.cont.data), l); + memcpy(f.cont.data, p, psz); + queue_u2f_pkt(&f); + } + + if (data + len != p) { + debugLog(0, "", "send_u2fhid_msg is bad"); + debugInt(data + len - p); + } +} + +void send_u2fhid_error(uint8_t err) +{ + U2FHID_FRAME f; + + bzero(&f, sizeof(f)); + f.cid = cid; + f.init.cmd = U2FHID_ERROR; + f.init.bcntl = 1; + f.init.data[0] = err; + queue_u2f_pkt(&f); +} + +void u2f_version(const APDU *a) +{ + // INCLUDES SW_NO_ERROR + static const uint8_t version_response[] = {'U', '2', 'F', '_', + 'V', '2', 0x90, 0x00}; + (void)a; + debugLog(0, "", "u2f version"); + send_u2f_msg(version_response, sizeof(version_response)); +} + +const HDNode *getDerivedNode(uint32_t *address_n, size_t address_n_count) +{ + static HDNode node; + if (!storage_getRootNode(&node)) { + layoutHome(); + debugLog(0, "", "ERR: Device not init"); + return 0; + } + if (!address_n || address_n_count == 0) { + return &node; + } + if (hdnode_private_ckd_cached(&node, address_n, address_n_count) == 0) { + layoutHome(); + debugLog(0, "", "ERR: Derive private failed"); + return 0; + } + return &node; +} + +const HDNode *generateKeyHandle(const uint8_t app_id[], uint8_t key_handle[]) +{ + uint8_t keybase[64]; + + // First half of keyhandle is random + random_buffer(key_handle, 32); + + // prepare keypair from /random data + const HDNode *node = + getDerivedNode((uint32_t*)key_handle, 32/sizeof(uint32_t)); + + // For second half of keyhandle + // Signature of app_id and random data + memcpy(&keybase[0], app_id, 32); + memcpy(&keybase[32], key_handle, 32); + uint8_t sig[64]; + ecdsa_sign(&nist256p1, node->private_key, + (uint8_t *)&keybase, sizeof(keybase), sig, + NULL); + + // Copy 32 bytes of signature into keyhandle + memcpy(&key_handle[32], sig, 32); + + // Done! + return node; +} + + +const HDNode *validateKeyHandle(const uint8_t app_id[], const uint8_t key_handle[]) +{ + uint8_t keybase[64]; + memcpy(&keybase[0], app_id, 32); + memcpy(&keybase[32], key_handle, 32); + + const HDNode *node = + getDerivedNode((uint32_t*)key_handle, 32/sizeof(uint32_t)); + + uint8_t sig[64]; + ecdsa_sign(&nist256p1, node->private_key, + (uint8_t *)&keybase, sizeof(keybase), sig, + NULL); + + if (memcmp(&key_handle[32], sig, 32) !=0) + return NULL; + + // Done! + return node; +} + + +void u2f_register(const APDU *a) +{ + static U2F_REGISTER_REQ last_req; + const U2F_REGISTER_REQ *req = (U2F_REGISTER_REQ *)a->data; + + // Validate basic request parameters + debugLog(0, "", "u2f register"); + if (APDU_LEN(*a) != sizeof(U2F_REGISTER_REQ)) { + debugLog(0, "", "u2f register - badlen"); + send_u2f_error(U2F_SW_WRONG_DATA); + return; + } + + // If this request is different from last request, reset state machine + if (memcmp(&last_req, req, sizeof(last_req)) != 0) { + memcpy(&last_req, req, sizeof(last_req)); + last_req_state = INIT; + } + + // First Time request, return not present and display request dialog + if (last_req_state == 0) { + // wake up crypto system to be ready for signing + getDerivedNode(NULL, 0); + // error: testof-user-presence is required + send_u2f_error(U2F_SW_CONDITIONS_NOT_SATISFIED); + buttonUpdate(); // Clear button state + layoutDialog(DIALOG_ICON_QUESTION, "Cancel", "Register", + NULL, "Register U2F", "security key", "", "", "", NULL); + dialog_timeout = U2F_TIMEOUT; + last_req_state = REG; + return; + } + + // Still awaiting Keypress + if (last_req_state == REG) { + // error: testof-user-presence is required + send_u2f_error(U2F_SW_CONDITIONS_NOT_SATISFIED); + dialog_timeout = U2F_TIMEOUT; + return; + } + + // Buttons said no! + if (last_req_state == REG_FAIL) { + send_u2f_error(U2F_SW_WRONG_DATA); // error:bad key handle + return; + } + + // Buttons said yes + if (last_req_state == REG_PASS) { + uint8_t data[sizeof(U2F_REGISTER_RESP) + 2]; + U2F_REGISTER_RESP *resp = (U2F_REGISTER_RESP *)&data; + bzero(data, sizeof(data)); + + + resp->registerId = U2F_REGISTER_ID; + resp->keyHandleLen = KEY_HANDLE_LEN; + // Generate keypair for this appId + const HDNode *node = + generateKeyHandle(req->appId, (uint8_t*)&resp->keyHandleCertSig); + + if (!node) { + debugLog(0, "", "getDerivedNode Fail"); + send_u2f_error(U2F_SW_WRONG_DATA); // error:bad key handle + return; + } + + ecdsa_get_public_key65(&nist256p1, node->private_key, + (uint8_t *)&resp->pubKey); + + memcpy(resp->keyHandleCertSig + resp->keyHandleLen, + U2F_ATT_CERT, sizeof(U2F_ATT_CERT)); + + uint8_t sig[64]; + U2F_REGISTER_SIG_STR sig_base; + sig_base.reserved = 0; + memcpy(sig_base.appId, req->appId, U2F_APPID_SIZE); + memcpy(sig_base.chal, req->chal, U2F_CHAL_SIZE); + memcpy(sig_base.keyHandle, &resp->keyHandleCertSig, KEY_HANDLE_LEN); + memcpy(sig_base.pubKey, &resp->pubKey, U2F_PUBKEY_LEN); + ecdsa_sign(&nist256p1, U2F_ATT_PRIV_KEY, (uint8_t *)&sig_base, + sizeof(sig_base), sig, NULL); + + // Where to write the signature in the response + uint8_t *resp_sig = resp->keyHandleCertSig + + resp->keyHandleLen + sizeof(U2F_ATT_CERT); + // Convert to der for the response + const uint8_t sig_len = ecdsa_sig_to_der(sig, resp_sig); + + // Append success bytes + memcpy(resp->keyHandleCertSig + resp->keyHandleLen + + sizeof(U2F_ATT_CERT) + sig_len, + "\x90\x00", 2); + + int l = 1 /* registerId */ + U2F_PUBKEY_LEN + + 1 /* keyhandleLen */ + resp->keyHandleLen + + sizeof(U2F_ATT_CERT) + sig_len + 2; + + send_u2f_msg(data, l); + return; + } + + // Didnt expect to get here + dialog_timeout = 0; +} + +void u2f_authenticate(const APDU *a) +{ + const U2F_AUTHENTICATE_REQ *req = (U2F_AUTHENTICATE_REQ *)a->data; + static U2F_AUTHENTICATE_REQ last_req; + + if (APDU_LEN(*a) < 64) { /// FIXME: decent value + debugLog(0, "", "u2f authenticate - badlen"); + send_u2f_error(U2F_SW_WRONG_DATA); + return; + } + + if (req->keyHandleLen != KEY_HANDLE_LEN) { + debugLog(0, "", "u2f auth - bad keyhandle len"); + send_u2f_error(U2F_SW_WRONG_DATA); // error:bad key handle + return; + } + + const HDNode *node = + validateKeyHandle(req->appId, req->keyHandle); + + if (!node) { + debugLog(0, "", "u2f auth - bad keyhandle len"); + send_u2f_error(U2F_SW_WRONG_DATA); // error:bad key handle + return; + } + + if (a->p1 == U2F_AUTH_CHECK_ONLY) { + debugLog(0, "", "u2f authenticate check"); + // This is a success for a good keyhandle + // A failed check would have happened earlier + // error: testof-user-presence is required + send_u2f_error(U2F_SW_CONDITIONS_NOT_SATISFIED); + return; + } + + if (a->p1 != U2F_AUTH_ENFORCE) { + debugLog(0, "", "u2f authenticate unknown"); + // error:bad key handle + send_u2f_error(U2F_SW_WRONG_DATA); + return; + } + + debugLog(0, "", "u2f authenticate enforce"); + + if (memcmp(&last_req, req, sizeof(last_req)) != 0) { + memcpy(&last_req, req, sizeof(last_req)); + last_req_state = INIT; + } + + if (last_req_state == INIT) { + // error: testof-user-presence is required + send_u2f_error(U2F_SW_CONDITIONS_NOT_SATISFIED); + buttonUpdate(); // Clear button state + layoutDialog(DIALOG_ICON_QUESTION, "Cancel", "Authenticate", NULL, + "Authenticate U2F", "security key", "", "", "", NULL); + dialog_timeout = U2F_TIMEOUT; + last_req_state = AUTH; + return; + } + + // Awaiting Keypress + if (last_req_state == AUTH) { + // error: testof-user-presence is required + send_u2f_error(U2F_SW_CONDITIONS_NOT_SATISFIED); + dialog_timeout = U2F_TIMEOUT; + return; + } + + // Buttons said no! + if (last_req_state == AUTH_FAIL) { + send_u2f_error( + U2F_SW_WRONG_DATA); // error:bad key handle + return; + } + + // Buttons said yes + if (last_req_state == AUTH_PASS) { + uint8_t buf[sizeof(U2F_AUTHENTICATE_RESP) + 2]; + U2F_AUTHENTICATE_RESP *resp = + (U2F_AUTHENTICATE_RESP *)&buf; + + const uint32_t ctr = storage_nextU2FCounter(); + resp->flags = U2F_AUTH_FLAG_TUP; + resp->ctr[0] = ctr >> 24 & 0xff; + resp->ctr[1] = ctr >> 16 & 0xff; + resp->ctr[2] = ctr >> 8 & 0xff; + resp->ctr[3] = ctr & 0xff; + + // Build and sign response + U2F_AUTHENTICATE_SIG_STR sig_base; + uint8_t sig[64]; + memcpy(sig_base.appId, req->appId, U2F_APPID_SIZE); + sig_base.flags = resp->flags; + memcpy(sig_base.ctr, resp->ctr, 4); + memcpy(sig_base.chal, req->chal, U2F_CHAL_SIZE); + ecdsa_sign(&nist256p1, node->private_key, + (uint8_t *)&sig_base, sizeof(sig_base), sig, + NULL); + + // Copy DER encoded signature into response + const uint8_t sig_len = ecdsa_sig_to_der(sig, resp->sig); + + // Append OK + memcpy(buf + sizeof(U2F_AUTHENTICATE_RESP) - + U2F_MAX_EC_SIG_SIZE + sig_len, + "\x90\x00", 2); + send_u2f_msg(buf, sizeof(U2F_AUTHENTICATE_RESP) - + U2F_MAX_EC_SIG_SIZE + sig_len + + 2); + last_req_state = INIT; + } +} + +void send_u2f_error(const uint16_t err) +{ + uint8_t data[2]; + data[0] = err >> 8 & 0xFF; + data[1] = err & 0xFF; + send_u2f_msg(data, 2); +} + +void send_u2f_msg(const uint8_t *data, const uint32_t len) +{ + send_u2fhid_msg(U2FHID_MSG, data, len); +} diff --git a/firmware/u2f.h b/firmware/u2f.h new file mode 100644 index 000000000..cd1d102bf --- /dev/null +++ b/firmware/u2f.h @@ -0,0 +1,58 @@ +/* + * This file is part of the TREZOR project. + * + * Copyright (C) 2015 Mark Bryars + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#ifndef __U2F_H__ +#define __U2F_H__ + +#include +#include +#include "u2f/u2f_hid.h" +#include "trezor.h" + +typedef struct { + uint8_t cla, ins, p1, p2; + uint8_t lc1, lc2, lc3; + uint8_t data[]; +} APDU; + +#define APDU_LEN(A) (uint32_t)(((A).lc1 << 16) + ((A).lc2 << 8) + ((A).lc3)) + +void u2fhid_read(const U2FHID_FRAME *buf); +bool u2fhid_write(uint8_t *buf); +void u2fhid_init(const U2FHID_INIT_REQ *init_req); +void u2fhid_ping(const uint8_t *buf, uint32_t len); +void u2fhid_wink(const uint8_t *buf, uint32_t len); +void u2fhid_sync(const uint8_t *buf, uint32_t len); +void u2fhid_lock(const uint8_t *buf, uint32_t len); +void u2fhid_msg(const APDU *a, uint32_t len); +void queue_u2f_pkt(const U2FHID_FRAME *u2f_pkt); + +uint8_t *u2f_out_data(void); +void u2f_register(const APDU *a); +void u2f_version(const APDU *a); +void u2f_authenticate(const APDU *a); + +void send_u2f_msg(const uint8_t *data, const uint32_t len); +void send_u2f_error(const uint16_t err); + +void send_u2fhid_msg(const uint8_t cmd, const uint8_t *data, + const uint32_t len); +void send_u2fhid_error(const uint8_t err); + +#endif diff --git a/firmware/u2f/u2f.h b/firmware/u2f/u2f.h new file mode 100644 index 000000000..4291c594b --- /dev/null +++ b/firmware/u2f/u2f.h @@ -0,0 +1,141 @@ +/* + Copyright (C) 2013-2015 Yubico AB + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1, 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 Lesser + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program; if not, see . +*/ + +// Common U2F raw message format header. +// 2014-08-14 J Ehrensvard, Yubico, Inc. + +#ifndef __U2F_H_INCLUDED__ +#define __U2F_H_INCLUDED__ + +#ifdef _MSC_VER // Windows +typedef unsigned char uint8_t; +typedef unsigned short uint16_t; +typedef unsigned int uint32_t; +typedef unsigned long int uint64_t; +#else +#include +#endif + +#ifdef __cplusplus +extern "C" +{ +#endif + +// General constants + +#define U2F_EC_KEY_SIZE 32 // EC key size in bytes +#define U2F_EC_POINT_SIZE ((U2F_EC_KEY_SIZE * 2) + 1) // Size of EC point +#define U2F_MAX_KH_SIZE 128 // Max size of key handle +#define U2F_MAX_ATT_CERT_SIZE 1024 // Max size of attestation certificate +#define U2F_MAX_EC_SIG_SIZE 72 // Max size of DER coded EC signature +#define U2F_CTR_SIZE 4 // Size of counter field +#define U2F_APPID_SIZE 32 // Size of application id +#define U2F_CHAL_SIZE 32 // Size of challenge + +#define ENC_SIZE(x) ((x + 7) & 0xfff8) + +// EC (uncompressed) point + +#define U2F_POINT_UNCOMPRESSED 0x04 // Uncompressed point format + + typedef struct + { + uint8_t pointFormat; // Point type + uint8_t x[U2F_EC_KEY_SIZE]; // X-value + uint8_t y[U2F_EC_KEY_SIZE]; // Y-value + } U2F_EC_POINT; + +// U2F native commands + +#define U2F_REGISTER 0x01 // Registration command +#define U2F_AUTHENTICATE 0x02 // Authenticate/sign command +#define U2F_VERSION 0x03 // Read version string command + +#define U2F_VENDOR_FIRST 0x40 // First vendor defined command +#define U2F_VENDOR_LAST 0x7f // Last vendor defined command + +// U2F_CMD_REGISTER command defines + +#define U2F_REGISTER_ID 0x05 // Version 2 registration identifier +#define U2F_REGISTER_HASH_ID 0x00 // Version 2 hash identintifier + + typedef struct + { + uint8_t chal[U2F_CHAL_SIZE]; // Challenge + uint8_t appId[U2F_APPID_SIZE]; // Application id + } U2F_REGISTER_REQ; + + typedef struct + { + uint8_t registerId; // Registration identifier (U2F_REGISTER_ID_V2) + U2F_EC_POINT pubKey; // Generated public key + uint8_t keyHandleLen; // Length of key handle + uint8_t keyHandleCertSig[U2F_MAX_KH_SIZE + // Key handle + U2F_MAX_ATT_CERT_SIZE + // Attestation certificate + U2F_MAX_EC_SIG_SIZE]; // Registration signature + } U2F_REGISTER_RESP; + +// U2F_CMD_AUTHENTICATE command defines + +// Authentication control byte + +#define U2F_AUTH_ENFORCE 0x03 // Enforce user presence and sign +#define U2F_AUTH_CHECK_ONLY 0x07 // Check only +#define U2F_AUTH_FLAG_TUP 0x01 // Test of user presence set + + typedef struct + { + uint8_t chal[U2F_CHAL_SIZE]; // Challenge + uint8_t appId[U2F_APPID_SIZE]; // Application id + uint8_t keyHandleLen; // Length of key handle + uint8_t keyHandle[U2F_MAX_KH_SIZE]; // Key handle + } U2F_AUTHENTICATE_REQ; + + typedef struct + { + uint8_t flags; // U2F_AUTH_FLAG_ values + uint8_t ctr[U2F_CTR_SIZE]; // Counter field (big-endian) + uint8_t sig[U2F_MAX_EC_SIG_SIZE]; // Signature + } U2F_AUTHENTICATE_RESP; + +// Common raw message format (ISO7816-4:2005 mapping) + + typedef struct + { + uint8_t cla; // Class - reserved + uint8_t ins; // U2F instruction + uint8_t p1; // U2F parameter 1 + uint8_t p2; // U2F parameter 2 + uint8_t lc1; // Length field, set to zero + uint8_t lc2; // Length field, MSB + uint8_t lc3; // Length field, LSB + uint8_t data[1]; // Data field + } U2F_MSG; + +// Command status responses + +#define U2F_SW_NO_ERROR 0x9000 // SW_NO_ERROR +#define U2F_SW_WRONG_DATA 0x6984 // SW_WRONG_DATA +#define U2F_SW_CONDITIONS_NOT_SATISFIED 0x6985 // SW_CONDITIONS_NOT_SATISFIED +#define U2F_SW_INS_NOT_SUPPORTED 0x6d00 // SW_INS_NOT_SUPPORTED +#define U2F_SW_CLA_NOT_SUPPORTED 0x6e00 // SW_CLA_NOT_SUPPORTED + +#ifdef __cplusplus +} +#endif + +#endif // __U2F_H_INCLUDED__ diff --git a/firmware/u2f/u2f_hid.h b/firmware/u2f/u2f_hid.h new file mode 100644 index 000000000..d576d197c --- /dev/null +++ b/firmware/u2f/u2f_hid.h @@ -0,0 +1,140 @@ +/* + Copyright (C) 2013-2015 Yubico AB + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1, 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 Lesser + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program; if not, see . +*/ + +// Common U2F HID transport header. +// 2014-08-21 J Ehrensvard, Yubico, Inc. + +#ifndef __U2FHID_H_INCLUDED__ +#define __U2FHID_H_INCLUDED__ + +#ifdef _MSC_VER // Windows +typedef unsigned char uint8_t; +typedef unsigned short uint16_t; +typedef unsigned int uint32_t; +typedef unsigned long int uint64_t; +#else +#include +#endif + +#ifdef __cplusplus +extern "C" +{ +#endif + +// Size of HID reports + +#define HID_RPT_SIZE 64 // Default size of raw HID report + +// Frame layout - command- and continuation frames + +#define CID_BROADCAST 0xffffffff // Broadcast channel id + +#define TYPE_MASK 0x80 // Frame type mask +#define TYPE_INIT 0x80 // Initial frame identifier +#define TYPE_CONT 0x00 // Continuation frame identifier + + typedef struct + { + uint32_t cid; // Channel identifier + union + { + uint8_t type; // Frame type - b7 defines type + struct + { + uint8_t cmd; // Command - b7 set + uint8_t bcnth; // Message byte count - high part + uint8_t bcntl; // Message byte count - low part + uint8_t data[HID_RPT_SIZE - 7]; // Data payload + } init; + struct + { + uint8_t seq; // Sequence number - b7 cleared + uint8_t data[HID_RPT_SIZE - 5]; // Data payload + } cont; + }; + } U2FHID_FRAME; + +#define FRAME_TYPE(f) ((f).type & TYPE_MASK) +#define FRAME_CMD(f) ((f).init.cmd & ~TYPE_MASK) +#define MSG_LEN(f) (((f).init.bcnth << 8) + (f).init.bcntl) +#define FRAME_SEQ(f) ((f).cont.seq & ~TYPE_MASK) + +// HID usage- and usage-page definitions + +#define FIDO_USAGE_PAGE 0xf1d0 // FIDO alliance HID usage page +#define FIDO_USAGE_U2FHID 0x01 // U2FHID usage for top-level collection +#define FIDO_USAGE_DATA_IN 0x20 // Raw IN data report +#define FIDO_USAGE_DATA_OUT 0x21 // Raw OUT data report + +// General constants + +#define U2FHID_IF_VERSION 2 // Current interface implementation version +#define U2FHID_FRAME_TIMEOUT 500 // Default frame timeout in ms +#define U2FHID_TRANS_TIMEOUT 3000 // Default message timeout in ms + +// U2FHID native commands + +#define U2FHID_PING (TYPE_INIT | 0x01) // Echo data through local processor only +#define U2FHID_MSG (TYPE_INIT | 0x03) // Send U2F message frame +#define U2FHID_LOCK (TYPE_INIT | 0x04) // Send lock channel command +#define U2FHID_INIT (TYPE_INIT | 0x06) // Channel initialization +#define U2FHID_WINK (TYPE_INIT | 0x08) // Send device identification wink +#define U2FHID_ERROR (TYPE_INIT | 0x3f) // Error response + +#define U2FHID_VENDOR_FIRST (TYPE_INIT | 0x40) // First vendor defined command +#define U2FHID_VENDOR_LAST (TYPE_INIT | 0x7f) // Last vendor defined command + +// U2FHID_INIT command defines + +#define INIT_NONCE_SIZE 8 // Size of channel initialization challenge +#define CAPFLAG_WINK 0x01 // Device supports WINK command +#define CAPFLAG_LOCK 0x02 // Device supports LOCK command + + typedef struct + { + uint8_t nonce[INIT_NONCE_SIZE]; // Client application nonce + } U2FHID_INIT_REQ; + + typedef struct + { + uint8_t nonce[INIT_NONCE_SIZE]; // Client application nonce + uint32_t cid; // Channel identifier + uint8_t versionInterface; // Interface version + uint8_t versionMajor; // Major version number + uint8_t versionMinor; // Minor version number + uint8_t versionBuild; // Build version number + uint8_t capFlags; // Capabilities flags + } U2FHID_INIT_RESP; + +// Low-level error codes. Return as negatives. + +#define ERR_NONE 0x00 // No error +#define ERR_INVALID_CMD 0x01 // Invalid command +#define ERR_INVALID_PAR 0x02 // Invalid parameter +#define ERR_INVALID_LEN 0x03 // Invalid message length +#define ERR_INVALID_SEQ 0x04 // Invalid message sequencing +#define ERR_MSG_TIMEOUT 0x05 // Message has timed out +#define ERR_CHANNEL_BUSY 0x06 // Channel busy +#define ERR_LOCK_REQUIRED 0x0a // Command requires channel lock +#define ERR_INVALID_CID 0x0b // Command not allowed on this cid +#define ERR_OTHER 0x7f // Other unspecified error + +#ifdef __cplusplus +} +#endif + +#endif // __U2FHID_H_INCLUDED__ diff --git a/firmware/u2f/u2f_keys.h b/firmware/u2f/u2f_keys.h new file mode 100644 index 000000000..73c8378f7 --- /dev/null +++ b/firmware/u2f/u2f_keys.h @@ -0,0 +1,46 @@ +#ifndef __U2F_KEYS_H_INCLUDED__ +#define __U2F_KEYS_H_INCLUDED__ + +#include + +uint8_t U2F_ATT_PRIV_KEY[] = {0x71, 0x26, 0xac, 0x2b, 0xf6, 0x44, 0xdc, 0x61, + 0x86, 0xad, 0x83, 0xef, 0x1f, 0xcd, 0xf1, 0x2a, + 0x57, 0xb5, 0xcf, 0xa2, 0x00, 0x0b, 0x8a, 0xd0, + 0x27, 0xe9, 0x56, 0xe8, 0x54, 0xc5, 0x0a, 0x8b}; + +uint8_t U2F_ATT_PUB_KEY[] = { + 0x04, 0xd9, 0x18, 0xbd, 0xfa, 0x8a, 0x54, 0xac, 0x92, 0xe9, 0x0d, + 0xa9, 0x1f, 0xca, 0x7a, 0xa2, 0x64, 0x54, 0xc0, 0xd1, 0x73, 0x36, + 0x31, 0x4d, 0xde, 0x83, 0xa5, 0x4b, 0x86, 0xb5, 0xdf, 0x4e, 0xf0, + 0x52, 0x65, 0x9a, 0x1d, 0x6f, 0xfc, 0xb7, 0x46, 0x7f, 0x1a, 0xcd, + 0xdb, 0x8a, 0x33, 0x08, 0x0b, 0x5e, 0xed, 0x91, 0x89, 0x13, 0xf4, + 0x43, 0xa5, 0x26, 0x1b, 0xc7, 0x7b, 0x68, 0x60, 0x6f, 0xc1}; + +uint8_t U2F_ATT_CERT[] = { + 0x30, 0x82, 0x01, 0x19, 0x30, 0x81, 0xC0, 0x02, 0x09, 0x00, 0x8B, 0x3F, + 0xA6, 0x46, 0xDE, 0x01, 0xCB, 0xB8, 0x30, 0x0A, 0x06, 0x08, 0x2A, 0x86, + 0x48, 0xCE, 0x3D, 0x04, 0x03, 0x02, 0x30, 0x15, 0x31, 0x13, 0x30, 0x11, + 0x06, 0x03, 0x55, 0x04, 0x03, 0x0C, 0x0A, 0x54, 0x72, 0x65, 0x7A, 0x6F, + 0x72, 0x20, 0x55, 0x32, 0x46, 0x30, 0x1E, 0x17, 0x0D, 0x31, 0x35, 0x31, + 0x30, 0x31, 0x31, 0x32, 0x33, 0x32, 0x33, 0x34, 0x36, 0x5A, 0x17, 0x0D, + 0x32, 0x35, 0x31, 0x30, 0x30, 0x38, 0x32, 0x33, 0x32, 0x33, 0x34, 0x36, + 0x5A, 0x30, 0x15, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x03, + 0x0C, 0x0A, 0x54, 0x72, 0x65, 0x7A, 0x6F, 0x72, 0x20, 0x55, 0x32, 0x46, + 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, + 0x01, 0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07, 0x03, + 0x42, 0x00, 0x04, 0xD9, 0x18, 0xBD, 0xFA, 0x8A, 0x54, 0xAC, 0x92, 0xE9, + 0x0D, 0xA9, 0x1F, 0xCA, 0x7A, 0xA2, 0x64, 0x54, 0xC0, 0xD1, 0x73, 0x36, + 0x31, 0x4D, 0xDE, 0x83, 0xA5, 0x4B, 0x86, 0xB5, 0xDF, 0x4E, 0xF0, 0x52, + 0x65, 0x9A, 0x1D, 0x6F, 0xFC, 0xB7, 0x46, 0x7F, 0x1A, 0xCD, 0xDB, 0x8A, + 0x33, 0x08, 0x0B, 0x5E, 0xED, 0x91, 0x89, 0x13, 0xF4, 0x43, 0xA5, 0x26, + 0x1B, 0xC7, 0x7B, 0x68, 0x60, 0x6F, 0xC1, 0x30, 0x0A, 0x06, 0x08, 0x2A, + 0x86, 0x48, 0xCE, 0x3D, 0x04, 0x03, 0x02, 0x03, 0x48, 0x00, 0x30, 0x45, + 0x02, 0x21, 0x00, 0x89, 0x38, 0x50, 0x13, 0xBB, 0x13, 0xF1, 0xE9, 0xEF, + 0x7A, 0x1B, 0x47, 0x59, 0x19, 0xBA, 0x18, 0x55, 0x32, 0xE6, 0x76, 0xD1, + 0xAD, 0x69, 0x9D, 0x97, 0x07, 0x89, 0x80, 0x08, 0x4F, 0xAA, 0xE8, 0x02, + 0x20, 0x35, 0x9A, 0x5F, 0x1D, 0xE5, 0x0A, 0x12, 0xA0, 0x9C, 0x96, 0x3D, + 0x5A, 0x39, 0xA8, 0x83, 0x3C, 0xBA, 0x2E, 0xAD, 0xD5, 0x90, 0x23, 0x85, + 0x81, 0x7D, 0xF0, 0xC7, 0x72, 0x8E, 0x79, 0x09, 0xD0, +}; + +#endif // __U2F_KEYS_H_INCLUDED__ diff --git a/firmware/usb.c b/firmware/usb.c index 102b573da..e084742af 100644 --- a/firmware/usb.c +++ b/firmware/usb.c @@ -24,6 +24,7 @@ #include "usb.h" #include "debug.h" #include "messages.h" +#include "u2f.h" #include "storage.h" #include "util.h" @@ -31,6 +32,8 @@ #define ENDPOINT_ADDRESS_OUT (0x01) #define ENDPOINT_ADDRESS_DEBUG_IN (0x82) #define ENDPOINT_ADDRESS_DEBUG_OUT (0x02) +#define ENDPOINT_ADDRESS_U2F_IN (0x83) +#define ENDPOINT_ADDRESS_U2F_OUT (0x03) static const struct usb_device_descriptor dev_descr = { .bLength = USB_DT_DEVICE_SIZE, @@ -111,6 +114,25 @@ static const uint8_t hid_report_descriptor[] = { 0x95, 0x13, 0x09, 0x01, 0xB1, 0x02, 0xC0, }; +static const uint8_t hid_report_descriptor_u2f[] = { + 0x06, 0xd0, 0xf1, // USAGE_PAGE (FIDO Alliance) + 0x09, 0x01, // USAGE (U2F HID Authenticator Device) + 0xa1, 0x01, // COLLECTION (Application) + 0x09, 0x20, // USAGE (Input Report Data) + 0x15, 0x00, // LOGICAL_MINIMUM (0) + 0x26,0xff, 0x00, // LOGICAL_MAXIMUM (255) + 0x75,0x08, // REPORT_SIZE (8) + 0x95,0x40, // REPORT_COUNT (64) + 0x81,0x02, // INPUT (Data,Var,Abs) + 0x09,0x21, // USAGE (Output Report Data) + 0x15,0x00, // LOGICAL_MINIMUM (0) + 0x26,0xff, 0x00, // LOGICAL_MAXIMUM (255) + 0x75,0x08, // REPORT_SIZE (8) + 0x95,0x40, // REPORT_COUNT (64) + 0x91,0x02, // OUTPUT (Data,Var,Abs) + 0xc0 // END_COLLECTION +}; + static const struct { struct usb_hid_descriptor hid_descriptor; struct { @@ -131,6 +153,27 @@ static const struct { } }; +static const struct { + struct usb_hid_descriptor hid_descriptor_u2f; + struct { + uint8_t bReportDescriptorType; + uint16_t wDescriptorLength; + } __attribute__((packed)) hid_report_u2f; +} __attribute__((packed)) hid_function_u2f = { + .hid_descriptor_u2f = { + .bLength = sizeof(hid_function_u2f), + .bDescriptorType = USB_DT_HID, + .bcdHID = 0x0111, + .bCountryCode = 0, + .bNumDescriptors = 1, + }, + .hid_report_u2f = { + .bReportDescriptorType = USB_DT_REPORT, + .wDescriptorLength = sizeof(hid_report_descriptor_u2f), + } +}; + + static const struct usb_endpoint_descriptor hid_endpoints[2] = {{ .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, @@ -162,6 +205,41 @@ static const struct usb_interface_descriptor hid_iface[] = {{ .extralen = sizeof(hid_function), }}; +static const struct usb_endpoint_descriptor hid_endpoints_u2f[2] = {{ + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = ENDPOINT_ADDRESS_U2F_IN, + .bmAttributes = USB_ENDPOINT_ATTR_INTERRUPT, + .wMaxPacketSize = 64, + .bInterval = 1, +}, { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = ENDPOINT_ADDRESS_U2F_OUT, + .bmAttributes = USB_ENDPOINT_ATTR_INTERRUPT, + .wMaxPacketSize = 64, + .bInterval = 1, +}}; + +static const struct usb_interface_descriptor hid_iface_u2f[] = {{ + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, +#if DEBUG_LINK + .bInterfaceNumber = 2, +#else + .bInterfaceNumber = 1, +#endif + .bAlternateSetting = 0, + .bNumEndpoints = 2, + .bInterfaceClass = USB_CLASS_HID, + .bInterfaceSubClass = 0, + .bInterfaceProtocol = 0, + .iInterface = 0, + .endpoint = hid_endpoints_u2f, + .extra = &hid_function_u2f, + .extralen = sizeof(hid_function_u2f), +}}; + #if DEBUG_LINK static const struct usb_endpoint_descriptor hid_endpoints_debug[2] = {{ .bLength = USB_DT_ENDPOINT_SIZE, @@ -198,6 +276,9 @@ static const struct usb_interface_descriptor hid_iface_debug[] = {{ static const struct usb_interface ifaces[] = {{ .num_altsetting = 1, .altsetting = hid_iface, +}, { + .num_altsetting = 1, + .altsetting = hid_iface_u2f, #if DEBUG_LINK }, { .num_altsetting = 1, @@ -210,9 +291,9 @@ static const struct usb_config_descriptor config = { .bDescriptorType = USB_DT_CONFIGURATION, .wTotalLength = 0, #if DEBUG_LINK - .bNumInterfaces = 2, + .bNumInterfaces = 3, #else - .bNumInterfaces = 1, + .bNumInterfaces = 2, #endif .bConfigurationValue = 1, .iConfiguration = 0, @@ -237,11 +318,18 @@ static int hid_control_request(usbd_device *dev, struct usb_setup_data *req, uin (req->wValue != 0x2200)) return 0; - /* Handle the HID report descriptor. */ - *buf = (uint8_t *)hid_report_descriptor; - *len = sizeof(hid_report_descriptor); + if (req->wIndex==1) { + debugLog(0, "", "hid_control_request u2f"); + *buf = (uint8_t *)hid_report_descriptor_u2f; + *len = sizeof(hid_report_descriptor_u2f); + return 1; + } - return 1; + debugLog(0, "", "hid_control_request trezor"); + /* Handle the HID report descriptor. */ + *buf = (uint8_t *)hid_report_descriptor; + *len = sizeof(hid_report_descriptor); + return 1; } static volatile char tiny = 0; @@ -259,6 +347,16 @@ static void hid_rx_callback(usbd_device *dev, uint8_t ep) } } +static void hid_u2f_rx_callback(usbd_device *dev, uint8_t ep) +{ + (void)ep; + static uint8_t buf[64] __attribute__ ((aligned(4))); + + debugLog(0, "", "hid_u2f_rx_callback"); + if ( usbd_ep_read_packet(dev, ENDPOINT_ADDRESS_U2F_OUT, buf, 64) != 64) return; + u2fhid_read((const U2FHID_FRAME *)buf); +} + #if DEBUG_LINK static void hid_debug_rx_callback(usbd_device *dev, uint8_t ep) { @@ -280,6 +378,8 @@ static void hid_set_config(usbd_device *dev, uint16_t wValue) usbd_ep_setup(dev, ENDPOINT_ADDRESS_IN, USB_ENDPOINT_ATTR_INTERRUPT, 64, 0); usbd_ep_setup(dev, ENDPOINT_ADDRESS_OUT, USB_ENDPOINT_ATTR_INTERRUPT, 64, hid_rx_callback); + usbd_ep_setup(dev, ENDPOINT_ADDRESS_U2F_IN, USB_ENDPOINT_ATTR_INTERRUPT, 64, 0); + usbd_ep_setup(dev, ENDPOINT_ADDRESS_U2F_OUT, USB_ENDPOINT_ATTR_INTERRUPT, 64, hid_u2f_rx_callback); #if DEBUG_LINK usbd_ep_setup(dev, ENDPOINT_ADDRESS_DEBUG_IN, USB_ENDPOINT_ATTR_INTERRUPT, 64, 0); usbd_ep_setup(dev, ENDPOINT_ADDRESS_DEBUG_OUT, USB_ENDPOINT_ATTR_INTERRUPT, 64, hid_debug_rx_callback); @@ -311,6 +411,10 @@ void usbPoll(void) if (data) { while ( usbd_ep_write_packet(usbd_dev, ENDPOINT_ADDRESS_IN, data, 64) != 64 ) {} } + data = u2f_out_data(); + if (data) { + while ( usbd_ep_write_packet(usbd_dev, ENDPOINT_ADDRESS_U2F_IN, data, 64) != 64 ) {} + } #if DEBUG_LINK // write pending debug data data = msg_debug_out_data(); diff --git a/trezor-common b/trezor-common index 3fc31bfe9..72268e816 160000 --- a/trezor-common +++ b/trezor-common @@ -1 +1 @@ -Subproject commit 3fc31bfe9c42ebf3c0a63c48f44537994787f9c9 +Subproject commit 72268e816b8e8e06f698b3729223a255c7c74167