blob: 94c74ab14355bd34759688e0878bb31a5e222bcf [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
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;
use opentitanlib::app::command::CommandDispatch;
use opentitanlib::app::TransportWrapper;
use opentitanlib::crypto::rsa::{
Exponent, Modulus, N0Inv, RsaPrivateKey, RsaPublicKey, Signature, RR,
};
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,
pub modulus: Modulus,
pub public_exponent: Exponent,
pub n0_inv: N0Inv,
pub rr: RR,
}
#[derive(serde::Serialize)]
pub struct RsaKeyInfoInWords {
pub key_num_bits: usize,
pub modulus: Vec<String>,
pub public_exponent: Vec<String>,
pub n0_inv: Vec<String>,
pub rr: Vec<String>,
}
/// Show public information of a private or public RSA key
#[derive(Debug, StructOpt)]
pub struct RsaKeyShowCommand {
#[structopt(
name = "DER_FILE",
help = "RSA public or private key file in DER format"
)]
der_file: PathBuf,
}
impl CommandDispatch for RsaKeyShowCommand {
fn run(
&self,
_context: &dyn Any,
_transport: &TransportWrapper,
) -> Result<Option<Box<dyn Annotate>>> {
let key = load_pub_or_priv_key(&self.der_file)?;
Ok(Some(Box::new(RsaKeyInfo {
key_num_bits: key.modulus_num_bits(),
modulus: key.modulus(),
public_exponent: key.exponent(),
n0_inv: key.n0_inv()?,
rr: key.rr(),
})))
}
}
/// Generate a 3072-bit RSA public private key pair with exponent 65537. RSA private key will
/// be written to <OUTPUT_DIR>/<BASENAME>.der and RSA public key will be written to
/// <OUTPUT_DIR>/<BASENAME>.pub.der
#[derive(Debug, StructOpt)]
pub struct RsaKeyGenerateCommand {
#[structopt(name = "OUTPUT_DIR", help = "Output directory")]
output_dir: PathBuf,
#[structopt(name = "BASENAME", help = "Basename for the generated key pair")]
basename: String,
}
impl CommandDispatch for RsaKeyGenerateCommand {
fn run(
&self,
_context: &dyn Any,
_transport: &TransportWrapper,
) -> Result<Option<Box<dyn Annotate>>> {
let private_key = RsaPrivateKey::new()?;
let mut der_file = self.output_dir.to_owned();
der_file.push(&self.basename);
der_file.set_extension("der");
private_key.to_pkcs8_der_file(&der_file)?;
der_file.set_extension("pub.der");
RsaPublicKey::from_private_key(&private_key).to_pkcs1_der_file(&der_file)?;
Ok(None)
}
}
/// 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)]
pub struct RsaSignResult {
pub digest: String,
pub signature: String,
}
#[derive(Debug, StructOpt)]
pub struct RsaSignCommand {
#[structopt(
name = "DER_FILE",
parse(try_from_str=RsaPrivateKey::from_pkcs8_der_file),
help = "RSA private key file in PKCS#1 DER format"
)]
private_key: RsaPrivateKey,
#[structopt(
name = "SHA256_DIGEST",
parse(try_from_str=ParseInt::from_str),
help = "SHA256 digest of the message"
)]
digest: Sha256Digest,
#[structopt(
name = "output",
short,
long,
help = "File name to write the signature to"
)]
output: Option<PathBuf>,
}
impl CommandDispatch for RsaSignCommand {
fn run(
&self,
_context: &dyn Any,
_transport: &TransportWrapper,
) -> Result<Option<Box<dyn Annotate>>> {
let signature = self.private_key.sign(&self.digest)?;
if let Some(output) = &self.output {
signature.write_to_file(output)?;
}
Ok(Some(Box::new(RsaSignResult {
digest: self.digest.to_string(),
signature: signature.to_string(),
})))
}
}
#[derive(Debug, StructOpt)]
pub struct RsaVerifyCommand {
#[structopt(name = "KEY", help = "Key file in DER format")]
der_file: PathBuf,
#[structopt(
name = "SHA256_DIGEST",
help = "SHA256 digest of the message as a hex string (big-endian), i.e. 0x..."
)]
digest: String,
#[structopt(
name = "SIGNATURE",
help = "Signature to be verified as a hex string (big-endian), i.e. 0x..."
)]
signature: String,
}
impl CommandDispatch for RsaVerifyCommand {
fn run(
&self,
_context: &dyn Any,
_transport: &TransportWrapper,
) -> Result<Option<Box<dyn Annotate>>> {
let key = RsaPublicKey::from_pkcs1_der_file(&self.der_file)?;
let digest = Sha256Digest::from_str(&self.digest)?;
let signature = Signature::from_str(&self.signature)?;
key.verify(&digest, &signature)?;
Ok(None)
}
}
#[derive(Debug, StructOpt, CommandDispatch)]
/// RSA commands.
#[allow(clippy::large_enum_variant)]
pub enum Rsa {
Key(RsaKeySubcommands),
Sign(RsaSignCommand),
Verify(RsaVerifyCommand),
}