Repository: chipsalliance/caliptra-sw
Pull request #125 (2023-03-30 23:02:35)

Implement caliptra_builder::build_and_sign_image.
This is used for building fmc/app-fw bundles for use in integration
tests.


Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 257c1b1f..61c3fdac 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -110,7 +110,14 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
 name = "caliptra-builder"
 version = "0.1.0"
 dependencies = [
+ "anyhow",
+ "caliptra-image-elf",
+ "caliptra-image-fake-keys",
+ "caliptra-image-gen",
+ "caliptra-image-openssl",
+ "caliptra-image-types",
  "elf",
+ "hex",
 ]
 
 [[package]]
@@ -274,6 +281,15 @@ dependencies = [
  "elf",
 ]
 
+[[package]]
+name = "caliptra-image-fake-keys"
+version = "0.1.0"
+dependencies = [
+ "caliptra-image-gen",
+ "caliptra-image-openssl",
+ "caliptra-image-types",
+]
+
 [[package]]
 name = "caliptra-image-gen"
 version = "0.1.0"
diff --git a/Cargo.toml b/Cargo.toml
index a8ff1288..4873e320 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -45,6 +45,7 @@ members = [
   "image/app",
   "image/openssl",
   "image/elf",
+  "image/fake-keys",
   "rom/dev",
   "rom/dev/tools/test-fmc",
   "rom/dev/tools/test-rt",
diff --git a/builder/Cargo.toml b/builder/Cargo.toml
index f7ba29e7..4a7ee713 100644
--- a/builder/Cargo.toml
+++ b/builder/Cargo.toml
@@ -8,4 +8,11 @@ edition = "2021"
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
-elf = "0.7.2"
\ No newline at end of file
+elf = "0.7.2"
+caliptra-image-elf = { path = "../image/elf" }
+caliptra-image-fake-keys = { path = "../image/fake-keys" }
+caliptra-image-gen = { path = "../image/gen" }
+caliptra-image-openssl = { path = "../image/openssl" }
+caliptra-image-types = { path = "../image/types" }
+hex = "0.4.3"
+anyhow = "1.0.70"
\ No newline at end of file
diff --git a/builder/src/elf_symbols.rs b/builder/src/elf_symbols.rs
index b69ef1cd..0628f473 100644
--- a/builder/src/elf_symbols.rs
+++ b/builder/src/elf_symbols.rs
@@ -178,6 +178,5 @@ mod test {
                 bind: SymbolBind::Global,
             })
         );
-        println!("{:?}", symbols);
     }
 }
diff --git a/builder/src/lib.rs b/builder/src/lib.rs
index 05da2dd8..f8bde5fe 100644
--- a/builder/src/lib.rs
+++ b/builder/src/lib.rs
@@ -3,14 +3,44 @@
 use std::fs;
 use std::io::{self, ErrorKind};
 use std::path::Path;
-use std::process::Command;
+use std::process::{Command, Stdio};
 
+use caliptra_image_elf::ElfExecutable;
+use caliptra_image_gen::{
+    ImageGenerator, ImageGeneratorConfig, ImageGeneratorOwnerConfig, ImageGeneratorVendorConfig,
+};
+use caliptra_image_openssl::OsslCrypto;
+use caliptra_image_types::ImageRevision;
 use elf::endian::LittleEndian;
 
 mod elf_symbols;
 
 pub use elf_symbols::{elf_symbols, Symbol, SymbolBind, SymbolType, SymbolVisibility};
 
+pub const ROM: FwId = FwId {
+    crate_name: "caliptra-rom",
+    bin_name: "caliptra-rom",
+    features: &[],
+};
+
+pub const ROM_WITH_UART: FwId = FwId {
+    crate_name: "caliptra-rom",
+    bin_name: "caliptra-rom",
+    features: &["emu"],
+};
+
+pub const FMC_WITH_UART: FwId = FwId {
+    crate_name: "caliptra-fmc",
+    bin_name: "caliptra-fmc",
+    features: &["emu"],
+};
+
+pub const APP_WITH_UART: FwId = FwId {
+    crate_name: "caliptra-runtime",
+    bin_name: "caliptra-runtime",
+    features: &["emu"],
+};
+
 fn other_err(e: impl Into<Box<dyn std::error::Error + Send + Sync>>) -> io::Error {
     io::Error::new(ErrorKind::Other, e)
 }
