1use 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 #[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#[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#[derive(Debug, Deserialize, Serialize, PartialEq)]
46#[serde(untagged)]
47pub enum ManifestExtEntrySpec {
48 SpxKey {
49 spx_key: PathBuf,
55 },
56 SpxSignature {
57 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 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 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 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 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 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 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 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}