diff --git a/.gitignore b/.gitignore index 3c2f91f89..e0baf9ab7 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,3 @@ *.list *.srec *.log -bootloader diff --git a/bootloader/.gitignore b/bootloader/.gitignore new file mode 100644 index 000000000..e0baf9ab7 --- /dev/null +++ b/bootloader/.gitignore @@ -0,0 +1,9 @@ +*.o +*.a +*.d +*.bin +*.elf +*.hex +*.list +*.srec +*.log diff --git a/bootloader/Makefile b/bootloader/Makefile new file mode 100644 index 000000000..fd8703d39 --- /dev/null +++ b/bootloader/Makefile @@ -0,0 +1,23 @@ +NAME = bootloader + +MCU_DIR=$(subst /bootloader,,$(PWD)) + +OBJS += bootloader.o +OBJS += signatures.o +OBJS += usb.o + +OBJS += $(MCU_DIR)/trezor-crypto/bignum.small.o +OBJS += $(MCU_DIR)/trezor-crypto/ecdsa.small.o +OBJS += $(MCU_DIR)/trezor-crypto/hmac.o +OBJS += $(MCU_DIR)/trezor-crypto/ripemd160.o +OBJS += $(MCU_DIR)/trezor-crypto/secp256k1.small.o +OBJS += $(MCU_DIR)/trezor-crypto/sha2.o + +CFLAGS += -DUSE_PRECOMPUTED_IV=0 +CFLAGS += -DUSE_PRECOMPUTED_CP=0 +CFLAGS += -DUSE_PUBKEY_VALIDATE=0 + +include $(MCU_DIR)/Makefile.include + +align: + ./firmware_align.py $(NAME).bin diff --git a/bootloader/bootloader.c b/bootloader/bootloader.c new file mode 100644 index 000000000..57bf5f974 --- /dev/null +++ b/bootloader/bootloader.c @@ -0,0 +1,132 @@ +/* + * This file is part of the TREZOR project. + * + * Copyright (C) 2014 Pavol Rusnak + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#include + +#include +#include +#include + +#include "bootloader.h" +#include "buttons.h" +#include "setup.h" +#include "usb.h" +#include "oled.h" +#include "util.h" +#include "signatures.h" +#include "layout.h" +#include "serialno.h" + +#ifdef APPVER +#error Bootloader cannot be used in app mode +#endif + +void show_unofficial_warning(void) +{ + layoutDialog(DIALOG_ICON_WARNING, "Abort", "I'll take the risk", NULL, "WARNING!", NULL, "Unofficial firmware", "detected.", NULL, NULL); + + do { + delay(100000); + buttonUpdate(); + } while (!button.YesUp && !button.NoUp); + + if (button.YesUp) { + return; // yes button was pressed -> return + } + + layoutDialog(DIALOG_ICON_ERROR, NULL, NULL, NULL, "Unofficial firmware", "aborted.", NULL, "Unplug your TREZOR", "and see our support", "page at mytrezor.com"); + system_halt(); +} + +void load_app(void) +{ + // jump to app + SCB_VTOR = FLASH_APP_START; // & 0xFFFF; + asm volatile("msr msp, %0"::"g" (*(volatile uint32_t *)FLASH_APP_START)); + (*(void (**)())(FLASH_APP_START + 4))(); +} + +void bootloader_loop(void) +{ + static char serial[25]; + + fill_serialno_fixed(serial); + + oledDrawBitmap(0, 0, &bmp_logo64); + oledDrawString(52, 0, "TREZOR"); + + oledDrawString(52, 20, "Serial No."); + oledDrawString(52, 40, serial + 12); // second part of serial + serial[12] = 0; + oledDrawString(52, 30, serial); // first part of serial + + oledDrawStringRight(OLED_WIDTH - 1, OLED_HEIGHT - 8, "BLv" VERSTR(VERSION_MAJOR) "." VERSTR(VERSION_MINOR) "." VERSTR(VERSION_PATCH)); + + oledRefresh(); + + usbInit(); + usbLoop(); +} + +void check_firmware_sanity(void) +{ + int broken = 0; + if (memcmp((void *)FLASH_META_MAGIC, "TRZR", 4)) { // magic does not match + broken++; + } + if (*((uint32_t *)FLASH_META_CODELEN) < 4096) { // firmware reports smaller size than 4kB + broken++; + } + if (*((uint32_t *)FLASH_META_CODELEN) > FLASH_TOTAL_SIZE - (FLASH_APP_START - FLASH_ORIGIN)) { // firmware reports bigger size than flash size + 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"); + system_halt(); + } +} + +int main(void) +{ + setup(); + memory_protect(); + oledInit(); + + // at least one button is unpressed + uint16_t state = gpio_port_read(BTN_PORT); + if ((state & BTN_PIN_YES) == BTN_PIN_YES || (state & BTN_PIN_NO) == BTN_PIN_NO) { + + check_firmware_sanity(); + + oledClear(); + oledDrawBitmap(40, 0, &bmp_logo64_empty); + oledRefresh(); + + if (!signatures_ok()) { + show_unofficial_warning(); + } + + load_app(); + + } + + bootloader_loop(); + + return 0; +} diff --git a/bootloader/bootloader.h b/bootloader/bootloader.h new file mode 100644 index 000000000..3546d150d --- /dev/null +++ b/bootloader/bootloader.h @@ -0,0 +1,36 @@ +/* + * This file is part of the TREZOR project. + * + * Copyright (C) 2014 Pavol Rusnak + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#ifndef __BOOTLOADER_H__ +#define __BOOTLOADER_H__ + +#define VERSION_MAJOR 1 +#define VERSION_MINOR 2 +#define VERSION_PATCH 5 + +#define STR(X) #X +#define VERSTR(X) STR(X) + +#define VERSION_MAJOR_CHAR "\x01" +#define VERSION_MINOR_CHAR "\x02" +#define VERSION_PATCH_CHAR "\x05" + +#include "memory.h" + +#endif diff --git a/bootloader/combine/prepare.py b/bootloader/combine/prepare.py new file mode 100755 index 000000000..491c3000f --- /dev/null +++ b/bootloader/combine/prepare.py @@ -0,0 +1,10 @@ +#!/usr/bin/python +bl = open('bl.bin').read() +fw = open('fw.bin').read() +combined = bl + fw[:256] + (32768-256)*'\x00' + fw[256:] + +open('combined.bin', 'w').write(combined) + +print 'bootloader : %d bytes' % len(bl) +print 'firmware : %d bytes' % len(fw) +print 'combined : %d bytes' % len(combined) diff --git a/bootloader/combine/write.sh b/bootloader/combine/write.sh new file mode 100755 index 000000000..6854ee600 --- /dev/null +++ b/bootloader/combine/write.sh @@ -0,0 +1,2 @@ +#!/bin/bash +st-flash write combined.bin 0x8000000 diff --git a/bootloader/firmware_align.py b/bootloader/firmware_align.py new file mode 100755 index 000000000..6a2788c9d --- /dev/null +++ b/bootloader/firmware_align.py @@ -0,0 +1,11 @@ +#!/usr/bin/python +import sys +import os + +fn = sys.argv[1] +fs = os.stat(fn).st_size +if fs > 32768: + raise Exception('bootloader has to be smaller than 32768 bytes') +with open(fn, 'ab') as f: + f.write(os.urandom(32768 - fs)) + f.close() diff --git a/bootloader/firmware_sign.py b/bootloader/firmware_sign.py new file mode 100755 index 000000000..152f92ed5 --- /dev/null +++ b/bootloader/firmware_sign.py @@ -0,0 +1,189 @@ +#!/usr/bin/python +import argparse +import hashlib +import struct +import binascii +import ecdsa + +SLOTS = 3 + +pubkeys = { + 1: '04d571b7f148c5e4232c3814f777d8faeaf1a84216c78d569b71041ffc768a5b2d810fc3bb134dd026b57e65005275aedef43e155f48fc11a32ec790a93312bd58', + 2: '0463279c0c0866e50c05c799d32bd6bab0188b6de06536d1109d2ed9ce76cb335c490e55aee10cc901215132e853097d5432eda06b792073bd7740c94ce4516cb1', + 3: '0443aedbb6f7e71c563f8ed2ef64ec9981482519e7ef4f4aa98b27854e8c49126d4956d300ab45fdc34cd26bc8710de0a31dbdf6de7435fd0b492be70ac75fde58', + 4: '04877c39fd7c62237e038235e9c075dab261630f78eeb8edb92487159fffedfdf6046c6f8b881fa407c4a4ce6c28de0b19c1f4e29f1fcbc5a58ffd1432a3e0938a', + 5: '047384c51ae81add0a523adbb186c91b906ffb64c2c765802bf26dbd13bdf12c319e80c2213a136c8ee03d7874fd22b70d68e7dee469decfbbb510ee9a460cda45', +} + +INDEXES_START = len('TRZR') + struct.calcsize(' SLOTS: + raise Exception("Invalid slot") + + if is_pem: + print "Paste ECDSA private key in PEM format and press Enter:" + print "(blank private key removes the signature on given index)" + pem_key = '' + while True: + key = raw_input() + pem_key += key + "\n" + if key == '': + break + if pem_key.strip() == '': + # Blank key,let's remove existing signature from slot + return modify(data, slot, 0, '\x00' * 64) + key = ecdsa.SigningKey.from_pem(pem_key) + else: + print "Paste SECEXP (in hex) and press Enter:" + print "(blank private key removes the signature on given index)" + secexp = raw_input() + if secexp.strip() == '': + # Blank key,let's remove existing signature from slot + return modify(data, slot, 0, '\x00' * 64) + key = ecdsa.SigningKey.from_secret_exponent(secexp = int(secexp, 16), curve=ecdsa.curves.SECP256k1, hashfunc=hashlib.sha256) + + to_sign = prepare(data)[256:] # without meta + + # Locate proper index of current signing key + pubkey = '04' + binascii.hexlify(key.get_verifying_key().to_string()) + index = None + for i, pk in pubkeys.iteritems(): + if pk == pubkey: + index = i + break + + if index == None: + raise Exception("Unable to find private key index. Unknown private key?") + + signature = key.sign_deterministic(to_sign, hashfunc=hashlib.sha256) + + return modify(data, slot, index, signature) + +def main(args): + if args.generate: + key = ecdsa.SigningKey.generate( + curve=ecdsa.curves.SECP256k1, + hashfunc=hashlib.sha256) + + print "PRIVATE KEY (SECEXP):" + print binascii.hexlify(key.to_string()) + print + + print "PRIVATE KEY (PEM):" + print key.to_pem() + + print "PUBLIC KEY:" + print '04' + binascii.hexlify(key.get_verifying_key().to_string()) + return + + if not args.path: + raise Exception("-f/--file is required") + + data = open(args.path, 'rb').read() + assert len(data) % 4 == 0 + + if data[:4] != 'TRZR': + print "Metadata has been added..." + data = prepare(data) + + if data[:4] != 'TRZR': + raise Exception("Firmware header expected") + + print "Firmware size %d bytes" % len(data) + + check_signatures(data) + + if args.sign: + data = sign(data, args.pem) + check_signatures(data) + + fp = open(args.path, 'w') + fp.write(data) + fp.close() + +if __name__ == '__main__': + args = parse_args() + main(args) diff --git a/bootloader/firmware_sign_split.py b/bootloader/firmware_sign_split.py new file mode 100755 index 000000000..3d72b968b --- /dev/null +++ b/bootloader/firmware_sign_split.py @@ -0,0 +1,31 @@ +#!/usr/bin/python + +import hashlib +import os +import subprocess +import ecdsa +from binascii import hexlify, unhexlify + +print 'master secret:', +h = raw_input() +if h: + h = unhexlify(h) +else: + h = hashlib.sha256(os.urandom(1024)).digest() + +print +print 'master secret:', hexlify(h) +print + +for i in range(1, 6): + se = hashlib.sha256(h + chr(i)).hexdigest() + print 'seckey', i, ':', se + sk = ecdsa.SigningKey.from_secret_exponent(secexp = int(se, 16), curve=ecdsa.curves.SECP256k1, hashfunc=hashlib.sha256) + print 'pubkey', i, ':', '04' + hexlify(sk.get_verifying_key().to_string()) + print sk.to_pem() + +p = subprocess.Popen('ssss-split -t 3 -n 5 -x'.split(' '), stdin = subprocess.PIPE) +p.communicate(input = hexlify(h) + '\n') + +# to recover use: +# $ ssss-combine -t 3 -x diff --git a/bootloader/signatures.c b/bootloader/signatures.c new file mode 100644 index 000000000..db9601b22 --- /dev/null +++ b/bootloader/signatures.c @@ -0,0 +1,66 @@ +/* + * This file is part of the TREZOR project. + * + * Copyright (C) 2014 Pavol Rusnak + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#include + +#include "signatures.h" +#include "ecdsa.h" +#include "bootloader.h" + +#define PUBKEYS 5 + +static const uint8_t *pubkey[PUBKEYS] = { + (uint8_t *)"\x04\xd5\x71\xb7\xf1\x48\xc5\xe4\x23\x2c\x38\x14\xf7\x77\xd8\xfa\xea\xf1\xa8\x42\x16\xc7\x8d\x56\x9b\x71\x04\x1f\xfc\x76\x8a\x5b\x2d\x81\x0f\xc3\xbb\x13\x4d\xd0\x26\xb5\x7e\x65\x00\x52\x75\xae\xde\xf4\x3e\x15\x5f\x48\xfc\x11\xa3\x2e\xc7\x90\xa9\x33\x12\xbd\x58", + (uint8_t *)"\x04\x63\x27\x9c\x0c\x08\x66\xe5\x0c\x05\xc7\x99\xd3\x2b\xd6\xba\xb0\x18\x8b\x6d\xe0\x65\x36\xd1\x10\x9d\x2e\xd9\xce\x76\xcb\x33\x5c\x49\x0e\x55\xae\xe1\x0c\xc9\x01\x21\x51\x32\xe8\x53\x09\x7d\x54\x32\xed\xa0\x6b\x79\x20\x73\xbd\x77\x40\xc9\x4c\xe4\x51\x6c\xb1", + (uint8_t *)"\x04\x43\xae\xdb\xb6\xf7\xe7\x1c\x56\x3f\x8e\xd2\xef\x64\xec\x99\x81\x48\x25\x19\xe7\xef\x4f\x4a\xa9\x8b\x27\x85\x4e\x8c\x49\x12\x6d\x49\x56\xd3\x00\xab\x45\xfd\xc3\x4c\xd2\x6b\xc8\x71\x0d\xe0\xa3\x1d\xbd\xf6\xde\x74\x35\xfd\x0b\x49\x2b\xe7\x0a\xc7\x5f\xde\x58", + (uint8_t *)"\x04\x87\x7c\x39\xfd\x7c\x62\x23\x7e\x03\x82\x35\xe9\xc0\x75\xda\xb2\x61\x63\x0f\x78\xee\xb8\xed\xb9\x24\x87\x15\x9f\xff\xed\xfd\xf6\x04\x6c\x6f\x8b\x88\x1f\xa4\x07\xc4\xa4\xce\x6c\x28\xde\x0b\x19\xc1\xf4\xe2\x9f\x1f\xcb\xc5\xa5\x8f\xfd\x14\x32\xa3\xe0\x93\x8a", + (uint8_t *)"\x04\x73\x84\xc5\x1a\xe8\x1a\xdd\x0a\x52\x3a\xdb\xb1\x86\xc9\x1b\x90\x6f\xfb\x64\xc2\xc7\x65\x80\x2b\xf2\x6d\xbd\x13\xbd\xf1\x2c\x31\x9e\x80\xc2\x21\x3a\x13\x6c\x8e\xe0\x3d\x78\x74\xfd\x22\xb7\x0d\x68\xe7\xde\xe4\x69\xde\xcf\xbb\xb5\x10\xee\x9a\x46\x0c\xda\x45", +}; + +#define SIGNATURES 3 + +int signatures_ok(void) +{ + uint32_t codelen = *((uint32_t *)FLASH_META_CODELEN); + uint8_t sigindex1, sigindex2, sigindex3; + + sigindex1 = *((uint8_t *)FLASH_META_SIGINDEX1); + sigindex2 = *((uint8_t *)FLASH_META_SIGINDEX2); + sigindex3 = *((uint8_t *)FLASH_META_SIGINDEX3); + + if (sigindex1 < 1 || sigindex1 > PUBKEYS) return 0; // invalid index + if (sigindex2 < 1 || sigindex2 > PUBKEYS) return 0; // invalid index + if (sigindex3 < 1 || sigindex3 > PUBKEYS) return 0; // invalid index + + if (sigindex1 == sigindex2) return 0; // duplicate use + if (sigindex1 == sigindex3) return 0; // duplicate use + if (sigindex2 == sigindex3) return 0; // duplicate use + + if (ecdsa_verify(pubkey[sigindex1 - 1], (uint8_t *)FLASH_META_SIG1, (uint8_t *)FLASH_APP_START, codelen) != 0) { // failure + return 0; + } + if (ecdsa_verify(pubkey[sigindex2 - 1], (uint8_t *)FLASH_META_SIG2, (uint8_t *)FLASH_APP_START, codelen) != 0) { // failure + return 0; + } + if (ecdsa_verify(pubkey[sigindex3 - 1], (uint8_t *)FLASH_META_SIG3, (uint8_t *)FLASH_APP_START, codelen) != 0) { // failture + return 0; + } + + return 1; +} diff --git a/bootloader/signatures.h b/bootloader/signatures.h new file mode 100644 index 000000000..e081be352 --- /dev/null +++ b/bootloader/signatures.h @@ -0,0 +1,25 @@ +/* + * This file is part of the TREZOR project. + * + * Copyright (C) 2014 Pavol Rusnak + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#ifndef __SIGNATURES_H__ +#define __SIGNATURES_H__ + +int signatures_ok(void); + +#endif diff --git a/bootloader/usb.c b/bootloader/usb.c new file mode 100644 index 000000000..1e36c9522 --- /dev/null +++ b/bootloader/usb.c @@ -0,0 +1,518 @@ +/* + * This file is part of the TREZOR project. + * + * Copyright (C) 2014 Pavol Rusnak + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#include +#include +#include + +#include + +#include "buttons.h" +#include "bootloader.h" +#include "oled.h" +#include "rng.h" +#include "usb.h" +#include "serialno.h" +#include "layout.h" +#include "util.h" +#include "signatures.h" +#include "sha2.h" + +static const struct usb_device_descriptor dev_descr = { + .bLength = USB_DT_DEVICE_SIZE, + .bDescriptorType = USB_DT_DEVICE, + .bcdUSB = 0x0200, + .bDeviceClass = 0, + .bDeviceSubClass = 0, + .bDeviceProtocol = 0, + .bMaxPacketSize0 = 64, + .idVendor = 0x534c, + .idProduct = 0x0001, + .bcdDevice = 0x0100, + .iManufacturer = 1, + .iProduct = 2, + .iSerialNumber = 3, + .bNumConfigurations = 1, +}; + +/* got via usbhid-dump from CP2110 */ +static const uint8_t hid_report_descriptor[] = { + 0x06, 0x00, 0xFF, 0x09, 0x01, 0xA1, 0x01, 0x09, 0x01, 0x75, 0x08, 0x95, 0x40, 0x26, 0xFF, 0x00, + 0x15, 0x00, 0x85, 0x01, 0x95, 0x01, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x02, + 0x95, 0x02, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x03, 0x95, 0x03, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x04, 0x95, 0x04, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, + 0x91, 0x02, 0x85, 0x05, 0x95, 0x05, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x06, + 0x95, 0x06, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x07, 0x95, 0x07, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x08, 0x95, 0x08, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, + 0x91, 0x02, 0x85, 0x09, 0x95, 0x09, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x0A, + 0x95, 0x0A, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x0B, 0x95, 0x0B, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x0C, 0x95, 0x0C, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, + 0x91, 0x02, 0x85, 0x0D, 0x95, 0x0D, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x0E, + 0x95, 0x0E, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x0F, 0x95, 0x0F, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x10, 0x95, 0x10, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, + 0x91, 0x02, 0x85, 0x11, 0x95, 0x11, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x12, + 0x95, 0x12, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x13, 0x95, 0x13, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x14, 0x95, 0x14, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, + 0x91, 0x02, 0x85, 0x15, 0x95, 0x15, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x16, + 0x95, 0x16, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x17, 0x95, 0x17, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x18, 0x95, 0x18, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, + 0x91, 0x02, 0x85, 0x19, 0x95, 0x19, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x1A, + 0x95, 0x1A, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x1B, 0x95, 0x1B, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x1C, 0x95, 0x1C, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, + 0x91, 0x02, 0x85, 0x1D, 0x95, 0x1D, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x1E, + 0x95, 0x1E, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x1F, 0x95, 0x1F, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x20, 0x95, 0x20, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, + 0x91, 0x02, 0x85, 0x21, 0x95, 0x21, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x22, + 0x95, 0x22, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x23, 0x95, 0x23, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x24, 0x95, 0x24, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, + 0x91, 0x02, 0x85, 0x25, 0x95, 0x25, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x26, + 0x95, 0x26, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x27, 0x95, 0x27, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x28, 0x95, 0x28, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, + 0x91, 0x02, 0x85, 0x29, 0x95, 0x29, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x2A, + 0x95, 0x2A, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x2B, 0x95, 0x2B, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x2C, 0x95, 0x2C, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, + 0x91, 0x02, 0x85, 0x2D, 0x95, 0x2D, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x2E, + 0x95, 0x2E, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x2F, 0x95, 0x2F, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x30, 0x95, 0x30, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, + 0x91, 0x02, 0x85, 0x31, 0x95, 0x31, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x32, + 0x95, 0x32, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x33, 0x95, 0x33, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x34, 0x95, 0x34, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, + 0x91, 0x02, 0x85, 0x35, 0x95, 0x35, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x36, + 0x95, 0x36, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x37, 0x95, 0x37, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x38, 0x95, 0x38, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, + 0x91, 0x02, 0x85, 0x39, 0x95, 0x39, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x3A, + 0x95, 0x3A, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x3B, 0x95, 0x3B, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x3C, 0x95, 0x3C, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, + 0x91, 0x02, 0x85, 0x3D, 0x95, 0x3D, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x3E, + 0x95, 0x3E, 0x09, 0x01, 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x3F, 0x95, 0x3F, 0x09, 0x01, + 0x81, 0x02, 0x09, 0x01, 0x91, 0x02, 0x85, 0x40, 0x95, 0x01, 0x09, 0x01, 0xB1, 0x02, 0x85, 0x41, + 0x95, 0x01, 0x09, 0x01, 0xB1, 0x02, 0x85, 0x42, 0x95, 0x06, 0x09, 0x01, 0xB1, 0x02, 0x85, 0x43, + 0x95, 0x01, 0x09, 0x01, 0xB1, 0x02, 0x85, 0x44, 0x95, 0x02, 0x09, 0x01, 0xB1, 0x02, 0x85, 0x45, + 0x95, 0x04, 0x09, 0x01, 0xB1, 0x02, 0x85, 0x46, 0x95, 0x02, 0x09, 0x01, 0xB1, 0x02, 0x85, 0x47, + 0x95, 0x02, 0x09, 0x01, 0xB1, 0x02, 0x85, 0x50, 0x95, 0x08, 0x09, 0x01, 0xB1, 0x02, 0x85, 0x51, + 0x95, 0x01, 0x09, 0x01, 0xB1, 0x02, 0x85, 0x52, 0x95, 0x01, 0x09, 0x01, 0xB1, 0x02, 0x85, 0x60, + 0x95, 0x0A, 0x09, 0x01, 0xB1, 0x02, 0x85, 0x61, 0x95, 0x3F, 0x09, 0x01, 0xB1, 0x02, 0x85, 0x62, + 0x95, 0x3F, 0x09, 0x01, 0xB1, 0x02, 0x85, 0x63, 0x95, 0x3F, 0x09, 0x01, 0xB1, 0x02, 0x85, 0x64, + 0x95, 0x3F, 0x09, 0x01, 0xB1, 0x02, 0x85, 0x65, 0x95, 0x3E, 0x09, 0x01, 0xB1, 0x02, 0x85, 0x66, + 0x95, 0x13, 0x09, 0x01, 0xB1, 0x02, 0xC0, +}; + +static const struct { + struct usb_hid_descriptor hid_descriptor; + struct { + uint8_t bReportDescriptorType; + uint16_t wDescriptorLength; + } __attribute__((packed)) hid_report; +} __attribute__((packed)) hid_function = { + .hid_descriptor = { + .bLength = sizeof(hid_function), + .bDescriptorType = USB_DT_HID, + .bcdHID = 0x0111, + .bCountryCode = 0, + .bNumDescriptors = 1, + }, + .hid_report = { + .bReportDescriptorType = USB_DT_REPORT, + .wDescriptorLength = sizeof(hid_report_descriptor), + } +}; + +static const struct usb_endpoint_descriptor hid_endpoints[2] = {{ + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = 0x81, + .bmAttributes = USB_ENDPOINT_ATTR_INTERRUPT, + .wMaxPacketSize = 64, + .bInterval = 1, +}, { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = 0x02, + .bmAttributes = USB_ENDPOINT_ATTR_INTERRUPT, + .wMaxPacketSize = 64, + .bInterval = 1, +}}; + +static const struct usb_interface_descriptor hid_iface[] = {{ + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 0, + .bAlternateSetting = 0, + .bNumEndpoints = 2, + .bInterfaceClass = USB_CLASS_HID, + .bInterfaceSubClass = 0, + .bInterfaceProtocol = 0, + .iInterface = 0, + .endpoint = hid_endpoints, + .extra = &hid_function, + .extralen = sizeof(hid_function), +}}; + +static const struct usb_interface ifaces[] = {{ + .num_altsetting = 1, + .altsetting = hid_iface, +}}; + +static const struct usb_config_descriptor config = { + .bLength = USB_DT_CONFIGURATION_SIZE, + .bDescriptorType = USB_DT_CONFIGURATION, + .wTotalLength = 0, + .bNumInterfaces = 1, + .bConfigurationValue = 1, + .iConfiguration = 0, + .bmAttributes = 0x80, + .bMaxPower = 0x32, + .interface = ifaces, +}; + +static const char *usb_strings[] = { + "SatoshiLabs", + "TREZOR", + "", // empty serial +}; + +static int hid_control_request(usbd_device *dev, struct usb_setup_data *req, uint8_t **buf, uint16_t *len, + void (**complete)(usbd_device *, struct usb_setup_data *)) +{ + (void)complete; + (void)dev; + + if ((req->bmRequestType != 0x81) || + (req->bRequest != USB_REQ_GET_DESCRIPTOR) || + (req->wValue != 0x2200)) return 0; + + *buf = (uint8_t *)hid_report_descriptor; + *len = sizeof(hid_report_descriptor); + + return 1; +} + +enum { + STATE_READY, + STATE_OPEN, + STATE_FLASHSTART, + STATE_FLASHING, + STATE_CHECK, + STATE_END, +}; + +static uint32_t flash_pos = 0, flash_len = 0; +static char flash_state = STATE_READY; +static uint8_t flash_anim = 0; +static uint16_t msg_id = 0xFFFF; +static uint32_t msg_size = 0; + +static uint8_t meta_backup[FLASH_META_LEN]; + +static void send_msg_success(usbd_device *dev) +{ + // send response: Success message (id 2), payload len 0 + while ( usbd_ep_write_packet(dev, 0x81, + "?##" // header + "\x00\x02" // msg_id + "\x00\x00\x00\x00" // payload_len + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + , 64) != 64) {} +} + +static void send_msg_failure(usbd_device *dev) +{ + // send response: Failure message (id 3), payload len 2 + // code = 99 (Failure_FirmwareError) + while ( usbd_ep_write_packet(dev, 0x81, + "?##" // header + "\x00\x03" // msg_id + "\x00\x00\x00\x02" // payload_len + "\x08\x63" // data + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + , 64) != 64) {} +} + +static void send_msg_features(usbd_device *dev) +{ + // send response: Features message (id 17), payload len 27 + // vendor = "bitcointrezor.com" + // major_version = VERSION_MAJOR + // minor_version = VERSION_MINOR + // patch_version = VERSION_PATCH + // bootloader_mode = True + while ( usbd_ep_write_packet(dev, 0x81, + "?##" // header + "\x00\x11" // msg_id + "\x00\x00\x00\x1b" // payload_len + "\x0a\x11" "bitcointrezor.com\x10" VERSION_MAJOR_CHAR "\x18" VERSION_MINOR_CHAR " " VERSION_PATCH_CHAR "(\x01" // data + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + , 64) != 64) {} +} + +static void send_msg_buttonrequest_firmwarecheck(usbd_device *dev) +{ + // send response: ButtonRequest message (id 26), payload len 2 + // code = ButtonRequest_FirmwareCheck (9) + while ( usbd_ep_write_packet(dev, 0x81, + "?##" // header + "\x00\x1a" // msg_id + "\x00\x00\x00\x02" // payload_len + "\x08\x09" // data + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + , 64) != 64) {} +} + +static void hid_rx_callback(usbd_device *dev, uint8_t ep) +{ + (void)ep; + static uint8_t buf[64] __attribute__((aligned(4))); + uint8_t *p; + static uint8_t towrite[4] __attribute__((aligned(4))); + static int wi; + int i; + uint32_t *w; + static SHA256_CTX ctx; + + if ( usbd_ep_read_packet(dev, 0x02, buf, 64) != 64) return; + + if (flash_state == STATE_END) { + return; + } + + if (flash_state == STATE_READY || flash_state == STATE_OPEN || flash_state == STATE_FLASHSTART || flash_state == STATE_CHECK) { + if (buf[0] != '?' || buf[1] != '#' || buf[2] != '#') { // invalid start - discard + return; + } + // struct.unpack(">HL") => msg, size + msg_id = (buf[3] << 8) + buf[4]; + msg_size = (buf[5] << 24)+ (buf[6] << 16) + (buf[7] << 8) + buf[8]; + } + + if (flash_state == STATE_READY || flash_state == STATE_OPEN) { + if (msg_id == 0x0000) { // Initialize message (id 0) + send_msg_features(dev); + flash_state = STATE_OPEN; + return; + } + if (msg_id == 0x0001) { // Ping message (id 1) + send_msg_success(dev); + return; + } + } + + 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); + do { + delay(100000); + buttonUpdate(); + } while (!button.YesUp && !button.NoUp); + if (button.YesUp) { + layoutProgress("INSTALLING ... Please wait", 0, 0); + // backup metadata + memcpy(meta_backup, (void *)FLASH_META_START, FLASH_META_LEN); + flash_unlock(); + // erase metadata area + for (i = FLASH_META_SECTOR_FIRST; i <= FLASH_META_SECTOR_LAST; i++) { + flash_erase_sector(i, FLASH_CR_PROGRAM_X32); + } + // erase code area + for (i = FLASH_CODE_SECTOR_FIRST; i <= FLASH_CODE_SECTOR_LAST; i++) { + flash_erase_sector(i, FLASH_CR_PROGRAM_X32); + } + flash_lock(); + send_msg_success(dev); + flash_state = STATE_FLASHSTART; + return; + } + 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); + return; + } + return; + } + + if (flash_state == STATE_FLASHSTART) { + if (msg_id == 0x0007) { // FirmwareUpload message (id 7) + 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); + return; + } + // read payload length + p = buf + 10; + flash_len = readprotobufint(&p); + 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); + return; + } + sha256_Init(&ctx); + flash_state = STATE_FLASHING; + flash_pos = 0; + wi = 0; + flash_unlock(); + while (p < buf + 64) { + towrite[wi] = *p; + wi++; + if (wi == 4) { + w = (uint32_t *)towrite; + flash_program_word(FLASH_META_START + flash_pos, *w); + flash_pos += 4; + wi = 0; + } + p++; + } + flash_lock(); + return; + } + return; + } + + if (flash_state == STATE_FLASHING) { + 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); + return; + } + p = buf + 1; + layoutProgress("INSTALLING ... Please wait", 1000 * flash_pos / flash_len, flash_anim / 8); + flash_anim++; + flash_unlock(); + while (p < buf + 64 && flash_pos < flash_len) { + towrite[wi] = *p; + wi++; + if (wi == 4) { + w = (uint32_t *)towrite; + if (flash_pos < FLASH_META_DESC_LEN) { + flash_program_word(FLASH_META_START + flash_pos, *w); // the first 256 bytes of firmware is metadata descriptor + } else { + flash_program_word(FLASH_APP_START + (flash_pos - FLASH_META_DESC_LEN), *w); // the rest is code + sha256_Update(&ctx, towrite, 4); + } + flash_pos += 4; + wi = 0; + } + p++; + } + flash_lock(); + // flashing done + if (flash_pos == flash_len) { + flash_state = STATE_CHECK; + send_msg_buttonrequest_firmwarecheck(dev); + } + return; + } + + if (flash_state == STATE_CHECK) { + if (msg_id != 0x001B) { // ButtonAck message (id 27) + return; + } + char digest[64]; + sha256_End(&ctx, digest); + char str[4][17]; + strlcpy(str[0], digest, 17); + strlcpy(str[1], digest + 16, 17); + strlcpy(str[2], digest + 32, 17); + strlcpy(str[3], digest + 48, 17); + layoutDialog(DIALOG_ICON_QUESTION, "Abort", "Continue", "Compare fingerprints", str[0], str[1], str[2], str[3], NULL, NULL); + + do { + delay(100000); + buttonUpdate(); + } while (!button.YesUp && !button.NoUp); + + bool hash_check_ok = button.YesUp; + + layoutProgress("INSTALLING ... Please wait", 1000, 0); + uint8_t flags = *((uint8_t *)FLASH_META_FLAGS); + // check if to restore old storage area but only if signatures are ok + if ((flags & 0x01) && signatures_ok()) { + // copy new stuff + memcpy(meta_backup, (void *)FLASH_META_START, FLASH_META_DESC_LEN); + // replace "TRZR" in header with 0000 when hash not confirmed + if (!hash_check_ok) { + meta_backup[0] = 0; + meta_backup[1] = 0; + meta_backup[2] = 0; + meta_backup[3] = 0; + } + flash_unlock(); + // erase storage + for (i = FLASH_META_SECTOR_FIRST; i <= FLASH_META_SECTOR_LAST; i++) { + flash_erase_sector(i, FLASH_CR_PROGRAM_X32); + } + // copy it back + for (i = 0; i < FLASH_META_LEN / 4; i++) { + w = (uint32_t *)(meta_backup + i * 4); + flash_program_word(FLASH_META_START + i * 4, *w); + } + flash_lock(); + } else { + // replace "TRZR" in header with 0000 when hash not confirmed + if (!hash_check_ok) { + // no need to erase, because we are just erasing bits + flash_unlock(); + flash_program_word(FLASH_META_START, 0x00000000); + flash_lock(); + } + } + 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); + 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."); + send_msg_failure(dev); + } + return; + } + +} + +static void hid_set_config(usbd_device *dev, uint16_t wValue) +{ + (void)wValue; + + usbd_ep_setup(dev, 0x81, USB_ENDPOINT_ATTR_INTERRUPT, 64, 0); + usbd_ep_setup(dev, 0x02, USB_ENDPOINT_ATTR_INTERRUPT, 64, hid_rx_callback); + + usbd_register_control_callback( + dev, + USB_REQ_TYPE_STANDARD | USB_REQ_TYPE_INTERFACE, + USB_REQ_TYPE_TYPE | USB_REQ_TYPE_RECIPIENT, + hid_control_request + ); +} + +static usbd_device *usbd_dev; +static uint8_t usbd_control_buffer[128]; + +void usbInit(void) +{ + usbd_dev = usbd_init(&otgfs_usb_driver, &dev_descr, &config, usb_strings, 3, usbd_control_buffer, sizeof(usbd_control_buffer)); + usbd_register_set_config_callback(usbd_dev, hid_set_config); +} + +void usbLoop(void) +{ + for (;;) { + usbd_poll(usbd_dev); + } +} diff --git a/bootloader/usb.h b/bootloader/usb.h new file mode 100644 index 000000000..e054a488b --- /dev/null +++ b/bootloader/usb.h @@ -0,0 +1,26 @@ +/* + * This file is part of the TREZOR project. + * + * Copyright (C) 2014 Pavol Rusnak + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#ifndef __USB_H__ +#define __USB_H__ + +void usbInit(void); +void usbLoop(void); + +#endif