Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: code wrappers subpackage #25

Merged
merged 30 commits into from
Jul 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
659d3ed
c_parsing_helper: Added __str__() to FunctionInfo, ArgInfo.
minosgalanakis Jun 7, 2024
44f3415
code_wrapper: Created placeholder directory.
minosgalanakis Jun 7, 2024
7e43fcd
code_wrapper: Whitespace fixes
minosgalanakis Jun 10, 2024
b3cc24b
c_parsing_helper: Adjusted FunctionInfo helper methods.
minosgalanakis Jun 13, 2024
890f936
Introduced psa_buffer modules
minosgalanakis Jun 13, 2024
6b2c6d0
code_wrapper: Fixed script permissions
minosgalanakis Jun 17, 2024
ad45da8
code_wrappers: Updated import statements
minosgalanakis Jun 13, 2024
3ab7aa4
c_parsing_helper: Adjusted regex and documentation.
minosgalanakis Jun 17, 2024
2ce5c04
c_wrapper_generator: Refactored _write_prologue()
minosgalanakis Jun 21, 2024
483507a
c_wrapper_generator: Refactored _write_epilogue()
minosgalanakis Jun 21, 2024
eefadcf
psa_wrapper.py: Fixed executable permissions
minosgalanakis Jun 21, 2024
7d93bad
c_wrapper_generator: Adjusted new-line logic
minosgalanakis Jun 21, 2024
9280214
code_wrapper: Minor fixes
minosgalanakis Jun 28, 2024
462927f
code_wrapper: Introduced PSAWrapperCFG class.
minosgalanakis Jul 4, 2024
425fb3b
psa_wrapper: Adjusted header parsing logic.
minosgalanakis Jul 4, 2024
8eb02d1
code_wrapper: Doc-string fixes.
minosgalanakis Jul 4, 2024
fdb9b5b
psa_wrapper: Minor refactoring.
minosgalanakis Jul 15, 2024
4af4535
c_wrapper_generator.py: Migrated block-strings to fstrings.
minosgalanakis Jul 15, 2024
5c07d10
c_wrapper_generator: Added blockstr_format utility function.
minosgalanakis Jul 15, 2024
eea3fe3
c_wrapper_generator: Optimised prologue spacing.
minosgalanakis Jul 15, 2024
03bfc58
psa_wrapper: Moved input_headers from configuration class.
minosgalanakis Jul 15, 2024
0ef9b2d
c_wrapper_generator: Refactored blockstr_format().
minosgalanakis Jul 17, 2024
0c8454a
psa_wrapper: Adjusted input parameter default.
minosgalanakis Jul 17, 2024
b62fd8e
generate_psa_wrappers: Reintroduced main() function.
minosgalanakis Jul 17, 2024
28bb689
psa_wrapper: Changed naming convention for class variables.
minosgalanakis Jul 18, 2024
6ea9a8d
c_wrapper_generator: Refactored strip_indentation().
minosgalanakis Jul 19, 2024
bd6648f
psa_wrapper: Set PSAWrapperConfiguration variables to instance vars.
minosgalanakis Jul 19, 2024
d17a507
c_wrapper_generator: Minor fixes to strip_indentation().
minosgalanakis Jul 22, 2024
01343e2
c_wrapper_generator: _function_guards are set as instance vars.
minosgalanakis Jul 22, 2024
c6b4647
c_wrapper_generator: Fixed an edge-case in strip_indentation().
minosgalanakis Jul 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
251 changes: 7 additions & 244 deletions scripts/generate_psa_wrappers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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:
Expand Down
39 changes: 38 additions & 1 deletion scripts/mbedtls_framework/c_parsing_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ def __init__(self, decl: str) -> None:
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."""
Expand All @@ -60,18 +62,38 @@ def __init__(self, #pylint: disable=too-many-arguments
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 /*...*/
Expand Down Expand Up @@ -112,6 +134,7 @@ def read_logical_lines(filename: str) -> Iterator[Tuple[int, str]]:

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)
Expand All @@ -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
Loading