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