Tools: Add python modules
Add generic python modules to TF-M to support build and image packaging
tools.
* arg_utils provides helpers to deal with argparse arguments
* c_include gets the list of include paths for a file from the
`compile_commands.json` build database
* c_macro includes a python implementation of (most of) the C
preprocessor.
* c_struct is a libclang-based evaluator of C datastructures (Including
enums) which can be used to generate python representations of nested
C datastructures which rely on complex macro configuration.
* crypto_conversion_utils provides helpers to convert various types of
crypto keys to different and convert string representations of
algorithms and hash functions to their python objects
* encrypt_data provides functions to encrypt bytes() objects
* file_loader provides automatic handler functions for various filetypes
based on their extensions, primarily useful for loading crypto keys
* key_derivation provides a python implementation of HKDF and a
SP200-108 CMAC KDF, both matching the TF-M/MbedTLS/CC3XX implementation
* sign_data provides functions to perform symmetric and asymmetric
signatures of bytes() objects
* sign_then_encrypt_data provides combined signing and encryption,
either via symmetric AEAD modes or a combination of the sign_data and
encrypt_data modules
* struct_pack provides helper functions for packing bytes objects
together.
Change-Id: I858dd8ef69c9069ec0a44e4ad3f9a1d70cc5d4da
Signed-off-by: Raef Coles <raef.coles@arm.com>
diff --git a/tools/modules/arg_utils.py b/tools/modules/arg_utils.py
new file mode 100644
index 0000000..d359a70
--- /dev/null
+++ b/tools/modules/arg_utils.py
@@ -0,0 +1,184 @@
+#!/usr/bin/env python3
+#-------------------------------------------------------------------------------
+# SPDX-FileCopyrightText: Copyright The TrustedFirmware-M Contributors
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+#-------------------------------------------------------------------------------
+
+import argparse
+
+import logging
+logger = logging.getLogger("TF-M")
+
+from elftools.elf.elffile import ELFFile
+from file_loader import load_bytes_from_file
+
+from c_struct import C_enum
+
+from os.path import isfile
+
+from crypto_conversion_utils import convert_curve_define, convert_hash_define
+
+prefix_sep = ":"
+
+def incompatible_arg(args : argparse.Namespace,
+ arg1 : str,
+ arg2 : str
+ ):
+ assert not (get_arg(args, arg1) and get_arg(args, arg2)), "Incompatible arguments {} and {} cannot both be specified".format(arg1, arg2)
+
+def add_prefixed_argument(parser: argparse.ArgumentParser,
+ arg_name : str,
+ prefix : str = "",
+ **args):
+
+ if "--" + arg_name not in parser._option_string_actions.keys():
+ parser.add_argument("--" + join_prefix(arg_name, prefix), **args)
+
+def _arg_type_enum(arg : str, enum : C_enum):
+ assert arg in enum.dict.keys(), "Invalid enum value {} not in {}".format(arg, list(enum.dict.keys()))
+ return enum.dict[arg]
+
+def arg_type_enum(enum : C_enum):
+ return lambda x: _arg_type_enum(x, enum)
+
+
+def add_prefixed_enum_argument(parser : argparse.ArgumentParser,
+ enum : C_enum,
+ arg_name : str,
+ default : str = None,
+ prefix : str = "",
+ **kwargs):
+
+ if default:
+ assert default in enum.dict.keys()
+
+ return add_prefixed_argument(parser=parser,
+ arg_name = arg_name,
+ type=arg_type_enum(enum),
+ prefix=prefix,
+ default=default,
+ metavar=enum.name,
+ choices=enum.dict.values(),
+ **kwargs)
+
+
+def join_prefix(arg : str, prefix : str):
+ assert(arg)
+
+ if not prefix:
+ return arg
+
+ if prefix_sep not in arg:
+ return prefix + prefix_sep + arg
+ else:
+ #Insert the prefix at the right of current prefixes
+ curr_prefix, arg = arg.rsplit(prefix_sep, 1)
+ return curr_prefix + prefix_sep + prefix + prefix_sep + arg
+
+def is_prefixed(arg : str, prefix : str):
+ return prefix + prefix_sep in arg
+
+def unjoin_prefix(arg : str, prefix : str):
+ if not prefix:
+ return arg
+
+ assert(is_prefixed(arg, prefix))
+
+ return arg.rsplit(prefix + prefix_sep, 1)[1]
+
+def get_arg(args : argparse.Namespace,
+ arg_name : str,
+ prefix : str = "",
+ ):
+ if hasattr(args, join_prefix(arg_name, prefix)):
+ return getattr(args, join_prefix(arg_name, prefix))
+ else:
+ logging.info("Arg {} value not set".format(arg_name))
+ return None
+
+def parse_args_automatically(args : argparse.Namespace,
+ arg_names : [str],
+ prefix : str = "",
+ ) -> dict:
+ return {a: get_arg(args, a, prefix) for a in arg_names if get_arg(args, a, prefix) is not None}
+
+def arg_type_version(arg):
+ assert (len(arg.split(".")) == 3), "Invalid image version format (must be \"x.y.z(+q)?\""
+ return [int(x) for x in arg.replace("+", ".").split(".")]
+
+def _arg_type_elf_section(arg : str,
+ section_name : str,
+ ) -> bytes:
+ if type(section_name) == str:
+ section_name = [section_name]
+
+ out = []
+
+ with ELFFile(open(arg, 'rb')) as elffile:
+ for s in section_name:
+ section = elffile.get_section_by_name(s)
+ if section:
+ out.append(section.data())
+ else:
+ out.append(None)
+ logging.warning("ELF file {} does not contain section {}".format(arg, s))
+ return out
+
+def arg_type_elf_section(section_name : str):
+ return lambda x: _arg_type_elf_section(x, section_name)
+
+def arg_type_elf(arg : str) -> bytes:
+
+ return {"code": code_section.data(), "data": data_section.data() if data_section else None}
+
+def arg_type_filepath(arg : str) -> str:
+ assert isfile(arg), "File {} does not exist".format(arg)
+ return arg
+
+def arg_type_bytes_from_file(arg : str) -> (bytes, bytes):
+ return load_bytes_from_file(arg)
+
+def arg_type_hex(arg : str) -> int:
+ assert(arg)
+ return bytes.fromhex(arg)
+
+def arg_type_bytes(arg : str) -> bytes:
+ try:
+ return arg_type_bytes_from_file(arg)
+ except FileNotFoundError:
+ assert len(arg) % 2 == 0, "Hex values must be a multiple of two characters long"
+ return arg_type_hex(arg.replace("0x", ""))
+
+def arg_type_bytes_output_file(arg : str):
+ return open(arg, "wb")
+
+def arg_type_text_output_file(arg : str):
+ return open(arg, "wt")
+
+def arg_type_hash(arg : str):
+ return convert_hash_define(arg)
+
+def arg_type_curve(arg : str):
+ return convert_curve_define(arg)
+
+def arg_type_bool(arg : str):
+ larg = arg.lower()
+
+ assert larg in ["true", "false", "on", "off"], "Invalid boolean value {}".format(arg)
+
+ return larg in ["true", "on"]
+
+def pre_parse_args(parser : argparse.ArgumentParser,
+ name : str,
+ **kwargs : dict):
+ pre_arg_parser = argparse.ArgumentParser(add_help=False)
+
+ pre_arg_parser.add_argument("--" + name, required=True, **kwargs)
+
+ if "--" + name not in parser._option_string_actions.keys():
+ parser.add_argument("--" + name, required=True, **kwargs)
+
+ parsed, _ = pre_arg_parser.parse_known_args()
+ return getattr(parsed, name)