blob: 4036ee38fa4c5344271bb68dd699b440c21c4d4d [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
122 @staticmethod
123 def test_cases_for_key_type_not_supported(
Gilles Peskineaf172842021-01-27 18:24:48 +0100124 kt: crypto_knowledge.KeyType,
125 param: Optional[int] = None,
126 param_descr: str = '',
Gilles Peskine14e428f2021-01-26 22:19:21 +0100127 ) -> List[test_case.TestCase]:
Gilles Peskineaf172842021-01-27 18:24:48 +0100128 """Return test cases exercising key creation when the given type is unsupported.
129
130 If param is present and not None, emit test cases conditioned on this
131 parameter not being supported. If it is absent or None, emit test cases
132 conditioned on the base type not being supported.
133 """
Gilles Peskine14e428f2021-01-26 22:19:21 +0100134 if kt.name == 'PSA_KEY_TYPE_RAW_DATA':
135 # This key type is always supported.
136 return []
Gilles Peskineaf172842021-01-27 18:24:48 +0100137 import_dependencies = [('!' if param is None else '') +
138 psa_want_symbol(kt.name)]
139 if kt.params is not None:
140 import_dependencies += [('!' if param == i else '') +
141 psa_want_symbol(sym)
142 for i, sym in enumerate(kt.params)]
Gilles Peskine14e428f2021-01-26 22:19:21 +0100143 if kt.name.endswith('_PUBLIC_KEY'):
144 generate_dependencies = []
145 else:
146 generate_dependencies = import_dependencies
147 test_cases = []
148 for bits in kt.sizes_to_test():
149 test_cases.append(test_case_for_key_type_not_supported(
Gilles Peskine7f756872021-02-16 12:13:12 +0100150 'import', kt.expression, bits,
151 finish_family_dependencies(import_dependencies, bits),
Gilles Peskineaf172842021-01-27 18:24:48 +0100152 test_case.hex_string(kt.key_material(bits)),
153 param_descr=param_descr,
Gilles Peskine14e428f2021-01-26 22:19:21 +0100154 ))
Gilles Peskineaf172842021-01-27 18:24:48 +0100155 if not generate_dependencies and param is not None:
156 # If generation is impossible for this key type, rather than
157 # supported or not depending on implementation capabilities,
158 # only generate the test case once.
159 continue
Gilles Peskine14e428f2021-01-26 22:19:21 +0100160 test_cases.append(test_case_for_key_type_not_supported(
Gilles Peskine7f756872021-02-16 12:13:12 +0100161 'generate', kt.expression, bits,
162 finish_family_dependencies(generate_dependencies, bits),
Gilles Peskineaf172842021-01-27 18:24:48 +0100163 str(bits),
164 param_descr=param_descr,
Gilles Peskine14e428f2021-01-26 22:19:21 +0100165 ))
166 # To be added: derive
167 return test_cases
168
169 def generate_not_supported(self) -> None:
170 """Generate test cases that exercise the creation of keys of unsupported types."""
171 test_cases = []
172 for key_type in sorted(self.constructors.key_types):
173 kt = crypto_knowledge.KeyType(key_type)
174 test_cases += self.test_cases_for_key_type_not_supported(kt)
Gilles Peskineaf172842021-01-27 18:24:48 +0100175 # To be added: parametrized key types FFDH
176 for curve_family in sorted(self.constructors.ecc_curves):
177 for constr in ('PSA_KEY_TYPE_ECC_KEY_PAIR',
178 'PSA_KEY_TYPE_ECC_PUBLIC_KEY'):
179 kt = crypto_knowledge.KeyType(constr, [curve_family])
180 test_cases += self.test_cases_for_key_type_not_supported(
181 kt, param_descr='type')
182 test_cases += self.test_cases_for_key_type_not_supported(
183 kt, 0, param_descr='curve')
Gilles Peskine14e428f2021-01-26 22:19:21 +0100184 self.write_test_data_file(
185 'test_suite_psa_crypto_not_supported.generated',
186 test_cases)
187
188 def generate_all(self):
189 self.generate_not_supported()
190
Gilles Peskine09940492021-01-26 22:16:30 +0100191def main(args):
192 """Command line entry point."""
193 parser = argparse.ArgumentParser(description=__doc__)
194 options = parser.parse_args(args)
195 generator = TestGenerator(options)
Gilles Peskine14e428f2021-01-26 22:19:21 +0100196 generator.generate_all()
Gilles Peskine09940492021-01-26 22:16:30 +0100197
198if __name__ == '__main__':
199 main(sys.argv[1:])