use anyhow::{bail, Context, Result};
use heck::ToUpperCamelCase;
use indexmap::IndexMap;
use std::fmt::Write;
use crate::asn1::codegen::{self, ConstantPool, VariableCodegenInfo, VariableInfo};
use crate::asn1::x509::X509;
use crate::template::subst::{Subst, SubstValue};
use crate::template::{EcdsaSignature, Signature, Template, Value, Variable, VariableType};
use crate::x509;
const INDENT: &str = " ";
pub struct Codegen {
pub source_h: String,
pub source_c: String,
pub source_unittest: String,
}
pub fn generate_cert(from_file: &str, tmpl: &Template) -> Result<Codegen> {
let mut source_c = String::new();
let mut source_h = String::new();
let mut source_unittest = String::new();
let license_and_warning = indoc::formatdoc! { r#"
// Copyright lowRISC contributors (OpenTitan project).
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
// This file was automatically generated using opentitantool from:
// {from_file}
"#};
source_c.push_str(&license_and_warning);
source_h.push_str(&license_and_warning);
let preproc_guard_include = tmpl.name.to_uppercase();
writeln!(source_h, "#ifndef __{}__", preproc_guard_include)?;
writeln!(source_h, "#define __{}__\n", preproc_guard_include)?;
source_c.push('\n');
writeln!(source_c, "#include \"{}.h\"", tmpl.name)?;
source_c.push_str("#include \"sw/device/silicon_creator/lib/cert/asn1.h\"\n\n");
source_h.push_str("#include \"sw/device/lib/base/status.h\"\n\n");
let mut tbs_vars = IndexMap::<String, VariableType>::new();
let mut sig_vars = IndexMap::<String, VariableType>::new();
for (var_name, var) in tmpl.variables.clone() {
if var_appears_in_sig(&var_name, &tmpl.certificate.signature) {
sig_vars.insert(var_name, var);
} else {
tbs_vars.insert(var_name, var);
}
}
let tbs_value_struct_name = format!("{}_tbs_values", tmpl.name);
source_h.push_str(&generate_value_struct(&tbs_value_struct_name, &tbs_vars));
let tbs_value_struct_name = tbs_value_struct_name + "_t";
let mut const_pool = ConstantPool::new();
let generate_tbs_fn_name = format!("{}_build_tbs", tmpl.name);
let generate_tbs_fn_params =
format!("{tbs_value_struct_name} *values, uint8_t *tbs, size_t *tbs_inout_size");
let (generate_tbs_fn_def, generate_tbs_fn_impl, max_tbs_size) = generate_builder(
CertificateComponent::Tbs,
&generate_tbs_fn_name,
&generate_tbs_fn_params,
&mut const_pool,
&tbs_vars,
|builder| X509::push_tbs_certificate(builder, &tmpl.certificate),
)?;
let tbs_binary_val_name = "tbs";
sig_vars.insert(
tbs_binary_val_name.to_string(),
VariableType::ByteArray { size: max_tbs_size },
);
let tbs_binary_val = Value::Variable(Variable {
name: tbs_binary_val_name.to_string(),
convert: None,
});
let sig_value_struct_name = format!("{}_sig_values", tmpl.name);
source_h.push_str(&generate_value_struct(&sig_value_struct_name, &sig_vars));
let sig_value_struct_name = sig_value_struct_name + "_t";
let generate_cert_fn_name = format!("{}_build_cert", tmpl.name);
let generate_cert_fn_params =
format!("{sig_value_struct_name} *values, uint8_t *cert, size_t *cert_inout_size");
let (generate_cert_fn_def, generate_cert_fn_impl, max_cert_size) = generate_builder(
CertificateComponent::Certificate,
&generate_cert_fn_name,
&generate_cert_fn_params,
&mut const_pool,
&sig_vars,
|builder| X509::push_certificate(builder, &tbs_binary_val, &tmpl.certificate.signature),
)?;
let max_tbs_size_const_name = format!("k{}MaxTbsSizeBytes", tmpl.name.to_upper_camel_case());
let max_cert_size_const_name = format!("k{}MaxCertSizeBytes", tmpl.name.to_upper_camel_case());
source_h.push_str("// Maximum possible size of a TBS and a certificate assuming:\n");
for (var_name, var_type) in tbs_vars.iter().chain(sig_vars.iter()) {
let (codegen, _) = c_variable_info(var_name, "", var_type);
if let VariableCodegenInfo::Pointer { .. } = codegen {
let size = match var_type {
VariableType::ByteArray { size }
| VariableType::Integer { size }
| VariableType::String { size } => *size,
VariableType::Boolean => bail!("internal error: boolean represented by a pointer"),
};
writeln!(source_h, "// - {var_name} is of size at most {size} bytes.")?;
}
}
source_h.push_str(&indoc::formatdoc! {"enum {{
{INDENT}{max_tbs_size_const_name} = {max_tbs_size},
{INDENT}{max_cert_size_const_name} = {max_cert_size},
}};"});
source_h.push_str("\n\n");
source_h.push_str(&generate_tbs_fn_def);
source_h.push_str(&generate_cert_fn_def);
source_h.push('\n');
source_c.push_str(&const_pool.codestring());
source_c.push('\n');
source_c.push_str(&generate_tbs_fn_impl);
source_c.push_str(&generate_cert_fn_impl);
source_c.push('\n');
writeln!(source_h, "\n#endif /* __{}__ */", preproc_guard_include)?;
let unittest_data = tmpl.random_test()?;
let expected_cert = x509::generate_certificate(&tmpl.subst(&unittest_data)?)?;
source_unittest.push_str(&license_and_warning);
source_unittest.push('\n');
source_unittest.push_str("extern \"C\" {\n");
writeln!(source_unittest, "#include \"{}.h\"", tmpl.name)?;
source_unittest.push_str("}\n");
source_unittest.push_str("#include \"gtest/gtest.h\"\n\n");
for (var_name, data) in unittest_data.values {
match data {
SubstValue::ByteArray(bytes) => {
writeln!(
source_unittest,
"uint8_t g_{var_name}[] = {{ {} }};",
bytes
.iter()
.map(|x| format!("{:#02x}", x))
.collect::<Vec<_>>()
.join(", ")
)?;
}
SubstValue::String(s) => writeln!(source_unittest, "char g_{var_name}[] = \"{s}\";")?,
SubstValue::Int32(val) => writeln!(source_unittest, "uint32_t g_{var_name} = {val};")?,
SubstValue::Boolean(val) => writeln!(source_unittest, "bool g_{var_name} = {val};")?,
}
}
source_unittest.push('\n');
writeln!(source_unittest, "{tbs_value_struct_name} g_tbs_values = {{")?;
source_unittest.push_str(&generate_value_struct_assignment(&tbs_vars)?);
source_unittest.push_str("};\n");
source_unittest.push('\n');
writeln!(
source_unittest,
"uint8_t g_{tbs_binary_val_name}[{max_tbs_size}];"
)?;
source_unittest.push('\n');
writeln!(source_unittest, "{sig_value_struct_name} g_sig_values = {{")?;
source_unittest.push_str(&generate_value_struct_assignment(&sig_vars)?);
source_unittest.push_str("};\n");
source_unittest.push('\n');
writeln!(source_unittest, "uint8_t g_cert_data[{max_cert_size}];\n")?;
writeln!(
source_unittest,
"const uint8_t kExpectedCert[{}] = {{ {} }};\n",
expected_cert.len(),
expected_cert
.iter()
.map(|x| format!("{:#02x}", x))
.collect::<Vec<_>>()
.join(", ")
)?;
source_unittest.push('\n');
source_unittest.push_str(&indoc::formatdoc!{ r#"
TEST({}, Verify) {{
{INDENT}EXPECT_EQ(kErrorOk, {generate_tbs_fn_name}(&g_tbs_values, g_{tbs_binary_val_name}, &g_sig_values.{tbs_binary_val_name}_size));
{INDENT}size_t cert_size = sizeof(g_cert_data);
{INDENT}EXPECT_EQ(kErrorOk, {generate_cert_fn_name}(&g_sig_values, g_cert_data, &cert_size));
{INDENT}EXPECT_EQ(cert_size, sizeof(kExpectedCert));
{INDENT}EXPECT_EQ(0, memcmp(g_cert_data, kExpectedCert, cert_size));
}}
"#,
tmpl.name.to_upper_camel_case()
});
Ok(Codegen {
source_h,
source_c,
source_unittest,
})
}
fn generate_value_struct(
value_struct_name: &str,
variables: &IndexMap<String, VariableType>,
) -> String {
let mut source = String::new();
writeln!(source, "typedef struct {value_struct_name} {{").unwrap();
for (var_name, var_type) in variables {
let (_, struct_def) = c_variable_info(var_name, "", var_type);
source.push_str(&struct_def);
}
writeln!(source, "}} {value_struct_name}_t;\n").unwrap();
source
}
fn generate_value_struct_assignment(variables: &IndexMap<String, VariableType>) -> Result<String> {
let mut source = String::new();
for (var_name, var_type) in variables {
let (codegen, _) = c_variable_info(var_name, "", var_type);
match codegen {
VariableCodegenInfo::Pointer {
ptr_expr,
size_expr,
} => {
writeln!(source, "{INDENT}.{ptr_expr} = g_{var_name},")?;
writeln!(source, "{INDENT}.{size_expr} = sizeof(g_{var_name}),")?;
}
VariableCodegenInfo::Int32 { value_expr }
| VariableCodegenInfo::Boolean { value_expr } => {
writeln!(source, "{INDENT}.{value_expr} = g_{var_name},\n")?;
}
}
}
Ok(source)
}
fn var_appears_in_sig(var_name: &str, sig: &Signature) -> bool {
match sig {
Signature::EcdsaWithSha256 { value } => {
let Some(EcdsaSignature { r, s }) = value else {
return false;
};
r.refers_to(var_name) || s.refers_to(var_name)
}
}
}
#[derive(Debug, PartialEq)]
enum CertificateComponent {
Certificate,
Tbs,
}
fn generate_builder(
component: CertificateComponent,
fn_name: &str,
fn_params_str: &str,
constants: &mut ConstantPool,
variables: &IndexMap<String, VariableType>,
gen: impl FnOnce(&mut codegen::Codegen) -> Result<()>,
) -> Result<(String, String, usize)> {
let mut generate_fn_impl = String::new();
writeln!(
generate_fn_impl,
"rom_error_t {fn_name}({fn_params_str}) {{"
)?;
let get_var_info = |var_name: &str| -> Result<VariableInfo> {
let var_type = variables
.get(var_name)
.with_context(|| format!("could not find variable '{var_name}'"))
.copied()?;
let (codegen, _) = c_variable_info(var_name, "values->", &var_type);
Ok(VariableInfo { var_type, codegen })
};
let generate_fn_def: String;
let implementation: String;
let max_size: usize;
if component == CertificateComponent::Tbs {
generate_fn_def = indoc::formatdoc! { r#"
/**
* Generates a TBS certificate.
*
* @param values Pointer to a structure giving the values to use to generate the TBS
* portion of the certificate.
* @param[out] tbs Pointer to a user-allocated buffer that will contain the TBS portion of
* the certificate.
* @param[in,out] tbs_inout_size Pointer to an integer holding the size of
* the provided buffer; this value will be updated to reflect the actual size of
* the output.
* @return The result of the operation.
*/
rom_error_t {fn_name}({fn_params_str});
"#
};
(implementation, max_size) = codegen::Codegen::generate(
"tbs",
"tbs_inout_size",
INDENT,
1,
constants,
&get_var_info,
gen,
)?;
} else {
generate_fn_def = indoc::formatdoc! { r#"
/**
* Generates an endorsed certificate from a TBS certificate and a signature.
*
* @param values Pointer to a structure giving the values to use to generate the
* certificate (TBS and signature).
* @param[out] cert Pointer to a user-allocated buffer that will contain the
* result.
* @param[in,out] cert_inout_size Pointer to an integer holding the size of
* the provided buffer, this value will be updated to reflect the actual size of
* the output.
* @return The result of the operation.
*/
rom_error_t {fn_name}({fn_params_str});
"#
};
(implementation, max_size) = codegen::Codegen::generate(
"cert",
"cert_inout_size",
INDENT,
1,
constants,
&get_var_info,
gen,
)?;
}
generate_fn_impl.push_str(&implementation);
generate_fn_impl.push_str(" return kErrorOk;\n");
generate_fn_impl.push_str("}\n\n");
Ok((generate_fn_def, generate_fn_impl, max_size))
}
fn c_integer_for_length(size: usize) -> Option<&'static str> {
match size {
4 => Some("uint32_t"),
_ => None,
}
}
fn c_variable_info(
name: &str,
struct_expr: &str,
var_type: &VariableType,
) -> (VariableCodegenInfo, String) {
match var_type {
VariableType::ByteArray { .. } => (
VariableCodegenInfo::Pointer {
ptr_expr: format!("{struct_expr}{name}"),
size_expr: format!("{struct_expr}{name}_size"),
},
indoc::formatdoc! {r#"
{INDENT}// Pointer to an array of bytes.
{INDENT}uint8_t *{name};
{INDENT}// Size of this array in bytes.
{INDENT}size_t {name}_size;
"#
},
),
VariableType::Integer { size } => match c_integer_for_length(*size) {
Some(c_type) => (
VariableCodegenInfo::Int32 {
value_expr: format!("{struct_expr}{name}"),
},
format!("{INDENT}{c_type} {name};\n"),
),
None => (
VariableCodegenInfo::Pointer {
ptr_expr: format!("{struct_expr}{name}"),
size_expr: format!("{struct_expr}{name}_size"),
},
indoc::formatdoc! {r#"
{INDENT}// Pointer to an unsigned big-endian in integer.
{INDENT}uint8_t *{name};
{INDENT}// Size of this integer in bytes.
{INDENT}size_t {name}_size;
"#
},
),
},
VariableType::String { .. } => (
VariableCodegenInfo::Pointer {
ptr_expr: format!("{struct_expr}{name}"),
size_expr: format!("{struct_expr}{name}_len"),
},
indoc::formatdoc! {r#"
{INDENT}// Pointer to a (not necessarily zero-terminated) string.
{INDENT}char *{name};
{INDENT}// Length of this string.
{INDENT}size_t {name}_len;
"#
},
),
VariableType::Boolean => (
VariableCodegenInfo::Boolean {
value_expr: format!("{struct_expr}{name}"),
},
format!("{INDENT}bool {name};\n"),
),
}
}