1#!/usr/bin/env python3 2# SPDX-License-Identifier: BSD-2-Clause 3# 4# Copyright (c) 2015, 2017, 2019, Linaro Limited 5# 6 7import sys 8 9 10def uuid_parse(s): 11 from uuid import UUID 12 return UUID(s) 13 14 15def int_parse(str): 16 return int(str, 0) 17 18 19def get_args(logger): 20 from argparse import ArgumentParser, RawDescriptionHelpFormatter 21 import textwrap 22 command_base = ['sign-enc', 'digest', 'stitch'] 23 command_aliases_digest = ['generate-digest'] 24 command_aliases_stitch = ['stitch-ta'] 25 command_aliases = command_aliases_digest + command_aliases_stitch 26 command_choices = command_base + command_aliases 27 28 dat = '[' + ', '.join(command_aliases_digest) + ']' 29 sat = '[' + ', '.join(command_aliases_stitch) + ']' 30 31 parser = ArgumentParser( 32 description='Sign and encrypt (optional) a Tusted Application for' + 33 ' OP-TEE.', 34 usage='\n %(prog)s command [ arguments ]\n\n' 35 36 ' command:\n' + 37 ' sign-enc Generate signed and optionally encrypted loadable' + 38 ' TA image file.\n' + 39 ' Takes arguments --uuid, --ta-version, --in, --out,' + 40 ' --key\n' + 41 ' and --enc-key (optional).\n' + 42 ' digest Generate loadable TA binary image digest' + 43 ' for offline\n' + 44 ' signing. Takes arguments --uuid, --ta-version,' + 45 ' --in, --key,\n' 46 ' --enc-key (optional) and --dig.\n' + 47 ' stitch Generate loadable signed and encrypted TA binary' + 48 ' image file from\n' + 49 ' TA raw image and its signature. Takes' + 50 ' arguments\n' + 51 ' --uuid, --in, --key, --enc-key (optional), --out,' + 52 ' and --sig.\n\n' + 53 ' %(prog)s --help show available commands and arguments\n\n', 54 formatter_class=RawDescriptionHelpFormatter, 55 epilog=textwrap.dedent('''\ 56 If no command is given, the script will default to "sign-enc". 57 58 command aliases: 59 The command \'digest\' can be aliased by ''' + dat + ''' 60 The command \'stitch\' can be aliased by ''' + sat + '\n' + ''' 61 example offline signing command using OpenSSL: 62 base64 -d <UUID>.dig | \\ 63 openssl pkeyutl -sign -inkey <KEYFILE>.pem \\ 64 -pkeyopt digest:sha256 \\ 65 -pkeyopt rsa_padding_mode:pkcs1 | \\ 66 base64 > <UUID>.sig 67 ''')) 68 69 parser.add_argument( 70 'command', choices=command_choices, nargs='?', 71 default='sign-enc', 72 help='Command, one of [' + ', '.join(command_base) + ']') 73 parser.add_argument('--uuid', required=True, 74 type=uuid_parse, help='String UUID of the TA') 75 parser.add_argument('--key', required=True, 76 help='Name of signing key file (PEM format)') 77 parser.add_argument('--enc-key', required=False, 78 help='Encryption key string') 79 parser.add_argument( 80 '--ta-version', required=False, type=int_parse, default=0, 81 help='TA version stored as a 32-bit unsigned integer and used for\n' + 82 'rollback protection of TA install in the secure database.\n' + 83 'Defaults to 0.') 84 parser.add_argument( 85 '--sig', required=False, dest='sigf', 86 help='Name of signature input file, defaults to <UUID>.sig') 87 parser.add_argument( 88 '--dig', required=False, dest='digf', 89 help='Name of digest output file, defaults to <UUID>.dig') 90 parser.add_argument( 91 '--in', required=True, dest='inf', 92 help='Name of application input file, defaults to <UUID>.stripped.elf') 93 parser.add_argument( 94 '--out', required=False, dest='outf', 95 help='Name of application output file, defaults to <UUID>.ta') 96 97 parsed = parser.parse_args() 98 99 # Check parameter combinations 100 101 if parsed.digf is None and \ 102 parsed.outf is not None and \ 103 parsed.command in ['digest'] + command_aliases_digest: 104 logger.error('A digest was requested, but argument --out was given.' + 105 ' Did you mean:\n ' + 106 parser.prog+' --dig ' + parsed.outf + ' ...') 107 sys.exit(1) 108 109 if parsed.digf is not None \ 110 and parsed.outf is not None \ 111 and parsed.command in ['digest'] + command_aliases_digest: 112 logger.warn('A digest was requested, but arguments --dig and ' + 113 '--out were given.\n' + 114 ' --out will be ignored.') 115 116 # Set defaults for optional arguments. 117 118 if parsed.sigf is None: 119 parsed.sigf = str(parsed.uuid)+'.sig' 120 if parsed.digf is None: 121 parsed.digf = str(parsed.uuid)+'.dig' 122 if parsed.inf is None: 123 parsed.inf = str(parsed.uuid)+'.stripped.elf' 124 if parsed.outf is None: 125 parsed.outf = str(parsed.uuid)+'.ta' 126 127 return parsed 128 129 130def main(): 131 from Cryptodome.Signature import pss 132 from Cryptodome.Hash import SHA256 133 from Cryptodome.PublicKey import RSA 134 import base64 135 import logging 136 import os 137 import struct 138 139 logging.basicConfig() 140 logger = logging.getLogger(os.path.basename(__file__)) 141 142 args = get_args(logger) 143 144 with open(args.key, 'rb') as f: 145 key = RSA.importKey(f.read()) 146 147 with open(args.inf, 'rb') as f: 148 img = f.read() 149 150 h = SHA256.new() 151 152 digest_len = h.digest_size 153 sig_len = key.size_in_bytes() 154 155 img_size = len(img) 156 157 hdr_version = args.ta_version # struct shdr_bootstrap_ta::ta_version 158 159 magic = 0x4f545348 # SHDR_MAGIC 160 if args.enc_key: 161 img_type = 2 # SHDR_ENCRYPTED_TA 162 else: 163 img_type = 1 # SHDR_BOOTSTRAP_TA 164 algo = 0x70414930 # TEE_ALG_RSASSA_PKCS1_PSS_MGF1_SHA256 165 166 shdr = struct.pack('<IIIIHH', 167 magic, img_type, img_size, algo, digest_len, sig_len) 168 shdr_uuid = args.uuid.bytes 169 shdr_version = struct.pack('<I', hdr_version) 170 171 if args.enc_key: 172 from Cryptodome.Cipher import AES 173 cipher = AES.new(bytearray.fromhex(args.enc_key), AES.MODE_GCM) 174 ciphertext, tag = cipher.encrypt_and_digest(img) 175 176 enc_algo = 0x40000810 # TEE_ALG_AES_GCM 177 flags = 0 # SHDR_ENC_KEY_DEV_SPECIFIC 178 ehdr = struct.pack('<IIHH', 179 enc_algo, flags, len(cipher.nonce), len(tag)) 180 181 h.update(shdr) 182 h.update(shdr_uuid) 183 h.update(shdr_version) 184 if args.enc_key: 185 h.update(ehdr) 186 h.update(cipher.nonce) 187 h.update(tag) 188 h.update(img) 189 img_digest = h.digest() 190 191 def write_image_with_signature(sig): 192 with open(args.outf, 'wb') as f: 193 f.write(shdr) 194 f.write(img_digest) 195 f.write(sig) 196 f.write(shdr_uuid) 197 f.write(shdr_version) 198 if args.enc_key: 199 f.write(ehdr) 200 f.write(cipher.nonce) 201 f.write(tag) 202 f.write(ciphertext) 203 else: 204 f.write(img) 205 206 def sign_encrypt_ta(): 207 if not key.has_private(): 208 logger.error('Provided key cannot be used for signing, ' + 209 'please use offline-signing mode.') 210 sys.exit(1) 211 else: 212 signer = pss.new(key) 213 sig = signer.sign(h) 214 if len(sig) != sig_len: 215 raise Exception(("Actual signature length is not equal to ", 216 "the computed one: {} != {}"). 217 format(len(sig), sig_len)) 218 write_image_with_signature(sig) 219 logger.info('Successfully signed application.') 220 221 def generate_digest(): 222 with open(args.digf, 'wb+') as digfile: 223 digfile.write(base64.b64encode(img_digest)) 224 225 def stitch_ta(): 226 try: 227 with open(args.sigf, 'r') as sigfile: 228 sig = base64.b64decode(sigfile.read()) 229 except IOError: 230 if not os.path.exists(args.digf): 231 generate_digest() 232 logger.error('No signature file found. Please sign\n %s\n' + 233 'offline and place the signature at \n %s\n' + 234 'or pass a different location ' + 235 'using the --sig argument.\n', 236 args.digf, args.sigf) 237 sys.exit(1) 238 else: 239 verifier = pss.new(key) 240 if verifier.verify(h, sig): 241 write_image_with_signature(sig) 242 logger.info('Successfully applied signature.') 243 else: 244 logger.error('Verification failed, ignoring given signature.') 245 sys.exit(1) 246 247 # dispatch command 248 { 249 'sign-enc': sign_encrypt_ta, 250 'digest': generate_digest, 251 'generate-digest': generate_digest, 252 'stitch': stitch_ta, 253 'stitch-ta': stitch_ta 254 }.get(args.command, 'sign_encrypt_ta')() 255 256 257if __name__ == "__main__": 258 main() 259