diff --git a/scripts/generate_psa_wrappers.py b/scripts/generate_psa_wrappers.py index 641149b6a..29cb4e3fd 100755 --- a/scripts/generate_psa_wrappers.py +++ b/scripts/generate_psa_wrappers.py @@ -5,248 +5,8 @@ # Copyright The Mbed TLS Contributors # SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later -### WARNING: the code in this file has not been extensively reviewed yet. -### We do not think it is harmful, but it may be below our normal standards -### for robustness and maintainability. - import argparse -import itertools -import os -from typing import Iterator, List, Optional, Tuple - -from mbedtls_framework import build_tree -from mbedtls_framework import c_parsing_helper -from mbedtls_framework import c_wrapper_generator -from mbedtls_framework import typing_util - - -class BufferParameter: - """Description of an input or output buffer parameter sequence to a PSA function.""" - #pylint: disable=too-few-public-methods - - def __init__(self, i: int, is_output: bool, - buffer_name: str, size_name: str) -> None: - """Initialize the parameter information. - - i is the index of the function argument that is the pointer to the buffer. - The size is argument i+1. For a variable-size output, the actual length - goes in argument i+2. - - buffer_name and size_names are the names of arguments i and i+1. - This class does not yet help with the output length. - """ - self.index = i - self.buffer_name = buffer_name - self.size_name = size_name - self.is_output = is_output - - -class PSAWrapperGenerator(c_wrapper_generator.Base): - """Generate a C source file containing wrapper functions for PSA Crypto API calls.""" - - _CPP_GUARDS = ('defined(MBEDTLS_PSA_CRYPTO_C) && ' + - 'defined(MBEDTLS_TEST_HOOKS) && \\\n ' + - '!defined(RECORD_PSA_STATUS_COVERAGE_LOG)') - _WRAPPER_NAME_PREFIX = 'mbedtls_test_wrap_' - _WRAPPER_NAME_SUFFIX = '' - - def gather_data(self) -> None: - """Gather PSA Crypto API function names.""" - root_dir = build_tree.guess_mbedtls_root() - for header_name in ['crypto.h', 'crypto_extra.h']: - # Temporary, while Mbed TLS does not just rely on the TF-PSA-Crypto - # build system to build its crypto library. When it does, the first - # case can just be removed. - if os.path.isdir(os.path.join(root_dir, 'tf-psa-crypto')): - header_path = os.path.join(root_dir, 'tf-psa-crypto', - 'include', 'psa', header_name) - else: - header_path = os.path.join(root_dir, 'include', 'psa', header_name) - c_parsing_helper.read_function_declarations(self.functions, header_path) - - _SKIP_FUNCTIONS = frozenset([ - 'mbedtls_psa_external_get_random', # not a library function - 'psa_get_key_domain_parameters', # client-side function - 'psa_get_key_slot_number', # client-side function - 'psa_key_derivation_verify_bytes', # not implemented yet - 'psa_key_derivation_verify_key', # not implemented yet - 'psa_set_key_domain_parameters', # client-side function - ]) - - def _skip_function(self, function: c_wrapper_generator.FunctionInfo) -> bool: - if function.return_type != 'psa_status_t': - return True - if function.name in self._SKIP_FUNCTIONS: - return True - return False - - # PAKE stuff: not implemented yet - _PAKE_STUFF = frozenset([ - 'psa_crypto_driver_pake_inputs_t *', - 'psa_pake_cipher_suite_t *', - ]) - - def _return_variable_name(self, - function: c_wrapper_generator.FunctionInfo) -> str: - """The name of the variable that will contain the return value.""" - if function.return_type == 'psa_status_t': - return 'status' - return super()._return_variable_name(function) - - _FUNCTION_GUARDS = c_wrapper_generator.Base._FUNCTION_GUARDS.copy() \ - #pylint: disable=protected-access - _FUNCTION_GUARDS.update({ - 'mbedtls_psa_register_se_key': 'defined(MBEDTLS_PSA_CRYPTO_SE_C)', - 'mbedtls_psa_inject_entropy': 'defined(MBEDTLS_PSA_INJECT_ENTROPY)', - 'mbedtls_psa_external_get_random': 'defined(MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG)', - 'mbedtls_psa_platform_get_builtin_key': 'defined(MBEDTLS_PSA_CRYPTO_BUILTIN_KEYS)', - 'psa_crypto_driver_pake_get_cipher_suite' : 'defined(PSA_WANT_ALG_SOME_PAKE)', - 'psa_crypto_driver_pake_get_password' : 'defined(PSA_WANT_ALG_SOME_PAKE)', - 'psa_crypto_driver_pake_get_password_len' : 'defined(PSA_WANT_ALG_SOME_PAKE)', - 'psa_crypto_driver_pake_get_peer' : 'defined(PSA_WANT_ALG_SOME_PAKE)', - 'psa_crypto_driver_pake_get_peer_len' : 'defined(PSA_WANT_ALG_SOME_PAKE)', - 'psa_crypto_driver_pake_get_user' : 'defined(PSA_WANT_ALG_SOME_PAKE)', - 'psa_crypto_driver_pake_get_user_len' : 'defined(PSA_WANT_ALG_SOME_PAKE)', - 'psa_pake_abort' : 'defined(PSA_WANT_ALG_SOME_PAKE)', - 'psa_pake_get_implicit_key' : 'defined(PSA_WANT_ALG_SOME_PAKE)', - 'psa_pake_input' : 'defined(PSA_WANT_ALG_SOME_PAKE)', - 'psa_pake_output' : 'defined(PSA_WANT_ALG_SOME_PAKE)', - 'psa_pake_set_password_key' : 'defined(PSA_WANT_ALG_SOME_PAKE)', - 'psa_pake_set_peer' : 'defined(PSA_WANT_ALG_SOME_PAKE)', - 'psa_pake_set_role' : 'defined(PSA_WANT_ALG_SOME_PAKE)', - 'psa_pake_set_user' : 'defined(PSA_WANT_ALG_SOME_PAKE)', - 'psa_pake_setup' : 'defined(PSA_WANT_ALG_SOME_PAKE)', - }) - - @staticmethod - def _detect_buffer_parameters(arguments: List[c_parsing_helper.ArgumentInfo], - argument_names: List[str]) -> Iterator[BufferParameter]: - """Detect function arguments that are buffers (pointer, size [,length]).""" - types = ['' if arg.suffix else arg.type for arg in arguments] - # pairs = list of (type_of_arg_N, type_of_arg_N+1) - # where each type_of_arg_X is the empty string if the type is an array - # or there is no argument X. - pairs = enumerate(itertools.zip_longest(types, types[1:], fillvalue='')) - for i, t01 in pairs: - if (t01[0] == 'const uint8_t *' or t01[0] == 'uint8_t *') and \ - t01[1] == 'size_t': - yield BufferParameter(i, not t01[0].startswith('const '), - argument_names[i], argument_names[i+1]) - - @staticmethod - def _write_poison_buffer_parameter(out: typing_util.Writable, - param: BufferParameter, - poison: bool) -> None: - """Write poisoning or unpoisoning code for a buffer parameter. - - Write poisoning code if poison is true, unpoisoning code otherwise. - """ - out.write(' MBEDTLS_TEST_MEMORY_{}({}, {});\n'.format( - 'POISON' if poison else 'UNPOISON', - param.buffer_name, param.size_name - )) - - def _write_poison_buffer_parameters(self, out: typing_util.Writable, - buffer_parameters: List[BufferParameter], - poison: bool) -> None: - """Write poisoning or unpoisoning code for the buffer parameters. - - Write poisoning code if poison is true, unpoisoning code otherwise. - """ - if not buffer_parameters: - return - out.write('#if !defined(MBEDTLS_PSA_ASSUME_EXCLUSIVE_BUFFERS)\n') - for param in buffer_parameters: - self._write_poison_buffer_parameter(out, param, poison) - out.write('#endif /* !defined(MBEDTLS_PSA_ASSUME_EXCLUSIVE_BUFFERS) */\n') - - @staticmethod - def _parameter_should_be_copied(function_name: str, - _buffer_name: Optional[str]) -> bool: - """Whether the specified buffer argument to a PSA function should be copied. - """ - # False-positives that do not need buffer copying - if function_name in ('mbedtls_psa_inject_entropy', - 'psa_crypto_driver_pake_get_password', - 'psa_crypto_driver_pake_get_user', - 'psa_crypto_driver_pake_get_peer'): - return False - - return True - - def _write_function_call(self, out: typing_util.Writable, - function: c_wrapper_generator.FunctionInfo, - argument_names: List[str]) -> None: - buffer_parameters = list( - param - for param in self._detect_buffer_parameters(function.arguments, - argument_names) - if self._parameter_should_be_copied(function.name, - function.arguments[param.index].name)) - self._write_poison_buffer_parameters(out, buffer_parameters, True) - super()._write_function_call(out, function, argument_names) - self._write_poison_buffer_parameters(out, buffer_parameters, False) - - def _write_prologue(self, out: typing_util.Writable, header: bool) -> None: - super()._write_prologue(out, header) - out.write(""" -#if {} - -#include - -#include -#include -#include -""" - .format(self._CPP_GUARDS)) - - def _write_epilogue(self, out: typing_util.Writable, header: bool) -> None: - out.write(""" -#endif /* {} */ -""" - .format(self._CPP_GUARDS)) - super()._write_epilogue(out, header) - - -class PSALoggingWrapperGenerator(PSAWrapperGenerator, c_wrapper_generator.Logging): - """Generate a C source file containing wrapper functions that log PSA Crypto API calls.""" - - def __init__(self, stream: str) -> None: - super().__init__() - self.set_stream(stream) - - _PRINTF_TYPE_CAST = c_wrapper_generator.Logging._PRINTF_TYPE_CAST.copy() - _PRINTF_TYPE_CAST.update({ - 'mbedtls_svc_key_id_t': 'unsigned', - 'psa_algorithm_t': 'unsigned', - 'psa_drv_slot_number_t': 'unsigned long long', - 'psa_key_derivation_step_t': 'int', - 'psa_key_id_t': 'unsigned', - 'psa_key_slot_number_t': 'unsigned long long', - 'psa_key_lifetime_t': 'unsigned', - 'psa_key_type_t': 'unsigned', - 'psa_key_usage_flags_t': 'unsigned', - 'psa_pake_role_t': 'int', - 'psa_pake_step_t': 'int', - 'psa_status_t': 'int', - }) - - def _printf_parameters(self, typ: str, var: str) -> Tuple[str, List[str]]: - if typ.startswith('const '): - typ = typ[6:] - if typ == 'uint8_t *': - # Skip buffers - return '', [] - if typ.endswith('operation_t *'): - return '', [] - if typ in self._PAKE_STUFF: - return '', [] - if typ == 'psa_key_attributes_t *': - return (var + '={id=%u, lifetime=0x%08x, type=0x%08x, bits=%u, alg=%08x, usage=%08x}', - ['(unsigned) psa_get_key_{}({})'.format(field, var) - for field in ['id', 'lifetime', 'type', 'bits', 'algorithm', 'usage_flags']]) - return super()._printf_parameters(typ, var) - +from mbedtls_framework.code_wrapper.psa_test_wrapper import PSATestWrapper, PSALoggingTestWrapper DEFAULT_C_OUTPUT_FILE_NAME = 'tests/src/psa_test_wrappers.c' DEFAULT_H_OUTPUT_FILE_NAME = 'tests/include/test/psa_test_wrappers.h' @@ -267,10 +27,13 @@ def main() -> None: .format(DEFAULT_H_OUTPUT_FILE_NAME))) options = parser.parse_args() if options.log: - generator = PSALoggingWrapperGenerator(options.log) #type: PSAWrapperGenerator + generator = PSALoggingTestWrapper(DEFAULT_H_OUTPUT_FILE_NAME, + DEFAULT_C_OUTPUT_FILE_NAME, + options.log) #type: PSATestWrapper else: - generator = PSAWrapperGenerator() - generator.gather_data() + generator = PSATestWrapper(DEFAULT_H_OUTPUT_FILE_NAME, + DEFAULT_C_OUTPUT_FILE_NAME) + if options.output_h: generator.write_h_file(options.output_h) if options.output_c: diff --git a/scripts/mbedtls_framework/c_parsing_helper.py b/scripts/mbedtls_framework/c_parsing_helper.py index 2657b7d23..0e428cd04 100644 --- a/scripts/mbedtls_framework/c_parsing_helper.py +++ b/scripts/mbedtls_framework/c_parsing_helper.py @@ -46,6 +46,8 @@ class ArgumentInfo: self.name = m.group('name') #type: Optional[str] self.suffix = m.group('suffix') if m.group('suffix') else '' #type: str + def __str__(self) -> str: + return self.decl class FunctionInfo: """Information about an API function.""" @@ -60,18 +62,38 @@ class FunctionInfo: qualifiers: Iterable[str], return_type: str, name: str, - arguments: List[str]) -> None: + arguments: List[str], + doc: str = "") -> None: + self.filename = filename self.line_number = line_number self.qualifiers = frozenset(qualifiers) self.return_type = return_type self.name = name self.arguments = [ArgumentInfo(arg) for arg in arguments] + self.doc = doc def returns_void(self) -> bool: """Whether the function returns void.""" return bool(self.VOID_RE.search(self.return_type)) + def __str__(self) -> str: + str_args = [str(a) for a in self.arguments] + str_text = "{} {} {}({})".format(" ".join(self.qualifiers), + self.return_type, self.name, + ", ".join(str_args)).strip() + str_text = self._c_wrap_(str_text) + return self.doc + "\n" + str_text + + @staticmethod + def _c_wrap_(in_str: str, line_len: int = 80) -> str: + """Auto-idents function declaration args using opening parenthesis.""" + if len(in_str) >= line_len: + p_idx = in_str.index("(") + ident = " " * p_idx + padded_comma = ",\n" + ident + in_str = in_str.replace(",", padded_comma) + return in_str # Match one C comment. # Note that we match both comment types, so things like // in a /*...*/ @@ -112,6 +134,7 @@ _C_FUNCTION_DECLARATION_RE = re.compile( def read_function_declarations(functions: Dict[str, FunctionInfo], filename: str) -> None: + """Collect function declarations from a C header file.""" for line_number, line in read_logical_lines(filename): m = _C_FUNCTION_DECLARATION_RE.match(line) @@ -129,3 +152,17 @@ def read_function_declarations(functions: Dict[str, FunctionInfo], return_type, name, arguments) + +_C_TYPEDEF_DECLARATION_RE = re.compile(r'typedef (?:struct )?(?P\w+) (?P\w+)') + +def read_typedefs(filename: str) -> Dict[str, str]: + """ Extract type definitions in a {typedef aliased name: original type} dictionary. + Multi-line typedef struct are not captured. """ + + type_decl = {} + + for _, line in read_logical_lines(filename): + m = _C_TYPEDEF_DECLARATION_RE.match(line) + if m: + type_decl[m.group("name")] = m.group("type") + return type_decl diff --git a/scripts/mbedtls_framework/c_wrapper_generator.py b/scripts/mbedtls_framework/c_wrapper_generator.py index 3cf1e05eb..db62bdd14 100644 --- a/scripts/mbedtls_framework/c_wrapper_generator.py +++ b/scripts/mbedtls_framework/c_wrapper_generator.py @@ -11,8 +11,7 @@ import os import re import sys -import typing -from typing import Dict, List, Optional, Tuple +from typing import Dict, NamedTuple, List, Optional, Tuple from .c_parsing_helper import ArgumentInfo, FunctionInfo from . import typing_util @@ -25,12 +24,34 @@ def c_declare(prefix: str, name: str, suffix: str) -> str: return prefix + name + suffix -WrapperInfo = typing.NamedTuple('WrapperInfo', [ +WrapperInfo = NamedTuple('WrapperInfo', [ ('argument_names', List[str]), ('guard', Optional[str]), ('wrapper_name', str), ]) +def strip_indentation(in_str: str, new_lines: int = 1, indent_lv: int = 0) -> str: + """Return a whitespace stripped str, with configurable whitespace in output. + + The method will remove space-character indentation from input string. + It will also remove all new-lines around the text-block. + The output indentation can be configured by indent_lv, and will use blocks + of 4 spaces. + At the end of the string a `new_lines` amount of empty lines will be added. + """ + + _ret_string = in_str.strip('\n') + + # Count empty spaces in beggining of each line. The smallest non-zero entry + # will be used to clean up input indentation. + indents = [len(n) for n in re.findall(r'(?m)^ +', in_str)] + if indents: + _ret_string = re.sub(r'(?m)^ {{{indent}}}'.format(indent=min(indents)), + '', _ret_string) + if indent_lv: + _ret_string = '\n'.join([' ' * indent_lv * 4 + s + for s in _ret_string.splitlines()]) + return _ret_string + ('\n' * (new_lines + 1)) class Base: """Generate a C source file containing wrapper functions.""" @@ -46,6 +67,8 @@ class Base: # Suffix appended to the function's name to form the wrapper name. _WRAPPER_NAME_SUFFIX = '_wrap' + _INCLUDES = [''] + # Functions with one of these qualifiers are skipped. _SKIP_FUNCTION_WITH_QUALIFIERS = frozenset(['inline', 'static']) @@ -55,6 +78,7 @@ class Base: self.program_name = os.path.basename(sys.argv[0]) # To be populated in a derived class self.functions = {} #type: Dict[str, FunctionInfo] + self._function_guards = {} #type: Dict[str, str] # Preprocessor symbol used as a guard against multiple inclusion in the # header. Must be set before writing output to a header. # Not used when writing .c output. @@ -65,42 +89,49 @@ class Base: This includes a description comment and some include directives. """ - out.write("""/* Automatically generated by {}, do not edit! */ + prologue = strip_indentation(f''' + /* Automatically generated by {self.program_name}, do not edit! */ + + /* Copyright The Mbed TLS Contributors + * SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + */ + + ''') -/* Copyright The Mbed TLS Contributors - * SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later - */ -""" - .format(self.program_name)) if header: - out.write(""" -#ifndef {guard} -#define {guard} + prologue += strip_indentation(f''' + #ifndef {self.header_guard} + #define {self.header_guard} -#ifdef __cplusplus -extern "C" {{ -#endif -""" - .format(guard=self.header_guard)) - out.write(""" -#include -""") + #ifdef __cplusplus + extern "C" {{ + #endif + + ''') + + for include in self._INCLUDES: + prologue += "#include {}\n".format(include) + + # Make certain there is an empty line at the end of this section. + prologue += '\n' if self._INCLUDES else '\n\n' + + out.write(prologue) def _write_epilogue(self, out: typing_util.Writable, header: bool) -> None: - """Write the epilogue of a C file. - """ + """Write the epilogue of a C file.""" + epilogue = "" if header: - out.write(""" -#ifdef __cplusplus -}} -#endif + epilogue += strip_indentation(f''' + #ifdef __cplusplus + }} + #endif -#endif /* {guard} */ -""" - .format(guard=self.header_guard)) - out.write(""" -/* End of automatically generated file. */ -""") + #endif /* {self.header_guard} */ + + ''') + + epilogue += ('/* End of automatically generated file. */\n') + out.write(epilogue) def _wrapper_function_name(self, original_name: str) -> str: """The name of the wrapper function. @@ -207,15 +238,12 @@ extern "C" {{ return True return False - _FUNCTION_GUARDS = { - } #type: Dict[str, str] - def _function_guard(self, function: FunctionInfo) -> Optional[str]: """A preprocessor condition for this function. The wrapper will be guarded with `#if` on this condition, if not None. """ - return self._FUNCTION_GUARDS.get(function.name) + return self._function_guards.get(function.name) def _wrapper_info(self, function: FunctionInfo) -> Optional[WrapperInfo]: """Information about the wrapper for one function. @@ -268,10 +296,8 @@ extern "C" {{ wrapper = self._wrapper_info(function) if wrapper is None: return - out.write(""" -/* Wrapper for {} */ -""" - .format(function.name)) + out.write('/* Wrapper for {} */\n'.format(function.name)) + if wrapper.guard is not None: out.write('#if {}\n'.format(wrapper.guard)) self._write_function_prototype(out, function, wrapper, False) @@ -280,6 +306,7 @@ extern "C" {{ out.write('}\n') if wrapper.guard is not None: out.write('#endif /* {} */\n'.format(wrapper.guard)) + out.write('\n') def _write_h_function_declaration(self, out: typing_util.Writable, function: FunctionInfo, @@ -311,13 +338,13 @@ extern "C" {{ wrapper = self._wrapper_info(function) if wrapper is None: return - out.write('\n') if wrapper.guard is not None: out.write('#if {}\n'.format(wrapper.guard)) self._write_h_function_declaration(out, function, wrapper) self._write_h_macro_definition(out, function, wrapper) if wrapper.guard is not None: out.write('#endif /* {} */\n'.format(wrapper.guard)) + out.write('\n') def write_c_file(self, filename: str) -> None: """Output a whole C file containing function wrapper definitions.""" diff --git a/scripts/mbedtls_framework/code_wrapper/__init__.py b/scripts/mbedtls_framework/code_wrapper/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/scripts/mbedtls_framework/code_wrapper/psa_buffer.py b/scripts/mbedtls_framework/code_wrapper/psa_buffer.py new file mode 100644 index 000000000..ca6296872 --- /dev/null +++ b/scripts/mbedtls_framework/code_wrapper/psa_buffer.py @@ -0,0 +1,29 @@ +""" PSA Buffer utility data-class. +""" + +# Copyright The Mbed TLS Contributors +# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + +from typing import List +from .. import typing_util + +class BufferParameter: + """Description of an input or output buffer parameter sequence to a PSA function.""" + #pylint: disable=too-few-public-methods + + def __init__(self, i: int, is_output: bool, + buffer_name: str, size_name: str) -> None: + """Initialize the parameter information. + + i is the index of the function argument that is the pointer to the buffer. + The size is argument i+1. For a variable-size output, the actual length + goes in argument i+2. + + buffer_name and size_names are the names of arguments i and i+1. + This class does not yet help with the output length. + """ + self.index = i + self.buffer_name = buffer_name + self.size_name = size_name + self.is_output = is_output + diff --git a/scripts/mbedtls_framework/code_wrapper/psa_test_wrapper.py b/scripts/mbedtls_framework/code_wrapper/psa_test_wrapper.py new file mode 100644 index 000000000..6b90fe20e --- /dev/null +++ b/scripts/mbedtls_framework/code_wrapper/psa_test_wrapper.py @@ -0,0 +1,39 @@ +"""Generate wrapper functions for PSA function calls. +""" + +# Copyright The Mbed TLS Contributors +# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + +import argparse +import itertools +import os +from typing import Iterator, List, Collection, Optional, Tuple + +from .. import build_tree +from .. import c_parsing_helper +from .. import c_wrapper_generator +from .. import typing_util + +from .psa_buffer import BufferParameter +from .psa_wrapper import PSAWrapper, PSALoggingWrapper, PSAWrapperConfiguration + +class PSATestWrapper(PSAWrapper): + """Generate a C source file containing wrapper functions for PSA Crypto API calls.""" + + _WRAPPER_NAME_PREFIX = 'mbedtls_test_wrap_' + _WRAPPER_NAME_SUFFIX = '' + + _PSA_WRAPPER_INCLUDES = ['', + '', + '', + ''] + +class PSALoggingTestWrapper(PSATestWrapper, PSALoggingWrapper): + """Generate a C source file containing wrapper functions that log PSA Crypto API calls.""" + + def __init__(self, out_h_f: str, + out_c_f: str, + stream: str, + in_headers: Optional[List[str]] = None) -> None: + super().__init__(out_h_f, out_c_f, stream, in_headers) + diff --git a/scripts/mbedtls_framework/code_wrapper/psa_wrapper.py b/scripts/mbedtls_framework/code_wrapper/psa_wrapper.py new file mode 100644 index 000000000..7e6b3e8c9 --- /dev/null +++ b/scripts/mbedtls_framework/code_wrapper/psa_wrapper.py @@ -0,0 +1,286 @@ +"""Generate wrapper functions for PSA function calls. +""" + +# Copyright The Mbed TLS Contributors +# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + +import argparse +import itertools +import os +from typing import Any, Iterator, List, Dict, Collection, Optional, Tuple + +from .. import build_tree +from .. import c_parsing_helper +from .. import c_wrapper_generator +from .. import typing_util + +from .psa_buffer import BufferParameter + +class PSAWrapperConfiguration: + """Configuration data class for PSA Wrapper.""" + + def __init__(self) -> None: + self.cpp_guards = ["MBEDTLS_PSA_CRYPTO_C", "MBEDTLS_TEST_HOOKS", "!RECORD_PSA_STATUS_COVERAGE_LOG"] + + self.skipped_functions = frozenset([ + 'mbedtls_psa_external_get_random', # not a library function + 'psa_get_key_domain_parameters', # client-side function + 'psa_get_key_slot_number', # client-side function + 'psa_key_derivation_verify_bytes', # not implemented yet + 'psa_key_derivation_verify_key', # not implemented yet + 'psa_set_key_domain_parameters', # client-side function + ]) + + self.skipped_argument_types = frozenset([ + # PAKE stuff: not implemented yet + 'psa_crypto_driver_pake_inputs_t *', + 'psa_pake_cipher_suite_t *', + ]) + + self.function_guards = { + 'mbedtls_psa_register_se_key': 'defined(MBEDTLS_PSA_CRYPTO_SE_C)', + 'mbedtls_psa_inject_entropy': 'defined(MBEDTLS_PSA_INJECT_ENTROPY)', + 'mbedtls_psa_external_get_random': 'defined(MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG)', + 'mbedtls_psa_platform_get_builtin_key': 'defined(MBEDTLS_PSA_CRYPTO_BUILTIN_KEYS)', + 'psa_crypto_driver_pake_get_cipher_suite' : 'defined(PSA_WANT_ALG_SOME_PAKE)', + 'psa_crypto_driver_pake_get_password' : 'defined(PSA_WANT_ALG_SOME_PAKE)', + 'psa_crypto_driver_pake_get_password_len' : 'defined(PSA_WANT_ALG_SOME_PAKE)', + 'psa_crypto_driver_pake_get_peer' : 'defined(PSA_WANT_ALG_SOME_PAKE)', + 'psa_crypto_driver_pake_get_peer_len' : 'defined(PSA_WANT_ALG_SOME_PAKE)', + 'psa_crypto_driver_pake_get_user' : 'defined(PSA_WANT_ALG_SOME_PAKE)', + 'psa_crypto_driver_pake_get_user_len' : 'defined(PSA_WANT_ALG_SOME_PAKE)', + 'psa_pake_abort' : 'defined(PSA_WANT_ALG_SOME_PAKE)', + 'psa_pake_get_implicit_key' : 'defined(PSA_WANT_ALG_SOME_PAKE)', + 'psa_pake_input' : 'defined(PSA_WANT_ALG_SOME_PAKE)', + 'psa_pake_output' : 'defined(PSA_WANT_ALG_SOME_PAKE)', + 'psa_pake_set_password_key' : 'defined(PSA_WANT_ALG_SOME_PAKE)', + 'psa_pake_set_peer' : 'defined(PSA_WANT_ALG_SOME_PAKE)', + 'psa_pake_set_role' : 'defined(PSA_WANT_ALG_SOME_PAKE)', + 'psa_pake_set_user' : 'defined(PSA_WANT_ALG_SOME_PAKE)', + 'psa_pake_setup' : 'defined(PSA_WANT_ALG_SOME_PAKE)', + } + +class PSAWrapper(c_wrapper_generator.Base): + """Generate a C source file containing wrapper functions for PSA Crypto API calls.""" + + _WRAPPER_NAME_PREFIX = 'mbedtls_test_wrap_' + _WRAPPER_NAME_SUFFIX = '' + + _PSA_WRAPPER_INCLUDES = [''] + _DEFAULT_IN_HEADERS = ['crypto.h', 'crypto_extra.h'] + + def __init__(self, + out_h_f: str, + out_c_f: str, + in_headers: Optional[List[str]] = None, + config: PSAWrapperConfiguration = PSAWrapperConfiguration()) -> None: + + super().__init__() + self.out_c_f = out_c_f + self.out_h_f = out_h_f + + self.mbedtls_root = build_tree.guess_mbedtls_root() + self.read_config(config) + self.read_headers(in_headers) + + def read_config(self, cfg: PSAWrapperConfiguration)-> None: + """Configure instance's parameters from a user provided config.""" + + self._cpp_guards = PSAWrapper.parse_def_guards(cfg.cpp_guards) + self._skip_functions = cfg.skipped_functions + self._function_guards.update(cfg.function_guards) + self._not_implemented = cfg.skipped_argument_types + + def read_headers(self, headers: Optional[List[str]]) -> None: + """Reads functions to be wrapped from source header files into self.functions.""" + self.in_headers = self._DEFAULT_IN_HEADERS if headers is None else headers + for header_name in self.in_headers: + header_path = self.rel_path(header_name) + c_parsing_helper.read_function_declarations(self.functions, header_path) + + def rel_path(self, filename: str, path_list: List[str] = ['include', 'psa']) -> str: + """Return the estimated path in relationship to the mbedtls_root. + + The method allows overriding the targetted sub-directory. + Currently the default is set to mbedtls_root/include/psa.""" + # Temporary, while Mbed TLS does not just rely on the TF-PSA-Crypto + # build system to build its crypto library. When it does, the first + # case can just be removed. + if os.path.isdir(os.path.join(self.mbedtls_root, 'tf-psa-crypto')): + path_list = ['tf-psa-crypto' ] + path_list + return os.path.join(self.mbedtls_root, *path_list, filename) + + return os.path.join(self.mbedtls_root, *path_list, filename) + + # Utility Methods + @staticmethod + def parse_def_guards(def_list: Collection[str])-> str: + """ Create define guards. + + Convert an input list of into a C preprocessor + expression of defined() && !defined() syntax string.""" + + output = "" + dl = [("defined({})".format(n) if n[0] != "!" else + "!defined({})".format(n[1:])) + for n in def_list] + + # Split the list in chunks of 2 and add new lines + for i in range(0, len(dl), 2): + output += "{} && {} && \\".format(dl[i], dl[i+1]) + "\n "\ + if i+2 <= len(dl) else dl[i] + return output + + @staticmethod + def _detect_buffer_parameters(arguments: List[c_parsing_helper.ArgumentInfo], + argument_names: List[str]) -> Iterator[BufferParameter]: + """Detect function arguments that are buffers (pointer, size [,length]).""" + types = ['' if arg.suffix else arg.type for arg in arguments] + # pairs = list of (type_of_arg_N, type_of_arg_N+1) + # where each type_of_arg_X is the empty string if the type is an array + # or there is no argument X. + pairs = enumerate(itertools.zip_longest(types, types[1:], fillvalue='')) + for i, t01 in pairs: + if (t01[0] == 'const uint8_t *' or t01[0] == 'uint8_t *') and \ + t01[1] == 'size_t': + yield BufferParameter(i, not t01[0].startswith('const '), + argument_names[i], argument_names[i+1]) + + @staticmethod + def _parameter_should_be_copied(function_name: str, + _buffer_name: Optional[str]) -> bool: + """Whether the specified buffer argument to a PSA function should be copied. + """ + # False-positives that do not need buffer copying + if function_name in ('mbedtls_psa_inject_entropy', + 'psa_crypto_driver_pake_get_password', + 'psa_crypto_driver_pake_get_user', + 'psa_crypto_driver_pake_get_peer'): + return False + + return True + + def _poison_wrap(self, param : BufferParameter, poison: bool, ident_lv = 1) -> str: + """Returns a call to MBEDTLS_TEST_MEMORY_[UN]POISON. + + The output is prefixed with MBEDTLS_TEST_MEMORY_ followed by POISON/UNPOISON + and the input parameter arguments (name, length) + """ + return "{}MBEDTLS_TEST_MEMORY_{}({}, {});\n".format((ident_lv * 4) * ' ', + 'POISON' if poison else 'UNPOISON', + param.buffer_name, param.size_name) + + def _poison_multi_write(self, + out: typing_util.Writable, + buffer_parameters: List['BufferParameter'], + poison: bool) -> None: + """Write poisoning or unpoisoning code for the buffer parameters. + + Write poisoning code if poison is true, unpoisoning code otherwise. + """ + + if not buffer_parameters: + return + out.write('#if !defined(MBEDTLS_PSA_ASSUME_EXCLUSIVE_BUFFERS)\n') + for param in buffer_parameters: + out.write(self._poison_wrap(param, poison)) + out.write('#endif /* !defined(MBEDTLS_PSA_ASSUME_EXCLUSIVE_BUFFERS) */\n') + + # Override parent's methods + def _write_function_call(self, out: typing_util.Writable, + function: c_wrapper_generator.FunctionInfo, + argument_names: List[str]) -> None: + buffer_parameters = list( + param + for param in self._detect_buffer_parameters(function.arguments, + argument_names) + if self._parameter_should_be_copied(function.name, + function.arguments[param.index].name)) + + self._poison_multi_write(out, buffer_parameters, True) + super()._write_function_call(out, function, argument_names) + self._poison_multi_write(out, buffer_parameters, False) + + def _skip_function(self, function: c_wrapper_generator.FunctionInfo) -> bool: + if function.return_type != 'psa_status_t': + return True + if function.name in self._skip_functions: + return True + return False + + def _return_variable_name(self, + function: c_wrapper_generator.FunctionInfo) -> str: + """The name of the variable that will contain the return value.""" + + if function.return_type == 'psa_status_t': + return 'status' + return super()._return_variable_name(function) + + def _write_prologue(self, out: typing_util.Writable, header: bool) -> None: + super()._write_prologue(out, header) + + prologue = [] + if self._cpp_guards: + prologue.append("#if {}".format(self._cpp_guards)) + prologue.append('') + + for include in self._PSA_WRAPPER_INCLUDES: + prologue.append("#include {}".format(include)) + + # Make certain there is an empty line at the end of this section. + for i in [-1, -2]: + if prologue[i] != '': + prologue.append('') + + out.write("\n".join(prologue)) + + def _write_epilogue(self, out: typing_util.Writable, header: bool) -> None: + if self._cpp_guards: + out.write("#endif /* {} */\n\n".format(self._cpp_guards)) + super()._write_epilogue(out, header) + +class PSALoggingWrapper(PSAWrapper, c_wrapper_generator.Logging): + """Generate a C source file containing wrapper functions that log PSA Crypto API calls.""" + + def __init__(self, + stream: str, + out_h_f: str, + out_c_f: str, + in_headers: Optional[List[str]] = None, + config: PSAWrapperConfiguration = PSAWrapperConfiguration()) -> None: + + super().__init__(out_h_f, out_c_f, in_headers, config) + self.set_stream(stream) + + _PRINTF_TYPE_CAST = c_wrapper_generator.Logging._PRINTF_TYPE_CAST.copy() + _PRINTF_TYPE_CAST.update({ + 'mbedtls_svc_key_id_t': 'unsigned', + 'psa_algorithm_t': 'unsigned', + 'psa_drv_slot_number_t': 'unsigned long long', + 'psa_key_derivation_step_t': 'int', + 'psa_key_id_t': 'unsigned', + 'psa_key_slot_number_t': 'unsigned long long', + 'psa_key_lifetime_t': 'unsigned', + 'psa_key_type_t': 'unsigned', + 'psa_key_usage_flags_t': 'unsigned', + 'psa_pake_role_t': 'int', + 'psa_pake_step_t': 'int', + 'psa_status_t': 'int', + }) + + def _printf_parameters(self, typ: str, var: str) -> Tuple[str, List[str]]: + if typ.startswith('const '): + typ = typ[6:] + if typ == 'uint8_t *': + # Skip buffers + return '', [] + if typ.endswith('operation_t *'): + return '', [] + if typ in self._not_implemented: + return '', [] + if typ == 'psa_key_attributes_t *': + return (var + '={id=%u, lifetime=0x%08x, type=0x%08x, bits=%u, alg=%08x, usage=%08x}', + ['(unsigned) psa_get_key_{}({})'.format(field, var) + for field in ['id', 'lifetime', 'type', 'bits', 'algorithm', 'usage_flags']]) + return super()._printf_parameters(typ, var) +