blob: d89833f6c3bae0a6847cd6317888e7914cc8f57a [file] [log] [blame]
Rouven Czerwinskibbaeed42019-08-07 20:07:00 +02001#!/usr/bin/env python3
Jens Wiklandercd5cf432017-11-28 16:59:15 +01002# SPDX-License-Identifier: BSD-2-Clause
Sumit Garg2de17fd2019-10-23 12:47:24 +05303#
4# Copyright (c) 2015, 2017, 2019, Linaro Limited
5#
Jens Wiklandercd5cf432017-11-28 16:59:15 +01006
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +02007import sys
Donald Chan169eac12021-10-24 14:22:54 -07008import math
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +02009
Jerome Forissier4a477922018-11-14 11:02:49 +010010
Mingyuan Xiangcf3d6ac2020-09-17 19:01:02 +080011algo = {'TEE_ALG_RSASSA_PKCS1_PSS_MGF1_SHA256': 0x70414930,
12 'TEE_ALG_RSASSA_PKCS1_V1_5_SHA256': 0x70004830}
13
Donald Chanc45a84b2022-01-01 22:32:45 +000014enc_key_type = {'SHDR_ENC_KEY_DEV_SPECIFIC': 0x0,
15 'SHDR_ENC_KEY_CLASS_WIDE': 0x1}
16
Donald Chana797f202022-01-10 19:31:13 +000017SHDR_BOOTSTRAP_TA = 1
18SHDR_ENCRYPTED_TA = 2
19SHDR_MAGIC = 0x4f545348
20SHDR_SIZE = 20
21
Mingyuan Xiangcf3d6ac2020-09-17 19:01:02 +080022
Jens Wiklandercd5cf432017-11-28 16:59:15 +010023def uuid_parse(s):
24 from uuid import UUID
25 return UUID(s)
26
27
28def int_parse(str):
29 return int(str, 0)
30
Jens Wiklanderbc420742015-05-05 14:59:15 +020031
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +020032def get_args(logger):
33 from argparse import ArgumentParser, RawDescriptionHelpFormatter
34 import textwrap
Donald Chan51eee1e2022-01-11 18:54:25 +000035 command_base = ['sign-enc', 'digest', 'stitch', 'verify']
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +020036 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
Jens Wiklanderbc420742015-05-05 14:59:15 +020040
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +020041 dat = '[' + ', '.join(command_aliases_digest) + ']'
42 sat = '[' + ', '.join(command_aliases_stitch) + ']'
43
44 parser = ArgumentParser(
Donald Chan169eac12021-10-24 14:22:54 -070045 description='Sign and encrypt (optional) a Trusted Application for' +
Sumit Garg2de17fd2019-10-23 12:47:24 +053046 ' OP-TEE.',
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +020047 usage='\n %(prog)s command [ arguments ]\n\n'
48
49 ' command:\n' +
Sumit Garg2de17fd2019-10-23 12:47:24 +053050 ' sign-enc Generate signed and optionally encrypted loadable' +
51 ' TA image file.\n' +
52 ' Takes arguments --uuid, --ta-version, --in, --out,' +
Donald Chanc45a84b2022-01-01 22:32:45 +000053 ' --key,\n' +
54 ' --enc-key (optional) and' +
55 ' --enc-key-type (optional).\n' +
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +020056 ' digest Generate loadable TA binary image digest' +
57 ' for offline\n' +
Sumit Garg2de17fd2019-10-23 12:47:24 +053058 ' signing. Takes arguments --uuid, --ta-version,' +
59 ' --in, --key,\n'
Donald Chanc45a84b2022-01-01 22:32:45 +000060 ' --enc-key (optional), --enc-key-type (optional),' +
61 ' --algo (optional) and --dig.\n' +
Sumit Garg2de17fd2019-10-23 12:47:24 +053062 ' stitch Generate loadable signed and encrypted TA binary' +
63 ' image file from\n' +
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +020064 ' TA raw image and its signature. Takes' +
Donald Chanc45a84b2022-01-01 22:32:45 +000065 ' arguments --uuid, --in, --key, --out,\n' +
66 ' --enc-key (optional), --enc-key-type (optional),\n' +
Donald Chan51eee1e2022-01-11 18:54:25 +000067 ' --algo (optional) and --sig.\n' +
68 ' verify Verify signed TA binary\n' +
69 ' Takes arguments --uuid, --in, --key\n\n' +
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +020070 ' %(prog)s --help show available commands and arguments\n\n',
71 formatter_class=RawDescriptionHelpFormatter,
72 epilog=textwrap.dedent('''\
Sumit Garg2de17fd2019-10-23 12:47:24 +053073 If no command is given, the script will default to "sign-enc".
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +020074
75 command aliases:
76 The command \'digest\' can be aliased by ''' + dat + '''
77 The command \'stitch\' can be aliased by ''' + sat + '\n' + '''
Mingyuan Xiangcf3d6ac2020-09-17 19:01:02 +080078 example offline signing command using OpenSSL for algorithm
79 TEE_ALG_RSASSA_PKCS1_PSS_MGF1_SHA256:
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +020080 base64 -d <UUID>.dig | \\
81 openssl pkeyutl -sign -inkey <KEYFILE>.pem \\
Mingyuan Xiangcf3d6ac2020-09-17 19:01:02 +080082 -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 | \\
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +020091 base64 > <UUID>.sig
92 '''))
93
94 parser.add_argument(
95 'command', choices=command_choices, nargs='?',
Sumit Garg2de17fd2019-10-23 12:47:24 +053096 default='sign-enc',
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +020097 help='Command, one of [' + ', '.join(command_base) + ']')
Jens Wiklandercd5cf432017-11-28 16:59:15 +010098 parser.add_argument('--uuid', required=True,
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +020099 type=uuid_parse, help='String UUID of the TA')
100 parser.add_argument('--key', required=True,
Donald Chan05c007f2022-01-05 17:54:00 +0000101 help='Name of signing key file (PEM format) or an ' +
102 'Amazon Resource Name (arn:) of an AWS KMS ' +
103 'asymmetric key')
Sumit Garg2de17fd2019-10-23 12:47:24 +0530104 parser.add_argument('--enc-key', required=False,
105 help='Encryption key string')
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200106 parser.add_argument(
Donald Chanc45a84b2022-01-01 22:32:45 +0000107 '--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(
Etienne Carriere47844622019-08-12 11:33:24 +0200113 '--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(
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200118 '--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(
Etienne Carriere9d8dd732019-08-12 16:15:42 +0200124 '--in', required=True, dest='inf',
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200125 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')
Mingyuan Xiangcf3d6ac2020-09-17 19:01:02 +0800129 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='')
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200135
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
Jens Wiklandercd5cf432017-11-28 16:59:15 +0100167
Jens Wiklanderbc420742015-05-05 14:59:15 +0200168
169def main():
Donald Chan169eac12021-10-24 14:22:54 -0700170 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
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200177 import base64
178 import logging
179 import os
Jens Wiklandercd5cf432017-11-28 16:59:15 +0100180 import struct
Jens Wiklanderbc420742015-05-05 14:59:15 +0200181
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200182 logging.basicConfig()
183 logger = logging.getLogger(os.path.basename(__file__))
Jens Wiklanderbc420742015-05-05 14:59:15 +0200184
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200185 args = get_args(logger)
Jens Wiklanderbc420742015-05-05 14:59:15 +0200186
Donald Chan05c007f2022-01-05 17:54:00 +0000187 if args.key.startswith('arn:'):
188 from sign_helper_kms import _RSAPrivateKeyInKMS
189 key = _RSAPrivateKeyInKMS(args.key)
190 else:
191 with open(args.key, 'rb') as f:
192 data = f.read()
Donald Chan169eac12021-10-24 14:22:54 -0700193
Donald Chan05c007f2022-01-05 17:54:00 +0000194 try:
195 key = serialization.load_pem_private_key(
196 data,
197 password=None,
198 backend=default_backend())
199 except ValueError:
200 key = serialization.load_pem_public_key(
201 data,
202 backend=default_backend())
Jens Wiklanderbc420742015-05-05 14:59:15 +0200203
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200204 with open(args.inf, 'rb') as f:
205 img = f.read()
206
Donald Chan169eac12021-10-24 14:22:54 -0700207 chosen_hash = hashes.SHA256()
208 h = hashes.Hash(chosen_hash, default_backend())
Jens Wiklanderbc420742015-05-05 14:59:15 +0200209
Donald Chan169eac12021-10-24 14:22:54 -0700210 digest_len = chosen_hash.digest_size
211 sig_len = math.ceil(key.key_size / 8)
Volodymyr Babchuk90ad2452019-08-21 21:00:32 +0300212
Jens Wiklandercd5cf432017-11-28 16:59:15 +0100213 img_size = len(img)
Jens Wiklanderbc420742015-05-05 14:59:15 +0200214
Etienne Carriere47844622019-08-12 11:33:24 +0200215 hdr_version = args.ta_version # struct shdr_bootstrap_ta::ta_version
216
Donald Chana797f202022-01-10 19:31:13 +0000217 magic = SHDR_MAGIC
Sumit Garg2de17fd2019-10-23 12:47:24 +0530218 if args.enc_key:
Donald Chana797f202022-01-10 19:31:13 +0000219 img_type = SHDR_ENCRYPTED_TA
Sumit Garg2de17fd2019-10-23 12:47:24 +0530220 else:
Donald Chana797f202022-01-10 19:31:13 +0000221 img_type = SHDR_BOOTSTRAP_TA
Etienne Carriere47844622019-08-12 11:33:24 +0200222
Jens Wiklandercd5cf432017-11-28 16:59:15 +0100223 shdr = struct.pack('<IIIIHH',
Mingyuan Xiangcf3d6ac2020-09-17 19:01:02 +0800224 magic, img_type, img_size, algo[args.algo],
225 digest_len, sig_len)
Jens Wiklandercd5cf432017-11-28 16:59:15 +0100226 shdr_uuid = args.uuid.bytes
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200227 shdr_version = struct.pack('<I', hdr_version)
Jens Wiklanderbc420742015-05-05 14:59:15 +0200228
Sumit Garg2de17fd2019-10-23 12:47:24 +0530229 if args.enc_key:
Donald Chan169eac12021-10-24 14:22:54 -0700230 from cryptography.hazmat.primitives.ciphers.aead import AESGCM
231 cipher = AESGCM(bytes.fromhex(args.enc_key))
232 # Use 12 bytes for nonce per recommendation
233 nonce = os.urandom(12)
234 out = cipher.encrypt(nonce, img, None)
235 ciphertext = out[:-16]
236 # Authentication Tag is always the last 16 bytes
237 tag = out[-16:]
Sumit Garg2de17fd2019-10-23 12:47:24 +0530238
Donald Chanc45a84b2022-01-01 22:32:45 +0000239 enc_algo = 0x40000810 # TEE_ALG_AES_GCM
240 flags = enc_key_type[args.enc_key_type]
Sumit Garg2de17fd2019-10-23 12:47:24 +0530241 ehdr = struct.pack('<IIHH',
Donald Chan169eac12021-10-24 14:22:54 -0700242 enc_algo, flags, len(nonce), len(tag))
Sumit Garg2de17fd2019-10-23 12:47:24 +0530243
Jens Wiklandercd5cf432017-11-28 16:59:15 +0100244 h.update(shdr)
245 h.update(shdr_uuid)
246 h.update(shdr_version)
Sumit Garg2de17fd2019-10-23 12:47:24 +0530247 if args.enc_key:
248 h.update(ehdr)
Donald Chan169eac12021-10-24 14:22:54 -0700249 h.update(nonce)
Sumit Garg2de17fd2019-10-23 12:47:24 +0530250 h.update(tag)
Jens Wiklandercd5cf432017-11-28 16:59:15 +0100251 h.update(img)
Donald Chan169eac12021-10-24 14:22:54 -0700252 img_digest = h.finalize()
Jens Wiklanderbc420742015-05-05 14:59:15 +0200253
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200254 def write_image_with_signature(sig):
255 with open(args.outf, 'wb') as f:
256 f.write(shdr)
257 f.write(img_digest)
258 f.write(sig)
259 f.write(shdr_uuid)
260 f.write(shdr_version)
Sumit Garg2de17fd2019-10-23 12:47:24 +0530261 if args.enc_key:
262 f.write(ehdr)
Donald Chan169eac12021-10-24 14:22:54 -0700263 f.write(nonce)
Sumit Garg2de17fd2019-10-23 12:47:24 +0530264 f.write(tag)
265 f.write(ciphertext)
266 else:
267 f.write(img)
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200268
Sumit Garg2de17fd2019-10-23 12:47:24 +0530269 def sign_encrypt_ta():
Donald Chan169eac12021-10-24 14:22:54 -0700270 if not isinstance(key, rsa.RSAPrivateKey):
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200271 logger.error('Provided key cannot be used for signing, ' +
272 'please use offline-signing mode.')
Markus S. Wamser6ff2e3f2019-08-02 14:48:46 +0200273 sys.exit(1)
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200274 else:
Donald Chan169eac12021-10-24 14:22:54 -0700275 if args.algo == 'TEE_ALG_RSASSA_PKCS1_PSS_MGF1_SHA256':
276 sig = key.sign(
277 img_digest,
278 padding.PSS(
279 mgf=padding.MGF1(chosen_hash),
280 salt_length=digest_len
281 ),
282 utils.Prehashed(chosen_hash)
283 )
284 elif args.algo == 'TEE_ALG_RSASSA_PKCS1_V1_5_SHA256':
285 sig = key.sign(
286 img_digest,
287 padding.PKCS1v15(),
288 utils.Prehashed(chosen_hash)
289 )
290
Volodymyr Babchuk90ad2452019-08-21 21:00:32 +0300291 if len(sig) != sig_len:
292 raise Exception(("Actual signature length is not equal to ",
293 "the computed one: {} != {}").
294 format(len(sig), sig_len))
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200295 write_image_with_signature(sig)
296 logger.info('Successfully signed application.')
297
298 def generate_digest():
299 with open(args.digf, 'wb+') as digfile:
300 digfile.write(base64.b64encode(img_digest))
301
302 def stitch_ta():
303 try:
304 with open(args.sigf, 'r') as sigfile:
305 sig = base64.b64decode(sigfile.read())
306 except IOError:
Markus S. Wamser6ff2e3f2019-08-02 14:48:46 +0200307 if not os.path.exists(args.digf):
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200308 generate_digest()
309 logger.error('No signature file found. Please sign\n %s\n' +
310 'offline and place the signature at \n %s\n' +
311 'or pass a different location ' +
312 'using the --sig argument.\n',
313 args.digf, args.sigf)
Markus S. Wamser6ff2e3f2019-08-02 14:48:46 +0200314 sys.exit(1)
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200315 else:
Mingyuan Xiangcf3d6ac2020-09-17 19:01:02 +0800316 try:
Donald Chan169eac12021-10-24 14:22:54 -0700317 if args.algo == 'TEE_ALG_RSASSA_PKCS1_PSS_MGF1_SHA256':
318 key.verify(
319 sig,
320 img_digest,
321 padding.PSS(
322 mgf=padding.MGF1(chosen_hash),
323 salt_length=digest_len
324 ),
325 utils.Prehashed(chosen_hash)
326 )
327 elif args.algo == 'TEE_ALG_RSASSA_PKCS1_V1_5_SHA256':
328 key.verify(
329 sig,
330 img_digest,
331 padding.PKCS1v15(),
332 utils.Prehashed(chosen_hash)
333 )
334 except exceptions.InvalidSignature:
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200335 logger.error('Verification failed, ignoring given signature.')
Markus S. Wamser6ff2e3f2019-08-02 14:48:46 +0200336 sys.exit(1)
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200337
Donald Chan169eac12021-10-24 14:22:54 -0700338 write_image_with_signature(sig)
339 logger.info('Successfully applied signature.')
340
Donald Chan51eee1e2022-01-11 18:54:25 +0000341 def verify_ta():
342 # Extract header
343 [magic,
344 img_type,
345 img_size,
346 algo_value,
347 digest_len,
348 sig_len] = struct.unpack('<IIIIHH', img[:SHDR_SIZE])
349
350 # Extract digest and signature
351 start, end = SHDR_SIZE, SHDR_SIZE + digest_len
352 digest = img[start:end]
353
354 start, end = end, SHDR_SIZE + digest_len + sig_len
355 signature = img[start:end]
356
357 # Extract UUID and TA version
358 start, end = end, end + 16 + 4
359 [uuid, ta_version] = struct.unpack('<16sI', img[start:end])
360
361 if magic != SHDR_MAGIC:
362 raise Exception("Unexpected magic: 0x{:08x}".format(magic))
363
364 if img_type != SHDR_BOOTSTRAP_TA:
365 raise Exception("Unsupported image type: {}".format(img_type))
366
367 if algo_value not in algo.values():
368 raise Exception('Unrecognized algorithm: 0x{:08x}'
369 .format(algo_value))
370
371 # Verify signature against hash digest
372 if algo_value == 0x70414930:
373 key.verify(
374 signature,
375 digest,
376 padding.PSS(
377 mgf=padding.MGF1(chosen_hash),
378 salt_length=digest_len
379 ),
380 utils.Prehashed(chosen_hash)
381 )
382 else:
383 key.verify(
384 signature,
385 digest,
386 padding.PKCS1v15(),
387 utils.Prehashed(chosen_hash)
388 )
389
390 h = hashes.Hash(chosen_hash, default_backend())
391
392 # sizeof(struct shdr)
393 h.update(img[:SHDR_SIZE])
394
395 # sizeof(struct shdr_bootstrap_ta)
396 h.update(img[start:end])
397
398 # raw image
399 start = end
400 end += img_size
401 h.update(img[start:end])
402
403 if digest != h.finalize():
404 raise Exception('Hash digest does not match')
405
406 logger.info('Trusted application is correctly verified.')
407
Markus S. Wamser6ff2e3f2019-08-02 14:48:46 +0200408 # dispatch command
409 {
Sumit Garg2de17fd2019-10-23 12:47:24 +0530410 'sign-enc': sign_encrypt_ta,
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200411 'digest': generate_digest,
412 'generate-digest': generate_digest,
413 'stitch': stitch_ta,
Donald Chan51eee1e2022-01-11 18:54:25 +0000414 'stitch-ta': stitch_ta,
415 'verify': verify_ta,
Sumit Garg2de17fd2019-10-23 12:47:24 +0530416 }.get(args.command, 'sign_encrypt_ta')()
Jens Wiklandercd5cf432017-11-28 16:59:15 +0100417
Jens Wiklanderbc420742015-05-05 14:59:15 +0200418
419if __name__ == "__main__":
Jens Wiklandercd5cf432017-11-28 16:59:15 +0100420 main()