1use anyhow::Result;
6use serde::{self, Deserialize, Serialize};
7use std::path::{Path, PathBuf};
8use thiserror::Error;
9use zerocopy::IntoBytes;
10
11use crate::chip::boolean::HardenedBool;
12use crate::image::manifest::*;
13use crate::image::manifest_def::le_bytes_to_word_arr;
14use crate::util::num_de::HexEncoded;
15use crate::with_unknown;
16use sphincsplus::{DecodeKey, SpxPublicKey};
17
18#[derive(Debug, Error)]
19pub enum ManifestExtError {
20 #[error("Extension ID 0x{0:x} has duplicate extension data.")]
21 DuplicateEntry(u32),
22}
23
24with_unknown! {
25 #[derive(Default)]
27 pub enum ManifestExtId: u32 {
28 spx_key = MANIFEST_EXT_ID_SPX_KEY,
29 spx_signature = MANIFEST_EXT_ID_SPX_SIGNATURE,
30 image_type = MANIFEST_EXT_ID_IMAGE_TYPE,
31 secver_write = MANIFEST_EXT_ID_SECVER_WRITE,
32 isfb = MANIFEST_EXT_ID_ISFB,
33 isfb_erase = MANIFEST_EXT_ID_ISFB_ERASE,
34 }
35}
36
37#[derive(Default, Debug, Deserialize, Serialize)]
39pub struct ManifestExtSpec {
40 pub extension_params: Vec<ManifestExtEntrySpec>,
41 #[serde(skip)]
42 relative_path: Option<PathBuf>,
43}
44
45#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
46pub struct ProductExpr {
47 pub mask: HexEncoded<u32>,
48 pub value: HexEncoded<u32>,
49}
50
51#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
55pub enum ManifestExtEntrySpec {
56 #[serde(alias = "spx_key")]
57 SpxKey {
58 spx_key: PathBuf,
60 },
61 #[serde(alias = "spx_signature")]
62 SpxSignature {
63 spx_signature: PathBuf,
65 },
66
67 #[serde(alias = "image_type")]
68 ImageType { image_type: u32 },
69
70 #[serde(alias = "secver_write")]
71 SecVerWrite {
72 secver_write: bool,
74 },
75
76 #[serde(alias = "integrator_specific_firmware_binding")]
77 Isfb {
78 strike_mask: HexEncoded<u128>,
79 product_expr: Vec<ProductExpr>,
80 },
81
82 #[serde(alias = "isfb_erase_policy")]
83 IsfbErasePolicy { erase_allowed: bool },
84
85 #[serde(alias = "raw")]
86 Raw {
87 name: HexEncoded<u32>,
88 identifier: HexEncoded<u32>,
89 signed: bool,
90 value: Vec<HexEncoded<u8>>,
91 },
92}
93
94#[derive(Debug)]
95pub enum ManifestExtEntry {
96 SpxKey(ManifestExtSpxKey),
97 SpxSignature(Box<ManifestExtSpxSignature>),
98 ImageType(ManifestExtImageType),
99 SecVerWrite(ManifestExtSecVerWrite),
100 Isfb(ManifestExtIsfb),
101 IsfbErasePolicy(ManifestExtIsfbErasePolicy),
102 Raw {
103 header: ManifestExtHeader,
104 data: Vec<u8>,
105 },
106}
107
108impl ManifestExtSpec {
109 pub fn read_from_file(path: &Path) -> Result<Self> {
114 let mut spec: Self = deser_hjson::from_str(&std::fs::read_to_string(path)?)?;
115 spec.relative_path = path.parent().map(|v| v.to_owned());
116 Ok(spec)
117 }
118
119 pub fn source_path(&self) -> Option<&Path> {
121 self.relative_path.as_deref()
122 }
123}
124
125impl ManifestExtEntrySpec {
126 pub fn id(&self) -> u32 {
127 match self {
128 ManifestExtEntrySpec::SpxKey { spx_key: _ } => MANIFEST_EXT_ID_SPX_KEY,
129 ManifestExtEntrySpec::SpxSignature { spx_signature: _ } => {
130 MANIFEST_EXT_ID_SPX_SIGNATURE
131 }
132 ManifestExtEntrySpec::SecVerWrite { .. } => MANIFEST_EXT_ID_SECVER_WRITE,
133 ManifestExtEntrySpec::Isfb { .. } => MANIFEST_EXT_ID_ISFB,
134 ManifestExtEntrySpec::IsfbErasePolicy { .. } => MANIFEST_EXT_ID_ISFB_ERASE,
135 ManifestExtEntrySpec::ImageType { image_type: _ } => MANIFEST_EXT_ID_IMAGE_TYPE,
136 ManifestExtEntrySpec::Raw { identifier, .. } => **identifier,
137 }
138 }
139
140 pub fn is_signed(&self) -> bool {
141 match self {
142 ManifestExtEntrySpec::SpxKey { .. }
143 | ManifestExtEntrySpec::SecVerWrite { .. }
144 | ManifestExtEntrySpec::Isfb { .. }
145 | ManifestExtEntrySpec::IsfbErasePolicy { .. }
146 | ManifestExtEntrySpec::ImageType { .. } => true,
147 ManifestExtEntrySpec::SpxSignature { .. } => false,
148 ManifestExtEntrySpec::Raw { signed, .. } => *signed,
149 }
150 }
151}
152
153impl ManifestExtEntry {
154 pub fn new_spx_key_entry(key: &SpxPublicKey) -> Result<Self> {
156 Ok(ManifestExtEntry::SpxKey(ManifestExtSpxKey {
157 header: ManifestExtHeader {
158 identifier: MANIFEST_EXT_ID_SPX_KEY,
159 name: MANIFEST_EXT_NAME_SPX_KEY,
160 },
161 key: SigverifySpxKey {
162 data: le_bytes_to_word_arr(key.as_bytes())?,
163 },
164 }))
165 }
166
167 pub fn new_spx_signature_entry(signature: &[u8]) -> Result<Self> {
169 Ok(ManifestExtEntry::SpxSignature(Box::new(
170 ManifestExtSpxSignature {
171 header: ManifestExtHeader {
172 identifier: MANIFEST_EXT_ID_SPX_SIGNATURE,
173 name: MANIFEST_EXT_NAME_SPX_SIGNATURE,
174 },
175 signature: SigverifySpxSignature {
176 data: le_bytes_to_word_arr(signature)?,
177 },
178 },
179 )))
180 }
181
182 pub fn new_image_type_entry(image_type: u32) -> Result<Self> {
183 Ok(ManifestExtEntry::ImageType(ManifestExtImageType {
184 header: ManifestExtHeader {
185 identifier: MANIFEST_EXT_ID_IMAGE_TYPE,
186 name: MANIFEST_EXT_NAME_IMAGE_TYPE,
187 },
188 image_type,
189 }))
190 }
191
192 pub fn new_secver_write_entry(write: u32) -> Result<Self> {
193 Ok(ManifestExtEntry::SecVerWrite(ManifestExtSecVerWrite {
194 header: ManifestExtHeader {
195 identifier: MANIFEST_EXT_ID_SECVER_WRITE,
196 name: MANIFEST_EXT_NAME_SECVER_WRITE,
197 },
198 write,
199 }))
200 }
201
202 pub fn new_isfb_erase_policy_entry(erase_allowed: u32) -> Result<Self> {
203 Ok(ManifestExtEntry::IsfbErasePolicy(
204 ManifestExtIsfbErasePolicy {
205 header: ManifestExtHeader {
206 identifier: MANIFEST_EXT_ID_ISFB_ERASE,
207 name: MANIFEST_EXT_NAME_ISFB_ERASE,
208 },
209 erase_allowed,
210 },
211 ))
212 }
213
214 pub fn new_isfb_entry(strike_mask: u128, product_expr_spec: Vec<ProductExpr>) -> Result<Self> {
215 let product_expr_count = product_expr_spec.len() as u32;
216 let product_expr = product_expr_spec
217 .into_iter()
218 .map(|expr| ManifestExtIsfbProductExpr {
219 mask: expr.mask.0,
220 value: expr.value.0,
221 })
222 .collect();
223 Ok(ManifestExtEntry::Isfb(ManifestExtIsfb {
224 header: ManifestExtHeader {
225 identifier: MANIFEST_EXT_ID_ISFB,
226 name: MANIFEST_EXT_NAME_ISFB,
227 },
228 strike_mask,
229 product_expr_count,
230 product_expr,
231 }))
232 }
233
234 pub fn from_spec(spec: &ManifestExtEntrySpec) -> Result<Self> {
236 Ok(match spec {
237 ManifestExtEntrySpec::SpxKey { spx_key } => {
238 ManifestExtEntry::new_spx_key_entry(&SpxPublicKey::read_pem_file(spx_key)?)?
239 }
240 ManifestExtEntrySpec::SpxSignature { spx_signature } => {
241 ManifestExtEntry::new_spx_signature_entry(&std::fs::read(spx_signature)?)?
242 }
243 ManifestExtEntrySpec::ImageType { image_type } => {
244 ManifestExtEntry::new_image_type_entry(*image_type)?
245 }
246 ManifestExtEntrySpec::SecVerWrite { secver_write } => {
247 ManifestExtEntry::new_secver_write_entry(
248 (if *secver_write {
249 HardenedBool::True
250 } else {
251 HardenedBool::False
252 })
253 .into(),
254 )?
255 }
256 ManifestExtEntrySpec::IsfbErasePolicy { erase_allowed } => {
257 ManifestExtEntry::new_isfb_erase_policy_entry(
258 (if *erase_allowed {
259 HardenedBool::True
260 } else {
261 HardenedBool::False
262 })
263 .into(),
264 )?
265 }
266 ManifestExtEntrySpec::Isfb {
267 strike_mask,
268 product_expr,
269 } => ManifestExtEntry::new_isfb_entry(**strike_mask, product_expr.to_vec())?,
270 ManifestExtEntrySpec::Raw {
271 name,
272 identifier,
273 value,
274 ..
275 } => ManifestExtEntry::Raw {
276 header: ManifestExtHeader {
277 identifier: **identifier,
278 name: **name,
279 },
280 data: value.iter().map(|v| **v).collect(),
281 },
282 })
283 }
284
285 pub fn header(&self) -> &ManifestExtHeader {
287 match self {
288 ManifestExtEntry::SpxKey(key) => &key.header,
289 ManifestExtEntry::SpxSignature(sig) => &sig.header,
290 ManifestExtEntry::ImageType(image_type) => &image_type.header,
291 ManifestExtEntry::SecVerWrite(sv) => &sv.header,
292 ManifestExtEntry::Isfb(isfb) => &isfb.header,
293 ManifestExtEntry::IsfbErasePolicy(erase) => &erase.header,
294 ManifestExtEntry::Raw { header, data: _ } => header,
295 }
296 }
297
298 pub fn to_vec(&self) -> Vec<u8> {
300 match self {
301 ManifestExtEntry::SpxKey(key) => key.as_bytes().to_vec(),
302 ManifestExtEntry::SpxSignature(sig) => sig.as_bytes().to_vec(),
303 ManifestExtEntry::ImageType(image_type) => image_type.as_bytes().to_vec(),
304 ManifestExtEntry::SecVerWrite(sv) => sv.as_bytes().to_vec(),
305 ManifestExtEntry::Isfb(isfb) => isfb.to_vec().unwrap(),
306 ManifestExtEntry::IsfbErasePolicy(erase) => erase.as_bytes().to_vec(),
307 ManifestExtEntry::Raw { header, data } => {
308 header.as_bytes().iter().chain(data).copied().collect()
309 }
310 }
311 }
312}
313
314#[cfg(test)]
315mod tests {
316 use super::*;
317 use crate::util::hexdump::hexdump_string;
318 use crate::util::num_de::HexEncoded;
319 use crate::util::testdata;
320
321 #[test]
322 fn test_manifest_ext_from_hjson() {
323 let spec = ManifestExtSpec::read_from_file(&testdata("image/manifest_ext.hjson")).unwrap();
324 assert_eq!(spec.source_path(), Some(testdata("image").as_path()));
325 assert_eq!(spec.extension_params.len(), 6);
326 assert_eq!(
327 spec.extension_params[0],
328 ManifestExtEntrySpec::SpxKey {
329 spx_key: "test_spx.pem".into()
330 }
331 );
332 assert!(spec.extension_params[0].is_signed());
333 assert_eq!(
334 spec.extension_params[1],
335 ManifestExtEntrySpec::SecVerWrite { secver_write: true }
336 );
337 assert!(spec.extension_params[1].is_signed());
338 assert_eq!(
339 spec.extension_params[2],
340 ManifestExtEntrySpec::Isfb {
341 strike_mask: HexEncoded(0x0fedcba987654321fedcba9876543210),
342 product_expr: vec![
343 ProductExpr {
344 mask: HexEncoded(0xffffffff),
345 value: HexEncoded(0xa5a5a5a5),
346 },
347 ProductExpr {
348 mask: HexEncoded(0xf0f0f0f0),
349 value: HexEncoded(0xa0a0a0a0),
350 },
351 ]
352 }
353 );
354 assert!(spec.extension_params[2].is_signed());
355 assert_eq!(
356 spec.extension_params[3],
357 ManifestExtEntrySpec::IsfbErasePolicy {
358 erase_allowed: false
359 }
360 );
361 assert!(spec.extension_params[3].is_signed());
362 assert_eq!(
363 spec.extension_params[4],
364 ManifestExtEntrySpec::Raw {
365 name: HexEncoded(0xbeef),
366 identifier: HexEncoded(0xabcd),
367 signed: true,
368 value: [0x01, 0x23, 0x45, 0x67].map(HexEncoded).to_vec()
369 }
370 );
371 assert!(spec.extension_params[4].is_signed());
372 assert_eq!(
373 spec.extension_params[5],
374 ManifestExtEntrySpec::SpxSignature {
375 spx_signature: "test_signature.bin".into()
376 }
377 );
378 assert!(!spec.extension_params[5].is_signed());
379 }
380
381 const MAN_EXT_ISFB_CONF: &str = "\
38200000000: 49 53 46 42 49 53 46 42 10 32 54 76 98 ba dc fe ISFBISFB.2Tv....
38300000010: 21 43 65 87 a9 cb ed 0f 02 00 00 00 4f 30 50 10 !Ce.........O0P.
38400000020: 05 00 00 01 f0 f0 f0 f0 a0 a0 a0 a0 ............
385";
386 #[test]
387 fn test_man_ext_isfb_write() -> Result<()> {
388 let isfb = ManifestExtEntry::new_isfb_entry(
389 0x0fedcba987654321fedcba9876543210,
390 vec![
391 ProductExpr {
392 mask: HexEncoded(0x1050304f),
393 value: HexEncoded(0x01000005),
394 },
395 ProductExpr {
396 mask: HexEncoded(0xf0f0f0f0),
397 value: HexEncoded(0xa0a0a0a0),
398 },
399 ],
400 )?;
401
402 let bin = isfb.to_vec();
403 eprintln!("{}", hexdump_string(&bin)?);
404 assert_eq!(hexdump_string(&bin)?, MAN_EXT_ISFB_CONF);
405 Ok(())
406 }
407
408 const MAN_EXT_ISFB_ERASE_POLICY: &str = "\
40900000000: 49 53 46 45 49 53 46 45 d4 01 00 00 ISFEISFE....
410";
411
412 #[test]
413 fn test_man_ext_isfb_erase_policy_write() -> Result<()> {
414 let isfb = ManifestExtEntry::new_isfb_erase_policy_entry(HardenedBool::False.into())?;
415
416 let bin = isfb.to_vec();
417 eprintln!("{}", hexdump_string(&bin)?);
418 assert_eq!(hexdump_string(&bin)?, MAN_EXT_ISFB_ERASE_POLICY);
419 Ok(())
420 }
421}