Merge branch 'u2f'

pull/25/head
Pavol Rusnak 8 years ago
commit 5e57a1ceaf
No known key found for this signature in database
GPG Key ID: 91F3B339B9A02A3D

@ -45,18 +45,18 @@ void layoutFirmwareHash(uint8_t *hash)
for (i = 0; i < 4; i++) {
data2hex(hash + i * 8, 8, str[i]);
}
layoutDialog(DIALOG_ICON_QUESTION, "Abort", "Continue", "Compare fingerprints", str[0], str[1], str[2], str[3], NULL, NULL);
layoutDialog(&bmp_icon_question, "Abort", "Continue", "Compare fingerprints", str[0], str[1], str[2], str[3], NULL, NULL);
}
void show_halt(void)
{
layoutDialog(DIALOG_ICON_ERROR, NULL, NULL, NULL, "Unofficial firmware", "aborted.", NULL, "Unplug your TREZOR", "and see our support", "page at mytrezor.com");
layoutDialog(&bmp_icon_error, NULL, NULL, NULL, "Unofficial firmware", "aborted.", NULL, "Unplug your TREZOR", "and see our support", "page at mytrezor.com");
system_halt();
}
void show_unofficial_warning(uint8_t *hash)
{
layoutDialog(DIALOG_ICON_WARNING, "Abort", "I'll take the risk", NULL, "WARNING!", NULL, "Unofficial firmware", "detected.", NULL, NULL);
layoutDialog(&bmp_icon_warning, "Abort", "I'll take the risk", NULL, "WARNING!", NULL, "Unofficial firmware", "detected.", NULL, NULL);
do {
delay(100000);
@ -124,7 +124,7 @@ void check_firmware_sanity(void)
broken++;
}
if (broken) {
layoutDialog(DIALOG_ICON_ERROR, NULL, NULL, NULL, "Firmware appears", "to be broken.", NULL, "Unplug your TREZOR", "and see our support", "page at mytrezor.com");
layoutDialog(&bmp_icon_error, NULL, NULL, NULL, "Firmware appears", "to be broken.", NULL, "Unplug your TREZOR", "and see our support", "page at mytrezor.com");
system_halt();
}
}
@ -133,7 +133,7 @@ uint32_t __stack_chk_guard;
void __attribute__((noreturn)) __stack_chk_fail(void)
{
layoutDialog(DIALOG_ICON_ERROR, NULL, NULL, NULL, "Stack smashing", "detected.", NULL, "Please unplug", "the device.", NULL);
layoutDialog(&bmp_icon_error, NULL, NULL, NULL, "Stack smashing", "detected.", NULL, "Please unplug", "the device.", NULL);
for (;;) {} // loop forever
}

@ -272,7 +272,7 @@ static void hid_rx_callback(usbd_device *dev, uint8_t ep)
if (flash_state == STATE_OPEN) {
if (msg_id == 0x0006) { // FirmwareErase message (id 6)
layoutDialog(DIALOG_ICON_QUESTION, "Abort", "Continue", NULL, "Install new", "firmware?", NULL, "Never do this without", "your recovery card!", NULL);
layoutDialog(&bmp_icon_question, "Abort", "Continue", NULL, "Install new", "firmware?", NULL, "Never do this without", "your recovery card!", NULL);
do {
delay(100000);
buttonUpdate();
@ -299,7 +299,7 @@ static void hid_rx_callback(usbd_device *dev, uint8_t ep)
}
send_msg_failure(dev);
flash_state = STATE_END;
layoutDialog(DIALOG_ICON_WARNING, NULL, NULL, NULL, "Firmware installation", "aborted.", NULL, "You may now", "unplug your TREZOR.", NULL);
layoutDialog(&bmp_icon_warning, NULL, NULL, NULL, "Firmware installation", "aborted.", NULL, "You may now", "unplug your TREZOR.", NULL);
return;
}
return;
@ -310,7 +310,7 @@ static void hid_rx_callback(usbd_device *dev, uint8_t ep)
if (buf[9] != 0x0a) { // invalid contents
send_msg_failure(dev);
flash_state = STATE_END;
layoutDialog(DIALOG_ICON_ERROR, NULL, NULL, NULL, "Error installing ", "firmware.", NULL, "Unplug your TREZOR", "and try again.", NULL);
layoutDialog(&bmp_icon_error, NULL, NULL, NULL, "Error installing ", "firmware.", NULL, "Unplug your TREZOR", "and try again.", NULL);
return;
}
// read payload length
@ -319,7 +319,7 @@ static void hid_rx_callback(usbd_device *dev, uint8_t ep)
if (flash_len > FLASH_TOTAL_SIZE + FLASH_META_DESC_LEN - (FLASH_APP_START - FLASH_ORIGIN)) { // firmware is too big
send_msg_failure(dev);
flash_state = STATE_END;
layoutDialog(DIALOG_ICON_ERROR, NULL, NULL, NULL, "Firmware is too big.", NULL, "Get official firmware", "from mytrezor.com", NULL, NULL);
layoutDialog(&bmp_icon_error, NULL, NULL, NULL, "Firmware is too big.", NULL, "Get official firmware", "from mytrezor.com", NULL, NULL);
return;
}
sha256_Init(&ctx);
@ -348,7 +348,7 @@ static void hid_rx_callback(usbd_device *dev, uint8_t ep)
if (buf[0] != '?') { // invalid contents
send_msg_failure(dev);
flash_state = STATE_END;
layoutDialog(DIALOG_ICON_ERROR, NULL, NULL, NULL, "Error installing ", "firmware.", NULL, "Unplug your TREZOR", "and try again.", NULL);
layoutDialog(&bmp_icon_error, NULL, NULL, NULL, "Error installing ", "firmware.", NULL, "Unplug your TREZOR", "and try again.", NULL);
return;
}
p = buf + 1;
@ -432,10 +432,10 @@ static void hid_rx_callback(usbd_device *dev, uint8_t ep)
}
flash_state = STATE_END;
if (hash_check_ok) {
layoutDialog(DIALOG_ICON_OK, NULL, NULL, NULL, "New firmware", "successfully installed.", NULL, "You may now", "unplug your TREZOR.", NULL);
layoutDialog(&bmp_icon_ok, NULL, NULL, NULL, "New firmware", "successfully installed.", NULL, "You may now", "unplug your TREZOR.", NULL);
send_msg_success(dev);
} else {
layoutDialog(DIALOG_ICON_WARNING, NULL, NULL, NULL, "Firmware installation", "aborted.", NULL, "You need to repeat", "the procedure with", "the correct firmware.");
layoutDialog(&bmp_icon_warning, NULL, NULL, NULL, "Firmware installation", "aborted.", NULL, "You need to repeat", "the procedure with", "the correct firmware.");
send_msg_failure(dev);
}
return;

@ -242,7 +242,7 @@ uint32_t __stack_chk_guard;
void __attribute__((noreturn)) __stack_chk_fail(void)
{
layoutDialog(DIALOG_ICON_ERROR, NULL, NULL, NULL, "Stack smashing", "detected.", NULL, "Please unplug", "the device.", NULL);
layoutDialog(&bmp_icon_error, NULL, NULL, NULL, "Stack smashing", "detected.", NULL, "Please unplug", "the device.", NULL);
for (;;) {} // loop forever
}

@ -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

