From 4fe98ef5603a1950a40ab2076e556964245f0b92 Mon Sep 17 00:00:00 2001 From: Conor Patrick Date: Mon, 31 Dec 2018 13:11:23 -0500 Subject: [PATCH 1/8] rename to solotool --- tools/{programmer.py => solotool.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tools/{programmer.py => solotool.py} (100%) diff --git a/tools/programmer.py b/tools/solotool.py similarity index 100% rename from tools/programmer.py rename to tools/solotool.py From d726465b678ac310cbbd5faf30ede79ccca692d1 Mon Sep 17 00:00:00 2001 From: Conor Patrick Date: Mon, 31 Dec 2018 14:27:15 -0500 Subject: [PATCH 2/8] combine into solotool.py for simplicity --- tools/gen_keys.py | 51 -------- tools/requirements.txt | 2 + tools/serial_monitor.py | 62 ---------- tools/sign_firmware.py | 84 ------------- tools/solotool.py | 265 ++++++++++++++++++++++++++++++++++------ 5 files changed, 228 insertions(+), 236 deletions(-) delete mode 100644 tools/gen_keys.py delete mode 100644 tools/serial_monitor.py delete mode 100644 tools/sign_firmware.py diff --git a/tools/gen_keys.py b/tools/gen_keys.py deleted file mode 100644 index 62b1b29..0000000 --- a/tools/gen_keys.py +++ /dev/null @@ -1,51 +0,0 @@ -# -# Copyright (C) 2018 SoloKeys, Inc. -# -# This file is part of Solo. -# -# Solo is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Solo 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 General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Solo. If not, see -# -# This code is available under licenses for commercial use. -# Please contact SoloKeys for more information. -# -from ecdsa import SigningKey, NIST256p -from ecdsa.util import randrange_from_seed__trytryagain -import sys - -if len(sys.argv) > 1: - print('using input seed file ', sys.argv[1]) - rng = open(sys.argv[1],'rb').read() - secexp = randrange_from_seed__trytryagain(rng, NIST256p.order) - sk = SigningKey.from_secret_exponent(secexp,curve = NIST256p) -else: - sk = SigningKey.generate(curve = NIST256p) - - - -sk_name = 'signing_key.pem' -print('Signing key for signing device firmware: '+sk_name) -open(sk_name,'wb+').write(sk.to_pem()) - -vk = sk.get_verifying_key() - -print('Public key in various formats:') -print() -print([c for c in vk.to_string()]) -print() -print(''.join(['%02x'%c for c in vk.to_string()])) -print() -print('"\\x' + '\\x'.join(['%02x'%c for c in vk.to_string()]) + '"') -print() - - diff --git a/tools/requirements.txt b/tools/requirements.txt index ae093c8..88b4e28 100644 --- a/tools/requirements.txt +++ b/tools/requirements.txt @@ -1,2 +1,4 @@ ecdsa intelhex +pyserial +python-fido2 diff --git a/tools/serial_monitor.py b/tools/serial_monitor.py deleted file mode 100644 index 473782b..0000000 --- a/tools/serial_monitor.py +++ /dev/null @@ -1,62 +0,0 @@ -#!/usr/bin/python -# -# Copyright (C) 2018 SoloKeys, Inc. -# -# This file is part of Solo. -# -# Solo is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Solo 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 General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Solo. If not, see -# -# This code is available under licenses for commercial use. -# Please contact SoloKeys for more information. -# - -# This is a basic, cross-platfrom serial emulator. -# It will automatically try to reconnect to a serial port that disconnects. -# Ideal for development with Solo. -# -# Requires pySerial -# -import sys,time -import serial - -if len(sys.argv) != 2: - print( -""" -usage: %s - * will look like COM10 or /dev/ttyACM0 or something. - * baud is 115200. -""" % sys.argv[0]) - sys.exit(1) - -port = sys.argv[1] - -ser = serial.Serial(port,115200,timeout=.05) - -def reconnect(): - while(1): - time.sleep(0.02) - try: - ser = serial.Serial(port,115200,timeout=.05) - return ser - except serial.SerialException: - pass -while 1: - try: - d = ser.read(1) - except serial.SerialException: - print('reconnecting...') - ser = reconnect() - print('done') - sys.stdout.buffer.write(d) - sys.stdout.flush() diff --git a/tools/sign_firmware.py b/tools/sign_firmware.py deleted file mode 100644 index b5935ee..0000000 --- a/tools/sign_firmware.py +++ /dev/null @@ -1,84 +0,0 @@ -# -# Copyright (C) 2018 SoloKeys, Inc. -# -# This file is part of Solo. -# -# Solo is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Solo 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 General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Solo. If not, see -# -# This code is available under licenses for commercial use. -# Please contact SoloKeys for more information. -# -import sys -import json,base64,array,binascii -from hashlib import sha256 - -from ecdsa import SigningKey, NIST256p -from intelhex import IntelHex - -def to_websafe(data): - data = data.replace('+','-') - data = data.replace('/','_') - data = data.replace('=','') - return data - -def from_websafe(data): - data = data.replace('-','+') - data = data.replace('_','/') - return data + '=='[:(3*len(data)) % 4] - -def get_firmware_object(sk_name, hex_file): - sk = SigningKey.from_pem(open(sk_name).read()) - fw = open(hex_file,'r').read() - fw = base64.b64encode(fw.encode()) - fw = to_websafe(fw.decode()) - ih = IntelHex() - ih.fromfile(hex_file, format='hex') - # start of firmware and the size of the flash region allocated for it. - # TODO put this somewhere else. - START = ih.segments()[0][0] - END = ((0x08000000 + ((128-19)*2048))-8) - - ih = IntelHex(hex_file) - segs = ih.segments() - arr = ih.tobinarray(start = START, size = END-START) - - im_size = END-START - - print('im_size: ', im_size) - print('firmware_size: ', len(arr)) - - byts = (arr).tobytes() if hasattr(arr,'tobytes') else (arr).tostring() - h = sha256() - h.update(byts) - sig = binascii.unhexlify(h.hexdigest()) - print('hash', binascii.hexlify(sig)) - sig = sk.sign_digest(sig) - - print('sig', binascii.hexlify(sig)) - - sig = base64.b64encode(sig) - sig = to_websafe(sig.decode()) - - #msg = {'data': read()} - msg = {'firmware': fw, 'signature':sig} - return msg - -if __name__ == '__main__': - if len(sys.argv) != 4: - print('usage: %s ' % sys.argv[0]) - msg = get_firmware_object(sys.argv[1],sys.argv[2]) - print('Saving signed firmware to', sys.argv[3]) - wfile = open(sys.argv[3],'wb+') - wfile.write(json.dumps(msg).encode()) - wfile.close() diff --git a/tools/solotool.py b/tools/solotool.py index 9165d8b..c1faa73 100644 --- a/tools/solotool.py +++ b/tools/solotool.py @@ -1,21 +1,21 @@ # # Copyright (C) 2018 SoloKeys, Inc. -# +# # This file is part of Solo. -# +# # Solo is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. -# +# # Solo 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 General Public License for more details. -# +# # You should have received a copy of the GNU General Public License # along with Solo. If not, see -# +# # This code is available under licenses for commercial use. # Please contact SoloKeys for more information. # @@ -24,9 +24,10 @@ # Requires python-fido2, intelhex import sys,os,time,struct,argparse -import array,struct,socket,json,base64 +import array,struct,socket,json,base64,binascii import tempfile from binascii import hexlify +from hashlib import sha256 from fido2.hid import CtapHidDevice, CTAPHID from fido2.client import Fido2Client, ClientError @@ -35,8 +36,57 @@ from fido2.ctap1 import CTAP1, ApduError from fido2.utils import Timeout from intelhex import IntelHex +import serial -from sign_firmware import * + +def to_websafe(data): + data = data.replace('+','-') + data = data.replace('/','_') + data = data.replace('=','') + return data + +def from_websafe(data): + data = data.replace('-','+') + data = data.replace('_','/') + return data + '=='[:(3*len(data)) % 4] + +def get_firmware_object(sk_name, hex_file): + from ecdsa import SigningKey, NIST256p + sk = SigningKey.from_pem(open(sk_name).read()) + fw = open(hex_file,'r').read() + fw = base64.b64encode(fw.encode()) + fw = to_websafe(fw.decode()) + ih = IntelHex() + ih.fromfile(hex_file, format='hex') + # start of firmware and the size of the flash region allocated for it. + # TODO put this somewhere else. + START = ih.segments()[0][0] + END = ((0x08000000 + ((128-19)*2048))-8) + + ih = IntelHex(hex_file) + segs = ih.segments() + arr = ih.tobinarray(start = START, size = END-START) + + im_size = END-START + + print('im_size: ', im_size) + print('firmware_size: ', len(arr)) + + byts = (arr).tobytes() if hasattr(arr,'tobytes') else (arr).tostring() + h = sha256() + h.update(byts) + sig = binascii.unhexlify(h.hexdigest()) + print('hash', binascii.hexlify(sig)) + sig = sk.sign_digest(sig) + + print('sig', binascii.hexlify(sig)) + + sig = base64.b64encode(sig) + sig = to_websafe(sig.decode()) + + #msg = {'data': read()} + msg = {'firmware': fw, 'signature':sig} + return msg class SoloBootloader: write = 0x40 @@ -55,12 +105,12 @@ class SoloBootloader: TAG = b'\x8C\x27\x90\xf6' -class Programmer(): +class SoloClient(): def __init__(self,): self.origin = 'https://example.org' self.exchange = self.exchange_hid - self.reboot = True + self.do_reboot = True def use_u2f(self,): self.exchange = self.exchange_u2f @@ -70,9 +120,9 @@ class Programmer(): def set_reboot(self,val): """ option to reboot after programming """ - self.reboot = val + self.do_reboot = val - def reboot(self,val): + def reboot(self,): """ option to reboot after programming """ try: self.exchange(SoloBootloader.reboot) @@ -110,7 +160,7 @@ class Programmer(): 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) + req = SoloClient.format_request(cmd,addr,data) data = self.send_data_hid(SoloBootloader.HIDCommandBoot, req) @@ -124,7 +174,7 @@ class Programmer(): appid = b'A'*32 chal = b'B'*32 - req = Programmer.format_request(cmd,addr,data) + req = SoloClient.format_request(cmd,addr,data) res = self.ctap1.authenticate(chal,appid, req) @@ -186,7 +236,7 @@ class Programmer(): soloboot = self.is_solo_bootloader() if soloboot or self.exchange == self.exchange_u2f: - req = Programmer.format_request(SoloBootloader.st_dfu) + req = SoloClient.format_request(SoloBootloader.st_dfu) self.send_only_hid(SoloBootloader.HIDCommandBoot, req) else: self.send_only_hid(SoloBootloader.HIDCommandEnterSTBoot, '') @@ -202,7 +252,7 @@ class Programmer(): print('Failed to disable bootloader') return False time.sleep(0.1) - self.exchange(SoloBootloader.reboot) + self.exchange(SoloBootloader.do_reboot) return True @@ -248,7 +298,7 @@ class Programmer(): print('time: %.2f s' % ((t2-t1)/1000.0)) print('Verifying...') - if self.reboot: + if self.do_reboot: if sig is not None: self.verify_flash(sig) else: @@ -283,7 +333,118 @@ def attempt_to_boot_bootloader(p): print('Failed to reconnect!') sys.exit(1) -if __name__ == '__main__': +def solo_main(): + parser = argparse.ArgumentParser() + parser.add_argument("--rng", action="store_true", help = 'Continuously dump random numbers generated from Solo.') + parser.add_argument("--wink", action="store_true", help = 'HID Wink command.') + args = parser.parse_args() + + p = SoloClient() + p.find_device() + + if args.rng: + while True: + r = p.get_rng(255) + sys.stdout.buffer.write(r) + sys.exit(0) + + if args.wink: + p.wink() + sys.exit(0) + +def asked_for_help(): + for i,v in enumerate(sys.argv): + if v == '-h' or v == '--help': + return True + return False + +def monitor_main(): + if asked_for_help() or len(sys.argv) != 2: + print( + """ + Reads serial output from USB serial port on Solo hacker. Automatically reconnects. + usage: %s [-h] + * will look like COM10 or /dev/ttyACM0 or something. + * baud is 115200. + """ % sys.argv[0]) + sys.exit(1) + + port = sys.argv[1] + + ser = serial.Serial(port,115200,timeout=.05) + + def reconnect(): + while(1): + time.sleep(0.02) + try: + ser = serial.Serial(port,115200,timeout=.05) + return ser + except serial.SerialException: + pass + while 1: + try: + d = ser.read(1) + except serial.SerialException: + print('reconnecting...') + ser = reconnect() + print('done') + sys.stdout.buffer.write(d) + sys.stdout.flush() + +def genkey_main(): + from ecdsa import SigningKey, NIST256p + from ecdsa.util import randrange_from_seed__trytryagain + + if asked_for_help() or len(sys.argv) not in (2,3): + print( + """ + Generates key pair that can be used for Solo's signed firmware updates. + usage: %s [input-seed-file] [-h] + * Generates NIST P256 keypair. + * Public key must be copied into correct source location in solo bootloader + * The private key can be used for signing updates. + * You may optionally supply a file to seed the RNG for key generating. + """ % sys.argv[0]) + sys.exit(1) + + if len(sys.argv) > 2: + seed = sys.argv[2] + print('using input seed file ', seed) + rng = open(seed,'rb').read() + secexp = randrange_from_seed__trytryagain(rng, NIST256p.order) + sk = SigningKey.from_secret_exponent(secexp,curve = NIST256p) + else: + sk = SigningKey.generate(curve = NIST256p) + + sk_name = sys.argv[1] + print('Signing key for signing device firmware: '+sk_name) + open(sk_name,'wb+').write(sk.to_pem()) + + vk = sk.get_verifying_key() + + print('Public key in various formats:') + print() + print([c for c in vk.to_string()]) + print() + print(''.join(['%02x'%c for c in vk.to_string()])) + print() + print('"\\x' + '\\x'.join(['%02x'%c for c in vk.to_string()]) + '"') + print() + +def sign_main(): + + if asked_for_help() or len(sys.argv) != 4: + print('Signs a firmware hex file, outputs a .json file that can be used for signed update.') + print('usage: %s [-h]' % sys.argv[0]) + print() + sys.exit(1) + msg = get_firmware_object(sys.argv[1],sys.argv[2]) + print('Saving signed firmware to', sys.argv[3]) + wfile = open(sys.argv[3],'wb+') + wfile.write(json.dumps(msg).encode()) + wfile.close() + +def programmer_main(): parser = argparse.ArgumentParser() parser.add_argument("[firmware]", nargs='?', default='', help = 'firmware file. Either a JSON or hex file. JSON file contains signature while hex does not.') @@ -295,12 +456,9 @@ if __name__ == '__main__': parser.add_argument("--enter-bootloader", action="store_true", help = 'Don\'t write anything, try to enter bootloader. Typically only supported by Solo Hacker builds.') parser.add_argument("--st-dfu", action="store_true", help = 'Don\'t write anything, try to enter ST DFU. Warning, you could brick your Solo if you overwrite everything. You should reprogram the option bytes just to be safe (boot to Solo bootloader first, then run this command).') parser.add_argument("--disable", action="store_true", help = 'Disable the Solo bootloader. Cannot be undone. No future updates can be applied.') - parser.add_argument("--rng", action="store_true", help = 'Continuously dump random numbers generated from Solo.') - parser.add_argument("--wink", action="store_true", help = 'HID Wink command.') args = parser.parse_args() - print() - p = Programmer() + p = SoloClient() p.find_device() if args.use_u2f: @@ -317,16 +475,6 @@ if __name__ == '__main__': p.reboot() sys.exit(0) - if args.rng: - while True: - r = p.get_rng(255) - sys.stdout.buffer.write(r) - sys.exit(0) - - if args.wink: - p.wink() - sys.exit(0) - if args.st_dfu: print('Sending command to boot into ST DFU...') p.enter_st_dfu() @@ -336,6 +484,12 @@ if __name__ == '__main__': p.disable_solo_bootloader() sys.exit(0) + fw = args.__dict__['[firmware]'] + if fw == '': + print('Need to supply firmware filename, or see help for more options.') + parser.print_help() + sys.exit(1) + try: p.version() except CtapError as e: @@ -346,12 +500,45 @@ if __name__ == '__main__': except ApduError: attempt_to_boot_bootloader(p) - if not args.reset_only: - fw = args.__dict__['[firmware]'] - if fw == '': - print('Need to supply firmware filename.') - args.print_help() - sys.exit(1) - p.program_file(fw) - else: + if args.reset_only: p.exchange(SoloBootloader.done,0,b'A'*64) + else: + p.program_file(fw) + +if __name__ == '__main__': + + if len(sys.argv) < 2 or (len(sys.argv) == 2 and asked_for_help()): + print('Diverse command line tool for working with Solo') + print('usage: %s [options] [-h]' % sys.argv[0]) + print('commands: program, solo, monitor, sign, genkey') + print( +""" +Examples: + {0} program + {0} program --reboot + {0} program --enter-bootloader + {0} solo --wink + {0} solo --rng + {0} monitor + {0} sign + {0} genkey [rng-seed-file] +""".format(sys.argv[0])) + sys.exit(1) + + + c = sys.argv[1] + sys.argv = sys.argv[:1] + sys.argv[2:] + sys.argv[0] = sys.argv[0] + ' ' + c + + if c == 'program': + programmer_main() + elif c == 'solo': + solo_main() + elif c == 'monitor': + monitor_main() + elif c == 'sign': + sign_main() + elif c == 'genkey': + genkey_main() + else: + print('invalid command: %s' % c) From 58605fd278aa622d368b95dc7f5873cded4c6148 Mon Sep 17 00:00:00 2001 From: Conor Patrick Date: Mon, 31 Dec 2018 15:06:31 -0500 Subject: [PATCH 3/8] add dfu functionality --- tools/requirements.txt | 1 + tools/solotool.py | 257 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 255 insertions(+), 3 deletions(-) diff --git a/tools/requirements.txt b/tools/requirements.txt index 88b4e28..2d1726d 100644 --- a/tools/requirements.txt +++ b/tools/requirements.txt @@ -2,3 +2,4 @@ ecdsa intelhex pyserial python-fido2 +pyusb diff --git a/tools/solotool.py b/tools/solotool.py index c1faa73..cbcd1fb 100644 --- a/tools/solotool.py +++ b/tools/solotool.py @@ -35,6 +35,9 @@ from fido2.ctap import CtapError from fido2.ctap1 import CTAP1, ApduError from fido2.utils import Timeout +import usb.core +import usb.util + from intelhex import IntelHex import serial @@ -218,7 +221,7 @@ class SoloClient(): def is_solo_bootloader(self,): try: - p.version() + self.version() return True except CtapError as e: if e.code == CtapError.ERR.INVALID_COMMAND: @@ -304,6 +307,184 @@ class SoloClient(): else: self.verify_flash(b'A'*64) +class DFU: + class type: + SEND = 0x21 + RECEIVE = 0xa1 + + class bmReq: + DETACH = 0x00 + DNLOAD = 0x01 + UPLOAD = 0x02 + GETSTATUS = 0x03 + CLRSTATUS = 0x04 + GETSTATE = 0x05 + ABORT = 0x06 + + class state: + APP_IDLE = 0x00 + APP_DETACH = 0x01 + IDLE = 0x02 + DOWNLOAD_SYNC = 0x03 + DOWNLOAD_BUSY = 0x04 + DOWNLOAD_IDLE = 0x05 + MANIFEST_SYNC = 0x06 + MANIFEST = 0x07 + MANIFEST_WAIT_RESET = 0x08 + UPLOAD_IDLE = 0x09 + ERROR = 0x0a + + class status: + def __init__(self,s): + self.status = s[0] + self.timeout = s[1] + (s[2] << 8) + (s[3] << 16) + self.state = s[4] + self.istring = s[5] + +class DFUDevice: + def __init__(self,): + pass + + + @staticmethod + def addr2list(a): + return [ a & 0xff, (a >> 8) & 0xff, (a >> 16) & 0xff, (a >> 24) & 0xff ] + + @staticmethod + def addr2block(addr,size): + addr -= 0x08000000 + addr //= size + addr += 2 + return addr + + @staticmethod + def block2addr(addr,size): + addr -= 2 + addr *= size + addr += 0x08000000 + return addr + + def find(self, altsetting = 0, ser=None): + + self.dev = None + if ser: + devs = usb.core.find(idVendor=0x0483, idProduct=0xDF11,find_all=1) + for x in devs: + if ser == (usb.util.get_string(x,x.iSerialNumber)): + print('connecting to ',ser) + self.dev = x + break + else: + self.dev = usb.core.find(idVendor=0x0483, idProduct=0xDF11,) + + #print (self.dev) + + if self.dev is None: + raise RuntimeError('No ST DFU devices found.') + self.dev.set_configuration() + + for cfg in self.dev: + for intf in cfg: + if intf.bAlternateSetting == altsetting: + intf.set_altsetting() + self.intf = intf + self.intNum = intf.bInterfaceNumber + return self.dev + + raise RuntimeError('No ST DFU alternate-%d found.' % altsetting) + + def init(self,): + if self.state() == DFU.state.ERROR: + self.clear_status() + + def close(self,): + pass + + def get_status(self,): + # bmReqType, bmReq, wValue, wIndex, data/size + s = self.dev.ctrl_transfer(DFU.type.RECEIVE, DFU.bmReq.GETSTATUS,0, self.intNum, 6) + return DFU.status(s) + + def state(self,): + return self.get_status().state + + def clear_status(self,): + # bmReqType, bmReq, wValue, wIndex, data/size + s = self.dev.ctrl_transfer(DFU.type.SEND, DFU.bmReq.CLRSTATUS, 0, self.intNum, None) + + def upload(self,block,size): + """ + address is ((block – 2) × size) + 0x08000000 + """ + # bmReqType, bmReq, wValue, wIndex, data/size + return self.dev.ctrl_transfer(DFU.type.RECEIVE, DFU.bmReq.UPLOAD, block, self.intNum, size) + + def set_addr(self, addr): + # must get_status after to take effect + return self.dnload(0x0, [0x21] + DFUDevice.addr2list(addr)) + + def dnload(self, block, data): + # bmReqType, bmReq, wValue, wIndex, data/size + return self.dev.ctrl_transfer(DFU.type.SEND, DFU.bmReq.DNLOAD, block, self.intNum, data) + + def erase(self, a): + d = [0x41, a & 0xff, (a >> 8) & 0xff, (a >> 16) & 0xff, (a >> 24) & 0xff] + return self.dnload(0x0, d) + + def mass_erase(self): + # self.set_addr(0x08000000) + # self.block_on_state(DFU.state.DOWNLOAD_BUSY) + # assert(DFU.state.DOWNLOAD_IDLE == self.state()) + self.dnload(0x0, [0x41,]) + self.block_on_state(DFU.state.DOWNLOAD_BUSY) + assert(DFU.state.DOWNLOAD_IDLE == self.state()) + + def write_page(self, addr, data): + if self.state() not in (DFU.state.IDLE, DFU.state.DOWNLOAD_IDLE): + self.clear_status() + self.clear_status() + if self.state() not in (DFU.state.IDLE, DFU.state.DOWNLOAD_IDLE): + raise RuntimeError('DFU device not in correct state for writing memory.') + + oldaddr = addr + addr = DFUDevice.addr2block(addr, len(data)) + # print('flashing %d bytes to block %d/%08x...' % (len(data), addr,oldaddr)) + + self.dnload(addr, data) + self.block_on_state(DFU.state.DOWNLOAD_BUSY) + assert(DFU.state.DOWNLOAD_IDLE == self.state()) + + def read_mem(self, addr, size): + addr = DFUDevice.addr2block(addr,size) + + if self.state() not in (DFU.state.IDLE, DFU.state.UPLOAD_IDLE): + self.clear_status() + self.clear_status() + if self.state() not in (DFU.state.IDLE, DFU.state.UPLOAD_IDLE): + raise RuntimeError('DFU device not in correct state for reading memory.') + + return self.upload(addr,size) + + def block_on_state(self,state): + s = self.get_status() + while s.state == state: + time.sleep(s.timeout/1000.0) + s = self.get_status() + + def detach(self,): + if self.state() not in (DFU.state.IDLE, DFU.state.DOWNLOAD_IDLE): + self.clear_status() + self.clear_status() + if self.state() not in (DFU.state.IDLE, DFU.state.DOWNLOAD_IDLE): + raise RuntimeError('DFU device not in correct state for detaching.') + # self.set_addr(0x08000000) + # self.block_on_state(DFU.state.DOWNLOAD_BUSY) + # assert(DFU.state.DOWNLOAD_IDLE == self.state()) + self.dnload(0x0, []) + return self.get_status() + # return self.dev.ctrl_transfer(DFU.type.SEND, DFU.bmReq.DETACH, 0, self.intNum, None) + + def attempt_to_find_device(p): found = False for i in range(0,5): @@ -444,6 +625,64 @@ def sign_main(): wfile.write(json.dumps(msg).encode()) wfile.close() +def use_dfu(args): + fw = args.__dict__['[firmware]'] + dfu = DFUDevice() + try: + dfu.find(ser = args.dfu_serial) + except RuntimeError: + print('No STU DFU device found. ') + if args.dfu_serial: + print('Serial number used: ', args.dfu_serial) + sys.exit(1) + dfu.init() + + if fw: + ih = IntelHex() + ih.fromfile(fw, format='hex') + + chunk = 2048 + seg = ih.segments()[0] + size = sum([x[1] - x[0] for x in ih.segments()]) + total = 0 + t1 = time.time()*1000 + + print('erasing...') + try: + dfu.mass_erase() + except usb.core.USBError: + dfu.write_page(0x08000000 + 2048*10,'ZZFF'*(2048//4)) + dfu.mass_erase() + + page = 0 + for start,end in ih.segments(): + for i in range(start, end, chunk): + page += 1 + s = i + data = ih.tobinarray(start=i,size = chunk) + dfu.write_page(i,data) + total += chunk + progress = total/float(size)*100 + sys.stdout.write('downloading %.2f%% %08x - %08x ... \r' % (progress,i,i+page)) + # time.sleep(0.100) + + # print('done') + # print(dfu.read_mem(i,16)) + t2 = time.time()*1000 + + print('time: %d ms' %(t2 - t1)) + print('verifying...') + for start,end in ih.segments(): + for i in range(start, end, chunk): + data1 = (dfu.read_mem(i,2048)) + data2 = ih.tobinarray(start=i,size = chunk) + assert(data1 == data2) + print('firmware readback verified.') + if args.detach: + dfu.detach() + + + def programmer_main(): parser = argparse.ArgumentParser() @@ -456,10 +695,22 @@ def programmer_main(): parser.add_argument("--enter-bootloader", action="store_true", help = 'Don\'t write anything, try to enter bootloader. Typically only supported by Solo Hacker builds.') parser.add_argument("--st-dfu", action="store_true", help = 'Don\'t write anything, try to enter ST DFU. Warning, you could brick your Solo if you overwrite everything. You should reprogram the option bytes just to be safe (boot to Solo bootloader first, then run this command).') parser.add_argument("--disable", action="store_true", help = 'Disable the Solo bootloader. Cannot be undone. No future updates can be applied.') + parser.add_argument("--detach", action="store_true", help = 'Detach from ST DFU and boot from main flash. Must be in DFU mode.') + parser.add_argument("--dfu-serial", default='', help = 'Specify a serial number for a specific DFU device to connect to.') args = parser.parse_args() + fw = args.__dict__['[firmware]'] + p = SoloClient() - p.find_device() + try: + p.find_device() + except RuntimeError: + if fw or args.detach: + use_dfu(args) + sys.exit(0) + else: + print('No Solo device detected.') + sys.exit(1) if args.use_u2f: p.use_u2f() @@ -484,7 +735,7 @@ def programmer_main(): p.disable_solo_bootloader() sys.exit(0) - fw = args.__dict__['[firmware]'] + if fw == '': print('Need to supply firmware filename, or see help for more options.') parser.print_help() From 1f380b0264f59cdc297ef5546d9812679e9ddbec Mon Sep 17 00:00:00 2001 From: Conor Patrick Date: Mon, 31 Dec 2018 15:20:02 -0500 Subject: [PATCH 4/8] patch issue with del on windows --- tools/solotool.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tools/solotool.py b/tools/solotool.py index cbcd1fb..3d79c19 100644 --- a/tools/solotool.py +++ b/tools/solotool.py @@ -37,6 +37,7 @@ from fido2.utils import Timeout import usb.core import usb.util +import usb._objfinalizer from intelhex import IntelHex import serial @@ -341,6 +342,15 @@ class DFU: self.state = s[4] self.istring = s[5] +# hot patch for windows libusb backend +olddel = usb._objfinalizer._AutoFinalizedObjectBase.__del__ +def newdel(self): + try: + olddel(self) + except OSError: + pass +usb._objfinalizer._AutoFinalizedObjectBase.__del__ = newdel + class DFUDevice: def __init__(self,): pass @@ -377,7 +387,6 @@ class DFUDevice: else: self.dev = usb.core.find(idVendor=0x0483, idProduct=0xDF11,) - #print (self.dev) if self.dev is None: raise RuntimeError('No ST DFU devices found.') From 8e8d74c5adf8e460d048f1cbc4467e99c8d9c8c7 Mon Sep 17 00:00:00 2001 From: Conor Patrick Date: Mon, 31 Dec 2018 15:23:47 -0500 Subject: [PATCH 5/8] Update solotool.py --- tools/solotool.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tools/solotool.py b/tools/solotool.py index 3d79c19..dbd5bc6 100644 --- a/tools/solotool.py +++ b/tools/solotool.py @@ -721,6 +721,10 @@ def programmer_main(): print('No Solo device detected.') sys.exit(1) + if args.detach: + use_dfu(args) + sys.exit(0) + if args.use_u2f: p.use_u2f() @@ -745,7 +749,7 @@ def programmer_main(): sys.exit(0) - if fw == '': + if fw == '' and not args.reset_only: print('Need to supply firmware filename, or see help for more options.') parser.print_help() sys.exit(1) From b8a27eadca95670f6c1f3b12b0007ba1411bae05 Mon Sep 17 00:00:00 2001 From: Conor Patrick Date: Wed, 2 Jan 2019 20:59:37 -0500 Subject: [PATCH 6/8] small improvements --- targets/stm32l442/Makefile | 8 ++-- targets/stm32l442/bootloader/main.c | 14 +++---- targets/stm32l442/merge_hex.py | 19 ++++++--- targets/stm32l442/src/device.c | 16 ++++++++ targets/stm32l442/src/init.c | 1 - tools/solotool.py | 60 ++++++++++++++++++++++------- 6 files changed, 88 insertions(+), 30 deletions(-) diff --git a/targets/stm32l442/Makefile b/targets/stm32l442/Makefile index b038069..f65e709 100644 --- a/targets/stm32l442/Makefile +++ b/targets/stm32l442/Makefile @@ -12,17 +12,17 @@ all-locked: $(MAKE) -f application.mk -j8 solo.hex EXTRA_DEFINES='-DFLASH_ROP=2' debugboot-app: - $(MAKE) -f application.mk -j8 solo.hex DEBUG=1 \ + $(MAKE) -f application.mk -j8 solo.hex DEBUG=2 \ LDSCRIPT=linker/stm32l4xx_extra.ld EXTRA_DEFINES='-DAPPLICATION_START_PAGE=16 -DSOLO_HACKER' debugboot-boot: $(MAKE) -f bootloader.mk -j8 bootloader.hex DEBUG=1 \ LDSCRIPT=linker/bootloader_stm32l4xx_extra.ld EXTRA_DEFINES='-DAPPLICATION_START_PAGE=16 -DSOLO_HACKER' -boot: - $(MAKE) -f bootloader.mk -j8 bootloader.hex DEBUG=$(DEBUG) +boot-sig-checking: + $(MAKE) -f bootloader.mk -j8 bootloader.hex -boot-hacker: +boot-no-sig: $(MAKE) -f bootloader.mk -j8 bootloader.hex EXTRA_DEFINES='-DSOLO_HACKER' clean: diff --git a/targets/stm32l442/bootloader/main.c b/targets/stm32l442/bootloader/main.c index 19e930c..afdc15f 100644 --- a/targets/stm32l442/bootloader/main.c +++ b/targets/stm32l442/bootloader/main.c @@ -1,21 +1,21 @@ /* * Copyright (C) 2018 SoloKeys, Inc. - * + * * This file is part of Solo. - * + * * Solo is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * Solo 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 General Public License for more details. - * + * * You should have received a copy of the GNU General Public License * along with Solo. If not, see - * + * * This code is available under licenses for commercial use. * Please contact SoloKeys for more information. */ @@ -114,10 +114,10 @@ int main(int argc, char * argv[]) } else { + printf1(TAG_RED,"Not authorized to boot (%08x == %08lx)\r\n", AUTH_WORD_ADDR, *(uint32_t*)AUTH_WORD_ADDR); } start_bootloader: - usbhid_init(); printf1(TAG_GEN,"init usb\n"); @@ -155,7 +155,7 @@ int main(int argc, char * argv[]) device_reboot(); } #ifdef SOLO_HACKER - // Boot ST bootloader if button is held for 2s + // Boot ST bootloader if button is held for 5s if (!device_is_button_pressed()) { stboot_time = millis(); diff --git a/targets/stm32l442/merge_hex.py b/targets/stm32l442/merge_hex.py index 5e46e03..397a72b 100644 --- a/targets/stm32l442/merge_hex.py +++ b/targets/stm32l442/merge_hex.py @@ -1,21 +1,21 @@ # # Copyright (C) 2018 SoloKeys, Inc. -# +# # This file is part of Solo. -# +# # Solo is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. -# +# # Solo 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 General Public License for more details. -# +# # You should have received a copy of the GNU General Public License # along with Solo. If not, see -# +# # This code is available under licenses for commercial use. # Please contact SoloKeys for more information. # @@ -59,6 +59,14 @@ for i in range(2, len(args)-1): print('merging %s with ' % (args[1]), args[i]) first.merge(IntelHex( args[i] ), overlap = 'replace') +first [ flash_addr(APPLICATION_END_PAGE-1) ] = 0x41 +first [ flash_addr(APPLICATION_END_PAGE-1)+1 ] = 0x41 + +first[AUTH_WORD_ADDR-4] = 0 +first[AUTH_WORD_ADDR-1] = 0 +first[AUTH_WORD_ADDR-2] = 0 +first[AUTH_WORD_ADDR-3] = 0 + first[AUTH_WORD_ADDR] = 0 first[AUTH_WORD_ADDR+1] = 0 first[AUTH_WORD_ADDR+2] = 0 @@ -69,6 +77,7 @@ first[AUTH_WORD_ADDR+5] = 0xff first[AUTH_WORD_ADDR+6] = 0xff first[AUTH_WORD_ADDR+7] = 0xff + if secret_attestation_key is not None: key = unhexlify(secret_attestation_key) diff --git a/targets/stm32l442/src/device.c b/targets/stm32l442/src/device.c index 94d024c..7bc5f48 100644 --- a/targets/stm32l442/src/device.c +++ b/targets/stm32l442/src/device.c @@ -124,6 +124,22 @@ void usb_init(void); void usbhid_init() { usb_init(); + +#if DEBUG_LEVEL>1 + wait_for_usb_tether(); +#endif + +} + +void wait_for_usb_tether() +{ + while (USBD_OK != CDC_Transmit_FS("tethered\r\n", 10) ) + ; + while (USBD_OK != CDC_Transmit_FS("tethered\r\n", 10) ) + ; + delay(10); + while (USBD_OK != CDC_Transmit_FS("tethered\r\n", 10) ) + ; } int usbhid_recv(uint8_t * msg) diff --git a/targets/stm32l442/src/init.c b/targets/stm32l442/src/init.c index 0004318..daa81d0 100644 --- a/targets/stm32l442/src/init.c +++ b/targets/stm32l442/src/init.c @@ -210,7 +210,6 @@ void usb_init() USBD_Composite_Set_Classes(&USBD_HID, &USBD_CDC); - // USBD_Composite_Set_Classes(&USBD_CDC, &USBD_CDC); in_endpoint_to_class[HID_EPIN_ADDR & 0x7F] = 0; out_endpoint_to_class[HID_EPOUT_ADDR & 0x7F] = 0; diff --git a/tools/solotool.py b/tools/solotool.py index dbd5bc6..3a579eb 100644 --- a/tools/solotool.py +++ b/tools/solotool.py @@ -170,7 +170,10 @@ class SoloClient(): ret = data[0] if ret != CtapError.ERR.SUCCESS: - raise RuntimeError('Device returned non-success code %02x' % ret) + str = '' + if ret == CtapError.ERR.NOT_ALLOWED: + str = 'Out of bounds write' + raise RuntimeError('Device returned non-success code %02x: %s' % (ret,str)) return data[1:] @@ -184,7 +187,10 @@ class SoloClient(): ret = res.signature[0] if ret != CtapError.ERR.SUCCESS: - raise RuntimeError('Device returned non-success code %02x' % ret) + str = '' + if ret == CtapError.ERR.NOT_ALLOWED: + str = 'Out of bounds write' + raise RuntimeError('Device returned non-success code %02x: %s' % (ret,str)) return res.signature[1:] @@ -506,7 +512,7 @@ def attempt_to_find_device(p): return found def attempt_to_boot_bootloader(p): - print('Bootloader not active. Attempting to boot into bootloader mode...') + try: p.enter_solo_bootloader() except OSError: @@ -520,8 +526,8 @@ def attempt_to_boot_bootloader(p): print('Solo rebooted. Reconnecting...') time.sleep(.500) if not attempt_to_find_device(p): - print('Failed to reconnect!') - sys.exit(1) + raise RuntimeError('Failed to reconnect!') + def solo_main(): parser = argparse.ArgumentParser() @@ -636,10 +642,16 @@ def sign_main(): def use_dfu(args): fw = args.__dict__['[firmware]'] - dfu = DFUDevice() - try: - dfu.find(ser = args.dfu_serial) - except RuntimeError: + + for i in range(0,8): + dfu = DFUDevice() + try: + dfu.find(ser = args.dfu_serial) + except RuntimeError: + time.sleep(0.25) + dfu = None + + if dfu is None: print('No STU DFU device found. ') if args.dfu_serial: print('Serial number used: ', args.dfu_serial) @@ -652,7 +664,7 @@ def use_dfu(args): chunk = 2048 seg = ih.segments()[0] - size = sum([x[1] - x[0] for x in ih.segments()]) + size = sum([max(x[1] - x[0],chunk) for x in ih.segments()]) total = 0 t1 = time.time()*1000 @@ -672,20 +684,27 @@ def use_dfu(args): dfu.write_page(i,data) total += chunk progress = total/float(size)*100 + sys.stdout.write('downloading %.2f%% %08x - %08x ... \r' % (progress,i,i+page)) # time.sleep(0.100) # print('done') # print(dfu.read_mem(i,16)) t2 = time.time()*1000 - + print() print('time: %d ms' %(t2 - t1)) print('verifying...') + progress = 0 for start,end in ih.segments(): for i in range(start, end, chunk): data1 = (dfu.read_mem(i,2048)) data2 = ih.tobinarray(start=i,size = chunk) - assert(data1 == data2) + total += chunk + progress = total/float(size)*100 + sys.stdout.write('reading %.2f%% %08x - %08x ... \r' % (progress,i,i+page)) + if (end-start) == chunk: + assert(data1 == data2) + print() print('firmware readback verified.') if args.detach: dfu.detach() @@ -706,19 +725,31 @@ def programmer_main(): parser.add_argument("--disable", action="store_true", help = 'Disable the Solo bootloader. Cannot be undone. No future updates can be applied.') parser.add_argument("--detach", action="store_true", help = 'Detach from ST DFU and boot from main flash. Must be in DFU mode.') parser.add_argument("--dfu-serial", default='', help = 'Specify a serial number for a specific DFU device to connect to.') + parser.add_argument("--use-dfu", action="store_true", help = 'Boot to ST-DFU before continuing.') args = parser.parse_args() fw = args.__dict__['[firmware]'] p = SoloClient() + + + try: p.find_device() + if args.use_dfu: + print('entering dfu..') + try: + attempt_to_boot_bootloader(p) + p.enter_st_dfu() + except RuntimeError: + # already in DFU mode? + pass except RuntimeError: + print('No Solo device detected.') if fw or args.detach: use_dfu(args) sys.exit(0) else: - print('No Solo device detected.') sys.exit(1) if args.detach: @@ -732,6 +763,7 @@ def programmer_main(): p.set_reboot(False) if args.enter_bootloader: + print('Attempting to boot into bootloader mode...') attempt_to_boot_bootloader(p) sys.exit(0) @@ -758,10 +790,12 @@ def programmer_main(): p.version() except CtapError as e: if e.code == CtapError.ERR.INVALID_COMMAND: + print('Bootloader not active. Attempting to boot into bootloader mode...') attempt_to_boot_bootloader(p) else: raise(e) except ApduError: + print('Bootloader not active. Attempting to boot into bootloader mode...') attempt_to_boot_bootloader(p) if args.reset_only: From 9565ae4cda6199e7de5c0cea6981c77d9b4b1575 Mon Sep 17 00:00:00 2001 From: Conor Patrick Date: Wed, 2 Jan 2019 21:07:56 -0500 Subject: [PATCH 7/8] combined merge_hex --- targets/stm32l442/Makefile | 8 ++-- targets/stm32l442/merge_hex.py | 88 ---------------------------------- tools/solotool.py | 68 +++++++++++++++++++++++++- 3 files changed, 71 insertions(+), 93 deletions(-) delete mode 100644 targets/stm32l442/merge_hex.py diff --git a/targets/stm32l442/Makefile b/targets/stm32l442/Makefile index f65e709..23accff 100644 --- a/targets/stm32l442/Makefile +++ b/targets/stm32l442/Makefile @@ -2,6 +2,8 @@ ifndef DEBUG DEBUG=0 endif +merge_hex=python ../../tools/solotool.py mergehex + all: $(MAKE) -f application.mk -j8 solo.hex DEBUG=$(DEBUG) EXTRA_DEFINES='-DFLASH_ROP=1' @@ -30,17 +32,17 @@ clean: $(MAKE) -f bootloader.mk clean flash: solo.hex bootloader.hex - python merge_hex.py solo.hex bootloader.hex all.hex + $(merge_hex) solo.hex bootloader.hex all.hex STM32_Programmer_CLI -c port=SWD -halt -e all --readunprotect STM32_Programmer_CLI -c port=SWD -halt -d all.hex -rst flash_dfu: solo.hex bootloader.hex - python merge_hex.py solo.hex bootloader.hex all.hex + $(merge_hex) solo.hex bootloader.hex all.hex # STM32_Programmer_CLI -c port=usb1 -halt -e all --readunprotect STM32_Programmer_CLI -c port=usb1 -halt -rdu -d all.hex flashboot: solo.hex bootloader.hex - python merge_hex.py solo.hex bootloader.hex all.hex + $(merge_hex) solo.hex bootloader.hex all.hex STM32_Programmer_CLI -c port=SWD -halt -e all --readunprotect STM32_Programmer_CLI -c port=SWD -halt -d bootloader.hex -rst diff --git a/targets/stm32l442/merge_hex.py b/targets/stm32l442/merge_hex.py deleted file mode 100644 index 397a72b..0000000 --- a/targets/stm32l442/merge_hex.py +++ /dev/null @@ -1,88 +0,0 @@ -# -# Copyright (C) 2018 SoloKeys, Inc. -# -# This file is part of Solo. -# -# Solo is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Solo 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 General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Solo. If not, see -# -# This code is available under licenses for commercial use. -# Please contact SoloKeys for more information. -# - -# Merges bootloader and application into 1 file for ST Solo -# -# Patches settings in flash so bootloader will boot application. - -from intelhex import IntelHex -import sys -from binascii import unhexlify - -if len(sys.argv) < 3: - print('usage: %s [...] [-s ] ') - sys.exit(1) - -def flash_addr(num): - return 0x08000000 + num * 2048 - -args = sys.argv[:] - -# generic / hacker attestation key -secret_attestation_key = "1b2626ecc8f69b0f69e34fb236d76466ba12ac16c3ab5750ba064e8b90e02448" - -# user supplied, optional -for i,x in enumerate(args): - if x == '-s': - secret_attestation_key = args[i+1] - args = args[:i] + args[i+2:] - break - - -# TODO put definitions somewhere else -PAGES = 128 -APPLICATION_END_PAGE = PAGES - 19 -AUTH_WORD_ADDR = (flash_addr(APPLICATION_END_PAGE)-8) -ATTEST_ADDR = (flash_addr(PAGES - 15)) - -first = IntelHex(args[1]) -for i in range(2, len(args)-1): - print('merging %s with ' % (args[1]), args[i]) - first.merge(IntelHex( args[i] ), overlap = 'replace') - -first [ flash_addr(APPLICATION_END_PAGE-1) ] = 0x41 -first [ flash_addr(APPLICATION_END_PAGE-1)+1 ] = 0x41 - -first[AUTH_WORD_ADDR-4] = 0 -first[AUTH_WORD_ADDR-1] = 0 -first[AUTH_WORD_ADDR-2] = 0 -first[AUTH_WORD_ADDR-3] = 0 - -first[AUTH_WORD_ADDR] = 0 -first[AUTH_WORD_ADDR+1] = 0 -first[AUTH_WORD_ADDR+2] = 0 -first[AUTH_WORD_ADDR+3] = 0 - -first[AUTH_WORD_ADDR+4] = 0xff -first[AUTH_WORD_ADDR+5] = 0xff -first[AUTH_WORD_ADDR+6] = 0xff -first[AUTH_WORD_ADDR+7] = 0xff - - -if secret_attestation_key is not None: - key = unhexlify(secret_attestation_key) - - - for i,x in enumerate(key): - first[ATTEST_ADDR + i] = x - -first.tofile(args[len(args)-1], format='hex') diff --git a/tools/solotool.py b/tools/solotool.py index 3a579eb..f02cac7 100644 --- a/tools/solotool.py +++ b/tools/solotool.py @@ -26,7 +26,7 @@ import sys,os,time,struct,argparse import array,struct,socket,json,base64,binascii import tempfile -from binascii import hexlify +from binascii import hexlify,unhexlify from hashlib import sha256 from fido2.hid import CtapHidDevice, CTAPHID @@ -803,12 +803,73 @@ def programmer_main(): else: p.program_file(fw) + +def main_mergehex(): + if len(sys.argv) < 3: + print('usage: %s [...] [-s ] ') + sys.exit(1) + + def flash_addr(num): + return 0x08000000 + num * 2048 + + args = sys.argv[:] + + # generic / hacker attestation key + secret_attestation_key = "1b2626ecc8f69b0f69e34fb236d76466ba12ac16c3ab5750ba064e8b90e02448" + + # user supplied, optional + for i,x in enumerate(args): + if x == '-s': + secret_attestation_key = args[i+1] + args = args[:i] + args[i+2:] + break + + + # TODO put definitions somewhere else + PAGES = 128 + APPLICATION_END_PAGE = PAGES - 19 + AUTH_WORD_ADDR = (flash_addr(APPLICATION_END_PAGE)-8) + ATTEST_ADDR = (flash_addr(PAGES - 15)) + + first = IntelHex(args[1]) + for i in range(2, len(args)-1): + print('merging %s with ' % (args[1]), args[i]) + first.merge(IntelHex( args[i] ), overlap = 'replace') + + first [ flash_addr(APPLICATION_END_PAGE-1) ] = 0x41 + first [ flash_addr(APPLICATION_END_PAGE-1)+1 ] = 0x41 + + first[AUTH_WORD_ADDR-4] = 0 + first[AUTH_WORD_ADDR-1] = 0 + first[AUTH_WORD_ADDR-2] = 0 + first[AUTH_WORD_ADDR-3] = 0 + + first[AUTH_WORD_ADDR] = 0 + first[AUTH_WORD_ADDR+1] = 0 + first[AUTH_WORD_ADDR+2] = 0 + first[AUTH_WORD_ADDR+3] = 0 + + first[AUTH_WORD_ADDR+4] = 0xff + first[AUTH_WORD_ADDR+5] = 0xff + first[AUTH_WORD_ADDR+6] = 0xff + first[AUTH_WORD_ADDR+7] = 0xff + + + if secret_attestation_key is not None: + key = unhexlify(secret_attestation_key) + + + for i,x in enumerate(key): + first[ATTEST_ADDR + i] = x + + first.tofile(args[len(args)-1], format='hex') + if __name__ == '__main__': if len(sys.argv) < 2 or (len(sys.argv) == 2 and asked_for_help()): print('Diverse command line tool for working with Solo') print('usage: %s [options] [-h]' % sys.argv[0]) - print('commands: program, solo, monitor, sign, genkey') + print('commands: program, solo, monitor, sign, genkey, mergehex') print( """ Examples: @@ -820,6 +881,7 @@ Examples: {0} monitor {0} sign {0} genkey [rng-seed-file] + {0} mergehex bootloader.hex solo.hex combined.hex """.format(sys.argv[0])) sys.exit(1) @@ -838,5 +900,7 @@ Examples: sign_main() elif c == 'genkey': genkey_main() + elif c == 'mergehex': + main_mergehex() else: print('invalid command: %s' % c) From de900dec2cc71bf6f5160220de12aec9d7c8228a Mon Sep 17 00:00:00 2001 From: Conor Patrick Date: Wed, 2 Jan 2019 21:16:29 -0500 Subject: [PATCH 8/8] Update solotool.py --- tools/solotool.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/solotool.py b/tools/solotool.py index f02cac7..cd95195 100644 --- a/tools/solotool.py +++ b/tools/solotool.py @@ -874,8 +874,10 @@ if __name__ == '__main__': """ Examples: {0} program + {0} program --use-dfu {0} program --reboot {0} program --enter-bootloader + {0} program --st-dfu {0} solo --wink {0} solo --rng {0} monitor