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