blob: 33deeb8bb444baac0849a91868236f43fc7b54b3 [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
8
Jerome Forissier4a477922018-11-14 11:02:49 +01009
Mingyuan Xiangcf3d6ac2020-09-17 19:01:02 +080010algo = {'TEE_ALG_RSASSA_PKCS1_PSS_MGF1_SHA256': 0x70414930,
11 'TEE_ALG_RSASSA_PKCS1_V1_5_SHA256': 0x70004830}
12
13
Jens Wiklandercd5cf432017-11-28 16:59:15 +010014def uuid_parse(s):
15 from uuid import UUID
16 return UUID(s)
17
18
19def int_parse(str):
20 return int(str, 0)
21
Jens Wiklanderbc420742015-05-05 14:59:15 +020022
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +020023def get_args(logger):
24 from argparse import ArgumentParser, RawDescriptionHelpFormatter
25 import textwrap
Sumit Garg2de17fd2019-10-23 12:47:24 +053026 command_base = ['sign-enc', 'digest', 'stitch']
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +020027 command_aliases_digest = ['generate-digest']
28 command_aliases_stitch = ['stitch-ta']
29 command_aliases = command_aliases_digest + command_aliases_stitch
30 command_choices = command_base + command_aliases
Jens Wiklanderbc420742015-05-05 14:59:15 +020031
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +020032 dat = '[' + ', '.join(command_aliases_digest) + ']'
33 sat = '[' + ', '.join(command_aliases_stitch) + ']'
34
35 parser = ArgumentParser(
Sumit Garg2de17fd2019-10-23 12:47:24 +053036 description='Sign and encrypt (optional) a Tusted Application for' +
37 ' OP-TEE.',
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +020038 usage='\n %(prog)s command [ arguments ]\n\n'
39
40 ' command:\n' +
Sumit Garg2de17fd2019-10-23 12:47:24 +053041 ' sign-enc Generate signed and optionally encrypted loadable' +
42 ' TA image file.\n' +
43 ' Takes arguments --uuid, --ta-version, --in, --out,' +
44 ' --key\n' +
45 ' and --enc-key (optional).\n' +
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +020046 ' digest Generate loadable TA binary image digest' +
47 ' for offline\n' +
Sumit Garg2de17fd2019-10-23 12:47:24 +053048 ' signing. Takes arguments --uuid, --ta-version,' +
49 ' --in, --key,\n'
Mingyuan Xiangcf3d6ac2020-09-17 19:01:02 +080050 ' --enc-key (optional), --algo (optional) and' +
51 ' --dig.\n' +
Sumit Garg2de17fd2019-10-23 12:47:24 +053052 ' stitch Generate loadable signed and encrypted TA binary' +
53 ' image file from\n' +
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +020054 ' TA raw image and its signature. Takes' +
55 ' arguments\n' +
Sumit Garg2de17fd2019-10-23 12:47:24 +053056 ' --uuid, --in, --key, --enc-key (optional), --out,' +
Mingyuan Xiangcf3d6ac2020-09-17 19:01:02 +080057 ' --algo (optional) and --sig.\n\n' +
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +020058 ' %(prog)s --help show available commands and arguments\n\n',
59 formatter_class=RawDescriptionHelpFormatter,
60 epilog=textwrap.dedent('''\
Sumit Garg2de17fd2019-10-23 12:47:24 +053061 If no command is given, the script will default to "sign-enc".
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +020062
63 command aliases:
64 The command \'digest\' can be aliased by ''' + dat + '''
65 The command \'stitch\' can be aliased by ''' + sat + '\n' + '''
Mingyuan Xiangcf3d6ac2020-09-17 19:01:02 +080066 example offline signing command using OpenSSL for algorithm
67 TEE_ALG_RSASSA_PKCS1_PSS_MGF1_SHA256:
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +020068 base64 -d <UUID>.dig | \\
69 openssl pkeyutl -sign -inkey <KEYFILE>.pem \\
Mingyuan Xiangcf3d6ac2020-09-17 19:01:02 +080070 -pkeyopt digest:sha256 -pkeyopt rsa_padding_mode:pss \\
71 -pkeyopt rsa_pss_saltlen:digest \\
72 -pkeyopt rsa_mgf1_md:sha256 | \\
73 base64 > <UUID>.sig\n
74 example offline signing command using OpenSSL for algorithm
75 TEE_ALG_RSASSA_PKCS1_V1_5_SHA256:
76 base64 -d <UUID>.dig | \\
77 openssl pkeyutl -sign -inkey <KEYFILE>.pem \\
78 -pkeyopt digest:sha256 -pkeyopt rsa_padding_mode:pkcs1 | \\
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +020079 base64 > <UUID>.sig
80 '''))
81
82 parser.add_argument(
83 'command', choices=command_choices, nargs='?',
Sumit Garg2de17fd2019-10-23 12:47:24 +053084 default='sign-enc',
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +020085 help='Command, one of [' + ', '.join(command_base) + ']')
Jens Wiklandercd5cf432017-11-28 16:59:15 +010086 parser.add_argument('--uuid', required=True,
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +020087 type=uuid_parse, help='String UUID of the TA')
88 parser.add_argument('--key', required=True,
Sumit Garg2de17fd2019-10-23 12:47:24 +053089 help='Name of signing key file (PEM format)')
90 parser.add_argument('--enc-key', required=False,
91 help='Encryption key string')
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +020092 parser.add_argument(
Etienne Carriere47844622019-08-12 11:33:24 +020093 '--ta-version', required=False, type=int_parse, default=0,
94 help='TA version stored as a 32-bit unsigned integer and used for\n' +
95 'rollback protection of TA install in the secure database.\n' +
96 'Defaults to 0.')
97 parser.add_argument(
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +020098 '--sig', required=False, dest='sigf',
99 help='Name of signature input file, defaults to <UUID>.sig')
100 parser.add_argument(
101 '--dig', required=False, dest='digf',
102 help='Name of digest output file, defaults to <UUID>.dig')
103 parser.add_argument(
Etienne Carriere9d8dd732019-08-12 16:15:42 +0200104 '--in', required=True, dest='inf',
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200105 help='Name of application input file, defaults to <UUID>.stripped.elf')
106 parser.add_argument(
107 '--out', required=False, dest='outf',
108 help='Name of application output file, defaults to <UUID>.ta')
Mingyuan Xiangcf3d6ac2020-09-17 19:01:02 +0800109 parser.add_argument('--algo', required=False, choices=list(algo.keys()),
110 default='TEE_ALG_RSASSA_PKCS1_PSS_MGF1_SHA256',
111 help='The hash and signature algorithm, ' +
112 'defaults to TEE_ALG_RSASSA_PKCS1_PSS_MGF1_SHA256. ' +
113 'Allowed values are: ' +
114 ', '.join(list(algo.keys())), metavar='')
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200115
116 parsed = parser.parse_args()
117
118 # Check parameter combinations
119
120 if parsed.digf is None and \
121 parsed.outf is not None and \
122 parsed.command in ['digest'] + command_aliases_digest:
123 logger.error('A digest was requested, but argument --out was given.' +
124 ' Did you mean:\n ' +
125 parser.prog+' --dig ' + parsed.outf + ' ...')
126 sys.exit(1)
127
128 if parsed.digf is not None \
129 and parsed.outf is not None \
130 and parsed.command in ['digest'] + command_aliases_digest:
131 logger.warn('A digest was requested, but arguments --dig and ' +
132 '--out were given.\n' +
133 ' --out will be ignored.')
134
135 # Set defaults for optional arguments.
136
137 if parsed.sigf is None:
138 parsed.sigf = str(parsed.uuid)+'.sig'
139 if parsed.digf is None:
140 parsed.digf = str(parsed.uuid)+'.dig'
141 if parsed.inf is None:
142 parsed.inf = str(parsed.uuid)+'.stripped.elf'
143 if parsed.outf is None:
144 parsed.outf = str(parsed.uuid)+'.ta'
145
146 return parsed
Jens Wiklandercd5cf432017-11-28 16:59:15 +0100147
Jens Wiklanderbc420742015-05-05 14:59:15 +0200148
149def main():
Volodymyr Babchukae85b832020-10-13 14:19:45 +0300150 try:
151 from Cryptodome.Signature import pss
152 from Cryptodome.Signature import pkcs1_15
153 from Cryptodome.Hash import SHA256
154 from Cryptodome.PublicKey import RSA
155 except ImportError:
156 from Crypto.Signature import pss
157 from Crypto.Signature import pkcs1_15
158 from Crypto.Hash import SHA256
159 from Crypto.PublicKey import RSA
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200160 import base64
161 import logging
162 import os
Jens Wiklandercd5cf432017-11-28 16:59:15 +0100163 import struct
Jens Wiklanderbc420742015-05-05 14:59:15 +0200164
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200165 logging.basicConfig()
166 logger = logging.getLogger(os.path.basename(__file__))
Jens Wiklanderbc420742015-05-05 14:59:15 +0200167
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200168 args = get_args(logger)
Jens Wiklanderbc420742015-05-05 14:59:15 +0200169
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200170 with open(args.key, 'rb') as f:
171 key = RSA.importKey(f.read())
Jens Wiklanderbc420742015-05-05 14:59:15 +0200172
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200173 with open(args.inf, 'rb') as f:
174 img = f.read()
175
Jens Wiklandercd5cf432017-11-28 16:59:15 +0100176 h = SHA256.new()
Jens Wiklanderbc420742015-05-05 14:59:15 +0200177
Jens Wiklandercd5cf432017-11-28 16:59:15 +0100178 digest_len = h.digest_size
Jens Wiklander9579e402019-11-26 11:15:48 +0100179 sig_len = key.size_in_bytes()
Volodymyr Babchuk90ad2452019-08-21 21:00:32 +0300180
Jens Wiklandercd5cf432017-11-28 16:59:15 +0100181 img_size = len(img)
Jens Wiklanderbc420742015-05-05 14:59:15 +0200182
Etienne Carriere47844622019-08-12 11:33:24 +0200183 hdr_version = args.ta_version # struct shdr_bootstrap_ta::ta_version
184
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200185 magic = 0x4f545348 # SHDR_MAGIC
Sumit Garg2de17fd2019-10-23 12:47:24 +0530186 if args.enc_key:
187 img_type = 2 # SHDR_ENCRYPTED_TA
188 else:
189 img_type = 1 # SHDR_BOOTSTRAP_TA
Etienne Carriere47844622019-08-12 11:33:24 +0200190
Jens Wiklandercd5cf432017-11-28 16:59:15 +0100191 shdr = struct.pack('<IIIIHH',
Mingyuan Xiangcf3d6ac2020-09-17 19:01:02 +0800192 magic, img_type, img_size, algo[args.algo],
193 digest_len, sig_len)
Jens Wiklandercd5cf432017-11-28 16:59:15 +0100194 shdr_uuid = args.uuid.bytes
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200195 shdr_version = struct.pack('<I', hdr_version)
Jens Wiklanderbc420742015-05-05 14:59:15 +0200196
Sumit Garg2de17fd2019-10-23 12:47:24 +0530197 if args.enc_key:
198 from Cryptodome.Cipher import AES
199 cipher = AES.new(bytearray.fromhex(args.enc_key), AES.MODE_GCM)
200 ciphertext, tag = cipher.encrypt_and_digest(img)
201
202 enc_algo = 0x40000810 # TEE_ALG_AES_GCM
203 flags = 0 # SHDR_ENC_KEY_DEV_SPECIFIC
204 ehdr = struct.pack('<IIHH',
205 enc_algo, flags, len(cipher.nonce), len(tag))
206
Jens Wiklandercd5cf432017-11-28 16:59:15 +0100207 h.update(shdr)
208 h.update(shdr_uuid)
209 h.update(shdr_version)
Sumit Garg2de17fd2019-10-23 12:47:24 +0530210 if args.enc_key:
211 h.update(ehdr)
212 h.update(cipher.nonce)
213 h.update(tag)
Jens Wiklandercd5cf432017-11-28 16:59:15 +0100214 h.update(img)
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200215 img_digest = h.digest()
Jens Wiklanderbc420742015-05-05 14:59:15 +0200216
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200217 def write_image_with_signature(sig):
218 with open(args.outf, 'wb') as f:
219 f.write(shdr)
220 f.write(img_digest)
221 f.write(sig)
222 f.write(shdr_uuid)
223 f.write(shdr_version)
Sumit Garg2de17fd2019-10-23 12:47:24 +0530224 if args.enc_key:
225 f.write(ehdr)
226 f.write(cipher.nonce)
227 f.write(tag)
228 f.write(ciphertext)
229 else:
230 f.write(img)
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200231
Sumit Garg2de17fd2019-10-23 12:47:24 +0530232 def sign_encrypt_ta():
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200233 if not key.has_private():
234 logger.error('Provided key cannot be used for signing, ' +
235 'please use offline-signing mode.')
Markus S. Wamser6ff2e3f2019-08-02 14:48:46 +0200236 sys.exit(1)
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200237 else:
Jens Wiklanderababd722019-11-25 16:00:51 +0100238 signer = pss.new(key)
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200239 sig = signer.sign(h)
Volodymyr Babchuk90ad2452019-08-21 21:00:32 +0300240 if len(sig) != sig_len:
241 raise Exception(("Actual signature length is not equal to ",
242 "the computed one: {} != {}").
243 format(len(sig), sig_len))
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200244 write_image_with_signature(sig)
245 logger.info('Successfully signed application.')
246
247 def generate_digest():
248 with open(args.digf, 'wb+') as digfile:
249 digfile.write(base64.b64encode(img_digest))
250
251 def stitch_ta():
252 try:
253 with open(args.sigf, 'r') as sigfile:
254 sig = base64.b64decode(sigfile.read())
255 except IOError:
Markus S. Wamser6ff2e3f2019-08-02 14:48:46 +0200256 if not os.path.exists(args.digf):
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200257 generate_digest()
258 logger.error('No signature file found. Please sign\n %s\n' +
259 'offline and place the signature at \n %s\n' +
260 'or pass a different location ' +
261 'using the --sig argument.\n',
262 args.digf, args.sigf)
Markus S. Wamser6ff2e3f2019-08-02 14:48:46 +0200263 sys.exit(1)
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200264 else:
Mingyuan Xiangcf3d6ac2020-09-17 19:01:02 +0800265 if args.algo == 'TEE_ALG_RSASSA_PKCS1_PSS_MGF1_SHA256':
266 verifier = pss.new(key)
267 elif args.algo == 'TEE_ALG_RSASSA_PKCS1_V1_5_SHA256':
268 verifier = pkcs1_15.new(key)
269 try:
270 verifier.verify(h, sig)
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200271 write_image_with_signature(sig)
272 logger.info('Successfully applied signature.')
Mingyuan Xiangcf3d6ac2020-09-17 19:01:02 +0800273 except ValueError:
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200274 logger.error('Verification failed, ignoring given signature.')
Markus S. Wamser6ff2e3f2019-08-02 14:48:46 +0200275 sys.exit(1)
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200276
Markus S. Wamser6ff2e3f2019-08-02 14:48:46 +0200277 # dispatch command
278 {
Sumit Garg2de17fd2019-10-23 12:47:24 +0530279 'sign-enc': sign_encrypt_ta,
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200280 'digest': generate_digest,
281 'generate-digest': generate_digest,
282 'stitch': stitch_ta,
283 'stitch-ta': stitch_ta
Sumit Garg2de17fd2019-10-23 12:47:24 +0530284 }.get(args.command, 'sign_encrypt_ta')()
Jens Wiklandercd5cf432017-11-28 16:59:15 +0100285
Jens Wiklanderbc420742015-05-05 14:59:15 +0200286
287if __name__ == "__main__":
Jens Wiklandercd5cf432017-11-28 16:59:15 +0100288 main()