1use std::collections::HashMap;
2use std::fs;
3use std::io::{Read, Write};
4use std::path::Path;
5
6use bao1x_api::signatures::{
7 FunctionCode, PADDING_LEN, SIGBLOCK_LEN, SealedFields, SignatureInFlash, UNSIGNED_LEN,
8};
9use ed25519_dalek::{DigestSigner, SigningKey};
10use pkcs8::PrivateKeyInfo;
11use pkcs8::der::Decodable;
12use ring::signature::{Ed25519KeyPair, KeyPair};
13use sha2::{Digest, Sha512};
14
15#[repr(u32)]
16#[derive(PartialEq, Eq, Clone, Copy)]
17pub enum Version {
18 Loader = 1,
19 LoaderPrehash = 2,
20 Bao1xV1 = 0x1_00,
21}
22use xous_semver::SemVer;
23
24pub fn generate_jal_x0(signed_offset: isize) -> Result<u32, String> {
25 if signed_offset & 1 != 0 {
27 return Err("JAL offset must be 2-byte aligned (even)".to_string());
28 }
29
30 const MIN_OFFSET: isize = -(1 << 20); const MAX_OFFSET: isize = (1 << 20) - 2; if signed_offset < MIN_OFFSET || signed_offset > MAX_OFFSET {
36 return Err(format!("JAL offset {} is out of range [{}, {}]", signed_offset, MIN_OFFSET, MAX_OFFSET));
37 }
38
39 let imm = signed_offset as u32;
40
41 let imm_20 = (imm >> 20) & 1; let imm_19_12 = (imm >> 12) & 0xFF; let imm_11 = (imm >> 11) & 1; let imm_10_1 = (imm >> 1) & 0x3FF; let instruction = (imm_20 << 31) | (imm_10_1 << 21) | (imm_11 << 20) | (imm_19_12 << 12) | 0x6F; Ok(instruction)
58}
59
60pub fn load_pem(src: &str) -> Result<pem::Pem, Box<dyn std::error::Error>> {
61 let mut input = vec![];
62 let mut pemfile = std::fs::File::open(src)?;
63 pemfile.read_to_end(&mut input)?;
64
65 Ok(pem::parse(input)?)
66}
67
68pub fn sign_image(
69 source: &[u8],
70 private_key: &pem::Pem,
71 defile: bool,
72 minver: &Option<SemVer>,
73 semver: Option<[u8; 16]>,
74 with_jump: bool,
75 length: usize,
76 version: Version,
77 function_code: Option<&str>,
78 anti_rollback_manual: Option<usize>,
79) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
80 let mut dest_file = vec![];
81
82 let minver_bytes = if let Some(mv) = minver { mv.into() } else { [0u8; 16] };
85 let semver: [u8; 16] = match semver {
86 Some(semver) => semver,
87 None => SemVer::from_git()?.into(),
88 };
89
90 match version {
91 Version::Loader | Version::LoaderPrehash => {
92 let mut source = source.to_owned();
93 source.append(&mut minver_bytes.to_vec());
104 source.append(&mut semver.to_vec());
105 let prehash = match version {
106 Version::Loader => false,
107 Version::LoaderPrehash => true,
108 _ => return Err(String::from("Unhandled image version").into()),
109 };
110 for &b in (version as u32).to_le_bytes().iter() {
111 source.push(b);
112 }
113 for &b in (source.len() as u32).to_le_bytes().iter() {
114 source.push(b);
115 }
116
117 let (signature, pubkey) = if prehash {
118 let mut h: Sha512 = Sha512::new();
120 h.update(&source);
121
122 let pkinfo = PrivateKeyInfo::from_der(&private_key.contents).map_err(|e| format!("{}", e))?;
123 assert!(pkinfo.private_key[0] == 0x4);
126 assert!(pkinfo.private_key[1] == 0x20);
127 let mut secbytes = [0u8; 32];
128 secbytes.copy_from_slice(&pkinfo.private_key[2..]);
129 let signing_key = SigningKey::from_bytes(&secbytes);
131
132 let sk = Ed25519KeyPair::from_pkcs8_maybe_unchecked(&private_key.contents)
134 .map_err(|e| format!("{}", e))?;
135 let mut pubkey_bytes = [0u8; 32];
136 pubkey_bytes.copy_from_slice(sk.public_key().as_ref());
137
138 let sig = signing_key.sign_digest(h.clone()).to_bytes();
139 (sig, pubkey_bytes)
140 } else {
141 let signing_key = Ed25519KeyPair::from_pkcs8_maybe_unchecked(&private_key.contents)
145 .map_err(|e| format!("{}", e))?;
146 let mut sig = [0u8; 64];
147 sig.copy_from_slice(signing_key.sign(&source).as_ref());
148 let mut pubkey_bytes = [0u8; 32];
149 pubkey_bytes.copy_from_slice(signing_key.public_key().as_ref());
150 (sig, pubkey_bytes)
151 };
152
153 let jal = generate_jal_x0(length as isize)?;
154 let extra_pad = if with_jump {
156 dest_file.write_all(&jal.to_le_bytes())?;
157 4
158 } else {
159 0
160 };
161
162 dest_file.write_all(&(version as u32).to_le_bytes())?;
163 dest_file.write_all(&(source.len() as u32).to_le_bytes())?;
164
165 dest_file.write_all(&signature)?;
167
168 dest_file.write_all(&pubkey)?;
172
173 let mut v = vec![];
175 v.resize(length - 4 - 4 - signature.len() - extra_pad - pubkey.len(), 0);
176 dest_file.write_all(&v)?;
177
178 if defile {
181 println!(
182 "WARNING: defiling the loader image. This corrupts the binary and should cause it to fail the signature check."
183 );
184 source[16778] ^= 0x1 }
186
187 dest_file.write_all(&source)?;
188
189 Ok(dest_file)
190 }
191 Version::Bao1xV1 => {
192 let pkinfo = PrivateKeyInfo::from_der(&private_key.contents).map_err(|e| format!("{}", e))?;
193 assert!(pkinfo.private_key[0] == 0x4);
196 assert!(pkinfo.private_key[1] == 0x20);
197 let mut secbytes = [0u8; 32];
198 secbytes.copy_from_slice(&pkinfo.private_key[2..]);
199 let signing_key = SigningKey::from_bytes(&secbytes);
201
202 let anti_rollback = if let Some(code) = anti_rollback_manual {
210 code as u32
211 } else {
212 let anti_rollback =
213 read_anti_rollback("./signing/anti-rollback.hjson").inspect_err(|_e| {
214 println!("anti-rollback.hjson file not present, can't determine anti-rollback code!");
215 })?;
216 *(anti_rollback
217 .get(function_code.unwrap_or("unspecified"))
218 .expect("anti-rollback code not specified"))
219 };
220
221 let function_code = match function_code {
222 Some("boot0") => FunctionCode::Boot0,
223 Some("boot1") => FunctionCode::Boot1,
224 Some("loader") => FunctionCode::Loader,
225 Some("kernel") => FunctionCode::Kernel,
226 Some("app") => FunctionCode::App,
227 Some("swap") => FunctionCode::Swap,
228 Some("baremetal") => FunctionCode::Baremetal,
229 _ => panic!("Invalid function code"),
230 };
231 let mut header = SignatureInFlash::default();
232 header.sealed_data.version = version as u32;
233 header.sealed_data.signed_len = (source.len() + SIGBLOCK_LEN - UNSIGNED_LEN) as u32;
234 header.sealed_data.function_code = function_code as u32;
235 header.sealed_data.anti_rollback = anti_rollback;
236 header.sealed_data.min_semver = minver_bytes;
237 header.sealed_data.semver = semver;
238
239 for (dst, src) in
241 header.sealed_data.pubkeys.iter_mut().zip(bao1x_api::pubkeys::PUBKEY_HEADER.iter())
242 {
243 dst.populate_from(src);
244 }
245
246 let mut protected = Vec::new();
247 protected.extend_from_slice(header.sealed_data.as_ref());
248 protected.resize(protected.len() + PADDING_LEN, 0);
249 protected.extend_from_slice(&source);
250
251 let mut h: Sha512 = Sha512::new();
253 h.update(&protected);
254
255 let sig = signing_key.sign_digest(h.clone()).to_bytes();
256 header._jal_instruction = generate_jal_x0(SIGBLOCK_LEN as isize)?;
257 header.signature.copy_from_slice(&sig);
258 header.aad_len = 0;
260
261 dest_file.write_all(&header.as_ref()[..UNSIGNED_LEN])?;
263 if defile {
266 println!(
267 "WARNING: defiling the loader image. This corrupts the binary and should cause it to fail the signature check."
268 );
269 protected[size_of::<SealedFields>() + 16] ^= 0x1 }
271
272 dest_file.write_all(&protected)?;
273
274 if function_code == FunctionCode::Kernel {
275 let target = bao1x_api::RRAM_STORAGE_LEN - (bao1x_api::KERNEL_START - bao1x_api::BOOT0_START);
276 if dest_file.len() > target {
277 println!(
278 "ERROR: Xous RRAM image is too big to fit: {} bytes too large ({} bytes; {} limit)",
279 dest_file.len() - target,
280 dest_file.len(),
281 target,
282 );
283 return Err(String::from("Image doesn't fit").into());
284 } else {
285 println!(
286 "=== Kernel is {} bytes: {} bytes free remaining ===",
287 dest_file.len(),
288 target - dest_file.len()
289 );
290 }
291 }
292 Ok(dest_file)
293 }
294 }
295}
296
297pub fn sign_file<S, T>(
298 input: &S,
299 output: &T,
300 private_key: &pem::Pem,
301 defile: bool,
302 minver: &Option<SemVer>,
303 version: Version,
304 with_jump: bool,
305 sector_length: usize,
306 function_code: Option<&str>,
307) -> Result<(), Box<dyn std::error::Error>>
308where
309 S: AsRef<Path>,
310 T: AsRef<Path>,
311{
312 let mut source = vec![];
313 let mut source_file = std::fs::File::open(input)?;
314 let mut dest_file = std::fs::File::create(output)?;
315 source_file.read_to_end(&mut source)?;
316
317 let result = sign_image(
318 &source,
319 private_key,
320 defile,
321 minver,
322 None,
323 with_jump,
324 sector_length,
325 version,
326 function_code,
327 None,
328 )?;
329 dest_file.write_all(&result)?;
330 Ok(())
331}
332
333pub fn convert_to_uf2<S, T>(
334 input: &S,
335 output: &T,
336 function_code: Option<&str>,
337 offset: Option<usize>,
338) -> Result<(), Box<dyn std::error::Error>>
339where
340 S: AsRef<Path>,
341 T: AsRef<Path>,
342{
343 let mut source = vec![];
344 let mut source_file = std::fs::File::open(input)?;
345 let mut dest_file = std::fs::File::create(output)?;
346 source_file.read_to_end(&mut source)?;
347
348 let app_start_addr = match function_code {
355 Some("boot0") => bao1x_api::BOOT0_START,
356 Some("boot1") => bao1x_api::BOOT1_START,
357 Some("loader") => bao1x_api::LOADER_START,
358 Some("baremetal") => bao1x_api::BAREMETAL_START,
359 Some("kernel") => bao1x_api::KERNEL_START,
360 Some("swap") => bao1x_api::SWAP_START_UF2,
361 Some("app") => bao1x_api::dabao::APP_RRAM_START,
362 _ => return Err(String::from("UF2 Image Requires a function code").into()),
363 };
364
365 match bin_to_uf2(
366 &source,
367 bao1x_api::BAOCHIP_1X_UF2_FAMILY,
368 app_start_addr as u32 + offset.unwrap_or(0) as u32,
369 ) {
370 Ok(u2f_blob) => {
371 dest_file.write_all(&u2f_blob)?;
372 Ok(())
373 }
374 Err(e) => Err(e.into()),
375 }
376}
377
378use byteorder::{LittleEndian, WriteBytesExt};
379
380const UF2_MAGIC_START0: u32 = 0x0A324655; const UF2_MAGIC_START1: u32 = 0x9E5D5157; const UF2_MAGIC_END: u32 = 0x0AB16F30; pub fn bin_to_uf2(bytes: &Vec<u8>, family_id: u32, app_start_addr: u32) -> Result<Vec<u8>, std::io::Error> {
386 let datapadding = [0u8; 512 - 256 - 32 - 4];
387 let nblocks: u32 = ((bytes.len() + 255) / 256) as u32;
388 let mut outp: Vec<u8> = Vec::new();
389 for blockno in 0..nblocks {
390 let ptr = 256 * blockno;
391 let chunk = match bytes.get(ptr as usize..ptr as usize + 256) {
392 Some(bytes) => bytes.to_vec(),
393 None => {
394 let mut chunk = bytes[ptr as usize..bytes.len()].to_vec();
395 while chunk.len() < 256 {
396 chunk.push(0);
397 }
398 chunk
399 }
400 };
401 let mut flags: u32 = 0;
402 if family_id != 0 {
403 flags |= 0x2000
404 }
405
406 outp.write_u32::<LittleEndian>(UF2_MAGIC_START0)?;
408 outp.write_u32::<LittleEndian>(UF2_MAGIC_START1)?;
409 outp.write_u32::<LittleEndian>(flags)?;
410 outp.write_u32::<LittleEndian>(ptr + app_start_addr)?;
411 outp.write_u32::<LittleEndian>(256)?;
412 outp.write_u32::<LittleEndian>(blockno)?;
413 outp.write_u32::<LittleEndian>(nblocks)?;
414 outp.write_u32::<LittleEndian>(family_id)?;
415
416 outp.write(&chunk)?;
418 outp.write(&datapadding)?;
419
420 outp.write_u32::<LittleEndian>(UF2_MAGIC_END)?;
422 }
423 Ok(outp)
424}
425
426fn hjson_to_json(hjson: &str) -> String {
428 hjson
429 .lines()
430 .map(|line| {
431 if let Some(pos) = line.find("//") { &line[..pos] } else { line }
433 })
434 .collect::<Vec<_>>()
435 .join("\n")
436}
437
438fn read_anti_rollback<P: AsRef<Path>>(path: P) -> Result<HashMap<String, u32>, Box<dyn std::error::Error>> {
440 let content = fs::read_to_string(path)?;
441 let json_content = hjson_to_json(&content);
442
443 let string_map: HashMap<String, String> = serde_json::from_str(&json_content)?;
445
446 let mut result = HashMap::new();
448 for (key, value) in string_map {
449 let version: u32 = value.parse()?;
450 result.insert(key, version);
451 }
452
453 Ok(result)
454}