ot_certs/template/
testgen.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 provides functionality to generate substitute data for template
6//! to test corner cases of the certificate generator.
7
8use anyhow::{Result, ensure};
9use rand::distributions::{DistString, Distribution, Uniform};
10
11use openssl::bn::{BigNum, BigNumContext};
12use openssl::ec::{EcGroup, EcKey};
13use openssl::nid::Nid;
14use openssl::pkey::Private;
15
16use crate::template::subst::{SubstData, SubstValue};
17use crate::template::{EcCurve, EcPublicKeyInfo, SubjectPublicKeyInfo, Template, Value, Variable};
18
19// Convert a template curve name to an openssl one.
20fn ecgroup_from_curve(curve: &EcCurve) -> EcGroup {
21    match curve {
22        EcCurve::Prime256v1 => EcGroup::from_curve_name(Nid::X9_62_PRIME256V1).unwrap(),
23    }
24}
25
26impl Template {
27    /// Generate a random set of data to substitute in the template to fill all the values.
28    /// This generator will make sure to generate valid values for the public key if possible.
29    pub fn random_test(&self) -> Result<SubstData> {
30        let mut data = SubstData::new();
31        for (var, var_type) in &self.variables {
32            let val = match *var_type {
33                super::VariableType::ByteArray { .. } => {
34                    SubstValue::ByteArray(self.random_bytes(var_type))
35                }
36                super::VariableType::String { .. } => {
37                    let size = self.random_size(var_type);
38                    let s = rand::distributions::Alphanumeric
39                        .sample_string(&mut rand::thread_rng(), size);
40                    SubstValue::String(s)
41                }
42                super::VariableType::Integer { .. } => {
43                    if var_type.size() == 4 {
44                        // FIXME the code does not properly distinguish between signed and
45                        // unsigned integers so we must generate positive numbers for now.
46                        let value = self.random_bytes(var_type);
47                        let value = u32::from_be_bytes(value.try_into().unwrap());
48
49                        SubstValue::Uint32(value)
50                    } else {
51                        SubstValue::ByteArray(self.random_bytes(var_type))
52                    }
53                }
54                super::VariableType::Boolean => SubstValue::Boolean(rand::random::<bool>()),
55            };
56            data.values.insert(var.to_string(), val);
57        }
58        // We want to make sure that we generate valid dates, otherwise tools like
59        // openssl might fail to parse the certificate.
60        if let Value::Variable(Variable { name, .. }) = &self.certificate.not_before {
61            data.values.insert(name.clone(), self.random_time());
62        }
63        if let Value::Variable(Variable { name, .. }) = &self.certificate.not_after {
64            data.values.insert(name.clone(), self.random_time());
65        }
66        // We want to make sure that we generate a valid public key, otherwise
67        // tools like openssl might fail to parse the certificate.
68        for (key, val) in self.random_public_key()?.values {
69            data.values.insert(key, val);
70        }
71        Ok(data)
72    }
73
74    /// Sample a random array size for this variable.
75    fn random_size(&self, var_type: &super::VariableType) -> usize {
76        let (min_size, max_size) = var_type.array_size();
77        if min_size == max_size {
78            min_size
79        } else {
80            Uniform::from(min_size..max_size + 1).sample(&mut rand::thread_rng())
81        }
82    }
83
84    /// Sample a random byte string for this variable.
85    /// The generated values follow the size guarantees specified in the
86    /// hjson template.
87    fn random_bytes(&self, var_type: &super::VariableType) -> Vec<u8> {
88        let size = self.random_size(var_type);
89        let mut bytes = (0..size).map(|_| rand::random::<u8>()).collect::<Vec<_>>();
90
91        if var_type.use_msb_tweak() {
92            bytes[0] |= 0x80;
93        } else if matches!(var_type, super::VariableType::Integer { .. }) {
94            let (min_int_size, _) = var_type.int_size(0);
95            if min_int_size > 0 {
96                bytes[size - min_int_size] |= 0x1;
97            }
98        }
99        bytes
100    }
101
102    fn random_time(&self) -> SubstValue {
103        // The smallest possible valid GeneralizedTime is YYYYMMDDHHMMSSZ
104        let mut rng = rand::thread_rng();
105        let year = Uniform::from(1900..2100).sample(&mut rng);
106        let month = Uniform::from(1..13).sample(&mut rng);
107        let day = Uniform::from(1..29).sample(&mut rng);
108        let hour = Uniform::from(0..24).sample(&mut rng);
109        let minute = Uniform::from(0..60).sample(&mut rng);
110        let sec = Uniform::from(0..60).sample(&mut rng);
111        let time = format!("{year:04}{month:02}{day:02}{hour:02}{minute:02}{sec:02}Z");
112        SubstValue::String(time)
113    }
114
115    fn random_public_key(&self) -> Result<SubstData> {
116        match &self.certificate.subject_public_key_info {
117            SubjectPublicKeyInfo::EcPublicKey(ec) => Self::random_ec_public_key(ec),
118        }
119    }
120
121    fn random_ec_public_key(ec_pubkey: &EcPublicKeyInfo) -> Result<SubstData> {
122        // Generate a public key using openssl.
123        let group = ecgroup_from_curve(&ec_pubkey.curve);
124        let privkey = EcKey::<Private>::generate(&group)?;
125        let mut ctx = BigNumContext::new()?;
126        let mut x = BigNum::new()?;
127        let mut y = BigNum::new()?;
128        let nbytes: i32 = group.degree().div_ceil(8) as i32;
129        privkey
130            .public_key()
131            .affine_coordinates(&group, &mut x, &mut y, &mut ctx)?;
132        let mut data = SubstData::new();
133        // If any of the coordinates is a variable, create a substitution for it.
134        if let Value::Variable(Variable { name, convert }) = &ec_pubkey.public_key.x {
135            ensure!(
136                matches!(convert, None | Some(super::Conversion::BigEndian)),
137                "cannot generate a random public key if 'x' a variable with invalid conversion"
138            );
139            data.values.insert(
140                name.clone(),
141                SubstValue::ByteArray(x.to_vec_padded(nbytes)?),
142            );
143        }
144        if let Value::Variable(Variable { name, convert }) = &ec_pubkey.public_key.y {
145            ensure!(
146                matches!(convert, None | Some(super::Conversion::BigEndian)),
147                "cannot generate a random public key if 'y' a variable with invalid conversion"
148            );
149            data.values.insert(
150                name.clone(),
151                SubstValue::ByteArray(y.to_vec_padded(nbytes)?),
152            );
153        }
154        Ok(data)
155    }
156}