Merge branch 'nickez/bootloader-version-in-info'
What changed, and why it matters
This commit adds a new read-only piece of information to the BitBox02 device info API: the installed bootloader's marketing version string. It does not change how the device boots, signs, or verifies anything. It is purely an informational feature for apps and users to see which bootloader is installed, and it is not treated by the authors as a security fix.
No security action required; review as normal feature code if desired.
Security signals we found
No security-relevant behavioral change: read-only API addition
Input validation present: header magic check and length clamp before memcpy
No privilege escalation, authentication bypass, or cryptographic change
No vendor disclosure of security relevance in commit or changelog
Evidence from the diff
The change exposes bootloader_version in the DeviceInfoResponse protobuf by reading the stage1 bootloader header’s stage1_marketing_version field from flash. It adds memory_get_bootloader_version() in C, binds it through Rust FFI, and returns it via the device-info HWW API. The implementation validates the header magic and version length before copying, and returns None/false for legacy bootloaders or invalid headers. Supporting build, fake-memory, and unit-test changes are included.
Changed components
messages/bitbox02_system.protosrc/memory/memory.csrc/rust/bitbox02/src/memory.rssrc/rust/bitbox02-rust/src/hww/api/device_info.rspy/bitbox02/bitbox02/bitbox02/bitbox02.pyInspect captured patch +276 / −22
### CHANGELOG.md
@@ -9,6 +9,7 @@ customers cannot upgrade their bootloader, its changes are recorded separately.
### [Unreleased]
### v9.27.1
+- API: include the installed bootloader version in the device info response
- Ethereum: display the EIP-712 message type before signing
- Bitcoin: allow signing messages with keys in the m/48' application namespace
### messages/bitbox02_system.proto
@@ -34,6 +34,8 @@ message DeviceInfoResponse {
// From v9.25.0. This together with `securechip_model` determines the password stretching
// algorithm.
string password_stretching_algo = 8;
+ // Marketing version of the installed stage1 bootloader. Not present on legacy bootloaders.
+ optional string bootloader_version = 9;
}
message InsertRemoveSDCardRequest {
### py/bitbox02/CHANGELOG.md
@@ -1,6 +1,7 @@
# Changelog
## [Unreleased]
+- `device_info()`: add the installed bootloader version to the returned device info
- Add `btc_xpubs()` to fetch multiple xpubs at once
- Bitcoin: add support for OP_RETURN outputs
- Add `change_password()`
### py/bitbox02/bitbox02/bitbox02/bitbox02.py
@@ -162,9 +162,14 @@ def device_info(self) -> Dict[str, Any]:
device_info_request = bitbox02_system.DeviceInfoRequest()
request.device_info.CopyFrom(device_info_request)
response = self._msg_query(request, expected_response="device_info")
- result = {
+ result: Dict[str, Any] = {
"name": response.device_info.name,
"version": response.device_info.version,
+ "bootloader_version": (
+ response.device_info.bootloader_version
+ if response.device_info.HasField("bootloader_version")
+ else None
+ ),
"initialized": response.device_info.initialized,
"mnemonic_passphrase_enabled": response.device_info.mnemonic_passphrase_enabled,
"monotonic_increments_remaining": response.device_info.monotonic_increments_remaining,
### py/bitbox02/bitbox02/communication/generated/bitbox02_system_pb2.py
@@ -13,7 +13,7 @@
-DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x15\x62itbox02_system.proto\x12\x14shiftcrypto.bitbox02\"\x14\n\x12\x43heckSDCardRequest\"\'\n\x13\x43heckSDCardResponse\x12\x10\n\x08inserted\x18\x01 \x01(\x08\"\x13\n\x11\x44\x65viceInfoRequest\"\xfa\x02\n\x12\x44\x65viceInfoResponse\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x13\n\x0binitialized\x18\x02 \x01(\x08\x12\x0f\n\x07version\x18\x03 \x01(\t\x12#\n\x1bmnemonic_passphrase_enabled\x18\x04 \x01(\x08\x12&\n\x1emonotonic_increments_remaining\x18\x05 \x01(\r\x12\x18\n\x10securechip_model\x18\x06 \x01(\t\x12J\n\tbluetooth\x18\x07 \x01(\x0b\x32\x32.shiftcrypto.bitbox02.DeviceInfoResponse.BluetoothH\x00\x88\x01\x01\x12 \n\x18password_stretching_algo\x18\x08 \x01(\t\x1aM\n\tBluetooth\x12\x15\n\rfirmware_hash\x18\x01 \x01(\x0c\x12\x18\n\x10\x66irmware_version\x18\x02 \x01(\t\x12\x0f\n\x07\x65nabled\x18\x03 \x01(\x08\x42\x0c\n\n_bluetooth\"\x9b\x01\n\x19InsertRemoveSDCardRequest\x12L\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32<.shiftcrypto.bitbox02.InsertRemoveSDCardRequest.SDCardAction\"0\n\x0cSDCardAction\x12\x0f\n\x0bREMOVE_CARD\x10\x00\x12\x0f\n\x0bINSERT_CARD\x10\x01\"\x0e\n\x0cResetRequest\",\n\x18SetDeviceLanguageRequest\x12\x10\n\x08language\x18\x01 \x01(\t\"$\n\x14SetDeviceNameRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\"%\n\x12SetPasswordRequest\x12\x0f\n\x07\x65ntropy\x18\x01 \x01(\x0c\"\x17\n\x15\x43hangePasswordRequestb\x06proto3')
+DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x15\x62itbox02_system.proto\x12\x14shiftcrypto.bitbox02\"\x14\n\x12\x43heckSDCardRequest\"\'\n\x13\x43heckSDCardResponse\x12\x10\n\x08inserted\x18\x01 \x01(\x08\"\x13\n\x11\x44\x65viceInfoRequest\"\xb2\x03\n\x12\x44\x65viceInfoResponse\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x13\n\x0binitialized\x18\x02 \x01(\x08\x12\x0f\n\x07version\x18\x03 \x01(\t\x12#\n\x1bmnemonic_passphrase_enabled\x18\x04 \x01(\x08\x12&\n\x1emonotonic_increments_remaining\x18\x05 \x01(\r\x12\x18\n\x10securechip_model\x18\x06 \x01(\t\x12J\n\tbluetooth\x18\x07 \x01(\x0b\x32\x32.shiftcrypto.bitbox02.DeviceInfoResponse.BluetoothH\x00\x88\x01\x01\x12 \n\x18password_stretching_algo\x18\x08 \x01(\t\x12\x1f\n\x12\x62ootloader_version\x18\t \x01(\tH\x01\x88\x01\x01\x1aM\n\tBluetooth\x12\x15\n\rfirmware_hash\x18\x01 \x01(\x0c\x12\x18\n\x10\x66irmware_version\x18\x02 \x01(\t\x12\x0f\n\x07\x65nabled\x18\x03 \x01(\x08\x42\x0c\n\n_bluetoothB\x15\n\x13_bootloader_version\"\x9b\x01\n\x19InsertRemoveSDCardRequest\x12L\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32<.shiftcrypto.bitbox02.InsertRemoveSDCardRequest.SDCardAction\"0\n\x0cSDCardAction\x12\x0f\n\x0bREMOVE_CARD\x10\x00\x12\x0f\n\x0bINSERT_CARD\x10\x01\"\x0e\n\x0cResetRequest\",\n\x18SetDeviceLanguageRequest\x12\x10\n\x08language\x18\x01 \x01(\t\"$\n\x14SetDeviceNameRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\"%\n\x12SetPasswordRequest\x12\x0f\n\x07\x65ntropy\x18\x01 \x01(\x0c\"\x17\n\x15\x43hangePasswordRequestb\x06proto3')
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'bitbox02_system_pb2', globals())
@@ -27,21 +27,21 @@
_DEVICEINFOREQUEST._serialized_start=110
_DEVICEINFOREQUEST._serialized_end=129
_DEVICEINFORESPONSE._serialized_start=132
- _DEVICEINFORESPONSE._serialized_end=510
- _DEVICEINFORESPONSE_BLUETOOTH._serialized_start=419
- _DEVICEINFORESPONSE_BLUETOOTH._serialized_end=496
- _INSERTREMOVESDCARDREQUEST._serialized_start=513
- _INSERTREMOVESDCARDREQUEST._serialized_end=668
- _INSERTREMOVESDCARDREQUEST_SDCARDACTION._serialized_start=620
- _INSERTREMOVESDCARDREQUEST_SDCARDACTION._serialized_end=668
- _RESETREQUEST._serialized_start=670
- _RESETREQUEST._serialized_end=684
- _SETDEVICELANGUAGEREQUEST._serialized_start=686
- _SETDEVICELANGUAGEREQUEST._serialized_end=730
- _SETDEVICENAMEREQUEST._serialized_start=732
- _SETDEVICENAMEREQUEST._serialized_end=768
- _SETPASSWORDREQUEST._serialized_start=770
- _SETPASSWORDREQUEST._serialized_end=807
- _CHANGEPASSWORDREQUEST._serialized_start=809
- _CHANGEPASSWORDREQUEST._serialized_end=832
+ _DEVICEINFORESPONSE._serialized_end=566
+ _DEVICEINFORESPONSE_BLUETOOTH._serialized_start=452
+ _DEVICEINFORESPONSE_BLUETOOTH._serialized_end=529
+ _INSERTREMOVESDCARDREQUEST._serialized_start=569
+ _INSERTREMOVESDCARDREQUEST._serialized_end=724
+ _INSERTREMOVESDCARDREQUEST_SDCARDACTION._serialized_start=676
+ _INSERTREMOVESDCARDREQUEST_SDCARDACTION._serialized_end=724
+ _RESETREQUEST._serialized_start=726
+ _RESETREQUEST._serialized_end=740
+ _SETDEVICELANGUAGEREQUEST._serialized_start=742
+ _SETDEVICELANGUAGEREQUEST._serialized_end=786
+ _SETDEVICENAMEREQUEST._serialized_start=788
+ _SETDEVICENAMEREQUEST._serialized_end=824
+ _SETPASSWORDREQUEST._serialized_start=826
+ _SETPASSWORDREQUEST._serialized_end=863
+ _CHANGEPASSWORDREQUEST._serialized_start=865
+ _CHANGEPASSWORDREQUEST._serialized_end=888
# @@protoc_insertion_point(module_scope)
### py/bitbox02/bitbox02/communication/generated/bitbox02_system_pb2.pyi
@@ -86,6 +86,7 @@ class DeviceInfoResponse(google.protobuf.message.Message):
SECURECHIP_MODEL_FIELD_NUMBER: builtins.int
BLUETOOTH_FIELD_NUMBER: builtins.int
PASSWORD_STRETCHING_ALGO_FIELD_NUMBER: builtins.int
+ BOOTLOADER_VERSION_FIELD_NUMBER: builtins.int
name: builtins.str
initialized: builtins.bool
version: builtins.str
@@ -97,6 +98,8 @@ class DeviceInfoResponse(google.protobuf.message.Message):
"""From v9.25.0. This together with `securechip_model` determines the password stretching
algorithm.
"""
+ bootloader_version: builtins.str
+ """Marketing version of the installed stage1 bootloader. Not present on legacy bootloaders."""
@property
def bluetooth(self) -> global___DeviceInfoResponse.Bluetooth:
"""Only present in Bluetooth-enabled devices."""
@@ -112,10 +115,14 @@ class DeviceInfoResponse(google.protobuf.message.Message):
securechip_model: builtins.str = ...,
bluetooth: global___DeviceInfoResponse.Bluetooth | None = ...,
password_stretching_algo: builtins.str = ...,
+ bootloader_version: builtins.str | None = ...,
) -> None: ...
- def HasField(self, field_name: typing.Literal["_bluetooth", b"_bluetooth", "bluetooth", b"bluetooth"]) -> builtins.bool: ...
- def ClearField(self, field_name: typing.Literal["_bluetooth", b"_bluetooth", "bluetooth", b"bluetooth", "initialized", b"initialized", "mnemonic_passphrase_enabled", b"mnemonic_passphrase_enabled", "monotonic_increments_remaining", b"monotonic_increments_remaining", "name", b"name", "password_stretching_algo", b"password_stretching_algo", "securechip_model", b"securechip_model", "version", b"version"]) -> None: ...
+ def HasField(self, field_name: typing.Literal["_bluetooth", b"_bluetooth", "_bootloader_version", b"_bootloader_version", "bluetooth", b"bluetooth", "bootloader_version", b"bootloader_version"]) -> builtins.bool: ...
+ def ClearField(self, field_name: typing.Literal["_bluetooth", b"_bluetooth", "_bootloader_version", b"_bootloader_version", "bluetooth", b"bluetooth", "bootloader_version", b"bootloader_version", "initialized", b"initialized", "mnemonic_passphrase_enabled", b"mnemonic_passphrase_enabled", "monotonic_increments_remaining", b"monotonic_increments_remaining", "name", b"name", "password_stretching_algo", b"password_stretching_algo", "securechip_model", b"securechip_model", "version", b"version"]) -> None: ...
+ @typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_bluetooth", b"_bluetooth"]) -> typing.Literal["bluetooth"] | None: ...
+ @typing.overload
+ def WhichOneof(self, oneof_group: typing.Literal["_bootloader_version", b"_bootloader_version"]) -> typing.Literal["bootloader_version"] | None: ...
global___DeviceInfoResponse = DeviceInfoResponse
### src/bootloader/stage0/stage0.c
@@ -2,6 +2,7 @@
#include "bootloader/boot_args.h"
#include "bootloader_upgrade/bootloader_upgrade.h"
+#include <bootloader/bootloader_product.h>
#ifndef BB02_STAGE0_DEVELOPMENT
#include "bootloader_upgrade/stage1_pubkeys.h"
#endif
### src/bootloader/stage0/stage0_descriptor.c
@@ -2,6 +2,7 @@
#include "stage0_descriptor.h"
#include "bootloader_upgrade/bootloader_upgrade.h"
+#include <bootloader/bootloader_product.h>
#ifdef BB02_STAGE0_DEVELOPMENT
#define BB02_STAGE0_DESCRIPTOR_FLAGS BB02_STAGE0_FLAG_DEVELOPMENT
### src/bootloader/stage0/stage1_sigcheck.c
@@ -3,6 +3,7 @@
#include "stage1_sigcheck.h"
#include "pukcc/curve_p256.h"
#include "pukcc/pukcc.h"
+#include <bootloader/bootloader_product.h>
#include <hal_sha_sync.h>
#include <stdbool.h>
#include <stddef.h>
### src/bootloader/stage1_header.c
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: Apache-2.0
#include "bootloader_upgrade/bootloader_upgrade.h"
+#include <bootloader/bootloader_product.h>
#include <bootloader/bootloader_version.h>
_Static_assert(
### src/bootloader_upgrade/bootloader_upgrade.h
@@ -3,7 +3,6 @@
#ifndef _BOOTLOADER_UPGRADE_H_
#define _BOOTLOADER_UPGRADE_H_
-#include <bootloader/bootloader_product.h>
#include <stddef.h>
#include <stdint.h>
### src/bootloader_upgrade/firmware_installer.c
@@ -14,6 +14,7 @@
#include "stage1_pubkeys.h"
#endif
#include "system.h"
+#include <bootloader/bootloader_product.h>
#include <hal_flash.h>
#include <sam.h>
#include <stdbool.h>
### src/bootloader_upgrade/firmware_installer_check.c
@@ -2,6 +2,7 @@
#include "firmware_installer_check.h"
#include "bootloader_upgrade.h"
+#include <bootloader/bootloader_product.h>
#include <string.h>
_Static_assert(
### src/memory/memory.c
@@ -14,6 +14,8 @@
#include "util.h"
#include <rust/rust.h>
+#include "bootloader_upgrade/bootloader_upgrade.h"
+
#ifndef TESTING
#include <hal_delay.h>
#else
@@ -33,6 +35,17 @@ static_assert(
static_assert(
MEMORY_MULTISIG_NAME_MAX_LEN_WITH_NULL == 31,
"MEMORY_MULTISIG_NAME_MAX_LEN_WITH_NULL must remain 31.");
+static_assert(
+ MEMORY_BOOTLOADER_VERSION_MAX_LEN == BB02_STAGE1_HEADER_STAGE1_MARKETING_VERSION_MAX_LEN,
+ "bootloader version maximum length mismatch");
+static_assert(
+ BB02_STAGE1_HEADER_ADDR >= FLASH_BOOT_START,
+ "stage1 header must be inside the readable bootloader area");
+static_assert(
+ BB02_STAGE1_HEADER_ADDR + offsetof(bb02_stage1_header_t, stage1_marketing_version) +
+ BB02_STAGE1_HEADER_STAGE1_MARKETING_VERSION_MAX_LEN <=
+ FLASH_BOOT_START + FLASH_BOOT_LEN,
+ "stage1 marketing version must be inside the readable bootloader area");
// Documentation of all appData chunks and their contents. A chunk is defined as
// 16 pages, which is the erase granularity: changing any byte in the page
@@ -774,6 +787,34 @@ void memory_bootloader_hash(uint8_t* hash_out)
#endif
}
+bool memory_get_bootloader_version(uint8_t* version_out, size_t* version_len_out)
+{
+ if (version_len_out == NULL) {
+ return false;
+ }
+ *version_len_out = 0;
+ if (version_out == NULL) {
+ return false;
+ }
+
+#ifdef TESTING
+ const bb02_stage1_header_t* header =
+ (const bb02_stage1_header_t*)memory_get_bootloader_stage1_header_fake();
+#else
+ const bb02_stage1_header_t* header = bb02_stage1_installed_header();
+#endif
+
+ const uint8_t version_len = header->stage1_marketing_version_len;
+ if (header->magic != BB02_STAGE1_HEADER_MAGIC ||
+ version_len > MEMORY_BOOTLOADER_VERSION_MAX_LEN) {
+ return false;
+ }
+
+ memcpy(version_out, header->stage1_marketing_version, version_len);
+ *version_len_out = version_len;
+ return true;
+}
+
bool memory_bootloader_set_flags(auto_enter_t auto_enter, upside_down_t upside_down)
{
chunk_shared_t chunk = {0};
### src/memory/memory.h
@@ -4,6 +4,7 @@
#define _MEMORY_H_
#include <stdbool.h>
+#include <stddef.h>
#include <stdint.h>
#include "compiler_util.h"
@@ -14,6 +15,9 @@
// How many multisig configurations (accounts) can be registered.
#define MEMORY_MULTISIG_NUM_ENTRIES 25
+// Maximum length of the stage1 bootloader marketing version, excluding a null terminator.
+#define MEMORY_BOOTLOADER_VERSION_MAX_LEN 37
+
typedef enum {
// Legacy/initial value, corresponds to the original Optiga factorysetup config.
MEMORY_OPTIGA_CONFIG_V0,
@@ -201,6 +205,17 @@ USE_RESULT bool memory_get_attestation_pubkey_and_certificate(
*/
void memory_bootloader_hash(uint8_t* hash_out);
+/**
+ * Retrieves the installed stage1 bootloader marketing version.
+ *
+ * @param[out] version_out Buffer that receives the version without a null terminator. Must fit
+ * MEMORY_BOOTLOADER_VERSION_MAX_LEN bytes.
+ * @param[out] version_len_out Number of bytes written to version_out.
+ * @return false if the arguments are invalid or the stage1 header is invalid (including for legacy
+ * bootloaders).
+ */
+USE_RESULT bool memory_get_bootloader_version(uint8_t* version_out, size_t* version_len_out);
+
typedef struct {
secbool_u8 value;
} auto_enter_t;
### src/rust/bitbox-hal/src/memory.rs
@@ -66,6 +66,7 @@ pub trait Memory {
fn ble_enabled(&mut self) -> bool;
fn ble_enable(&mut self, enable: bool) -> Result<(), ()>;
fn get_active_ble_firmware_version(&mut self) -> Result<String, Error>;
+ fn get_bootloader_version(&mut self) -> Option<String>;
fn ble_firmware_flash_chunk(
&mut self,
slot: BleFirmwareSlot,
### src/rust/bitbox-platform-host/src/memory.rs
@@ -13,6 +13,7 @@ pub struct FakeMemory {
ble_metadata: BleMetadata,
ble_firmware_slots: [Vec<u8>; 2],
active_ble_firmware_version: String,
+ bootloader_version: Option<String>,
securechip_type: SecurechipType,
optiga_config_version: OptigaConfigVersion,
platform: Platform,
@@ -60,6 +61,7 @@ impl FakeMemory {
vec![0xff; bitbox_hal::memory::BLE_FIRMWARE_MAX_SIZE],
],
active_ble_firmware_version: "0.0.0".into(),
+ bootloader_version: None,
securechip_type: SecurechipType::Optiga,
optiga_config_version: OptigaConfigVersion::V0,
platform: Platform::BitBox02,
@@ -89,6 +91,10 @@ impl FakeMemory {
self.platform = platform;
}
+ pub fn set_bootloader_version(&mut self, version: Option<&str>) {
+ self.bootloader_version = version.map(String::from);
+ }
+
pub fn set_salt_root(&mut self, salt_root: &[u8; 32]) {
self.salt_root = *salt_root;
}
@@ -132,6 +138,10 @@ impl bitbox_hal::Memory for FakeMemory {
Ok(self.active_ble_firmware_version.clone())
}
+ fn get_bootloader_version(&mut self) -> Option<String> {
+ self.bootloader_version.clone()
+ }
+
fn ble_firmware_flash_chunk(
&mut self,
slot: BleFirmwareSlot,
### src/rust/bitbox-proto/src/generated/shiftcrypto.bitbox02.rs
@@ -139,6 +139,9 @@ pub struct DeviceInfoResponse {
/// algorithm.
#[prost(string, tag = "8")]
pub password_stretching_algo: ::prost::alloc::string::String,
+ /// Marketing version of the installed stage1 bootloader. Not present on legacy bootloaders.
+ #[prost(string, optional, tag = "9")]
+ pub bootloader_version: ::core::option::Option<::prost::alloc::string::String>,
}
/// Nested message and enum types in `DeviceInfoResponse`.
pub mod device_info_response {
### src/rust/bitbox02-rust/src/hww/api/device_info.rs
@@ -48,5 +48,33 @@ pub async fn process(hal: &mut impl crate::hal::Hal) -> Result<Response, Error>
hal_memory::PasswordStretchAlgo::V0 => "V1".into(),
hal_memory::PasswordStretchAlgo::V1 => "V2".into(),
},
+ bootloader_version: hal.memory().get_bootloader_version(),
}))
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ use crate::hal::testing::TestingHal;
+
+ async fn _device_info(hal: &mut TestingHal<'_>) -> pb::DeviceInfoResponse {
+ match process(hal).await.unwrap() {
+ Response::DeviceInfo(response) => response,
+ _ => panic!("unexpected response"),
+ }
+ }
+
+ #[async_test::test]
+ async fn test_process_bootloader_version() {
+ let mut hal = TestingHal::new();
+ hal.memory.set_bootloader_version(Some("v1.2.2"));
+ assert_eq!(
+ _device_info(&mut hal).await.bootloader_version.as_deref(),
+ Some("v1.2.2")
+ );
+
+ hal.memory.set_bootloader_version(None);
+ assert_eq!(_device_info(&mut hal).await.bootloader_version, None);
+ }
+}
### src/rust/bitbox02-sys/build.rs
@@ -22,6 +22,7 @@ const ALLOWLIST_VARS: &[&str] = &[
"MAX_PK_SCRIPT_SIZE",
"MAX_VARINT_SIZE",
"MEMORY_MULTISIG_NUM_ENTRIES",
+ "MEMORY_BOOTLOADER_VERSION_MAX_LEN",
"MEMORY_PLATFORM_BITBOX02_PLUS",
"MEMORY_PLATFORM_BITBOX02",
"MEMORY_SECURECHIP_TYPE_ATECC",
@@ -97,6 +98,7 @@ const ALLOWLIST_FNS: &[&str] = &[
"memory_get_attestation_pubkey_and_certificate",
"memory_get_authorization_key",
"memory_get_ble_metadata",
+ "memory_get_bootloader_version",
"memory_get_device_name",
"memory_get_encrypted_seed_and_hmac",
"memory_get_encryption_key",
### src/rust/bitbox02/src/hal/memory.rs
@@ -114,6 +114,10 @@ impl Memory for BitBox02Memory {
crate::spi_mem::get_active_ble_firmware_version().map_err(|_| Error::Unknown)
}
+ fn get_bootloader_version(&mut self) -> Option<String> {
+ crate::memory::get_bootloader_version()
+ }
+
fn ble_firmware_flash_chunk(
&mut self,
slot: BleFirmwareSlot,
### src/rust/bitbox02/src/memory.rs
@@ -60,6 +60,22 @@ pub fn get_attestation_bootloader_hash() -> [u8; 32] {
hash
}
+pub fn get_bootloader_version() -> Option<String> {
+ let mut version = [0u8; bitbox02_sys::MEMORY_BOOTLOADER_VERSION_MAX_LEN as usize];
+ let mut version_len = 0usize;
+ if !unsafe {
+ bitbox02_sys::memory_get_bootloader_version(version.as_mut_ptr(), &mut version_len)
+ } {
+ return None;
+ }
+ assert!(version_len <= version.len());
+ Some(
+ core::str::from_utf8(&version[..version_len])
+ .unwrap()
+ .into(),
+ )
+}
+
pub fn get_attestation_pubkey_and_certificate(
device_pubkey: &mut [u8; 64],
certificate: &mut [u8; 64],
### src/rust/bitbox03/src/memory.rs
@@ -19,6 +19,10 @@ impl hal::memory::Memory for BitBox03Memory {
todo!()
}
+ fn get_bootloader_version(&mut self) -> Option<alloc::string::String> {
+ None
+ }
+
fn ble_firmware_flash_chunk(
&mut self,
_slot: bitbox_hal::memory::BleFirmwareSlot,
### test/hardware-fakes/include/fake_memory.h
@@ -4,6 +4,7 @@
#define _FAKE_MEMORY_H_
#include <stdbool.h>
+#include <stddef.h>
#include <stdint.h>
#include <flags.h>
@@ -18,4 +19,6 @@ void memory_read_shared_bootdata_fake(uint8_t* chunk_out);
const uint8_t* fake_memory_get_salt_root(void);
void memory_bootloader_hash_fake(uint8_t* hash_out);
void memory_set_bootloader_hash_fake(const uint8_t* fake_hash);
+const uint8_t* memory_get_bootloader_stage1_header_fake(void);
+void memory_set_bootloader_stage1_header_fake(const uint8_t* header, size_t header_len);
#endif
### test/hardware-fakes/src/fake_memory.c
@@ -1,5 +1,6 @@
// SPDX-License-Identifier: Apache-2.0
+#include <bootloader_upgrade/bootloader_upgrade.h>
#include <fake_memory.h>
#include <flags.h>
#include <memory/memory.h>
@@ -17,6 +18,7 @@
static uint8_t _memory_shared_data[FLASH_SHARED_DATA_LEN] = {0};
static uint8_t _memory_app_data[FLASH_APPDATA_LEN] = {0};
static uint8_t _memory_smarteeprom[SMARTEEPROM_RESERVED_FLASH_PAGES * FLASH_PAGE_SIZE] = {0};
+static bb02_stage1_header_t _bootloader_stage1_header = {0};
static void _init_file_if_needed(
const char* base_path,
@@ -51,6 +53,7 @@ void fake_memory_factoryreset(void)
memset(_memory_shared_data, 0xff, sizeof(_memory_shared_data));
memset(_memory_app_data, 0xff, sizeof(_memory_app_data));
memset(_memory_smarteeprom, 0xff, sizeof(_memory_smarteeprom));
+ memset(&_bootloader_stage1_header, 0, sizeof(_bootloader_stage1_header));
const char* base_path = getenv(FAKE_MEMORY_ENV_VAR);
if (base_path) {
@@ -239,3 +242,15 @@ void memory_set_bootloader_hash_fake(const uint8_t* fake_hash)
// NOLINTNEXTLINE(bugprone-not-null-terminated-result)
memcpy(_bootloader_hash, fake_hash, sizeof(_bootloader_hash));
}
+
+const uint8_t* memory_get_bootloader_stage1_header_fake(void)
+{
+ return (const uint8_t*)&_bootloader_stage1_header;
+}
+
+void memory_set_bootloader_stage1_header_fake(const uint8_t* header, size_t header_len)
+{
+ assert(header != NULL);
+ assert(header_len == sizeof(_bootloader_stage1_header));
+ memcpy(&_bootloader_stage1_header, header, sizeof(_bootloader_stage1_header));
+}
### test/unit-test/test_bootloader_upgrade_check.c
@@ -7,6 +7,7 @@
#include <string.h>
#include <cmocka.h>
+#include "bootloader/bootloader_product.h"
#include "bootloader/stage0/stage0_descriptor.h"
#include "bootloader_upgrade/bootloader_upgrade.h"
#include "bootloader_upgrade/firmware_installer_check.h"
### test/unit-test/test_memory.c
@@ -5,6 +5,8 @@
#include <stddef.h>
#include <cmocka.h>
+#include <bootloader_upgrade/bootloader_upgrade.h>
+#include <fake_memory.h>
#include <memory/memory.h>
#include <memory/memory_shared.h>
#include <rust/rust.h>
@@ -590,6 +592,88 @@ static void _test_memory_set_seed_birthdate(void** state)
assert_true(memory_set_seed_birthdate(timestamp));
}
+static bb02_stage1_header_t _make_stage1_header(const uint8_t* version, size_t version_len)
+{
+ assert_true(version_len <= BB02_STAGE1_HEADER_STAGE1_MARKETING_VERSION_MAX_LEN);
+ bb02_stage1_header_t header = {
+ .magic = BB02_STAGE1_HEADER_MAGIC,
+ .header_len = BB02_STAGE1_HEADER_LEN,
+ .image_len = BB02_STAGE1_HEADER_LEN + 512u,
+ .stage1_marketing_version_len = (uint8_t)version_len,
+ };
+ memcpy(header.stage1_marketing_version, version, version_len);
+ return header;
+}
+
+static void _set_stage1_header(const bb02_stage1_header_t* header)
+{
+ memory_set_bootloader_stage1_header_fake((const uint8_t*)header, sizeof(*header));
+}
+
+static void _test_memory_get_bootloader_version(void** state)
+{
+ (void)state;
+ const uint8_t expected[] = "v1.2.2";
+ bb02_stage1_header_t header = _make_stage1_header(expected, sizeof(expected) - 1);
+ _set_stage1_header(&header);
+
+ uint8_t version_out[MEMORY_BOOTLOADER_VERSION_MAX_LEN] = {0};
+ size_t version_len = 0;
+ assert_true(memory_get_bootloader_version(version_out, &version_len));
+ assert_int_equal(version_len, sizeof(expected) - 1);
+ assert_memory_equal(version_out, expected, version_len);
+
+ const uint8_t max_version[MEMORY_BOOTLOADER_VERSION_MAX_LEN] = {
+ "v1.2.2-pre+01234567890123456789012345",
+ };
+ header = _make_stage1_header(max_version, sizeof(max_version));
+ _set_stage1_header(&header);
+ assert_true(memory_get_bootloader_version(version_out, &version_len));
+ assert_int_equal(version_len, sizeof(max_version));
+ assert_memory_equal(version_out, max_version, version_len);
+}
+
+static void _test_memory_get_bootloader_version_invalid_magic(void** state)
+{
+ (void)state;
+ const bb02_stage1_header_t header = {0};
+ _set_stage1_header(&header);
+
+ uint8_t version_out[MEMORY_BOOTLOADER_VERSION_MAX_LEN] = {0};
+ size_t version_len = 123;
+ assert_false(memory_get_bootloader_version(version_out, &version_len));
+ assert_int_equal(version_len, 0);
+}
+
+static void _test_memory_get_bootloader_version_invalid_length(void** state)
+{
+ (void)state;
+ const bb02_stage1_header_t header = {
+ .magic = BB02_STAGE1_HEADER_MAGIC,
+ .stage1_marketing_version_len = MEMORY_BOOTLOADER_VERSION_MAX_LEN + 1,
+ };
+ _set_stage1_header(&header);
+
+ uint8_t version_out[MEMORY_BOOTLOADER_VERSION_MAX_LEN] = {0};
+ size_t version_len = 123;
+ assert_false(memory_get_bootloader_version(version_out, &version_len));
+ assert_int_equal(version_len, 0);
+}
+
+static void _test_memory_get_bootloader_version_invalid_arguments(void** state)
+{
+ (void)state;
+ const uint8_t expected[] = "v1.2.2";
+ const bb02_stage1_header_t header = _make_stage1_header(expected, sizeof(expected) - 1);
+ _set_stage1_header(&header);
+
+ uint8_t version_out[MEMORY_BOOTLOADER_VERSION_MAX_LEN] = {0};
+ size_t version_len = 123;
+ assert_false(memory_get_bootloader_version(NULL, &version_len));
+ assert_int_equal(version_len, 0);
+ assert_false(memory_get_bootloader_version(version_out, NULL));
+}
+
static void _test_memory_set_attestation_device_pubkey(void** state)
{
EMPTYCHUNK(empty_chunk);
@@ -680,6 +764,10 @@ int main(void)
cmocka_unit_test(_test_memory_get_device_name),
cmocka_unit_test(_test_memory_device_name),
cmocka_unit_test(_test_memory_set_seed_birthdate),
+ cmocka_unit_test(_test_memory_get_bootloader_version),
+ cmocka_unit_test(_test_memory_get_bootloader_version_invalid_magic),
+ cmocka_unit_test(_test_memory_get_bootloader_version_invalid_length),
+ cmocka_unit_test(_test_memory_get_bootloader_version_invalid_arguments),
cmocka_unit_test(_test_memory_set_attestation_device_pubkey),
cmocka_unit_test(_test_memory_set_attestation_certificate),
};
### test/unit-test/test_stage0_descriptor.c
@@ -6,6 +6,7 @@
#include <stdint.h>
#include <cmocka.h>
+#include "bootloader/bootloader_product.h"
#include "bootloader/stage0/stage0_descriptor.h"
#include "bootloader_upgrade/bootloader_upgrade.h"
### test/unit-test/test_stage0_sigcheck.c
@@ -8,6 +8,7 @@
#include <string.h>
#include <cmocka.h>
+#include "bootloader/bootloader_product.h"
#include "bootloader/stage0/stage1_sigcheck.h"
#include "hal_sha_sync.h"
#include "pukcc/pukcc.h"Why this scored 20/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.