blob: 1f72005a8d24a1a58195cd2a7dec9ce41d965d27 [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
14
Jens Wiklandercd5cf432017-11-28 16:59:15 +010015def uuid_parse(s):
16 from uuid import UUID
17 return UUID(s)
18
19
20def int_parse(str):
21 return int(str, 0)
22
Jens Wiklanderbc420742015-05-05 14:59:15 +020023
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +020024def get_args(logger):
25 from argparse import ArgumentParser, RawDescriptionHelpFormatter
26 import textwrap
Sumit Garg2de17fd2019-10-23 12:47:24 +053027 command_base = ['sign-enc', 'digest', 'stitch']
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +020028 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
Jens Wiklanderbc420742015-05-05 14:59:15 +020032
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +020033 dat = '[' + ', '.join(command_aliases_digest) + ']'
34 sat = '[' + ', '.join(command_aliases_stitch) + ']'
35
36 parser = ArgumentParser(
Donald Chan169eac12021-10-24 14:22:54 -070037 description='Sign and encrypt (optional) a Trusted Application for' +
Sumit Garg2de17fd2019-10-23 12:47:24 +053038 ' OP-TEE.',
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +020039 usage='\n %(prog)s command [ arguments ]\n\n'
40
41 ' command:\n' +
Sumit Garg2de17fd2019-10-23 12:47:24 +053042 ' 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' +
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +020047 ' digest Generate loadable TA binary image digest' +
48 ' for offline\n' +
Sumit Garg2de17fd2019-10-23 12:47:24 +053049 ' signing. Takes arguments --uuid, --ta-version,' +
50 ' --in, --key,\n'
Mingyuan Xiangcf3d6ac2020-09-17 19:01:02 +080051 ' --enc-key (optional), --algo (optional) and' +
52 ' --dig.\n' +
Sumit Garg2de17fd2019-10-23 12:47:24 +053053 ' stitch Generate loadable signed and encrypted TA binary' +
54 ' image file from\n' +
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +020055 ' TA raw image and its signature. Takes' +
56 ' arguments\n' +
Sumit Garg2de17fd2019-10-23 12:47:24 +053057 ' --uuid, --in, --key, --enc-key (optional), --out,' +
Mingyuan Xiangcf3d6ac2020-09-17 19:01:02 +080058 ' --algo (optional) and --sig.\n\n' +
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +020059 ' %(prog)s --help show available commands and arguments\n\n',
60 formatter_class=RawDescriptionHelpFormatter,
61 epilog=textwrap.dedent('''\
Sumit Garg2de17fd2019-10-23 12:47:24 +053062 If no command is given, the script will default to "sign-enc".
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +020063
64 command aliases:
65 The command \'digest\' can be aliased by ''' + dat + '''
66 The command \'stitch\' can be aliased by ''' + sat + '\n' + '''
Mingyuan Xiangcf3d6ac2020-09-17 19:01:02 +080067 example offline signing command using OpenSSL for algorithm
68 TEE_ALG_RSASSA_PKCS1_PSS_MGF1_SHA256:
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +020069 base64 -d <UUID>.dig | \\
70 openssl pkeyutl -sign -inkey <KEYFILE>.pem \\
Mingyuan Xiangcf3d6ac2020-09-17 19:01:02 +080071 -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 | \\
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +020080 base64 > <UUID>.sig
81 '''))
82
83 parser.add_argument(
84 'command', choices=command_choices, nargs='?',
Sumit Garg2de17fd2019-10-23 12:47:24 +053085 default='sign-enc',
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +020086 help='Command, one of [' + ', '.join(command_base) + ']')
Jens Wiklandercd5cf432017-11-28 16:59:15 +010087 parser.add_argument('--uuid', required=True,
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +020088 type=uuid_parse, help='String UUID of the TA')
89 parser.add_argument('--key', required=True,
Sumit Garg2de17fd2019-10-23 12:47:24 +053090 help='Name of signing key file (PEM format)')
91 parser.add_argument('--enc-key', required=False,
92 help='Encryption key string')
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +020093 parser.add_argument(
Etienne Carriere47844622019-08-12 11:33:24 +020094 '--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(
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +020099 '--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(
Etienne Carriere9d8dd732019-08-12 16:15:42 +0200105 '--in', required=True, dest='inf',
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200106 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')
Mingyuan Xiangcf3d6ac2020-09-17 19:01:02 +0800110 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='')
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200116
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
Jens Wiklandercd5cf432017-11-28 16:59:15 +0100148
Jens Wiklanderbc420742015-05-05 14:59:15 +0200149
150def main():
Donald Chan169eac12021-10-24 14:22:54 -0700151 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
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200158 import base64
159 import logging
160 import os
Jens Wiklandercd5cf432017-11-28 16:59:15 +0100161 import struct
Jens Wiklanderbc420742015-05-05 14:59:15 +0200162
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200163 logging.basicConfig()
164 logger = logging.getLogger(os.path.basename(__file__))
Jens Wiklanderbc420742015-05-05 14:59:15 +0200165
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200166 args = get_args(logger)
Jens Wiklanderbc420742015-05-05 14:59:15 +0200167
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200168 with open(args.key, 'rb') as f:
Donald Chan169eac12021-10-24 14:22:54 -0700169 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())
Jens Wiklanderbc420742015-05-05 14:59:15 +0200177
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200178 with open(args.inf, 'rb') as f:
179 img = f.read()
180
Donald Chan169eac12021-10-24 14:22:54 -0700181 chosen_hash = hashes.SHA256()
182 h = hashes.Hash(chosen_hash, default_backend())
Jens Wiklanderbc420742015-05-05 14:59:15 +0200183
Donald Chan169eac12021-10-24 14:22:54 -0700184 digest_len = chosen_hash.digest_size
185 sig_len = math.ceil(key.key_size / 8)
Volodymyr Babchuk90ad2452019-08-21 21:00:32 +0300186
Jens Wiklandercd5cf432017-11-28 16:59:15 +0100187 img_size = len(img)
Jens Wiklanderbc420742015-05-05 14:59:15 +0200188
Etienne Carriere47844622019-08-12 11:33:24 +0200189 hdr_version = args.ta_version # struct shdr_bootstrap_ta::ta_version
190
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200191 magic = 0x4f545348 # SHDR_MAGIC
Sumit Garg2de17fd2019-10-23 12:47:24 +0530192 if args.enc_key:
193 img_type = 2 # SHDR_ENCRYPTED_TA
194 else:
195 img_type = 1 # SHDR_BOOTSTRAP_TA
Etienne Carriere47844622019-08-12 11:33:24 +0200196
Jens Wiklandercd5cf432017-11-28 16:59:15 +0100197 shdr = struct.pack('<IIIIHH',
Mingyuan Xiangcf3d6ac2020-09-17 19:01:02 +0800198 magic, img_type, img_size, algo[args.algo],
199 digest_len, sig_len)
Jens Wiklandercd5cf432017-11-28 16:59:15 +0100200 shdr_uuid = args.uuid.bytes
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200201 shdr_version = struct.pack('<I', hdr_version)
Jens Wiklanderbc420742015-05-05 14:59:15 +0200202
Sumit Garg2de17fd2019-10-23 12:47:24 +0530203 if args.enc_key:
Donald Chan169eac12021-10-24 14:22:54 -0700204 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:]
Sumit Garg2de17fd2019-10-23 12:47:24 +0530212
213 enc_algo = 0x40000810 # TEE_ALG_AES_GCM
214 flags = 0 # SHDR_ENC_KEY_DEV_SPECIFIC
215 ehdr = struct.pack('<IIHH',
Donald Chan169eac12021-10-24 14:22:54 -0700216 enc_algo, flags, len(nonce), len(tag))
Sumit Garg2de17fd2019-10-23 12:47:24 +0530217
Jens Wiklandercd5cf432017-11-28 16:59:15 +0100218 h.update(shdr)
219 h.update(shdr_uuid)
220 h.update(shdr_version)
Sumit Garg2de17fd2019-10-23 12:47:24 +0530221 if args.enc_key:
222 h.update(ehdr)
Donald Chan169eac12021-10-24 14:22:54 -0700223 h.update(nonce)
Sumit Garg2de17fd2019-10-23 12:47:24 +0530224 h.update(tag)
Jens Wiklandercd5cf432017-11-28 16:59:15 +0100225 h.update(img)
Donald Chan169eac12021-10-24 14:22:54 -0700226 img_digest = h.finalize()
Jens Wiklanderbc420742015-05-05 14:59:15 +0200227
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200228 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)
Sumit Garg2de17fd2019-10-23 12:47:24 +0530235 if args.enc_key:
236 f.write(ehdr)
Donald Chan169eac12021-10-24 14:22:54 -0700237 f.write(nonce)
Sumit Garg2de17fd2019-10-23 12:47:24 +0530238 f.write(tag)
239 f.write(ciphertext)
240 else:
241 f.write(img)
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200242
Sumit Garg2de17fd2019-10-23 12:47:24 +0530243 def sign_encrypt_ta():
Donald Chan169eac12021-10-24 14:22:54 -0700244 if not isinstance(key, rsa.RSAPrivateKey):
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200245 logger.error('Provided key cannot be used for signing, ' +
246 'please use offline-signing mode.')
Markus S. Wamser6ff2e3f2019-08-02 14:48:46 +0200247 sys.exit(1)
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200248 else:
Donald Chan169eac12021-10-24 14:22:54 -0700249 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
Volodymyr Babchuk90ad2452019-08-21 21:00:32 +0300265 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))
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200269 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:
Markus S. Wamser6ff2e3f2019-08-02 14:48:46 +0200281 if not os.path.exists(args.digf):
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200282 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)
Markus S. Wamser6ff2e3f2019-08-02 14:48:46 +0200288 sys.exit(1)
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200289 else:
Mingyuan Xiangcf3d6ac2020-09-17 19:01:02 +0800290 try:
Donald Chan169eac12021-10-24 14:22:54 -0700291 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:
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200309 logger.error('Verification failed, ignoring given signature.')
Markus S. Wamser6ff2e3f2019-08-02 14:48:46 +0200310 sys.exit(1)
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200311
Donald Chan169eac12021-10-24 14:22:54 -0700312 write_image_with_signature(sig)
313 logger.info('Successfully applied signature.')
314
Markus S. Wamser6ff2e3f2019-08-02 14:48:46 +0200315 # dispatch command
316 {
Sumit Garg2de17fd2019-10-23 12:47:24 +0530317 'sign-enc': sign_encrypt_ta,
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200318 'digest': generate_digest,
319 'generate-digest': generate_digest,
320 'stitch': stitch_ta,
321 'stitch-ta': stitch_ta
Sumit Garg2de17fd2019-10-23 12:47:24 +0530322 }.get(args.command, 'sign_encrypt_ta')()
Jens Wiklandercd5cf432017-11-28 16:59:15 +0100323
Jens Wiklanderbc420742015-05-05 14:59:15 +0200324
325if __name__ == "__main__":
Jens Wiklandercd5cf432017-11-28 16:59:15 +0100326 main()