1a8e1175bSopenharmony_ci"""Collect macro definitions from header files. 2a8e1175bSopenharmony_ci""" 3a8e1175bSopenharmony_ci 4a8e1175bSopenharmony_ci# Copyright The Mbed TLS Contributors 5a8e1175bSopenharmony_ci# SPDX-License-Identifier: Apache-2.0 6a8e1175bSopenharmony_ci# 7a8e1175bSopenharmony_ci# Licensed under the Apache License, Version 2.0 (the "License"); you may 8a8e1175bSopenharmony_ci# not use this file except in compliance with the License. 9a8e1175bSopenharmony_ci# You may obtain a copy of the License at 10a8e1175bSopenharmony_ci# 11a8e1175bSopenharmony_ci# http://www.apache.org/licenses/LICENSE-2.0 12a8e1175bSopenharmony_ci# 13a8e1175bSopenharmony_ci# Unless required by applicable law or agreed to in writing, software 14a8e1175bSopenharmony_ci# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 15a8e1175bSopenharmony_ci# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16a8e1175bSopenharmony_ci# See the License for the specific language governing permissions and 17a8e1175bSopenharmony_ci# limitations under the License. 18a8e1175bSopenharmony_ci 19a8e1175bSopenharmony_ciimport itertools 20a8e1175bSopenharmony_ciimport re 21a8e1175bSopenharmony_cifrom typing import Dict, IO, Iterable, Iterator, List, Optional, Pattern, Set, Tuple, Union 22a8e1175bSopenharmony_ci 23a8e1175bSopenharmony_ci 24a8e1175bSopenharmony_ciclass ReadFileLineException(Exception): 25a8e1175bSopenharmony_ci def __init__(self, filename: str, line_number: Union[int, str]) -> None: 26a8e1175bSopenharmony_ci message = 'in {} at {}'.format(filename, line_number) 27a8e1175bSopenharmony_ci super(ReadFileLineException, self).__init__(message) 28a8e1175bSopenharmony_ci self.filename = filename 29a8e1175bSopenharmony_ci self.line_number = line_number 30a8e1175bSopenharmony_ci 31a8e1175bSopenharmony_ci 32a8e1175bSopenharmony_ciclass read_file_lines: 33a8e1175bSopenharmony_ci # Dear Pylint, conventionally, a context manager class name is lowercase. 34a8e1175bSopenharmony_ci # pylint: disable=invalid-name,too-few-public-methods 35a8e1175bSopenharmony_ci """Context manager to read a text file line by line. 36a8e1175bSopenharmony_ci 37a8e1175bSopenharmony_ci ``` 38a8e1175bSopenharmony_ci with read_file_lines(filename) as lines: 39a8e1175bSopenharmony_ci for line in lines: 40a8e1175bSopenharmony_ci process(line) 41a8e1175bSopenharmony_ci ``` 42a8e1175bSopenharmony_ci is equivalent to 43a8e1175bSopenharmony_ci ``` 44a8e1175bSopenharmony_ci with open(filename, 'r') as input_file: 45a8e1175bSopenharmony_ci for line in input_file: 46a8e1175bSopenharmony_ci process(line) 47a8e1175bSopenharmony_ci ``` 48a8e1175bSopenharmony_ci except that if process(line) raises an exception, then the read_file_lines 49a8e1175bSopenharmony_ci snippet annotates the exception with the file name and line number. 50a8e1175bSopenharmony_ci """ 51a8e1175bSopenharmony_ci def __init__(self, filename: str, binary: bool = False) -> None: 52a8e1175bSopenharmony_ci self.filename = filename 53a8e1175bSopenharmony_ci self.file = None #type: Optional[IO[str]] 54a8e1175bSopenharmony_ci self.line_number = 'entry' #type: Union[int, str] 55a8e1175bSopenharmony_ci self.generator = None #type: Optional[Iterable[Tuple[int, str]]] 56a8e1175bSopenharmony_ci self.binary = binary 57a8e1175bSopenharmony_ci def __enter__(self) -> 'read_file_lines': 58a8e1175bSopenharmony_ci self.file = open(self.filename, 'rb' if self.binary else 'r') 59a8e1175bSopenharmony_ci self.generator = enumerate(self.file) 60a8e1175bSopenharmony_ci return self 61a8e1175bSopenharmony_ci def __iter__(self) -> Iterator[str]: 62a8e1175bSopenharmony_ci assert self.generator is not None 63a8e1175bSopenharmony_ci for line_number, content in self.generator: 64a8e1175bSopenharmony_ci self.line_number = line_number 65a8e1175bSopenharmony_ci yield content 66a8e1175bSopenharmony_ci self.line_number = 'exit' 67a8e1175bSopenharmony_ci def __exit__(self, exc_type, exc_value, exc_traceback) -> None: 68a8e1175bSopenharmony_ci if self.file is not None: 69a8e1175bSopenharmony_ci self.file.close() 70a8e1175bSopenharmony_ci if exc_type is not None: 71a8e1175bSopenharmony_ci raise ReadFileLineException(self.filename, self.line_number) \ 72a8e1175bSopenharmony_ci from exc_value 73a8e1175bSopenharmony_ci 74a8e1175bSopenharmony_ci 75a8e1175bSopenharmony_ciclass PSAMacroEnumerator: 76a8e1175bSopenharmony_ci """Information about constructors of various PSA Crypto types. 77a8e1175bSopenharmony_ci 78a8e1175bSopenharmony_ci This includes macro names as well as information about their arguments 79a8e1175bSopenharmony_ci when applicable. 80a8e1175bSopenharmony_ci 81a8e1175bSopenharmony_ci This class only provides ways to enumerate expressions that evaluate to 82a8e1175bSopenharmony_ci values of the covered types. Derived classes are expected to populate 83a8e1175bSopenharmony_ci the set of known constructors of each kind, as well as populate 84a8e1175bSopenharmony_ci `self.arguments_for` for arguments that are not of a kind that is 85a8e1175bSopenharmony_ci enumerated here. 86a8e1175bSopenharmony_ci """ 87a8e1175bSopenharmony_ci #pylint: disable=too-many-instance-attributes 88a8e1175bSopenharmony_ci 89a8e1175bSopenharmony_ci def __init__(self) -> None: 90a8e1175bSopenharmony_ci """Set up an empty set of known constructor macros. 91a8e1175bSopenharmony_ci """ 92a8e1175bSopenharmony_ci self.statuses = set() #type: Set[str] 93a8e1175bSopenharmony_ci self.lifetimes = set() #type: Set[str] 94a8e1175bSopenharmony_ci self.locations = set() #type: Set[str] 95a8e1175bSopenharmony_ci self.persistence_levels = set() #type: Set[str] 96a8e1175bSopenharmony_ci self.algorithms = set() #type: Set[str] 97a8e1175bSopenharmony_ci self.ecc_curves = set() #type: Set[str] 98a8e1175bSopenharmony_ci self.dh_groups = set() #type: Set[str] 99a8e1175bSopenharmony_ci self.key_types = set() #type: Set[str] 100a8e1175bSopenharmony_ci self.key_usage_flags = set() #type: Set[str] 101a8e1175bSopenharmony_ci self.hash_algorithms = set() #type: Set[str] 102a8e1175bSopenharmony_ci self.mac_algorithms = set() #type: Set[str] 103a8e1175bSopenharmony_ci self.ka_algorithms = set() #type: Set[str] 104a8e1175bSopenharmony_ci self.kdf_algorithms = set() #type: Set[str] 105a8e1175bSopenharmony_ci self.pake_algorithms = set() #type: Set[str] 106a8e1175bSopenharmony_ci self.aead_algorithms = set() #type: Set[str] 107a8e1175bSopenharmony_ci self.sign_algorithms = set() #type: Set[str] 108a8e1175bSopenharmony_ci # macro name -> list of argument names 109a8e1175bSopenharmony_ci self.argspecs = {} #type: Dict[str, List[str]] 110a8e1175bSopenharmony_ci # argument name -> list of values 111a8e1175bSopenharmony_ci self.arguments_for = { 112a8e1175bSopenharmony_ci 'mac_length': [], 113a8e1175bSopenharmony_ci 'min_mac_length': [], 114a8e1175bSopenharmony_ci 'tag_length': [], 115a8e1175bSopenharmony_ci 'min_tag_length': [], 116a8e1175bSopenharmony_ci } #type: Dict[str, List[str]] 117a8e1175bSopenharmony_ci # Whether to include intermediate macros in enumerations. Intermediate 118a8e1175bSopenharmony_ci # macros serve as category headers and are not valid values of their 119a8e1175bSopenharmony_ci # type. See `is_internal_name`. 120a8e1175bSopenharmony_ci # Always false in this class, may be set to true in derived classes. 121a8e1175bSopenharmony_ci self.include_intermediate = False 122a8e1175bSopenharmony_ci 123a8e1175bSopenharmony_ci def is_internal_name(self, name: str) -> bool: 124a8e1175bSopenharmony_ci """Whether this is an internal macro. Internal macros will be skipped.""" 125a8e1175bSopenharmony_ci if not self.include_intermediate: 126a8e1175bSopenharmony_ci if name.endswith('_BASE') or name.endswith('_NONE'): 127a8e1175bSopenharmony_ci return True 128a8e1175bSopenharmony_ci if '_CATEGORY_' in name: 129a8e1175bSopenharmony_ci return True 130a8e1175bSopenharmony_ci return name.endswith('_FLAG') or name.endswith('_MASK') 131a8e1175bSopenharmony_ci 132a8e1175bSopenharmony_ci def gather_arguments(self) -> None: 133a8e1175bSopenharmony_ci """Populate the list of values for macro arguments. 134a8e1175bSopenharmony_ci 135a8e1175bSopenharmony_ci Call this after parsing all the inputs. 136a8e1175bSopenharmony_ci """ 137a8e1175bSopenharmony_ci self.arguments_for['hash_alg'] = sorted(self.hash_algorithms) 138a8e1175bSopenharmony_ci self.arguments_for['mac_alg'] = sorted(self.mac_algorithms) 139a8e1175bSopenharmony_ci self.arguments_for['ka_alg'] = sorted(self.ka_algorithms) 140a8e1175bSopenharmony_ci self.arguments_for['kdf_alg'] = sorted(self.kdf_algorithms) 141a8e1175bSopenharmony_ci self.arguments_for['aead_alg'] = sorted(self.aead_algorithms) 142a8e1175bSopenharmony_ci self.arguments_for['sign_alg'] = sorted(self.sign_algorithms) 143a8e1175bSopenharmony_ci self.arguments_for['curve'] = sorted(self.ecc_curves) 144a8e1175bSopenharmony_ci self.arguments_for['group'] = sorted(self.dh_groups) 145a8e1175bSopenharmony_ci self.arguments_for['persistence'] = sorted(self.persistence_levels) 146a8e1175bSopenharmony_ci self.arguments_for['location'] = sorted(self.locations) 147a8e1175bSopenharmony_ci self.arguments_for['lifetime'] = sorted(self.lifetimes) 148a8e1175bSopenharmony_ci 149a8e1175bSopenharmony_ci @staticmethod 150a8e1175bSopenharmony_ci def _format_arguments(name: str, arguments: Iterable[str]) -> str: 151a8e1175bSopenharmony_ci """Format a macro call with arguments. 152a8e1175bSopenharmony_ci 153a8e1175bSopenharmony_ci The resulting format is consistent with 154a8e1175bSopenharmony_ci `InputsForTest.normalize_argument`. 155a8e1175bSopenharmony_ci """ 156a8e1175bSopenharmony_ci return name + '(' + ', '.join(arguments) + ')' 157a8e1175bSopenharmony_ci 158a8e1175bSopenharmony_ci _argument_split_re = re.compile(r' *, *') 159a8e1175bSopenharmony_ci @classmethod 160a8e1175bSopenharmony_ci def _argument_split(cls, arguments: str) -> List[str]: 161a8e1175bSopenharmony_ci return re.split(cls._argument_split_re, arguments) 162a8e1175bSopenharmony_ci 163a8e1175bSopenharmony_ci def distribute_arguments(self, name: str) -> Iterator[str]: 164a8e1175bSopenharmony_ci """Generate macro calls with each tested argument set. 165a8e1175bSopenharmony_ci 166a8e1175bSopenharmony_ci If name is a macro without arguments, just yield "name". 167a8e1175bSopenharmony_ci If name is a macro with arguments, yield a series of 168a8e1175bSopenharmony_ci "name(arg1,...,argN)" where each argument takes each possible 169a8e1175bSopenharmony_ci value at least once. 170a8e1175bSopenharmony_ci """ 171a8e1175bSopenharmony_ci try: 172a8e1175bSopenharmony_ci if name not in self.argspecs: 173a8e1175bSopenharmony_ci yield name 174a8e1175bSopenharmony_ci return 175a8e1175bSopenharmony_ci argspec = self.argspecs[name] 176a8e1175bSopenharmony_ci if argspec == []: 177a8e1175bSopenharmony_ci yield name + '()' 178a8e1175bSopenharmony_ci return 179a8e1175bSopenharmony_ci argument_lists = [self.arguments_for[arg] for arg in argspec] 180a8e1175bSopenharmony_ci arguments = [values[0] for values in argument_lists] 181a8e1175bSopenharmony_ci yield self._format_arguments(name, arguments) 182a8e1175bSopenharmony_ci # Dear Pylint, enumerate won't work here since we're modifying 183a8e1175bSopenharmony_ci # the array. 184a8e1175bSopenharmony_ci # pylint: disable=consider-using-enumerate 185a8e1175bSopenharmony_ci for i in range(len(arguments)): 186a8e1175bSopenharmony_ci for value in argument_lists[i][1:]: 187a8e1175bSopenharmony_ci arguments[i] = value 188a8e1175bSopenharmony_ci yield self._format_arguments(name, arguments) 189a8e1175bSopenharmony_ci arguments[i] = argument_lists[i][0] 190a8e1175bSopenharmony_ci except BaseException as e: 191a8e1175bSopenharmony_ci raise Exception('distribute_arguments({})'.format(name)) from e 192a8e1175bSopenharmony_ci 193a8e1175bSopenharmony_ci def distribute_arguments_without_duplicates( 194a8e1175bSopenharmony_ci self, seen: Set[str], name: str 195a8e1175bSopenharmony_ci ) -> Iterator[str]: 196a8e1175bSopenharmony_ci """Same as `distribute_arguments`, but don't repeat seen results.""" 197a8e1175bSopenharmony_ci for result in self.distribute_arguments(name): 198a8e1175bSopenharmony_ci if result not in seen: 199a8e1175bSopenharmony_ci seen.add(result) 200a8e1175bSopenharmony_ci yield result 201a8e1175bSopenharmony_ci 202a8e1175bSopenharmony_ci def generate_expressions(self, names: Iterable[str]) -> Iterator[str]: 203a8e1175bSopenharmony_ci """Generate expressions covering values constructed from the given names. 204a8e1175bSopenharmony_ci 205a8e1175bSopenharmony_ci `names` can be any iterable collection of macro names. 206a8e1175bSopenharmony_ci 207a8e1175bSopenharmony_ci For example: 208a8e1175bSopenharmony_ci * ``generate_expressions(['PSA_ALG_CMAC', 'PSA_ALG_HMAC'])`` 209a8e1175bSopenharmony_ci generates ``'PSA_ALG_CMAC'`` as well as ``'PSA_ALG_HMAC(h)'`` for 210a8e1175bSopenharmony_ci every known hash algorithm ``h``. 211a8e1175bSopenharmony_ci * ``macros.generate_expressions(macros.key_types)`` generates all 212a8e1175bSopenharmony_ci key types. 213a8e1175bSopenharmony_ci """ 214a8e1175bSopenharmony_ci seen = set() #type: Set[str] 215a8e1175bSopenharmony_ci return itertools.chain(*( 216a8e1175bSopenharmony_ci self.distribute_arguments_without_duplicates(seen, name) 217a8e1175bSopenharmony_ci for name in names 218a8e1175bSopenharmony_ci )) 219a8e1175bSopenharmony_ci 220a8e1175bSopenharmony_ci 221a8e1175bSopenharmony_ciclass PSAMacroCollector(PSAMacroEnumerator): 222a8e1175bSopenharmony_ci """Collect PSA crypto macro definitions from C header files. 223a8e1175bSopenharmony_ci """ 224a8e1175bSopenharmony_ci 225a8e1175bSopenharmony_ci def __init__(self, include_intermediate: bool = False) -> None: 226a8e1175bSopenharmony_ci """Set up an object to collect PSA macro definitions. 227a8e1175bSopenharmony_ci 228a8e1175bSopenharmony_ci Call the read_file method of the constructed object on each header file. 229a8e1175bSopenharmony_ci 230a8e1175bSopenharmony_ci * include_intermediate: if true, include intermediate macros such as 231a8e1175bSopenharmony_ci PSA_XXX_BASE that do not designate semantic values. 232a8e1175bSopenharmony_ci """ 233a8e1175bSopenharmony_ci super().__init__() 234a8e1175bSopenharmony_ci self.include_intermediate = include_intermediate 235a8e1175bSopenharmony_ci self.key_types_from_curve = {} #type: Dict[str, str] 236a8e1175bSopenharmony_ci self.key_types_from_group = {} #type: Dict[str, str] 237a8e1175bSopenharmony_ci self.algorithms_from_hash = {} #type: Dict[str, str] 238a8e1175bSopenharmony_ci 239a8e1175bSopenharmony_ci @staticmethod 240a8e1175bSopenharmony_ci def algorithm_tester(name: str) -> str: 241a8e1175bSopenharmony_ci """The predicate for whether an algorithm is built from the given constructor. 242a8e1175bSopenharmony_ci 243a8e1175bSopenharmony_ci The given name must be the name of an algorithm constructor of the 244a8e1175bSopenharmony_ci form ``PSA_ALG_xxx`` which is used as ``PSA_ALG_xxx(yyy)`` to build 245a8e1175bSopenharmony_ci an algorithm value. Return the corresponding predicate macro which 246a8e1175bSopenharmony_ci is used as ``predicate(alg)`` to test whether ``alg`` can be built 247a8e1175bSopenharmony_ci as ``PSA_ALG_xxx(yyy)``. The predicate is usually called 248a8e1175bSopenharmony_ci ``PSA_ALG_IS_xxx``. 249a8e1175bSopenharmony_ci """ 250a8e1175bSopenharmony_ci prefix = 'PSA_ALG_' 251a8e1175bSopenharmony_ci assert name.startswith(prefix) 252a8e1175bSopenharmony_ci midfix = 'IS_' 253a8e1175bSopenharmony_ci suffix = name[len(prefix):] 254a8e1175bSopenharmony_ci if suffix in ['DSA', 'ECDSA']: 255a8e1175bSopenharmony_ci midfix += 'RANDOMIZED_' 256a8e1175bSopenharmony_ci elif suffix == 'RSA_PSS': 257a8e1175bSopenharmony_ci suffix += '_STANDARD_SALT' 258a8e1175bSopenharmony_ci return prefix + midfix + suffix 259a8e1175bSopenharmony_ci 260a8e1175bSopenharmony_ci def record_algorithm_subtype(self, name: str, expansion: str) -> None: 261a8e1175bSopenharmony_ci """Record the subtype of an algorithm constructor. 262a8e1175bSopenharmony_ci 263a8e1175bSopenharmony_ci Given a ``PSA_ALG_xxx`` macro name and its expansion, if the algorithm 264a8e1175bSopenharmony_ci is of a subtype that is tracked in its own set, add it to the relevant 265a8e1175bSopenharmony_ci set. 266a8e1175bSopenharmony_ci """ 267a8e1175bSopenharmony_ci # This code is very ad hoc and fragile. It should be replaced by 268a8e1175bSopenharmony_ci # something more robust. 269a8e1175bSopenharmony_ci if re.match(r'MAC(?:_|\Z)', name): 270a8e1175bSopenharmony_ci self.mac_algorithms.add(name) 271a8e1175bSopenharmony_ci elif re.match(r'KDF(?:_|\Z)', name): 272a8e1175bSopenharmony_ci self.kdf_algorithms.add(name) 273a8e1175bSopenharmony_ci elif re.search(r'0x020000[0-9A-Fa-f]{2}', expansion): 274a8e1175bSopenharmony_ci self.hash_algorithms.add(name) 275a8e1175bSopenharmony_ci elif re.search(r'0x03[0-9A-Fa-f]{6}', expansion): 276a8e1175bSopenharmony_ci self.mac_algorithms.add(name) 277a8e1175bSopenharmony_ci elif re.search(r'0x05[0-9A-Fa-f]{6}', expansion): 278a8e1175bSopenharmony_ci self.aead_algorithms.add(name) 279a8e1175bSopenharmony_ci elif re.search(r'0x09[0-9A-Fa-f]{2}0000', expansion): 280a8e1175bSopenharmony_ci self.ka_algorithms.add(name) 281a8e1175bSopenharmony_ci elif re.search(r'0x08[0-9A-Fa-f]{6}', expansion): 282a8e1175bSopenharmony_ci self.kdf_algorithms.add(name) 283a8e1175bSopenharmony_ci 284a8e1175bSopenharmony_ci # "#define" followed by a macro name with either no parameters 285a8e1175bSopenharmony_ci # or a single parameter and a non-empty expansion. 286a8e1175bSopenharmony_ci # Grab the macro name in group 1, the parameter name if any in group 2 287a8e1175bSopenharmony_ci # and the expansion in group 3. 288a8e1175bSopenharmony_ci _define_directive_re = re.compile(r'\s*#\s*define\s+(\w+)' + 289a8e1175bSopenharmony_ci r'(?:\s+|\((\w+)\)\s*)' + 290a8e1175bSopenharmony_ci r'(.+)') 291a8e1175bSopenharmony_ci _deprecated_definition_re = re.compile(r'\s*MBEDTLS_DEPRECATED') 292a8e1175bSopenharmony_ci 293a8e1175bSopenharmony_ci def read_line(self, line): 294a8e1175bSopenharmony_ci """Parse a C header line and record the PSA identifier it defines if any. 295a8e1175bSopenharmony_ci This function analyzes lines that start with "#define PSA_" 296a8e1175bSopenharmony_ci (up to non-significant whitespace) and skips all non-matching lines. 297a8e1175bSopenharmony_ci """ 298a8e1175bSopenharmony_ci # pylint: disable=too-many-branches 299a8e1175bSopenharmony_ci m = re.match(self._define_directive_re, line) 300a8e1175bSopenharmony_ci if not m: 301a8e1175bSopenharmony_ci return 302a8e1175bSopenharmony_ci name, parameter, expansion = m.groups() 303a8e1175bSopenharmony_ci expansion = re.sub(r'/\*.*?\*/|//.*', r' ', expansion) 304a8e1175bSopenharmony_ci if parameter: 305a8e1175bSopenharmony_ci self.argspecs[name] = [parameter] 306a8e1175bSopenharmony_ci if re.match(self._deprecated_definition_re, expansion): 307a8e1175bSopenharmony_ci # Skip deprecated values, which are assumed to be 308a8e1175bSopenharmony_ci # backward compatibility aliases that share 309a8e1175bSopenharmony_ci # numerical values with non-deprecated values. 310a8e1175bSopenharmony_ci return 311a8e1175bSopenharmony_ci if self.is_internal_name(name): 312a8e1175bSopenharmony_ci # Macro only to build actual values 313a8e1175bSopenharmony_ci return 314a8e1175bSopenharmony_ci elif (name.startswith('PSA_ERROR_') or name == 'PSA_SUCCESS') \ 315a8e1175bSopenharmony_ci and not parameter: 316a8e1175bSopenharmony_ci self.statuses.add(name) 317a8e1175bSopenharmony_ci elif name.startswith('PSA_KEY_TYPE_') and not parameter: 318a8e1175bSopenharmony_ci self.key_types.add(name) 319a8e1175bSopenharmony_ci elif name.startswith('PSA_KEY_TYPE_') and parameter == 'curve': 320a8e1175bSopenharmony_ci self.key_types_from_curve[name] = name[:13] + 'IS_' + name[13:] 321a8e1175bSopenharmony_ci elif name.startswith('PSA_KEY_TYPE_') and parameter == 'group': 322a8e1175bSopenharmony_ci self.key_types_from_group[name] = name[:13] + 'IS_' + name[13:] 323a8e1175bSopenharmony_ci elif name.startswith('PSA_ECC_FAMILY_') and not parameter: 324a8e1175bSopenharmony_ci self.ecc_curves.add(name) 325a8e1175bSopenharmony_ci elif name.startswith('PSA_DH_FAMILY_') and not parameter: 326a8e1175bSopenharmony_ci self.dh_groups.add(name) 327a8e1175bSopenharmony_ci elif name.startswith('PSA_ALG_') and not parameter: 328a8e1175bSopenharmony_ci if name in ['PSA_ALG_ECDSA_BASE', 329a8e1175bSopenharmony_ci 'PSA_ALG_RSA_PKCS1V15_SIGN_BASE']: 330a8e1175bSopenharmony_ci # Ad hoc skipping of duplicate names for some numerical values 331a8e1175bSopenharmony_ci return 332a8e1175bSopenharmony_ci self.algorithms.add(name) 333a8e1175bSopenharmony_ci self.record_algorithm_subtype(name, expansion) 334a8e1175bSopenharmony_ci elif name.startswith('PSA_ALG_') and parameter == 'hash_alg': 335a8e1175bSopenharmony_ci self.algorithms_from_hash[name] = self.algorithm_tester(name) 336a8e1175bSopenharmony_ci elif name.startswith('PSA_KEY_USAGE_') and not parameter: 337a8e1175bSopenharmony_ci self.key_usage_flags.add(name) 338a8e1175bSopenharmony_ci else: 339a8e1175bSopenharmony_ci # Other macro without parameter 340a8e1175bSopenharmony_ci return 341a8e1175bSopenharmony_ci 342a8e1175bSopenharmony_ci _nonascii_re = re.compile(rb'[^\x00-\x7f]+') 343a8e1175bSopenharmony_ci _continued_line_re = re.compile(rb'\\\r?\n\Z') 344a8e1175bSopenharmony_ci def read_file(self, header_file): 345a8e1175bSopenharmony_ci for line in header_file: 346a8e1175bSopenharmony_ci m = re.search(self._continued_line_re, line) 347a8e1175bSopenharmony_ci while m: 348a8e1175bSopenharmony_ci cont = next(header_file) 349a8e1175bSopenharmony_ci line = line[:m.start(0)] + cont 350a8e1175bSopenharmony_ci m = re.search(self._continued_line_re, line) 351a8e1175bSopenharmony_ci line = re.sub(self._nonascii_re, rb'', line).decode('ascii') 352a8e1175bSopenharmony_ci self.read_line(line) 353a8e1175bSopenharmony_ci 354a8e1175bSopenharmony_ci 355a8e1175bSopenharmony_ciclass InputsForTest(PSAMacroEnumerator): 356a8e1175bSopenharmony_ci # pylint: disable=too-many-instance-attributes 357a8e1175bSopenharmony_ci """Accumulate information about macros to test. 358a8e1175bSopenharmony_cienumerate 359a8e1175bSopenharmony_ci This includes macro names as well as information about their arguments 360a8e1175bSopenharmony_ci when applicable. 361a8e1175bSopenharmony_ci """ 362a8e1175bSopenharmony_ci 363a8e1175bSopenharmony_ci def __init__(self) -> None: 364a8e1175bSopenharmony_ci super().__init__() 365a8e1175bSopenharmony_ci self.all_declared = set() #type: Set[str] 366a8e1175bSopenharmony_ci # Identifier prefixes 367a8e1175bSopenharmony_ci self.table_by_prefix = { 368a8e1175bSopenharmony_ci 'ERROR': self.statuses, 369a8e1175bSopenharmony_ci 'ALG': self.algorithms, 370a8e1175bSopenharmony_ci 'ECC_CURVE': self.ecc_curves, 371a8e1175bSopenharmony_ci 'DH_GROUP': self.dh_groups, 372a8e1175bSopenharmony_ci 'KEY_LIFETIME': self.lifetimes, 373a8e1175bSopenharmony_ci 'KEY_LOCATION': self.locations, 374a8e1175bSopenharmony_ci 'KEY_PERSISTENCE': self.persistence_levels, 375a8e1175bSopenharmony_ci 'KEY_TYPE': self.key_types, 376a8e1175bSopenharmony_ci 'KEY_USAGE': self.key_usage_flags, 377a8e1175bSopenharmony_ci } #type: Dict[str, Set[str]] 378a8e1175bSopenharmony_ci # Test functions 379a8e1175bSopenharmony_ci self.table_by_test_function = { 380a8e1175bSopenharmony_ci # Any function ending in _algorithm also gets added to 381a8e1175bSopenharmony_ci # self.algorithms. 382a8e1175bSopenharmony_ci 'key_type': [self.key_types], 383a8e1175bSopenharmony_ci 'block_cipher_key_type': [self.key_types], 384a8e1175bSopenharmony_ci 'stream_cipher_key_type': [self.key_types], 385a8e1175bSopenharmony_ci 'ecc_key_family': [self.ecc_curves], 386a8e1175bSopenharmony_ci 'ecc_key_types': [self.ecc_curves], 387a8e1175bSopenharmony_ci 'dh_key_family': [self.dh_groups], 388a8e1175bSopenharmony_ci 'dh_key_types': [self.dh_groups], 389a8e1175bSopenharmony_ci 'hash_algorithm': [self.hash_algorithms], 390a8e1175bSopenharmony_ci 'mac_algorithm': [self.mac_algorithms], 391a8e1175bSopenharmony_ci 'cipher_algorithm': [], 392a8e1175bSopenharmony_ci 'hmac_algorithm': [self.mac_algorithms, self.sign_algorithms], 393a8e1175bSopenharmony_ci 'aead_algorithm': [self.aead_algorithms], 394a8e1175bSopenharmony_ci 'key_derivation_algorithm': [self.kdf_algorithms], 395a8e1175bSopenharmony_ci 'key_agreement_algorithm': [self.ka_algorithms], 396a8e1175bSopenharmony_ci 'asymmetric_signature_algorithm': [self.sign_algorithms], 397a8e1175bSopenharmony_ci 'asymmetric_signature_wildcard': [self.algorithms], 398a8e1175bSopenharmony_ci 'asymmetric_encryption_algorithm': [], 399a8e1175bSopenharmony_ci 'pake_algorithm': [self.pake_algorithms], 400a8e1175bSopenharmony_ci 'other_algorithm': [], 401a8e1175bSopenharmony_ci 'lifetime': [self.lifetimes], 402a8e1175bSopenharmony_ci } #type: Dict[str, List[Set[str]]] 403a8e1175bSopenharmony_ci mac_lengths = [str(n) for n in [ 404a8e1175bSopenharmony_ci 1, # minimum expressible 405a8e1175bSopenharmony_ci 4, # minimum allowed by policy 406a8e1175bSopenharmony_ci 13, # an odd size in a plausible range 407a8e1175bSopenharmony_ci 14, # an even non-power-of-two size in a plausible range 408a8e1175bSopenharmony_ci 16, # same as full size for at least one algorithm 409a8e1175bSopenharmony_ci 63, # maximum expressible 410a8e1175bSopenharmony_ci ]] 411a8e1175bSopenharmony_ci self.arguments_for['mac_length'] += mac_lengths 412a8e1175bSopenharmony_ci self.arguments_for['min_mac_length'] += mac_lengths 413a8e1175bSopenharmony_ci aead_lengths = [str(n) for n in [ 414a8e1175bSopenharmony_ci 1, # minimum expressible 415a8e1175bSopenharmony_ci 4, # minimum allowed by policy 416a8e1175bSopenharmony_ci 13, # an odd size in a plausible range 417a8e1175bSopenharmony_ci 14, # an even non-power-of-two size in a plausible range 418a8e1175bSopenharmony_ci 16, # same as full size for at least one algorithm 419a8e1175bSopenharmony_ci 63, # maximum expressible 420a8e1175bSopenharmony_ci ]] 421a8e1175bSopenharmony_ci self.arguments_for['tag_length'] += aead_lengths 422a8e1175bSopenharmony_ci self.arguments_for['min_tag_length'] += aead_lengths 423a8e1175bSopenharmony_ci 424a8e1175bSopenharmony_ci def add_numerical_values(self) -> None: 425a8e1175bSopenharmony_ci """Add numerical values that are not supported to the known identifiers.""" 426a8e1175bSopenharmony_ci # Sets of names per type 427a8e1175bSopenharmony_ci self.algorithms.add('0xffffffff') 428a8e1175bSopenharmony_ci self.ecc_curves.add('0xff') 429a8e1175bSopenharmony_ci self.dh_groups.add('0xff') 430a8e1175bSopenharmony_ci self.key_types.add('0xffff') 431a8e1175bSopenharmony_ci self.key_usage_flags.add('0x80000000') 432a8e1175bSopenharmony_ci 433a8e1175bSopenharmony_ci # Hard-coded values for unknown algorithms 434a8e1175bSopenharmony_ci # 435a8e1175bSopenharmony_ci # These have to have values that are correct for their respective 436a8e1175bSopenharmony_ci # PSA_ALG_IS_xxx macros, but are also not currently assigned and are 437a8e1175bSopenharmony_ci # not likely to be assigned in the near future. 438a8e1175bSopenharmony_ci self.hash_algorithms.add('0x020000fe') # 0x020000ff is PSA_ALG_ANY_HASH 439a8e1175bSopenharmony_ci self.mac_algorithms.add('0x03007fff') 440a8e1175bSopenharmony_ci self.ka_algorithms.add('0x09fc0000') 441a8e1175bSopenharmony_ci self.kdf_algorithms.add('0x080000ff') 442a8e1175bSopenharmony_ci self.pake_algorithms.add('0x0a0000ff') 443a8e1175bSopenharmony_ci # For AEAD algorithms, the only variability is over the tag length, 444a8e1175bSopenharmony_ci # and this only applies to known algorithms, so don't test an 445a8e1175bSopenharmony_ci # unknown algorithm. 446a8e1175bSopenharmony_ci 447a8e1175bSopenharmony_ci def get_names(self, type_word: str) -> Set[str]: 448a8e1175bSopenharmony_ci """Return the set of known names of values of the given type.""" 449a8e1175bSopenharmony_ci return { 450a8e1175bSopenharmony_ci 'status': self.statuses, 451a8e1175bSopenharmony_ci 'algorithm': self.algorithms, 452a8e1175bSopenharmony_ci 'ecc_curve': self.ecc_curves, 453a8e1175bSopenharmony_ci 'dh_group': self.dh_groups, 454a8e1175bSopenharmony_ci 'key_type': self.key_types, 455a8e1175bSopenharmony_ci 'key_usage': self.key_usage_flags, 456a8e1175bSopenharmony_ci }[type_word] 457a8e1175bSopenharmony_ci 458a8e1175bSopenharmony_ci # Regex for interesting header lines. 459a8e1175bSopenharmony_ci # Groups: 1=macro name, 2=type, 3=argument list (optional). 460a8e1175bSopenharmony_ci _header_line_re = \ 461a8e1175bSopenharmony_ci re.compile(r'#define +' + 462a8e1175bSopenharmony_ci r'(PSA_((?:(?:DH|ECC|KEY)_)?[A-Z]+)_\w+)' + 463a8e1175bSopenharmony_ci r'(?:\(([^\n()]*)\))?') 464a8e1175bSopenharmony_ci # Regex of macro names to exclude. 465a8e1175bSopenharmony_ci _excluded_name_re = re.compile(r'_(?:GET|IS|OF)_|_(?:BASE|FLAG|MASK)\Z') 466a8e1175bSopenharmony_ci # Additional excluded macros. 467a8e1175bSopenharmony_ci _excluded_names = set([ 468a8e1175bSopenharmony_ci # Macros that provide an alternative way to build the same 469a8e1175bSopenharmony_ci # algorithm as another macro. 470a8e1175bSopenharmony_ci 'PSA_ALG_AEAD_WITH_DEFAULT_LENGTH_TAG', 471a8e1175bSopenharmony_ci 'PSA_ALG_FULL_LENGTH_MAC', 472a8e1175bSopenharmony_ci # Auxiliary macro whose name doesn't fit the usual patterns for 473a8e1175bSopenharmony_ci # auxiliary macros. 474a8e1175bSopenharmony_ci 'PSA_ALG_AEAD_WITH_DEFAULT_LENGTH_TAG_CASE', 475a8e1175bSopenharmony_ci ]) 476a8e1175bSopenharmony_ci def parse_header_line(self, line: str) -> None: 477a8e1175bSopenharmony_ci """Parse a C header line, looking for "#define PSA_xxx".""" 478a8e1175bSopenharmony_ci m = re.match(self._header_line_re, line) 479a8e1175bSopenharmony_ci if not m: 480a8e1175bSopenharmony_ci return 481a8e1175bSopenharmony_ci name = m.group(1) 482a8e1175bSopenharmony_ci self.all_declared.add(name) 483a8e1175bSopenharmony_ci if re.search(self._excluded_name_re, name) or \ 484a8e1175bSopenharmony_ci name in self._excluded_names or \ 485a8e1175bSopenharmony_ci self.is_internal_name(name): 486a8e1175bSopenharmony_ci return 487a8e1175bSopenharmony_ci dest = self.table_by_prefix.get(m.group(2)) 488a8e1175bSopenharmony_ci if dest is None: 489a8e1175bSopenharmony_ci return 490a8e1175bSopenharmony_ci dest.add(name) 491a8e1175bSopenharmony_ci if m.group(3): 492a8e1175bSopenharmony_ci self.argspecs[name] = self._argument_split(m.group(3)) 493a8e1175bSopenharmony_ci 494a8e1175bSopenharmony_ci _nonascii_re = re.compile(rb'[^\x00-\x7f]+') #type: Pattern 495a8e1175bSopenharmony_ci def parse_header(self, filename: str) -> None: 496a8e1175bSopenharmony_ci """Parse a C header file, looking for "#define PSA_xxx".""" 497a8e1175bSopenharmony_ci with read_file_lines(filename, binary=True) as lines: 498a8e1175bSopenharmony_ci for line in lines: 499a8e1175bSopenharmony_ci line = re.sub(self._nonascii_re, rb'', line).decode('ascii') 500a8e1175bSopenharmony_ci self.parse_header_line(line) 501a8e1175bSopenharmony_ci 502a8e1175bSopenharmony_ci _macro_identifier_re = re.compile(r'[A-Z]\w+') 503a8e1175bSopenharmony_ci def generate_undeclared_names(self, expr: str) -> Iterable[str]: 504a8e1175bSopenharmony_ci for name in re.findall(self._macro_identifier_re, expr): 505a8e1175bSopenharmony_ci if name not in self.all_declared: 506a8e1175bSopenharmony_ci yield name 507a8e1175bSopenharmony_ci 508a8e1175bSopenharmony_ci def accept_test_case_line(self, function: str, argument: str) -> bool: 509a8e1175bSopenharmony_ci #pylint: disable=unused-argument 510a8e1175bSopenharmony_ci undeclared = list(self.generate_undeclared_names(argument)) 511a8e1175bSopenharmony_ci if undeclared: 512a8e1175bSopenharmony_ci raise Exception('Undeclared names in test case', undeclared) 513a8e1175bSopenharmony_ci return True 514a8e1175bSopenharmony_ci 515a8e1175bSopenharmony_ci @staticmethod 516a8e1175bSopenharmony_ci def normalize_argument(argument: str) -> str: 517a8e1175bSopenharmony_ci """Normalize whitespace in the given C expression. 518a8e1175bSopenharmony_ci 519a8e1175bSopenharmony_ci The result uses the same whitespace as 520a8e1175bSopenharmony_ci ` PSAMacroEnumerator.distribute_arguments`. 521a8e1175bSopenharmony_ci """ 522a8e1175bSopenharmony_ci return re.sub(r',', r', ', re.sub(r' +', r'', argument)) 523a8e1175bSopenharmony_ci 524a8e1175bSopenharmony_ci def add_test_case_line(self, function: str, argument: str) -> None: 525a8e1175bSopenharmony_ci """Parse a test case data line, looking for algorithm metadata tests.""" 526a8e1175bSopenharmony_ci sets = [] 527a8e1175bSopenharmony_ci if function.endswith('_algorithm'): 528a8e1175bSopenharmony_ci sets.append(self.algorithms) 529a8e1175bSopenharmony_ci if function == 'key_agreement_algorithm' and \ 530a8e1175bSopenharmony_ci argument.startswith('PSA_ALG_KEY_AGREEMENT('): 531a8e1175bSopenharmony_ci # We only want *raw* key agreement algorithms as such, so 532a8e1175bSopenharmony_ci # exclude ones that are already chained with a KDF. 533a8e1175bSopenharmony_ci # Keep the expression as one to test as an algorithm. 534a8e1175bSopenharmony_ci function = 'other_algorithm' 535a8e1175bSopenharmony_ci sets += self.table_by_test_function[function] 536a8e1175bSopenharmony_ci if self.accept_test_case_line(function, argument): 537a8e1175bSopenharmony_ci for s in sets: 538a8e1175bSopenharmony_ci s.add(self.normalize_argument(argument)) 539a8e1175bSopenharmony_ci 540a8e1175bSopenharmony_ci # Regex matching a *.data line containing a test function call and 541a8e1175bSopenharmony_ci # its arguments. The actual definition is partly positional, but this 542a8e1175bSopenharmony_ci # regex is good enough in practice. 543a8e1175bSopenharmony_ci _test_case_line_re = re.compile(r'(?!depends_on:)(\w+):([^\n :][^:\n]*)') 544a8e1175bSopenharmony_ci def parse_test_cases(self, filename: str) -> None: 545a8e1175bSopenharmony_ci """Parse a test case file (*.data), looking for algorithm metadata tests.""" 546a8e1175bSopenharmony_ci with read_file_lines(filename) as lines: 547a8e1175bSopenharmony_ci for line in lines: 548a8e1175bSopenharmony_ci m = re.match(self._test_case_line_re, line) 549a8e1175bSopenharmony_ci if m: 550a8e1175bSopenharmony_ci self.add_test_case_line(m.group(1), m.group(2)) 551