[sw, rom_ext_signer] Add signing and remaining image update API
- Retrieving exponent is work-in-progress, and at the moment
dummy hardcoded vector of `0xA5` is used.
- Retrieving modulus is work-in-progress, and at the moment
dummy hardcoded vector of `0xA5` is used.
- Peripheral Lockdown Info encodingis work in progress, and at
the moment dummy hardcoded vector of `0xA5` is used.
- It is not clear yet how the device_usage_value is encoded in
the 256-bit usage_constraints blob, and at the moment dummy
hardcoded vector of `0xA5` is used.
This change adds image signing functionality through Mundane.
* There was some refactoring done, missing fields
`extension{0,1,2,3}_offset` have been added.
* `image_length` is no longer passed in the config, as it is
set automatically in the manifest assembly file.
* `image_timestamp` is no longer passed in the config, and is
calculated at runtime.
Signed-off-by: Silvestrs Timofejevs <silvestrst@lowrisc.org>
diff --git a/sw/host/rom_ext_image_tools/signer/Cargo.toml b/sw/host/rom_ext_image_tools/signer/Cargo.toml
index e4fcfc0..f21464a 100644
--- a/sw/host/rom_ext_image_tools/signer/Cargo.toml
+++ b/sw/host/rom_ext_image_tools/signer/Cargo.toml
@@ -25,6 +25,9 @@
debug = true
[dependencies]
-mundane = "0.4.3"
rom_ext_config = { path = "config" }
rom_ext_image = { path = "image" }
+
+[dependencies.mundane]
+version = "0.4.3"
+features = ["rsa-pkcs1v15"]
diff --git a/sw/host/rom_ext_image_tools/signer/config/src/parser.rs b/sw/host/rom_ext_image_tools/signer/config/src/parser.rs
index 634db71..af22d67 100644
--- a/sw/host/rom_ext_image_tools/signer/config/src/parser.rs
+++ b/sw/host/rom_ext_image_tools/signer/config/src/parser.rs
@@ -4,6 +4,7 @@
use std::fs;
use std::path::Path;
+use std::path::PathBuf;
use serde_derive::Deserialize;
use serde_hjson::Value;
@@ -12,37 +13,36 @@
#[derive(Deserialize, Debug)]
pub struct ParsedConfig {
pub input_files: InputFiles,
- pub usage_constraints: UsageConstraints,
pub peripheral_lockdown_info: PeripheralLockdownInfo,
pub manifest_identifier: String,
- pub image_length: String,
pub image_version: String,
- pub image_timestamp: String,
- pub extension0_checksum: String,
- pub extension1_checksum: String,
- pub extension2_checksum: String,
- pub extension3_checksum: String,
+ pub extensions: [Extension; 4],
}
/// Input files that are required for signing.
#[derive(Deserialize, Debug)]
pub struct InputFiles {
- pub image_path: String,
- pub private_key_der_path: String,
+ pub image_path: PathBuf,
+ pub private_key_der_path: PathBuf,
+ pub usage_constraints_path: PathBuf,
+ pub system_state_value_path: PathBuf,
}
-/// TODO - possibly should be a binary file.
-#[derive(Deserialize, Debug)]
-pub struct UsageConstraints {
- pub value: u32,
-}
-
-/// TODO
+/// Peripheral Lockdown Information configuration data.
+///
+/// This data is used to produce 128-bit encoded manifest field.
#[derive(Deserialize, Debug)]
pub struct PeripheralLockdownInfo {
pub value: u32,
}
+/// ROM_EXT.
+#[derive(Deserialize, Debug)]
+pub struct Extension {
+ pub offset: String,
+ pub checksum: String,
+}
+
impl ParsedConfig {
pub fn new(config: &Path) -> Self {
// Read the entire configuration file.
diff --git a/sw/host/rom_ext_image_tools/signer/dev/config.hjson b/sw/host/rom_ext_image_tools/signer/dev/config.hjson
index 2e2c98e..17d25f7 100644
--- a/sw/host/rom_ext_image_tools/signer/dev/config.hjson
+++ b/sw/host/rom_ext_image_tools/signer/dev/config.hjson
@@ -5,21 +5,31 @@
input_files: {
image_path: "sw/host/rom_ext_image_tools/signer/dev/rom_ext_blank_image.bin",
private_key_der_path: "sw/host/rom_ext_image_tools/signer/dev/test_key_private.der",
- },
- // TBD
- usage_constraints: {
- value: 0,
+ usage_constraints_path: "sw/host/rom_ext_image_tools/signer/dev/usage_constraints.bin",
+ system_state_value_path: "sw/host/rom_ext_image_tools/signer/dev/system_state_value.bin",
},
// TBD
peripheral_lockdown_info: {
value: 0,
},
manifest_identifier: "43981",
- image_length: "0xdeadbeef",
image_version: "0xdeadbeef",
- image_timestamp: "0xaabbccddeeff1122",
- extension0_checksum: "0xdeadbeef",
- extension1_checksum: "0xdeadbeef",
- extension2_checksum: "0xdeadbeef",
- extension3_checksum: "0xdeadbeef",
+ extensions: [
+ {
+ offset: "0xdeadbeef",
+ checksum: "0xdeadbeef",
+ },
+ {
+ offset: "0xdeadbeef",
+ checksum: "0xdeadbeef",
+ },
+ {
+ offset: "0xdeadbeef",
+ checksum: "0xdeadbeef",
+ },
+ {
+ offset: "0xdeadbeef",
+ checksum: "0xdeadbeef",
+ },
+ ],
}
diff --git a/sw/host/rom_ext_image_tools/signer/image/src/image.rs b/sw/host/rom_ext_image_tools/signer/image/src/image.rs
index ef261c4..cb43aa5 100644
--- a/sw/host/rom_ext_image_tools/signer/image/src/image.rs
+++ b/sw/host/rom_ext_image_tools/signer/image/src/image.rs
@@ -2,14 +2,21 @@
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
+#![deny(warnings)]
+#![deny(unused)]
+#![deny(unsafe_code)]
+
use std;
use std::ffi::OsString;
use std::fs;
use std::path::Path;
use std::path::PathBuf;
+use std::time::SystemTime;
+use std::time::UNIX_EPOCH;
use crate::manifest;
use rom_ext_config::parser::ParsedConfig;
+use rom_ext_config::parser::PeripheralLockdownInfo;
/// Stripped binary image buffer.
pub struct RawImage {
@@ -31,51 +38,90 @@
}
}
- /// Updates the manifest portion of the image buffer.
+ /// Updates the fields from the configuration file.
///
- /// This function updates the image manifest data with values parsed
- /// from configuration file.
- pub fn update_generic_fields(&mut self, config: &ParsedConfig) {
- // TODO checks to make sure that the config values (strings) are not
- // bigger than the actual field size.
+ /// This function updates the image manifest data with values parsed from
+ /// the configuration file (known ahead of time). Some of the other fields
+ /// like signature key public exponent and modulus, are obtained at
+ /// run-time.
+ pub fn update_static_fields(&mut self, config: &ParsedConfig) {
+ // TODO: checks to make sure that the config values (strings) are not
+ // bigger than the actual field size.
- let mut myclosure = |value, offset| {
+ let mut update = |value, offset| {
let bytes = str_to_vec_u8(value);
- let data = &mut self.data;
- let begin = offset as usize;
- let end = begin + bytes.len();
- data.splice(begin..end, bytes.iter().cloned());
+ self.update_field(&bytes, offset);
};
- myclosure(
+ update(
&config.manifest_identifier,
manifest::ROM_EXT_MANIFEST_IDENTIFIER_OFFSET,
);
- myclosure(&config.image_length, manifest::ROM_EXT_IMAGE_LENGTH_OFFSET);
- myclosure(
+ update(
&config.image_version,
manifest::ROM_EXT_IMAGE_VERSION_OFFSET,
);
- myclosure(
- &config.image_timestamp,
- manifest::ROM_EXT_IMAGE_TIMESTAMP_OFFSET,
+
+ let offsets = [
+ (
+ manifest::ROM_EXT_EXTENSION0_OFFSET_OFFSET,
+ manifest::ROM_EXT_EXTENSION0_CHECKSUM_OFFSET,
+ ),
+ (
+ manifest::ROM_EXT_EXTENSION1_OFFSET_OFFSET,
+ manifest::ROM_EXT_EXTENSION1_CHECKSUM_OFFSET,
+ ),
+ (
+ manifest::ROM_EXT_EXTENSION2_OFFSET_OFFSET,
+ manifest::ROM_EXT_EXTENSION2_CHECKSUM_OFFSET,
+ ),
+ (
+ manifest::ROM_EXT_EXTENSION3_OFFSET_OFFSET,
+ manifest::ROM_EXT_EXTENSION3_CHECKSUM_OFFSET,
+ ),
+ ];
+ for (i, offset) in offsets.iter().enumerate() {
+ update(&config.extensions[i].offset, offset.0);
+ update(&config.extensions[i].checksum, offset.1);
+ }
+
+ let usage_constraints_path = &config.input_files.usage_constraints_path;
+ self.update_usage_constraints_field(usage_constraints_path);
+
+ let lockdown_info = &config.peripheral_lockdown_info;
+ self.update_peripheral_lockdown_info_field(lockdown_info);
+
+ // TODO: calculated at runtime, so we should probably rename this
+ // function.
+ self.update_timestamp_field();
+ }
+
+ /// Updates ROM_EXT manifest signature key public exponent field.
+ pub fn update_exponent_field(&mut self, exponent: &[u8]) {
+ self.update_field(
+ exponent,
+ manifest::ROM_EXT_SIGNATURE_KEY_PUBLIC_EXPONENT_OFFSET,
);
- myclosure(
- &config.extension0_checksum,
- manifest::ROM_EXT_EXTENSION0_CHECKSUM_OFFSET,
- );
- myclosure(
- &config.extension1_checksum,
- manifest::ROM_EXT_EXTENSION1_CHECKSUM_OFFSET,
- );
- myclosure(
- &config.extension2_checksum,
- manifest::ROM_EXT_EXTENSION2_CHECKSUM_OFFSET,
- );
- myclosure(
- &config.extension3_checksum,
- manifest::ROM_EXT_EXTENSION3_CHECKSUM_OFFSET,
- );
+ }
+
+ /// Updates ROM_EXT manifest signature key modulus field.
+ pub fn update_modulus_field(&mut self, modulus: &[u8]) {
+ self.update_field(modulus, manifest::ROM_EXT_SIGNATURE_KEY_MODULUS_OFFSET);
+ }
+
+ /// Updates ROM_EXT manifest signature field.
+ pub fn update_signature_field(&mut self, signature: &[u8]) {
+ self.update_field(signature, manifest::ROM_EXT_IMAGE_SIGNATURE_OFFSET);
+ }
+
+ /// Returns the portion of the image used for signing.
+ ///
+ /// Manifest identifier and the signature itself are not signed. The rest
+ /// of the image, including all the manifest fields that follow the
+ /// signature field.
+ pub fn data_to_sign(&self) -> &[u8] {
+ let offset = manifest::ROM_EXT_SIGNED_AREA_START_OFFSET as usize;
+ &self.data[offset..]
}
/// Writes the image buffer contents into a file.
@@ -91,6 +137,51 @@
fs::write(output_file, &self.data).expect("Failed to write the new binary file!");
}
+
+ /// Updates ROM_EXT manifest usage constraints field.
+ fn update_usage_constraints_field(&mut self, path: &Path) {
+ // Update fields from config.
+ let usage_constraints = fs::read(path).expect("Failed to read usage constraints!");
+ self.update_field(
+ &usage_constraints,
+ manifest::ROM_EXT_USAGE_CONSTRAINTS_OFFSET,
+ );
+ }
+
+ /// Updates ROM_EXT manifest peripheral lockdown info field.
+ ///
+ /// The information is encoded into the 128-bit binary blob.
+ fn update_peripheral_lockdown_info_field(&mut self, _info: &PeripheralLockdownInfo) {
+ // TODO: generate the peripheral_lockdown_blob from
+ // PeripheralLockdownInfo, meanwhile use a hard-coded vector.
+
+ self.update_field(
+ &[0xA5; 16],
+ manifest::ROM_EXT_PERIPHERAL_LOCKDOWN_INFO_OFFSET,
+ );
+ }
+
+ /// Updates ROM_EXT manifest timestamp field.
+ ///
+ /// The generated time stamp is u64. Normally time stamp is a signed
+ /// integer, however there is no risk of overflow into a "negative".
+ fn update_timestamp_field(&mut self) {
+ let duration = SystemTime::now()
+ .duration_since(UNIX_EPOCH)
+ .expect("Failed to obtain the current time!");
+
+ let bytes = &duration.as_secs().to_le_bytes();
+ self.update_field(bytes, manifest::ROM_EXT_IMAGE_TIMESTAMP_OFFSET);
+ }
+
+ /// Updates a ROM_EXT manifest field.
+ ///
+ /// Generic function, is used by the specific field update counterparts.
+ fn update_field(&mut self, field_data: &[u8], field_offset: u32) {
+ let begin = field_offset as usize;
+ let end = begin + field_data.len();
+ self.data.splice(begin..end, field_data.iter().cloned());
+ }
}
/// Converts hex/decimal uint string into a little endian byte vector.
diff --git a/sw/host/rom_ext_image_tools/signer/src/main.rs b/sw/host/rom_ext_image_tools/signer/src/main.rs
index 6c8de02..32a83bf 100644
--- a/sw/host/rom_ext_image_tools/signer/src/main.rs
+++ b/sw/host/rom_ext_image_tools/signer/src/main.rs
@@ -7,11 +7,20 @@
#![deny(unsafe_code)]
use std::env;
+use std::fs;
use std::path::Path;
use rom_ext_config::parser::ParsedConfig;
use rom_ext_image::image::RawImage;
+use mundane::hash::Sha256;
+use mundane::public::rsa::RsaPkcs1v15;
+use mundane::public::rsa::RsaPrivKey;
+use mundane::public::rsa::RsaSignature;
+use mundane::public::rsa::B3072;
+use mundane::public::DerPrivateKey;
+use mundane::public::Signature;
+
fn main() {
let arg: String = env::args().nth(1).expect("Config path is missing");
@@ -21,10 +30,74 @@
let config = ParsedConfig::new(&config_path);
// Read raw binary.
- let image_path = Path::new(&config.input_files.image_path);
- let mut raw_image = RawImage::new(&image_path);
+ let mut image = RawImage::new(&config.input_files.image_path);
- // Modify raw binary.
- raw_image.update_generic_fields(&config);
- raw_image.write_file();
+ // Get the private key used for signature generation. It is also used to
+ // extract key public exponent and modulus.
+ let private_key_der =
+ fs::read(&config.input_files.private_key_der_path).expect("Failed to read the image!");
+
+ // Update "signed" manifest fields.
+ image.update_static_fields(&config);
+
+ let exponent = &signature_key_public_exponent_le();
+ image.update_exponent_field(exponent);
+
+ let modulus = &signature_key_modulus_le();
+ image.update_modulus_field(modulus);
+
+ // Convert ASN.1 DER private key into Mundane RsaPrivKey.
+ let private_key =
+ RsaPrivKey::parse_from_der(&private_key_der).expect("Failed to parse private key!");
+
+ // Produce the signature from concatenated system_state_value,
+ // device_usage_value and the portion of the "signed" portion of the image.
+ let image_sign_data = image.data_to_sign();
+ let device_usage_value = &device_usage_value(&config.input_files.usage_constraints_path);
+ let system_state_value = &system_state_value(&config.input_files.system_state_value_path);
+
+ let mut message_to_sign = Vec::<u8>::new();
+ message_to_sign.extend_from_slice(system_state_value);
+ message_to_sign.extend_from_slice(device_usage_value);
+ message_to_sign.extend_from_slice(image_sign_data);
+
+ let signature =
+ RsaSignature::<B3072, RsaPkcs1v15, Sha256>::sign(&private_key, &message_to_sign)
+ .expect("Failed to sign!");
+
+ image.update_signature_field(&signature.bytes());
+
+ // The whole image has been updated and signed, write the result to disk.
+ image.write_file();
+}
+
+/// Generate a dummy signature key public exponent.
+///
+/// Eventually this value will be obtained from the private key.
+fn signature_key_public_exponent_le() -> Vec<u8> {
+ vec![0xA5; 1]
+}
+
+/// Generate a dummy signature key modulus.
+///
+/// Eventually this value will be obtained from the private key.
+fn signature_key_modulus_le() -> Vec<u8> {
+ vec![0xA5; 384]
+}
+
+/// Generates the device usage value.
+///
+/// This value is extrapolated from the ROM_EXT manifest usage_constraints
+/// field, and does not reside in the ROM_EXT manifest directly.
+pub fn device_usage_value(path: &Path) -> Vec<u8> {
+ let _usage_constraints = fs::read(path).expect("Failed to read usage constraints!");
+
+ // TODO: generate the device_usage_value from usage_constraints.
+ // meanwhile use a "dummy" hard-coded vector.
+ vec![0xA5; 1024]
+}
+
+/// Obtains the system state value from the binary on disk.
+pub fn system_state_value(path: &Path) -> Vec<u8> {
+ fs::read(path).expect("Failed to read system state value!")
}