blob: 37a22b51ecb520ecc45f98d1f78c746359d4cc01 [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
Mingyuan Xiangcf3d6ac2020-09-17 19:01:02 +080017
Jens Wiklandercd5cf432017-11-28 16:59:15 +010018def uuid_parse(s):
19 from uuid import UUID
20 return UUID(s)
21
22
23def int_parse(str):
24 return int(str, 0)
25
Jens Wiklanderbc420742015-05-05 14:59:15 +020026
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +020027def get_args(logger):
28 from argparse import ArgumentParser, RawDescriptionHelpFormatter
29 import textwrap
Sumit Garg2de17fd2019-10-23 12:47:24 +053030 command_base = ['sign-enc', 'digest', 'stitch']
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +020031 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
Jens Wiklanderbc420742015-05-05 14:59:15 +020035
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +020036 dat = '[' + ', '.join(command_aliases_digest) + ']'
37 sat = '[' + ', '.join(command_aliases_stitch) + ']'
38
39 parser = ArgumentParser(
Donald Chan169eac12021-10-24 14:22:54 -070040 description='Sign and encrypt (optional) a Trusted Application for' +
Sumit Garg2de17fd2019-10-23 12:47:24 +053041 ' OP-TEE.',
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +020042 usage='\n %(prog)s command [ arguments ]\n\n'
43
44 ' command:\n' +
Sumit Garg2de17fd2019-10-23 12:47:24 +053045 ' sign-enc Generate signed and optionally encrypted loadable' +
46 ' TA image file.\n' +
47 ' Takes arguments --uuid, --ta-version, --in, --out,' +
Donald Chanc45a84b2022-01-01 22:32:45 +000048 ' --key,\n' +
49 ' --enc-key (optional) and' +
50 ' --enc-key-type (optional).\n' +
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +020051 ' digest Generate loadable TA binary image digest' +
52 ' for offline\n' +
Sumit Garg2de17fd2019-10-23 12:47:24 +053053 ' signing. Takes arguments --uuid, --ta-version,' +
54 ' --in, --key,\n'
Donald Chanc45a84b2022-01-01 22:32:45 +000055 ' --enc-key (optional), --enc-key-type (optional),' +
56 ' --algo (optional) and --dig.\n' +
Sumit Garg2de17fd2019-10-23 12:47:24 +053057 ' stitch Generate loadable signed and encrypted TA binary' +
58 ' image file from\n' +
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +020059 ' TA raw image and its signature. Takes' +
Donald Chanc45a84b2022-01-01 22:32:45 +000060 ' arguments --uuid, --in, --key, --out,\n' +
61 ' --enc-key (optional), --enc-key-type (optional),\n' +
62 ' --algo (optional) and --sig.\n\n' +
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +020063 ' %(prog)s --help show available commands and arguments\n\n',
64 formatter_class=RawDescriptionHelpFormatter,
65 epilog=textwrap.dedent('''\
Sumit Garg2de17fd2019-10-23 12:47:24 +053066 If no command is given, the script will default to "sign-enc".
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +020067
68 command aliases:
69 The command \'digest\' can be aliased by ''' + dat + '''
70 The command \'stitch\' can be aliased by ''' + sat + '\n' + '''
Mingyuan Xiangcf3d6ac2020-09-17 19:01:02 +080071 example offline signing command using OpenSSL for algorithm
72 TEE_ALG_RSASSA_PKCS1_PSS_MGF1_SHA256:
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +020073 base64 -d <UUID>.dig | \\
74 openssl pkeyutl -sign -inkey <KEYFILE>.pem \\
Mingyuan Xiangcf3d6ac2020-09-17 19:01:02 +080075 -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 | \\
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +020084 base64 > <UUID>.sig
85 '''))
86
87 parser.add_argument(
88 'command', choices=command_choices, nargs='?',
Sumit Garg2de17fd2019-10-23 12:47:24 +053089 default='sign-enc',
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +020090 help='Command, one of [' + ', '.join(command_base) + ']')
Jens Wiklandercd5cf432017-11-28 16:59:15 +010091 parser.add_argument('--uuid', required=True,
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +020092 type=uuid_parse, help='String UUID of the TA')
93 parser.add_argument('--key', required=True,
Sumit Garg2de17fd2019-10-23 12:47:24 +053094 help='Name of signing key file (PEM format)')
95 parser.add_argument('--enc-key', required=False,
96 help='Encryption key string')
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +020097 parser.add_argument(
Donald Chanc45a84b2022-01-01 22:32:45 +000098 '--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(
Etienne Carriere47844622019-08-12 11:33:24 +0200104 '--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(
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200109 '--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(
Etienne Carriere9d8dd732019-08-12 16:15:42 +0200115 '--in', required=True, dest='inf',
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200116 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')
Mingyuan Xiangcf3d6ac2020-09-17 19:01:02 +0800120 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='')
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200126
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
Jens Wiklandercd5cf432017-11-28 16:59:15 +0100158
Jens Wiklanderbc420742015-05-05 14:59:15 +0200159
160def main():
Donald Chan169eac12021-10-24 14:22:54 -0700161 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
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200168 import base64
169 import logging
170 import os
Jens Wiklandercd5cf432017-11-28 16:59:15 +0100171 import struct
Jens Wiklanderbc420742015-05-05 14:59:15 +0200172
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200173 logging.basicConfig()
174 logger = logging.getLogger(os.path.basename(__file__))
Jens Wiklanderbc420742015-05-05 14:59:15 +0200175
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200176 args = get_args(logger)
Jens Wiklanderbc420742015-05-05 14:59:15 +0200177
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200178 with open(args.key, 'rb') as f:
Donald Chan169eac12021-10-24 14:22:54 -0700179 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())
Jens Wiklanderbc420742015-05-05 14:59:15 +0200187
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200188 with open(args.inf, 'rb') as f:
189 img = f.read()
190
Donald Chan169eac12021-10-24 14:22:54 -0700191 chosen_hash = hashes.SHA256()
192 h = hashes.Hash(chosen_hash, default_backend())
Jens Wiklanderbc420742015-05-05 14:59:15 +0200193
Donald Chan169eac12021-10-24 14:22:54 -0700194 digest_len = chosen_hash.digest_size
195 sig_len = math.ceil(key.key_size / 8)
Volodymyr Babchuk90ad2452019-08-21 21:00:32 +0300196
Jens Wiklandercd5cf432017-11-28 16:59:15 +0100197 img_size = len(img)
Jens Wiklanderbc420742015-05-05 14:59:15 +0200198
Etienne Carriere47844622019-08-12 11:33:24 +0200199 hdr_version = args.ta_version # struct shdr_bootstrap_ta::ta_version
200
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200201 magic = 0x4f545348 # SHDR_MAGIC
Sumit Garg2de17fd2019-10-23 12:47:24 +0530202 if args.enc_key:
203 img_type = 2 # SHDR_ENCRYPTED_TA
204 else:
205 img_type = 1 # SHDR_BOOTSTRAP_TA
Etienne Carriere47844622019-08-12 11:33:24 +0200206
Jens Wiklandercd5cf432017-11-28 16:59:15 +0100207 shdr = struct.pack('<IIIIHH',
Mingyuan Xiangcf3d6ac2020-09-17 19:01:02 +0800208 magic, img_type, img_size, algo[args.algo],
209 digest_len, sig_len)
Jens Wiklandercd5cf432017-11-28 16:59:15 +0100210 shdr_uuid = args.uuid.bytes
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200211 shdr_version = struct.pack('<I', hdr_version)
Jens Wiklanderbc420742015-05-05 14:59:15 +0200212
Sumit Garg2de17fd2019-10-23 12:47:24 +0530213 if args.enc_key:
Donald Chan169eac12021-10-24 14:22:54 -0700214 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:]
Sumit Garg2de17fd2019-10-23 12:47:24 +0530222
Donald Chanc45a84b2022-01-01 22:32:45 +0000223 enc_algo = 0x40000810 # TEE_ALG_AES_GCM
224 flags = enc_key_type[args.enc_key_type]
Sumit Garg2de17fd2019-10-23 12:47:24 +0530225 ehdr = struct.pack('<IIHH',
Donald Chan169eac12021-10-24 14:22:54 -0700226 enc_algo, flags, len(nonce), len(tag))
Sumit Garg2de17fd2019-10-23 12:47:24 +0530227
Jens Wiklandercd5cf432017-11-28 16:59:15 +0100228 h.update(shdr)
229 h.update(shdr_uuid)
230 h.update(shdr_version)
Sumit Garg2de17fd2019-10-23 12:47:24 +0530231 if args.enc_key:
232 h.update(ehdr)
Donald Chan169eac12021-10-24 14:22:54 -0700233 h.update(nonce)
Sumit Garg2de17fd2019-10-23 12:47:24 +0530234 h.update(tag)
Jens Wiklandercd5cf432017-11-28 16:59:15 +0100235 h.update(img)
Donald Chan169eac12021-10-24 14:22:54 -0700236 img_digest = h.finalize()
Jens Wiklanderbc420742015-05-05 14:59:15 +0200237
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200238 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)
Sumit Garg2de17fd2019-10-23 12:47:24 +0530245 if args.enc_key:
246 f.write(ehdr)
Donald Chan169eac12021-10-24 14:22:54 -0700247 f.write(nonce)
Sumit Garg2de17fd2019-10-23 12:47:24 +0530248 f.write(tag)
249 f.write(ciphertext)
250 else:
251 f.write(img)
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200252
Sumit Garg2de17fd2019-10-23 12:47:24 +0530253 def sign_encrypt_ta():
Donald Chan169eac12021-10-24 14:22:54 -0700254 if not isinstance(key, rsa.RSAPrivateKey):
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200255 logger.error('Provided key cannot be used for signing, ' +
256 'please use offline-signing mode.')
Markus S. Wamser6ff2e3f2019-08-02 14:48:46 +0200257 sys.exit(1)
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200258 else:
Donald Chan169eac12021-10-24 14:22:54 -0700259 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
Volodymyr Babchuk90ad2452019-08-21 21:00:32 +0300275 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))
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200279 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:
Markus S. Wamser6ff2e3f2019-08-02 14:48:46 +0200291 if not os.path.exists(args.digf):
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200292 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)
Markus S. Wamser6ff2e3f2019-08-02 14:48:46 +0200298 sys.exit(1)
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200299 else:
Mingyuan Xiangcf3d6ac2020-09-17 19:01:02 +0800300 try:
Donald Chan169eac12021-10-24 14:22:54 -0700301 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:
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200319 logger.error('Verification failed, ignoring given signature.')
Markus S. Wamser6ff2e3f2019-08-02 14:48:46 +0200320 sys.exit(1)
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200321
Donald Chan169eac12021-10-24 14:22:54 -0700322 write_image_with_signature(sig)
323 logger.info('Successfully applied signature.')
324
Markus S. Wamser6ff2e3f2019-08-02 14:48:46 +0200325 # dispatch command
326 {
Sumit Garg2de17fd2019-10-23 12:47:24 +0530327 'sign-enc': sign_encrypt_ta,
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200328 'digest': generate_digest,
329 'generate-digest': generate_digest,
330 'stitch': stitch_ta,
331 'stitch-ta': stitch_ta
Sumit Garg2de17fd2019-10-23 12:47:24 +0530332 }.get(args.command, 'sign_encrypt_ta')()
Jens Wiklandercd5cf432017-11-28 16:59:15 +0100333
Jens Wiklanderbc420742015-05-05 14:59:15 +0200334
335if __name__ == "__main__":
Jens Wiklandercd5cf432017-11-28 16:59:15 +0100336 main()