opentitanlib/image/
manifest_ext.rs

1// Copyright lowRISC contributors (OpenTitan project).
2// Licensed under the Apache License, Version 2.0, see LICENSE for details.
3// SPDX-License-Identifier: Apache-2.0
4
5use 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    /// Known manifest extension variant IDs.
26    #[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/// Top level spec for manifest extension HJSON files.
38#[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/// Specs for the known extension variants.
52///
53/// This includes a raw variant that can take any id, name, and value.
54#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
55pub enum ManifestExtEntrySpec {
56    #[serde(alias = "spx_key")]
57    SpxKey {
58        /// The path to the SPHINCS+ public or private key.
59        spx_key: PathBuf,
60    },
61    #[serde(alias = "spx_signature")]
62    SpxSignature {
63        /// The path to the SPHINCS+ signature.
64        spx_signature: PathBuf,
65    },
66
67    #[serde(alias = "image_type")]
68    ImageType { image_type: u32 },
69
70    #[serde(alias = "secver_write")]
71    SecVerWrite {
72        /// Whether or not to write the security version into boot data.
73        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    /// Reads in a `ManifestExtSpec` from an HJSON file.
110    ///
111    /// The parent of `path` (the directory containing the HJSON file to be loaded) is used when
112    /// resolving relative paths for any extension data that references a file.
113    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    /// The partent of the path that was provided when loading this spec.
120    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    /// Creates a new manifest extension from a given SPHINCS+ `key`.
155    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    /// Creates a new manifest extension from a given SPHINCS+ `signature`.
168    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    /// Creates a new manifest extension from a given `spec`.
235    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    /// Returns the header portion of this extension.
286    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    /// Allocates a new byte `Vec<u8>` and writes the binary extension data to it.
299    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}