[opentitanlib] Add manfiest definition for HJSON deserilization
Signed-off-by: Jon Flatley <jflat@google.com>
diff --git a/sw/host/opentitanlib/BUILD b/sw/host/opentitanlib/BUILD
index cc4fe85..fe8df10 100644
--- a/sw/host/opentitanlib/BUILD
+++ b/sw/host/opentitanlib/BUILD
@@ -24,6 +24,10 @@
"src/bootstrap/rescue.rs",
"src/crypto/mod.rs",
"src/crypto/sha256.rs",
+ "src/image/image.rs",
+ "src/image/manifest.rs",
+ "src/image/manifest_def.rs",
+ "src/image/mod.rs",
"src/io/gpio.rs",
"src/io/i2c.rs",
"src/io/mod.rs",
@@ -36,7 +40,6 @@
"src/otp/otp_img.rs",
"src/otp/otp_mmap.rs",
"src/otp/vmem_serialize.rs",
- "src/signer/manifest.rs",
"src/spiflash/flash.rs",
"src/spiflash/mod.rs",
"src/spiflash/sfdp.rs",
@@ -65,7 +68,6 @@
"src/util/bigint.rs",
"src/util/bitfield.rs",
"src/util/file.rs",
- "src/util/image.rs",
"src/util/mod.rs",
"src/util/num_de.rs",
"src/util/parse_int.rs",
@@ -89,12 +91,12 @@
],
crate = ":opentitanlib",
data = [
+ "src/image/testdata/hello.txt",
+ "src/image/testdata/manifest.hjson",
+ "src/image/testdata/world.txt",
"src/otp/testdata/lc_ctrl_state.hjson",
"src/otp/testdata/otp_ctrl_img_dev.hjson",
"src/otp/testdata/otp_ctrl_mmap.hjson",
"src/otp/testdata/output.vmem",
- "src/signer/testdata/manifest.hjson",
- "src/util/testdata/hello.txt",
- "src/util/testdata/world.txt",
],
)
diff --git a/sw/host/opentitanlib/src/util/image.rs b/sw/host/opentitanlib/src/image/image.rs
similarity index 100%
rename from sw/host/opentitanlib/src/util/image.rs
rename to sw/host/opentitanlib/src/image/image.rs
diff --git a/sw/host/opentitanlib/src/signer/manifest.rs b/sw/host/opentitanlib/src/image/manifest.rs
similarity index 98%
rename from sw/host/opentitanlib/src/signer/manifest.rs
rename to sw/host/opentitanlib/src/image/manifest.rs
index af7de91..0c76754 100644
--- a/sw/host/opentitanlib/src/signer/manifest.rs
+++ b/sw/host/opentitanlib/src/image/manifest.rs
@@ -93,8 +93,7 @@
Self {
selector_bits: 0,
device_id: LifecycleDeviceId {
- device_id: [MANIFEST_USAGE_CONSTRAINT_UNSELECTED_WORD_VAL;
- 8usize],
+ device_id: [MANIFEST_USAGE_CONSTRAINT_UNSELECTED_WORD_VAL; 8usize],
},
manuf_state_creator: MANIFEST_USAGE_CONSTRAINT_UNSELECTED_WORD_VAL,
manuf_state_owner: MANIFEST_USAGE_CONSTRAINT_UNSELECTED_WORD_VAL,
diff --git a/sw/host/opentitanlib/src/image/manifest_def.rs b/sw/host/opentitanlib/src/image/manifest_def.rs
new file mode 100644
index 0000000..cdbba60
--- /dev/null
+++ b/sw/host/opentitanlib/src/image/manifest_def.rs
@@ -0,0 +1,178 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+use crate::image::manifest::*;
+use crate::util::bigint::fixed_size_bigint;
+use crate::util::num_de::HexEncoded;
+use crate::util::parse_int::ParseInt;
+
+use anyhow::{anyhow, Result};
+use serde::Deserialize;
+use std::convert::{TryFrom, TryInto};
+use std::iter::IntoIterator;
+
+fixed_size_bigint!(ManifestRsa, 3072);
+
+#[derive(Debug, Deserialize)]
+struct ManifestBigInt(Option<HexEncoded<ManifestRsa>>);
+
+#[derive(Debug, Deserialize)]
+struct ManifestSmallInt<T: ParseInt>(Option<HexEncoded<T>>);
+
+/// A macro for wrapping manifest struct definitions that parse from HJSON.
+///
+/// The #[repr(C)] version of `Manifest` can only be built when the fields in `ManifestDef` are
+/// present. This macro sets up the field by field conversion and provides the field names for
+/// purposes of error reporting.
+macro_rules! manifest_def {
+ (struct $name:ident {
+ $($field_name:ident: $field_type:ty,)*
+ }, $out_type:ident) => {
+ #[derive(Deserialize, Debug)]
+ struct $name {
+ $($field_name: $field_type,)*
+ }
+
+ impl ManifestPacked<$out_type> for $name {
+ fn unpack(self, _name: &'static str) -> Result<$out_type> {
+ Ok($out_type {
+ // Call `unpack()` on each field with the field's name included for use in
+ // error messages.
+ $($field_name: self.$field_name
+ .unpack(stringify!($field_name))?.try_into()?,)*
+ })
+ }
+ }
+ }
+}
+
+trait ManifestPacked<T> {
+ /// The default error for missing fields.
+ fn unpack_err(&self, name: &'static str) -> Result<T> {
+ Err(anyhow!("Manifest is missing field {}", name))
+ }
+
+ /// Unpack optional fields in the manifest, and error if the field isn't defined.
+ fn unpack(self, name: &'static str) -> Result<T>;
+}
+
+impl ManifestPacked<ManifestRsa> for ManifestBigInt {
+ fn unpack(self, name: &'static str) -> Result<ManifestRsa> {
+ match self.0 {
+ Some(v) => Ok(v.0),
+ None => self.unpack_err(name),
+ }
+ }
+}
+
+impl<T: ParseInt> ManifestPacked<T> for ManifestSmallInt<T> {
+ fn unpack(self, name: &'static str) -> Result<T> {
+ match self.0 {
+ Some(v) => Ok(v.0),
+ None => self.unpack_err(name),
+ }
+ }
+}
+
+impl<T: ParseInt, const N: usize> ManifestPacked<[T; N]> for [ManifestSmallInt<T>; N] {
+ fn unpack(self, name: &'static str) -> Result<[T; N]> {
+ let results = self.map(|e| e.unpack(name));
+ if let Some(err_idx) = results.iter().position(Result::is_err) {
+ match IntoIterator::into_iter(results).nth(err_idx).unwrap()? {
+ _ => unreachable!(),
+ }
+ } else {
+ Ok(results.map(|x| x.unwrap()))
+ }
+ }
+}
+
+manifest_def! {
+ struct ManifestDef {
+ signature: ManifestBigInt,
+ usage_constraints: ManifestUsageConstraintsDef,
+ modulus: ManifestBigInt,
+ exponent: ManifestSmallInt<u32>,
+ identifier: ManifestSmallInt<u32>,
+ length: ManifestSmallInt<u32>,
+ version_major: ManifestSmallInt<u32>,
+ version_minor: ManifestSmallInt<u32>,
+ security_version: ManifestSmallInt<u32>,
+ timestamp: ManifestSmallInt<u64>,
+ binding_value: [ManifestSmallInt<u32>; 8],
+ max_key_version: ManifestSmallInt<u32>,
+ code_start: ManifestSmallInt<u32>,
+ code_end: ManifestSmallInt<u32>,
+ entry_point: ManifestSmallInt<u32>,
+ }, Manifest
+}
+
+manifest_def! {
+ struct ManifestUsageConstraintsDef {
+ selector_bits: ManifestSmallInt<u32>,
+ device_id: [ManifestSmallInt<u32>; 8],
+ manuf_state_creator: ManifestSmallInt<u32>,
+ manuf_state_owner: ManifestSmallInt<u32>,
+ life_cycle_state: ManifestSmallInt<u32>,
+ }, ManifestUsageConstraints
+}
+
+impl TryFrom<ManifestRsa> for SigverifyRsaBuffer {
+ type Error = anyhow::Error;
+
+ fn try_from(rsa: ManifestRsa) -> Result<SigverifyRsaBuffer> {
+ // Convert between the BigInt byte representation and the manifest word representation.
+ Ok(SigverifyRsaBuffer {
+ data: rsa
+ .to_le_bytes()
+ .chunks(4)
+ .map(|v| Ok(u32::from_le_bytes(v.try_into()?)))
+ .collect::<Result<Vec<u32>>>()?
+ .as_slice()
+ .try_into()?,
+ })
+ }
+}
+
+impl TryFrom<[u32; 96]> for SigverifyRsaBuffer {
+ type Error = anyhow::Error;
+
+ fn try_from(words: [u32; 96]) -> Result<SigverifyRsaBuffer> {
+ Ok(SigverifyRsaBuffer { data: words })
+ }
+}
+
+impl TryFrom<[u32; 8]> for KeymgrBindingValue {
+ type Error = anyhow::Error;
+
+ fn try_from(words: [u32; 8]) -> Result<KeymgrBindingValue> {
+ Ok(KeymgrBindingValue { data: words })
+ }
+}
+
+impl TryFrom<[u32; 8]> for LifecycleDeviceId {
+ type Error = anyhow::Error;
+
+ fn try_from(words: [u32; 8]) -> Result<LifecycleDeviceId> {
+ Ok(LifecycleDeviceId { device_id: words })
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::testdata;
+ use deser_hjson::from_str;
+
+ #[test]
+ fn test_manifest_from_hjson() {
+ let def: ManifestDef = from_str(
+ &std::fs::read_to_string(testdata!("manifest.hjson"))
+ .unwrap(),
+ )
+ .unwrap();
+
+ let _: Manifest = def.unpack("").unwrap();
+ }
+}
diff --git a/sw/host/opentitanlib/src/signer/mod.rs b/sw/host/opentitanlib/src/image/mod.rs
similarity index 81%
rename from sw/host/opentitanlib/src/signer/mod.rs
rename to sw/host/opentitanlib/src/image/mod.rs
index d40c6e5..d6560bb 100644
--- a/sw/host/opentitanlib/src/signer/mod.rs
+++ b/sw/host/opentitanlib/src/image/mod.rs
@@ -2,4 +2,6 @@
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
+pub mod image;
pub mod manifest;
+pub mod manifest_def;
diff --git a/sw/host/opentitanlib/src/util/testdata/hello.txt b/sw/host/opentitanlib/src/image/testdata/hello.txt
similarity index 100%
rename from sw/host/opentitanlib/src/util/testdata/hello.txt
rename to sw/host/opentitanlib/src/image/testdata/hello.txt
diff --git a/sw/host/opentitanlib/src/image/testdata/manifest.hjson b/sw/host/opentitanlib/src/image/testdata/manifest.hjson
new file mode 100644
index 0000000..87e3c7f
--- /dev/null
+++ b/sw/host/opentitanlib/src/image/testdata/manifest.hjson
@@ -0,0 +1,158 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+/**
+ * Manifest for boot stage images stored in flash.
+ *
+ * OpenTitan secure boot, at a minimum, consists of three boot stages: ROM,
+ * ROM_EXT, and the first owner boot stage, e.g. BL0. ROM is stored in the
+ * read-only Mask ROM while remaining stages are stored in flash. This structure
+ * must be placed at the start of ROM_EXT and first owner boot stage images so
+ * that ROM and ROM_EXT can verify the integrity and authenticity of the next
+ * stage and configure peripherals as needed before handing over execution.
+ *
+ * Use of this struct for stages following the first owner boot stage is
+ * optional.
+ *
+ * Note: The definitions in
+ * sw/host/rom_ext_image_tools/signer/image/src/manifest.rs must be updated if
+ * this struct is modified. Please see the instructions in that file.
+ */
+{
+ /**
+ * Image signature.
+ *
+ * RSASSA-PKCS1-v1_5 signature of the image generated using a 3072-bit RSA
+ * private key and the SHA-256 hash function. The signed region of an image
+ * starts immediately after this field and ends at the end of the image.
+ *
+ * On-target verification should also integrate usage constraints comparison
+ * to signature verification to harden it against potential attacks. During
+ * verification, the digest of an image should be computed by first reading
+ * the usage constraints from the hardware and then concatenating the rest of
+ * the image:
+ *
+ * digest = SHA256(usage_constraints_from_hw || rest_of_the_image)
+ *
+ * The start and the length of the region that should be concatenated to the
+ * usage constraints read from the hardware can be obtained using
+ * `manifest_digest_region_get()`.
+ */
+ signature: "0xA54D06061A27A2F6AE70F6A1FA51A840F62B64864E20DD713F633420802359DA7AD61881B2B7A6C4B57D4BBD84966168CA1F1C2FC843940BA18EAA436A12E3E17DEE78F3E1603F2BAB4CC60F4703C6CFBACC2F231E9090AAB18851531FC8AB95845D007D774C66140DB8474ABDA36DA28549DC59713C4750E23317FE46FED91672D2CFB91C37331E1CB4A94BA0C5E87F0A869FE39C55CA686F4943B305AA1890CBED0A33A37A51DECB0627DB353EAAEB48BC2C3FCC710D6D35F8DE11342FBC4CB697687A55999572C7F34B5837749BCA8CA48D073D30D71006B0C7056BF073814CD1A262454FBD003BCD6871999413F0C2E8C321B0534714D8E542B1398D0080B1A11919D66EDA8F467D309624ED7AE596B16EB082BFC17F2E6F33BCEAEEF18133582475406F4F784C3DABD22935D01C84B5C2638A8C287C93173C620D5050BAF8D4CC8C1D2FE2A40407A7D609F2C164CF0BB48508C441C252A930489A50994876CAFACE79D48B6C1234C915320527E335AAED816C39EAAC0123456789012345"
+
+ /**
+ * Usage constraints.
+ */
+ usage_constraints: {
+ /**
+ * Usage constraint selector bits.
+ *
+ * The bits of this field are mapped to the remaining fields as follows:
+ * - Bits 0-7: `device_id[0-7]`
+ * - Bit 8 : `manuf_state_creator`
+ * - Bit 9 : `manuf_state_owner`
+ * - Bit 10 : `life_cycle_state`
+ */
+ selector_bits: "0x00000000"
+ /**
+ * Device identifier value which is compared against the `DEVICE_ID` value
+ * stored in the `HW_CFG` partition in OTP.
+ *
+ * Mapped to bits 0-7 of `selector_bits`.
+ */
+ device_id: [
+ "0x00000000", "0x00000000", "0x00000000", "0x00000000", "0x00000000", "0x00000000",
+ "0x00000000", "0x00000000",
+ ],
+ /**
+ * Device Silicon Creator manufacting status compared against the
+ * `CREATOR_SW_MANUF_STATUS` value stored in the `CREATOR_SW_CFG` partition in
+ * OTP.
+ *
+ * Mapped to bit 8 of `selector_bits`.
+ */
+ manuf_state_creator: "0x00000000"
+ /**
+ * Device Silicon Owner manufacturing status compared against the
+ * `OWNER_SW_MANUF_STATUS` value stored in the `OWNER_SW_CFG` partition in
+ * OTP.
+ *
+ * Mapped to bit 9 of `selector_bits`.
+ */
+ manuf_state_owner: "0x00000000"
+ /**
+ * Device life cycle status compared against the status reported by the life
+ * cycle controller.
+ *
+ * Mapped to bit 10 of `selector_bits`.
+ */
+ life_cycle_state: "0x00000000"
+ }
+ /**
+ * Modulus of the signer's 3072-bit RSA public key.
+ */
+ modulus: "0xA54D06061A27A2F6AE70F6A1FA51A840F62B64864E20DD713F633420802359DA7AD61881B2B7A6C4B57D4BBD84966168CA1F1C2FC843940BA18EAA436A12E3E17DEE78F3E1603F2BAB4CC60F4703C6CFBACC2F231E9090AAB18851531FC8AB95845D007D774C66140DB8474ABDA36DA28549DC59713C4750E23317FE46FED91672D2CFB91C37331E1CB4A94BA0C5E87F0A869FE39C55CA686F4943B305AA1890CBED0A33A37A51DECB0627DB353EAAEB48BC2C3FCC710D6D35F8DE11342FBC4CB697687A55999572C7F34B5837749BCA8CA48D073D30D71006B0C7056BF073814CD1A262454FBD003BCD6871999413F0C2E8C321B0534714D8E542B1398D0080B1A11919D66EDA8F467D309624ED7AE596B16EB082BFC17F2E6F33BCEAEEF18133582475406F4F784C3DABD22935D01C84B5C2638A8C287C93173C620D5050BAF8D4CC8C1D2FE2A40407A7D609F2C164CF0BB48508C441C252A930489A50994876CAFACE79D48B6C1234C915320527E335AAED816C39EAAC0123456789012345"
+ /**
+ * Exponent of the signer's RSA public key.
+ */
+ exponent: 3
+ /**
+ * Manifest identifier.
+ */
+ identifier: "0x00000000"
+ /**
+ * Length of the image including the manifest in bytes.
+ *
+ * Note that the length includes the signature but the signature is excluded
+ * from the signed region.
+ */
+ length: "0x00000000"
+ /**
+ * Image major version.
+ */
+ version_major: "0x00000000"
+ /**
+ * Image minor version.
+ */
+ version_minor: "0x00000000"
+ /**
+ * Security version of the image used for anti-rollback protection.
+ */
+ security_version: "0x00000000"
+ /**
+ * Image timestamp.
+ *
+ * Unix timestamp that gives the creation time of the image, seconds since
+ * 00:00:00 on January 1, 1970 UTC (the Unix Epoch).
+ */
+ timestamp: "0x00000000"
+ /**
+ * Binding value used by key manager to derive secret values.
+ *
+ * A change in this value changes the secret value of key manager, and
+ * consequently, the versioned keys and identity seeds generated at subsequent
+ * boot stages.
+ */
+ binding_value: ["0x00000000", "0x00000000", "0x00000000", "0x00000000", "0x00000000",
+ "0x00000000", "0x00000000", "0x00000000", ]
+ /**
+ * Maximum allowed version for keys generated at the next boot stage.
+ */
+ max_key_version: "0x00000000"
+ /**
+ * Offset of the start of the executable region of the image from the start
+ * of the manifest in bytes.
+ */
+ code_start: "0x00000000"
+ /**
+ * Offset of the end of the executable region (exclusive) of the image from
+ * the start of the manifest in bytes.
+ */
+ code_end: "0x00000000"
+ /**
+ * Offset of the first instruction to execute in the image from the start of
+ * the manifest in bytes.
+ */
+ entry_point: "0x00000000"
+}
diff --git a/sw/host/opentitanlib/src/util/testdata/world.txt b/sw/host/opentitanlib/src/image/testdata/world.txt
similarity index 100%
rename from sw/host/opentitanlib/src/util/testdata/world.txt
rename to sw/host/opentitanlib/src/image/testdata/world.txt
diff --git a/sw/host/opentitanlib/src/lib.rs b/sw/host/opentitanlib/src/lib.rs
index 02e80d0..be5c096 100644
--- a/sw/host/opentitanlib/src/lib.rs
+++ b/sw/host/opentitanlib/src/lib.rs
@@ -8,6 +8,7 @@
pub mod crypto;
pub mod io;
pub mod otp;
+pub mod image;
pub mod spiflash;
pub mod transport;
pub mod util;
diff --git a/sw/host/opentitanlib/src/util/mod.rs b/sw/host/opentitanlib/src/util/mod.rs
index b4ce99a..78519ee 100644
--- a/sw/host/opentitanlib/src/util/mod.rs
+++ b/sw/host/opentitanlib/src/util/mod.rs
@@ -5,7 +5,6 @@
pub mod bigint;
pub mod bitfield;
pub mod file;
-pub mod image;
pub mod num_de;
pub mod parse_int;
pub mod present;
diff --git a/sw/host/opentitanlib/src/util/num_de.rs b/sw/host/opentitanlib/src/util/num_de.rs
index d621e9e..df74a86 100644
--- a/sw/host/opentitanlib/src/util/num_de.rs
+++ b/sw/host/opentitanlib/src/util/num_de.rs
@@ -158,15 +158,15 @@
/// Wrapper type to force deserialization assuming octal encoding.
#[derive(Deserialize, Debug)]
-pub struct OctEncoded<T: ParseInt>(#[serde(with = "self")] T);
+pub struct OctEncoded<T: ParseInt>(#[serde(with = "self")] pub T);
/// Wrapper type to force deserialization assuming decimal encoding.
#[derive(Deserialize, Debug)]
-pub struct DecEncoded<T: ParseInt>(#[serde(with = "self")] T);
+pub struct DecEncoded<T: ParseInt>(#[serde(with = "self")] pub T);
/// Wrapper type to force deserialization assuming hexadecimal encoding.
#[derive(Deserialize, Debug)]
-pub struct HexEncoded<T: ParseInt>(#[serde(with = "self")] T);
+pub struct HexEncoded<T: ParseInt>(#[serde(with = "self")] pub T);
macro_rules! impl_parse_int_enc {
($ty:ident, $radix:expr) => {
diff --git a/sw/host/opentitantool/src/command/bootstrap.rs b/sw/host/opentitantool/src/command/bootstrap.rs
index 93872e6..2e76ecf 100644
--- a/sw/host/opentitantool/src/command/bootstrap.rs
+++ b/sw/host/opentitantool/src/command/bootstrap.rs
@@ -12,7 +12,7 @@
use opentitanlib::app::TransportWrapper;
use opentitanlib::bootstrap::{Bootstrap, BootstrapOptions, BootstrapProtocol};
use opentitanlib::transport;
-use opentitanlib::util::image::ImageAssembler;
+use opentitanlib::image::image::ImageAssembler;
use opentitanlib::util::parse_int::ParseInt;
/// Bootstrap the target device.
diff --git a/sw/host/opentitantool/src/command/image.rs b/sw/host/opentitantool/src/command/image.rs
index 82374c0..e28919d 100644
--- a/sw/host/opentitantool/src/command/image.rs
+++ b/sw/host/opentitantool/src/command/image.rs
@@ -11,7 +11,7 @@
use opentitanlib::app::command::CommandDispatch;
use opentitanlib::app::TransportWrapper;
-use opentitanlib::util::image::ImageAssembler;
+use opentitanlib::image::image::ImageAssembler;
use opentitanlib::util::parse_int::ParseInt;
/// Bootstrap the target device.