1use anyhow::{Context, Result, ensure};
6use serde::{self, Deserialize, Serialize};
7use std::path::{Path, PathBuf};
8use thiserror::Error;
9use zerocopy::IntoBytes;
10
11use crate::chip::boolean::HardenedBool;
12use crate::crypto::mldsa::load_mldsa_key;
13use crate::image::manifest::*;
14use crate::image::manifest_def::le_bytes_to_word_arr;
15use crate::util::num_de::HexEncoded;
16use crate::with_unknown;
17use ml_dsa::{MlDsa87, VerifyingKey};
18use sphincsplus::{DecodeKey, SpxPublicKey};
19
20#[derive(Debug, Error)]
21pub enum ManifestExtError {
22 #[error("Extension ID 0x{0:x} has duplicate extension data.")]
23 DuplicateEntry(u32),
24}
25
26with_unknown! {
27 #[derive(Default)]
29 pub enum ManifestExtId: u32 {
30 spx_key = MANIFEST_EXT_ID_SPX_KEY,
31 spx_signature = MANIFEST_EXT_ID_SPX_SIGNATURE,
32 image_type = MANIFEST_EXT_ID_IMAGE_TYPE,
33 secver_write = MANIFEST_EXT_ID_SECVER_WRITE,
34 isfb = MANIFEST_EXT_ID_ISFB,
35 isfb_erase = MANIFEST_EXT_ID_ISFB_ERASE,
36 owner_transfer_blob = MANIFEST_EXT_ID_OWNER_TRANSFER_BLOB,
37 mldsa87_key = MANIFEST_EXT_ID_MLDSA87_KEY,
38 mldsa87_signature = MANIFEST_EXT_ID_MLDSA87_SIGNATURE,
39 }
40}
41
42#[derive(Default, Debug, Deserialize, Serialize)]
44pub struct ManifestExtSpec {
45 pub extension_params: Vec<ManifestExtEntrySpec>,
46 #[serde(skip)]
47 relative_path: Option<PathBuf>,
48}
49
50#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
51pub struct ProductExpr {
52 pub mask: HexEncoded<u32>,
53 pub value: HexEncoded<u32>,
54}
55
56#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
60pub enum ManifestExtEntrySpec {
61 #[serde(alias = "spx_key")]
62 SpxKey {
63 spx_key: PathBuf,
65 },
66 #[serde(alias = "spx_signature")]
67 SpxSignature {
68 spx_signature: PathBuf,
70 },
71 #[serde(alias = "mldsa87_key")]
72 Mldsa87Key {
73 mldsa87_key: PathBuf,
75 },
76 #[serde(alias = "mldsa87_signature")]
77 Mldsa87Signature {
78 mldsa87_signature: PathBuf,
80 },
81
82 #[serde(alias = "image_type")]
83 ImageType { image_type: u32 },
84
85 #[serde(alias = "secver_write")]
86 SecVerWrite {
87 secver_write: bool,
89 },
90
91 #[serde(alias = "integrator_specific_firmware_binding")]
92 Isfb {
93 strike_mask: HexEncoded<u128>,
94 product_expr: Vec<ProductExpr>,
95 },
96
97 #[serde(alias = "isfb_erase_policy")]
98 IsfbErasePolicy { erase_allowed: bool },
99
100 #[serde(alias = "owner_transfer_blob")]
101 OwnerTransferBlob {
102 owner_block: PathBuf,
103 #[serde(default)]
104 detached_signature: Option<PathBuf>,
105 },
106
107 #[serde(alias = "raw")]
108 Raw {
109 name: HexEncoded<u32>,
110 identifier: HexEncoded<u32>,
111 signed: bool,
112 #[serde(default)]
113 alignment: Option<HexEncoded<u32>>,
114 #[serde(default)]
115 value: Option<Vec<HexEncoded<u8>>>,
116 #[serde(default)]
117 file: Option<PathBuf>,
118 },
119}
120
121#[derive(Debug)]
122pub enum ManifestExtEntry {
123 SpxKey(ManifestExtSpxKey),
124 SpxSignature(Box<ManifestExtSpxSignature>),
125 Mldsa87Key(Box<ManifestExtMldsa87Key>),
126 Mldsa87Signature(Box<ManifestExtMldsa87Signature>),
127 ImageType(ManifestExtImageType),
128 SecVerWrite(ManifestExtSecVerWrite),
129 Isfb(ManifestExtIsfb),
130 IsfbErasePolicy(ManifestExtIsfbErasePolicy),
131 OwnerTransferBlob {
132 header: ManifestExtHeader,
133 owner_block: Vec<u8>,
134 detached_signature: Option<Vec<u8>>,
135 },
136 Raw {
137 header: ManifestExtHeader,
138 alignment: u32,
139 data: Vec<u8>,
140 },
141}
142
143impl ManifestExtSpec {
144 pub fn read_from_file(path: &Path) -> Result<Self> {
149 let mut spec: Self = deser_hjson::from_str(&std::fs::read_to_string(path)?)?;
150 spec.relative_path = path.parent().map(|v| v.to_owned());
151 Ok(spec)
152 }
153
154 pub fn source_path(&self) -> Option<&Path> {
156 self.relative_path.as_deref()
157 }
158}
159
160impl ManifestExtEntrySpec {
161 pub fn id(&self) -> u32 {
162 match self {
163 ManifestExtEntrySpec::SpxKey { spx_key: _ } => MANIFEST_EXT_ID_SPX_KEY,
164 ManifestExtEntrySpec::SpxSignature { spx_signature: _ } => {
165 MANIFEST_EXT_ID_SPX_SIGNATURE
166 }
167 ManifestExtEntrySpec::Mldsa87Key { .. } => MANIFEST_EXT_ID_MLDSA87_KEY,
168 ManifestExtEntrySpec::Mldsa87Signature { .. } => MANIFEST_EXT_ID_MLDSA87_SIGNATURE,
169 ManifestExtEntrySpec::SecVerWrite { .. } => MANIFEST_EXT_ID_SECVER_WRITE,
170 ManifestExtEntrySpec::Isfb { .. } => MANIFEST_EXT_ID_ISFB,
171 ManifestExtEntrySpec::IsfbErasePolicy { .. } => MANIFEST_EXT_ID_ISFB_ERASE,
172 ManifestExtEntrySpec::ImageType { image_type: _ } => MANIFEST_EXT_ID_IMAGE_TYPE,
173 ManifestExtEntrySpec::OwnerTransferBlob { .. } => MANIFEST_EXT_ID_OWNER_TRANSFER_BLOB,
174 ManifestExtEntrySpec::Raw { identifier, .. } => **identifier,
175 }
176 }
177
178 pub fn is_signed(&self) -> bool {
179 match self {
180 ManifestExtEntrySpec::SpxKey { .. }
181 | ManifestExtEntrySpec::Mldsa87Key { .. }
182 | ManifestExtEntrySpec::SecVerWrite { .. }
183 | ManifestExtEntrySpec::Isfb { .. }
184 | ManifestExtEntrySpec::IsfbErasePolicy { .. }
185 | ManifestExtEntrySpec::ImageType { .. } => true,
186 ManifestExtEntrySpec::SpxSignature { .. }
187 | ManifestExtEntrySpec::Mldsa87Signature { .. }
188 | ManifestExtEntrySpec::OwnerTransferBlob { .. } => false,
189 ManifestExtEntrySpec::Raw { signed, .. } => *signed,
190 }
191 }
192}
193
194impl ManifestExtEntry {
195 pub fn new_spx_key_entry(key: &SpxPublicKey) -> Result<Self> {
197 Ok(ManifestExtEntry::SpxKey(ManifestExtSpxKey {
198 header: ManifestExtHeader {
199 identifier: MANIFEST_EXT_ID_SPX_KEY,
200 name: MANIFEST_EXT_NAME_SPX_KEY,
201 },
202 key: SigverifySpxKey {
203 data: le_bytes_to_word_arr(key.as_bytes())?,
204 },
205 }))
206 }
207
208 pub fn new_spx_signature_entry(signature: &[u8]) -> Result<Self> {
210 Ok(ManifestExtEntry::SpxSignature(Box::new(
211 ManifestExtSpxSignature {
212 header: ManifestExtHeader {
213 identifier: MANIFEST_EXT_ID_SPX_SIGNATURE,
214 name: MANIFEST_EXT_NAME_SPX_SIGNATURE,
215 },
216 signature: SigverifySpxSignature {
217 data: le_bytes_to_word_arr(signature)?,
218 },
219 },
220 )))
221 }
222
223 pub fn new_mldsa87_key_entry(key: &VerifyingKey<MlDsa87>) -> Result<Self> {
225 Ok(ManifestExtEntry::Mldsa87Key(Box::new(
226 ManifestExtMldsa87Key {
227 header: ManifestExtHeader {
228 identifier: MANIFEST_EXT_ID_MLDSA87_KEY,
229 name: MANIFEST_EXT_NAME_MLDSA87_KEY,
230 },
231 key: SigverifyMldsa87Key {
232 data: le_bytes_to_word_arr(&key.encode())?,
233 },
234 },
235 )))
236 }
237
238 pub fn new_mldsa87_signature_entry(signature: &[u8]) -> Result<Self> {
240 ensure!(
241 signature.len() == MLDSA87_SIGNATURE_BYTES,
242 "Unexpected ML-DSA-87 signature length {}",
243 signature.len()
244 );
245 Ok(ManifestExtEntry::Mldsa87Signature(Box::new(
246 ManifestExtMldsa87Signature {
247 header: ManifestExtHeader {
248 identifier: MANIFEST_EXT_ID_MLDSA87_SIGNATURE,
249 name: MANIFEST_EXT_NAME_MLDSA87_SIGNATURE,
250 },
251 signature: SigverifyMldsa87Signature {
252 data: le_bytes_to_word_arr(signature)?,
253 },
254 },
255 )))
256 }
257
258 pub fn new_image_type_entry(image_type: u32) -> Result<Self> {
259 Ok(ManifestExtEntry::ImageType(ManifestExtImageType {
260 header: ManifestExtHeader {
261 identifier: MANIFEST_EXT_ID_IMAGE_TYPE,
262 name: MANIFEST_EXT_NAME_IMAGE_TYPE,
263 },
264 image_type,
265 }))
266 }
267
268 pub fn new_secver_write_entry(write: u32) -> Result<Self> {
269 Ok(ManifestExtEntry::SecVerWrite(ManifestExtSecVerWrite {
270 header: ManifestExtHeader {
271 identifier: MANIFEST_EXT_ID_SECVER_WRITE,
272 name: MANIFEST_EXT_NAME_SECVER_WRITE,
273 },
274 write,
275 }))
276 }
277
278 pub fn new_isfb_erase_policy_entry(erase_allowed: u32) -> Result<Self> {
279 Ok(ManifestExtEntry::IsfbErasePolicy(
280 ManifestExtIsfbErasePolicy {
281 header: ManifestExtHeader {
282 identifier: MANIFEST_EXT_ID_ISFB_ERASE,
283 name: MANIFEST_EXT_NAME_ISFB_ERASE,
284 },
285 erase_allowed,
286 },
287 ))
288 }
289
290 pub fn new_isfb_entry(strike_mask: u128, product_expr_spec: Vec<ProductExpr>) -> Result<Self> {
291 let product_expr_count = product_expr_spec.len() as u32;
292 let product_expr = product_expr_spec
293 .into_iter()
294 .map(|expr| ManifestExtIsfbProductExpr {
295 mask: expr.mask.0,
296 value: expr.value.0,
297 })
298 .collect();
299 Ok(ManifestExtEntry::Isfb(ManifestExtIsfb {
300 header: ManifestExtHeader {
301 identifier: MANIFEST_EXT_ID_ISFB,
302 name: MANIFEST_EXT_NAME_ISFB,
303 },
304 strike_mask,
305 product_expr_count,
306 product_expr,
307 }))
308 }
309
310 pub fn from_spec(spec: &ManifestExtEntrySpec) -> Result<Self> {
312 Ok(match spec {
313 ManifestExtEntrySpec::SpxKey { spx_key } => {
314 ManifestExtEntry::new_spx_key_entry(&SpxPublicKey::read_pem_file(spx_key)?)?
315 }
316 ManifestExtEntrySpec::SpxSignature { spx_signature } => {
317 ManifestExtEntry::new_spx_signature_entry(&std::fs::read(spx_signature)?)?
318 }
319 ManifestExtEntrySpec::Mldsa87Key { mldsa87_key } => {
320 ManifestExtEntry::new_mldsa87_key_entry(&load_mldsa_key::<MlDsa87>(mldsa87_key)?.0)?
321 }
322 ManifestExtEntrySpec::Mldsa87Signature { mldsa87_signature } => {
323 ManifestExtEntry::new_mldsa87_signature_entry(&std::fs::read(mldsa87_signature)?)?
324 }
325 ManifestExtEntrySpec::ImageType { image_type } => {
326 ManifestExtEntry::new_image_type_entry(*image_type)?
327 }
328 ManifestExtEntrySpec::SecVerWrite { secver_write } => {
329 ManifestExtEntry::new_secver_write_entry(
330 (if *secver_write {
331 HardenedBool::True
332 } else {
333 HardenedBool::False
334 })
335 .into(),
336 )?
337 }
338 ManifestExtEntrySpec::IsfbErasePolicy { erase_allowed } => {
339 ManifestExtEntry::new_isfb_erase_policy_entry(
340 (if *erase_allowed {
341 HardenedBool::True
342 } else {
343 HardenedBool::False
344 })
345 .into(),
346 )?
347 }
348 ManifestExtEntrySpec::Isfb {
349 strike_mask,
350 product_expr,
351 } => ManifestExtEntry::new_isfb_entry(**strike_mask, product_expr.to_vec())?,
352 ManifestExtEntrySpec::OwnerTransferBlob {
353 owner_block,
354 detached_signature,
355 } => {
356 let owner_data = std::fs::read(owner_block).with_context(|| {
357 format!("Failed to read owner block file {:?}", owner_block)
358 })?;
359 anyhow::ensure!(
360 owner_data.len() == 2048,
361 "Owner block must be exactly 2048 bytes (got {})",
362 owner_data.len()
363 );
364 let sig_data = match detached_signature {
365 Some(path) => {
366 let mut data = std::fs::read(path).with_context(|| {
367 format!("Failed to read detached signature file {:?}", path)
368 })?;
369 anyhow::ensure!(
370 data.len() <= 8192,
371 "Detached signature size ({}) exceeds maximum limit (8192 bytes)",
372 data.len()
373 );
374 data.resize(8192, 0xff);
375 Some(data)
376 }
377 None => None,
378 };
379 ManifestExtEntry::OwnerTransferBlob {
380 header: ManifestExtHeader {
381 identifier: MANIFEST_EXT_ID_OWNER_TRANSFER_BLOB,
382 name: MANIFEST_EXT_NAME_OWNER_TRANSFER_BLOB,
383 },
384 owner_block: owner_data,
385 detached_signature: sig_data,
386 }
387 }
388 ManifestExtEntrySpec::Raw {
389 name,
390 identifier,
391 alignment,
392 value,
393 file,
394 ..
395 } => {
396 let data = if let Some(path) = file {
397 std::fs::read(path)
398 .with_context(|| format!("Failed to read raw extension file {:?}", path))?
399 } else if let Some(val) = value {
400 val.iter().map(|v| **v).collect()
401 } else {
402 anyhow::bail!("Raw extension must specify either value or file");
403 };
404 ManifestExtEntry::Raw {
405 header: ManifestExtHeader {
406 identifier: **identifier,
407 name: **name,
408 },
409 alignment: alignment.as_ref().map(|v| **v).unwrap_or(4),
410 data,
411 }
412 }
413 })
414 }
415
416 pub fn alignment(&self) -> u32 {
418 match self {
419 ManifestExtEntry::OwnerTransferBlob { .. } => 2048,
420 ManifestExtEntry::Raw { alignment, .. } => *alignment,
421 _ => 4,
422 }
423 }
424
425 pub fn header(&self) -> &ManifestExtHeader {
427 match self {
428 ManifestExtEntry::SpxKey(key) => &key.header,
429 ManifestExtEntry::SpxSignature(sig) => &sig.header,
430 ManifestExtEntry::Mldsa87Key(key) => &key.header,
431 ManifestExtEntry::Mldsa87Signature(sig) => &sig.header,
432 ManifestExtEntry::ImageType(image_type) => &image_type.header,
433 ManifestExtEntry::SecVerWrite(sv) => &sv.header,
434 ManifestExtEntry::Isfb(isfb) => &isfb.header,
435 ManifestExtEntry::IsfbErasePolicy(erase) => &erase.header,
436 ManifestExtEntry::OwnerTransferBlob { header, .. } => header,
437 ManifestExtEntry::Raw {
438 header,
439 alignment: _,
440 data: _,
441 } => header,
442 }
443 }
444
445 pub fn to_vec(&self) -> Vec<u8> {
447 match self {
448 ManifestExtEntry::SpxKey(key) => key.as_bytes().to_vec(),
449 ManifestExtEntry::SpxSignature(sig) => sig.as_bytes().to_vec(),
450 ManifestExtEntry::Mldsa87Key(key) => key.as_bytes().to_vec(),
451 ManifestExtEntry::Mldsa87Signature(sig) => sig.as_bytes().to_vec(),
452 ManifestExtEntry::ImageType(image_type) => image_type.as_bytes().to_vec(),
453 ManifestExtEntry::SecVerWrite(sv) => sv.as_bytes().to_vec(),
454 ManifestExtEntry::Isfb(isfb) => isfb.to_vec().unwrap(),
455 ManifestExtEntry::IsfbErasePolicy(erase) => erase.as_bytes().to_vec(),
456 ManifestExtEntry::OwnerTransferBlob {
457 header,
458 owner_block,
459 detached_signature,
460 } => {
461 let mut v = header.as_bytes().to_vec();
462 v.extend_from_slice(owner_block);
463 if let Some(sig) = detached_signature {
464 v.extend_from_slice(sig);
465 }
466 v
467 }
468 ManifestExtEntry::Raw {
469 header,
470 alignment: _,
471 data,
472 } => header.as_bytes().iter().chain(data).copied().collect(),
473 }
474 }
475}
476
477#[cfg(test)]
478mod tests {
479 use super::*;
480 use crate::util::hexdump::hexdump_string;
481 use crate::util::num_de::HexEncoded;
482 use crate::util::testdata;
483
484 #[test]
485 fn test_manifest_ext_from_hjson() {
486 let spec = ManifestExtSpec::read_from_file(&testdata("image/manifest_ext.hjson")).unwrap();
487 assert_eq!(spec.source_path(), Some(testdata("image").as_path()));
488 assert_eq!(spec.extension_params.len(), 9);
489 assert_eq!(
490 spec.extension_params[0],
491 ManifestExtEntrySpec::SpxKey {
492 spx_key: "test_spx.pem".into()
493 }
494 );
495 assert!(spec.extension_params[0].is_signed());
496 assert_eq!(
497 spec.extension_params[1],
498 ManifestExtEntrySpec::SecVerWrite { secver_write: true }
499 );
500 assert!(spec.extension_params[1].is_signed());
501 assert_eq!(
502 spec.extension_params[2],
503 ManifestExtEntrySpec::Isfb {
504 strike_mask: HexEncoded(0x0fedcba987654321fedcba9876543210),
505 product_expr: vec![
506 ProductExpr {
507 mask: HexEncoded(0xffffffff),
508 value: HexEncoded(0xa5a5a5a5),
509 },
510 ProductExpr {
511 mask: HexEncoded(0xf0f0f0f0),
512 value: HexEncoded(0xa0a0a0a0),
513 },
514 ]
515 }
516 );
517 assert!(spec.extension_params[2].is_signed());
518 assert_eq!(
519 spec.extension_params[3],
520 ManifestExtEntrySpec::IsfbErasePolicy {
521 erase_allowed: false
522 }
523 );
524 assert!(spec.extension_params[3].is_signed());
525 assert_eq!(
526 spec.extension_params[4],
527 ManifestExtEntrySpec::Raw {
528 name: HexEncoded(0xbeef),
529 identifier: HexEncoded(0xabcd),
530 signed: true,
531 alignment: None,
532 value: Some([0x01, 0x23, 0x45, 0x67].map(HexEncoded).to_vec()),
533 file: None,
534 }
535 );
536 assert!(spec.extension_params[4].is_signed());
537 assert_eq!(
538 spec.extension_params[5],
539 ManifestExtEntrySpec::SpxSignature {
540 spx_signature: "test_signature.bin".into()
541 }
542 );
543 assert!(!spec.extension_params[5].is_signed());
544 assert_eq!(
545 spec.extension_params[6],
546 ManifestExtEntrySpec::OwnerTransferBlob {
547 owner_block: "owner_block.bin".into(),
548 detached_signature: Some("signature.bin".into()),
549 }
550 );
551 assert!(!spec.extension_params[6].is_signed());
552 assert_eq!(
553 spec.extension_params[7],
554 ManifestExtEntrySpec::Mldsa87Key {
555 mldsa87_key: "test_mldsa87.pem".into()
556 }
557 );
558 assert!(spec.extension_params[7].is_signed());
559 assert_eq!(
560 spec.extension_params[8],
561 ManifestExtEntrySpec::Mldsa87Signature {
562 mldsa87_signature: "test_mldsa87_signature.bin".into()
563 }
564 );
565 assert!(!spec.extension_params[8].is_signed());
566 }
567
568 const MAN_EXT_ISFB_CONF: &str = "\
56900000000: 49 53 46 42 49 53 46 42 10 32 54 76 98 ba dc fe ISFBISFB.2Tv....
57000000010: 21 43 65 87 a9 cb ed 0f 02 00 00 00 4f 30 50 10 !Ce.........O0P.
57100000020: 05 00 00 01 f0 f0 f0 f0 a0 a0 a0 a0 ............
572";
573 #[test]
574 fn test_man_ext_isfb_write() -> Result<()> {
575 let isfb = ManifestExtEntry::new_isfb_entry(
576 0x0fedcba987654321fedcba9876543210,
577 vec![
578 ProductExpr {
579 mask: HexEncoded(0x1050304f),
580 value: HexEncoded(0x01000005),
581 },
582 ProductExpr {
583 mask: HexEncoded(0xf0f0f0f0),
584 value: HexEncoded(0xa0a0a0a0),
585 },
586 ],
587 )?;
588
589 let bin = isfb.to_vec();
590 eprintln!("{}", hexdump_string(&bin)?);
591 assert_eq!(hexdump_string(&bin)?, MAN_EXT_ISFB_CONF);
592 Ok(())
593 }
594
595 const MAN_EXT_ISFB_ERASE_POLICY: &str = "\
59600000000: 49 53 46 45 49 53 46 45 d4 01 00 00 ISFEISFE....
597";
598
599 #[test]
600 fn test_man_ext_isfb_erase_policy_write() -> Result<()> {
601 let isfb = ManifestExtEntry::new_isfb_erase_policy_entry(HardenedBool::False.into())?;
602
603 let bin = isfb.to_vec();
604 eprintln!("{}", hexdump_string(&bin)?);
605 assert_eq!(hexdump_string(&bin)?, MAN_EXT_ISFB_ERASE_POLICY);
606 Ok(())
607 }
608}