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 Crypto.Signature import PKCS1_v1_5 132 from Crypto.Hash import SHA256 133 from Crypto.PublicKey import RSA 134 from Crypto.Util.number import ceil_div 135 import base64 136 import logging 137 import os 138 import struct 139 140 logging.basicConfig() 141 logger = logging.getLogger(os.path.basename(__file__)) 142 143 args = get_args(logger) 144 145 with open(args.key, 'rb') as f: 146 key = RSA.importKey(f.read()) 147 148 with open(args.inf, 'rb') as f: 149 img = f.read() 150 151 h = SHA256.new() 152 153 digest_len = h.digest_size 154 try: 155 # This works in pycrypto 156 sig_len = ceil_div(key.size() + 1, 8) 157 except NotImplementedError: 158 # ... and this one - in pycryptodome 159 sig_len = key.size_in_bytes() 160 161 img_size = len(img) 162 163 hdr_version = args.ta_version # struct shdr_bootstrap_ta::ta_version 164 165 magic = 0x4f545348 # SHDR_MAGIC 166 if args.enc_key: 167 img_type = 2 # SHDR_ENCRYPTED_TA 168 else: 169 img_type = 1 # SHDR_BOOTSTRAP_TA 170 algo = 0x70004830 # TEE_ALG_RSASSA_PKCS1_V1_5_SHA256 171 172 shdr = struct.pack('<IIIIHH', 173 magic, img_type, img_size, algo, digest_len, sig_len) 174 shdr_uuid = args.uuid.bytes 175 shdr_version = struct.pack('<I', hdr_version) 176 177 if args.enc_key: 178 from Cryptodome.Cipher import AES 179 cipher = AES.new(bytearray.fromhex(args.enc_key), AES.MODE_GCM) 180 ciphertext, tag = cipher.encrypt_and_digest(img) 181 182 enc_algo = 0x40000810 # TEE_ALG_AES_GCM 183 flags = 0 # SHDR_ENC_KEY_DEV_SPECIFIC 184 ehdr = struct.pack('<IIHH', 185 enc_algo, flags, len(cipher.nonce), len(tag)) 186 187 h.update(shdr) 188 h.update(shdr_uuid) 189 h.update(shdr_version) 190 if args.enc_key: 191 h.update(ehdr) 192 h.update(cipher.nonce) 193 h.update(tag) 194 h.update(img) 195 img_digest = h.digest() 196 197 def write_image_with_signature(sig): 198 with open(args.outf, 'wb') as f: 199 f.write(shdr) 200 f.write(img_digest) 201 f.write(sig) 202 f.write(shdr_uuid) 203 f.write(shdr_version) 204 if args.enc_key: 205 f.write(ehdr) 206 f.write(cipher.nonce) 207 f.write(tag) 208 f.write(ciphertext) 209 else: 210 f.write(img) 211 212 def sign_encrypt_ta(): 213 if not key.has_private(): 214 logger.error('Provided key cannot be used for signing, ' + 215 'please use offline-signing mode.') 216 sys.exit(1) 217 else: 218 signer = PKCS1_v1_5.new(key) 219 sig = signer.sign(h) 220 if len(sig) != sig_len: 221 raise Exception(("Actual signature length is not equal to ", 222 "the computed one: {} != {}"). 223 format(len(sig), sig_len)) 224 write_image_with_signature(sig) 225 logger.info('Successfully signed application.') 226 227 def generate_digest(): 228 with open(args.digf, 'wb+') as digfile: 229 digfile.write(base64.b64encode(img_digest)) 230 231 def stitch_ta(): 232 try: 233 with open(args.sigf, 'r') as sigfile: 234 sig = base64.b64decode(sigfile.read()) 235 except IOError: 236 if not os.path.exists(args.digf): 237 generate_digest() 238 logger.error('No signature file found. Please sign\n %s\n' + 239 'offline and place the signature at \n %s\n' + 240 'or pass a different location ' + 241 'using the --sig argument.\n', 242 args.digf, args.sigf) 243 sys.exit(1) 244 else: 245 verifier = PKCS1_v1_5.new(key) 246 if verifier.verify(h, sig): 247 write_image_with_signature(sig) 248 logger.info('Successfully applied signature.') 249 else: 250 logger.error('Verification failed, ignoring given signature.') 251 sys.exit(1) 252 253 # dispatch command 254 { 255 'sign-enc': sign_encrypt_ta, 256 'digest': generate_digest, 257 'generate-digest': generate_digest, 258 'stitch': stitch_ta, 259 'stitch-ta': stitch_ta 260 }.get(args.command, 'sign_encrypt_ta')() 261 262 263if __name__ == "__main__": 264 main() 265