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/encrypt_data.py b/tools/modules/encrypt_data.py
new file mode 100644
index 0000000..e43c9fa
--- /dev/null
+++ b/tools/modules/encrypt_data.py
@@ -0,0 +1,105 @@
+#!/usr/bin/env python3
+#-------------------------------------------------------------------------------
+# SPDX-FileCopyrightText: Copyright The TrustedFirmware-M Contributors
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+#-------------------------------------------------------------------------------
+
+from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
+
+import key_derivation
+
+import argparse
+import logging
+logger = logging.getLogger("TF-M")
+from arg_utils import *
+import secrets
+
+def _encrypt_aes_ctr(data : bytes,
+                     iv : bytes,
+                     key : bytes
+                     ) -> bytes:
+    assert len(iv) >= 8
+
+    if len(iv) < 16:
+        iv += bytes(16 - len(iv))
+
+    c = Cipher(algorithms.AES(key), modes.CTR(iv))
+    return c.encryptor().update(data)
+
+encrypt_algs = {
+    "AES_CTR": _encrypt_aes_ctr,
+}
+
+def add_arguments(parser : argparse.ArgumentParser,
+                  prefix : str = "",
+                  required : bool = True,
+                  ) -> None:
+    add_prefixed_argument(parser, "encrypt_key", prefix, help="encryption key input file",
+                          required=required, type=arg_type_bytes)
+    add_prefixed_argument(parser, "encrypt_alg", prefix, help="encryption algorithm",
+                          choices=encrypt_algs.keys(), required=required)
+    add_prefixed_argument(parser, "iv", prefix, help="encryption intial value",
+                          type=arg_type_bytes, required=False)
+
+    key_derivation.add_arguments(parser, prefix, required=False)
+
+def parse_args(args : argparse.Namespace,
+               prefix : str = "",
+               ) -> dict:
+    out = parse_args_automatically(args, ["encrypt_alg", "encrypt_key", "iv"], prefix)
+    out["kdf_args"] = key_derivation.parse_args(args)
+
+    return out
+
+def encrypt_data(data : bytes,
+                 encrypt_key : bytes,
+                 encrypt_alg : str,
+                 kdf_args : dict,
+                 iv : bytes = None,
+                 **kwargs,
+                 ) -> (bytes, bytes):
+    if not data:
+        return iv, data
+
+    assert(encrypt_key)
+    assert(encrypt_alg)
+
+    if kdf_args:
+        # FixMe: If RSE_SYMMETRIC_PROVISIONING is OFF, should we use directly
+        #        the encrypt_key without deriving another one?
+        encrypt_key = key_derivation.derive_symmetric_key(**kdf_args,
+                                                          input_key = encrypt_key,
+                                                          length = 32
+                                                          )
+
+    if not iv:
+        iv = secrets.token_bytes(12) + int(0).to_bytes(4, 'little')
+
+    encrypted_data = encrypt_algs[encrypt_alg](
+                                  data = data,
+                                  key = encrypt_key,
+                                  iv = iv)
+    return iv, encrypted_data
+
+script_description = """
+Encrypt some data
+"""
+if __name__ == "__main__":
+    import argparse
+
+    parser = argparse.ArgumentParser(allow_abbrev=False,
+                                     formatter_class=argparse.ArgumentDefaultsHelpFormatter,
+                                     description=script_description)
+    parser.add_argument("--log_level", help="log level", required=False, default="ERROR", choices=logging._levelToName.values())
+    parser.add_argument("--data", help="data to sign", type=arg_type_bytes, required=True)
+
+    add_arguments(parser, required=True)
+
+    args = parser.parse_args()
+    logger.setLevel(args.log_level)
+    config = parse_args(args)
+    config |= parse_args_automatically(args, ["data"])
+
+    print(encrypt_data(**config)[1].hex())