[opentitantool] add a command to export a public key to header file

The "show" command can be used to display a key but the output formats
supported are only json, json5, etc. It does not make sense to add
an output format just for the ROM since it is so specific to this
particular command. Instead I created another command that output
the header that can be used in the ROM/ROM_EXT.

Signed-off-by: Amaury Pouly <amaury.pouly@lowrisc.org>
diff --git a/sw/host/opentitantool/BUILD b/sw/host/opentitantool/BUILD
index c106f9c..e00da2b 100644
--- a/sw/host/opentitantool/BUILD
+++ b/sw/host/opentitantool/BUILD
@@ -2,7 +2,7 @@
 # Licensed under the Apache License, Version 2.0, see LICENSE for details.
 # SPDX-License-Identifier: Apache-2.0
 
-load("@rules_rust//rust:defs.bzl", "rust_binary")
+load("@rules_rust//rust:defs.bzl", "rust_binary", "rust_test")
 load("@rules_pkg//pkg:mappings.bzl", "pkg_filegroup", "pkg_files")
 
 package(default_visibility = ["//visibility:public"])
@@ -35,6 +35,7 @@
     proc_macro_deps = [
         "//sw/host/opentitanlib/opentitantool_derive",
     ],
+    # stamping is necessary because opentitantool builds version.rs that needs it
     stamp = 1,
     tags = [
         # Only until we figure out how to deal with version.rs including
@@ -78,3 +79,10 @@
     ],
     prefix = "opentitantool",
 )
+
+rust_test(
+    name = "opentitantool_test",
+    crate = ":opentitantool",
+    # stamping is necessary because opentitantool builds version.rs that needs it
+    stamp = 1,
+)
diff --git a/sw/host/opentitantool/src/command/rsa.rs b/sw/host/opentitantool/src/command/rsa.rs
index f6e8c15..94c74ab 100644
--- a/sw/host/opentitantool/src/command/rsa.rs
+++ b/sw/host/opentitantool/src/command/rsa.rs
@@ -2,9 +2,12 @@
 // Licensed under the Apache License, Version 2.0, see LICENSE for details.
 // SPDX-License-Identifier: Apache-2.0
 
-use anyhow::Result;
+use anyhow::{bail, Result};
+use regex::Regex;
 use serde_annotate::Annotate;
 use std::any::Any;
+use std::fs::File;
+use std::io::Write;
 use std::path::PathBuf;
 use structopt::StructOpt;
 
@@ -16,6 +19,18 @@
 use opentitanlib::crypto::sha256::Sha256Digest;
 use opentitanlib::util::parse_int::ParseInt;
 
