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'] 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\n' + 68 ' %(prog)s --help show available commands and arguments\n\n', 69 formatter_class=RawDescriptionHelpFormatter, 70 epilog=textwrap.dedent('''\ 71 If no command is given, the script will default to "sign-enc". 72 73 command aliases: 74 The command \'digest\' can be aliased by ''' + dat + ''' 75 The command \'stitch\' can be aliased by ''' + sat + '\n' + ''' 76 example offline signing command using OpenSSL for algorithm 77 TEE_ALG_RSASSA_PKCS1_PSS_MGF1_SHA256: 78 base64 -d <UUID>.dig | \\ 79 openssl pkeyutl -sign -inkey <KEYFILE>.pem \\ 80 -pkeyopt digest:sha256 -pkeyopt rsa_padding_mode:pss \\ 81 -pkeyopt rsa_pss_saltlen:digest \\ 82 -pkeyopt rsa_mgf1_md:sha256 | \\ 83 base64 > <UUID>.sig\n 84 example offline signing command using OpenSSL for algorithm 85 TEE_ALG_RSASSA_PKCS1_V1_5_SHA256: 86 base64 -d <UUID>.dig | \\ 87 openssl pkeyutl -sign -inkey <KEYFILE>.pem \\ 88 -pkeyopt digest:sha256 -pkeyopt rsa_padding_mode:pkcs1 | \\ 89 base64 > <UUID>.sig 90 ''')) 91 92 parser.add_argument( 93 'command', choices=command_choices, nargs='?', 94 default='sign-enc', 95 help='Command, one of [' + ', '.join(command_base) + ']') 96 parser.add_argument('--uuid', required=True, 97 type=uuid_parse, help='String UUID of the TA') 98 parser.add_argument('--key', required=True, 99 help='Name of signing key file (PEM format)') 100 parser.add_argument('--enc-key', required=False, 101 help='Encryption key string') 102 parser.add_argument( 103 '--enc-key-type', required=False, default='SHDR_ENC_KEY_DEV_SPECIFIC', 104 choices=list(enc_key_type.keys()), 105 help='Encryption key type.\n' + 106 '(SHDR_ENC_KEY_DEV_SPECIFIC or SHDR_ENC_KEY_CLASS_WIDE).\n' + 107 'Defaults to SHDR_ENC_KEY_DEV_SPECIFIC.') 108 parser.add_argument( 109 '--ta-version', required=False, type=int_parse, default=0, 110 help='TA version stored as a 32-bit unsigned integer and used for\n' + 111 'rollback protection of TA install in the secure database.\n' + 112 'Defaults to 0.') 113 parser.add_argument( 114 '--sig', required=False, dest='sigf', 115 help='Name of signature input file, defaults to <UUID>.sig') 116 parser.add_argument( 117 '--dig', required=False, dest='digf', 118 help='Name of digest output file, defaults to <UUID>.dig') 119 parser.add_argument( 120 '--in', required=True, dest='inf', 121 help='Name of application input file, defaults to <UUID>.stripped.elf') 122 parser.add_argument( 123 '--out', required=False, dest='outf', 124 help='Name of application output file, defaults to <UUID>.ta') 125 parser.add_argument('--algo', required=False, choices=list(algo.keys()), 126 default='TEE_ALG_RSASSA_PKCS1_PSS_MGF1_SHA256', 127 help='The hash and signature algorithm, ' + 128 'defaults to TEE_ALG_RSASSA_PKCS1_PSS_MGF1_SHA256. ' + 129 'Allowed values are: ' + 130 ', '.join(list(algo.keys())), metavar='') 131 132 parsed = parser.parse_args() 133 134 # Check parameter combinations 135 136 if parsed.digf is None and \ 137 parsed.outf is not None and \ 138 parsed.command in ['digest'] + command_aliases_digest: 139 logger.error('A digest was requested, but argument --out was given.' + 140 ' Did you mean:\n ' + 141 parser.prog+' --dig ' + parsed.outf + ' ...') 142 sys.exit(1) 143 144 if parsed.digf is not None \ 145 and parsed.outf is not None \ 146 and parsed.command in ['digest'] + command_aliases_digest: 147 logger.warn('A digest was requested, but arguments --dig and ' + 148 '--out were given.\n' + 149 ' --out will be ignored.') 150 151 # Set defaults for optional arguments. 152 153 if parsed.sigf is None: 154 parsed.sigf = str(parsed.uuid)+'.sig' 155 if parsed.digf is None: 156 parsed.digf = str(parsed.uuid)+'.dig' 157 if parsed.inf is None: 158 parsed.inf = str(parsed.uuid)+'.stripped.elf' 159 if parsed.outf is None: 160 parsed.outf = str(parsed.uuid)+'.ta' 161 162 return parsed 163 164 165def main(): 166 from cryptography import exceptions 167 from cryptography.hazmat.backends import default_backend 168 from cryptography.hazmat.primitives import serialization 169 from cryptography.hazmat.primitives import hashes 170 from cryptography.hazmat.primitives.asymmetric import padding 171 from cryptography.hazmat.primitives.asymmetric import rsa 172 from cryptography.hazmat.primitives.asymmetric import utils 173 import base64 174 import logging 175 import os 176 import struct 177 178 logging.basicConfig() 179 logger = logging.getLogger(os.path.basename(__file__)) 180 181 args = get_args(logger) 182 183 with open(args.key, 'rb') as f: 184 data = f.read() 185 186 try: 187 key = serialization.load_pem_private_key(data, password=None, 188 backend=default_backend()) 189 except ValueError: 190 key = serialization.load_pem_public_key(data, 191 backend=default_backend()) 192 193 with open(args.inf, 'rb') as f: 194 img = f.read() 195 196 chosen_hash = hashes.SHA256() 197 h = hashes.Hash(chosen_hash, default_backend()) 198 199 digest_len = chosen_hash.digest_size 200 sig_len = math.ceil(key.key_size / 8) 201 202 img_size = len(img) 203 204 hdr_version = args.ta_version # struct shdr_bootstrap_ta::ta_version 205 206 magic = SHDR_MAGIC 207 if args.enc_key: 208 img_type = SHDR_ENCRYPTED_TA 209 else: 210 img_type = SHDR_BOOTSTRAP_TA 211 212 shdr = struct.pack('<IIIIHH', 213 magic, img_type, img_size, algo[args.algo], 214 digest_len, sig_len) 215 shdr_uuid = args.uuid.bytes 216 shdr_version = struct.pack('<I', hdr_version) 217 218 if args.enc_key: 219 from cryptography.hazmat.primitives.ciphers.aead import AESGCM 220 cipher = AESGCM(bytes.fromhex(args.enc_key)) 221 # Use 12 bytes for nonce per recommendation 222 nonce = os.urandom(12) 223 out = cipher.encrypt(nonce, img, None) 224 ciphertext = out[:-16] 225 # Authentication Tag is always the last 16 bytes 226 tag = out[-16:] 227 228 enc_algo = 0x40000810 # TEE_ALG_AES_GCM 229 flags = enc_key_type[args.enc_key_type] 230 ehdr = struct.pack('<IIHH', 231 enc_algo, flags, len(nonce), len(tag)) 232 233 h.update(shdr) 234 h.update(shdr_uuid) 235 h.update(shdr_version) 236 if args.enc_key: 237 h.update(ehdr) 238 h.update(nonce) 239 h.update(tag) 240 h.update(img) 241 img_digest = h.finalize() 242 243 def write_image_with_signature(sig): 244 with open(args.outf, 'wb') as f: 245 f.write(shdr) 246 f.write(img_digest) 247 f.write(sig) 248 f.write(shdr_uuid) 249 f.write(shdr_version) 250 if args.enc_key: 251 f.write(ehdr) 252 f.write(nonce) 253 f.write(tag) 254 f.write(ciphertext) 255 else: 256 f.write(img) 257 258 def sign_encrypt_ta(): 259 if not isinstance(key, rsa.RSAPrivateKey): 260 logger.error('Provided key cannot be used for signing, ' + 261 'please use offline-signing mode.') 262 sys.exit(1) 263 else: 264 if args.algo == 'TEE_ALG_RSASSA_PKCS1_PSS_MGF1_SHA256': 265 sig = key.sign( 266 img_digest, 267 padding.PSS( 268 mgf=padding.MGF1(chosen_hash), 269 salt_length=digest_len 270 ), 271 utils.Prehashed(chosen_hash) 272 ) 273 elif args.algo == 'TEE_ALG_RSASSA_PKCS1_V1_5_SHA256': 274 sig = key.sign( 275 img_digest, 276 padding.PKCS1v15(), 277 utils.Prehashed(chosen_hash) 278 ) 279 280 if len(sig) != sig_len: 281 raise Exception(("Actual signature length is not equal to ", 282 "the computed one: {} != {}"). 283 format(len(sig), sig_len)) 284 write_image_with_signature(sig) 285 logger.info('Successfully signed application.') 286 287 def generate_digest(): 288 with open(args.digf, 'wb+') as digfile: 289 digfile.write(base64.b64encode(img_digest)) 290 291 def stitch_ta(): 292 try: 293 with open(args.sigf, 'r') as sigfile: 294 sig = base64.b64decode(sigfile.read()) 295 except IOError: 296 if not os.path.exists(args.digf): 297 generate_digest() 298 logger.error('No signature file found. Please sign\n %s\n' + 299 'offline and place the signature at \n %s\n' + 300 'or pass a different location ' + 301 'using the --sig argument.\n', 302 args.digf, args.sigf) 303 sys.exit(1) 304 else: 305 try: 306 if args.algo == 'TEE_ALG_RSASSA_PKCS1_PSS_MGF1_SHA256': 307 key.verify( 308 sig, 309 img_digest, 310 padding.PSS( 311 mgf=padding.MGF1(chosen_hash), 312 salt_length=digest_len 313 ), 314 utils.Prehashed(chosen_hash) 315 ) 316 elif args.algo == 'TEE_ALG_RSASSA_PKCS1_V1_5_SHA256': 317 key.verify( 318 sig, 319 img_digest, 320 padding.PKCS1v15(), 321 utils.Prehashed(chosen_hash) 322 ) 323 except exceptions.InvalidSignature: 324 logger.error('Verification failed, ignoring given signature.') 325 sys.exit(1) 326 327 write_image_with_signature(sig) 328 logger.info('Successfully applied signature.') 329 330 # dispatch command 331 { 332 'sign-enc': sign_encrypt_ta, 333 'digest': generate_digest, 334 'generate-digest': generate_digest, 335 'stitch': stitch_ta, 336 'stitch-ta': stitch_ta 337 }.get(args.command, 'sign_encrypt_ta')() 338 339 340if __name__ == "__main__": 341 main() 342