blob: 19cb78cf6cebe8783fead5900ac07e9195d26fab [file] [log] [blame]
Gilles Peskine09940492021-01-26 22:16:30 +01001#!/usr/bin/env python3
2"""Generate test data for PSA cryptographic mechanisms.
3"""
4
5# Copyright The Mbed TLS Contributors
6# SPDX-License-Identifier: Apache-2.0
7#
8# Licensed under the Apache License, Version 2.0 (the "License"); you may
9# not use this file except in compliance with the License.
10# You may obtain a copy of the License at
11#
12# http://www.apache.org/licenses/LICENSE-2.0
13#
14# Unless required by applicable law or agreed to in writing, software
15# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
16# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17# See the License for the specific language governing permissions and
18# limitations under the License.
19
20import argparse
Gilles Peskine14e428f2021-01-26 22:19:21 +010021import os
22import re
Gilles Peskine09940492021-01-26 22:16:30 +010023import sys
Gilles Peskineaf172842021-01-27 18:24:48 +010024from typing import Iterable, List, Optional, TypeVar
Gilles Peskine09940492021-01-26 22:16:30 +010025
26import scripts_path # pylint: disable=unused-import
Gilles Peskine14e428f2021-01-26 22:19:21 +010027from mbedtls_dev import crypto_knowledge
Gilles Peskine09940492021-01-26 22:16:30 +010028from mbedtls_dev import macro_collector
Gilles Peskine14e428f2021-01-26 22:19:21 +010029from mbedtls_dev import test_case
Gilles Peskine09940492021-01-26 22:16:30 +010030
31T = TypeVar('T') #pylint: disable=invalid-name
32
Gilles Peskine14e428f2021-01-26 22:19:21 +010033
Gilles Peskine7f756872021-02-16 12:13:12 +010034def psa_want_symbol(name: str) -> str:
Gilles Peskineaf172842021-01-27 18:24:48 +010035 """Return the PSA_WANT_xxx symbol associated with a PSA crypto feature."""
36 if name.startswith('PSA_'):
37 return name[:4] + 'WANT_' + name[4:]
38 else:
39 raise ValueError('Unable to determine the PSA_WANT_ symbol for ' + name)
40
Gilles Peskine7f756872021-02-16 12:13:12 +010041def finish_family_dependency(dep: str, bits: int) -> str:
42 """Finish dep if it's a family dependency symbol prefix.
43
44 A family dependency symbol prefix is a PSA_WANT_ symbol that needs to be
45 qualified by the key size. If dep is such a symbol, finish it by adjusting
46 the prefix and appending the key size. Other symbols are left unchanged.
47 """
48 return re.sub(r'_FAMILY_(.*)', r'_\1_' + str(bits), dep)
49
50def finish_family_dependencies(dependencies: List[str], bits: int) -> List[str]:
51 """Finish any family dependency symbol prefixes.
52
53 Apply `finish_family_dependency` to each element of `dependencies`.
54 """
55 return [finish_family_dependency(dep, bits) for dep in dependencies]
Gilles Peskineaf172842021-01-27 18:24:48 +010056
57def test_case_for_key_type_not_supported(
58 verb: str, key_type: str, bits: int,
59 dependencies: List[str],
60 *args: str,
61 param_descr: str = '',
62) -> test_case.TestCase:
Gilles Peskine14e428f2021-01-26 22:19:21 +010063 """Return one test case exercising a key creation method
64 for an unsupported key type or size.
65 """
66 tc = test_case.TestCase()
Gilles Peskineaf172842021-01-27 18:24:48 +010067 short_key_type = re.sub(r'PSA_(KEY_TYPE|ECC_FAMILY)_', r'', key_type)
Gilles Peskine14e428f2021-01-26 22:19:21 +010068 adverb = 'not' if dependencies else 'never'
Gilles Peskineaf172842021-01-27 18:24:48 +010069 if param_descr:
70 adverb = param_descr + ' ' + adverb
Gilles Peskine14e428f2021-01-26 22:19:21 +010071 tc.set_description('PSA {} {} {}-bit {} supported'
Gilles Peskineaf172842021-01-27 18:24:48 +010072 .format(verb, short_key_type, bits, adverb))
Gilles Peskine14e428f2021-01-26 22:19:21 +010073 tc.set_dependencies(dependencies)
74 tc.set_function(verb + '_not_supported')
75 tc.set_arguments([key_type] + list(args))
76 return tc
77
Gilles Peskine09940492021-01-26 22:16:30 +010078class TestGenerator:
79 """Gather information and generate test data."""
80
81 def __init__(self, options):
82 self.test_suite_directory = self.get_option(options, 'directory',
83 'tests/suites')
84 self.constructors = self.read_psa_interface()
85
86 @staticmethod
87 def get_option(options, name: str, default: T) -> T:
88 value = getattr(options, name, None)
89 return default if value is None else value
90
91 @staticmethod
92 def remove_unwanted_macros(
93 constructors: macro_collector.PSAMacroCollector
94 ) -> None:
95 # Mbed TLS doesn't support DSA. Don't attempt to generate any related
96 # test case.
97 constructors.key_types.discard('PSA_KEY_TYPE_DSA_KEY_PAIR')
98 constructors.key_types.discard('PSA_KEY_TYPE_DSA_PUBLIC_KEY')
99 constructors.algorithms_from_hash.pop('PSA_ALG_DSA', None)
100 constructors.algorithms_from_hash.pop('PSA_ALG_DETERMINISTIC_DSA', None)
101
102 def read_psa_interface(self) -> macro_collector.PSAMacroCollector:
103 """Return the list of known key types, algorithms, etc."""
104 constructors = macro_collector.PSAMacroCollector()
105 header_file_names = ['include/psa/crypto_values.h',
106 'include/psa/crypto_extra.h']
107 for header_file_name in header_file_names:
108 with open(header_file_name, 'rb') as header_file:
109 constructors.read_file(header_file)
110 self.remove_unwanted_macros(constructors)
111 return constructors
112
Gilles Peskine14e428f2021-01-26 22:19:21 +0100113 def write_test_data_file(self, basename: str,
114 test_cases: Iterable[test_case.TestCase]) -> None:
115 """Write the test cases to a .data file.
116
117 The output file is ``basename + '.data'`` in the test suite directory.
118 """
119 filename = os.path.join(self.test_suite_directory, basename + '.data')
120 test_case.write_data_file(filename, test_cases)
121
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100122 ALWAYS_SUPPORTED = frozenset([
123 'PSA_KEY_TYPE_DERIVE',
124 'PSA_KEY_TYPE_RAW_DATA',
125 ])
Gilles Peskine14e428f2021-01-26 22:19:21 +0100126 def test_cases_for_key_type_not_supported(
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100127 self,
Gilles Peskineaf172842021-01-27 18:24:48 +0100128 kt: crypto_knowledge.KeyType,
129 param: Optional[int] = None,
130 param_descr: str = '',
Gilles Peskine14e428f2021-01-26 22:19:21 +0100131 ) -> List[test_case.TestCase]:
Gilles Peskineaf172842021-01-27 18:24:48 +0100132 """Return test cases exercising key creation when the given type is unsupported.
133
134 If param is present and not None, emit test cases conditioned on this
135 parameter not being supported. If it is absent or None, emit test cases
136 conditioned on the base type not being supported.
137 """
Gilles Peskine60b29fe2021-02-16 14:06:50 +0100138 if kt.name in self.ALWAYS_SUPPORTED:
139 # Don't generate test cases for key types that are always supported.
140 # They would be skipped in all configurations, which is noise.
Gilles Peskine14e428f2021-01-26 22:19:21 +0100141 return []
Gilles Peskineaf172842021-01-27 18:24:48 +0100142 import_dependencies = [('!' if param is None else '') +
143 psa_want_symbol(kt.name)]
144 if kt.params is not None:
145 import_dependencies += [('!' if param == i else '') +
146 psa_want_symbol(sym)
147 for i, sym in enumerate(kt.params)]
Gilles Peskine14e428f2021-01-26 22:19:21 +0100148 if kt.name.endswith('_PUBLIC_KEY'):
149 generate_dependencies = []
150 else:
151 generate_dependencies = import_dependencies
152 test_cases = []
153 for bits in kt.sizes_to_test():
154 test_cases.append(test_case_for_key_type_not_supported(
Gilles Peskine7f756872021-02-16 12:13:12 +0100155 'import', kt.expression, bits,
156 finish_family_dependencies(import_dependencies, bits),
Gilles Peskineaf172842021-01-27 18:24:48 +0100157 test_case.hex_string(kt.key_material(bits)),
158 param_descr=param_descr,
Gilles Peskine14e428f2021-01-26 22:19:21 +0100159 ))
Gilles Peskineaf172842021-01-27 18:24:48 +0100160 if not generate_dependencies and param is not None:
161 # If generation is impossible for this key type, rather than
162 # supported or not depending on implementation capabilities,
163 # only generate the test case once.
164 continue
Gilles Peskine14e428f2021-01-26 22:19:21 +0100165 test_cases.append(test_case_for_key_type_not_supported(
Gilles Peskine7f756872021-02-16 12:13:12 +0100166 'generate', kt.expression, bits,
167 finish_family_dependencies(generate_dependencies, bits),
Gilles Peskineaf172842021-01-27 18:24:48 +0100168 str(bits),
169 param_descr=param_descr,
Gilles Peskine14e428f2021-01-26 22:19:21 +0100170 ))
171 # To be added: derive
172 return test_cases
173
174 def generate_not_supported(self) -> None:
175 """Generate test cases that exercise the creation of keys of unsupported types."""
176 test_cases = []
177 for key_type in sorted(self.constructors.key_types):
178 kt = crypto_knowledge.KeyType(key_type)
179 test_cases += self.test_cases_for_key_type_not_supported(kt)
Gilles Peskineaf172842021-01-27 18:24:48 +0100180 # To be added: parametrized key types FFDH
181 for curve_family in sorted(self.constructors.ecc_curves):
182 for constr in ('PSA_KEY_TYPE_ECC_KEY_PAIR',
183 'PSA_KEY_TYPE_ECC_PUBLIC_KEY'):
184 kt = crypto_knowledge.KeyType(constr, [curve_family])
185 test_cases += self.test_cases_for_key_type_not_supported(
186 kt, param_descr='type')
187 test_cases += self.test_cases_for_key_type_not_supported(
188 kt, 0, param_descr='curve')
Gilles Peskine14e428f2021-01-26 22:19:21 +0100189 self.write_test_data_file(
190 'test_suite_psa_crypto_not_supported.generated',
191 test_cases)
192
193 def generate_all(self):
194 self.generate_not_supported()
195
Gilles Peskine09940492021-01-26 22:16:30 +0100196def main(args):
197 """Command line entry point."""
198 parser = argparse.ArgumentParser(description=__doc__)
199 options = parser.parse_args(args)
200 generator = TestGenerator(options)
Gilles Peskine14e428f2021-01-26 22:19:21 +0100201 generator.generate_all()
Gilles Peskine09940492021-01-26 22:16:30 +0100202
203if __name__ == '__main__':
204 main(sys.argv[1:])