@ -46,6 +46,7 @@
#include "ripemd160.h"
#include "curves.h"
#include "secp256k1.h"
#include <libopencm3/stm32/flash.h>
// message methods
@ -96,7 +97,7 @@ const CoinType *fsm_getCoin(const char *name)
const HDNode *fsm_getDerivedNode(const char *curve, uint32_t *address_n, size_t address_n_count)
{
static HDNode node;
if (!storage_getRootNode(&node, curve)) {
if (!storage_getRootNode(&node, curve, true)) {
fsm_sendFailure(FailureType_Failure_NotInitialized, "Device not initialized or passphrase request cancelled or unsupported curve");
layoutHome();
return 0;
@ -160,7 +161,7 @@ void fsm_msgPing(Ping *msg)
RESP_INIT(Success);
if (msg->has_button_protection && msg->button_protection) {
layoutDialogSwipe(DIALOG_ICON_QUESTION, "Cancel", "Confirm", NULL, "Do you really want to", "answer to ping?", NULL, NULL, NULL, NULL);
layoutDialogSwipe(&bmp_icon_question, "Cancel", "Confirm", NULL, "Do you really want to", "answer to ping?", NULL, NULL, NULL, NULL);
if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) {
fsm_sendFailure(FailureType_Failure_ActionCancelled, "Ping cancelled");
layoutHome();
@ -195,16 +196,16 @@ void fsm_msgChangePin(ChangePin *msg)
bool removal = msg->has_remove && msg->remove;
if (removal) {
if (storage_hasPin()) {
layoutDialogSwipe(DIALOG_ICON_QUESTION, "Cancel", "Confirm", NULL, "Do you really want to", "remove current PIN?", NULL, NULL, NULL, NULL);
layoutDialogSwipe(&bmp_icon_question, "Cancel", "Confirm", NULL, "Do you really want to", "remove current PIN?", NULL, NULL, NULL, NULL);
} else {
fsm_sendSuccess("PIN removed");
return;
}
} else {
if (storage_hasPin()) {
layoutDialogSwipe(DIALOG_ICON_QUESTION, "Cancel", "Confirm", NULL, "Do you really want to", "change current PIN?", NULL, NULL, NULL, NULL);
layoutDialogSwipe(&bmp_icon_question, "Cancel", "Confirm", NULL, "Do you really want to", "change current PIN?", NULL, NULL, NULL, NULL);
} else {
layoutDialogSwipe(DIALOG_ICON_QUESTION, "Cancel", "Confirm", NULL, "Do you really want to", "set new PIN?", NULL, NULL, NULL, NULL);
layoutDialogSwipe(&bmp_icon_question, "Cancel", "Confirm", NULL, "Do you really want to", "set new PIN?", NULL, NULL, NULL, NULL);
}
}
if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) {
@ -232,7 +233,7 @@ void fsm_msgChangePin(ChangePin *msg)
void fsm_msgWipeDevice(WipeDevice *msg)
{
(void)msg;
layoutDialogSwipe(DIALOG_ICON_QUESTION, "Cancel", "Confirm", NULL, "Do you really want to", "wipe the device?", NULL, "All data will be lost.", NULL, NULL);
layoutDialogSwipe(&bmp_icon_question, "Cancel", "Confirm", NULL, "Do you really want to", "wipe the device?", NULL, "All data will be lost.", NULL, NULL);
if (!protectButton(ButtonRequestType_ButtonRequest_WipeDevice, false)) {
fsm_sendFailure(FailureType_Failure_ActionCancelled, "Wipe cancelled");
layoutHome();
@ -262,7 +263,7 @@ void fsm_msgFirmwareUpload(FirmwareUpload *msg)
void fsm_msgGetEntropy(GetEntropy *msg)
{
layoutDialogSwipe(DIALOG_ICON_QUESTION, "Cancel", "Confirm", NULL, "Do you really want to", "send entropy?", NULL, NULL, NULL, NULL);
layoutDialogSwipe(&bmp_icon_question, "Cancel", "Confirm", NULL, "Do you really want to", "send entropy?", NULL, NULL, NULL, NULL);
if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) {
fsm_sendFailure(FailureType_Failure_ActionCancelled, "Entropy cancelled");
layoutHome();
@ -331,7 +332,7 @@ void fsm_msgLoadDevice(LoadDevice *msg)
return;
}
layoutDialogSwipe(DIALOG_ICON_QUESTION, "Cancel", "I take the risk", NULL, "Loading private seed", "is not recommended.", "Continue only if you", "know what you are", "doing!", NULL);
layoutDialogSwipe(&bmp_icon_question, "Cancel", "I take the risk", NULL, "Loading private seed", "is not recommended.", "Continue only if you", "know what you are", "doing!", NULL);
if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) {
fsm_sendFailure(FailureType_Failure_ActionCancelled, "Load cancelled");
layoutHome();
@ -488,7 +489,7 @@ void fsm_msgClearSession(ClearSession *msg)
void fsm_msgApplySettings(ApplySettings *msg)
{
if (msg->has_label) {
layoutDialogSwipe(DIALOG_ICON_QUESTION, "Cancel", "Confirm", NULL, "Do you really want to", "change label to", msg->label, "?", NULL, NULL);
layoutDialogSwipe(&bmp_icon_question, "Cancel", "Confirm", NULL, "Do you really want to", "change label to", msg->label, "?", NULL, NULL);
if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) {
fsm_sendFailure(FailureType_Failure_ActionCancelled, "Apply settings cancelled");
layoutHome();
@ -496,7 +497,7 @@ void fsm_msgApplySettings(ApplySettings *msg)
}
}
if (msg->has_language) {
layoutDialogSwipe(DIALOG_ICON_QUESTION, "Cancel", "Confirm", NULL, "Do you really want to", "change language to", msg->language, "?", NULL, NULL);
layoutDialogSwipe(&bmp_icon_question, "Cancel", "Confirm", NULL, "Do you really want to", "change language to", msg->language, "?", NULL, NULL);
if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) {
fsm_sendFailure(FailureType_Failure_ActionCancelled, "Apply settings cancelled");
layoutHome();
@ -504,7 +505,7 @@ void fsm_msgApplySettings(ApplySettings *msg)
}
}
if (msg->has_use_passphrase) {
layoutDialogSwipe(DIALOG_ICON_QUESTION, "Cancel", "Confirm", NULL, "Do you really want to", msg->use_passphrase ? "enable passphrase" : "disable passphrase", "encryption?", NULL, NULL, NULL);
layoutDialogSwipe(&bmp_icon_question, "Cancel", "Confirm", NULL, "Do you really want to", msg->use_passphrase ? "enable passphrase" : "disable passphrase", "encryption?", NULL, NULL, NULL);
if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) {
fsm_sendFailure(FailureType_Failure_ActionCancelled, "Apply settings cancelled");
layoutHome();
@ -512,7 +513,7 @@ void fsm_msgApplySettings(ApplySettings *msg)
}
}
if (msg->has_homescreen) {
layoutDialogSwipe(DIALOG_ICON_QUESTION, "Cancel", "Confirm", NULL, "Do you really want to", "change the home", "screen ?", NULL, NULL, NULL);
layoutDialogSwipe(&bmp_icon_question, "Cancel", "Confirm", NULL, "Do you really want to", "change the home", "screen ?", NULL, NULL, NULL);
if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) {
fsm_sendFailure(FailureType_Failure_ActionCancelled, "Apply settings cancelled");
layoutHome();
@ -975,4 +976,41 @@ void fsm_msgDebugLinkStop(DebugLinkStop *msg)
(void)msg;
}
void fsm_msgDebugLinkMemoryRead(DebugLinkMemoryRead *msg)
{
RESP_INIT(DebugLinkMemory);
uint32_t length = 1024;
if (msg->has_length && msg->length < length)
length = msg->length;
resp->has_memory = true;
memcpy(resp->memory.bytes, (void*) msg->address, length);
resp->memory.size = length;
msg_debug_write(MessageType_MessageType_DebugLinkMemory, resp);
}
void fsm_msgDebugLinkMemoryWrite(DebugLinkMemoryWrite *msg)
{
uint32_t length = msg->memory.size;
if (msg->flash) {
flash_clear_status_flags();
flash_unlock();
uint32_t* src = (uint32_t *) msg->memory.bytes;
for (unsigned int i = 0; i < length; i += 4) {
flash_program_word(msg->address + i, *src);
src++;
}
flash_lock();
} else {
memcpy((void *) msg->address, msg->memory.bytes, length);
}
}
void fsm_msgDebugLinkFlashErase(DebugLinkFlashErase *msg)
{
flash_clear_status_flags();
flash_unlock();
flash_erase_sector(msg->sector, FLASH_CR_PROGRAM_X32);
flash_lock();
}
#endif

@ -63,6 +63,9 @@ void fsm_msgWordAck(WordAck *msg);
//void fsm_msgDebugLinkDecision(DebugLinkDecision *msg);
void fsm_msgDebugLinkGetState(DebugLinkGetState *msg);
void fsm_msgDebugLinkStop(DebugLinkStop *msg);
void fsm_msgDebugLinkMemoryWrite(DebugLinkMemoryWrite *msg);
void fsm_msgDebugLinkMemoryRead(DebugLinkMemoryRead *msg);
void fsm_msgDebugLinkFlashErase(DebugLinkFlashErase *msg);
#endif
#endif

@ -31,7 +31,7 @@
void *layoutLast = layoutHome;
void layoutDialogSwipe(LayoutDialogIcon icon, const char *btnNo, const char *btnYes, const char *desc, const char *line1, const char *line2, const char *line3, const char *line4, const char *line5, const char *line6)
void layoutDialogSwipe(const BITMAP *icon, const char *btnNo, const char *btnYes, const char *desc, const char *line1, const char *line2, const char *line3, const char *line4, const char *line5, const char *line6)
{
layoutLast = layoutDialogSwipe;
oledSwipeLeft();
@ -119,7 +119,7 @@ void layoutConfirmOutput(const CoinType *coin, const TxOutputType *out)
static char first_half[17 + 1];
strlcpy(first_half, out->address, sizeof(first_half));
const char *str_out = str_amount(out->amount, coin->has_coin_shortcut ? coin->coin_shortcut : NULL, buf_out, sizeof(buf_out));
layoutDialogSwipe(DIALOG_ICON_QUESTION,
layoutDialogSwipe(&bmp_icon_question,
"Cancel",
"Confirm",
NULL,
@ -136,7 +136,7 @@ void layoutConfirmTx(const CoinType *coin, uint64_t amount_out, uint64_t amount_
{
const char *str_out = str_amount(amount_out, coin->has_coin_shortcut ? coin->coin_shortcut : NULL, buf_out, sizeof(buf_out));
const char *str_fee = str_amount(amount_fee, coin->has_coin_shortcut ? coin->coin_shortcut : NULL, buf_fee, sizeof(buf_fee));
layoutDialogSwipe(DIALOG_ICON_QUESTION,
layoutDialogSwipe(&bmp_icon_question,
"Cancel",
"Confirm",
NULL,
@ -153,7 +153,7 @@ void layoutFeeOverThreshold(const CoinType *coin, uint64_t fee, uint32_t kb)
{
(void)kb;
const char *str_out = str_amount(fee, coin->has_coin_shortcut ? coin->coin_shortcut : NULL, buf_out, sizeof(buf_out));
layoutDialogSwipe(DIALOG_ICON_QUESTION,
layoutDialogSwipe(&bmp_icon_question,
"Cancel",
"Confirm",
NULL,
@ -191,7 +191,7 @@ const char **split_message(const uint8_t *msg, uint32_t len, uint32_t rowlen)
void layoutSignMessage(const uint8_t *msg, uint32_t len)
{
const char **str = split_message(msg, len, 16);
layoutDialogSwipe(DIALOG_ICON_QUESTION, "Cancel", "Confirm",
layoutDialogSwipe(&bmp_icon_question, "Cancel", "Confirm",
"Sign message?",
str[0], str[1], str[2], str[3], NULL, NULL);
}
@ -199,7 +199,7 @@ void layoutSignMessage(const uint8_t *msg, uint32_t len)
void layoutVerifyAddress(const char *address)
{
const char **str = split_message((const uint8_t *)address, strlen(address), 17);
layoutDialogSwipe(DIALOG_ICON_INFO, "Cancel", "Confirm",
layoutDialogSwipe(&bmp_icon_info, "Cancel", "Confirm",
"Confirm address?",
"Message signed by:",
NULL, str[0], str[1], str[2], NULL);
@ -208,7 +208,7 @@ void layoutVerifyAddress(const char *address)
void layoutVerifyMessage(const uint8_t *msg, uint32_t len)
{
const char **str = split_message(msg, len, 16);
layoutDialogSwipe(DIALOG_ICON_INFO, "Cancel", "Confirm",
layoutDialogSwipe(&bmp_icon_info, "Cancel", "Confirm",
"Verified message",
str[0], str[1], str[2], str[3], NULL, NULL);
}
@ -216,7 +216,7 @@ void layoutVerifyMessage(const uint8_t *msg, uint32_t len)
void layoutCipherKeyValue(bool encrypt, const char *key)
{
const char **str = split_message((const uint8_t *)key, strlen(key), 16);
layoutDialogSwipe(DIALOG_ICON_QUESTION, "Cancel", "Confirm",
layoutDialogSwipe(&bmp_icon_question, "Cancel", "Confirm",
encrypt ? "Encode value of this key?" : "Decode value of this key?",
str[0], str[1], str[2], str[3], NULL, NULL);
}
@ -224,7 +224,7 @@ void layoutCipherKeyValue(bool encrypt, const char *key)
void layoutEncryptMessage(const uint8_t *msg, uint32_t len, bool signing)
{
const char **str = split_message(msg, len, 16);
layoutDialogSwipe(DIALOG_ICON_QUESTION, "Cancel", "Confirm",
layoutDialogSwipe(&bmp_icon_question, "Cancel", "Confirm",
signing ? "Encrypt+Sign message?" : "Encrypt message?",
str[0], str[1], str[2], str[3], NULL, NULL);
}
@ -232,7 +232,7 @@ void layoutEncryptMessage(const uint8_t *msg, uint32_t len, bool signing)
void layoutDecryptMessage(const uint8_t *msg, uint32_t len, const char *address)
{
const char **str = split_message(msg, len, 16);
layoutDialogSwipe(DIALOG_ICON_INFO, NULL, "OK",
layoutDialogSwipe(&bmp_icon_info, NULL, "OK",
address ? "Decrypted signed message" : "Decrypted message",
str[0], str[1], str[2], str[3], NULL, NULL);
}
@ -286,7 +286,7 @@ void layoutPublicKey(const uint8_t *pubkey)
data2hex(pubkey, 1, desc + 12);
data2hex(pubkey + 1, 32, hex);
const char **str = split_message((const uint8_t *)hex, 32*2, 16);
layoutDialogSwipe(DIALOG_ICON_QUESTION, NULL, "Continue", NULL,
layoutDialogSwipe(&bmp_icon_question, NULL, "Continue", NULL,
desc, str[0], str[1], str[2], str[3], NULL);
}
@ -326,7 +326,7 @@ void layoutSignIdentity(const IdentityType *identity, const char *challenge)
row_user[0] = 0;
}
layoutDialogSwipe(DIALOG_ICON_QUESTION, "Cancel", "Confirm",
layoutDialogSwipe(&bmp_icon_question, "Cancel", "Confirm",
"Do you want to sign in?",
row_proto[0] ? row_proto : NULL,
row_hostport[0] ? row_hostport : NULL,
@ -335,3 +335,10 @@ void layoutSignIdentity(const IdentityType *identity, const char *challenge)
NULL,
NULL);
}
void layoutU2FDialog(const char *verb, const char *appname, const BITMAP *appicon) {
if (!appicon) {
appicon = &bmp_icon_question;
}
layoutDialog(appicon, NULL, verb, NULL, verb, "U2F security key?", "", appname, "", NULL);
}

@ -22,8 +22,9 @@
#include "layout.h"
#include "types.pb.h"
#include "bitmaps.h"
void layoutDialogSwipe(LayoutDialogIcon icon, const char *btnNo, const char *btnYes, const char *desc, const char *line1, const char *line2, const char *line3, const char *line4, const char *line5, const char *line6);
void layoutDialogSwipe(const BITMAP *icon, const char *btnNo, const char *btnYes, const char *desc, const char *line1, const char *line2, const char *line3, const char *line4, const char *line5, const char *line6);
void layoutProgressSwipe(const char *desc, int permil);
void layoutScreensaver(void);
@ -40,5 +41,6 @@ void layoutDecryptMessage(const uint8_t *msg, uint32_t len, const char *address)
void layoutAddress(const char *address, const char *desc);
void layoutPublicKey(const uint8_t *pubkey);
void layoutSignIdentity(const IdentityType *identity, const char *challenge);
void layoutU2FDialog(const char *verb, const char *appname, const BITMAP *appicon);
#endif

@ -97,9 +97,13 @@ static const struct MessagesMap_t MessagesMap[] = {
// {'d', 'i', MessageType_MessageType_DebugLinkDecision, DebugLinkDecision_fields, (void (*)(void *))fsm_msgDebugLinkDecision},
{'d', 'i', MessageType_MessageType_DebugLinkGetState, DebugLinkGetState_fields, (void (*)(void *))fsm_msgDebugLinkGetState},
{'d', 'i', MessageType_MessageType_DebugLinkStop, DebugLinkStop_fields, (void (*)(void *))fsm_msgDebugLinkStop},
{'d', 'i', MessageType_MessageType_DebugLinkMemoryRead, DebugLinkMemoryRead_fields, (void (*)(void *))fsm_msgDebugLinkMemoryRead},
{'d', 'i', MessageType_MessageType_DebugLinkMemoryWrite, DebugLinkMemoryWrite_fields, (void (*)(void *))fsm_msgDebugLinkMemoryWrite},
{'d', 'i', MessageType_MessageType_DebugLinkFlashErase, DebugLinkFlashErase_fields, (void (*)(void *))fsm_msgDebugLinkFlashErase},
// debug out messages
{'d', 'o', MessageType_MessageType_DebugLinkState, DebugLinkState_fields, 0},
{'d', 'o', MessageType_MessageType_DebugLinkLog, DebugLinkLog_fields, 0},
{'d', 'o', MessageType_MessageType_DebugLinkMemory, DebugLinkMemory_fields, 0},
#endif
// end
{0, 0, 0, 0, 0}

@ -44,6 +44,7 @@ bool protectButton(ButtonRequestType type, bool confirm_only)
resp.has_code = true;
resp.code = type;
usbTiny(1);
buttonUpdate(); // Clear button state
msg_write(MessageType_MessageType_ButtonRequest, &resp);
for (;;) {
@ -57,7 +58,7 @@ bool protectButton(ButtonRequestType type, bool confirm_only)
// button acked - check buttons
if (acked) {
usbDelay(3500);
usbDelay(3300);
buttonUpdate();
if (button.YesUp) {
result = true;
@ -162,9 +163,9 @@ bool protectPin(bool use_cached)
if (wait == 1) {
secstrbuf[16] = 0;
}
layoutDialog(DIALOG_ICON_INFO, NULL, NULL, NULL, "Wrong PIN entered", NULL, "Please wait", secstr, "to continue ...", NULL);
layoutDialog(&bmp_icon_info, NULL, NULL, NULL, "Wrong PIN entered", NULL, "Please wait", secstr, "to continue ...", NULL);
// wait one second
usbDelay(840000);
usbDelay(800000);
if (msg_tiny_id == MessageType_MessageType_Initialize) {
protectAbortedByInitialize = true;
msg_tiny_id = 0xFFFF;
@ -224,7 +225,7 @@ bool protectPassphrase(void)
usbTiny(1);
msg_write(MessageType_MessageType_PassphraseRequest, &resp);
layoutDialogSwipe(DIALOG_ICON_INFO, NULL, NULL, NULL, "Please enter your", "passphrase using", "the computer's", "keyboard.", NULL, NULL);
layoutDialogSwipe(&bmp_icon_info, NULL, NULL, NULL, "Please enter your", "passphrase using", "the computer's", "keyboard.", NULL, NULL);
bool result;
for (;;) {

@ -137,3 +137,6 @@ DebugLinkState.recovery_fake_word max_size:12
DebugLinkLog.bucket max_size:33
DebugLinkLog.text max_size:256
DebugLinkMemory.memory max_size:1024
DebugLinkMemoryWrite.memory max_size:1024

@ -150,7 +150,7 @@ const pb_field_t GetAddress_fields[5] = {
};
const pb_field_t EthereumGetAddress_fields[3] = {
PB_FIELD2( 1, UINT32 , REPEATED, CALLBACK, FIRST, EthereumGetAddress, address_n, address_n, 0),
PB_FIELD2( 1, UINT32 , REPEATED, STATIC , FIRST, EthereumGetAddress, address_n, address_n, 0),
PB_FIELD2( 2, BOOL , OPTIONAL, STATIC , OTHER, EthereumGetAddress, show_display, address_n, 0),
PB_LAST_FIELD
};
@ -161,7 +161,7 @@ const pb_field_t Address_fields[2] = {
};
const pb_field_t EthereumAddress_fields[2] = {
PB_FIELD2( 1, BYTES , REQUIRED, CALLBACK, FIRST, EthereumAddress, address, address, 0),
PB_FIELD2( 1, BYTES , REQUIRED, STATIC , FIRST, EthereumAddress, address, address, 0),
PB_LAST_FIELD
};
@ -329,13 +329,13 @@ const pb_field_t TxAck_fields[2] = {
};
const pb_field_t EthereumSignTx_fields[9] = {
PB_FIELD2( 1, UINT32 , REPEATED, CALLBACK, FIRST, EthereumSignTx, address_n, address_n, 0),
PB_FIELD2( 2, BYTES , OPTIONAL, CALLBACK, OTHER, EthereumSignTx, nonce, address_n, 0),
PB_FIELD2( 3, BYTES , OPTIONAL, CALLBACK, OTHER, EthereumSignTx, gas_price, nonce, 0),
PB_FIELD2( 4, BYTES , OPTIONAL, CALLBACK, OTHER, EthereumSignTx, gas_limit, gas_price, 0),
PB_FIELD2( 5, BYTES , OPTIONAL, CALLBACK, OTHER, EthereumSignTx, to, gas_limit, 0),
PB_FIELD2( 6, BYTES , OPTIONAL, CALLBACK, OTHER, EthereumSignTx, value, to, 0),
PB_FIELD2( 7, BYTES , OPTIONAL, CALLBACK, OTHER, EthereumSignTx, data_initial_chunk, value, 0),
PB_FIELD2( 1, UINT32 , REPEATED, STATIC , FIRST, EthereumSignTx, address_n, address_n, 0),
PB_FIELD2( 2, BYTES , OPTIONAL, STATIC , OTHER, EthereumSignTx, nonce, address_n, 0),
PB_FIELD2( 3, BYTES , OPTIONAL, STATIC , OTHER, EthereumSignTx, gas_price, nonce, 0),
PB_FIELD2( 4, BYTES , OPTIONAL, STATIC , OTHER, EthereumSignTx, gas_limit, gas_price, 0),
PB_FIELD2( 5, BYTES , OPTIONAL, STATIC , OTHER, EthereumSignTx, to, gas_limit, 0),
PB_FIELD2( 6, BYTES , OPTIONAL, STATIC , OTHER, EthereumSignTx, value, to, 0),
PB_FIELD2( 7, BYTES , OPTIONAL, STATIC , OTHER, EthereumSignTx, data_initial_chunk, value, 0),
PB_FIELD2( 8, UINT32 , OPTIONAL, STATIC , OTHER, EthereumSignTx, data_length, data_initial_chunk, 0),
PB_LAST_FIELD
};
@ -343,13 +343,13 @@ const pb_field_t EthereumSignTx_fields[9] = {
const pb_field_t EthereumTxRequest_fields[5] = {
PB_FIELD2( 1, UINT32 , OPTIONAL, STATIC , FIRST, EthereumTxRequest, data_length, data_length, 0),
PB_FIELD2( 2, UINT32 , OPTIONAL, STATIC , OTHER, EthereumTxRequest, signature_v, data_length, 0),
PB_FIELD2( 3, BYTES , OPTIONAL, CALLBACK, OTHER, EthereumTxRequest, signature_r, signature_v, 0),
PB_FIELD2( 4, BYTES , OPTIONAL, CALLBACK, OTHER, EthereumTxRequest, signature_s, signature_r, 0),
PB_FIELD2( 3, BYTES , OPTIONAL, STATIC , OTHER, EthereumTxRequest, signature_r, signature_v, 0),
PB_FIELD2( 4, BYTES , OPTIONAL, STATIC , OTHER, EthereumTxRequest, signature_s, signature_r, 0),
PB_LAST_FIELD
};
const pb_field_t EthereumTxAck_fields[2] = {
PB_FIELD2( 1, BYTES , OPTIONAL, CALLBACK, FIRST, EthereumTxAck, data_chunk, data_chunk, 0),
PB_FIELD2( 1, BYTES , OPTIONAL, STATIC , FIRST, EthereumTxAck, data_chunk, data_chunk, 0),
PB_LAST_FIELD
};
@ -423,6 +423,29 @@ const pb_field_t DebugLinkLog_fields[4] = {
PB_LAST_FIELD
};
const pb_field_t DebugLinkMemoryRead_fields[3] = {
PB_FIELD2( 1, UINT32 , OPTIONAL, STATIC , FIRST, DebugLinkMemoryRead, address, address, 0),
PB_FIELD2( 2, UINT32 , OPTIONAL, STATIC , OTHER, DebugLinkMemoryRead, length, address, 0),
PB_LAST_FIELD
};
const pb_field_t DebugLinkMemory_fields[2] = {
PB_FIELD2( 1, BYTES , OPTIONAL, STATIC , FIRST, DebugLinkMemory, memory, memory, 0),
PB_LAST_FIELD
};
const pb_field_t DebugLinkMemoryWrite_fields[4] = {
PB_FIELD2( 1, UINT32 , OPTIONAL, STATIC , FIRST, DebugLinkMemoryWrite, address, address, 0),
PB_FIELD2( 2, BYTES , OPTIONAL, STATIC , OTHER, DebugLinkMemoryWrite, memory, address, 0),
PB_FIELD2( 3, BOOL , OPTIONAL, STATIC , OTHER, DebugLinkMemoryWrite, flash, memory, 0),
PB_LAST_FIELD
};
const pb_field_t DebugLinkFlashErase_fields[2] = {
PB_FIELD2( 1, UINT32 , OPTIONAL, STATIC , FIRST, DebugLinkFlashErase, sector, sector, 0),
PB_LAST_FIELD
};
/* Check that field information fits in pb_field_t */
#if !defined(PB_FIELD_32BIT)
@ -433,7 +456,7 @@ const pb_field_t DebugLinkLog_fields[4] = {
* numbers or field sizes that are larger than what can fit in 8 or 16 bit
* field descriptors.
*/
STATIC_ASSERT((pb_membersize(Features, coins[0]) < 65536 && pb_membersize(PublicKey, node) < 65536 && pb_membersize(GetAddress, multisig) < 65536 && pb_membersize(LoadDevice, node) < 65536 && pb_membersize(SimpleSignTx, inputs[0]) < 65536 && pb_membersize(SimpleSignTx, outputs[0]) < 65536 && pb_membersize(SimpleSignTx, transactions[0]) < 65536 && pb_membersize(TxRequest, details) < 65536 && pb_membersize(TxRequest, serialized) < 65536 && pb_membersize(TxAck, tx) < 65536 && pb_membersize(SignIdentity, identity) < 65536 && pb_membersize(GetECDHSessionKey, identity) < 65536 && pb_membersize(DebugLinkState, node) < 65536), YOU_MUST_DEFINE_PB_FIELD_32BIT_FOR_MESSAGES_Initialize_GetFeatures_Features_ClearSession_ApplySettings_ChangePin_Ping_Success_Failure_ButtonRequest_ButtonAck_PinMatrixRequest_PinMatrixAck_Cancel_PassphraseRequest_PassphraseAck_GetEntropy_Entropy_GetPublicKey_PublicKey_GetAddress_EthereumGetAddress_Address_EthereumAddress_WipeDevice_LoadDevice_ResetDevice_EntropyRequest_EntropyAck_RecoveryDevice_WordRequest_WordAck_SignMessage_VerifyMessage_MessageSignature_EncryptMessage_EncryptedMessage_DecryptMessage_DecryptedMessage_CipherKeyValue_CipheredKeyValue_EstimateTxSize_TxSize_SignTx_SimpleSignTx_TxRequest_TxAck_EthereumSignTx_EthereumTxRequest_EthereumTxAck_SignIdentity_SignedIdentity_GetECDHSessionKey_ECDHSessionKey_FirmwareErase_FirmwareUpload_DebugLinkDecision_DebugLinkGetState_DebugLinkState_DebugLinkStop_DebugLinkLog)
STATIC_ASSERT((pb_membersize(Features, coins[0]) < 65536 && pb_membersize(PublicKey, node) < 65536 && pb_membersize(GetAddress, multisig) < 65536 && pb_membersize(LoadDevice, node) < 65536 && pb_membersize(SimpleSignTx, inputs[0]) < 65536 && pb_membersize(SimpleSignTx, outputs[0]) < 65536 && pb_membersize(SimpleSignTx, transactions[0]) < 65536 && pb_membersize(TxRequest, details) < 65536 && pb_membersize(TxRequest, serialized) < 65536 && pb_membersize(TxAck, tx) < 65536 && pb_membersize(SignIdentity, identity) < 65536 && pb_membersize(GetECDHSessionKey, identity) < 65536 && pb_membersize(DebugLinkState, node) < 65536), YOU_MUST_DEFINE_PB_FIELD_32BIT_FOR_MESSAGES_Initialize_GetFeatures_Features_ClearSession_ApplySettings_ChangePin_Ping_Success_Failure_ButtonRequest_ButtonAck_PinMatrixRequest_PinMatrixAck_Cancel_PassphraseRequest_PassphraseAck_GetEntropy_Entropy_GetPublicKey_PublicKey_GetAddress_EthereumGetAddress_Address_EthereumAddress_WipeDevice_LoadDevice_ResetDevice_EntropyRequest_EntropyAck_RecoveryDevice_WordRequest_WordAck_SignMessage_VerifyMessage_MessageSignature_EncryptMessage_EncryptedMessage_DecryptMessage_DecryptedMessage_CipherKeyValue_CipheredKeyValue_EstimateTxSize_TxSize_SignTx_SimpleSignTx_TxRequest_TxAck_EthereumSignTx_EthereumTxRequest_EthereumTxAck_SignIdentity_SignedIdentity_GetECDHSessionKey_ECDHSessionKey_FirmwareErase_FirmwareUpload_DebugLinkDecision_DebugLinkGetState_DebugLinkState_DebugLinkStop_DebugLinkLog_DebugLinkMemoryRead_DebugLinkMemory_DebugLinkMemoryWrite_DebugLinkFlashErase)
#endif
#if !defined(PB_FIELD_16BIT) && !defined(PB_FIELD_32BIT)

@ -72,7 +72,11 @@ typedef enum _MessageType {
MessageType_MessageType_DebugLinkGetState = 101,
MessageType_MessageType_DebugLinkState = 102,
MessageType_MessageType_DebugLinkStop = 103,
MessageType_MessageType_DebugLinkLog = 104
MessageType_MessageType_DebugLinkLog = 104,
MessageType_MessageType_DebugLinkMemoryRead = 110,
MessageType_MessageType_DebugLinkMemory = 111,
MessageType_MessageType_DebugLinkMemoryWrite = 112,
MessageType_MessageType_DebugLinkFlashErase = 113
} MessageType;
/* Struct definitions */
@ -197,6 +201,11 @@ typedef struct _DebugLinkDecision {
bool yes_no;
} DebugLinkDecision;
typedef struct _DebugLinkFlashErase {
bool has_sector;
uint32_t sector;
} DebugLinkFlashErase;
typedef struct _DebugLinkLog {
bool has_level;
uint32_t level;
@ -206,6 +215,37 @@ typedef struct _DebugLinkLog {
char text[256];
} DebugLinkLog;
typedef struct {
size_t size;
uint8_t bytes[1024];
} DebugLinkMemory_memory_t;
typedef struct _DebugLinkMemory {
bool has_memory;
DebugLinkMemory_memory_t memory;
} DebugLinkMemory;
typedef struct _DebugLinkMemoryRead {
bool has_address;
uint32_t address;
bool has_length;
uint32_t length;
} DebugLinkMemoryRead;
typedef struct {
size_t size;
uint8_t bytes[1024];
} DebugLinkMemoryWrite_memory_t;
typedef struct _DebugLinkMemoryWrite {
bool has_address;
uint32_t address;
bool has_memory;
DebugLinkMemoryWrite_memory_t memory;
bool has_flash;
bool flash;
} DebugLinkMemoryWrite;
typedef struct {
size_t size;
uint8_t bytes[1024];
@ -360,39 +400,100 @@ typedef struct _EstimateTxSize {
char coin_name[17];
} EstimateTxSize;
typedef struct {
size_t size;
uint8_t bytes[20];
} EthereumAddress_address_t;
typedef struct _EthereumAddress {
pb_callback_t address;
EthereumAddress_address_t address;
} EthereumAddress;
typedef struct _EthereumGetAddress {
pb_callback_t address_n;
size_t address_n_count;
uint32_t address_n[8];
bool has_show_display;
bool show_display;
} EthereumGetAddress;
typedef struct {
size_t size;
uint8_t bytes[32];
} EthereumSignTx_nonce_t;
typedef struct {
size_t size;
uint8_t bytes[32];
} EthereumSignTx_gas_price_t;
typedef struct {
size_t size;
uint8_t bytes[32];
} EthereumSignTx_gas_limit_t;
typedef struct {
size_t size;
uint8_t bytes[20];
} EthereumSignTx_to_t;
typedef struct {
size_t size;
uint8_t bytes[32];
} EthereumSignTx_value_t;
typedef struct {
size_t size;
uint8_t bytes[1024];
} EthereumSignTx_data_initial_chunk_t;
typedef struct _EthereumSignTx {
pb_callback_t address_n;
pb_callback_t nonce;
pb_callback_t gas_price;
pb_callback_t gas_limit;
pb_callback_t to;
pb_callback_t value;
pb_callback_t data_initial_chunk;
size_t address_n_count;
uint32_t address_n[8];
bool has_nonce;
EthereumSignTx_nonce_t nonce;
bool has_gas_price;
EthereumSignTx_gas_price_t gas_price;
bool has_gas_limit;
EthereumSignTx_gas_limit_t gas_limit;
bool has_to;
EthereumSignTx_to_t to;
bool has_value;
EthereumSignTx_value_t value;
bool has_data_initial_chunk;
EthereumSignTx_data_initial_chunk_t data_initial_chunk;
bool has_data_length;
uint32_t data_length;
} EthereumSignTx;
typedef struct {
size_t size;
uint8_t bytes[1024];
} EthereumTxAck_data_chunk_t;
typedef struct _EthereumTxAck {
pb_callback_t data_chunk;
bool has_data_chunk;
EthereumTxAck_data_chunk_t data_chunk;
} EthereumTxAck;
typedef struct {
size_t size;
uint8_t bytes[32];
} EthereumTxRequest_signature_r_t;
typedef struct {
size_t size;
uint8_t bytes[32];
} EthereumTxRequest_signature_s_t;
typedef struct _EthereumTxRequest {
bool has_data_length;
uint32_t data_length;
bool has_signature_v;
uint32_t signature_v;
pb_callback_t signature_r;
pb_callback_t signature_s;
bool has_signature_r;
EthereumTxRequest_signature_r_t signature_r;
bool has_signature_s;
EthereumTxRequest_signature_s_t signature_s;
} EthereumTxRequest;
typedef struct _Failure {
@ -747,9 +848,9 @@ extern const uint32_t SimpleSignTx_lock_time_default;
#define GetPublicKey_init_default {0, {0, 0, 0, 0, 0, 0, 0, 0}, false, "", false, 0}
#define PublicKey_init_default {HDNodeType_init_default, false, ""}
#define GetAddress_init_default {0, {0, 0, 0, 0, 0, 0, 0, 0}, false, "Bitcoin", false, 0, false, MultisigRedeemScriptType_init_default}
#define EthereumGetAddress_init_default {{{NULL}, NULL}, false, 0}
#define EthereumGetAddress_init_default {0, {0, 0, 0, 0, 0, 0, 0, 0}, false, 0}
#define Address_init_default {""}
#define EthereumAddress_init_default {{{NULL}, NULL}}
#define EthereumAddress_init_default {{0, {0}}}
#define WipeDevice_init_default {0}
#define LoadDevice_init_default {false, "", false, HDNodeType_init_default, false, "", false, 0, false, "english", false, "", false, 0}
#define ResetDevice_init_default {false, 0, false, 256u, false, 0, false, 0, false, "english", false, ""}
@ -773,9 +874,9 @@ extern const uint32_t SimpleSignTx_lock_time_default;
#define SimpleSignTx_init_default {0, {}, 0, {}, 0, {}, false, "Bitcoin", false, 1u, false, 0u}
#define TxRequest_init_default {false, (RequestType)0, false, TxRequestDetailsType_init_default, false, TxRequestSerializedType_init_default}
#define TxAck_init_default {false, TransactionType_init_default}
#define EthereumSignTx_init_default {{{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}, false, 0}
#define EthereumTxRequest_init_default {false, 0, false, 0, {{NULL}, NULL}, {{NULL}, NULL}}
#define EthereumTxAck_init_default {{{NULL}, NULL}}
#define EthereumSignTx_init_default {0, {0, 0, 0, 0, 0, 0, 0, 0}, false, {0, {0}}, false, {0, {0}}, false, {0, {0}}, false, {0, {0}}, false, {0, {0}}, false, {0, {0}}, false, 0}
#define EthereumTxRequest_init_default {false, 0, false, 0, false, {0, {0}}, false, {0, {0}}}
#define EthereumTxAck_init_default {false, {0, {0}}}
#define SignIdentity_init_default {false, IdentityType_init_default, false, {0, {0}}, false, "", false, ""}
#define SignedIdentity_init_default {false, "", false, {0, {0}}, false, {0, {0}}}
#define GetECDHSessionKey_init_default {false, IdentityType_init_default, false, {0, {0}}, false, ""}
@ -787,6 +888,10 @@ extern const uint32_t SimpleSignTx_lock_time_default;
#define DebugLinkState_init_default {false, {0, {0}}, false, "", false, "", false, "", false, HDNodeType_init_default, false, 0, false, "", false, {0, {0}}, false, "", false, 0}
#define DebugLinkStop_init_default {0}
#define DebugLinkLog_init_default {false, 0, false, "", false, ""}
#define DebugLinkMemoryRead_init_default {false, 0, false, 0}
#define DebugLinkMemory_init_default {false, {0, {0}}}
#define DebugLinkMemoryWrite_init_default {false, 0, false, {0, {0}}, false, 0}
#define DebugLinkFlashErase_init_default {false, 0}
#define Initialize_init_zero {0}
#define GetFeatures_init_zero {0}
#define Features_init_zero {false, "", false, 0, false, 0, false, 0, false, 0, false, "", false, 0, false, 0, false, "", false, "", 0, {CoinType_init_zero, CoinType_init_zero, CoinType_init_zero, CoinType_init_zero, CoinType_init_zero, CoinType_init_zero}, false, 0, false, {0, {0}}, false, {0, {0}}, false, 0, false, 0, false, 0}
@ -808,9 +913,9 @@ extern const uint32_t SimpleSignTx_lock_time_default;
#define GetPublicKey_init_zero {0, {0, 0, 0, 0, 0, 0, 0, 0}, false, "", false, 0}
#define PublicKey_init_zero {HDNodeType_init_zero, false, ""}
#define GetAddress_init_zero {0, {0, 0, 0, 0, 0, 0, 0, 0}, false, "", false, 0, false, MultisigRedeemScriptType_init_zero}
#define EthereumGetAddress_init_zero {{{NULL}, NULL}, false, 0}
#define EthereumGetAddress_init_zero {0, {0, 0, 0, 0, 0, 0, 0, 0}, false, 0}
#define Address_init_zero {""}
#define EthereumAddress_init_zero {{{NULL}, NULL}}
#define EthereumAddress_init_zero {{0, {0}}}
#define WipeDevice_init_zero {0}
#define LoadDevice_init_zero {false, "", false, HDNodeType_init_zero, false, "", false, 0, false, "", false, "", false, 0}
#define ResetDevice_init_zero {false, 0, false, 0, false, 0, false, 0, false, "", false, ""}
@ -834,9 +939,9 @@ extern const uint32_t SimpleSignTx_lock_time_default;
#define SimpleSignTx_init_zero {0, {}, 0, {}, 0, {}, false, "", false, 0, false, 0}
#define TxRequest_init_zero {false, (RequestType)0, false, TxRequestDetailsType_init_zero, false, TxRequestSerializedType_init_zero}
#define TxAck_init_zero {false, TransactionType_init_zero}
#define EthereumSignTx_init_zero {{{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}, false, 0}
#define EthereumTxRequest_init_zero {false, 0, false, 0, {{NULL}, NULL}, {{NULL}, NULL}}
#define EthereumTxAck_init_zero {{{NULL}, NULL}}
#define EthereumSignTx_init_zero {0, {0, 0, 0, 0, 0, 0, 0, 0}, false, {0, {0}}, false, {0, {0}}, false, {0, {0}}, false, {0, {0}}, false, {0, {0}}, false, {0, {0}}, false, 0}
#define EthereumTxRequest_init_zero {false, 0, false, 0, false, {0, {0}}, false, {0, {0}}}
#define EthereumTxAck_init_zero {false, {0, {0}}}
#define SignIdentity_init_zero {false, IdentityType_init_zero, false, {0, {0}}, false, "", false, ""}
#define SignedIdentity_init_zero {false, "", false, {0, {0}}, false, {0, {0}}}
#define GetECDHSessionKey_init_zero {false, IdentityType_init_zero, false, {0, {0}}, false, ""}
@ -848,6 +953,10 @@ extern const uint32_t SimpleSignTx_lock_time_default;
#define DebugLinkState_init_zero {false, {0, {0}}, false, "", false, "", false, "", false, HDNodeType_init_zero, false, 0, false, "", false, {0, {0}}, false, "", false, 0}
#define DebugLinkStop_init_zero {0}
#define DebugLinkLog_init_zero {false, 0, false, "", false, ""}
#define DebugLinkMemoryRead_init_zero {false, 0, false, 0}
#define DebugLinkMemory_init_zero {false, {0, {0}}}
#define DebugLinkMemoryWrite_init_zero {false, 0, false, {0, {0}}, false, 0}
#define DebugLinkFlashErase_init_zero {false, 0}
/* Field tags (for use in manual encoding/decoding) */
#define Address_address_tag 1
@ -867,9 +976,16 @@ extern const uint32_t SimpleSignTx_lock_time_default;
#define CipherKeyValue_iv_tag 7
#define CipheredKeyValue_value_tag 1
#define DebugLinkDecision_yes_no_tag 1
#define DebugLinkFlashErase_sector_tag 1
#define DebugLinkLog_level_tag 1
#define DebugLinkLog_bucket_tag 2
#define DebugLinkLog_text_tag 3
#define DebugLinkMemory_memory_tag 1
#define DebugLinkMemoryRead_address_tag 1
#define DebugLinkMemoryRead_length_tag 2
#define DebugLinkMemoryWrite_address_tag 1
#define DebugLinkMemoryWrite_memory_tag 2
#define DebugLinkMemoryWrite_flash_tag 3
#define DebugLinkState_layout_tag 1
#define DebugLinkState_pin_tag 2
#define DebugLinkState_matrix_tag 3
@ -1072,6 +1188,10 @@ extern const pb_field_t DebugLinkGetState_fields[1];
extern const pb_field_t DebugLinkState_fields[11];
extern const pb_field_t DebugLinkStop_fields[1];
extern const pb_field_t DebugLinkLog_fields[4];
extern const pb_field_t DebugLinkMemoryRead_fields[3];
extern const pb_field_t DebugLinkMemory_fields[2];
extern const pb_field_t DebugLinkMemoryWrite_fields[4];
extern const pb_field_t DebugLinkFlashErase_fields[2];
/* Maximum encoded size of messages (where known) */
#define Initialize_size 0
@ -1095,7 +1215,9 @@ extern const pb_field_t DebugLinkLog_fields[4];
#define GetPublicKey_size 84
#define PublicKey_size (121 + HDNodeType_size)
#define GetAddress_size (75 + MultisigRedeemScriptType_size)
#define EthereumGetAddress_size 50
#define Address_size 38
#define EthereumAddress_size 22
#define WipeDevice_size 0
#define LoadDevice_size (320 + HDNodeType_size)
#define ResetDevice_size 66
@ -1119,6 +1241,9 @@ extern const pb_field_t DebugLinkLog_fields[4];
#define SimpleSignTx_size (31 + 0*TxInputType_size + 0*TxOutputType_size + 0*TransactionType_size)
#define TxRequest_size (18 + TxRequestDetailsType_size + TxRequestSerializedType_size)
#define TxAck_size (6 + TransactionType_size)
#define EthereumSignTx_size 1239
#define EthereumTxRequest_size 80
#define EthereumTxAck_size 1027
#define SignIdentity_size (558 + IdentityType_size)
#define SignedIdentity_size 140
#define GetECDHSessionKey_size (107 + IdentityType_size)
@ -1130,6 +1255,10 @@ extern const pb_field_t DebugLinkLog_fields[4];
#define DebugLinkState_size (1468 + HDNodeType_size)
#define DebugLinkStop_size 0
#define DebugLinkLog_size 300
#define DebugLinkMemoryRead_size 12
#define DebugLinkMemory_size 1027
#define DebugLinkMemoryWrite_size 1035
#define DebugLinkFlashErase_size 6
#ifdef __cplusplus
} /* extern "C" */

@ -41,7 +41,7 @@ void next_word(void) {
if (word_pos == 0) {
const char * const *wl = mnemonic_wordlist();
strlcpy(fake_word, wl[random_uniform(2048)], sizeof(fake_word));
layoutDialogSwipe(DIALOG_ICON_INFO, NULL, NULL, NULL, "Please enter the word", NULL, fake_word, NULL, "on your computer", NULL);
layoutDialogSwipe(&bmp_icon_info, NULL, NULL, NULL, "Please enter the word", NULL, fake_word, NULL, "on your computer", NULL);
} else {
fake_word[0] = 0;
char desc[] = "##th word";
@ -60,7 +60,7 @@ void next_word(void) {
if (word_pos == 3 || word_pos == 23) {
desc[2] = 'r'; desc[3] = 'd';
}
layoutDialogSwipe(DIALOG_ICON_INFO, NULL, NULL, NULL, "Please enter the", NULL, (word_pos < 10 ? desc + 1 : desc), NULL, "of your mnemonic", NULL);
layoutDialogSwipe(&bmp_icon_info, NULL, NULL, NULL, "Please enter the", NULL, (word_pos < 10 ? desc + 1 : desc), NULL, "of your mnemonic", NULL);
}
WordRequest resp;
memset(&resp, 0, sizeof(WordRequest));

@ -52,7 +52,7 @@ void reset_init(bool display_random, uint32_t _strength, bool passphrase_protect
data2hex(int_entropy + 24, 8, ent_str[3]);
if (display_random) {
layoutDialogSwipe(DIALOG_ICON_INFO, "Cancel", "Continue", NULL, "Internal entropy:", ent_str[0], ent_str[1], ent_str[2], ent_str[3], NULL);
layoutDialogSwipe(&bmp_icon_info, "Cancel", "Continue", NULL, "Internal entropy:", ent_str[0], ent_str[1], ent_str[2], ent_str[3], NULL);
if (!protectButton(ButtonRequestType_ButtonRequest_ResetDevice, false)) {
fsm_sendFailure(FailureType_Failure_ActionCancelled, "Reset cancelled");
layoutHome();
@ -129,15 +129,15 @@ void reset_entropy(const uint8_t *ext_entropy, uint32_t len)
current_word_display[j + 1] = 0;
if (word_pos == (int)strength/32*3) { // last word
if (pass == 1) {
layoutDialogSwipe(DIALOG_ICON_INFO, NULL, "Finish", NULL, "Please check the seed", NULL, (word_pos < 10 ? desc + 1 : desc), current_word_display, NULL, NULL);
layoutDialogSwipe(&bmp_icon_info, NULL, "Finish", NULL, "Please check the seed", NULL, (word_pos < 10 ? desc + 1 : desc), current_word_display, NULL, NULL);
} else {
layoutDialogSwipe(DIALOG_ICON_INFO, NULL, "Again", NULL, "Write down the seed", NULL, (word_pos < 10 ? desc + 1 : desc), current_word_display, NULL, NULL);
layoutDialogSwipe(&bmp_icon_info, NULL, "Again", NULL, "Write down the seed", NULL, (word_pos < 10 ? desc + 1 : desc), current_word_display, NULL, NULL);
}
} else {
if (pass == 1) {
layoutDialogSwipe(DIALOG_ICON_INFO, NULL, "Next", NULL, "Please check the seed", NULL, (word_pos < 10 ? desc + 1 : desc), current_word_display, NULL, NULL);
layoutDialogSwipe(&bmp_icon_info, NULL, "Next", NULL, "Please check the seed", NULL, (word_pos < 10 ? desc + 1 : desc), current_word_display, NULL, NULL);
} else {
layoutDialogSwipe(DIALOG_ICON_INFO, NULL, "Next", NULL, "Write down the seed", NULL, (word_pos < 10 ? desc + 1 : desc), current_word_display, NULL, NULL);
layoutDialogSwipe(&bmp_icon_info, NULL, "Next", NULL, "Write down the seed", NULL, (word_pos < 10 ? desc + 1 : desc), current_word_display, NULL, NULL);
}
}
if (!protectButton(ButtonRequestType_ButtonRequest_ConfirmWord, true)) {

@ -48,13 +48,15 @@ char storage_uuid_str[25];
/*
storage layout:
offset | type/length | description
--------+-------------+-------------------------------
0x0000 | 4 bytes | magic = 'stor'
0x0004 | 12 bytes | uuid
0x0010 | ? | Storage structure
0x4000 | 4 kbytes | area for pin failures
0x5000 | 12 kbytes | reserved
offset | type/length | description
--------+--------------+-------------------------------
0x0000 | 4 bytes | magic = 'stor'
0x0004 | 12 bytes | uuid
0x0010 | ? bytes | Storage structure
--------+--------------+-------------------------------
0x4000 | 4 kbytes | area for pin failures
0x5000 | 256 bytes | area for u2f counter updates
0x5100 | 11.75 kbytes | reserved
The area for pin failures looks like this:
0 ... 0 pinfail 0xffffffff .. 0xffffffff
@ -63,16 +65,29 @@ the number of zeros is the number of pin failures.
This layout is used because we can only clear bits without
erasing the flash.
The area for u2f counter updates is just a sequence of zero-bits
followed by a sequence of one-bits. The bits in a byte are numbered
from LSB to MSB. The number of zero bits is the offset that should
be added to the storage u2f_counter to get the real counter value.
*/
#define FLASH_STORAGE_PINAREA (FLASH_META_START + 0x4000)
#define FLASH_STORAGE_PINAREA_LEN (0x1000)
#define FLASH_STORAGE_U2FAREA (FLASH_STORAGE_PINAREA + FLASH_STORAGE_PINAREA_LEN)
#define FLASH_STORAGE_U2FAREA_LEN (0x100)
#define FLASH_STORAGE_REALLEN (4 + sizeof(storage_uuid) + sizeof(Storage))
_Static_assert(FLASH_STORAGE_START + FLASH_STORAGE_REALLEN <= FLASH_STORAGE_PINAREA, "Storage struct is too large for TREZOR flash");
_Static_assert((sizeof(storage_uuid) & 3) == 0, "storage uuid unaligned");
_Static_assert((sizeof(storage) & 3) == 0, "storage unaligned");
static bool sessionSeedCached;
/* Current u2f offset, i.e. u2f counter is
* storage.u2f_counter + storage_u2f_offset.
* This corresponds to the number of cleared bits in the U2FAREA.
*/
static uint32_t storage_u2f_offset;
static bool sessionSeedCached, sessionSeedUsesPassphrase;
static uint8_t sessionSeed[64];
@ -83,12 +98,17 @@ static char sessionPassphrase[51];
#define STORAGE_VERSION 6
void storage_show_error(void)
{
layoutDialog(&bmp_icon_error, NULL, NULL, NULL, "Storage failure", "detected.", NULL, "Please unplug", "the device.", NULL);
for (;;) { }
}
void storage_check_flash_errors(void)
{
// flash operation failed
if (FLASH_SR & (FLASH_SR_PGAERR | FLASH_SR_PGPERR | FLASH_SR_PGSERR | FLASH_SR_WRPERR)) {
layoutDialog(DIALOG_ICON_ERROR, NULL, NULL, NULL, "Storage failure", "detected.", NULL, "Please unplug", "the device.", NULL);
for (;;) { }
storage_show_error();
}
}
@ -132,6 +152,15 @@ bool storage_from_flash(void)
storage.has_pin_failed_attempts = false;
storage.pin_failed_attempts = 0;
}
uint32_t *u2fptr = (uint32_t*) FLASH_STORAGE_U2FAREA;
while (*u2fptr == 0)
u2fptr++;
storage_u2f_offset = 32 * (u2fptr - (uint32_t*) FLASH_STORAGE_U2FAREA);
uint32_t u2fword = *u2fptr;
while ((u2fword & 1) == 0) {
storage_u2f_offset++;
u2fword >>= 1;
}
// upgrade storage version
if (version != STORAGE_VERSION) {
storage.version = STORAGE_VERSION;
@ -185,15 +214,13 @@ static uint32_t storage_flash_words(uint32_t addr, uint32_t *src, int nwords) {
return addr;
}
void storage_commit(void)
static void storage_commit_locked(void)
{
uint8_t meta_backup[FLASH_META_DESC_LEN];
// backup meta
memcpy(meta_backup, (uint8_t*)FLASH_META_START, FLASH_META_DESC_LEN);
flash_clear_status_flags();
flash_unlock();
// erase storage
flash_erase_sector(FLASH_META_SECTOR_FIRST, FLASH_CR_PROGRAM_X32);
// copy meta
@ -209,6 +236,13 @@ void storage_commit(void)
flash_program_word(flash, 0);
flash += 4;
}
}
void storage_commit(void)
{
flash_clear_status_flags();
flash_unlock();
storage_commit_locked();
flash_lock();
storage_check_flash_errors();
}
@ -298,27 +332,29 @@ void get_root_node_callback(uint32_t iter, uint32_t total)
layoutProgress("Waking up", 1000 * iter / total);
}
const uint8_t *storage_getSeed(void)
const uint8_t *storage_getSeed(bool usePassphrase)
{
// root node is properly cached
if (sessionSeedCached) {
if (usePassphrase == sessionSeedUsesPassphrase
&& sessionSeedCached) {
return sessionSeed;
}
// if storage has mnemonic, convert it to node and use it
if (storage.has_mnemonic) {
if (!protectPassphrase()) {
if (usePassphrase && !protectPassphrase()) {
return NULL;
}
mnemonic_to_seed(storage.mnemonic, sessionPassphrase, sessionSeed, get_root_node_callback); // BIP-0039
mnemonic_to_seed(storage.mnemonic, usePassphrase ? sessionPassphrase : "", sessionSeed, get_root_node_callback); // BIP-0039
sessionSeedCached = true;
sessionSeedUsesPassphrase = usePassphrase;
return sessionSeed;
}
return NULL;
}
bool storage_getRootNode(HDNode *node, const char *curve)
bool storage_getRootNode(HDNode *node, const char *curve, bool usePassphrase)
{
// if storage has node, decrypt and use it
if (storage.has_node && strcmp(curve, SECP256K1_NAME) == 0) {
@ -347,7 +383,7 @@ bool storage_getRootNode(HDNode *node, const char *curve)
return true;
}
const uint8_t *seed = storage_getSeed();
const uint8_t *seed = storage_getSeed(usePassphrase);
if (seed == NULL) {
return false;
}
@ -427,23 +463,49 @@ bool session_isPinCached(void)
return sessionPinCached;
}
void storage_clearPinArea()
void storage_clearPinArea(void)
{
flash_clear_status_flags();
flash_unlock();
flash_erase_sector(FLASH_META_SECTOR_LAST, FLASH_CR_PROGRAM_X32);
flash_lock();
storage_check_flash_errors();
storage_u2f_offset = 0;
}
// called when u2f area or pin area overflows
static void storage_area_recycle(uint32_t new_pinfails)
{
// first clear storage marker. In case of a failure below it is better
// to clear the storage than to allow restarting with zero PIN failures
flash_program_word(FLASH_STORAGE_START, 0);
if (*(uint32_t *)FLASH_STORAGE_START != 0) {
storage_show_error();
}
// erase storage sector
flash_erase_sector(FLASH_META_SECTOR_LAST, FLASH_CR_PROGRAM_X32);
flash_program_word(FLASH_STORAGE_PINAREA, new_pinfails);
if (*(uint32_t *)FLASH_STORAGE_PINAREA != new_pinfails) {
storage_show_error();
}
if (storage_u2f_offset > 0) {
storage.has_u2f_counter = true;
storage.u2f_counter += storage_u2f_offset;
storage_u2f_offset = 0;
}
storage_commit_locked();
}
void storage_resetPinFails(uint32_t *pinfailsptr)
{
flash_clear_status_flags();
flash_unlock();
if ((uint32_t) (pinfailsptr + 1) - FLASH_STORAGE_PINAREA
>= FLASH_STORAGE_PINAREA_LEN) {
// erase extra storage sector
flash_erase_sector(FLASH_META_SECTOR_LAST, FLASH_CR_PROGRAM_X32);
if ((uint32_t) (pinfailsptr + 1)
>= FLASH_STORAGE_PINAREA + FLASH_STORAGE_PINAREA_LEN) {
// recycle extra storage sector
storage_area_recycle(0xffffffff);
} else {
flash_program_word((uint32_t) pinfailsptr, 0);
}
@ -480,3 +542,20 @@ bool storage_isInitialized(void)
{
return storage.has_node || storage.has_mnemonic;
}
uint32_t storage_nextU2FCounter(void)
{
uint32_t *ptr = ((uint32_t *) FLASH_STORAGE_U2FAREA) + (storage_u2f_offset / 32);
uint32_t newval = 0xfffffffe << (storage_u2f_offset & 31);
flash_clear_status_flags();
flash_unlock();
flash_program_word((uint32_t) ptr, newval);
storage_u2f_offset++;
if (storage_u2f_offset >= 8 * FLASH_STORAGE_U2FAREA_LEN) {
storage_area_recycle(*storage_getPinFailsPtr());
}
flash_lock();
storage_check_flash_errors();
return storage.u2f_counter + storage_u2f_offset;
}

@ -33,9 +33,9 @@ void session_clear(bool clear_pin);
void storage_loadDevice(LoadDevice *msg);
const uint8_t *storage_getSeed(void);
const uint8_t *storage_getSeed(bool usePassphrase);
bool storage_getRootNode(HDNode *node, const char *curve);
bool storage_getRootNode(HDNode *node, const char *curve, bool usePassphrase);
const char *storage_getLabel(void);
void storage_setLabel(const char *label);
@ -61,6 +61,8 @@ void storage_resetPinFails(uint32_t *pinfailptr);
bool storage_increasePinFails(uint32_t *pinfailptr);
uint32_t *storage_getPinFailsPtr(void);
uint32_t storage_nextU2FCounter(void);
bool storage_isInitialized(void);
extern Storage storage;

@ -32,7 +32,7 @@ uint32_t __stack_chk_guard;
void __attribute__((noreturn)) __stack_chk_fail(void)
{
layoutDialog(DIALOG_ICON_ERROR, NULL, NULL, NULL, "Stack smashing", "detected.", NULL, "Please unplug", "the device.", NULL);
layoutDialog(&bmp_icon_error, NULL, NULL, NULL, "Stack smashing", "detected.", NULL, "Please unplug", "the device.", NULL);
for (;;) {} // loop forever
}

@ -0,0 +1,768 @@
/*
* This file is part of the TREZOR project.
*
* Copyright (C) 2015 Mark Bryars <mbryars@google.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <string.h>
#include <ecdsa.h>
#include "debug.h"
#include "storage.h"
#include "bip32.h"
#include "layout2.h"
#include "usb.h"
#include "buttons.h"
#include "trezor.h"
#include "curves.h"
#include "nist256p1.h"
#include "rng.h"
#include "hmac.h"
#include "util.h"
#include "macros.h"
#include "u2f/u2f.h"
#include "u2f/u2f_hid.h"
#include "u2f/u2f_keys.h"
#include "u2f_knownapps.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 (800000/2)
#define U2F_OUT_PKT_BUFFER_LEN 128
// Initialise without a cid
static uint32_t cid = 0;
// 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_PATH_LEN 32
#define KEY_HANDLE_LEN (KEY_PATH_LEN + SHA256_DIGEST_LENGTH)
// Derivation path is m/U2F'/r'/r'/r'/r'/r'/r'/r'/r'
#define KEY_PATH_ENTRIES (1 + KEY_PATH_LEN / sizeof(uint32_t))
// Auth/Register request state machine
typedef enum {
INIT = 0,
AUTH = 10,
AUTH_PASS = 11,
REG = 20,
REG_PASS = 21
} 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;
#if DEBUG_LOG
char *debugInt(const uint32_t i)
{
static uint8_t n = 0;
static char id[8][9];
uint32hex(i, id[n]);
debugLog(0, "", id[n]);
char *ret = (char *)id[n];
n = (n + 1) % 8;
return ret;
}
#else
#define debugInt(I) do{}while(0)
#endif
static uint32_t dialog_timeout = 0;
uint32_t next_cid(void)
{
// extremely unlikely but hey
do {
cid = random32();
} while (cid == 0 || cid == CID_BROADCAST);
return cid;
}
typedef struct {
uint8_t buf[57+127*59];
uint8_t *buf_ptr;
uint32_t len;
uint8_t seq;
uint8_t cmd;
} U2F_ReadBuffer;
U2F_ReadBuffer *reader;
void u2fhid_read(char tiny, const U2FHID_FRAME *f)
{
// Always handle init packets directly
if (f->init.cmd == U2FHID_INIT) {
u2fhid_init(f);
if (tiny && reader && f->cid == cid) {
// abort current channel
reader->cmd = 0;
reader->len = 0;
reader->seq = 255;
}
return;
}
if (tiny) {
// read continue packet
if (reader == 0 || cid != f->cid) {
send_u2fhid_error(f->cid, ERR_CHANNEL_BUSY);
return;
}
if ((f->type & TYPE_INIT) && reader->seq == 255) {
u2fhid_init_cmd(f);
return;
}
if (reader->seq != f->cont.seq) {
send_u2fhid_error(f->cid, ERR_INVALID_SEQ);
reader->cmd = 0;
reader->len = 0;
reader->seq = 255;
return;
}
// check out of bounds
if ((reader->buf_ptr - reader->buf) >= (signed) reader->len
|| (reader->buf_ptr + sizeof(f->cont.data) - reader->buf) > (signed) sizeof(reader->buf))
return;
reader->seq++;
memcpy(reader->buf_ptr, f->cont.data, sizeof(f->cont.data));
reader->buf_ptr += sizeof(f->cont.data);
return;
}
u2fhid_read_start(f);
}
void u2fhid_init_cmd(const U2FHID_FRAME *f) {
reader->seq = 0;
reader->buf_ptr = reader->buf;
reader->len = MSG_LEN(*f);
reader->cmd = f->type;
memcpy(reader->buf_ptr, f->init.data, sizeof(f->init.data));
reader->buf_ptr += sizeof(f->init.data);
cid = f->cid;
}
void u2fhid_read_start(const U2FHID_FRAME *f) {
U2F_ReadBuffer readbuffer;
if (!(f->type & TYPE_INIT)) {
return;
}
// Broadcast is reserved for init
if (f->cid == CID_BROADCAST || f->cid == 0) {
send_u2fhid_error(f->cid, ERR_INVALID_CID);
return;
}
if ((unsigned)MSG_LEN(*f) > sizeof(reader->buf)) {
send_u2fhid_error(f->cid, ERR_INVALID_LEN);
return;
}
reader = &readbuffer;
u2fhid_init_cmd(f);
usbTiny(1);
for(;;) {
// Do we need to wait for more data
while ((reader->buf_ptr - reader->buf) < (signed)reader->len) {
uint8_t lastseq = reader->seq;
uint8_t lastcmd = reader->cmd;
int counter = U2F_TIMEOUT;
while (reader->seq == lastseq && reader->cmd == lastcmd) {
if (counter-- == 0) {
// timeout
send_u2fhid_error(cid, ERR_MSG_TIMEOUT);
cid = 0;
reader = 0;
usbTiny(0);
layoutHome();
return;
}
usbPoll();
}
}
// We have all the data
switch (reader->cmd) {
case 0:
// message was aborted by init
break;
case U2FHID_PING:
u2fhid_ping(reader->buf, reader->len);
break;
case U2FHID_MSG:
u2fhid_msg((APDU *)reader->buf, reader->len);
break;
case U2FHID_WINK:
u2fhid_wink(reader->buf, reader->len);
break;
default:
send_u2fhid_error(cid, ERR_INVALID_CMD);
break;
}
// wait for next commmand/ button press
reader->cmd = 0;
reader->seq = 255;
while (dialog_timeout > 0 && reader->cmd == 0) {
dialog_timeout--;
usbPoll(); // may trigger new request
buttonUpdate();
if (button.YesUp &&
(last_req_state == AUTH || last_req_state == REG)) {
last_req_state++;
// standard requires to remember button press for 10 seconds.
dialog_timeout = 10 * U2F_TIMEOUT;
}
}
if (reader->cmd == 0) {
last_req_state = INIT;
cid = 0;
reader = 0;
usbTiny(0);
layoutHome();
return;
}
}
}
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(cid, ERR_INVALID_LEN);
if (dialog_timeout > 0)
dialog_timeout = U2F_TIMEOUT;
U2FHID_FRAME f;
MEMSET_BZERO(&f, sizeof(f));
f.cid = cid;
f.init.cmd = U2FHID_WINK;
f.init.bcntl = 0;
queue_u2f_pkt(&f);
}
void u2fhid_init(const U2FHID_FRAME *in)
{
const U2FHID_INIT_REQ *init_req = (const U2FHID_INIT_REQ *)&in->init.data;
U2FHID_FRAME f;
U2FHID_INIT_RESP *resp = (U2FHID_INIT_RESP *)f.init.data;
debugLog(0, "", "u2fhid_init");
if (in->cid == 0) {
send_u2fhid_error(in->cid, ERR_INVALID_CID);
return;
}
MEMSET_BZERO(&f, sizeof(f));
f.cid = in->cid;
f.init.cmd = U2FHID_INIT;
f.init.bcnth = 0;
f.init.bcntl = U2FHID_INIT_RESP_SIZE;
memcpy(resp->nonce, init_req->nonce, sizeof(init_req->nonce));
resp->cid = in->cid == CID_BROADCAST ? next_cid() : in->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)
{
if ((APDU_LEN(*a) + sizeof(APDU)) > len) {
debugLog(0, "", "BAD APDU LENGTH");
debugInt(APDU_LEN(*a));
debugInt(len);
return;
}
if (a->cla != 0) {
send_u2f_error(U2F_SW_CLA_NOT_SUPPORTED);
return;
}
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);
}
}
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");
MEMSET_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");
MEMSET_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(uint32_t fcid, uint8_t err)
{
U2FHID_FRAME f;
MEMSET_BZERO(&f, sizeof(f));
f.cid = fcid;
f.init.cmd = U2FHID_ERROR;
f.init.bcntl = 1;
f.init.data[0] = err;
queue_u2f_pkt(&f);
}
void u2f_version(const APDU *a)
{
if (APDU_LEN(*a) != 0) {
debugLog(0, "", "u2f version - badlen");
send_u2f_error(U2F_SW_WRONG_LENGTH);
return;
}
// INCLUDES SW_NO_ERROR
static const uint8_t version_response[] = {'U', '2', 'F', '_',
'V', '2', 0x90, 0x00};
debugLog(0, "", "u2f version");
send_u2f_msg(version_response, sizeof(version_response));
}
void getReadableAppId(const uint8_t appid[U2F_APPID_SIZE], const char **appname, const BITMAP **appicon) {
unsigned int i;
static char buf[8+2+8+1];
for (i = 0; i < sizeof(u2f_well_known)/sizeof(U2FWellKnown); i++) {
if (memcmp(appid, u2f_well_known[i].appid, U2F_APPID_SIZE) == 0) {
*appname = u2f_well_known[i].appname;
*appicon = u2f_well_known[i].appicon;
return;
}
}
data2hex(appid, 4, &buf[0]);
buf[8] = buf[9] = '.';
data2hex(appid + (U2F_APPID_SIZE - 4), 4, &buf[10]);
*appname = buf;
*appicon = NULL;
}
const HDNode *getDerivedNode(uint32_t *address_n, size_t address_n_count)
{
static HDNode node;
if (!storage_getRootNode(&node, NIST256P1_NAME, false)) {
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[U2F_APPID_SIZE + KEY_PATH_LEN];
// Derivation path is m/U2F'/r'/r'/r'/r'/r'/r'/r'/r'
uint32_t i, key_path[KEY_PATH_ENTRIES];
key_path[0] = U2F_KEY_PATH;
for (i = 1; i < KEY_PATH_ENTRIES; i++) {
// high bit for hardened keys
key_path[i]= 0x80000000 | random32();
}
// First half of keyhandle is key_path
memcpy(key_handle, &key_path[1], KEY_PATH_LEN);
// prepare keypair from /random data
const HDNode *node = getDerivedNode(key_path, KEY_PATH_ENTRIES);
if (!node)
return NULL;
// For second half of keyhandle
// Signature of app_id and random data
memcpy(&keybase[0], app_id, U2F_APPID_SIZE);
memcpy(&keybase[U2F_APPID_SIZE], key_handle, KEY_PATH_LEN);
hmac_sha256(node->private_key, sizeof(node->private_key),
keybase, sizeof(keybase), &key_handle[KEY_PATH_LEN]);
// Done!
return node;
}
const HDNode *validateKeyHandle(const uint8_t app_id[], const uint8_t key_handle[])
{
uint32_t key_path[KEY_PATH_ENTRIES];
key_path[0] = U2F_KEY_PATH;
memcpy(&key_path[1], key_handle, KEY_PATH_LEN);
const HDNode *node = getDerivedNode(key_path, KEY_PATH_ENTRIES);
if (!node)
return NULL;
uint8_t keybase[U2F_APPID_SIZE + KEY_PATH_LEN];
memcpy(&keybase[0], app_id, U2F_APPID_SIZE);
memcpy(&keybase[U2F_APPID_SIZE], key_handle, KEY_PATH_LEN);
uint8_t hmac[SHA256_DIGEST_LENGTH];
hmac_sha256(node->private_key, sizeof(node->private_key),
keybase, sizeof(keybase), hmac);
if (memcmp(&key_handle[KEY_PATH_LEN], hmac, SHA256_DIGEST_LENGTH) != 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;
if (!storage_isInitialized()) {
send_u2f_error(U2F_SW_CONDITIONS_NOT_SATISFIED);
return;
}
// 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_LENGTH);
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 == INIT) {
// wake up crypto system to be ready for signing
getDerivedNode(NULL, 0);
// error: testof-user-presence is required
buttonUpdate(); // Clear button state
const char *appname;
const BITMAP *appicon;
getReadableAppId(req->appId, &appname, &appicon);
layoutU2FDialog("Register", appname, appicon);
last_req_state = REG;
}
// 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 yes
if (last_req_state == REG_PASS) {
uint8_t data[sizeof(U2F_REGISTER_RESP) + 2];
U2F_REGISTER_RESP *resp = (U2F_REGISTER_RESP *)&data;
MEMSET_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(node->curve->params, 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;
last_req_state = INIT;
dialog_timeout = 0;
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 (!storage_isInitialized()) {
send_u2f_error(U2F_SW_CONDITIONS_NOT_SATISFIED);
return;
}
if (APDU_LEN(*a) < 64) { /// FIXME: decent value
debugLog(0, "", "u2f authenticate - badlen");
send_u2f_error(U2F_SW_WRONG_LENGTH);
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
buttonUpdate(); // Clear button state
const char *appname;
const BITMAP *appicon;
getReadableAppId(req->appId, &appname, &appicon);
layoutU2FDialog("Authenticate", appname, appicon);
last_req_state = AUTH;
}
// 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 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);
last_req_state = INIT;
dialog_timeout = 0;
send_u2f_msg(buf, sizeof(U2F_AUTHENTICATE_RESP) -
U2F_MAX_EC_SIG_SIZE + sig_len +
2);
}
}
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);
}

@ -0,0 +1,62 @@
/*
* This file is part of the TREZOR project.
*
* Copyright (C) 2015 Mark Bryars <mbryars@google.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef __U2F_H__
#define __U2F_H__
#include <stdint.h>
#include <stdbool.h>
#include "u2f/u2f_hid.h"
#include "trezor.h"
#define U2F_KEY_PATH 0x80553246
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(char tiny, const U2FHID_FRAME *buf);
void u2fhid_init_cmd(const U2FHID_FRAME *f);
void u2fhid_read_start(const U2FHID_FRAME *f);
bool u2fhid_write(uint8_t *buf);
void u2fhid_init(const U2FHID_FRAME *in);
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, uint32_t len);
void send_u2f_error(uint16_t err);
void send_u2fhid_msg(const uint8_t cmd, const uint8_t *data,
const uint32_t len);
void send_u2fhid_error(uint32_t fcid, uint8_t err);
#endif

@ -0,0 +1,45 @@
#!/bin/bash
cat > u2f_keys.h <<EOF
#ifndef __U2F_KEYS_H_INCLUDED__
#define __U2F_KEYS_H_INCLUDED__
#include <stdint.h>
const uint8_t U2F_ATT_PRIV_KEY[] = {
EOF
if [ \! -e trezordevkey.pem ]; then
openssl ecparam -genkey -out trezordevkey.pem -name prime256v1
fi
openssl ec -in trezordevkey.pem -text |
perl -e '$key = "\t"; while (<>) {
if (/priv:/) { $priv = 1 }
elsif (/pub:/) { $priv = 0 }
elsif ($priv) {
while ($_ =~ s/.*?([0-9a-f]{2})//) {
$key .= "0x$1,";
if ($num++ % 8 == 7) { $key .= "\n\t"; }
else {$key .= " ";}
}
}
}
$key =~ s/,\s*$/\n/s;
print $key;' >> u2f_keys.h
cat >> u2f_keys.h <<EOF
};
const uint8_t U2F_ATT_CERT[] = {
EOF
openssl req -new -key trezordevkey.pem -out trezordevcert.req -subj "/CN=Trezor U2F"
openssl x509 -req -in trezordevcert.req -signkey trezordevkey.pem -days 3650 -out trezordevcert.pem
openssl x509 -in trezordevcert.pem -outform der | od -tx1 -w12 -Anone | perl -pe 's/ ([0-9a-f]{2})/ 0x$1,/g; $_ =~ s/^ /\t/;' >> u2f_keys.h
cat >> u2f_keys.h <<EOF
};
#endif // __U2F_KEYS_H_INCLUDED__
EOF
rm trezordevcert.req trezordevcert.pem

@ -0,0 +1,5 @@
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIHEmrCv2RNxhhq2D7x/N8SpXtc+iAAuK0CfpVuhUxQqLoAoGCCqGSM49
AwEHoUQDQgAE2Ri9+opUrJLpDakfynqiZFTA0XM2MU3eg6VLhrXfTvBSZZodb/y3
Rn8azduKMwgLXu2RiRP0Q6UmG8d7aGBvwQ==
-----END EC PRIVATE KEY-----

@ -0,0 +1,143 @@
/*
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 <http://www.gnu.org/licenses/>.
*/
// 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 <stdint.h>
#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_LENGTH 0x6700 // SW_WRONG_LENGTH
#define U2F_SW_DATA_INVALID 0x6984 // SW_WRONG_DATA
#define U2F_SW_CONDITIONS_NOT_SATISFIED 0x6985 // SW_CONDITIONS_NOT_SATISFIED
#define U2F_SW_WRONG_DATA 0x6a80 // SW_WRONG_DATA
#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__

@ -0,0 +1,142 @@
/*
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 <http://www.gnu.org/licenses/>.
*/
// 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 <stdint.h>
#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;
#define U2FHID_INIT_RESP_SIZE 17
// 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__

@ -0,0 +1,40 @@
#ifndef __U2F_KEYS_H_INCLUDED__
#define __U2F_KEYS_H_INCLUDED__
#include <stdint.h>
const 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
};
const uint8_t U2F_ATT_CERT[] = {
0x30, 0x82, 0x01, 0x18, 0x30, 0x81, 0xc0, 0x02, 0x09, 0x00, 0xb1, 0xd9,
0x8f, 0x42, 0x64, 0x72, 0xd3, 0x2c, 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, 0x36, 0x30,
0x34, 0x32, 0x39, 0x31, 0x33, 0x33, 0x31, 0x35, 0x33, 0x5a, 0x17, 0x0d,
0x32, 0x36, 0x30, 0x34, 0x32, 0x37, 0x31, 0x33, 0x33, 0x31, 0x35, 0x33,
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, 0x47, 0x00, 0x30, 0x44,
0x02, 0x20, 0x24, 0x1e, 0x81, 0xff, 0xd2, 0xe5, 0xe6, 0x15, 0x36, 0x94,
0xc3, 0x55, 0x2e, 0x8f, 0xeb, 0xd7, 0x1e, 0x89, 0x35, 0x92, 0x1c, 0xb4,
0x83, 0x41, 0x43, 0x71, 0x1c, 0x76, 0xea, 0xee, 0xf3, 0x95, 0x02, 0x20,
0x5f, 0x80, 0xeb, 0x10, 0xf2, 0x5c, 0xcc, 0x39, 0x8b, 0x3c, 0xa8, 0xa9,
0xad, 0xa4, 0x02, 0x7f, 0x93, 0x13, 0x20, 0x77, 0xb7, 0xab, 0xce, 0x77,
0x46, 0x5a, 0x27, 0xf5, 0x3d, 0x33, 0xa1, 0x1d,
};
#endif // __U2F_KEYS_H_INCLUDED__

@ -0,0 +1,72 @@
/*
* This file is part of the TREZOR project.
*
* Copyright (C) 2016 Jochen Hoenicke <hoenicke@gmail.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef __U2F_KNOWNAPPS_H_INCLUDED__
#define __U2F_KNOWNAPPS_H_INCLUDED__
#include <stdint.h>
#include "u2f/u2f.h"
#include "bitmaps.h"
typedef struct {
const uint8_t appid[U2F_APPID_SIZE];
const char *appname;
const BITMAP *appicon;
} U2FWellKnown;
static const U2FWellKnown u2f_well_known[4] = {
{
// https://www.gstatic.com/securitykey/origins.json
{ 0xa5,0x46,0x72,0xb2,0x22,0xc4,0xcf,0x95,
0xe1,0x51,0xed,0x8d,0x4d,0x3c,0x76,0x7a,
0x6c,0xc3,0x49,0x43,0x59,0x43,0x79,0x4e,
0x88,0x4f,0x3d,0x02,0x3a,0x82,0x29,0xfd },
"Google",
&bmp_u2f_google
},
{
// https://github.com/u2f/trusted_facets
{ 0x70,0x61,0x7d,0xfe,0xd0,0x65,0x86,0x3a,
0xf4,0x7c,0x15,0x55,0x6c,0x91,0x79,0x88,
0x80,0x82,0x8c,0xc4,0x07,0xfd,0xf7,0x0a,
0xe8,0x50,0x11,0x56,0x94,0x65,0xa0,0x75 },
"Github",
&bmp_u2f_github
},
{
// https://www.dropbox.com/u2f-app-id.json
{ 0xc5,0x0f,0x8a,0x7b,0x70,0x8e,0x92,0xf8,
0x2e,0x7a,0x50,0xe2,0xbd,0xc5,0x5d,0x8f,
0xd9,0x1a,0x22,0xfe,0x6b,0x29,0xc0,0xcd,
0xf7,0x80,0x55,0x30,0x84,0x2a,0xf5,0x81 },
"Dropbox",
&bmp_u2f_dropbox
},
{
// https://slushpool.com/static/security/u2f.json
{ 0x08,0xb2,0xa3,0xd4,0x19,0x39,0xaa,0x31,
0x66,0x84,0x93,0xcb,0x36,0xcd,0xcc,0x4f,
0x16,0xc4,0xd9,0xb4,0xc8,0x23,0x8b,0x73,
0xc2,0xf6,0x72,0xc0,0x33,0x00,0x71,0x97 },
"Slush Pool",
&bmp_u2f_slushpool
}
};
#endif // U2F_KNOWNAPPS_INCLUDED

@ -24,13 +24,22 @@
#include "usb.h"
#include "debug.h"
#include "messages.h"
#include "u2f.h"
#include "storage.h"
#include "util.h"
#if DEBUG_LINK
#define USB_INTERFACE_INDEX_U2F 2
#else
#define USB_INTERFACE_INDEX_U2F 1
#endif
#define ENDPOINT_ADDRESS_IN (0x81)
#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,
@ -87,6 +96,24 @@ static const uint8_t hid_report_descriptor_debug[] = {
0xc0 // END_COLLECTION
};
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;
@ -108,6 +135,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,
@ -139,6 +187,37 @@ 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 = 2,
}, {
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
.bEndpointAddress = ENDPOINT_ADDRESS_U2F_OUT,
.bmAttributes = USB_ENDPOINT_ATTR_INTERRUPT,
.wMaxPacketSize = 64,
.bInterval = 2,
}};
static const struct usb_interface_descriptor hid_iface_u2f[] = {{
.bLength = USB_DT_INTERFACE_SIZE,
.bDescriptorType = USB_DT_INTERFACE,
.bInterfaceNumber = USB_INTERFACE_INDEX_U2F,
.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,
@ -180,6 +259,9 @@ static const struct usb_interface ifaces[] = {{
.num_altsetting = 1,
.altsetting = hid_iface_debug,
#endif
}, {
.num_altsetting = 1,
.altsetting = hid_iface_u2f,
}};
static const struct usb_config_descriptor config = {
@ -187,9 +269,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,
@ -214,15 +296,29 @@ static int hid_control_request(usbd_device *dev, struct usb_setup_data *req, uin
(req->wValue != 0x2200))
return 0;
/* Handle the HID report descriptor. */
if (req->wIndex == USB_INTERFACE_INDEX_U2F) {
debugLog(0, "", "hid_control_request u2f");
*buf = (uint8_t *)hid_report_descriptor_u2f;
*len = sizeof(hid_report_descriptor_u2f);
return 1;
}
#if DEBUG_LINK
if (req->wIndex == 1) {
debugLog(0, "", "hid_control_request debug");
*buf = (uint8_t *)hid_report_descriptor_debug;
*len = sizeof(hid_report_descriptor_debug);
} else {
debugLog(0, "", "hid_control_request trezor");
*buf = (uint8_t *)hid_report_descriptor;
*len = sizeof(hid_report_descriptor);
}
return 1;
#else
debugLog(0, "", "hid_control_request trezor");
*buf = (uint8_t *)hid_report_descriptor;
*len = sizeof(hid_report_descriptor);
#endif
return 1;
}
@ -241,6 +337,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(tiny, (const U2FHID_FRAME *)buf);
}
#if DEBUG_LINK
static void hid_debug_rx_callback(usbd_device *dev, uint8_t ep)
{
@ -262,6 +368,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);
@ -293,6 +401,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();

@ -23,6 +23,10 @@ const uint8_t bmp_logo48_data[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x
const uint8_t bmp_logo48_empty_data[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x01, 0x81, 0x80, 0x00, 0x00, 0x06, 0x00, 0x60, 0x00, 0x00, 0x08, 0x00, 0x10, 0x00, 0x00, 0x10, 0x7e, 0x08, 0x00, 0x00, 0x21, 0x81, 0x84, 0x00, 0x00, 0x22, 0x00, 0x44, 0x00, 0x00, 0x44, 0x00, 0x22, 0x00, 0x00, 0x48, 0x00, 0x12, 0x00, 0x00, 0x88, 0x00, 0x11, 0x00, 0x00, 0x90, 0x00, 0x09, 0x00, 0x00, 0x90, 0x00, 0x09, 0x00, 0x00, 0x90, 0x00, 0x09, 0x00, 0x00, 0x90, 0x00, 0x09, 0x00, 0x00, 0x90, 0x00, 0x09, 0x00, 0x00, 0x90, 0x00, 0x09, 0x00, 0x03, 0x9f, 0xff, 0xf9, 0xc0, 0x04, 0x00, 0x00, 0x00, 0x20, 0x08, 0x00, 0x00, 0x00, 0x10, 0x08, 0x00, 0x00, 0x00, 0x10, 0x08, 0x3f, 0xff, 0xfc, 0x10, 0x08, 0x40, 0x00, 0x02, 0x10, 0x08, 0x80, 0x00, 0x01, 0x10, 0x08, 0x80, 0x00, 0x01, 0x10, 0x08, 0x80, 0x00, 0x01, 0x10, 0x08, 0x80, 0x00, 0x01, 0x10, 0x08, 0x80, 0x00, 0x01, 0x10, 0x08, 0x80, 0x00, 0x01, 0x10, 0x08, 0x80, 0x00, 0x01, 0x10, 0x08, 0x80, 0x00, 0x01, 0x10, 0x08, 0x80, 0x00, 0x01, 0x10, 0x08, 0x80, 0x00, 0x01, 0x10, 0x08, 0x80, 0x00, 0x01, 0x10, 0x08, 0x80, 0x00, 0x01, 0x10, 0x08, 0x80, 0x00, 0x01, 0x10, 0x08, 0x80, 0x00, 0x01, 0x10, 0x08, 0x80, 0x00, 0x01, 0x10, 0x08, 0x80, 0x00, 0x01, 0x10, 0x08, 0x60, 0x00, 0x06, 0x10, 0x08, 0x1c, 0x00, 0x38, 0x10, 0x08, 0x03, 0x81, 0xc0, 0x10, 0x07, 0x00, 0x7e, 0x00, 0xe0, 0x00, 0xe0, 0x00, 0x07, 0x00, 0x00, 0x1c, 0x00, 0x38, 0x00, 0x00, 0x03, 0x81, 0xc0, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, };
const uint8_t bmp_logo64_data[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xfc, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0x80, 0x00, 0x00, 0x07, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x3f, 0xf0, 0x1f, 0xf8, 0x00, 0x00, 0x3f, 0xc0, 0x07, 0xf8, 0x00, 0x00, 0x7f, 0x80, 0x03, 0xfc, 0x00, 0x00, 0x7f, 0x00, 0x01, 0xfc, 0x00, 0x00, 0x7e, 0x00, 0x00, 0xfc, 0x00, 0x00, 0xfe, 0x00, 0x00, 0xfe, 0x00, 0x00, 0xfe, 0x00, 0x00, 0xfe, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x7e, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x7e, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x7e, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x7e, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x7e, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x7e, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x7e, 0x00, 0x00, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x1f, 0xff, 0x80, 0x03, 0xff, 0xf0, 0x1f, 0xc0, 0x00, 0x00, 0x07, 0xf0, 0x1f, 0x80, 0x00, 0x00, 0x03, 0xf0, 0x1f, 0x80, 0x00, 0x00, 0x03, 0xf0, 0x1f, 0x80, 0x00, 0x00, 0x03, 0xf0, 0x1f, 0x80, 0x00, 0x00, 0x03, 0xf0, 0x1f, 0x80, 0x00, 0x00, 0x03, 0xf0, 0x1f, 0x80, 0x00, 0x00, 0x03, 0xf0, 0x1f, 0x80, 0x00, 0x00, 0x03, 0xf0, 0x1f, 0x80, 0x00, 0x00, 0x03, 0xf0, 0x1f, 0x80, 0x00, 0x00, 0x03, 0xf0, 0x1f, 0x80, 0x00, 0x00, 0x03, 0xf0, 0x1f, 0x80, 0x00, 0x00, 0x03, 0xf0, 0x1f, 0x80, 0x00, 0x00, 0x03, 0xf0, 0x1f, 0x80, 0x00, 0x00, 0x03, 0xf0, 0x1f, 0x80, 0x00, 0x00, 0x03, 0xf0, 0x1f, 0x80, 0x00, 0x00, 0x03, 0xf0, 0x1f, 0x80, 0x00, 0x00, 0x03, 0xf0, 0x1f, 0x80, 0x00, 0x00, 0x03, 0xf0, 0x1f, 0x80, 0x00, 0x00, 0x03, 0xf0, 0x1f, 0x80, 0x00, 0x00, 0x03, 0xf0, 0x1f, 0x80, 0x00, 0x00, 0x03, 0xf0, 0x1f, 0xe0, 0x00, 0x00, 0x0f, 0xf0, 0x1f, 0xfc, 0x00, 0x00, 0x7f, 0xf0, 0x1f, 0xff, 0x00, 0x01, 0xff, 0xf0, 0x1f, 0xff, 0xf0, 0x1f, 0xff, 0xf0, 0x1f, 0xff, 0xfc, 0x7f, 0xff, 0xf0, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x01, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x01, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x07, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, };
const uint8_t bmp_logo64_empty_data[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x70, 0x1c, 0x00, 0x00, 0x00, 0x01, 0x80, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x80, 0x00, 0x00, 0x04, 0x00, 0x00, 0x40, 0x00, 0x00, 0x08, 0x00, 0x00, 0x20, 0x00, 0x00, 0x10, 0x0f, 0xe0, 0x10, 0x00, 0x00, 0x20, 0x30, 0x18, 0x08, 0x00, 0x00, 0x20, 0x40, 0x04, 0x08, 0x00, 0x00, 0x40, 0x80, 0x02, 0x04, 0x00, 0x00, 0x41, 0x00, 0x01, 0x04, 0x00, 0x00, 0x42, 0x00, 0x00, 0x84, 0x00, 0x00, 0x82, 0x00, 0x00, 0x82, 0x00, 0x00, 0x82, 0x00, 0x00, 0x82, 0x00, 0x00, 0x84, 0x00, 0x00, 0x42, 0x00, 0x00, 0x84, 0x00, 0x00, 0x42, 0x00, 0x00, 0x84, 0x00, 0x00, 0x42, 0x00, 0x00, 0x84, 0x00, 0x00, 0x42, 0x00, 0x00, 0x84, 0x00, 0x00, 0x42, 0x00, 0x00, 0x84, 0x00, 0x00, 0x42, 0x00, 0x00, 0x84, 0x00, 0x00, 0x42, 0x00, 0x00, 0x87, 0xff, 0xff, 0xc2, 0x00, 0x03, 0x80, 0x00, 0x00, 0x03, 0x80, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x70, 0x10, 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x00, 0x7f, 0xfc, 0x00, 0x10, 0x10, 0x3f, 0x80, 0x03, 0xf8, 0x10, 0x10, 0x40, 0x00, 0x00, 0x04, 0x10, 0x10, 0x80, 0x00, 0x00, 0x02, 0x10, 0x10, 0x80, 0x00, 0x00, 0x02, 0x10, 0x10, 0x80, 0x00, 0x00, 0x02, 0x10, 0x10, 0x80, 0x00, 0x00, 0x02, 0x10, 0x10, 0x80, 0x00, 0x00, 0x02, 0x10, 0x10, 0x80, 0x00, 0x00, 0x02, 0x10, 0x10, 0x80, 0x00, 0x00, 0x02, 0x10, 0x10, 0x80, 0x00, 0x00, 0x02, 0x10, 0x10, 0x80, 0x00, 0x00, 0x02, 0x10, 0x10, 0x80, 0x00, 0x00, 0x02, 0x10, 0x10, 0x80, 0x00, 0x00, 0x02, 0x10, 0x10, 0x80, 0x00, 0x00, 0x02, 0x10, 0x10, 0x80, 0x00, 0x00, 0x02, 0x10, 0x10, 0x80, 0x00, 0x00, 0x02, 0x10, 0x10, 0x80, 0x00, 0x00, 0x02, 0x10, 0x10, 0x80, 0x00, 0x00, 0x02, 0x10, 0x10, 0x80, 0x00, 0x00, 0x02, 0x10, 0x10, 0x80, 0x00, 0x00, 0x02, 0x10, 0x10, 0x80, 0x00, 0x00, 0x02, 0x10, 0x10, 0x80, 0x00, 0x00, 0x02, 0x10, 0x10, 0x60, 0x00, 0x00, 0x0c, 0x10, 0x10, 0x1c, 0x00, 0x00, 0x70, 0x10, 0x10, 0x03, 0x00, 0x01, 0x80, 0x10, 0x10, 0x00, 0xf0, 0x1e, 0x00, 0x10, 0x10, 0x00, 0x0c, 0x60, 0x00, 0x10, 0x0e, 0x00, 0x03, 0x80, 0x00, 0xe0, 0x01, 0xc0, 0x00, 0x00, 0x07, 0x00, 0x00, 0x30, 0x00, 0x00, 0x18, 0x00, 0x00, 0x0e, 0x00, 0x00, 0xe0, 0x00, 0x00, 0x01, 0xc0, 0x07, 0x00, 0x00, 0x00, 0x00, 0x38, 0x38, 0x00, 0x00, 0x00, 0x00, 0x07, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, };
const uint8_t bmp_u2f_dropbox_data[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x03, 0x00, 0x01, 0xf0, 0x0f, 0x80, 0x07, 0xf8, 0x1f, 0xe0, 0x0f, 0xfc, 0x3f, 0xf8, 0x3f, 0xfe, 0x7f, 0xfc, 0x7f, 0xfe, 0x7f, 0xff, 0x7f, 0xfc, 0x1f, 0xfe, 0x3f, 0xf0, 0x0f, 0xfc, 0x1f, 0xe0, 0x03, 0xf8, 0x07, 0x80, 0x00, 0xe0, 0x03, 0x00, 0x00, 0x40, 0x03, 0x00, 0x00, 0x60, 0x07, 0x80, 0x01, 0xf0, 0x1f, 0xe0, 0x03, 0xf8, 0x3f, 0xf0, 0x0f, 0xfc, 0x7f, 0xfc, 0x3f, 0xfe, 0x7f, 0xfe, 0x7f, 0xfe, 0x1f, 0xfe, 0x7f, 0xfc, 0x0f, 0xfc, 0x3f, 0xf0, 0x03, 0xf9, 0x9f, 0xc0, 0x00, 0xe3, 0xc7, 0x80, 0x00, 0x47, 0xe2, 0x00, 0x03, 0x1f, 0xf8, 0x40, 0x03, 0xff, 0xfd, 0xc0, 0x01, 0xff, 0xff, 0x80, 0x00, 0x7f, 0xff, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x0f, 0xf8, 0x00, 0x00, 0x07, 0xe0, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, };
const uint8_t bmp_u2f_github_data[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x78, 0x1f, 0x80, 0x01, 0xf8, 0x1f, 0xe0, 0x03, 0xf8, 0x1f, 0xff, 0xff, 0xf8, 0x1f, 0xff, 0xff, 0xf8, 0x1f, 0xff, 0xff, 0xf8, 0x1f, 0xff, 0xff, 0xf8, 0x3f, 0xff, 0xff, 0xfc, 0x3f, 0xff, 0xff, 0xfc, 0x7f, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03, 0xc0, 0xff, 0xfc, 0x00, 0x00, 0x3f, 0xf8, 0x00, 0x00, 0x1f, 0xf8, 0x20, 0x04, 0x1f, 0xf0, 0x70, 0x0e, 0x0f, 0x70, 0xf0, 0x0f, 0x0e, 0x70, 0xf8, 0x1f, 0x0e, 0x70, 0x70, 0x0e, 0x0e, 0x30, 0x70, 0x0e, 0x0c, 0x38, 0x00, 0x00, 0x1c, 0x1c, 0x00, 0x00, 0x38, 0x0e, 0x00, 0x00, 0x70, 0x07, 0x80, 0x01, 0xe0, 0x01, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, };
const uint8_t bmp_u2f_google_data[] = { 0x00, 0x0f, 0xf0, 0x00, 0x00, 0x7f, 0xfe, 0x00, 0x01, 0xff, 0xff, 0x80, 0x03, 0xff, 0xff, 0xc0, 0x07, 0xf0, 0x0f, 0xe0, 0x0f, 0xc0, 0x03, 0xf0, 0x1f, 0x00, 0x01, 0xf8, 0x3e, 0x00, 0x00, 0xfc, 0x3c, 0x00, 0x01, 0xfc, 0x7c, 0x0f, 0xf3, 0xfe, 0x78, 0x1f, 0xff, 0xfe, 0x78, 0x3f, 0xff, 0xfe, 0xf0, 0x3f, 0xff, 0xff, 0xf0, 0x7f, 0xff, 0xff, 0xf0, 0x7f, 0x00, 0x1f, 0xf0, 0x7f, 0x00, 0x1f, 0xf0, 0x7f, 0x00, 0x1f, 0xf0, 0x7f, 0x00, 0x1f, 0xf0, 0x7f, 0xfe, 0x1f, 0xf0, 0x3f, 0xfe, 0x1f, 0x78, 0x3f, 0xfc, 0x1e, 0x78, 0x1f, 0xf8, 0x3e, 0x7c, 0x07, 0xf0, 0x3e, 0x3c, 0x00, 0x00, 0x7c, 0x3e, 0x00, 0x00, 0xfc, 0x1f, 0x00, 0x00, 0xf8, 0x0f, 0xc0, 0x03, 0xf0, 0x07, 0xf0, 0x0f, 0xe0, 0x03, 0xff, 0xff, 0xc0, 0x01, 0xff, 0xff, 0x80, 0x00, 0x7f, 0xfe, 0x00, 0x00, 0x0f, 0xf0, 0x00, };
const uint8_t bmp_u2f_slushpool_data[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xef, 0x00, 0x00, 0x00, 0xef, 0x00, 0x00, 0x01, 0xee, 0x00, 0x01, 0xff, 0xff, 0x00, 0x01, 0xff, 0xff, 0xc0, 0x01, 0xff, 0xff, 0xe0, 0x03, 0xff, 0xff, 0xf0, 0x03, 0xff, 0xff, 0xf8, 0x00, 0xfe, 0x0f, 0xf8, 0x00, 0xfe, 0x03, 0xf8, 0x00, 0xfc, 0x03, 0xf8, 0x00, 0xfc, 0x03, 0xf8, 0x01, 0xfc, 0x03, 0xf8, 0x01, 0xfc, 0x03, 0xf0, 0x01, 0xfc, 0x07, 0xf0, 0x01, 0xfc, 0x1f, 0xf0, 0x0f, 0xff, 0xff, 0xe0, 0x0f, 0xff, 0xff, 0xe0, 0x0f, 0xff, 0xff, 0xc0, 0x0f, 0xff, 0xff, 0x00, 0x0f, 0xff, 0xfc, 0x00, 0x03, 0xf0, 0x00, 0x00, 0x07, 0xf0, 0x00, 0x00, 0x07, 0xf0, 0x00, 0x00, 0x07, 0xf0, 0x00, 0x00, 0x07, 0xe0, 0x00, 0x00, 0x07, 0xe0, 0x00, 0x00, 0x0f, 0xe0, 0x00, 0x00, 0x0f, 0xe0, 0x00, 0x00, 0x0f, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, };
const BITMAP bmp_digit0 = {16, 16, bmp_digit0_data};
const BITMAP bmp_digit1 = {16, 16, bmp_digit1_data};
@ -47,3 +51,7 @@ const BITMAP bmp_logo48 = {40, 48, bmp_logo48_data};
const BITMAP bmp_logo48_empty = {40, 48, bmp_logo48_empty_data};
const BITMAP bmp_logo64 = {48, 64, bmp_logo64_data};
const BITMAP bmp_logo64_empty = {48, 64, bmp_logo64_empty_data};
const BITMAP bmp_u2f_dropbox = {32, 32, bmp_u2f_dropbox_data};
const BITMAP bmp_u2f_github = {32, 32, bmp_u2f_github_data};
const BITMAP bmp_u2f_google = {32, 32, bmp_u2f_google_data};
const BITMAP bmp_u2f_slushpool = {32, 32, bmp_u2f_slushpool_data};

@ -31,5 +31,9 @@ extern const BITMAP bmp_logo48;
extern const BITMAP bmp_logo48_empty;
extern const BITMAP bmp_logo64;
extern const BITMAP bmp_logo64_empty;
extern const BITMAP bmp_u2f_dropbox;
extern const BITMAP bmp_u2f_github;
extern const BITMAP bmp_u2f_google;
extern const BITMAP bmp_u2f_slushpool;
#endif

Binary file not shown.

After

Width:  |  Height:  |  Size: 263 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 240 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 B

@ -22,33 +22,13 @@
#include "layout.h"
#include "oled.h"
void layoutDialog(LayoutDialogIcon icon, const char *btnNo, const char *btnYes, const char *desc, const char *line1, const char *line2, const char *line3, const char *line4, const char *line5, const char *line6)
void layoutDialog(const BITMAP *icon, const char *btnNo, const char *btnYes, const char *desc, const char *line1, const char *line2, const char *line3, const char *line4, const char *line5, const char *line6)
{
int left = 0;
oledClear();
switch (icon) {
case DIALOG_NOICON:
break;
case DIALOG_ICON_ERROR:
oledDrawBitmap(0, 0, &bmp_icon_error);
left = 20;
break;
case DIALOG_ICON_INFO:
oledDrawBitmap(0, 0, &bmp_icon_info);
left = 20;
break;
case DIALOG_ICON_QUESTION:
oledDrawBitmap(0, 0, &bmp_icon_question);
left = 20;
break;
case DIALOG_ICON_WARNING:
oledDrawBitmap(0, 0, &bmp_icon_warning);
left = 20;
break;
case DIALOG_ICON_OK:
oledDrawBitmap(0, 0, &bmp_icon_ok);
left = 20;
break;
if (icon) {
oledDrawBitmap(0, 0, icon);
left = icon->width + 4;
}
if (line1) oledDrawString(left, 0 * 9, line1);
if (line2) oledDrawString(left, 1 * 9, line2);

@ -22,17 +22,9 @@
#include <stdlib.h>
#include <stdbool.h>
#include "bitmaps.h"
typedef enum {
DIALOG_NOICON = 0,
DIALOG_ICON_ERROR,
DIALOG_ICON_INFO,
DIALOG_ICON_QUESTION,
DIALOG_ICON_WARNING,
DIALOG_ICON_OK,
} LayoutDialogIcon;
void layoutDialog(LayoutDialogIcon icon, const char *btnNo, const char *btnYes, const char *desc, const char *line1, const char *line2, const char *line3, const char *line4, const char *line5, const char *line6);
void layoutDialog(const BITMAP *icon, const char *btnNo, const char *btnYes, const char *desc, const char *line1, const char *line2, const char *line3, const char *line4, const char *line5, const char *line6);
void layoutProgressUpdate(bool refresh);
void layoutProgress(const char *desc, int permil);

@ -1 +1 @@
Subproject commit e6295a33cda4f0541311c9098569531c2d84220b
Subproject commit 70d8ac8f85f16a0a16019749402f26dcc635d841
Loading…
Cancel
Save