Skip to content

Commit 699fba6

Browse files
committed
bootloader: add OP_HARDWARE api endpoint
This will allow the bbapp to flash different FW versions, depending on the PCB variants. The initial implementation will respond with a single byte that identifies the Secure Chip: 0 for ATECC, 1 for Optiga.
1 parent d94935e commit 699fba6

File tree

6 files changed

+65
-6
lines changed

6 files changed

+65
-6
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,9 +151,10 @@ customers cannot upgrade their bootloader, its changes are recorded separately.
151151

152152
## Bootloader
153153

154-
### v1.0.7
154+
### v1.1.0
155155
- Update manufacturer HID descriptor to bitbox.swiss
156156
- Remove qtouch code from production bootloader
157+
- Implement OP_HARDWARE endpoint, to identify the secure chip model
157158

158159
### v1.0.6
159160
- Replace root pubkeys

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ endif()
9393
# Example 'v1.0.0'. They MUST not contain a pre-release label such as '-beta'.
9494
set(FIRMWARE_VERSION "v9.22.0")
9595
set(FIRMWARE_BTC_ONLY_VERSION "v9.22.0")
96-
set(BOOTLOADER_VERSION "v1.0.7")
96+
set(BOOTLOADER_VERSION "v1.1.0")
9797

9898
find_package(PythonInterp 3.6 REQUIRED)
9999

py/bitbox02/bitbox02/bitbox02/bootloader.py

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,12 @@
1818
import io
1919
import math
2020
import hashlib
21+
import enum
22+
from typing import TypedDict
2123

2224
from bitbox02.communication import TransportLayer
23-
from bitbox02.communication.devices import DeviceInfo
25+
26+
from bitbox02.communication.devices import DeviceInfo, parse_device_version
2427

2528
BOOTLOADER_CMD = 0x80 + 0x40 + 0x03
2629
NUM_ROOT_KEYS = 3
@@ -44,6 +47,19 @@
4447
SIGDATA_LEN = SIGNING_PUBKEYS_DATA_LEN + FIRMWARE_DATA_LEN
4548

4649

