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/sign_data.py b/tools/modules/sign_data.py
new file mode 100644
index 0000000..b102fb2
--- /dev/null
+++ b/tools/modules/sign_data.py
@@ -0,0 +1,160 @@
+#!/usr/bin/env python3
+#-------------------------------------------------------------------------------
+# SPDX-FileCopyrightText: Copyright The TrustedFirmware-M Contributors
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+#-------------------------------------------------------------------------------
+
+from cryptography.hazmat.primitives.asymmetric import utils, ec
+from cryptography.hazmat.primitives import hashes
+from cryptography.hazmat.primitives.serialization import load_pem_private_key, Encoding, PublicFormat
+
+import pyhsslms
+
+from cryptography.hazmat.primitives.ciphers.aead import AESCCM
+import secrets
+
+import argparse
+import logging
+logger = logging.getLogger("TF-M")
+from arg_utils import *
+
+def _asn1_sig_to_raw(sig : bytes , curve : ec.EllipticCurve) -> bytes:
+    point_size = curve.key_size // 8
+    r, s = utils.decode_dss_signature(sig)
+    return bytes(0).join([x.to_bytes(point_size, byteorder="big") for x in [r, s]])
+
+def _pubkey_ecdsa(key : str) -> bytes:
+    with open(key, "rb") as f:
+        key_data = f.read()
+
+    priv_key = load_pem_private_key(key_data, password=None)
+    return priv_key.public_key().public_bytes(Encoding.DER, PublicFormat.SubjectPublicKeyInfo)
+
+def _pubkey_lms(key : str) -> bytes:
+    return pyhsslms.HssLmsPublicKey(key[:-4]).hss_pub.pub.serialize()
+
+pubkey_algs = {
+    "ECDSA": _pubkey_ecdsa,
+    "LMS":   _pubkey_lms,
+}
+
+def _sign_ecdsa(data : bytes,
+                key : str,
+                curve : ec.EllipticCurve = None,
+                hash_alg : hashes.HashAlgorithm = None,
+                **kwargs : {},
+                ) -> bytes:
+    with open(key, "rb") as f:
+        key_data = f.read()
+
+    priv_key = load_pem_private_key(key_data, password=None)
+
+    hash_alg_defaults = {
+        ec.SECP256R1: hashes.SHA256,
+        ec.SECP384R1: hashes.SHA384,
+    }
+
+    if curve:
+        assert curve.name == priv_key.curve.name, "Key curve {} does not match required curve {}".format(priv_key.curve.name, curve.name)
+
+    if not hash_alg:
+        hash_alg = hash_alg_defaults[type(priv_key.curve)]
+
+    logger.info("Signing with ECDSA key {} (curve {}) and hash {}".format(key, priv_key.curve.name, hash_alg.name))
+
+    digest = hashes.Hash(hash_alg())
+    digest.update(data)
+    logger.info("Signing hash {}".format(digest.finalize().hex()))
+
+    asn1_sig = priv_key.sign(data, ec.ECDSA(hash_alg()))
+
+    return _asn1_sig_to_raw(asn1_sig, priv_key.curve)
+
+def _sign_lms(data : bytes,
+              key : str,
+              **kwargs : {},
+              ) -> bytes:
+    priv_key = pyhsslms.HssLmsPrivateKey(key[:-4])
+    logger.info("Signing with LMS key {}".format(key))
+    return priv_key.sign(data)[4:]
+
+def _sign_aes_ccm(data : bytes,
+                  key : str,
+                  iv : bytes,
+                  **kwargs : {},
+                  ) -> bytes:
+    with open(key, "rb") as f:
+        key_data = f.read()
+
+    if not iv:
+        iv = secrets.token_bytes(12)
+
+    return AESCCM(key_data).encrypt(iv, plaintext, data)
+
+def sign_data(data : bytes,
+              sign_key : str,
+              sign_alg : str,
+              sign_hash_alg : str = None,
+              **kwargs,
+              ) -> bytes:
+    assert(sign_key)
+    assert(sign_alg)
+
+    sig = sign_algs[sign_alg](data = data,
+                              key = sign_key,
+                              hash_alg = sign_hash_alg,
+                              **kwargs)
+
+    return sig
+
+def get_pubkey(sign_key : str,
+               sign_alg : str,
+               **kwargs
+               ) -> bytes:
+    return pubkey_algs[sign_alg](sign_key)
+
+sign_algs = {
+    "ECDSA":     _sign_ecdsa,
+    "LMS":       _sign_lms,
+}
+
+def add_arguments(parser : argparse.ArgumentParser,
+                  prefix : str = "",
+                  required : bool = True,
+                  ) -> None:
+    add_prefixed_argument(parser, "sign_key", prefix, help="signing key input file",
+                          type=str, required=required)
+    add_prefixed_argument(parser, "sign_alg", prefix, help="signing algorithm",
+                          choices=sign_algs.keys(), required=required)
+    add_prefixed_argument(parser, "sign_hash_alg", prefix, help="signing hash algorithm",
+                          type=arg_type_hash, required=False)
+
+def parse_args(args : argparse.Namespace,
+               prefix : str = "",
+               ) -> dict:
+    out = parse_args_automatically(args, ["sign_key", "sign_alg", "sign_hash_alg"], prefix)
+    return out
+
+script_description = """
+Sign 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(sign_data(**config).hex())