diff --git a/fido2/ctaphid.c b/fido2/ctaphid.c index 86ec845..f586764 100644 --- a/fido2/ctaphid.c +++ b/fido2/ctaphid.c @@ -30,6 +30,7 @@ #include "time.h" #include "util.h" #include "log.h" +#include "extensions.h" #include APP_CONFIG typedef enum @@ -682,6 +683,23 @@ uint8_t ctaphid_handle_packet(uint8_t * pkt_raw) printf1(TAG_HID,"CTAPHID_CANCEL\n"); is_busy = 0; break; +#if defined(IS_BOOTLOADER) + case CTAPHID_BOOT: + printf1(TAG_HID,"CTAPHID_BOOT\n"); + ctap_response_init(&ctap_resp); + u2f_set_writeback_buffer(&ctap_resp); + is_busy = bootloader_bridge(len, ctap_buffer); + + ctaphid_write_buffer_init(&wb); + wb.cid = cid; + wb.cmd = CTAPHID_BOOT; + wb.bcnt = (ctap_resp.length + 1); + ctaphid_write(&wb, &is_busy, 1); + ctaphid_write(&wb, ctap_resp.data, ctap_resp.length); + ctaphid_write(&wb, NULL, 0); + is_busy = 0; + break; +#endif default: printf2(TAG_ERR,"error, unimplemented HID cmd: %02x\r\n", buffer_cmd()); ctaphid_send_error(cid, CTAP1_ERR_INVALID_COMMAND); diff --git a/fido2/ctaphid.h b/fido2/ctaphid.h index f0e5bf5..4336062 100644 --- a/fido2/ctaphid.h +++ b/fido2/ctaphid.h @@ -38,6 +38,9 @@ #define CTAPHID_ERROR (TYPE_INIT | 0x3f) #define CTAPHID_KEEPALIVE (TYPE_INIT | 0x3b) +// Custom commands between 0x40-0x7f +#define CTAPHID_BOOT (TYPE_INIT | 0x50) + #define ERR_INVALID_CMD 0x01 #define ERR_INVALID_PAR 0x02 #define ERR_INVALID_SEQ 0x04 diff --git a/fido2/extensions/extensions.h b/fido2/extensions/extensions.h index 9ee4cc3..1fb6db0 100644 --- a/fido2/extensions/extensions.h +++ b/fido2/extensions/extensions.h @@ -25,6 +25,6 @@ int16_t extend_u2f(struct u2f_request_apdu* req, uint32_t len); -int bootloader_bridge(uint8_t klen, uint8_t * keyh); +int bootloader_bridge(int klen, uint8_t * keyh); #endif /* EXTENSIONS_H_ */ diff --git a/fido2/u2f.c b/fido2/u2f.c index 3a4f690..c040076 100644 --- a/fido2/u2f.c +++ b/fido2/u2f.c @@ -34,6 +34,7 @@ static int16_t u2f_authenticate(struct u2f_authenticate_request * req, uint8_t c int8_t u2f_response_writeback(const uint8_t * buf, uint16_t len); void u2f_reset_response(); + static CTAP_RESPONSE * _u2f_resp = NULL; void u2f_request(struct u2f_request_apdu* req, CTAP_RESPONSE * resp) @@ -43,7 +44,7 @@ void u2f_request(struct u2f_request_apdu* req, CTAP_RESPONSE * resp) uint32_t len = ((req->LC3) | ((uint32_t)req->LC2 << 8) | ((uint32_t)req->LC1 << 16)); uint8_t byte; - _u2f_resp = resp; + u2f_set_writeback_buffer(resp); if (req->cla != 0) { @@ -137,6 +138,10 @@ void u2f_reset_response() ctap_response_init(_u2f_resp); } +void u2f_set_writeback_buffer(CTAP_RESPONSE * resp) +{ + _u2f_resp = resp; +} static void dump_signature_der(uint8_t * sig) { diff --git a/fido2/u2f.h b/fido2/u2f.h index e2027d3..0d28b0c 100644 --- a/fido2/u2f.h +++ b/fido2/u2f.h @@ -116,6 +116,7 @@ void u2f_request(struct u2f_request_apdu* req, CTAP_RESPONSE * resp); int8_t u2f_response_writeback(const uint8_t * buf, uint16_t len); void u2f_reset_response(); +void u2f_set_writeback_buffer(CTAP_RESPONSE * resp); int16_t u2f_version(); diff --git a/targets/stm32l442/bootloader/bootloader.c b/targets/stm32l442/bootloader/bootloader.c index 435b25a..ca9610d 100644 --- a/targets/stm32l442/bootloader/bootloader.c +++ b/targets/stm32l442/bootloader/bootloader.c @@ -28,8 +28,9 @@ typedef struct { uint8_t op; uint8_t addr[3]; uint8_t tag[4]; - uint8_t len; - uint8_t payload[255 - 9]; + uint8_t lenh; + uint8_t lenl; + uint8_t payload[255 - 10]; } __attribute__((packed)) BootloaderReq; @@ -49,30 +50,29 @@ static void authorize_application() ptr = (uint32_t *)AUTH_WORD_ADDR; flash_write((uint32_t)ptr, (uint8_t *)&zero, 4); } + int is_authorized_to_boot() { uint32_t * auth = (uint32_t *)AUTH_WORD_ADDR; return *auth == 0; } -int bootloader_bridge(uint8_t klen, uint8_t * keyh) +int bootloader_bridge(int klen, uint8_t * keyh) { static int has_erased = 0; BootloaderReq * req = (BootloaderReq * )keyh; - uint8_t payload[256]; uint8_t hash[32]; uint8_t version = 1; + uint16_t len = (req->lenh << 8) | (req->lenl); - uint8_t * pubkey = (uint8_t*)"\x85\xaa\xce\xda\xd4\xb4\xd8\x0d\xf7\x0e\xe8\x91\x6d\x69\x8e\x00\x7a\x27\x40\x76\x93\x7a\x1d\x63\xb1\xcf\xe8\x22\xdd\x9f\xbc\x43\x3e\x34\x0a\x05\x9d\x8a\x9d\x72\xdc\xc2\x4b\x56\x9c\x64\x3d\xc1\x0d\x14\x64\x69\x52\x31\xd7\x54\xa3\xb6\x69\xa7\x6f\x6b\x81\x8d"; - const struct uECC_Curve_t * curve = NULL; - - if (req->len > 255-9) + if (len > klen-10) { + printf1(TAG_BOOT,"Invalid length %d / %d\r\n", len, klen-9); return CTAP1_ERR_INVALID_LENGTH; } - memset(payload, 0xff, sizeof(payload)); - memmove(payload, req->payload, req->len); + uint8_t * pubkey = (uint8_t*)"\x85\xaa\xce\xda\xd4\xb4\xd8\x0d\xf7\x0e\xe8\x91\x6d\x69\x8e\x00\x7a\x27\x40\x76\x93\x7a\x1d\x63\xb1\xcf\xe8\x22\xdd\x9f\xbc\x43\x3e\x34\x0a\x05\x9d\x8a\x9d\x72\xdc\xc2\x4b\x56\x9c\x64\x3d\xc1\x0d\x14\x64\x69\x52\x31\xd7\x54\xa3\xb6\x69\xa7\x6f\x6b\x81\x8d"; + const struct uECC_Curve_t * curve = NULL; uint32_t addr = ((*((uint32_t*)req->addr)) & 0xffffff) | 0x8000000; @@ -82,7 +82,7 @@ int bootloader_bridge(uint8_t klen, uint8_t * keyh) case BootWrite: printf1(TAG_BOOT, "BootWrite: %08lx\r\n",(uint32_t)ptr); if ((uint32_t)ptr < APPLICATION_START_ADDR || (uint32_t)ptr >= APPLICATION_END_ADDR - || ((uint32_t)ptr+req->len) > APPLICATION_END_ADDR) + || ((uint32_t)ptr+len) > APPLICATION_END_ADDR) { printf1(TAG_BOOT,"Bound exceeded [%08lx, %08lx]\r\n",APPLICATION_START_ADDR,APPLICATION_END_ADDR); return CTAP2_ERR_NOT_ALLOWED; @@ -99,11 +99,17 @@ int bootloader_bridge(uint8_t klen, uint8_t * keyh) exit(1); } - flash_write((uint32_t)ptr,payload, req->len); + flash_write((uint32_t)ptr,req->payload, len); break; case BootDone: printf1(TAG_BOOT, "BootDone: "); - dump_hex1(TAG_BOOT, payload, 32); +#ifndef SOLO_HACKER + if (len != 64) + { + printf1(TAG_BOOT,"Invalid length for signature\r\n"); + return CTAP1_ERR_INVALID_LENGTH; + } + dump_hex1(TAG_BOOT, req->payload, 32); ptr = (uint32_t *)APPLICATION_START_ADDR; crypto_sha256_init(); crypto_sha256_update((uint8_t*)ptr, APPLICATION_END_ADDR-APPLICATION_START_ADDR); @@ -113,11 +119,12 @@ int bootloader_bridge(uint8_t klen, uint8_t * keyh) if (! uECC_verify(pubkey, hash, 32, - payload, + req->payload, curve)) { return CTAP2_ERR_OPERATION_DENIED; } +#endif authorize_application(); REBOOT_FLAG = 1; break; diff --git a/targets/stm32l442/bootloader/main.c b/targets/stm32l442/bootloader/main.c index 83f670f..0a2e4ea 100644 --- a/targets/stm32l442/bootloader/main.c +++ b/targets/stm32l442/bootloader/main.c @@ -70,7 +70,8 @@ int main(int argc, char * argv[]) /*TAG_U2F|*/ // TAG_PARSE | // TAG_TIME| - // TAG_DUMP| + // TAG_DUMP| + // TAG_DUMP2| TAG_BOOT| TAG_EXT| TAG_GREEN| diff --git a/tools/ctap_test.py b/tools/ctap_test.py index 76f981f..727aea2 100644 --- a/tools/ctap_test.py +++ b/tools/ctap_test.py @@ -74,7 +74,6 @@ class Tester(): #cmd,resp = self.recv_raw() def send_data(self, cmd, data): - #print('<<', hexlify(data)) if type(data) != type(b''): data = struct.pack('%dB' % len(data), *[ord(x) for x in data]) with Timeout(1.0) as event: diff --git a/tools/programmer.py b/tools/programmer.py index 2407014..81204a7 100644 --- a/tools/programmer.py +++ b/tools/programmer.py @@ -1,7 +1,7 @@ # Programs solo using the Solo bootloader # Requires python-fido2, intelhex -import sys,os,time,struct +import sys,os,time,struct,argparse import array,struct,socket,json,base64 import tempfile from binascii import hexlify @@ -10,6 +10,7 @@ from fido2.hid import CtapHidDevice, CTAPHID from fido2.client import Fido2Client, ClientError from fido2.ctap import CtapError from fido2.ctap1 import CTAP1 +from fido2.utils import Timeout from intelhex import IntelHex @@ -22,12 +23,26 @@ class SoloBootloader: erase = 0x43 version = 0x44 + HIDCommand = 0x50 + TAG = b'\x8C\x27\x90\xf6' class Programmer(): def __init__(self,): self.origin = 'https://example.org' + self.exchange = self.exchange_hid + self.reboot = True + + def use_u2f(self,): + self.exchange = self.exchange_u2f + + def use_hid(self,): + self.exchange = self.exchange_hid + + def set_reboot(self,val): + """ option to reboot after programming """ + self.reboot = val def find_device(self,): dev = next(CtapHidDevice.list_devices(), None) @@ -36,16 +51,36 @@ class Programmer(): self.dev = dev self.ctap1 = CTAP1(dev) + if self.exchange == self.exchange_hid: + self.send_data_hid(CTAPHID.INIT, '\x11\x11\x11\x11\x11\x11\x11\x11') + @staticmethod def format_request(cmd,addr = 0,data = b'A'*16): arr = b'\x00'*9 addr = struct.pack('H', len(data)) return cmd + addr[:3] + SoloBootloader.TAG + length + data - def exchange(self,cmd,addr=0,data=b'A'*16): + def send_data_hid(self, cmd, data): + if type(data) != type(b''): + data = struct.pack('%dB' % len(data), *[ord(x) for x in data]) + with Timeout(1.0) as event: + return self.dev.call(cmd, data,event) + + def exchange_hid(self,cmd,addr=0,data=b'A'*16): + req = Programmer.format_request(cmd,addr,data) + + data = self.send_data_hid(SoloBootloader.HIDCommand, req) + + ret = data[0] + if ret != CtapError.ERR.SUCCESS: + raise RuntimeError('Device returned non-success code %02x' % ret) + + return data[1:] + + def exchange_u2f(self,cmd,addr=0,data=b'A'*16): appid = b'A'*32 chal = b'B'*32 @@ -77,21 +112,32 @@ class Programmer(): def program_file(self,name): data = json.loads(open(name,'r').read()) - fw = base64.b64decode(from_websafe(data['firmware']).encode()) - sig = base64.b64decode(from_websafe(data['signature']).encode()) + if name.lower().endswith('.json'): + fw = base64.b64decode(from_websafe(data['firmware']).encode()) + sig = base64.b64decode(from_websafe(data['signature']).encode()) + ih = IntelHex() + tmp = tempfile.NamedTemporaryFile(delete=False) + tmp.write(fw) + tmp.seek(0) + tmp.close() + ih.fromfile(tmp.name, format='hex') + else: + if not name.lower().endswith('.hex'): + print('Warning, assuming "%s" is an Intel Hex file.' % name) + sig = None + ih = IntelHex() + ih.fromfile(tmp.name, format='hex') - ih = IntelHex() - tmp = tempfile.NamedTemporaryFile(delete=False) - tmp.write(fw) - tmp.seek(0) - tmp.close() - ih.fromfile(tmp.name, format='hex') + if self.exchange == self.exchange_hid: + chunk = 2048 + else: + chunk = 240 - chunk = 240 seg = ih.segments()[0] size = seg[1] - seg[0] total = 0 t1 = time.time()*1000 + print('erasing...') for i in range(seg[0], seg[1], chunk): s = i e = min(i+chunk,seg[1]) @@ -100,23 +146,41 @@ class Programmer(): total += chunk progress = total/float(size)*100 sys.stdout.write('downloading %.2f%%...\r' % progress) - sys.stdout.write('downloading 100% \r\n') + sys.stdout.write('downloaded 100% \r\n') t2 = time.time()*1000 print('time: %.2f s' % ((t2-t1)/1000.0)) print('Verifying...') - self.verify_flash(sig) + if sig is not None: + self.verify_flash(sig) + else: + self.verify_flash(b'A'*64) if __name__ == '__main__': - if len(sys.argv) != 2: - print('usage: %s ' % sys.argv[0]) - sys.exit(1) + + parser = argparse.ArgumentParser() + parser.add_argument("", help = 'firmware file. Either a JSON or hex file. JSON file contains signature while hex does not.') + parser.add_argument("--use-hid", action="store_true", help = 'Programs using custom HID command (default). Quicker than using U2F authenticate which is what a browser has to use.') + parser.add_argument("--use-u2f", action="store_true", help = 'Programs using U2F authenticate. This is what a web application will use.') + parser.add_argument("--no-reset", action="store_true", help = 'Don\'t reset after writing firmware. Stay in bootloader mode.') + parser.add_argument("--reset-only", action="store_true", help = 'Don\'t write anything, try to boot without a signature.') + args = parser.parse_args() + print() p = Programmer() p.find_device() + if args.use_u2f: + p.use_u2f() + + if args.no_reset: + p.set_reboot(False) + print('version is ', p.version()) - p.program_file(sys.argv[1]) + if not args.reset_only: + p.program_file(args.__dict__['']) + else: + p.exchange(SoloBootloader.done,0,b'A'*64) diff --git a/web/js/wallet.js b/web/js/wallet.js index 63f3e3a..b1d2cd6 100644 --- a/web/js/wallet.js +++ b/web/js/wallet.js @@ -425,9 +425,10 @@ function formatBootRequest(cmd, addr, data) { array[6] = 0x90; array[7] = 0xf6; - array[8] = data.length & 0xff; + array[8] = 0; + array[9] = data.length & 0xff; - var offset = 9; + var offset = 10; var i; for (i = 0; i < data.length; i++){