ot_certs/
codegen.rs

1// Copyright lowRISC contributors (OpenTitan project).
2// Licensed under the Apache License, Version 2.0, see LICENSE for details.
3// SPDX-License-Identifier: Apache-2.0
4
5//! This module is capable of generating C code for generating a binary X.509
6//! certificate according to a [`Template`].
7
8use anyhow::{Context, Result};
9use heck::ToUpperCamelCase;
10use indexmap::IndexMap;
11use itertools::Itertools;
12use std::fmt::Write;
13
14use crate::asn1::codegen::{self, CodegenOutput, VariableCodegenInfo, VariableInfo};
15use crate::asn1::x509::X509;
16use crate::template::subst::{Subst, SubstValue};
17use crate::template::{
18    EcdsaSignature, Signature, SizeRange, Template, Value, Variable, VariableType,
19};
20use crate::x509;
21
22/// The amount of test cases to generate for covering more corner cases.
23const TEST_CASE_COUNT: u32 = 100;
24
25pub struct Codegen {
26    /// Header.
27    pub source_h: String,
28    /// Code containing the template and setters.
29    pub source_c: String,
30    /// Code containing the unittest.
31    pub source_unittest: String,
32}
33
34/// Generate the certificate template header and source file.
35///
36/// The generated files will indicate that they have been automatically
37/// generated from `from_file`.
38/// Returns the implementation first and the header file second.
39/// NOTE: the implementation file will `#include "<header>.h"` the header
40/// where `<header>` comes from `tmpl.name`.
41///
42/// The generated header file contains the following elements. Below `<name>`
43/// refers to `tmpl.name` and `<Name>` to the "camel-case" variant of `<name>`.
44/// 1. License header, warning, include guard.
45/// 2. Relevant includes.
46/// 3. Definition of a data structure to hold the values of the variables
47///    used in the TBS. It is named `<name>_tbs_values_t`.
48/// 4. Definition of a data structure to hold the values of the variables
49///    used in the signature. It is named `<name>_sig_values_t`. Note that this
50///    structure contains an extra field called `tbs` (and its size `tbs_size`)
51///    that must point to the buffer containing the TBS.
52/// 5. An enumeration hold two values: one gives the maximum size of the TBS
53///    given the variables sizes defined in the template, and another one for
54///    the maximum size of the whole certificate. They are named
55///    `k<Name>MaxTbsSizeBytes` and `k<Name>MaxCertSizeBytes` respectively.
56/// 6. Definition and documentation of a function that takes as input a
57///    a `<name>_tbs_values_t` and a buffer to produce the TBS. It is named
58///    `<name>_build_tbs` and returns a `rom_error_t`.
59/// 7. Definition and documentation of a function that takes as input a
60///    a `<name>_sig_values_t` and a buffer to produce the certificate.
61///    It is named `<name>_build_cert` and returns a `rom_error_t`.
62pub fn generate_cert(from_file: &str, tmpl: &Template) -> Result<Codegen> {
63    let mut source_c = String::new();
64    let mut source_h = String::new();
65    let mut source_unittest = String::new();
66
67    let license_and_warning = indoc::formatdoc! { r#"
68    // Copyright lowRISC contributors (OpenTitan project).
69    // Licensed under the Apache License, Version 2.0, see LICENSE for details.
70    // SPDX-License-Identifier: Apache-2.0
71
72    // This file was automatically generated using opentitantool from:
73    // {from_file}
74    "#};
75
76    // License, warning about autogenerated code and guard inclusion checks.
77    source_c.push_str(&license_and_warning);
78    source_h.push_str(&license_and_warning);
79    let preproc_guard_include = tmpl.name.to_uppercase();
80    writeln!(source_h, "#ifndef __{}__", preproc_guard_include)?;
81    writeln!(source_h, "#define __{}__\n", preproc_guard_include)?;
82
83    // Headers inclusion.
84    source_c.push('\n');
85    writeln!(source_c, "#include \"{}.h\"", tmpl.name)?;
86    source_c.push_str("#include \"sw/device/silicon_creator/lib/cert/asn1.h\"\n\n");
87    source_c.push_str("#include \"sw/device/silicon_creator/lib/cert/template.h\"\n\n");
88
89    source_h.push_str("#include \"sw/device/lib/base/status.h\"\n\n");
90
91    // Partition variables between TBS and signature.
92    let mut tbs_vars = IndexMap::<String, VariableType>::new();
93    let mut sig_vars = IndexMap::<String, VariableType>::new();
94    for (var_name, var) in tmpl.variables.clone() {
95        if var_appears_in_sig(&var_name, &tmpl.certificate.signature) {
96            sig_vars.insert(var_name, var);
97        } else {
98            tbs_vars.insert(var_name, var);
99        }
100    }
101
102    // Structure containing the TBS variables.
103    let tbs_value_struct_name = format!("{}_tbs_values", tmpl.name);
104    source_h.push_str(&generate_value_struct(&tbs_value_struct_name, &tbs_vars));
105    let tbs_value_struct_name = tbs_value_struct_name + "_t";
106
107    // Generate TBS function.
108    let generate_tbs_fn_name = format!("{}_build_tbs", tmpl.name);
109    let generate_tbs_fn_params =
110        format!("{tbs_value_struct_name} *values, uint8_t *out_buf, size_t *inout_size");
111    let (generate_tbs_fn_def, generate_tbs_fn_impl) = generate_builder(
112        CertificateComponent::Tbs,
113        &generate_tbs_fn_name,
114        &generate_tbs_fn_params,
115        &tbs_vars,
116        |builder| X509::push_tbs_certificate(builder, &tmpl.certificate),
117    )?;
118
119    // Create a special variable to hold the TBS binary.
120    let tbs_binary_val_name = "tbs";
121    sig_vars.insert(
122        tbs_binary_val_name.to_string(),
123        VariableType::ByteArray {
124            size: SizeRange::RangeSize(
125                generate_tbs_fn_impl.min_size,
126                generate_tbs_fn_impl.max_size,
127            ),
128            tweak_msb: None,
129        },
130    );
131    let tbs_binary_val = Value::Variable(Variable {
132        name: tbs_binary_val_name.to_string(),
133        convert: None,
134    });
135
136    // Structure containing the signature variables.
137    let sig_value_struct_name = format!("{}_sig_values", tmpl.name);
138    source_h.push_str(&generate_value_struct(&sig_value_struct_name, &sig_vars));
139    let sig_value_struct_name = sig_value_struct_name + "_t";
140
141    // Generate sig function.
142    let generate_cert_fn_name = format!("{}_build_cert", tmpl.name);
143    let generate_cert_fn_params =
144        format!("{sig_value_struct_name} *values, uint8_t *out_buf, size_t *inout_size");
145    let (generate_cert_fn_def, generate_cert_fn_impl) = generate_builder(
146        CertificateComponent::Certificate,
147        &generate_cert_fn_name,
148        &generate_cert_fn_params,
149        &sig_vars,
150        |builder| X509::push_certificate(builder, &tbs_binary_val, &tmpl.certificate.signature),
151    )?;
152
153    let tmpl_name = tmpl.name.to_upper_camel_case();
154
155    // Create constants for the variable size range.
156    source_h.push_str("enum {\n");
157    for (var_name, var_type) in tbs_vars.iter().chain(sig_vars.iter()) {
158        let (codegen, _) = c_variable_info(var_name, "", var_type);
159        let const_name = var_name.to_upper_camel_case();
160        let (min_size, max_size) = if let VariableCodegenInfo::Pointer { .. } = codegen {
161            let (min_size, max_size) = var_type.array_size();
162            if var_type.has_constant_array_size() {
163                writeln!(
164                    source_h,
165                    "k{tmpl_name}Exact{const_name}SizeBytes = {max_size},"
166                )?;
167            }
168            (min_size, max_size)
169        } else {
170            // Arbitrary range to hold scalars.
171            (1, 100)
172        };
173        writeln!(
174            source_h,
175            "k{tmpl_name}Min{const_name}SizeBytes = {min_size},"
176        )?;
177        writeln!(
178            source_h,
179            "k{tmpl_name}Max{const_name}SizeBytes = {max_size},"
180        )?;
181    }
182    source_h.push_str("};\n");
183
184    // Create field name mapping.
185    for (var_name, _) in tbs_vars.iter().chain(sig_vars.iter()) {
186        let const_name = var_name.to_upper_camel_case();
187        writeln!(source_h, "#define k{tmpl_name}Field{const_name} {var_name}")?;
188    }
189
190    let max_cert_size_const_name = format!("k{}MaxCertSizeBytes", tmpl.name.to_upper_camel_case());
191    source_h.push_str("// Maximum possible size of a certificate\n");
192    source_h.push_str(&indoc::formatdoc! {"enum {{
193        {max_cert_size_const_name} = {},
194    }};", generate_cert_fn_impl.max_size});
195
196    // Output definition of the functions.
197    source_h.push_str("\n\n");
198    source_h.push_str(&generate_tbs_fn_def);
199    source_h.push_str(&generate_cert_fn_def);
200    source_h.push('\n');
201
202    // Output the implementation of the functions.
203    source_c.push_str(&generate_tbs_fn_impl.code);
204    source_c.push_str(&generate_cert_fn_impl.code);
205    source_c.push('\n');
206
207    writeln!(source_h, "\n#endif /* __{}__ */", preproc_guard_include)?;
208
209    // Generate unittest.
210    source_unittest.push_str(&license_and_warning);
211    source_unittest.push('\n');
212    source_unittest.push_str("extern \"C\" {\n");
213    writeln!(source_unittest, "#include \"{}.h\"", tmpl.name)?;
214    source_unittest.push_str("}\n");
215    source_unittest.push_str("#include \"gtest/gtest.h\"\n\n");
216
217    for idx in 0..TEST_CASE_COUNT {
218        let test_case = generate_test_case(
219            &format!("Verify{idx}"),
220            &tbs_vars,
221            &sig_vars,
222            generate_tbs_fn_impl.min_size,
223            generate_tbs_fn_impl.max_size,
224            generate_cert_fn_impl.max_size,
225            tmpl,
226        )?;
227        source_unittest.push_str(&test_case);
228    }
229
230    Ok(Codegen {
231        source_h,
232        source_c,
233        source_unittest,
234    })
235}
236
237// Generate a unit test test case with random variables.
238fn generate_test_case(
239    test_name: &str,
240    tbs_vars: &IndexMap<String, VariableType>,
241    sig_vars: &IndexMap<String, VariableType>,
242    min_tbs_size: usize,
243    max_tbs_size: usize,
244    max_cert_size: usize,
245    tmpl: &Template,
246) -> Result<String> {
247    let mut source_unittest = String::new();
248    let unittest_data = tmpl.random_test()?;
249    let expected_cert = x509::generate_certificate(&tmpl.subst(&unittest_data)?)?;
250
251    let tmpl_name = tmpl.name.to_upper_camel_case();
252    let generate_tbs_fn_name = format!("{}_build_tbs", tmpl.name);
253    let generate_cert_fn_name = format!("{}_build_cert", tmpl.name);
254    let tbs_value_struct_name = format!("{}_tbs_values_t", tmpl.name);
255    let sig_value_struct_name = format!("{}_sig_values_t", tmpl.name);
256
257    source_unittest.push_str(&format! { r#"
258        TEST({tmpl_name}, {test_name})
259    "#});
260
261    source_unittest.push_str(
262        "
263        {
264    ",
265    );
266
267    // Generate constants holding the data.
268    for (var_name, data) in unittest_data.values {
269        match data {
270            SubstValue::ByteArray(bytes) => {
271                writeln!(
272                    source_unittest,
273                    "static uint8_t g_{var_name}[] = {{ {} }};",
274                    bytes
275                        .iter()
276                        .map(|x| format!("{:#02x}", x))
277                        .collect::<Vec<_>>()
278                        .join(", ")
279                )?;
280            }
281            SubstValue::String(s) => {
282                let s = s.chars().map(|c| format!("'{c}'")).join(", ");
283                writeln!(source_unittest, "static char g_{var_name}[] = {{{s}}};")?
284            }
285
286            SubstValue::Uint32(val) => writeln!(source_unittest, "uint32_t g_{var_name} = {val};")?,
287            SubstValue::Boolean(val) => writeln!(source_unittest, "bool g_{var_name} = {val};")?,
288        }
289    }
290    // Generate structure to hold the TBS data.
291    source_unittest.push('\n');
292    writeln!(source_unittest, "{tbs_value_struct_name} g_tbs_values = {{")?;
293    source_unittest.push_str(&generate_value_struct_assignment(tbs_vars)?);
294    source_unittest.push_str("};\n");
295    // Generate buffer for the TBS data.
296    source_unittest.push('\n');
297    writeln!(source_unittest, "uint8_t g_tbs[{max_tbs_size}];")?;
298    // Generate structure to hold the certificate data.
299    source_unittest.push('\n');
300    writeln!(source_unittest, "{sig_value_struct_name} g_sig_values = {{")?;
301    source_unittest.push_str(&generate_value_struct_assignment(sig_vars)?);
302    source_unittest.push_str("};\n");
303    // Generate buffer for the certificate data.
304    source_unittest.push('\n');
305    writeln!(source_unittest, "uint8_t g_cert_data[{max_cert_size}];\n")?;
306    // Generate expected result.
307    writeln!(
308        source_unittest,
309        "const uint8_t kExpectedCert[{}] = {{ {} }};\n",
310        expected_cert.len(),
311        expected_cert
312            .iter()
313            .map(|x| format!("0x{:02x}", x))
314            .collect::<Vec<_>>()
315            .join(", ")
316    )?;
317    source_unittest.push('\n');
318
319    // Comment out tbs size setter if the size is constant.
320    let no_tbs_size = if min_tbs_size == max_tbs_size {
321        "// "
322    } else {
323        ""
324    };
325
326    // Generate the body of the test.
327    source_unittest.push_str(&format! { r#"
328            size_t tbs_size = sizeof(g_tbs);
329            EXPECT_EQ(kErrorOk, {generate_tbs_fn_name}(&g_tbs_values, g_tbs, &tbs_size));
330            EXPECT_GE(tbs_size, {min_tbs_size});
331            EXPECT_LE(tbs_size, {max_tbs_size});
332
333            {no_tbs_size} g_sig_values.tbs_size = tbs_size;
334
335            size_t cert_size = sizeof(g_cert_data);
336            EXPECT_EQ(kErrorOk, {generate_cert_fn_name}(&g_sig_values, g_cert_data, &cert_size));
337            EXPECT_EQ(cert_size, sizeof(kExpectedCert));
338            printf("Generated cert: \n");
339            for (size_t i=0; i<cert_size; i++) {{
340              printf("%02x, ", g_cert_data[i]);
341            }}
342            printf("\n");
343            EXPECT_EQ(0, memcmp(g_cert_data, kExpectedCert, cert_size));
344        "#,
345    });
346
347    source_unittest.push_str(
348        "
349        }
350    ",
351    );
352
353    Ok(source_unittest)
354}
355
356// Generate a structure holding the value of the variables.
357fn generate_value_struct(
358    value_struct_name: &str,
359    variables: &IndexMap<String, VariableType>,
360) -> String {
361    let mut source = String::new();
362    writeln!(source, "typedef struct {value_struct_name} {{").unwrap();
363    for (var_name, var_type) in variables {
364        let (_, struct_def) = c_variable_info(var_name, "", var_type);
365        source.push_str(&struct_def);
366    }
367    writeln!(source, "}} {value_struct_name}_t;\n").unwrap();
368    source
369}
370
371// Generate an assignment of a structure holding the values of the variables.
372// This is used in the unittest to fill the TBS and sig structures.
373fn generate_value_struct_assignment(variables: &IndexMap<String, VariableType>) -> Result<String> {
374    let mut source = String::new();
375    for (var_name, var_type) in variables {
376        let (codegen, _) = c_variable_info(var_name, "", var_type);
377        // The TBS variable is special
378        match codegen {
379            VariableCodegenInfo::Pointer {
380                ptr_expr,
381                size_expr,
382            } => {
383                writeln!(source, ".{ptr_expr} = g_{var_name},")?;
384                if !var_type.has_constant_array_size() {
385                    writeln!(source, ".{size_expr} = sizeof(g_{var_name}),")?;
386                }
387            }
388            VariableCodegenInfo::Uint32 { value_expr }
389            | VariableCodegenInfo::Boolean { value_expr } => {
390                writeln!(source, ".{value_expr} = g_{var_name},\n")?;
391            }
392        }
393    }
394    Ok(source)
395}
396
397// Decide if a variable appears in a signature field (if not, it is in the TBS).
398fn var_appears_in_sig(var_name: &str, sig: &Signature) -> bool {
399    match sig {
400        Signature::EcdsaWithSha256 { value } => {
401            let Some(EcdsaSignature { r, s }) = value else {
402                return false;
403            };
404            r.refers_to(var_name) || s.refers_to(var_name)
405        }
406    }
407}
408
409#[derive(Debug, PartialEq)]
410enum CertificateComponent {
411    Certificate,
412    Tbs,
413}
414
415/// Generate a function that generates a TBS/cert.
416///
417/// This functions returns the header definition, the generated implementation
418/// code and the produced TBS/cert size range.
419fn generate_builder(
420    component: CertificateComponent,
421    fn_name: &str,
422    fn_params_str: &str,
423    variables: &IndexMap<String, VariableType>,
424    build: impl FnOnce(&mut codegen::Codegen) -> Result<()>,
425) -> Result<(String, CodegenOutput)> {
426    let get_var_info = |var_name: &str| -> Result<VariableInfo> {
427        let var_type = variables
428            .get(var_name)
429            .with_context(|| format!("could not find variable '{var_name}'"))
430            .copied()?;
431        let (codegen, _) = c_variable_info(var_name, "values->", &var_type);
432        Ok(VariableInfo { var_type, codegen })
433    };
434    let generate_fn_def: String;
435    let mut generated_code: CodegenOutput;
436    if component == CertificateComponent::Tbs {
437        generate_fn_def = indoc::formatdoc! { r#"
438        /**
439         * Generates a TBS certificate.
440         *
441         * @param values Pointer to a structure giving the values to use to generate the TBS
442         * portion of the certificate.
443         * @param[out] out_buf Pointer to a user-allocated buffer that will contain the TBS portion of
444         * the certificate.
445         * @param[in,out] inout_size Pointer to an integer holding the size of
446         * the provided buffer; this value will be updated to reflect the actual size of
447         * the output.
448         * @return The result of the operation.
449         */
450        rom_error_t {fn_name}({fn_params_str});
451
452        "#
453        };
454        generated_code = codegen::Codegen::generate(
455            /* buf_name */ "out_buf",
456            /* buf_size_name */ "inout_size",
457            &get_var_info,
458            build,
459        )?;
460    } else {
461        generate_fn_def = indoc::formatdoc! { r#"
462        /**
463         * Generates an endorsed certificate from a TBS certificate and a signature.
464         *
465         * @param values Pointer to a structure giving the values to use to generate the
466         * certificate (TBS and signature).
467         * @param[out] out_buf Pointer to a user-allocated buffer that will contain the
468         * result.
469         * @param[in,out] inout_size Pointer to an integer holding the size of
470         * the provided buffer, this value will be updated to reflect the actual size of
471         * the output.
472         * @return The result of the operation.
473         */
474        rom_error_t {fn_name}({fn_params_str});
475
476        "#
477        };
478        generated_code = codegen::Codegen::generate(
479            /* buf_name */ "out_buf",
480            /* buf_size_name */ "inout_size",
481            &get_var_info,
482            build,
483        )?;
484    }
485
486    let mut generate_fn_impl = String::new();
487    writeln!(
488        generate_fn_impl,
489        "rom_error_t {fn_name}({fn_params_str}) {{"
490    )?;
491    generate_fn_impl.push_str(&generated_code.code);
492    generate_fn_impl.push_str("  return kErrorOk;\n");
493    generate_fn_impl.push_str("}\n\n");
494    generated_code.code = generate_fn_impl;
495
496    Ok((generate_fn_def, generated_code))
497}
498
499// Decide whether a integer should use a special C type instead
500// of being represented by a big-endian byte array.
501fn c_integer_for_length(size: usize) -> Option<&'static str> {
502    match size {
503        4 => Some("uint32_t"),
504        _ => None,
505    }
506}
507
508// Return information about a variable (codegen info, definition in struct).
509fn c_variable_info(
510    name: &str,
511    struct_expr: &str,
512    var_type: &VariableType,
513) -> (VariableCodegenInfo, String) {
514    match var_type {
515        VariableType::ByteArray { .. } => {
516            if var_type.has_constant_array_size() {
517                (
518                    VariableCodegenInfo::Pointer {
519                        ptr_expr: format!("{struct_expr}{name}"),
520                        size_expr: format!("{}", var_type.size()),
521                    },
522                    indoc::formatdoc! {r#"
523                        // Pointer to an array of bytes.
524                        uint8_t *{name};
525                    "#
526                    },
527                )
528            } else {
529                (
530                    VariableCodegenInfo::Pointer {
531                        ptr_expr: format!("{struct_expr}{name}"),
532                        size_expr: format!("{struct_expr}{name}_size"),
533                    },
534                    indoc::formatdoc! {r#"
535                        // Pointer to an array of bytes.
536                        uint8_t *{name};
537                        // Size of this array in bytes.
538                        size_t {name}_size;
539                    "#
540                    },
541                )
542            }
543        }
544        VariableType::Integer { .. } => match c_integer_for_length(var_type.size()) {
545            Some(c_type) => (
546                VariableCodegenInfo::Uint32 {
547                    value_expr: format!("{struct_expr}{name}"),
548                },
549                format!("    {c_type} {name};\n"),
550            ),
551            None => {
552                if var_type.has_constant_array_size() {
553                    (
554                        VariableCodegenInfo::Pointer {
555                            ptr_expr: format!("{struct_expr}{name}"),
556                            size_expr: format!("{}", var_type.size()),
557                        },
558                        indoc::formatdoc! {r#"
559                            // Pointer to an unsigned big-endian in integer.
560                            uint8_t *{name};
561                        "#
562                        },
563                    )
564                } else {
565                    (
566                        VariableCodegenInfo::Pointer {
567                            ptr_expr: format!("{struct_expr}{name}"),
568                            size_expr: format!("{struct_expr}{name}_size"),
569                        },
570                        indoc::formatdoc! {r#"
571                            // Pointer to an unsigned big-endian in integer.
572                            uint8_t *{name};
573                            // Size of this array in bytes.
574                            size_t {name}_size;
575                        "#
576                        },
577                    )
578                }
579            }
580        },
581        VariableType::String { .. } => {
582            if var_type.has_constant_array_size() {
583                (
584                    VariableCodegenInfo::Pointer {
585                        ptr_expr: format!("{struct_expr}{name}"),
586                        size_expr: format!("{}", var_type.size()),
587                    },
588                    indoc::formatdoc! {r#"
589                        // Pointer to a (not necessarily zero-terminated) string.
590                        char *{name};
591                    "#
592                    },
593                )
594            } else {
595                (
596                    VariableCodegenInfo::Pointer {
597                        ptr_expr: format!("{struct_expr}{name}"),
598                        size_expr: format!("{struct_expr}{name}_len"),
599                    },
600                    indoc::formatdoc! {r#"
601                        // Pointer to a (not necessarily zero-terminated) string.
602                        char *{name};
603                        // Length of this string.
604                        size_t {name}_len;
605                    "#
606                    },
607                )
608            }
609        }
610        VariableType::Boolean => (
611            VariableCodegenInfo::Boolean {
612                value_expr: format!("{struct_expr}{name}"),
613            },
614            format!("bool {name};\n"),
615        ),
616    }
617}