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