mirror of
https://github.com/Mbed-TLS/mbedtls-framework.git
synced 2026-07-30 15:57:46 +00:00
Merge pull request #25 from minosgalanakis/feature/code_wrappers
Feature: code wrappers subpackage
This commit is contained in:
@@ -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 <psa/crypto.h>
|
||||
|
||||
#include <test/memory.h>
|
||||
#include <test/psa_crypto_helpers.h>
|
||||
#include <test/psa_test_wrappers.h>
|
||||
"""
|
||||
.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:
|
||||
|
||||
@@ -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<type>\w+) (?P<name>\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
|
||||
|
||||
@@ -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 = ['<mbedtls/build_info.h>']
|
||||
|
||||
# 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 <mbedtls/build_info.h>
|
||||
""")
|
||||
#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."""
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 = ['<psa/crypto.h>',
|
||||
'<test/memory.h>',
|
||||
'<test/psa_crypto_helpers.h>',
|
||||
'<test/psa_test_wrappers.h>']
|
||||
|
||||
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)
|
||||
|
||||
@@ -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 = ['<psa/crypto.h>']
|
||||
_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)
|
||||
|
||||
Reference in New Issue
Block a user