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