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 8import math 9 10 11algo = {'TEE_ALG_RSASSA_PKCS1_PSS_MGF1_SHA256': 0x70414930, 12 'TEE_ALG_RSASSA_PKCS1_V1_5_SHA256': 0x70004830} 13 14enc_key_type = {'SHDR_ENC_KEY_DEV_SPECIFIC': 0x0, 15 'SHDR_ENC_KEY_CLASS_WIDE': 0x1} 16 17SHDR_BOOTSTRAP_TA = 1 18SHDR_ENCRYPTED_TA = 2 19SHDR_MAGIC = 0x4f545348 20SHDR_SIZE = 20 21 22 23def uuid_parse(s): 24 from uuid import UUID 25 return UUID(s) 26 27 28def int_parse(str): 29 return int(str, 0) 30 31 32def get_args(logger): 33 from argparse import ArgumentParser, RawDescriptionHelpFormatter 34 import textwrap 35 command_base = ['sign-enc', 'digest', 'stitch', 'verify'] 36 command_aliases_digest = ['generate-digest'] 37 command_aliases_stitch = ['stitch-ta'] 38 command_aliases = command_aliases_digest + command_aliases_stitch 39 command_choices = command_base + command_aliases 40 41 dat = '[' + ', '.join(command_aliases_digest) + ']' 42 sat = '[' + ', '.join(command_aliases_stitch) + ']' 43 44 parser = ArgumentParser( 45 description='Sign and encrypt (optional) a Trusted Application for' + 46 ' OP-TEE.', 47 usage='\n %(prog)s command [ arguments ]\n\n' 48 49 ' command:\n' + 50 ' sign-enc Generate signed and optionally encrypted loadable' + 51 ' TA image file.\n' + 52 ' Takes arguments --uuid, --ta-version, --in, --out,' + 53 ' --key,\n' + 54 ' --enc-key (optional) and' + 55 ' --enc-key-type (optional).\n' + 56 ' digest Generate loadable TA binary image digest' + 57 ' for offline\n' + 58 ' signing. Takes arguments --uuid, --ta-version,' + 59 ' --in, --key,\n' 60 ' --enc-key (optional), --enc-key-type (optional),' + 61 ' --algo (optional) and --dig.\n' + 62 ' stitch Generate loadable signed and encrypted TA binary' + 63 ' image file from\n' + 64 ' TA raw image and its signature. Takes' + 65 ' arguments --uuid, --in, --key, --out,\n' + 66 ' --enc-key (optional), --enc-key-type (optional),\n' + 67 ' --algo (optional) and --sig.\n' + 68 ' verify Verify signed TA binary\n' + 69 ' Takes arguments --uuid, --in, --key\n\n' + 70 ' %(prog)s --help show available commands and arguments\n\n', 71 formatter_class=RawDescriptionHelpFormatter, 72 epilog=textwrap.dedent('''\ 73 If no command is given, the script will default to "sign-enc". 74 75 command aliases: 76 The command \'digest\' can be aliased by ''' + dat + ''' 77 The command \'stitch\' can be aliased by ''' + sat + '\n' + ''' 78 example offline signing command using OpenSSL for algorithm 79 TEE_ALG_RSASSA_PKCS1_PSS_MGF1_SHA256: 80 base64 -d <UUID>.dig | \\ 81 openssl pkeyutl -sign -inkey <KEYFILE>.pem \\ 82 -pkeyopt digest:sha256 -pkeyopt rsa_padding_mode:pss \\ 83 -pkeyopt rsa_pss_saltlen:digest \\ 84 -pkeyopt rsa_mgf1_md:sha256 | \\ 85 base64 > <UUID>.sig\n 86 example offline signing command using OpenSSL for algorithm 87 TEE_ALG_RSASSA_PKCS1_V1_5_SHA256: 88 base64 -d <UUID>.dig | \\ 89 openssl pkeyutl -sign -inkey <KEYFILE>.pem \\ 90 -pkeyopt digest:sha256 -pkeyopt rsa_padding_mode:pkcs1 | \\ 91 base64 > <UUID>.sig 92 ''')) 93 94 parser.add_argument( 95 'command', choices=command_choices, nargs='?', 96 default='sign-enc', 97 help='Command, one of [' + ', '.join(command_base) + ']') 98 parser.add_argument('--uuid', required=True, 99 type=uuid_parse, help='String UUID of the TA') 100 parser.add_argument('--key', required=True, 101 help='Name of signing key file (PEM format)') 102 parser.add_argument('--enc-key', required=False, 103 help='Encryption key string') 104 parser.add_argument( 105 '--enc-key-type', required=False, default='SHDR_ENC_KEY_DEV_SPECIFIC', 106 choices=list(enc_key_type.keys()), 107 help='Encryption key type.\n' + 108 '(SHDR_ENC_KEY_DEV_SPECIFIC or SHDR_ENC_KEY_CLASS_WIDE).\n' + 109 'Defaults to SHDR_ENC_KEY_DEV_SPECIFIC.') 110 parser.add_argument( 111 '--ta-version', required=False, type=int_parse, default=0, 112 help='TA version stored as a 32-bit unsigned integer and used for\n' + 113 'rollback protection of TA install in the secure database.\n' + 114 'Defaults to 0.') 115 parser.add_argument( 116 '--sig', required=False, dest='sigf', 117 help='Name of signature input file, defaults to <UUID>.sig') 118 parser.add_argument( 119 '--dig', required=False, dest='digf', 120 help='Name of digest output file, defaults to <UUID>.dig') 121 parser.add_argument( 122 '--in', required=True, dest='inf', 123 help='Name of application input file, defaults to <UUID>.stripped.elf') 124 parser.add_argument( 125 '--out', required=False, dest='outf', 126 help='Name of application output file, defaults to <UUID>.ta') 127 parser.add_argument('--algo', required=False, choices=list(algo.keys()), 128 default='TEE_ALG_RSASSA_PKCS1_PSS_MGF1_SHA256', 129 help='The hash and signature algorithm, ' + 130 'defaults to TEE_ALG_RSASSA_PKCS1_PSS_MGF1_SHA256. ' + 131 'Allowed values are: ' + 132 ', '.join(list(algo.keys())), metavar='') 133 134 parsed = parser.parse_args() 135 136 # Check parameter combinations 137 138 if parsed.digf is None and \ 139 parsed.outf is not None and \ 140 parsed.command in ['digest'] + command_aliases_digest: 141 logger.error('A digest was requested, but argument --out was given.' + 142 ' Did you mean:\n ' + 143 parser.prog+' --dig ' + parsed.outf + ' ...') 144 sys.exit(1) 145 146 if parsed.digf is not None \ 147 and parsed.outf is not None \ 148 and parsed.command in ['digest'] + command_aliases_digest: 149 logger.warn('A digest was requested, but arguments --dig and ' + 150 '--out were given.\n' + 151 ' --out will be ignored.') 152 153 # Set defaults for optional arguments. 154 155 if parsed.sigf is None: 156 parsed.sigf = str(parsed.uuid)+'.sig' 157 if parsed.digf is None: 158 parsed.digf = str(parsed.uuid)+'.dig' 159 if parsed.inf is None: 160 parsed.inf = str(parsed.uuid)+'.stripped.elf' 161 if parsed.outf is None: 162 parsed.outf = str(parsed.uuid)+'.ta' 163 164 return parsed 165 166 167def main(): 168 from cryptography import exceptions 169 from cryptography.hazmat.backends import default_backend 170 from cryptography.hazmat.primitives import serialization 171 from cryptography.hazmat.primitives import hashes 172 from cryptography.hazmat.primitives.asymmetric import padding 173 from cryptography.hazmat.primitives.asymmetric import rsa 174 from cryptography.hazmat.primitives.asymmetric import utils 175 import base64 176 import logging 177 import os 178 import struct 179 180 logging.basicConfig() 181 logger = logging.getLogger(os.path.basename(__file__)) 182 183 args = get_args(logger) 184 185 with open(args.key, 'rb') as f: 186 data = f.read() 187 188 try: 189 key = serialization.load_pem_private_key(data, password=None, 190 backend=default_backend()) 191 except ValueError: 192 key = serialization.load_pem_public_key(data, 193 backend=default_backend()) 194 195 with open(args.inf, 'rb') as f: 196 img = f.read() 197 198 chosen_hash = hashes.SHA256() 199 h = hashes.Hash(chosen_hash, default_backend()) 200 201 digest_len = chosen_hash.digest_size 202 sig_len = math.ceil(key.key_size / 8) 203 204 img_size = len(img) 205 206 hdr_version = args.ta_version # struct shdr_bootstrap_ta::ta_version 207 208 magic = SHDR_MAGIC 209 if args.enc_key: 210 img_type = SHDR_ENCRYPTED_TA 211 else: 212 img_type = SHDR_BOOTSTRAP_TA 213 214 shdr = struct.pack('<IIIIHH', 215 magic, img_type, img_size, algo[args.algo], 216 digest_len, sig_len) 217 shdr_uuid = args.uuid.bytes 218 shdr_version = struct.pack('<I', hdr_version) 219 220 if args.enc_key: 221 from cryptography.hazmat.primitives.ciphers.aead import AESGCM 222 cipher = AESGCM(bytes.fromhex(args.enc_key)) 223 # Use 12 bytes for nonce per recommendation 224 nonce = os.urandom(12) 225 out = cipher.encrypt(nonce, img, None) 226 ciphertext = out[:-16] 227 # Authentication Tag is always the last 16 bytes 228 tag = out[-16:] 229 230 enc_algo = 0x40000810 # TEE_ALG_AES_GCM 231 flags = enc_key_type[args.enc_key_type] 232 ehdr = struct.pack('<IIHH', 233 enc_algo, flags, len(nonce), len(tag)) 234 235 h.update(shdr) 236 h.update(shdr_uuid) 237 h.update(shdr_version) 238 if args.enc_key: 239 h.update(ehdr) 240 h.update(nonce) 241 h.update(tag) 242 h.update(img) 243 img_digest = h.finalize() 244 245 def write_image_with_signature(sig): 246 with open(args.outf, 'wb') as f: 247 f.write(shdr) 248 f.write(img_digest) 249 f.write(sig) 250 f.write(shdr_uuid) 251 f.write(shdr_version) 252 if args.enc_key: 253 f.write(ehdr) 254 f.write(nonce) 255 f.write(tag) 256 f.write(ciphertext) 257 else: 258 f.write(img) 259 260 def sign_encrypt_ta(): 261 if not isinstance(key, rsa.RSAPrivateKey): 262 logger.error('Provided key cannot be used for signing, ' + 263 'please use offline-signing mode.') 264 sys.exit(1) 265 else: 266 if args.algo == 'TEE_ALG_RSASSA_PKCS1_PSS_MGF1_SHA256': 267 sig = key.sign( 268 img_digest, 269 padding.PSS( 270 mgf=padding.MGF1(chosen_hash), 271 salt_length=digest_len 272 ), 273 utils.Prehashed(chosen_hash) 274 ) 275 elif args.algo == 'TEE_ALG_RSASSA_PKCS1_V1_5_SHA256': 276 sig = key.sign( 277 img_digest, 278 padding.PKCS1v15(), 279 utils.Prehashed(chosen_hash) 280 ) 281 282 if len(sig) != sig_len: 283 raise Exception(("Actual signature length is not equal to ", 284 "the computed one: {} != {}"). 285 format(len(sig), sig_len)) 286 write_image_with_signature(sig) 287 logger.info('Successfully signed application.') 288 289 def generate_digest(): 290 with open(args.digf, 'wb+') as digfile: 291 digfile.write(base64.b64encode(img_digest)) 292 293 def stitch_ta(): 294 try: 295 with open(args.sigf, 'r') as sigfile: 296 sig = base64.b64decode(sigfile.read()) 297 except IOError: 298 if not os.path.exists(args.digf): 299 generate_digest() 300 logger.error('No signature file found. Please sign\n %s\n' + 301 'offline and place the signature at \n %s\n' + 302 'or pass a different location ' + 303 'using the --sig argument.\n', 304 args.digf, args.sigf) 305 sys.exit(1) 306 else: 307 try: 308 if args.algo == 'TEE_ALG_RSASSA_PKCS1_PSS_MGF1_SHA256': 309 key.verify( 310 sig, 311 img_digest, 312 padding.PSS( 313 mgf=padding.MGF1(chosen_hash), 314 salt_length=digest_len 315 ), 316 utils.Prehashed(chosen_hash) 317 ) 318 elif args.algo == 'TEE_ALG_RSASSA_PKCS1_V1_5_SHA256': 319 key.verify( 320 sig, 321 img_digest, 322 padding.PKCS1v15(), 323 utils.Prehashed(chosen_hash) 324 ) 325 except exceptions.InvalidSignature: 326 logger.error('Verification failed, ignoring given signature.') 327 sys.exit(1) 328 329 write_image_with_signature(sig) 330 logger.info('Successfully applied signature.') 331 332 def verify_ta(): 333 # Extract header 334 [magic, 335 img_type, 336 img_size, 337 algo_value, 338 digest_len, 339 sig_len] = struct.unpack('<IIIIHH', img[:SHDR_SIZE]) 340 341 # Extract digest and signature 342 start, end = SHDR_SIZE, SHDR_SIZE + digest_len 343 digest = img[start:end] 344 345 start, end = end, SHDR_SIZE + digest_len + sig_len 346 signature = img[start:end] 347 348 # Extract UUID and TA version 349 start, end = end, end + 16 + 4 350 [uuid, ta_version] = struct.unpack('<16sI', img[start:end]) 351 352 if magic != SHDR_MAGIC: 353 raise Exception("Unexpected magic: 0x{:08x}".format(magic)) 354 355 if img_type != SHDR_BOOTSTRAP_TA: 356 raise Exception("Unsupported image type: {}".format(img_type)) 357 358 if algo_value not in algo.values(): 359 raise Exception('Unrecognized algorithm: 0x{:08x}' 360 .format(algo_value)) 361 362 # Verify signature against hash digest 363 if algo_value == 0x70414930: 364 key.verify( 365 signature, 366 digest, 367 padding.PSS( 368 mgf=padding.MGF1(chosen_hash), 369 salt_length=digest_len 370 ), 371 utils.Prehashed(chosen_hash) 372 ) 373 else: 374 key.verify( 375 signature, 376 digest, 377 padding.PKCS1v15(), 378 utils.Prehashed(chosen_hash) 379 ) 380 381 h = hashes.Hash(chosen_hash, default_backend()) 382 383 # sizeof(struct shdr) 384 h.update(img[:SHDR_SIZE]) 385 386 # sizeof(struct shdr_bootstrap_ta) 387 h.update(img[start:end]) 388 389 # raw image 390 start = end 391 end += img_size 392 h.update(img[start:end]) 393 394 if digest != h.finalize(): 395 raise Exception('Hash digest does not match') 396 397 logger.info('Trusted application is correctly verified.') 398 399 # dispatch command 400 { 401 'sign-enc': sign_encrypt_ta, 402 'digest': generate_digest, 403 'generate-digest': generate_digest, 404 'stitch': stitch_ta, 405 'stitch-ta': stitch_ta, 406 'verify': verify_ta, 407 }.get(args.command, 'sign_encrypt_ta')() 408 409 410if __name__ == "__main__": 411 main() 412