diff --git a/CHANGELOG.md b/CHANGELOG.md index c6dbd97d3..af749713d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -151,9 +151,10 @@ customers cannot upgrade their bootloader, its changes are recorded separately. ## Bootloader -### v1.0.7 +### v1.1.0 - Update manufacturer HID descriptor to bitbox.swiss - Remove qtouch code from production bootloader +- Implement OP_HARDWARE endpoint, to identify the secure chip model ### v1.0.6 - Replace root pubkeys diff --git a/CMakeLists.txt b/CMakeLists.txt index 17e5672a3..c365b92eb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -93,7 +93,7 @@ endif() # Example 'v1.0.0'. They MUST not contain a pre-release label such as '-beta'. set(FIRMWARE_VERSION "v9.22.0") set(FIRMWARE_BTC_ONLY_VERSION "v9.22.0") -set(BOOTLOADER_VERSION "v1.0.7") +set(BOOTLOADER_VERSION "v1.1.0") find_package(PythonInterp 3.6 REQUIRED) diff --git a/py/bitbox02/bitbox02/bitbox02/bootloader.py b/py/bitbox02/bitbox02/bitbox02/bootloader.py index 64c83ae44..07d32cb85 100644 --- a/py/bitbox02/bitbox02/bitbox02/bootloader.py +++ b/py/bitbox02/bitbox02/bitbox02/bootloader.py @@ -18,9 +18,12 @@ import io import math import hashlib +import enum +from typing import TypedDict from bitbox02.communication import TransportLayer -from bitbox02.communication.devices import DeviceInfo + +from bitbox02.communication.devices import DeviceInfo, parse_device_version BOOTLOADER_CMD = 0x80 + 0x40 + 0x03 NUM_ROOT_KEYS = 3 @@ -44,6 +47,19 @@ SIGDATA_LEN = SIGNING_PUBKEYS_DATA_LEN + FIRMWARE_DATA_LEN +class SecureChipModel(enum.Enum): + """Secure chip model variants for the BitBox02 platform.""" + + ATECC = "ATECC" + OPTIGA = "Optiga" + + +class Hardware(TypedDict): + """Hardware configuration containing secure chip model information.""" + + secure_chip_model: SecureChipModel + + def parse_signed_firmware(firmware: bytes) -> typing.Tuple[bytes, bytes, bytes]: """ Split raw firmware bytes into magic, sigdata and firmware @@ -75,6 +91,10 @@ def __init__(self, transport: TransportLayer, device_info: DeviceInfo): "bb02btc-bootloader": SIGDATA_MAGIC_BTCONLY, "bitboxbase-bootloader": SIGDATA_MAGIC_BITBOXBASE_STANDARD, }.get(device_info["product_string"]) + self.version = parse_device_version(device_info["serial_number"]) + # Delete the prelease part, as it messes with the comparison (e.g. 3.0.0-pre < 3.0.0 is + # True, but the 3.0.0-pre has already the same API breaking changes like 3.0.0...). + self.version = self.version.replace(prerelease=None) assert self.expected_magic def _query(self, msg: bytes) -> bytes: @@ -94,6 +114,25 @@ def versions(self) -> typing.Tuple[int, int]: firmware_v, signing_pubkeys_v = struct.unpack(" Hardware: + """ + Returns (hardware variant). + """ + secure_chip: SecureChipModel = SecureChipModel.ATECC + + # Previous bootloader versions do not support the call and have ATECC SC. + if self.version >= "1.1.0": + response = self._query(b"W") + response_code = response[:1] + + if response_code == b"\x00": + secure_chip = SecureChipModel.ATECC + elif response_code == b"\x01": + secure_chip = SecureChipModel.OPTIGA + else: + raise ValueError(f"Unrecognized securechip model: {response_code}") + return {"secure_chip_model": secure_chip} + def get_hashes( self, display_firmware_hash: bool = False, display_signing_keydata_hash: bool = False ) -> typing.Tuple[bytes, bytes]: diff --git a/py/bitbox02/bitbox02/communication/bitbox_api_protocol.py b/py/bitbox02/bitbox02/communication/bitbox_api_protocol.py index 51a814858..a067a53b5 100644 --- a/py/bitbox02/bitbox02/communication/bitbox_api_protocol.py +++ b/py/bitbox02/bitbox02/communication/bitbox_api_protocol.py @@ -557,9 +557,7 @@ def __init__( # Delete the prelease part, as it messes with the comparison (e.g. 3.0.0-pre < 3.0.0 is # True, but the 3.0.0-pre has already the same API breaking changes like 3.0.0...). - self.version = semver.VersionInfo( - self.version.major, self.version.minor, self.version.patch, build=self.version.build - ) + self.version = self.version.replace(prerelease=None) # raises exceptions if the library is out of date self._check_max_version() diff --git a/py/send_message.py b/py/send_message.py index 617408d8d..89821c534 100755 --- a/py/send_message.py +++ b/py/send_message.py @@ -1482,6 +1482,11 @@ def _get_versions(self) -> None: version = self._device.versions() print(f"Firmware version: {version[0]}, Pubkeys version: {version[1]}") + def _get_hardware(self) -> None: + secure_chip = self._device.hardware()["secure_chip_model"] + print(f"Hardware variant:") + print(f"- Secure Chip: {secure_chip.value}") + def _erase(self) -> None: self._device.erase() @@ -1506,6 +1511,7 @@ def _menu(self) -> None: choices = ( ("Boot", self._boot), ("Print versions", self._get_versions), + ("Print hardware variant", self._get_hardware), ("Erase firmware", self._erase), ("Show firmware hash at startup", self._show_fw_hash), ("Don't show firmware hash at startup", self._dont_show_fw_hash), diff --git a/src/bootloader/bootloader.c b/src/bootloader/bootloader.c index 9636ac9da..ca27d4e27 100644 --- a/src/bootloader/bootloader.c +++ b/src/bootloader/bootloader.c @@ -67,6 +67,8 @@ #define OP_SCREEN_ROTATE ((uint8_t)'f') /* 0x66 */ // OP_SET_SHOW_FIRMWARE_HASH - Enable or disable the flag to automatically show the firmware hash. #define OP_SET_SHOW_FIRMWARE_HASH ((uint8_t)'H') /* 0x4A */ +// OP_HARDWARE - Return the secure chip variant. +#define OP_HARDWARE ((uint8_t)'W') /* 0x57 */ // API return codes #define OP_STATUS_OK ((uint8_t)0) @@ -766,6 +768,16 @@ static size_t _api_screen_rotate(uint8_t* output) return _report_status(OP_STATUS_OK, output); } +static size_t _api_hardware(uint8_t* output) +{ + uint8_t type = 0; + if (memory_get_securechip_type() == MEMORY_SECURECHIP_TYPE_OPTIGA) { + type = 1; + } + output[BOOT_OP_LEN] = type; + return _report_status(OP_STATUS_OK, output) + 1; +} + static size_t _api_command(const uint8_t* input, uint8_t* output, const size_t max_out_len) { memset(output, 0, max_out_len); @@ -809,6 +821,9 @@ static size_t _api_command(const uint8_t* input, uint8_t* output, const size_t m case OP_SCREEN_ROTATE: len = _api_screen_rotate(output); break; + case OP_HARDWARE: + len = _api_hardware(output); + break; default: len = _report_status(OP_STATUS_ERR_INVALID_CMD, output); _loading_ready = false;