blob: b68713b1ec5a250382d7f3dcbaabc30ac7c89c16 [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 Peskineaf172842021-01-27 18:24:48 +010034def psa_want_symbol(name):
35 """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
41
42def test_case_for_key_type_not_supported(
43 verb: str, key_type: str, bits: int,
44 dependencies: List[str],
45 *args: str,
46 param_descr: str = '',
47) -> test_case.TestCase:
Gilles Peskine14e428f2021-01-26 22:19:21 +010048 """Return one test case exercising a key creation method
49 for an unsupported key type or size.
50 """
51 tc = test_case.TestCase()
Gilles Peskineaf172842021-01-27 18:24:48 +010052 short_key_type = re.sub(r'PSA_(KEY_TYPE|ECC_FAMILY)_', r'', key_type)
Gilles Peskine14e428f2021-01-26 22:19:21 +010053 adverb = 'not' if dependencies else 'never'
Gilles Peskineaf172842021-01-27 18:24:48 +010054 if param_descr:
55 adverb = param_descr + ' ' + adverb
Gilles Peskine14e428f2021-01-26 22:19:21 +010056 tc.set_description('PSA {} {} {}-bit {} supported'
Gilles Peskineaf172842021-01-27 18:24:48 +010057 .format(verb, short_key_type, bits, adverb))
Gilles Peskine14e428f2021-01-26 22:19:21 +010058 tc.set_dependencies(dependencies)
59 tc.set_function(verb + '_not_supported')
60 tc.set_arguments([key_type] + list(args))
61 return tc
62
Gilles Peskine09940492021-01-26 22:16:30 +010063class TestGenerator:
64 """Gather information and generate test data."""
65
66 def __init__(self, options):
67 self.test_suite_directory = self.get_option(options, 'directory',
68 'tests/suites')
69 self.constructors = self.read_psa_interface()
70
71 @staticmethod
72 def get_option(options, name: str, default: T) -> T:
73 value = getattr(options, name, None)
74 return default if value is None else value
75
76 @staticmethod
77 def remove_unwanted_macros(
78 constructors: macro_collector.PSAMacroCollector
79 ) -> None:
80 # Mbed TLS doesn't support DSA. Don't attempt to generate any related
81 # test case.
82 constructors.key_types.discard('PSA_KEY_TYPE_DSA_KEY_PAIR')
83 constructors.key_types.discard('PSA_KEY_TYPE_DSA_PUBLIC_KEY')
84 constructors.algorithms_from_hash.pop('PSA_ALG_DSA', None)
85 constructors.algorithms_from_hash.pop('PSA_ALG_DETERMINISTIC_DSA', None)
86
87 def read_psa_interface(self) -> macro_collector.PSAMacroCollector:
88 """Return the list of known key types, algorithms, etc."""
89 constructors = macro_collector.PSAMacroCollector()
90 header_file_names = ['include/psa/crypto_values.h',
91 'include/psa/crypto_extra.h']
92 for header_file_name in header_file_names:
93 with open(header_file_name, 'rb') as header_file:
94 constructors.read_file(header_file)
95 self.remove_unwanted_macros(constructors)
96 return constructors
97
Gilles Peskine14e428f2021-01-26 22:19:21 +010098 def write_test_data_file(self, basename: str,
99 test_cases: Iterable[test_case.TestCase]) -> None:
100 """Write the test cases to a .data file.
101
102 The output file is ``basename + '.data'`` in the test suite directory.
103 """
104 filename = os.path.join(self.test_suite_directory, basename + '.data')
105 test_case.write_data_file(filename, test_cases)
106
107 @staticmethod
108 def test_cases_for_key_type_not_supported(
Gilles Peskineaf172842021-01-27 18:24:48 +0100109 kt: crypto_knowledge.KeyType,
110 param: Optional[int] = None,
111 param_descr: str = '',
Gilles Peskine14e428f2021-01-26 22:19:21 +0100112 ) -> List[test_case.TestCase]:
Gilles Peskineaf172842021-01-27 18:24:48 +0100113 """Return test cases exercising key creation when the given type is unsupported.
114
115 If param is present and not None, emit test cases conditioned on this
116 parameter not being supported. If it is absent or None, emit test cases
117 conditioned on the base type not being supported.
118 """
Gilles Peskine14e428f2021-01-26 22:19:21 +0100119 if kt.name == 'PSA_KEY_TYPE_RAW_DATA':
120 # This key type is always supported.
121 return []
Gilles Peskineaf172842021-01-27 18:24:48 +0100122 import_dependencies = [('!' if param is None else '') +
123 psa_want_symbol(kt.name)]
124 if kt.params is not None:
125 import_dependencies += [('!' if param == i else '') +
126 psa_want_symbol(sym)
127 for i, sym in enumerate(kt.params)]
Gilles Peskine14e428f2021-01-26 22:19:21 +0100128 if kt.name.endswith('_PUBLIC_KEY'):
129 generate_dependencies = []
130 else:
131 generate_dependencies = import_dependencies
132 test_cases = []
133 for bits in kt.sizes_to_test():
134 test_cases.append(test_case_for_key_type_not_supported(
Gilles Peskineaf172842021-01-27 18:24:48 +0100135 'import', kt.expression, bits, import_dependencies,
136 test_case.hex_string(kt.key_material(bits)),
137 param_descr=param_descr,
Gilles Peskine14e428f2021-01-26 22:19:21 +0100138 ))
Gilles Peskineaf172842021-01-27 18:24:48 +0100139 if not generate_dependencies and param is not None:
140 # If generation is impossible for this key type, rather than
141 # supported or not depending on implementation capabilities,
142 # only generate the test case once.
143 continue
Gilles Peskine14e428f2021-01-26 22:19:21 +0100144 test_cases.append(test_case_for_key_type_not_supported(
Gilles Peskineaf172842021-01-27 18:24:48 +0100145 'generate', kt.expression, bits, generate_dependencies,
146 str(bits),
147 param_descr=param_descr,
Gilles Peskine14e428f2021-01-26 22:19:21 +0100148 ))
149 # To be added: derive
150 return test_cases
151
152 def generate_not_supported(self) -> None:
153 """Generate test cases that exercise the creation of keys of unsupported types."""
154 test_cases = []
155 for key_type in sorted(self.constructors.key_types):
156 kt = crypto_knowledge.KeyType(key_type)
157 test_cases += self.test_cases_for_key_type_not_supported(kt)
Gilles Peskineaf172842021-01-27 18:24:48 +0100158 # To be added: parametrized key types FFDH
159 for curve_family in sorted(self.constructors.ecc_curves):
160 for constr in ('PSA_KEY_TYPE_ECC_KEY_PAIR',
161 'PSA_KEY_TYPE_ECC_PUBLIC_KEY'):
162 kt = crypto_knowledge.KeyType(constr, [curve_family])
163 test_cases += self.test_cases_for_key_type_not_supported(
164 kt, param_descr='type')
165 test_cases += self.test_cases_for_key_type_not_supported(
166 kt, 0, param_descr='curve')
Gilles Peskine14e428f2021-01-26 22:19:21 +0100167 self.write_test_data_file(
168 'test_suite_psa_crypto_not_supported.generated',
169 test_cases)
170
171 def generate_all(self):
172 self.generate_not_supported()
173
Gilles Peskine09940492021-01-26 22:16:30 +0100174def main(args):
175 """Command line entry point."""
176 parser = argparse.ArgumentParser(description=__doc__)
177 options = parser.parse_args(args)
178 generator = TestGenerator(options)
Gilles Peskine14e428f2021-01-26 22:19:21 +0100179 generator.generate_all()
Gilles Peskine09940492021-01-26 22:16:30 +0100180
181if __name__ == '__main__':
182 main(sys.argv[1:])