50+
class SecureChipModel(enum.Enum):
51+
"""Secure chip model variants for the BitBox02 platform."""
52+
53+
ATECC = "ATECC"
54+
OPTIGA = "Optiga"
55+
56+
57+
class Hardware(TypedDict):
58+
"""Hardware configuration containing secure chip model information."""
59+
60+
secure_chip_model: SecureChipModel
61+
62+
4763
def parse_signed_firmware(firmware: bytes) -> typing.Tuple[bytes, bytes, bytes]:
4864
"""
4965
Split raw firmware bytes into magic, sigdata and firmware
@@ -75,6 +91,10 @@ def __init__(self, transport: TransportLayer, device_info: DeviceInfo):
7591
"bb02btc-bootloader": SIGDATA_MAGIC_BTCONLY,
7692
"bitboxbase-bootloader": SIGDATA_MAGIC_BITBOXBASE_STANDARD,
7793
}.get(device_info["product_string"])
94+
self.version = parse_device_version(device_info["serial_number"])
95+
# Delete the prelease part, as it messes with the comparison (e.g. 3.0.0-pre < 3.0.0 is
96+
# True, but the 3.0.0-pre has already the same API breaking changes like 3.0.0...).
97+
self.version = self.version.replace(prerelease=None)
7898
assert self.expected_magic
7999

80100
def _query(self, msg: bytes) -> bytes:
@@ -94,6 +114,25 @@ def versions(self) -> typing.Tuple[int, int]:
94114
firmware_v, signing_pubkeys_v = struct.unpack("<II", response[:8])
95115
return firmware_v, signing_pubkeys_v
96116

117+
def hardware(self) -> Hardware:
118+
"""
119+
Returns (hardware variant).
120+
"""
121+
secure_chip: SecureChipModel = SecureChipModel.ATECC
122+
123+
# Previous bootloader versions do not support the call and have ATECC SC.
124+
if self.version >= "1.1.0":
125+
response = self._query(b"W")
126+
response_code = response[:1]
127+
128+
if response_code == b"\x00":
129+
secure_chip = SecureChipModel.ATECC
130+
elif response_code == b"\x01":
131+
secure_chip = SecureChipModel.OPTIGA
132+
else:
133+
raise ValueError(f"Unrecognized securechip model: {response_code!r}")
134+
return {"secure_chip_model": secure_chip}
135+
97136
def get_hashes(
98137
self, display_firmware_hash: bool = False, display_signing_keydata_hash: bool = False
99138
) -> typing.Tuple[bytes, bytes]:

py/bitbox02/bitbox02/communication/bitbox_api_protocol.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -557,9 +557,7 @@ def __init__(
557557

558558
# Delete the prelease part, as it messes with the comparison (e.g. 3.0.0-pre < 3.0.0 is
559559
# True, but the 3.0.0-pre has already the same API breaking changes like 3.0.0...).
560-
self.version = semver.VersionInfo(
561-
self.version.major, self.version.minor, self.version.patch, build=self.version.build
562-
)
560+
self.version = self.version.replace(prerelease=None)
563561

564562
# raises exceptions if the library is out of date
565563
self._check_max_version()

py/send_message.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1482,6 +1482,11 @@ def _get_versions(self) -> None:
14821482
version = self._device.versions()
14831483
print(f"Firmware version: {version[0]}, Pubkeys version: {version[1]}")
14841484

1485+
def _get_hardware(self) -> None:
1486+
secure_chip = self._device.hardware()["secure_chip_model"]
1487+
print(f"Hardware variant:")
1488+
print(f"- Secure Chip: {secure_chip.value}")
1489+
14851490
def _erase(self) -> None:
14861491
self._device.erase()
14871492

@@ -1506,6 +1511,7 @@ def _menu(self) -> None:
15061511
choices = (
15071512
("Boot", self._boot),
15081513
("Print versions", self._get_versions),
1514+
("Print hardware variant", self._get_hardware),
15091515
("Erase firmware", self._erase),
15101516
("Show firmware hash at startup", self._show_fw_hash),
15111517
("Don't show firmware hash at startup", self._dont_show_fw_hash),

src/bootloader/bootloader.c

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@
6767
#define OP_SCREEN_ROTATE ((uint8_t)'f') /* 0x66 */
6868
// OP_SET_SHOW_FIRMWARE_HASH - Enable or disable the flag to automatically show the firmware hash.
6969
#define OP_SET_SHOW_FIRMWARE_HASH ((uint8_t)'H') /* 0x4A */
70+
// OP_HARDWARE - Return the secure chip variant.
71+
#define OP_HARDWARE ((uint8_t)'W') /* 0x57 */
7072

7173
// API return codes
7274
#define OP_STATUS_OK ((uint8_t)0)
@@ -766,6 +768,16 @@ static size_t _api_screen_rotate(uint8_t* output)
766768
return _report_status(OP_STATUS_OK, output);
767769
}
768770

771+
static size_t _api_hardware(uint8_t* output)
772+
{
773+
uint8_t type = 0;
774+
if (memory_get_securechip_type() == MEMORY_SECURECHIP_TYPE_OPTIGA) {
775+
type = 1;
776+
}
777+
output[BOOT_OP_LEN] = type;
778+
return _report_status(OP_STATUS_OK, output) + 1;
779+
}
780+
769781
static size_t _api_command(const uint8_t* input, uint8_t* output, const size_t max_out_len)
770782
{
771783
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
809821
case OP_SCREEN_ROTATE:
810822
len = _api_screen_rotate(output);
811823
break;
824+
case OP_HARDWARE:
825+
len = _api_hardware(output);
826+
break;
812827
default:
813828
len = _report_status(OP_STATUS_ERR_INVALID_CMD, output);
814829
_loading_ready = false;

0 commit comments

Comments
 (0)