Skip to content

Commit

Permalink
bootloader: add OP_HARDWARE api endpoint
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Beerosagos committed Jan 21, 2025
1 parent d94935e commit b16fa93
Show file tree
Hide file tree
Showing 6 changed files with 65 additions and 6 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
41 changes: 40 additions & 1 deletion py/bitbox02/bitbox02/bitbox02/bootloader.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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:
Expand All @@ -94,6 +114,25 @@ def versions(self) -> typing.Tuple[int, int]:
firmware_v, signing_pubkeys_v = struct.unpack("<II", response[:8])
return firmware_v, signing_pubkeys_v

def hardware(self) -> 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]:
Expand Down
4 changes: 1 addition & 3 deletions py/bitbox02/bitbox02/communication/bitbox_api_protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
6 changes: 6 additions & 0 deletions py/send_message.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand All @@ -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),
Expand Down
15 changes: 15 additions & 0 deletions src/bootloader/bootloader.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down

0 comments on commit b16fa93

Please sign in to comment.