#!/usr/bin/env python3 # SPDX-License-Identifier: BSD-2-Clause # # Copyright (c) 2015, 2017, 2019, Linaro Limited # import sys import math sig_tee_alg = {'TEE_ALG_RSASSA_PKCS1_PSS_MGF1_SHA256': 0x70414930, 'TEE_ALG_RSASSA_PKCS1_V1_5_SHA256': 0x70004830} enc_tee_alg = {'TEE_ALG_AES_GCM': 0x40000810} enc_key_type = {'SHDR_ENC_KEY_DEV_SPECIFIC': 0x0, 'SHDR_ENC_KEY_CLASS_WIDE': 0x1} TEE_ATTR_RSA_MODULUS = 0xD0000130 TEE_ATTR_RSA_PUBLIC_EXPONENT = 0xD0000230 SHDR_BOOTSTRAP_TA = 1 SHDR_ENCRYPTED_TA = 2 SHDR_SUBKEY = 3 SHDR_MAGIC = 0x4f545348 SHDR_SIZE = 20 SK_HDR_SIZE = 20 EHDR_SIZE = 12 UUID_SIZE = 16 # Use 12 bytes for nonce per recommendation NONCE_SIZE = 12 TAG_SIZE = 16 def value_to_key(db, val): for k, v in db.items(): if v == val: return k def uuid_v5_sha512(namespace_bytes, name): from cryptography.hazmat.primitives import hashes from uuid import UUID h = hashes.Hash(hashes.SHA512()) h.update(namespace_bytes + bytes(name, 'utf-8')) digest = h.finalize() return UUID(bytes=digest[:16], version=5) def name_img_to_str(name_img): return name_img.decode().split('\x00', 1)[0] def uuid_parse(s): from uuid import UUID return UUID(s) def int_parse(str): return int(str, 0) def get_args(): import argparse import textwrap class OnlyOne(argparse.Action): def __call__(self, parser, namespace, values, option_string=None): a = self.dest + '_assigned' if getattr(namespace, a, False): raise argparse.ArgumentError(self, 'Can only be given once') setattr(namespace, a, True) setattr(namespace, self.dest, values) def arg_add_uuid(parser): parser.add_argument( '--uuid', required=True, type=uuid_parse, help='String UUID of the TA') def arg_add_key(parser): parser.add_argument( '--key', required=True, help=''' Name of signing and verification key file (PEM format) or an Amazon Resource Name (arn:) of an AWS KMS asymmetric key. At least public key for the commands digest, stitch, and verify, else a private key''') def arg_add_enc_key(parser): parser.add_argument( '--enc-key', required=False, help='Encryption key string') def arg_add_enc_key_type(parser): parser.add_argument( '--enc-key-type', required=False, default='SHDR_ENC_KEY_DEV_SPECIFIC', choices=list(enc_key_type.keys()), help=''' Encryption key type, Defaults to SHDR_ENC_KEY_DEV_SPECIFIC.''') def arg_add_ta_version(parser): parser.add_argument( '--ta-version', required=False, type=int_parse, default=0, help=''' TA version stored as a 32-bit unsigned integer and used for rollback protection of TA install in the secure database. Defaults to 0.''') def arg_add_sig(parser): parser.add_argument( '--sig', required=True, dest='sigf', help='Name of signature input file, defaults to .sig') def arg_add_dig(parser): parser.add_argument( '--dig', required=True, dest='digf', help='Name of digest output file, defaults to .dig') def arg_add_in(parser): parser.add_argument( '--in', required=False, dest='inf', help=''' Name of application input file, defaults to .stripped.elf''') def arg_add_out(parser): parser.add_argument( '--out', required=True, dest='outf', help='Name of application output file, defaults to .ta') def arg_add_algo(parser): parser.add_argument( '--algo', required=False, choices=list(sig_tee_alg.keys()), default='TEE_ALG_RSASSA_PKCS1_PSS_MGF1_SHA256', help=''' The hash and signature algorithm. Defaults to TEE_ALG_RSASSA_PKCS1_PSS_MGF1_SHA256.''') def arg_add_subkey(parser): parser.add_argument( '--subkey', action=OnlyOne, help='Name of subkey input file') def arg_add_name(parser): parser.add_argument('--name', help='Input name for subspace of a subkey') def arg_add_subkey_uuid_in(parser): parser.add_argument( '--in', required=True, dest='inf', help='Name of subkey input file') def arg_add_max_depth(parser): parser.add_argument( '--max-depth', required=False, type=int_parse, help=''' Max depth of subkeys below this subkey''') def arg_add_name_size(parser): parser.add_argument( '--name-size', required=True, type=int_parse, help=''' Size of (unsigned) input name for subspace of a subkey. Set to 0 to create an identity subkey (a subkey having the same UUID as the next subkey or TA)''') def arg_add_subkey_version(parser): parser.add_argument( '--subkey-version', required=False, type=int_parse, default=0, help='Subkey version used for rollback protection') def arg_add_subkey_in(parser): parser.add_argument( '--in', required=True, dest='inf', help=''' Name of PEM file with the public key of the new subkey''') def arg_add_subkey_out(parser): parser.add_argument( '--out', required=True, dest='outf', help='Name of subkey output file') def get_outf_default(parsed): return str(parsed.uuid) + '.ta' def get_inf_default(parsed): return str(parsed.uuid) + '.stripped.elf' def get_sigf_default(parsed): return str(parsed.uuid) + '.sig' def get_digf_default(parsed): return str(parsed.uuid) + '.dig' def assign_default_value(parsed, attr, func): if hasattr(parsed, attr) and getattr(parsed, attr) is None: setattr(parsed, attr, func(parsed)) parser = argparse.ArgumentParser( description='Sign and encrypt (optional) a Trusted Application ' + ' for OP-TEE.', usage='%(prog)s ...', epilog=' -h for detailed help') subparsers = parser.add_subparsers( title='valid commands, with possible aliases in ()', dest='command', metavar='') parser_sign_enc = subparsers.add_parser( 'sign-enc', prog=parser.prog + ' sign-enc', help='Generate signed and optionally encrypted loadable TA image file') parser_sign_enc.set_defaults(func=command_sign_enc) arg_add_uuid(parser_sign_enc) arg_add_ta_version(parser_sign_enc) arg_add_in(parser_sign_enc) arg_add_out(parser_sign_enc) arg_add_key(parser_sign_enc) arg_add_subkey(parser_sign_enc) arg_add_name(parser_sign_enc) arg_add_enc_key(parser_sign_enc) arg_add_enc_key_type(parser_sign_enc) arg_add_algo(parser_sign_enc) parser_digest = subparsers.add_parser( 'digest', aliases=['generate-digest'], prog=parser.prog + ' digest', formatter_class=argparse.RawDescriptionHelpFormatter, help='Generate loadable TA binary image digest for offline signing', epilog=textwrap.dedent('''\ example offline signing command using OpenSSL for algorithm TEE_ALG_RSASSA_PKCS1_PSS_MGF1_SHA256: base64 -d .dig | \\ openssl pkeyutl -sign -inkey .pem \\ -pkeyopt digest:sha256 -pkeyopt rsa_padding_mode:pss \\ -pkeyopt rsa_pss_saltlen:digest \\ -pkeyopt rsa_mgf1_md:sha256 | \\ base64 > .sig example offline signing command using OpenSSL for algorithm TEE_ALG_RSASSA_PKCS1_V1_5_SHA256: base64 -d .dig | \\ openssl pkeyutl -sign -inkey .pem \\ -pkeyopt digest:sha256 -pkeyopt rsa_padding_mode:pkcs1 | \\ base64 > .sig ''')) parser_digest.set_defaults(func=command_digest) arg_add_uuid(parser_digest) arg_add_ta_version(parser_digest) arg_add_in(parser_digest) arg_add_key(parser_digest) arg_add_enc_key(parser_digest) arg_add_enc_key_type(parser_digest) arg_add_algo(parser_digest) arg_add_dig(parser_digest) parser_stitch = subparsers.add_parser( 'stitch', aliases=['stitch-ta'], prog=parser.prog + ' stich', help='Generate loadable signed and encrypted TA binary image file' + ' from TA raw image and its signature') parser_stitch.set_defaults(func=command_stitch) arg_add_uuid(parser_stitch) arg_add_ta_version(parser_stitch) arg_add_in(parser_stitch) arg_add_key(parser_stitch) arg_add_out(parser_stitch) arg_add_enc_key(parser_stitch) arg_add_enc_key_type(parser_stitch) arg_add_algo(parser_stitch) arg_add_sig(parser_stitch) parser_verify = subparsers.add_parser( 'verify', prog=parser.prog + ' verify', help='Verify signed TA binary') parser_verify.set_defaults(func=command_verify) arg_add_uuid(parser_verify) arg_add_in(parser_verify) arg_add_key(parser_verify) parser_display = subparsers.add_parser( 'display', prog=parser.prog + ' display', help='Parses and displays a signed TA binary') parser_display.set_defaults(func=command_display) arg_add_in(parser_display) parser_subkey_uuid = subparsers.add_parser( 'subkey-uuid', prog=parser.prog + ' subkey-uuid', help='calculate the UUID of next TA or subkey') parser_subkey_uuid.set_defaults(func=command_subkey_uuid) arg_add_subkey_uuid_in(parser_subkey_uuid) arg_add_name(parser_subkey_uuid) parser_sign_subkey = subparsers.add_parser( 'sign-subkey', prog=parser.prog + ' sign-subkey', help='Sign a subkey') parser_sign_subkey.set_defaults(func=command_sign_subkey) arg_add_name(parser_sign_subkey) arg_add_subkey_in(parser_sign_subkey) arg_add_uuid(parser_sign_subkey) arg_add_key(parser_sign_subkey) arg_add_subkey_out(parser_sign_subkey) arg_add_max_depth(parser_sign_subkey) arg_add_name_size(parser_sign_subkey) arg_add_subkey(parser_sign_subkey) arg_add_subkey_version(parser_sign_subkey) arg_add_algo(parser_sign_subkey) argv = sys.argv[1:] if (len(argv) > 0 and argv[0][0] == '-' and argv[0] != '-h' and argv[0] != '--help'): # The default sub-command is 'sign-enc' so add it to the parser # if one is missing argv = ['sign-enc'] + argv parsed = parser.parse_args(argv) if parsed.command is None: parser.print_help() sys.exit(1) # Set a few defaults if defined for the current command assign_default_value(parsed, 'inf', get_inf_default) assign_default_value(parsed, 'outf', get_outf_default) assign_default_value(parsed, 'sigf', get_sigf_default) assign_default_value(parsed, 'digf', get_digf_default) return parsed def load_asymmetric_key_img(data): from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.serialization import ( load_pem_private_key, load_pem_public_key) try: return load_pem_private_key(data, password=None, backend=default_backend()) except ValueError: return load_pem_public_key(data, backend=default_backend()) def load_asymmetric_key(arg_key): if arg_key.startswith('arn:'): from sign_helper_kms import _RSAPrivateKeyInKMS return _RSAPrivateKeyInKMS(arg_key) else: with open(arg_key, 'rb') as f: return load_asymmetric_key_img(f.read()) class BinaryImage: def __init__(self, arg_inf, arg_key): from cryptography.hazmat.primitives import hashes # Exactly what inf is holding isn't determined a this stage if isinstance(arg_inf, str): with open(arg_inf, 'rb') as f: self.inf = f.read() else: self.inf = arg_inf if arg_key is None: self.key = None else: if isinstance(arg_key, str): self.key = load_asymmetric_key(arg_key) else: self.key = arg_key self.sig_size = math.ceil(self.key.key_size / 8) self.chosen_hash = hashes.SHA256() self.hash_size = self.chosen_hash.digest_size def __pack_img(self, img_type, sign_algo): import struct self.sig_algo = sign_algo self.img_type = img_type self.shdr = struct.pack('= getattr(self, 'previous_max_depth')): logger.error('Max depth of previous subkey is {} ' .format(self.previous_max_depth) + 'and the next value must be smaller') sys.exit(1) def int_to_bytes(x: int) -> bytes: return x.to_bytes((x.bit_length() + 8) // 8, 'big') n_bytes = int_to_bytes(subkey_pkey.public_numbers().n) e_bytes = int_to_bytes(subkey_pkey.public_numbers().e) attrs_end_offs = 16 + 5 * 4 + 2 * 3 * 4 shdr_subkey = struct.pack(' 0: # name_size is the previous subkey header name_img = self.inf[offs:offs + name_size] print(' next name: "{}"'.format(name_img_to_str(name_img))) offs += name_size print('Next header at offset: {} (0x{:x})' .format(offs, offs)) shdr = self.inf[offs:offs + SHDR_SIZE] [magic, img_type, img_size, algo_value, hash_size, sig_size] = struct.unpack(' 0: sk_image = BinaryImage(sk_image.next_inf, None) sk_image.parse() if name is None: name = '' self.previous_max_depth = sk_image.max_depth self.name_img = str.encode(name).ljust(sk_image.name_size, b'\0') def write(self, outf): with open(outf, 'wb') as f: if hasattr(self, 'subkey_img'): f.write(self.subkey_img) f.write(self.name_img) f.write(self.shdr) f.write(self.img_digest) f.write(self.sig) if hasattr(self, 'ta_uuid'): f.write(self.ta_uuid) f.write(self.ta_version) if hasattr(self, 'ehdr'): f.write(self.ehdr) f.write(self.nonce) f.write(self.tag) f.write(self.ciphertext) else: f.write(self.img) def load_ta_image(args): ta_image = BinaryImage(args.inf, args.key) if args.enc_key: ta_image.encrypt_ta(args.enc_key, args.enc_key_type, args.algo, args.uuid, args.ta_version) else: ta_image.set_bootstrap_ta(args.algo, args.uuid, args.ta_version) return ta_image def command_sign_enc(args): ta_image = load_ta_image(args) if args.subkey: ta_image.add_subkey(args.subkey, args.name) ta_image.sign() ta_image.write(args.outf) logger.info('Successfully signed application.') def command_sign_subkey(args): image = BinaryImage(args.inf, args.key) if args.subkey: image.add_subkey(args.subkey, args.name) image.set_subkey(args.algo, args.name, args.uuid, args.subkey_version, args.max_depth, args.name_size) image.sign() image.write(args.outf) logger.info('Successfully signed subkey.') def command_digest(args): import base64 ta_image = load_ta_image(args) with open(args.digf, 'wb+') as digfile: digfile.write(base64.b64encode(ta_image.img_digest)) def command_stitch(args): ta_image = load_ta_image(args) ta_image.add_signature(args.sigf) ta_image.verify_signature() ta_image.write(args.outf) logger.info('Successfully applied signature.') def command_verify(args): import uuid image = BinaryImage(args.inf, args.key) next_uuid = None max_depth = -1 while True: image.parse() if hasattr(image, 'subkey_hdr'): # Subkey print('Subkey UUID: {}'.format(uuid.UUID(bytes=image.uuid))) image.verify_signature() image.verify_digest() if next_uuid: if uuid.UUID(bytes=image.uuid) != next_uuid: raise Exception('UUID {} does not match {}' .format(uuid.UUID(bytes=image.uuid), next_uuid)) if max_depth >= 0: if image.max_depth < 0 or image.max_depth >= max_depth: raise Exception('Invalid max_depth {} not less than {}' .format(image.max_depth, max_depth)) max_depth = image.max_depth if len(image.next_inf) == 0: logger.info('Subkey is correctly verified.') return if image.name_size > 0: next_uuid = uuid_v5_sha512(image.uuid, name_img_to_str(image.name_img)) else: next_uuid = image.uuid image = BinaryImage(image.next_inf, image.subkey_key) else: # TA print('TA UUID: {}'.format(uuid.UUID(bytes=image.ta_uuid))) if next_uuid: if uuid.UUID(bytes=image.ta_uuid) != next_uuid: raise Exception('UUID {} does not match {}' .format(uuid.UUID(bytes=image.ta_uuid), next_uuid)) if hasattr(image, 'ciphertext'): if args.enc_key is None: logger.error('--enc_key needed to decrypt TA') sys.exit(1) image.decrypt_ta(args.enc_key) image.verify_signature() image.verify_digest() image.verify_uuid(args.uuid) logger.info('Trusted application is correctly verified.') return def command_display(args): ta_image = BinaryImage(args.inf, None) ta_image.display() def command_subkey_uuid(args): import uuid sk_image = BinaryImage(args.inf, None) sk_image.parse() if not hasattr(sk_image, 'next_inf'): logger.error('Invalid subkey file') sys.exit(1) print('Subkey UUID: {}'.format(uuid.UUID(bytes=sk_image.uuid))) while len(sk_image.next_inf) > 0: sk_image = BinaryImage(sk_image.next_inf, None) sk_image.parse() print('Subkey UUID: {}'.format(uuid.UUID(bytes=sk_image.uuid))) if args.name: if len(args.name) > sk_image.name_size: logger.error('Length of name ({}) '.format(len(args.name)) + 'is larger than max name size ({})' .format(sk_image.name_size)) sys.exit(1) print('Next subkey UUID: {}' .format(uuid_v5_sha512(sk_image.uuid, args.name))) else: print('Next subkey UUID unchanged: {}' .format(uuid.UUID(bytes=sk_image.uuid))) def main(): import logging import os global logger logging.basicConfig() logger = logging.getLogger(os.path.basename(__file__)) args = get_args() args.func(args) if __name__ == "__main__": main()