@@ -32,11 +62,55 @@ fn run_cmd(cmd: &mut Command) -> io::Result<()> {
     }
 }
 
-pub fn build_firmware_elf(fw_crate_name: &str, bin_name: &str) -> io::Result<Vec<u8>> {
+fn run_cmd_stdout(cmd: &mut Command, input: Option<&[u8]>) -> io::Result<String> {
+    cmd.stdin(Stdio::piped());
+    cmd.stdout(Stdio::piped());
+
+    let mut child = cmd.spawn()?;
+    if let (Some(mut stdin), Some(input)) = (child.stdin.take(), input) {
+        std::io::Write::write_all(&mut stdin, input)?;
+    }
+    let out = child.wait_with_output()?;
+    if out.status.success() {
+        Ok(String::from_utf8_lossy(&out.stdout).into())
+    } else {
+        Err(other_err(format!(
+            "Process {:?} {:?} exited with status code {:?} stderr {}",
+            cmd.get_program(),
+            cmd.get_args(),
+            out.status.code(),
+            String::from_utf8_lossy(&out.stderr)
+        )))
+    }
+}
+
+// Represent the Cargo identity of a firmware binary.
+#[derive(Default)]
+pub struct FwId<'a> {
+    // The crate name (For example, "caliptra-rom")
+    pub crate_name: &'a str,
+
+    // If the crate contains multiple binaries, the name of the binary. Leave
+    // empty to build the crate's default binary.
+    pub bin_name: &'a str,
+
+    // The features to use the build the binary
+    pub features: &'a [&'a str],
+}
+
+pub fn build_firmware_elf(id: &FwId) -> io::Result<Vec<u8>> {
     const WORKSPACE_DIR: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/..");
     const TARGET: &str = "riscv32imc-unknown-none-elf";
     const PROFILE: &str = "firmware";
 
+    let mut features_csv = id.features.join(",");
+    if !id.features.contains(&"riscv") {
+        if !features_csv.is_empty() {
+            features_csv.push(',');
+        }
+        features_csv.push_str("riscv");
+    }
+
     run_cmd(
         Command::new(env!("CARGO"))
             .current_dir(WORKSPACE_DIR)
@@ -45,26 +119,27 @@ pub fn build_firmware_elf(fw_crate_name: &str, bin_name: &str) -> io::Result<Vec
             .arg("--locked")
             .arg("--target")
             .arg(TARGET)
-            .arg("--features=emu,riscv")
+            .arg("--features")
+            .arg(features_csv)
             .arg("--no-default-features")
             .arg("--profile")
             .arg(PROFILE)
             .arg("-p")
-            .arg(fw_crate_name)
+            .arg(id.crate_name)
             .arg("--bin")
-            .arg(bin_name),
+            .arg(id.bin_name),
     )?;
     fs::read(
         Path::new(WORKSPACE_DIR)
             .join("target")
             .join(TARGET)
             .join(PROFILE)
-            .join(bin_name),
+            .join(id.bin_name),
     )
 }
 