+/// Given the path to a public key, returns the public key. Given
+/// the path to a private key, extracts the public key from the private
+/// key and returns the public key.
+fn load_pub_or_priv_key(path: &PathBuf) -> Result<RsaPublicKey> {
+    if let Ok(key) = RsaPublicKey::from_pkcs1_der_file(path) {
+        return Ok(key);
+    }
+    Ok(RsaPublicKey::from_private_key(
+        &RsaPrivateKey::from_pkcs8_der_file(path)?,
+    ))
+}
+
 #[derive(serde::Serialize)]
 pub struct RsaKeyInfo {
     pub key_num_bits: usize,
@@ -50,13 +65,7 @@
         _context: &dyn Any,
         _transport: &TransportWrapper,
     ) -> Result<Option<Box<dyn Annotate>>> {
-        let key = match RsaPublicKey::from_pkcs1_der_file(&self.der_file) {
-            Ok(key) => Ok(key),
-            Err(_) => match RsaPrivateKey::from_pkcs8_der_file(&self.der_file) {
-                Ok(key) => Ok(RsaPublicKey::from_private_key(&key)),
-                Err(e) => Err(e),
-            },
-        }?;
+        let key = load_pub_or_priv_key(&self.der_file)?;
 
         Ok(Some(Box::new(RsaKeyInfo {
             key_num_bits: key.modulus_num_bits(),
@@ -98,10 +107,147 @@
     }
 }
 
+/// Export public information of a private or public RSA key
+/// to a C header that can be used in the ROM or ROM_EXT
+#[derive(Debug, StructOpt)]
+pub struct RsaKeyExportCommand {
+    #[structopt(
+        name = "DER_FILE",
+        help = "RSA public or private key file in DER format"
+    )]
+    der_file: PathBuf,
+    #[structopt(name = "OUTPUT_FILE", help = "output header file to generate")]
+    output_file: Option<PathBuf>,
+}
+
+/// Write the content of a big integer as an array of 32-bit words.
+/// The number must be represented as an array of bytes in little-endian whose
+/// length is a multiple of four. The output is compatible with the format used
+/// by the ROM and ROM_EXT with sigverify_rom_key_t, ie the modulus and n0_inv
+/// are represented as an array of 32-bit words in little-endian. To make the function's
+/// output flexible, the function can print up to a specified number of items per
+/// line, and each line can be prefixed and suffixed with a specified string
+fn write_bigint_as_u32<W: Write>(
+    out: &mut W,
+    number: Vec<u8>,
+    nr_per_line: usize,
+    prefix: &str,
+    suffix: &str,
+) -> Result<()> {
+    let chunk = std::mem::size_of::<u32>();
+    assert!(
+        number.len() % chunk == 0,
+        "the big integer size is not a multiple of 4 bytes"
+    );
+    for (idx, num) in number.windows(chunk).step_by(chunk).enumerate() {
+        // print prefix and newline on multiples of nr_per_line
+        if idx % nr_per_line == 0 {
+            if idx != 0 {
+                write!(out, "{}\n", suffix)?;
+            }
+            write!(out, "{}", prefix)?;
+        }
+        // print one 32-bit word
+        let val = u32::from_le_bytes(num.try_into().unwrap());
+        write!(out, "{:#010x}, ", val)?;
+    }
+    // extra return to the line
+    write!(out, "{}\n", suffix)?;
+    Ok(())
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use anyhow::Result;
+
+    #[test]
+    fn test_write_bigint_as_u32() -> Result<()> {
+        let mut buf: Vec<u8> = Vec::new();
+        let bignum = vec![
+            0x10, 0x32, 0x54, 0x76, 0x98, 0xba, 0xdc, 0xfe, 0x18, 0x29, 0x3a, 0x4b,
+        ];
+        write_bigint_as_u32(&mut buf, bignum, 2, "X", "Y")?;
+        let output = br#"X0x76543210, 0xfedcba98, Y
+X0x4b3a2918, Y
+"#;
+        assert_eq!(buf, output);
+        Ok(())
+    }
+}
+
+impl CommandDispatch for RsaKeyExportCommand {
+    fn run(
+        &self,
+        _context: &dyn Any,
+        _transport: &TransportWrapper,
+    ) -> Result<Option<Box<dyn Annotate>>> {
+        let key = load_pub_or_priv_key(&self.der_file)?;
+
+        let output_path = match &self.output_file {
+            Some(path) => path.clone(),
+            None => {
+                // if no output file is provided, derive the name of the file from the
+                // name of the key
+                let mut out_path = self.der_file.clone();
+                out_path.set_extension("h");
+                out_path
+            }
+        };
+        // We try to catch the mistake of a user that specifies the key file as output,
+        // which would overwrite it. This will not detect situations where there is a symlink
+        // involved so this will only catch "obvious" mistakes.
+        if self.der_file == output_path {
+            bail!("the output file is the same as the key file, this would overwrite the key, not allowing this")
+        }
+        println!("exporting key to {}", output_path.display());
+
+        let mut file = File::create(&output_path)?;
+        // construct a key name from the key file name
+        let keyname = self
+            .der_file
+            .file_name()
+            .expect("this should be a valid file name since we opened the file")
+            .to_string_lossy();
+        let re = Regex::new(r#"(.pub)?.der$"#).unwrap();
+        let keyname = re.replace_all(&keyname, "");
+        let re = Regex::new(r#"[^a-zA-Z0-9]"#).unwrap();
+        let keyname = re.replace_all(&keyname, "_").to_ascii_uppercase();
+        // we cannot know the purpose of this key but the header guard should probably include it
+        // so we add some extra text to the guard that will not compile to force the user to fix it
+        let header_guard = format!("{}_H", keyname);
+        // write header guard
+        writeln!(&mut file, "#ifndef {}", header_guard)?;
+        writeln!(&mut file, "#define {}", header_guard)?;
+        writeln!(&mut file, "")?;
+        writeln!(&mut file, "#define {} \\", keyname)?;
+        writeln!(&mut file, " {{ \\")?;
+        writeln!(&mut file, "    .n = \\")?;
+        writeln!(&mut file, "        {{{{ \\")?;
+        write_bigint_as_u32(
+            &mut file,
+            key.modulus().to_le_bytes(),
+            5,
+            "            ",
+            "\\",
+        )?;
+        writeln!(&mut file, "        }}}}, \\")?;
+        writeln!(&mut file, "    .n0_inv = {{ \\")?;
+        write_bigint_as_u32(&mut file, key.n0_inv()?.to_le_bytes(), 4, "        ", "\\")?;
+        writeln!(&mut file, "        }}, \\")?;
+        writeln!(&mut file, " }}")?;
+        writeln!(&mut file, "")?;
+        writeln!(&mut file, "#endif // {}", header_guard)?;
+
+        Ok(None)
+    }
+}
+
 #[derive(Debug, StructOpt, CommandDispatch)]
 pub enum RsaKeySubcommands {
     Show(RsaKeyShowCommand),
     Generate(RsaKeyGenerateCommand),
+    Export(RsaKeyExportCommand),
 }
 
 #[derive(serde::Serialize)]