opentitanlib/image/
manifest.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
5//! Structs for reading and writing manifests of flash boot stage images.
6//!
7//! Note: The structs below must match the definitions in
8//! sw/device/silicon_creator/lib/manifest.h.
9
10#![deny(warnings)]
11#![deny(unused)]
12#![deny(unsafe_code)]
13
14use crate::with_unknown;
15use anyhow::Result;
16use byteorder::{LittleEndian, WriteBytesExt};
17use serde::{Deserialize, Serialize};
18use serde_annotate::Annotate;
19use std::io::Write;
20use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
21
22// Currently, these definitions must be updated manually but they can be
23// generated using the following commands (requires bindgen):
24//   cargo install bindgen
25//   cd "${REPO_TOP}"
26//   bindgen --allowlist-type manifest_t --allowlist-var "MANIFEST_.*" \
27//      --allowlist-var "CHIP_.*" \
28//      --no-doc-comments --no-layout-tests \
29//      sw/device/silicon_creator/lib/manifest.h \
30//      sw/device/silicon_creator/lib/base/chip.h \
31//      -- -I./ -Isw/device/lib/base/freestanding
32// TODO: Generate some constants as hex if possible, replacing manually for now.
33
34pub const CHIP_MANIFEST_SIZE: u32 = 1024;
35
36// TODO(moidx): Update to a valid number once we figure out a manifest
37// versioning scheme.
38pub const CHIP_MANIFEST_VERSION_MAJOR2: u16 = 0x0002;
39pub const CHIP_MANIFEST_VERSION_MINOR1: u16 = 0x6c47;
40pub const CHIP_MANIFEST_VERSION_MAJOR1: u16 = 0x71c3;
41pub const CHIP_MANIFEST_EXT_TABLE_COUNT: usize = 15;
42pub const MANIFEST_USAGE_CONSTRAINT_UNSELECTED_WORD_VAL: u32 = 0xa5a5a5a5;
43pub const MANIFEST_EXT_ID_SPX_KEY: u32 = 0x94ac01ec;
44pub const MANIFEST_EXT_ID_SPX_SIGNATURE: u32 = 0xad77f84a;
45pub const MANIFEST_EXT_ID_IMAGE_TYPE: u32 = 0x494d4754;
46pub const MANIFEST_EXT_ID_SECVER_WRITE: u32 = 0x3f086a41;
47pub const MANIFEST_EXT_ID_ISFB: u32 = 0x42465349;
48pub const MANIFEST_EXT_ID_ISFB_ERASE: u32 = 0x45465349;
49pub const MANIFEST_EXT_ID_OWNER_TRANSFER_BLOB: u32 = 0x4254574f;
50pub const MANIFEST_EXT_ID_MLDSA87_KEY: u32 = 0xa3e247b8;
51pub const MANIFEST_EXT_ID_MLDSA87_SIGNATURE: u32 = 0x4714ac7d;
52pub const MANIFEST_EXT_NAME_SPX_KEY: u32 = 0x30545845;
53pub const MANIFEST_EXT_NAME_SPX_SIGNATURE: u32 = 0x31545845;
54pub const MANIFEST_EXT_NAME_IMAGE_TYPE: u32 = 0x494d4754;
55pub const MANIFEST_EXT_NAME_SECVER_WRITE: u32 = 0x56434553;
56pub const MANIFEST_EXT_NAME_ISFB: u32 = 0x42465349;
57pub const MANIFEST_EXT_NAME_ISFB_ERASE: u32 = 0x45465349;
58pub const MANIFEST_EXT_NAME_OWNER_TRANSFER_BLOB: u32 = 0x4254574f;
59pub const MANIFEST_EXT_NAME_MLDSA87_KEY: u32 = 0x4b384c4d;
60pub const MANIFEST_EXT_NAME_MLDSA87_SIGNATURE: u32 = 0x53384c4d;
61pub const CHIP_ROM_EXT_IDENTIFIER: u32 = 0x4552544f;
62pub const CHIP_BL0_IDENTIFIER: u32 = 0x3042544f;
63pub const CHIP_ROM_EXT_SIZE_MIN: u32 = 8788;
64pub const CHIP_ROM_EXT_SIZE_MAX: u32 = 0x10000;
65pub const CHIP_BL0_SIZE_MIN: u32 = 8788;
66pub const CHIP_BL0_SIZE_MAX: u32 = 0x70000;
67
68with_unknown! {
69    pub enum ManifestKind: u32 {
70        RomExt = CHIP_ROM_EXT_IDENTIFIER,
71        Application = CHIP_BL0_IDENTIFIER,
72    }
73}
74
75/// Buffer of reserved bytes.
76///
77/// This struct is used to hold reserved bytes in the manifest to pad fields
78/// with 0xa5 to match layout specifications.
79#[repr(C)]
80#[derive(KnownLayout, Immutable, IntoBytes, FromBytes, Debug, Clone, Copy)]
81pub struct ReservedBuffer<const COUNT: usize> {
82    a: [u8; COUNT],
83}
84
85impl<const COUNT: usize> Default for ReservedBuffer<COUNT> {
86    fn default() -> Self {
87        Self {
88            a: std::array::from_fn(|_| 0xa5),
89        }
90    }
91}
92
93/// Manifest for boot stage images stored in flash.
94#[repr(C)]
95#[derive(KnownLayout, Immutable, IntoBytes, FromBytes, Debug, Default)]
96pub struct Manifest {
97    pub signature: SigverifyBuffer,
98    pub reserved_signature: ReservedBuffer<160>,
99    pub reserved_unsigned: ReservedBuffer<160>,
100    pub usage_constraints: ManifestUsageConstraints,
101    pub pub_key: SigverifyBuffer,
102    pub reserved_public_key: ReservedBuffer<160>,
103    pub reserved: ReservedBuffer<160>,
104    pub address_translation: u32,
105    pub identifier: u32,
106    pub manifest_version: ManifestVersion,
107    pub signed_region_end: u32,
108    pub length: u32,
109    pub version_major: u32,
110    pub version_minor: u32,
111    pub security_version: u32,
112    pub timestamp: Timestamp,
113    pub binding_value: KeymgrBindingValue,
114    pub max_key_version: u32,
115    pub code_start: u32,
116    pub code_end: u32,
117    pub entry_point: u32,
118    pub extensions: ManifestExtTable,
119}
120
121/// A type that holds 2 16-bit values for manifest major and minor format versions.
122#[repr(C)]
123#[derive(Immutable, IntoBytes, FromBytes, Debug, Default, Copy, Clone)]
124pub struct ManifestVersion {
125    pub minor: u16,
126    pub major: u16,
127}
128
129/// A type that holds 1964 32-bit words for SPHINCS+ signatures.
130#[repr(C)]
131#[derive(Immutable, IntoBytes, FromBytes, Debug, Copy, Clone)]
132pub struct SigverifySpxSignature {
133    pub data: [u32; 1964usize],
134}
135
136impl Default for SigverifySpxSignature {
137    fn default() -> Self {
138        Self {
139            data: [0; 1964usize],
140        }
141    }
142}
143
144/// Extension header.
145#[repr(C)]
146#[derive(Immutable, IntoBytes, FromBytes, Debug, Default, Serialize, Deserialize)]
147pub struct ManifestExtHeader {
148    pub identifier: u32,
149    pub name: u32,
150}
151
152impl ManifestExtHeader {
153    pub fn write(&self, dest: &mut impl Write) -> Result<()> {
154        dest.write_all(self.as_bytes())?;
155        Ok(())
156    }
157}
158
159/// SPHINCS+ signature manifest extension.
160#[repr(C)]
161#[derive(Immutable, IntoBytes, FromBytes, Debug, Default)]
162pub struct ManifestExtSpxSignature {
163    pub header: ManifestExtHeader,
164    pub signature: SigverifySpxSignature,
165}
166
167/// A type that holds 8 32-bit words for SPHINCS+ public keys.
168#[repr(C)]
169#[derive(Immutable, IntoBytes, FromBytes, Debug, Default, Copy, Clone)]
170pub struct SigverifySpxKey {
171    pub data: [u32; 8usize],
172}
173
174/// SPHINCS+ public key manifest extension.
175#[repr(C)]
176#[derive(Immutable, IntoBytes, FromBytes, Debug, Default)]
177pub struct ManifestExtSpxKey {
178    pub header: ManifestExtHeader,
179    pub key: SigverifySpxKey,
180}
181
182/// Size of an ML-DSA-87 public key in bytes (FIPS-204).
183pub const MLDSA87_PUBLIC_KEY_BYTES: usize = 2592;
184/// Size of an ML-DSA-87 signature in bytes (FIPS-204).
185pub const MLDSA87_SIGNATURE_BYTES: usize = 4627;
186/// Context string of the ML-DSA-87 image signature (FIPS-204). Must match
187/// `kSigverifyMldsa87ImageContext` in `sw/device/silicon_creator/lib/sigverify/mldsa87_key.c`.
188pub const MLDSA87_IMAGE_CONTEXT: &[u8] = b"OT-MLDSA-IMAGE";
189
190/// A type that holds an ML-DSA-87 signature, zero-padded to a whole number of 32-bit words.
191#[repr(C)]
192#[derive(Immutable, IntoBytes, FromBytes, Debug, Copy, Clone)]
193pub struct SigverifyMldsa87Signature {
194    pub data: [u32; MLDSA87_SIGNATURE_BYTES.div_ceil(size_of::<u32>())],
195}
196
197impl Default for SigverifyMldsa87Signature {
198    fn default() -> Self {
199        Self {
200            data: [0; MLDSA87_SIGNATURE_BYTES.div_ceil(size_of::<u32>())],
201        }
202    }
203}
204
205/// ML-DSA-87 signature manifest extension.
206#[repr(C)]
207#[derive(Immutable, IntoBytes, FromBytes, Debug, Default)]
208pub struct ManifestExtMldsa87Signature {
209    pub header: ManifestExtHeader,
210    pub signature: SigverifyMldsa87Signature,
211}
212
213/// A type that holds an ML-DSA-87 public key as 32-bit words.
214#[repr(C)]
215#[derive(Immutable, IntoBytes, FromBytes, Debug, Copy, Clone)]
216pub struct SigverifyMldsa87Key {
217    pub data: [u32; MLDSA87_PUBLIC_KEY_BYTES / size_of::<u32>()],
218}
219
220impl Default for SigverifyMldsa87Key {
221    fn default() -> Self {
222        Self {
223            data: [0; MLDSA87_PUBLIC_KEY_BYTES / size_of::<u32>()],
224        }
225    }
226}
227
228/// ML-DSA-87 public key manifest extension.
229#[repr(C)]
230#[derive(Immutable, IntoBytes, FromBytes, Debug, Default)]
231pub struct ManifestExtMldsa87Key {
232    pub header: ManifestExtHeader,
233    pub key: SigverifyMldsa87Key,
234}
235
236/// A type that holds 16 32-bit words for ECDSA-P256.
237#[repr(C)]
238#[derive(Immutable, IntoBytes, FromBytes, Debug, Clone, Copy, Default)]
239pub struct SigverifyBuffer {
240    pub data: [u32; 16usize],
241}
242
243#[repr(C)]
244#[derive(Immutable, IntoBytes, FromBytes, Debug, Default)]
245pub struct ManifestExtImageType {
246    pub header: ManifestExtHeader,
247    pub image_type: u32,
248}
249
250/// SecVer Write manifest extension
251#[repr(C)]
252#[derive(Immutable, IntoBytes, FromBytes, Debug, Default)]
253pub struct ManifestExtSecVerWrite {
254    pub header: ManifestExtHeader,
255    pub write: u32,
256}
257
258/// Integrator Specific Firmware Binding product expression.
259#[derive(Debug, Deserialize, Annotate)]
260pub struct ManifestExtIsfbProductExpr {
261    pub mask: u32,
262    pub value: u32,
263}
264
265/// Integrator Specific Firmware Binding manifest extension.
266#[derive(Debug, Serialize, Deserialize)]
267pub struct ManifestExtIsfb {
268    pub header: ManifestExtHeader,
269    pub strike_mask: u128,
270    pub product_expr_count: u32,
271    pub product_expr: Vec<ManifestExtIsfbProductExpr>,
272}
273
274impl ManifestExtIsfb {
275    pub fn write(&self, dest: &mut impl Write) -> Result<()> {
276        self.header.write(dest)?;
277        dest.write_u128::<LittleEndian>(self.strike_mask)?;
278        dest.write_u32::<LittleEndian>(self.product_expr_count)?;
279        for x in &self.product_expr {
280            dest.write_u32::<LittleEndian>(x.mask)?;
281            dest.write_u32::<LittleEndian>(x.value)?;
282        }
283        Ok(())
284    }
285
286    pub fn to_vec(&self) -> Result<Vec<u8>> {
287        let mut buf = Vec::new();
288        self.write(&mut buf)?;
289        Ok(buf)
290    }
291}
292
293#[repr(C)]
294#[derive(Immutable, IntoBytes, FromBytes, Debug, Default)]
295pub struct ManifestExtIsfbErasePolicy {
296    pub header: ManifestExtHeader,
297    pub erase_allowed: u32,
298}
299
300/// A type that holds the 256-bit device identifier.
301#[repr(C)]
302#[derive(Immutable, IntoBytes, FromBytes, Debug, Default)]
303pub struct LifecycleDeviceId {
304    pub device_id: [u32; 8usize],
305}
306
307/// Manifest usage constraints.
308#[repr(C)]
309#[derive(Immutable, IntoBytes, FromBytes, Debug)]
310pub struct ManifestUsageConstraints {
311    pub selector_bits: u32,
312    pub device_id: LifecycleDeviceId,
313    pub manuf_state_creator: u32,
314    pub manuf_state_owner: u32,
315    pub life_cycle_state: u32,
316}
317
318impl Default for ManifestUsageConstraints {
319    fn default() -> Self {
320        Self {
321            selector_bits: 0,
322            device_id: LifecycleDeviceId {
323                device_id: [MANIFEST_USAGE_CONSTRAINT_UNSELECTED_WORD_VAL; 8usize],
324            },
325            manuf_state_creator: MANIFEST_USAGE_CONSTRAINT_UNSELECTED_WORD_VAL,
326            manuf_state_owner: MANIFEST_USAGE_CONSTRAINT_UNSELECTED_WORD_VAL,
327            life_cycle_state: MANIFEST_USAGE_CONSTRAINT_UNSELECTED_WORD_VAL,
328        }
329    }
330}
331
332/// Manifest timestamp
333#[repr(C)]
334#[derive(Immutable, IntoBytes, FromBytes, Debug, Default)]
335pub struct Timestamp {
336    pub timestamp_low: u32,
337    pub timestamp_high: u32,
338}
339
340#[repr(C)]
341#[derive(Immutable, IntoBytes, FromBytes, Debug, Default)]
342pub struct KeymgrBindingValue {
343    pub data: [u32; 8usize],
344}
345
346#[repr(C)]
347#[derive(KnownLayout, Immutable, IntoBytes, FromBytes, Debug, Default, Copy, Clone)]
348pub struct ManifestExtTableEntry {
349    pub identifier: u32,
350    pub offset: u32,
351}
352
353#[repr(C)]
354#[derive(KnownLayout, Immutable, IntoBytes, FromBytes, Debug, Default, Copy, Clone)]
355pub struct ManifestExtTable {
356    pub entries: [ManifestExtTableEntry; CHIP_MANIFEST_EXT_TABLE_COUNT],
357}
358
359#[cfg(test)]
360mod tests {
361    use super::*;
362    use std::mem::{offset_of, size_of};
363
364    /// Checks the layout of the manifest struct.
365    ///
366    /// Implemented as a function because using `offset_of!` at compile-time
367    /// requires a nightly compiler.
368    #[test]
369    pub fn test_manifest_layout() {
370        assert_eq!(offset_of!(Manifest, signature), 0);
371        assert_eq!(offset_of!(Manifest, reserved_signature), 64);
372        assert_eq!(offset_of!(Manifest, reserved_unsigned), 224);
373        assert_eq!(offset_of!(Manifest, usage_constraints), 384);
374        assert_eq!(offset_of!(Manifest, pub_key), 432);
375        assert_eq!(offset_of!(Manifest, reserved_public_key), 496);
376        assert_eq!(offset_of!(Manifest, reserved), 656);
377        assert_eq!(offset_of!(Manifest, address_translation), 816);
378        assert_eq!(offset_of!(Manifest, identifier), 820);
379        assert_eq!(offset_of!(Manifest, manifest_version), 824);
380        assert_eq!(offset_of!(Manifest, signed_region_end), 828);
381        assert_eq!(offset_of!(Manifest, length), 832);
382        assert_eq!(offset_of!(Manifest, version_major), 836);
383        assert_eq!(offset_of!(Manifest, version_minor), 840);
384        assert_eq!(offset_of!(Manifest, security_version), 844);
385        assert_eq!(offset_of!(Manifest, timestamp), 848);
386        assert_eq!(offset_of!(Manifest, binding_value), 856);
387        assert_eq!(offset_of!(Manifest, max_key_version), 888);
388        assert_eq!(offset_of!(Manifest, code_start), 892);
389        assert_eq!(offset_of!(Manifest, code_end), 896);
390        assert_eq!(offset_of!(Manifest, entry_point), 900);
391        assert_eq!(offset_of!(Manifest, extensions), 904);
392        assert_eq!(size_of::<Manifest>(), CHIP_MANIFEST_SIZE as usize);
393    }
394}