-pub fn build_firmware_rom(fw_crate_name: &str, bin_name: &str) -> io::Result<Vec<u8>> {
-    let elf_bytes = build_firmware_elf(fw_crate_name, bin_name)?;
+pub fn build_firmware_rom(id: &FwId) -> io::Result<Vec<u8>> {
+    let elf_bytes = build_firmware_elf(id)?;
     elf2rom(&elf_bytes)
 }
 
@@ -93,14 +168,89 @@ pub fn elf2rom(elf_bytes: &[u8]) -> io::Result<Vec<u8>> {
     Ok(result)
 }
 
+pub struct ImageOptions {
+    fmc_min_svn: u32,
+    fmc_svn: u32,
+    app_min_svn: u32,
+    app_svn: u32,
+    vendor_config: ImageGeneratorVendorConfig,
+    owner_config: Option<ImageGeneratorOwnerConfig>,
+}
+impl Default for ImageOptions {
+    fn default() -> Self {
+        Self {
+            fmc_min_svn: Default::default(),
+            fmc_svn: Default::default(),
+            app_min_svn: Default::default(),
+            app_svn: Default::default(),
+            vendor_config: caliptra_image_fake_keys::VENDOR_CONFIG_KEY_0,
+            owner_config: Some(caliptra_image_fake_keys::OWNER_CONFIG),
+        }
+    }
+}
+
+pub fn build_and_sign_image(fmc: &FwId, app: &FwId, opts: ImageOptions) -> anyhow::Result<Vec<u8>> {
+    let fmc_elf = build_firmware_elf(fmc)?;
+    let app_elf = build_firmware_elf(app)?;
+    let gen = ImageGenerator::new(OsslCrypto::default());
+    let image = gen.generate(&ImageGeneratorConfig {
+        fmc: ElfExecutable::new(
+            &fmc_elf,
+            opts.fmc_min_svn,
+            opts.fmc_svn,
+            image_revision_from_git_repo()?,
+        )?,
+        runtime: ElfExecutable::new(
+            &app_elf,
+            opts.app_min_svn,
+            opts.app_svn,
+            image_revision_from_git_repo()?,
+        )?,
+        vendor_config: opts.vendor_config,
+        owner_config: opts.owner_config,
+    })?;
+    Ok(image.to_bytes()?)
+}
+
+fn image_revision_from_git_repo() -> io::Result<ImageRevision> {
+    let commit_id = run_cmd_stdout(Command::new("git").arg("rev-parse").arg("HEAD"), None)?;
+    let rtl_git_status =
+        run_cmd_stdout(Command::new("git").arg("status").arg("--porcelain"), None)?;
+    image_revision_from_str(&commit_id, rtl_git_status.is_empty())
+}
+
+fn image_revision_from_str(commit_id_str: &str, is_clean: bool) -> io::Result<ImageRevision> {
+    // (dirtdirtdirtdirtdirt)
+    const DIRTY_SUFFIX: [u8; 10] = [0xd1, 0x47, 0xd1, 0x47, 0xd1, 0x47, 0xd1, 0x47, 0xd1, 0x47];
+
+    let mut commit_id = ImageRevision::default();
+    hex::decode_to_slice(commit_id_str.trim(), &mut commit_id).map_err(|e| {
+        other_err(format!(
+            "Unable to decode git commit {commit_id_str:?}: {e}"
+        ))
+    })?;
+
+    if !is_clean {
+        // spoil the revision because the git client is dirty
+        commit_id[10..].copy_from_slice(&DIRTY_SUFFIX);
+    }
+    Ok(commit_id)
+}
+
 #[cfg(test)]
 mod test {
+
     use super::*;
 
     #[test]
     fn test_build_firmware() {
         // Ensure that we can build the ELF and elf2rom can parse it
-        build_firmware_rom("caliptra-drivers-test-bin", "test_success").unwrap();
+        build_firmware_rom(&FwId {
+            crate_name: "caliptra-drivers-test-bin",
+            bin_name: "test_success",
+            ..Default::default()
+        })
+        .unwrap();
     }
 
     #[test]
@@ -108,4 +258,33 @@ mod test {
         let rom_bytes = elf2rom(include_bytes!("testdata/example.elf")).unwrap();
         assert_eq!(&rom_bytes, include_bytes!("testdata/example.rom.golden"));
     }
+
+    #[test]
+    fn test_image_revision_from_str() {
+        assert_eq!(
+            image_revision_from_str("d6a462a63a9cf2dafa5bbc6cf78b1fccc308009a", true).unwrap(),
+            [
+                0xd6, 0xa4, 0x62, 0xa6, 0x3a, 0x9c, 0xf2, 0xda, 0xfa, 0x5b, 0xbc, 0x6c, 0xf7, 0x8b,
+                0x1f, 0xcc, 0xc3, 0x08, 0x00, 0x9a
+            ]
+        );
+        assert_eq!(
+            image_revision_from_str("d6a462a63a9cf2dafa5bbc6cf78b1fccc308009a\n", true).unwrap(),
+            [
+                0xd6, 0xa4, 0x62, 0xa6, 0x3a, 0x9c, 0xf2, 0xda, 0xfa, 0x5b, 0xbc, 0x6c, 0xf7, 0x8b,
+                0x1f, 0xcc, 0xc3, 0x08, 0x00, 0x9a
+            ]
+        );
+        assert_eq!(
+            image_revision_from_str("d6a462a63a9cf2dafa5bbc6cf78b1fccc308009a", false).unwrap(),
+            [
+                0xd6, 0xa4, 0x62, 0xa6, 0x3a, 0x9c, 0xf2, 0xda, 0xfa, 0x5b, 0xd1, 0x47, 0xd1, 0x47,
+                0xd1, 0x47, 0xd1, 0x47, 0xd1, 0x47
+            ]
+        );
+        assert_eq!(
+            image_revision_from_str("d6a462a63a9cf2dafa5bbc6cf78b1fccc30800", false).unwrap_err().to_string(),
+            "Unable to decode git commit \"d6a462a63a9cf2dafa5bbc6cf78b1fccc30800\": Invalid string length");
+        assert!(image_revision_from_str("d6a462a63a9cf2dafa5bbc6cf78b1fccc308009g", true).is_err());
+    }
 }
diff --git a/image/fake-keys/Cargo.toml b/image/fake-keys/Cargo.toml
index 1ec7f522..00219516 100644
--- a/image/fake-keys/Cargo.toml
+++ b/image/fake-keys/Cargo.toml
@@ -8,6 +8,7 @@ edition = "2021"
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
+caliptra-image-gen = { path = "../gen" }
 caliptra-image-types = { path = "../types" }
 
 [dev-dependencies]
diff --git a/image/fake-keys/src/lib.rs b/image/fake-keys/src/lib.rs
index 5cec5587..30fd69b4 100644
--- a/image/fake-keys/src/lib.rs
+++ b/image/fake-keys/src/lib.rs
@@ -1,6 +1,10 @@
 // Licensed under the Apache-2.0 license
 
-use caliptra_image_types::{ImageEccPrivKey, ImageEccPubKey};
+use caliptra_image_gen::{ImageGeneratorOwnerConfig, ImageGeneratorVendorConfig};
+use caliptra_image_types::{
+    ImageEccPrivKey, ImageEccPubKey, ImageOwnerPrivKeys, ImageOwnerPubKeys, ImageVendorPrivKeys,
+    ImageVendorPubKeys,
+};
 
 /// Generated with
 ///
@@ -98,3 +102,51 @@ pub const OWNER_KEY_PRIVATE: ImageEccPrivKey = [
     0x59fdf849, 0xe39f4256, 0x19342ed2, 0x81d28d3d, 0x45ab3219, 0x5174582c, 0xecb4e9df, 0x9cc2e991,
     0xb75f88fd, 0xfa4bc6a4, 0x6b88340f, 0x05dd8890,
 ];
+
+pub const VENDOR_PUBLIC_KEYS: ImageVendorPubKeys = ImageVendorPubKeys {
+    ecc_pub_keys: [
+        VENDOR_KEY_0_PUBLIC,
+        VENDOR_KEY_1_PUBLIC,
+        VENDOR_KEY_2_PUBLIC,
+        VENDOR_KEY_3_PUBLIC,
+    ],
+};
+
+pub const VENDOR_PRIVATE_KEYS: ImageVendorPrivKeys = ImageVendorPrivKeys {
+    ecc_priv_keys: [
+        VENDOR_KEY_0_PRIVATE,
+        VENDOR_KEY_1_PRIVATE,
+        VENDOR_KEY_2_PRIVATE,
+        VENDOR_KEY_3_PRIVATE,
+    ],
+};
+
+pub const VENDOR_CONFIG_KEY_0: ImageGeneratorVendorConfig = ImageGeneratorVendorConfig {
+    pub_keys: VENDOR_PUBLIC_KEYS,
+    ecc_key_idx: 0,
+    priv_keys: Some(VENDOR_PRIVATE_KEYS),
+};
+
+pub const VENDOR_CONFIG_KEY_1: ImageGeneratorVendorConfig = ImageGeneratorVendorConfig {
+    ecc_key_idx: 1,
+    ..VENDOR_CONFIG_KEY_0
+};
+
+pub const VENDOR_CONFIG_KEY_2: ImageGeneratorVendorConfig = ImageGeneratorVendorConfig {
+    ecc_key_idx: 2,
+    ..VENDOR_CONFIG_KEY_0
+};
+
+pub const VENDOR_CONFIG_KEY_3: ImageGeneratorVendorConfig = ImageGeneratorVendorConfig {
+    ecc_key_idx: 3,
+    ..VENDOR_CONFIG_KEY_0
+};
+
+pub const OWNER_CONFIG: ImageGeneratorOwnerConfig = ImageGeneratorOwnerConfig {
+    pub_keys: ImageOwnerPubKeys {
+        ecc_pub_key: OWNER_KEY_PUBLIC,
+    },
+    priv_keys: Some(ImageOwnerPrivKeys {
+        ecc_priv_key: OWNER_KEY_PRIVATE,
+    }),
+};
diff --git a/image/types/src/lib.rs b/image/types/src/lib.rs
index 1843a7e1..1fc42bde 100644
--- a/image/types/src/lib.rs
+++ b/image/types/src/lib.rs
@@ -69,6 +69,42 @@ pub struct ImageBundle {
     pub runtime: Vec<u8>,
 }
 
+#[cfg(feature = "std")]
+impl ImageBundle {
+    pub fn to_bytes(&self) -> std::io::Result<Vec<u8>> {
+        use std::io::ErrorKind;
+        let mut result = vec![];
+        result.extend_from_slice(self.manifest.as_bytes());
+        if self.manifest.fmc.offset as usize != result.len() {
+            return Err(std::io::Error::new(
+                ErrorKind::Other,
+                "actual fmc offset does not match manifest",
+            ));
+        }
+        if self.manifest.fmc.size as usize != self.fmc.len() {
+            return Err(std::io::Error::new(
+                ErrorKind::Other,
+                "actual fmc size does not match manifest",
+            ));
+        }
+        result.extend_from_slice(&self.fmc);
+        if self.manifest.runtime.offset as usize != result.len() {
+            return Err(std::io::Error::new(
+                ErrorKind::Other,
+                "actual runtime offset does not match manifest",
+            ));
+        }
+        if self.manifest.runtime.size as usize != self.runtime.len() {
+            return Err(std::io::Error::new(
+                ErrorKind::Other,
+                "actual runtime size does not match manifest",
+            ));
+        }
+        result.extend_from_slice(&self.runtime);
+        Ok(result)
+    }
+}
+
 /// Calipatra Image Manifest
 #[repr(C)]
 #[derive(AsBytes, FromBytes, Default, Debug)]


Test patch:
diff --git a/drivers/tests/integration_tests.rs b/drivers/tests/integration_tests.rs
index 595d6c99..75796c1a 100644
--- a/drivers/tests/integration_tests.rs
+++ b/drivers/tests/integration_tests.rs
@@ -1,10 +1,15 @@
 // Licensed under the Apache-2.0 license
 
+use caliptra_builder::FwId;
 use caliptra_hw_model::{HwModel, InitParams};
 
 fn run_driver_test(test_bin_name: &str) {
-    let rom =
-        caliptra_builder::build_firmware_rom("caliptra-drivers-test-bin", test_bin_name).unwrap();
+    let rom = caliptra_builder::build_firmware_rom(&FwId {
+        crate_name: "caliptra-drivers-test-bin",
+        bin_name: test_bin_name,
+        features: &["emu"],
+    })
+    .unwrap();
     let mut model = caliptra_hw_model::create(InitParams {
         rom: &rom,
         ..Default::default()
diff --git a/rom/dev/tests/test_panic_missing.rs b/rom/dev/tests/test_panic_missing.rs
index 7eb6fa47..39452a1d 100644
--- a/rom/dev/tests/test_panic_missing.rs
+++ b/rom/dev/tests/test_panic_missing.rs
@@ -1,8 +1,10 @@
 // Licensed under the Apache-2.0 license
 
+use caliptra_builder::ROM_WITH_UART;
+
 #[test]
 fn test_panic_missing() {
-    let rom_elf = caliptra_builder::build_firmware_elf("caliptra-rom", "caliptra-rom").unwrap();
+    let rom_elf = caliptra_builder::build_firmware_elf(&ROM_WITH_UART).unwrap();
     let symbols = caliptra_builder::elf_symbols(&rom_elf).unwrap();
     if symbols.iter().any(|s| s.name.contains("panic_is_possible")) {
         panic!(