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 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) or an ' + 102 'Amazon Resource Name (arn:) of an AWS KMS ' + 103 'asymmetric key') 104 parser.add_argument('--enc-key', required=False, 105 help='Encryption key string') 106 parser.add_argument( 107 '--enc-key-type', required=False, default='SHDR_ENC_KEY_DEV_SPECIFIC', 108 choices=list(enc_key_type.keys()), 109 help='Encryption key type.\n' + 110 '(SHDR_ENC_KEY_DEV_SPECIFIC or SHDR_ENC_KEY_CLASS_WIDE).\n' + 111 'Defaults to SHDR_ENC_KEY_DEV_SPECIFIC.') 112 parser.add_argument( 113 '--ta-version', required=False, type=int_parse, default=0, 114 help='TA version stored as a 32-bit unsigned integer and used for\n' + 115 'rollback protection of TA install in the secure database.\n' + 116 'Defaults to 0.') 117 parser.add_argument( 118 '--sig', required=False, dest='sigf', 119 help='Name of signature input file, defaults to <UUID>.sig') 120 parser.add_argument( 121 '--dig', required=False, dest='digf', 122 help='Name of digest output file, defaults to <UUID>.dig') 123 parser.add_argument( 124 '--in', required=True, dest='inf', 125 help='Name of application input file, defaults to <UUID>.stripped.elf') 126 parser.add_argument( 127 '--out', required=False, dest='outf', 128 help='Name of application output file, defaults to <UUID>.ta') 129 parser.add_argument('--algo', required=False, choices=list(algo.keys()), 130 default='TEE_ALG_RSASSA_PKCS1_PSS_MGF1_SHA256', 131 help='The hash and signature algorithm, ' + 132 'defaults to TEE_ALG_RSASSA_PKCS1_PSS_MGF1_SHA256. ' + 133 'Allowed values are: ' + 134 ', '.join(list(algo.keys())), metavar='') 135 136 parsed = parser.parse_args() 137 138 # Check parameter combinations 139 140 if parsed.digf is None and \ 141 parsed.outf is not None and \ 142 parsed.command in ['digest'] + command_aliases_digest: 143 logger.error('A digest was requested, but argument --out was given.' + 144 ' Did you mean:\n ' + 145 parser.prog+' --dig ' + parsed.outf + ' ...') 146 sys.exit(1) 147 148 if parsed.digf is not None \ 149 and parsed.outf is not None \ 150 and parsed.command in ['digest'] + command_aliases_digest: 151 logger.warn('A digest was requested, but arguments --dig and ' + 152 '--out were given.\n' + 153 ' --out will be ignored.') 154 155 # Set defaults for optional arguments. 156 157 if parsed.sigf is None: 158 parsed.sigf = str(parsed.uuid)+'.sig' 159 if parsed.digf is None: 160 parsed.digf = str(parsed.uuid)+'.dig' 161 if parsed.inf is None: 162 parsed.inf = str(parsed.uuid)+'.stripped.elf' 163 if parsed.outf is None: 164 parsed.outf = str(parsed.uuid)+'.ta' 165 166 return parsed 167 168 169def main(): 170 from cryptography import exceptions 171 from cryptography.hazmat.backends import default_backend 172 from cryptography.hazmat.primitives import serialization 173 from cryptography.hazmat.primitives import hashes 174 from cryptography.hazmat.primitives.asymmetric import padding 175 from cryptography.hazmat.primitives.asymmetric import rsa 176 from cryptography.hazmat.primitives.asymmetric import utils 177 import base64 178 import logging 179 import os 180 import struct 181 182 logging.basicConfig() 183 global logger 184 logger = logging.getLogger(os.path.basename(__file__)) 185 186 args = get_args() 187 188 if args.key.startswith('arn:'): 189 from sign_helper_kms import _RSAPrivateKeyInKMS 190 key = _RSAPrivateKeyInKMS(args.key) 191 else: 192 with open(args.key, 'rb') as f: 193 data = f.read() 194 195 try: 196 key = serialization.load_pem_private_key( 197 data, 198 password=None, 199 backend=default_backend()) 200 except ValueError: 201 key = serialization.load_pem_public_key( 202 data, 203 backend=default_backend()) 204 205 with open(args.inf, 'rb') as f: 206 img = f.read() 207 208 chosen_hash = hashes.SHA256() 209 h = hashes.Hash(chosen_hash, default_backend()) 210 211 digest_len = chosen_hash.digest_size 212 sig_len = math.ceil(key.key_size / 8) 213 214 img_size = len(img) 215 216 hdr_version = args.ta_version # struct shdr_bootstrap_ta::ta_version 217 218 magic = SHDR_MAGIC 219 if args.enc_key: 220 img_type = SHDR_ENCRYPTED_TA 221 else: 222 img_type = SHDR_BOOTSTRAP_TA 223 224 shdr = struct.pack('<IIIIHH', 225 magic, img_type, img_size, algo[args.algo], 226 digest_len, sig_len) 227 shdr_uuid = args.uuid.bytes 228 shdr_version = struct.pack('<I', hdr_version) 229 230 if args.enc_key: 231 from cryptography.hazmat.primitives.ciphers.aead import AESGCM 232 cipher = AESGCM(bytes.fromhex(args.enc_key)) 233 # Use 12 bytes for nonce per recommendation 234 nonce = os.urandom(12) 235 out = cipher.encrypt(nonce, img, None) 236 ciphertext = out[:-16] 237 # Authentication Tag is always the last 16 bytes 238 tag = out[-16:] 239 240 enc_algo = 0x40000810 # TEE_ALG_AES_GCM 241 flags = enc_key_type[args.enc_key_type] 242 ehdr = struct.pack('<IIHH', 243 enc_algo, flags, len(nonce), len(tag)) 244 245 h.update(shdr) 246 h.update(shdr_uuid) 247 h.update(shdr_version) 248 if args.enc_key: 249 h.update(ehdr) 250 h.update(nonce) 251 h.update(tag) 252 h.update(img) 253 img_digest = h.finalize() 254 255 def write_image_with_signature(sig): 256 with open(args.outf, 'wb') as f: 257 f.write(shdr) 258 f.write(img_digest) 259 f.write(sig) 260 f.write(shdr_uuid) 261 f.write(shdr_version) 262 if args.enc_key: 263 f.write(ehdr) 264 f.write(nonce) 265 f.write(tag) 266 f.write(ciphertext) 267 else: 268 f.write(img) 269 270 def sign_encrypt_ta(): 271 if not isinstance(key, rsa.RSAPrivateKey): 272 logger.error('Provided key cannot be used for signing, ' + 273 'please use offline-signing mode.') 274 sys.exit(1) 275 else: 276 if args.algo == 'TEE_ALG_RSASSA_PKCS1_PSS_MGF1_SHA256': 277 sig = key.sign( 278 img_digest, 279 padding.PSS( 280 mgf=padding.MGF1(chosen_hash), 281 salt_length=digest_len 282 ), 283 utils.Prehashed(chosen_hash) 284 ) 285 elif args.algo == 'TEE_ALG_RSASSA_PKCS1_V1_5_SHA256': 286 sig = key.sign( 287 img_digest, 288 padding.PKCS1v15(), 289 utils.Prehashed(chosen_hash) 290 ) 291 292 if len(sig) != sig_len: 293 raise Exception(("Actual signature length is not equal to ", 294 "the computed one: {} != {}"). 295 format(len(sig), sig_len)) 296 write_image_with_signature(sig) 297 logger.info('Successfully signed application.') 298 299 def generate_digest(): 300 with open(args.digf, 'wb+') as digfile: 301 digfile.write(base64.b64encode(img_digest)) 302 303 def stitch_ta(): 304 try: 305 with open(args.sigf, 'r') as sigfile: 306 sig = base64.b64decode(sigfile.read()) 307 except IOError: 308 if not os.path.exists(args.digf): 309 generate_digest() 310 logger.error('No signature file found. Please sign\n %s\n' + 311 'offline and place the signature at \n %s\n' + 312 'or pass a different location ' + 313 'using the --sig argument.\n', 314 args.digf, args.sigf) 315 sys.exit(1) 316 else: 317 try: 318 if args.algo == 'TEE_ALG_RSASSA_PKCS1_PSS_MGF1_SHA256': 319 key.verify( 320 sig, 321 img_digest, 322 padding.PSS( 323 mgf=padding.MGF1(chosen_hash), 324 salt_length=digest_len 325 ), 326 utils.Prehashed(chosen_hash) 327 ) 328 elif args.algo == 'TEE_ALG_RSASSA_PKCS1_V1_5_SHA256': 329 key.verify( 330 sig, 331 img_digest, 332 padding.PKCS1v15(), 333 utils.Prehashed(chosen_hash) 334 ) 335 except exceptions.InvalidSignature: 336 logger.error('Verification failed, ignoring given signature.') 337 sys.exit(1) 338 339 write_image_with_signature(sig) 340 logger.info('Successfully applied signature.') 341 342 def verify_ta(): 343 # Extract header 344 [magic, 345 img_type, 346 img_size, 347 algo_value, 348 digest_len, 349 sig_len] = struct.unpack('<IIIIHH', img[:SHDR_SIZE]) 350 351 # Extract digest and signature 352 start, end = SHDR_SIZE, SHDR_SIZE + digest_len 353 digest = img[start:end] 354 355 start, end = end, SHDR_SIZE + digest_len + sig_len 356 signature = img[start:end] 357 358 # Extract UUID and TA version 359 start, end = end, end + 16 + 4 360 [uuid, ta_version] = struct.unpack('<16sI', img[start:end]) 361 362 if magic != SHDR_MAGIC: 363 raise Exception("Unexpected magic: 0x{:08x}".format(magic)) 364 365 if img_type != SHDR_BOOTSTRAP_TA: 366 raise Exception("Unsupported image type: {}".format(img_type)) 367 368 if algo_value not in algo.values(): 369 raise Exception('Unrecognized algorithm: 0x{:08x}' 370 .format(algo_value)) 371 372 # Verify signature against hash digest 373 if algo_value == 0x70414930: 374 key.verify( 375 signature, 376 digest, 377 padding.PSS( 378 mgf=padding.MGF1(chosen_hash), 379 salt_length=digest_len 380 ), 381 utils.Prehashed(chosen_hash) 382 ) 383 else: 384 key.verify( 385 signature, 386 digest, 387 padding.PKCS1v15(), 388 utils.Prehashed(chosen_hash) 389 ) 390 391 h = hashes.Hash(chosen_hash, default_backend()) 392 393 # sizeof(struct shdr) 394 h.update(img[:SHDR_SIZE]) 395 396 # sizeof(struct shdr_bootstrap_ta) 397 h.update(img[start:end]) 398 399 # raw image 400 start = end 401 end += img_size 402 h.update(img[start:end]) 403 404 if digest != h.finalize(): 405 raise Exception('Hash digest does not match') 406 407 logger.info('Trusted application is correctly verified.') 408 409 # dispatch command 410 { 411 'sign-enc': sign_encrypt_ta, 412 'digest': generate_digest, 413 'generate-digest': generate_digest, 414 'stitch': stitch_ta, 415 'stitch-ta': stitch_ta, 416 'verify': verify_ta, 417 }.get(args.command, 'sign_encrypt_ta')() 418 419 420if __name__ == "__main__": 421 main() 422