def connect_to_usart_bitboxbase(debug: bool, serial_port: usart.SerialPort) -> int: """ Connects and runs the main menu over a BitBoxBase connected over UART. """ print("Trying to connect to BitBoxBase firmware...") bootloader_device: devices.DeviceInfo = get_bitboxbase_default_device( serial_port.port) def show_pairing(code: str) -> bool: print("(Pairing should be automatic) Pairing code:") print(code) return True def attestation_check(result: bool) -> None: if result: print("Device attestation PASSED") else: print("Device attestation FAILED") try: transport = usart.U2FUsart(serial_port) base_dev = BitBoxBase( transport, bootloader_device, show_pairing_callback=show_pairing, attestation_check_callback=attestation_check, ) if debug: print("Device Info:") pprint.pprint(base_dev) return SendMessageBitBoxBase(base_dev, debug).run() except usart.U2FUsartErrorResponse as err: if err.error_code != usart.U2FUsartErrorResponse.ENDPOINT_UNAVAILABLE: raise except usart.U2FUsartTimeoutError: print("Timed out. Maybe the device is not connected?", file=sys.stderr) return 1 print("BitBox unavailable. Starting bootloader connection.") transport = usart.U2FUsart(serial_port) bootloader = bitbox02.Bootloader(transport, bootloader_device) return SendMessageBootloader(bootloader).run()
def main() -> int: """Main function""" parser = argparse.ArgumentParser( description="Tool for flashing a new firmware on BitBox devices." ) parser.add_argument("--debug", action="store_true", help="Flash a debug (unsigned) firmware.") parser.add_argument( "--usart", action="store", help="Flash firmware using U2F-over-UART (BitBoxBase), with the specified serial port.", ) parser.add_argument("firmware", nargs=1, help="Firmware to flash.") args = parser.parse_args() if not args.debug and ".signed.bin" not in args.firmware[0]: eprint("Expecting firmware to end with '.signed.bin'") return 1 if args.usart is not None: serial_port = usart.SerialPort(args.usart) bootloader_device = _find_and_open_usart_bitbox(serial_port) transport: TransportLayer = usart.U2FUsart(serial_port) bootloader = Bootloader(transport, bootloader_device) else: bootloader_device, transport = _find_and_open_usb_bitbox02() bootloader = Bootloader(transport, bootloader_device) with open(args.firmware[0], "rb") as file: firmware = file.read() def progress(perc: float) -> None: sys.stdout.write(f"{perc*100:.02f}%\r") if bootloader.erased(): print("device contains NO firmware") else: print("firmware version: %d\nsigning pubkeys version: %d" % bootloader.versions()) firmware_hash, signing_keydata_hash = bootloader.get_hashes() print("firmware hash:", firmware_hash.hex()) print("signing keydata hash:", signing_keydata_hash.hex()) if args.debug: bootloader.flash_unsigned_firmware(firmware, progress) else: bootloader.flash_signed_firmware(firmware, progress) print() # print a newline sleep(1) # Pause to show the upgrade finished at 100% bootloader.reboot() return 0
def _find_and_open_usart_bitbox(serial_port: usart.SerialPort) -> devices.DeviceInfo: """ Connects to a BitBoxBase bootloader over UART. If the BitBoxBase is currently running a firmware, it will be rebooted and this function will connect to the bootloader when it shows up. """ print("Connecting to BitBox bootloader over UART.") bootloader_device: devices.DeviceInfo = get_bitboxbase_default_device(serial_port.port) # First, try to connect to the bootloader directly. bootloader_status = _try_usart_bootloader_connection(serial_port, bootloader_device) if bootloader_status == UsartBootloaderProbeResult.SUCCESS: return bootloader_device if bootloader_status == UsartBootloaderProbeResult.TIMEOUT: print("No reponse from BitBox. Maybe it's not connected properly?") sys.exit(1) # The bootloader wasn't valid, try to connect to the firmware instead. print("BitBox bootloader not available.") print("Trying to connect to BitBox firmware instead...") def _show_pairing(code: str) -> bool: print("(Pairing should be automatic) Pairing code:") print(code) return True try: transport = usart.U2FUsart(serial_port) bitbox_attempt = BitBoxBase( transport, bootloader_device, show_pairing_callback=_show_pairing ) print("Connected. Rebooting.") bitbox_attempt.reboot() except usart.U2FUsartTimeoutError: pass finally: bitbox_attempt.close() print("Reboot completed.") # wait for it to reboot while True: bootloader_status = _try_usart_bootloader_connection(serial_port, bootloader_device) if bootloader_status == UsartBootloaderProbeResult.SUCCESS: return bootloader_device if bootloader_status == UsartBootloaderProbeResult.TIMEOUT: print("Waiting for the BitBox bootloader to show up...") sleep(1) else: print("Stuck in bitbox mode - didn't reboot properly!")
def _try_usart_bootloader_connection( serial_port: usart.SerialPort, bootloader_device: devices.DeviceInfo ) -> UsartBootloaderProbeResult: """ Probes the connection to a BitBoxBase bootloader over the specified UART port. """ transport = usart.U2FUsart(serial_port) try: bootloader_attempt = Bootloader(transport, bootloader_device) bootloader_attempt.versions() success = UsartBootloaderProbeResult.SUCCESS except usart.U2FUsartErrorResponse as err: if err.error_code != usart.U2FUsartErrorResponse.ENDPOINT_UNAVAILABLE: raise success = UsartBootloaderProbeResult.NOT_AVAILABLE except usart.U2FUsartTimeoutError: success = UsartBootloaderProbeResult.TIMEOUT finally: bootloader_attempt.close() return success