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