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::image::manifest::*;
12use crate::image::manifest_def::le_bytes_to_word_arr;
13use crate::util::num_de::HexEncoded;
14use crate::with_unknown;
15use sphincsplus::{DecodeKey, SpxPublicKey};
16
17#[derive(Debug, Error)]
18pub enum ManifestExtError {
19    #[error("Extension ID 0x{0:x} has duplicate extension data.")]
20    DuplicateEntry(u32),
21}
22
23with_unknown! {
24    /// Known manifest extension variant IDs.
25    #[derive(Default)]
26    pub enum ManifestExtId: u32 {
27        spx_key = MANIFEST_EXT_ID_SPX_KEY,
28        spx_signature = MANIFEST_EXT_ID_SPX_SIGNATURE,
29        image_type = MANIFEST_EXT_ID_IMAGE_TYPE,
30    }
31}
32
33/// Top level spec for manifest extension HJSON files.
34#[derive(Default, Debug, Deserialize, Serialize)]
35pub struct ManifestExtSpec {
36    pub signed_region: Vec<ManifestExtEntrySpec>,
37    pub unsigned_region: Vec<ManifestExtEntrySpec>,
38    #[serde(skip)]
39    relative_path: Option<PathBuf>,
40}
41
42/// Specs for the known extension variants.
43///
44/// This includes a raw variant that can take any id, name, and value.
45#[derive(Debug, Deserialize, Serialize, PartialEq)]
46#[serde(untagged)]
47pub enum ManifestExtEntrySpec {
48    SpxKey {
49        /// The path to the SPHINCS+ public or private key.
50        ///
51        /// If a relative path is used, this path will typically be resolved relative to the path
52        /// given by `ManifestExtSpc::source_path()` of the `ManifestExtSpec` that contains this
53        /// spec.
54        spx_key: PathBuf,
55    },
56    SpxSignature {
57        /// The path to the SPHINCS+ signature.
58        ///
59        /// If a relative path is used, this path will typically be resolved relative to the path
60        /// given by `ManifestExtSpc::source_path()` of the `ManifestExtSpec` that contains this
61        /// spec.
62        spx_signature: PathBuf,
63    },
64
65    #[serde(alias = "image_type")]
66    ImageType { image_type: u32 },
67
68    #[serde(alias = "raw")]
69    Raw {
70        name: HexEncoded<u32>,
71        identifier: HexEncoded<u32>,
72        value: Vec<HexEncoded<u8>>,
73    },
74}
75
76#[derive(Debug)]
77pub enum ManifestExtEntry {
78    SpxKey(ManifestExtSpxKey),
79    SpxSignature(Box<ManifestExtSpxSignature>),
80    ImageType(ManifestExtImageType),
81    Raw {
82        header: ManifestExtHeader,
83        data: Vec<u8>,
84    },
85}
86
87impl ManifestExtSpec {
88    /// Reads in a `ManifestExtSpec` from an HJSON file.
89    ///
90    /// The parent of `path` (the directory containing the HJSON file to be loaded) is used when
91    /// resolving relative paths for any extension data that references a file.
92    pub fn read_from_file(path: &Path) -> Result<Self> {
93        let mut spec: Self = deser_hjson::from_str(&std::fs::read_to_string(path)?)?;
94        spec.relative_path = path.parent().map(|v| v.to_owned());
95        Ok(spec)
96    }
97
98    /// The partent of the path that was provided when loading this spec.
99    pub fn source_path(&self) -> Option<&Path> {
100        self.relative_path.as_deref()
101    }
102}
103
104impl ManifestExtEntrySpec {
105    pub fn id(&self) -> u32 {
106        match self {
107            ManifestExtEntrySpec::SpxKey { spx_key: _ } => MANIFEST_EXT_ID_SPX_KEY,
108            ManifestExtEntrySpec::SpxSignature { spx_signature: _ } => {
109                MANIFEST_EXT_ID_SPX_SIGNATURE
110            }
111            ManifestExtEntrySpec::Raw {
112                name: _,
113                identifier,
114                value: _,
115            } => **identifier,
116            ManifestExtEntrySpec::ImageType { image_type: _ } => MANIFEST_EXT_ID_IMAGE_TYPE,
117        }
118    }
119}
120
121impl ManifestExtEntry {
122    /// Creates a new manifest extension from a given SPHINCS+ `key`.
123    pub fn new_spx_key_entry(key: &SpxPublicKey) -> Result<Self> {
124        Ok(ManifestExtEntry::SpxKey(ManifestExtSpxKey {
125            header: ManifestExtHeader {
126                identifier: MANIFEST_EXT_ID_SPX_KEY,
127                name: MANIFEST_EXT_NAME_SPX_KEY,
128            },
129            key: SigverifySpxKey {
130                data: le_bytes_to_word_arr(key.as_bytes())?,
131            },
132        }))
133    }
134
135    /// Creates a new manifest extension from a given SPHINCS+ `signature`.
136    pub fn new_spx_signature_entry(signature: &[u8]) -> Result<Self> {
137        Ok(ManifestExtEntry::SpxSignature(Box::new(
138            ManifestExtSpxSignature {
139                header: ManifestExtHeader {
140                    identifier: MANIFEST_EXT_ID_SPX_SIGNATURE,
141                    name: MANIFEST_EXT_NAME_SPX_SIGNATURE,
142                },
143                signature: SigverifySpxSignature {
144                    data: le_bytes_to_word_arr(signature)?,
145                },
146            },
147        )))
148    }
149
150    pub fn new_image_type_entry(image_type: u32) -> Result<Self> {
151        Ok(ManifestExtEntry::ImageType(ManifestExtImageType {
152            header: ManifestExtHeader {
153                identifier: MANIFEST_EXT_ID_IMAGE_TYPE,
154                name: MANIFEST_EXT_NAME_IMAGE_TYPE,
155            },
156            image_type,
157        }))
158    }
159
160    /// Creates a new manifest extension from a given `spec`.
161    ///
162    /// For extensions that reference other resources, such as SPHINCS+ keys or signatures, this
163    /// function will attempt to load those resources to create the extension.
164    pub fn from_spec(spec: &ManifestExtEntrySpec, relative_path: Option<&Path>) -> Result<Self> {
165        let relative_path = relative_path.unwrap_or(Path::new(""));
166        Ok(match spec {
167            ManifestExtEntrySpec::SpxKey { spx_key } => ManifestExtEntry::new_spx_key_entry(
168                &SpxPublicKey::read_pem_file(relative_path.join(spx_key))?,
169            )?,
170            ManifestExtEntrySpec::SpxSignature { spx_signature } => {
171                ManifestExtEntry::new_spx_signature_entry(&std::fs::read(
172                    relative_path.join(spx_signature),
173                )?)?
174            }
175            ManifestExtEntrySpec::ImageType { image_type } => {
176                ManifestExtEntry::new_image_type_entry(*image_type)?
177            }
178            ManifestExtEntrySpec::Raw {
179                name,
180                identifier,
181                value,
182            } => ManifestExtEntry::Raw {
183                header: ManifestExtHeader {
184                    identifier: **identifier,
185                    name: **name,
186                },
187                data: value.iter().map(|v| **v).collect(),
188            },
189        })
190    }
191
192    /// Returns the header portion of this extension.
193    pub fn header(&self) -> &ManifestExtHeader {
194        match self {
195            ManifestExtEntry::SpxKey(key) => &key.header,
196            ManifestExtEntry::SpxSignature(sig) => &sig.header,
197            ManifestExtEntry::ImageType(image_type) => &image_type.header,
198            ManifestExtEntry::Raw { header, data: _ } => header,
199        }
200    }
201
202    /// Allocates a new byte `Vec<u8>` and writes the binary extension data to it.
203    pub fn to_vec(&self) -> Vec<u8> {
204        match self {
205            ManifestExtEntry::SpxKey(key) => key.as_bytes().to_vec(),
206            ManifestExtEntry::SpxSignature(sig) => sig.as_bytes().to_vec(),
207            ManifestExtEntry::ImageType(image_type) => image_type.as_bytes().to_vec(),
208            ManifestExtEntry::Raw { header, data } => {
209                header.as_bytes().iter().chain(data).copied().collect()
210            }
211        }
212    }
213}
214
215#[cfg(test)]
216mod tests {
217    use super::*;
218    use crate::util::num_de::HexEncoded;
219    use crate::util::testdata;
220
221    #[test]
222    fn test_manifest_ext_from_hjson() {
223        let spec = ManifestExtSpec::read_from_file(&testdata("image/manifest_ext.hjson")).unwrap();
224        assert_eq!(spec.source_path(), Some(testdata("image").as_path()));
225        assert_eq!(spec.signed_region.len(), 2);
226        assert_eq!(
227            spec.signed_region[0],
228            ManifestExtEntrySpec::SpxKey {
229                spx_key: "test_spx.pem".into()
230            }
231        );
232        assert_eq!(
233            spec.signed_region[1],
234            ManifestExtEntrySpec::Raw {
235                name: HexEncoded(0xbeef),
236                identifier: HexEncoded(0xabcd),
237                value: [0x01, 0x23, 0x45, 0x67].map(HexEncoded).to_vec()
238            }
239        );
240        assert_eq!(spec.unsigned_region.len(), 1);
241        assert_eq!(
242            spec.unsigned_region[0],
243            ManifestExtEntrySpec::SpxSignature {
244                spx_signature: "test_signature.bin".into()
245            }
246        );
247    }
248}