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 parser_sign_enc.set_defaults(func=command_sign_enc) 138 arg_add_uuid(parser_sign_enc) 139 arg_add_ta_version(parser_sign_enc) 140 arg_add_in(parser_sign_enc) 141 arg_add_out(parser_sign_enc) 142 arg_add_key(parser_sign_enc) 143 arg_add_enc_key(parser_sign_enc) 144 arg_add_enc_key_type(parser_sign_enc) 145 arg_add_algo(parser_sign_enc) 146 147 parser_digest = subparsers.add_parser( 148 'digest', aliases=['generate-digest'], prog=parser.prog + ' digest', 149 formatter_class=argparse.RawDescriptionHelpFormatter, 150 help='Generate loadable TA binary image digest for offline signing', 151 epilog=textwrap.dedent('''\ 152 example offline signing command using OpenSSL for algorithm 153 TEE_ALG_RSASSA_PKCS1_PSS_MGF1_SHA256: 154 base64 -d <UUID>.dig | \\ 155 openssl pkeyutl -sign -inkey <KEYFILE>.pem \\ 156 -pkeyopt digest:sha256 -pkeyopt rsa_padding_mode:pss \\ 157 -pkeyopt rsa_pss_saltlen:digest \\ 158 -pkeyopt rsa_mgf1_md:sha256 | \\ 159 base64 > <UUID>.sig 160 161 example offline signing command using OpenSSL for algorithm 162 TEE_ALG_RSASSA_PKCS1_V1_5_SHA256: 163 base64 -d <UUID>.dig | \\ 164 openssl pkeyutl -sign -inkey <KEYFILE>.pem \\ 165 -pkeyopt digest:sha256 -pkeyopt rsa_padding_mode:pkcs1 | \\ 166 base64 > <UUID>.sig 167 ''')) 168 parser_digest.set_defaults(func=command_digest) 169 arg_add_uuid(parser_digest) 170 arg_add_ta_version(parser_digest) 171 arg_add_in(parser_digest) 172 arg_add_key(parser_digest) 173 arg_add_enc_key(parser_digest) 174 arg_add_enc_key_type(parser_digest) 175 arg_add_algo(parser_digest) 176 arg_add_dig(parser_digest) 177 178 parser_stitch = subparsers.add_parser( 179 'stitch', aliases=['stitch-ta'], prog=parser.prog + ' stich', 180 help='Generate loadable signed and encrypted TA binary image file' + 181 ' from TA raw image and its signature') 182 parser_stitch.set_defaults(func=command_stitch) 183 arg_add_uuid(parser_stitch) 184 arg_add_ta_version(parser_stitch) 185 arg_add_in(parser_stitch) 186 arg_add_key(parser_stitch) 187 arg_add_out(parser_stitch) 188 arg_add_enc_key(parser_stitch) 189 arg_add_enc_key_type(parser_stitch) 190 arg_add_algo(parser_stitch) 191 arg_add_sig(parser_stitch) 192 193 parser_verify = subparsers.add_parser( 194 'verify', prog=parser.prog + ' verify', 195 help='Verify signed TA binary') 196 parser_verify.set_defaults(func=command_verify) 197 arg_add_uuid(parser_verify) 198 arg_add_in(parser_verify) 199 arg_add_key(parser_verify) 200 201 argv = sys.argv[1:] 202 if (len(argv) > 0 and argv[0][0] == '-' and 203 argv[0] != '-h' and argv[0] != '--help'): 204 # The default sub-command is 'sign-enc' so add it to the parser 205 # if one is missing 206 argv = ['sign-enc'] + argv 207 208 parsed = parser.parse_args(argv) 209 210 if parsed.command is None: 211 parser.print_help() 212 sys.exit(1) 213 214 # Set a few defaults if defined for the current command 215 assign_default_value(parsed, 'inf', get_inf_default) 216 assign_default_value(parsed, 'outf', get_outf_default) 217 assign_default_value(parsed, 'sigf', get_sigf_default) 218 assign_default_value(parsed, 'digf', get_digf_default) 219 220 return parsed 221 222 223def load_asymmetric_key(arg_key): 224 if arg_key.startswith('arn:'): 225 from sign_helper_kms import _RSAPrivateKeyInKMS 226 key = _RSAPrivateKeyInKMS(arg_key) 227 else: 228 from cryptography.hazmat.backends import default_backend 229 from cryptography.hazmat.primitives.serialization import ( 230 load_pem_private_key, load_pem_public_key) 231 232 with open(arg_key, 'rb') as f: 233 data = f.read() 234 235 try: 236 key = load_pem_private_key(data, password=None, 237 backend=default_backend()) 238 except ValueError: 239 key = load_pem_public_key(data, backend=default_backend()) 240 241 return key 242 243 244class BinaryImage: 245 def __init__(self, arg_inf, arg_key): 246 from cryptography.hazmat.primitives import hashes 247 248 # Exactly what inf is holding isn't determined a this stage 249 with open(arg_inf, 'rb') as f: 250 self.inf = f.read() 251 252 self.key = load_asymmetric_key(arg_key) 253 254 self.chosen_hash = hashes.SHA256() 255 self.digest_len = self.chosen_hash.digest_size 256 self.sig_len = math.ceil(self.key.key_size / 8) 257 258 def __pack_img(self, img_type, sign_algo): 259 import struct 260 261 self.sig_algo = sign_algo 262 self.img_type = img_type 263 self.shdr = struct.pack('<IIIIHH', SHDR_MAGIC, img_type, len(self.img), 264 sig_tee_alg[sign_algo], self.digest_len, 265 self.sig_len) 266 267 def __calc_digest(self): 268 from cryptography.hazmat.backends import default_backend 269 from cryptography.hazmat.primitives import hashes 270 271 h = hashes.Hash(self.chosen_hash, default_backend()) 272 h.update(self.shdr) 273 h.update(self.ta_uuid) 274 h.update(self.ta_version) 275 if hasattr(self, 'ehdr'): 276 h.update(self.ehdr) 277 h.update(self.nonce) 278 h.update(self.tag) 279 h.update(self.img) 280 return h.finalize() 281 282 def encrypt_ta(self, enc_key, key_type, sig_algo, uuid, ta_version): 283 from cryptography.hazmat.primitives.ciphers.aead import AESGCM 284 import struct 285 import os 286 287 self.img = self.inf 288 289 cipher = AESGCM(bytes.fromhex(enc_key)) 290 self.nonce = os.urandom(NONCE_SIZE) 291 out = cipher.encrypt(self.nonce, self.img, None) 292 self.ciphertext = out[:-TAG_SIZE] 293 # Authentication Tag is always the last bytes 294 self.tag = out[-TAG_SIZE:] 295 296 enc_algo = enc_tee_alg['TEE_ALG_AES_GCM'] 297 flags = enc_key_type[key_type] 298 self.ehdr = struct.pack('<IIHH', enc_algo, flags, len(self.nonce), 299 len(self.tag)) 300 301 self.__pack_img(SHDR_ENCRYPTED_TA, sig_algo) 302 self.ta_uuid = uuid.bytes 303 self.ta_version = struct.pack('<I', ta_version) 304 self.img_digest = self.__calc_digest() 305 306 def set_bootstrap_ta(self, sig_algo, uuid, ta_version): 307 import struct 308 309 self.img = self.inf 310 self.__pack_img(SHDR_BOOTSTRAP_TA, sig_algo) 311 self.ta_uuid = uuid.bytes 312 self.ta_version = struct.pack('<I', ta_version) 313 self.img_digest = self.__calc_digest() 314 315 def parse(self): 316 import struct 317 318 offs = 0 319 self.shdr = self.inf[offs:offs + SHDR_SIZE] 320 [magic, img_type, img_size, algo_value, digest_len, 321 sig_len] = struct.unpack('<IIIIHH', self.shdr) 322 offs += SHDR_SIZE 323 324 if magic != SHDR_MAGIC: 325 raise Exception("Unexpected magic: 0x{:08x}".format(magic)) 326 327 if algo_value not in sig_tee_alg.values(): 328 raise Exception('Unrecognized algorithm: 0x{:08x}' 329 .format(algo_value)) 330 self.sig_algo = value_to_key(sig_tee_alg, algo_value) 331 332 if digest_len != self.digest_len: 333 raise Exception("Unexpected digest len: {}".format(digest_len)) 334 335 self.img_digest = self.inf[offs:offs + digest_len] 336 offs += digest_len 337 self.sig = self.inf[offs:offs + sig_len] 338 offs += sig_len 339 340 if img_type == SHDR_BOOTSTRAP_TA or img_type == SHDR_ENCRYPTED_TA: 341 self.ta_uuid = self.inf[offs:offs + UUID_SIZE] 342 offs += UUID_SIZE 343 self.ta_version = self.inf[offs:offs + 4] 344 offs += 4 345 if img_type == SHDR_ENCRYPTED_TA: 346 self.ehdr = self.inf[offs: offs + EHDR_SIZE] 347 offs += EHDR_SIZE 348 [enc_algo, flags, nonce_len, 349 tag_len] = struct.unpack('<IIHH', self.ehdr) 350 if enc_value not in enc_tee_alg.values(): 351 raise Exception('Unrecognized encrypt algorithm: 0x{:08x}' 352 .format(enc_value)) 353 if nonce_len != 12: 354 raise Exception("Unexpected nonce len: {}" 355 .format(nonce_len)) 356 self.nonce = self.inf[offs:offs + nonce_len] 357 offs += nonce_len 358 359 if tag_len != 16: 360 raise Exception("Unexpected tag len: {}".format(tag_len)) 361 self.tag = self.inf[-tag_len:] 362 self.ciphertext = self.inf[offs:-tag_len] 363 if len(self.ciphertext) != img_size: 364 raise Exception("Unexpected ciphertext size: ", 365 "got {}, expected {}" 366 .format(len(self.ciphertext), img_size)) 367 else: 368 self.img = self.inf[offs:] 369 if len(self.img) != img_size: 370 raise Exception("Unexpected img size: got {}, expected {}" 371 .format(len(self.img), img_size)) 372 else: 373 raise Exception("Unsupported image type: {}".format(img_type)) 374 375 def decrypt_ta(enc_key): 376 from cryptography.hazmat.primitives.ciphers.aead import AESGCM 377 378 cipher = AESGCM(bytes.fromhex(enc_key)) 379 self.img = cipher.decrypt(self.nonce, self.ciphertext, None) 380 381 def __get_padding(self): 382 from cryptography.hazmat.primitives.asymmetric import padding 383 384 if self.sig_algo == 'TEE_ALG_RSASSA_PKCS1_PSS_MGF1_SHA256': 385 pad = padding.PSS(mgf=padding.MGF1(self.chosen_hash), 386 salt_length=self.digest_len) 387 elif self.sig_algo == 'TEE_ALG_RSASSA_PKCS1_V1_5_SHA256': 388 pad = padding.PKCS1v15() 389 390 return pad 391 392 def sign(self): 393 from cryptography.hazmat.primitives.asymmetric import utils 394 from cryptography.hazmat.primitives.asymmetric import rsa 395 396 if not isinstance(self.key, rsa.RSAPrivateKey): 397 logger.error('Provided key cannot be used for signing, ' + 398 'please use offline-signing mode.') 399 sys.exit(1) 400 else: 401 self.sig = self.key.sign(self.img_digest, self.__get_padding(), 402 utils.Prehashed(self.chosen_hash)) 403 404 if len(self.sig) != self.sig_len: 405 raise Exception(("Actual signature length is not equal to ", 406 "the computed one: {} != {}"). 407 format(len(self.sig), self.sig_len)) 408 409 def add_signature(self, sigf): 410 import base64 411 412 with open(sigf, 'r') as f: 413 self.sig = base64.b64decode(f.read()) 414 415 if len(self.sig) != self.sig_len: 416 raise Exception(("Actual signature length is not equal to ", 417 "the expected one: {} != {}"). 418 format(len(self.sig), self.sig_len)) 419 420 def verify_signature(self): 421 from cryptography.hazmat.primitives.asymmetric import utils 422 from cryptography.hazmat.primitives.asymmetric import rsa 423 from cryptography import exceptions 424 425 if isinstance(self.key, rsa.RSAPrivateKey): 426 pkey = self.key.public_key() 427 else: 428 pkey = self.key 429 430 try: 431 pkey.verify(self.sig, self.img_digest, self.__get_padding(), 432 utils.Prehashed(self.chosen_hash)) 433 except exceptions.InvalidSignature: 434 logger.error('Verification failed, ignoring given signature.') 435 sys.exit(1) 436 437 def verify_digest(self): 438 if self.img_digest != self.__calc_digest(): 439 raise Exception('Hash digest does not match') 440 441 def verify_uuid(self, uuid): 442 if self.ta_uuid != uuid.bytes: 443 raise Exception('UUID does not match') 444 445 def write(self, outf): 446 with open(outf, 'wb') as f: 447 f.write(self.shdr) 448 f.write(self.img_digest) 449 f.write(self.sig) 450 f.write(self.ta_uuid) 451 f.write(self.ta_version) 452 if hasattr(self, 'ehdr'): 453 f.write(self.ehdr) 454 f.write(self.nonce) 455 f.write(self.tag) 456 f.write(self.ciphertext) 457 else: 458 f.write(self.img) 459 460 461def load_ta_image(args): 462 ta_image = BinaryImage(args.inf, args.key) 463 464 if args.enc_key: 465 ta_image.encrypt_ta(args.enc_key, args.enc_key_type, 466 args.algo, args.uuid, args.ta_version) 467 else: 468 ta_image.set_bootstrap_ta(args.algo, args.uuid, args.ta_version) 469 470 return ta_image 471 472 473def command_sign_enc(args): 474 ta_image = load_ta_image(args) 475 ta_image.sign() 476 ta_image.write(args.outf) 477 logger.info('Successfully signed application.') 478 479 480def command_digest(args): 481 import base64 482 483 ta_image = load_ta_image(args) 484 with open(args.digf, 'wb+') as digfile: 485 digfile.write(base64.b64encode(ta_image.img_digest)) 486 487 488def command_stitch(args): 489 ta_image = load_ta_image(args) 490 ta_image.add_signature(args.sigf) 491 ta_image.verify_signature() 492 ta_image.write(args.outf) 493 logger.info('Successfully applied signature.') 494 495 496def command_verify(args): 497 ta_image = BinaryImage(args.inf, args.key) 498 ta_image.parse() 499 if hasattr(ta_image, 'ciphertext'): 500 if args.enc_key is None: 501 logger.error('--enc_key needed to decrypt TA') 502 sys.exit(1) 503 ta_image.decrypt_ta(args.enc_key) 504 ta_image.verify_signature() 505 ta_image.verify_digest() 506 ta_image.verify_uuid(args.uuid) 507 logger.info('Trusted application is correctly verified.') 508 509 510def main(): 511 import logging 512 import os 513 514 global logger 515 logging.basicConfig() 516 logger = logging.getLogger(os.path.basename(__file__)) 517 518 args = get_args() 519 args.func(args) 520 521 522if __name__ == "__main__": 523 main() 524