Skip to content

Commit 5397e1b

Browse files
authored
Merge pull request #822 from betrusted-io/baobit-audit
Baobit audit
2 parents a902b54 + eb2e370 commit 5397e1b

File tree

6 files changed

+76
-23
lines changed

6 files changed

+76
-23
lines changed

bao1x-boot/boot1/src/audit.rs

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use core::convert::TryInto;
22

33
use bao1x_api::pubkeys::{BOOT0_SELF_CHECK, BOOT0_TO_BOOT1, BOOT1_TO_LOADER_OR_BAREMETAL};
4-
use bao1x_api::signatures::{SignatureInFlash, UNSIGNED_LEN};
4+
use bao1x_api::signatures::{SIGBLOCK_LEN, SignatureInFlash, UNSIGNED_LEN};
55
use bao1x_api::*;
66
use bao1x_hal::acram::OneWayCounter;
77
use bao1x_hal::sigcheck::ERASE_VALUE;
@@ -29,6 +29,18 @@ fn hash_region(region: &[u8], description: &str) {
2929
crate::println!("{}: {}", description, hex_str);
3030
}
3131

32+
fn report_toolchain(sig: &SignatureInFlash, description: &str) {
33+
let hash = sig.sealed_data.toolchain;
34+
if hash == [0u8; 20] {
35+
crate::println!("{}: unspecified", description);
36+
} else {
37+
let mut buffer = [0u8; 40];
38+
hex::encode_to_slice(&hash, &mut buffer).unwrap();
39+
let hex_str = core::str::from_utf8(&buffer).unwrap();
40+
crate::println!("{}: {}", description, hex_str);
41+
}
42+
}
43+
3244
/// Stepping detection: attempt to modify the RRCR configuration. If bit 12 (code area protection)
3345
/// can be flipped, then we are A0 stepping.
3446
fn detect_stepping() -> &'static str {
@@ -57,7 +69,6 @@ pub fn audit() {
5769
crate::println!("Board type reads as: {:?}", boardtype);
5870
crate::println!("Boot partition is: {:?}", owc.get_decoded::<AltBootCoding>());
5971
crate::println!("Semver is: {}", crate::version::SEMVER);
60-
crate::println!("Baobit commit is: {}", crate::version::BAOBIT_COMMIT);
6172
crate::println!("Description is: {}", crate::RELEASE_DESCRIPTION);
6273
crate::println!("Stepping is: {}", detect_stepping());
6374
let slot_mgr = bao1x_hal::acram::SlotManager::new();
@@ -169,12 +180,13 @@ pub fn audit() {
169180
let b0_pk: &SignatureInFlash = unsafe { b0_pk_ptr.as_ref().unwrap() };
170181
let boot0_used = unsafe {
171182
core::slice::from_raw_parts(
172-
(bao1x_api::BOOT0_START + UNSIGNED_LEN) as *const u8,
173-
b0_pk.sealed_data.signed_len as usize,
183+
(bao1x_api::BOOT0_START + SIGBLOCK_LEN) as *const u8,
184+
b0_pk.sealed_data.signed_len as usize - (SIGBLOCK_LEN - UNSIGNED_LEN),
174185
)
175186
};
176-
// only the portion that's protected by signature
187+
// only the portion that's strictly reproducible
177188
hash_region(boot0_used, "boot0 code only");
189+
report_toolchain(b0_pk, "boot0 baobit toolchain");
178190

179191
let boot1_region = unsafe {
180192
core::slice::from_raw_parts(
@@ -185,6 +197,18 @@ pub fn audit() {
185197
// includes free space
186198
hash_region(boot1_region, "boot1 partition");
187199

200+
let b1_pk_ptr = bao1x_api::BOOT1_START as *const SignatureInFlash;
201+
let b1_pk: &SignatureInFlash = unsafe { b1_pk_ptr.as_ref().unwrap() };
202+
let boot1_used = unsafe {
203+
core::slice::from_raw_parts(
204+
(bao1x_api::BOOT1_START + SIGBLOCK_LEN) as *const u8,
205+
b1_pk.sealed_data.signed_len as usize - (SIGBLOCK_LEN - UNSIGNED_LEN),
206+
)
207+
};
208+
// only the portion that's strictly reproducible
209+
hash_region(boot1_used, "boot1 code only");
210+
report_toolchain(b1_pk, "boot1 baobit toolchain");
211+
188212
// detailed state checks
189213
let mut secure = true;
190214
// check that boot1 pubkeys match the indelible entries

libs/bao1x-api/src/signatures.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,16 @@ pub struct SealedFields {
162162
///
163163
/// If no valid keys are found, the device is effectively bricked and goes into a "die" state.
164164
pub pubkeys: [Pubkey; 4],
165+
/// Placeholder for baobit commit: this is the hash of the toolchain used to generate the image.
166+
/// In many images, this is just 0's; but for signed release images, this is injected into the file
167+
/// prior to applying the final signature. This injection has to happen after everything is done
168+
/// because the rules of the reproducible build system disallow it from knowing its final commit
169+
/// state: you have to commit its final state, which changes its reported commit state, and so
170+
/// forth, so at build time you can't know what toolchain was used to build you - only after
171+
/// the build is complete is the information revealed to the user.
172+
///
173+
/// This field is long enough to hold a SHA-1 git hash.
174+
pub toolchain: [u8; 20],
165175
}
166176

167177
impl AsRef<[u8]> for SealedFields {
@@ -178,6 +188,7 @@ impl Default for SealedFields {
178188
min_semver: [0u8; 16],
179189
semver: [0u8; 16],
180190
pubkeys: [Pubkey::default(); 4],
191+
toolchain: [0u8; 20],
181192
}
182193
}
183194
}

signing/fido-signer/src/main.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ struct Args {
4343
/// Function code e.g. partition - if provided, creates a uf2 file
4444
#[arg(short = 'p', long = "function-code", value_name = "CODE")]
4545
function_code: Option<String>,
46+
47+
/// Hash to embed in the signature (hex string)
48+
#[arg(short = 'b', long = "baobit-hash", value_name = "HASH")]
49+
baobit_hash: Option<String>,
4650
}
4751

4852
#[derive(Debug, Deserialize, Serialize)]
@@ -58,6 +62,11 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
5862
// Parse command line arguments
5963
let args = Args::parse();
6064

65+
let baobit_hash: Option<[u8; 20]> = args.baobit_hash.as_deref().map(|hex_str| {
66+
let vec = hex::decode(hex_str).expect("invalid hex string");
67+
vec.try_into().expect("Baobit hash must be exactly 20 bytes")
68+
});
69+
6170
// Load and parse the credential file
6271
let credential_data = fs::read_to_string(&args.credential_file)
6372
.map_err(|e| format!("Failed to read credential file: {}", e))?;
@@ -100,6 +109,13 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
100109
sig.as_mut().copy_from_slice(&file[offset..offset + size_of::<SignatureInFlash>()]);
101110
if sig.sealed_data.magic == bao1x_api::signatures::MAGIC_NUMBER {
102111
is_bao1x = true;
112+
// inject the baobit hash here, if it is specified
113+
if let Some(toolchain) = baobit_hash {
114+
patch_hash_in_file(&file_path, &toolchain, offset)?;
115+
}
116+
// Re-read the file so we hash the patched contents
117+
let file = fs::read(&file_path)
118+
.map_err(|e| format!("Failed to re-read file '{}': {}", file_path.display(), e))?;
103119
let mut h: Sha512 = Sha512::new();
104120
// hash the sealed region
105121
h.update(&file[offset + SignatureInFlash::sealed_data_offset()..]);
@@ -212,6 +228,22 @@ fn patch_signature_in_file(
212228
Ok(())
213229
}
214230

231+
fn patch_hash_in_file(
232+
file_path: &PathBuf,
233+
hash: &[u8],
234+
offset: usize,
235+
) -> Result<(), Box<dyn std::error::Error>> {
236+
// Open file with read and write permissions
237+
let mut file = OpenOptions::new().read(true).write(true).open(file_path)?;
238+
239+
let mut signature_struct = read_header_from_file(&mut file, offset)?;
240+
signature_struct.sealed_data.toolchain.copy_from_slice(&hash);
241+
242+
write_header_to_file(&mut file, &signature_struct, offset)?;
243+
244+
Ok(())
245+
}
246+
215247
fn read_header_from_file(
216248
file: &mut std::fs::File,
217249
offset: usize,

tools/restore.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -657,6 +657,7 @@ def main():
657657
# now try to download all the artifacts and check their versions
658658
# this list should visit kernels in order from newest to oldest.
659659
URL_LIST = [
660+
'https://ci.betrusted.io/releases/v0.10.0/',
660661
'https://ci.betrusted.io/releases/v0.9.16/',
661662
'https://ci.betrusted.io/releases/v0.9.15/',
662663
'https://ci.betrusted.io/releases/v0.9.14/',

xtask/src/main.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
6060
let do_version = env::args().filter(|x| x == "--no-timestamp").count() == 0;
6161
let git_describe_opt = get_flag("--git-describe")?.first().cloned();
6262
let git_rev_opt = get_flag("--git-rev")?.first().cloned();
63-
let baobit_commit_opt = get_flag("--baobit-commit")?.first().cloned();
64-
generate_version(do_version, git_describe_opt.clone(), baobit_commit_opt.clone());
63+
generate_version(do_version, git_describe_opt.clone());
6564
if let Some(desc) = git_describe_opt {
6665
builder.set_git_describe(desc);
6766
}

xtask/src/versioning.rs

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,7 @@ fn write_if_changed(path: &str, new_data: &[u8]) {
2727
vfile.write_all(new_data).expect(&format!("couldn't write to {}", path));
2828
}
2929

30-
pub(crate) fn generate_version(
31-
add_timestamp: bool,
32-
forced_version: Option<String>,
33-
baobit_commit: Option<String>,
34-
) {
30+
pub(crate) fn generate_version(add_timestamp: bool, forced_version: Option<String>) {
3531
let gitver = {
3632
if let Some(ver) = forced_version {
3733
ver.as_bytes().to_vec()
@@ -77,17 +73,7 @@ pub(crate) fn generate_version(
7773
.expect("couldn't add our semver");
7874
new_data.extend_from_slice(&semver_code);
7975

80-
// Baochip versioning is just SEMVER + BAOBIT_COMMIT. Now that
81-
// the semver_code has been added to new_data, we can extend it with the Baobit commit.
82-
if let Some(ref commit) = baobit_commit {
83-
writeln!(semver_code, "pub const BAOBIT_COMMIT: &'static str = \"{}\";", commit)
84-
.expect("couldn't add baobit commit");
85-
} else {
86-
// For builds that aren't built in the reproducible environment, the baobit commit is unspecified
87-
writeln!(semver_code, "pub const BAOBIT_COMMIT: &'static str = \"unspecified\";")
88-
.expect("couldn't add baobit commit placeholder");
89-
}
90-
76+
// Baochip versioning is just SEMVER
9177
write_if_changed(version_file, &new_data);
9278
write_if_changed(boot0_version_file, &semver_code);
9379
write_if_changed(boot1_version_file, &semver_code);

0 commit comments

Comments
 (0)