blob: 4cc9630333eae2641be21226d039e3d9bf48de04 [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
Jens Wiklandercd5cf432017-11-28 16:59:15 +010010def uuid_parse(s):
11 from uuid import UUID
12 return UUID(s)
13
14
15def int_parse(str):
16 return int(str, 0)
17
Jens Wiklanderbc420742015-05-05 14:59:15 +020018
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +020019def get_args(logger):
20 from argparse import ArgumentParser, RawDescriptionHelpFormatter
21 import textwrap
Sumit Garg2de17fd2019-10-23 12:47:24 +053022 command_base = ['sign-enc', 'digest', 'stitch']
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +020023 command_aliases_digest = ['generate-digest']
24 command_aliases_stitch = ['stitch-ta']
25 command_aliases = command_aliases_digest + command_aliases_stitch
26 command_choices = command_base + command_aliases
Jens Wiklanderbc420742015-05-05 14:59:15 +020027
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +020028 dat = '[' + ', '.join(command_aliases_digest) + ']'
29 sat = '[' + ', '.join(command_aliases_stitch) + ']'
30
31 parser = ArgumentParser(
Sumit Garg2de17fd2019-10-23 12:47:24 +053032 description='Sign and encrypt (optional) a Tusted Application for' +
33 ' OP-TEE.',
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +020034 usage='\n %(prog)s command [ arguments ]\n\n'
35
36 ' command:\n' +
Sumit Garg2de17fd2019-10-23 12:47:24 +053037 ' sign-enc Generate signed and optionally encrypted loadable' +
38 ' TA image file.\n' +
39 ' Takes arguments --uuid, --ta-version, --in, --out,' +
40 ' --key\n' +
41 ' and --enc-key (optional).\n' +
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +020042 ' digest Generate loadable TA binary image digest' +
43 ' for offline\n' +
Sumit Garg2de17fd2019-10-23 12:47:24 +053044 ' signing. Takes arguments --uuid, --ta-version,' +
45 ' --in, --key,\n'
46 ' --enc-key (optional) and --dig.\n' +
47 ' stitch Generate loadable signed and encrypted TA binary' +
48 ' image file from\n' +
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +020049 ' TA raw image and its signature. Takes' +
50 ' arguments\n' +
Sumit Garg2de17fd2019-10-23 12:47:24 +053051 ' --uuid, --in, --key, --enc-key (optional), --out,' +
52 ' and --sig.\n\n' +
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +020053 ' %(prog)s --help show available commands and arguments\n\n',
54 formatter_class=RawDescriptionHelpFormatter,
55 epilog=textwrap.dedent('''\
Sumit Garg2de17fd2019-10-23 12:47:24 +053056 If no command is given, the script will default to "sign-enc".
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +020057
58 command aliases:
59 The command \'digest\' can be aliased by ''' + dat + '''
60 The command \'stitch\' can be aliased by ''' + sat + '\n' + '''
61 example offline signing command using OpenSSL:
62 base64 -d <UUID>.dig | \\
63 openssl pkeyutl -sign -inkey <KEYFILE>.pem \\
64 -pkeyopt digest:sha256 \\
65 -pkeyopt rsa_padding_mode:pkcs1 | \\
66 base64 > <UUID>.sig
67 '''))
68
69 parser.add_argument(
70 'command', choices=command_choices, nargs='?',
Sumit Garg2de17fd2019-10-23 12:47:24 +053071 default='sign-enc',
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +020072 help='Command, one of [' + ', '.join(command_base) + ']')
Jens Wiklandercd5cf432017-11-28 16:59:15 +010073 parser.add_argument('--uuid', required=True,
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +020074 type=uuid_parse, help='String UUID of the TA')
75 parser.add_argument('--key', required=True,
Sumit Garg2de17fd2019-10-23 12:47:24 +053076 help='Name of signing key file (PEM format)')
77 parser.add_argument('--enc-key', required=False,
78 help='Encryption key string')
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +020079 parser.add_argument(
Etienne Carriere47844622019-08-12 11:33:24 +020080 '--ta-version', required=False, type=int_parse, default=0,
81 help='TA version stored as a 32-bit unsigned integer and used for\n' +
82 'rollback protection of TA install in the secure database.\n' +
83 'Defaults to 0.')
84 parser.add_argument(
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +020085 '--sig', required=False, dest='sigf',
86 help='Name of signature input file, defaults to <UUID>.sig')
87 parser.add_argument(
88 '--dig', required=False, dest='digf',
89 help='Name of digest output file, defaults to <UUID>.dig')
90 parser.add_argument(
Etienne Carriere9d8dd732019-08-12 16:15:42 +020091 '--in', required=True, dest='inf',
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +020092 help='Name of application input file, defaults to <UUID>.stripped.elf')
93 parser.add_argument(
94 '--out', required=False, dest='outf',
95 help='Name of application output file, defaults to <UUID>.ta')
96
97 parsed = parser.parse_args()
98
99 # Check parameter combinations
100
101 if parsed.digf is None and \
102 parsed.outf is not None and \
103 parsed.command in ['digest'] + command_aliases_digest:
104 logger.error('A digest was requested, but argument --out was given.' +
105 ' Did you mean:\n ' +
106 parser.prog+' --dig ' + parsed.outf + ' ...')
107 sys.exit(1)
108
109 if parsed.digf is not None \
110 and parsed.outf is not None \
111 and parsed.command in ['digest'] + command_aliases_digest:
112 logger.warn('A digest was requested, but arguments --dig and ' +
113 '--out were given.\n' +
114 ' --out will be ignored.')
115
116 # Set defaults for optional arguments.
117
118 if parsed.sigf is None:
119 parsed.sigf = str(parsed.uuid)+'.sig'
120 if parsed.digf is None:
121 parsed.digf = str(parsed.uuid)+'.dig'
122 if parsed.inf is None:
123 parsed.inf = str(parsed.uuid)+'.stripped.elf'
124 if parsed.outf is None:
125 parsed.outf = str(parsed.uuid)+'.ta'
126
127 return parsed
Jens Wiklandercd5cf432017-11-28 16:59:15 +0100128
Jens Wiklanderbc420742015-05-05 14:59:15 +0200129
130def main():
Jens Wiklanderababd722019-11-25 16:00:51 +0100131 from Cryptodome.Signature import pss
132 from Cryptodome.Hash import SHA256
133 from Cryptodome.PublicKey import RSA
134 from Cryptodome.Util.number import ceil_div
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200135 import base64
136 import logging
137 import os
Jens Wiklandercd5cf432017-11-28 16:59:15 +0100138 import struct
Jens Wiklanderbc420742015-05-05 14:59:15 +0200139
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200140 logging.basicConfig()
141 logger = logging.getLogger(os.path.basename(__file__))
Jens Wiklanderbc420742015-05-05 14:59:15 +0200142
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200143 args = get_args(logger)
Jens Wiklanderbc420742015-05-05 14:59:15 +0200144
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200145 with open(args.key, 'rb') as f:
146 key = RSA.importKey(f.read())
Jens Wiklanderbc420742015-05-05 14:59:15 +0200147
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200148 with open(args.inf, 'rb') as f:
149 img = f.read()
150
Jens Wiklandercd5cf432017-11-28 16:59:15 +0100151 h = SHA256.new()
Jens Wiklanderbc420742015-05-05 14:59:15 +0200152
Jens Wiklandercd5cf432017-11-28 16:59:15 +0100153 digest_len = h.digest_size
Volodymyr Babchuk90ad2452019-08-21 21:00:32 +0300154 try:
155 # This works in pycrypto
156 sig_len = ceil_div(key.size() + 1, 8)
157 except NotImplementedError:
158 # ... and this one - in pycryptodome
159 sig_len = key.size_in_bytes()
160
Jens Wiklandercd5cf432017-11-28 16:59:15 +0100161 img_size = len(img)
Jens Wiklanderbc420742015-05-05 14:59:15 +0200162
Etienne Carriere47844622019-08-12 11:33:24 +0200163 hdr_version = args.ta_version # struct shdr_bootstrap_ta::ta_version
164
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200165 magic = 0x4f545348 # SHDR_MAGIC
Sumit Garg2de17fd2019-10-23 12:47:24 +0530166 if args.enc_key:
167 img_type = 2 # SHDR_ENCRYPTED_TA
168 else:
169 img_type = 1 # SHDR_BOOTSTRAP_TA
Jens Wiklanderababd722019-11-25 16:00:51 +0100170 algo = 0x70414930 # TEE_ALG_RSASSA_PKCS1_PSS_MGF1_SHA256
Etienne Carriere47844622019-08-12 11:33:24 +0200171
Jens Wiklandercd5cf432017-11-28 16:59:15 +0100172 shdr = struct.pack('<IIIIHH',
173 magic, img_type, img_size, algo, digest_len, sig_len)
174 shdr_uuid = args.uuid.bytes
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200175 shdr_version = struct.pack('<I', hdr_version)
Jens Wiklanderbc420742015-05-05 14:59:15 +0200176
Sumit Garg2de17fd2019-10-23 12:47:24 +0530177 if args.enc_key:
178 from Cryptodome.Cipher import AES
179 cipher = AES.new(bytearray.fromhex(args.enc_key), AES.MODE_GCM)
180 ciphertext, tag = cipher.encrypt_and_digest(img)
181
182 enc_algo = 0x40000810 # TEE_ALG_AES_GCM
183 flags = 0 # SHDR_ENC_KEY_DEV_SPECIFIC
184 ehdr = struct.pack('<IIHH',
185 enc_algo, flags, len(cipher.nonce), len(tag))
186
Jens Wiklandercd5cf432017-11-28 16:59:15 +0100187 h.update(shdr)
188 h.update(shdr_uuid)
189 h.update(shdr_version)
Sumit Garg2de17fd2019-10-23 12:47:24 +0530190 if args.enc_key:
191 h.update(ehdr)
192 h.update(cipher.nonce)
193 h.update(tag)
Jens Wiklandercd5cf432017-11-28 16:59:15 +0100194 h.update(img)
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200195 img_digest = h.digest()
Jens Wiklanderbc420742015-05-05 14:59:15 +0200196
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200197 def write_image_with_signature(sig):
198 with open(args.outf, 'wb') as f:
199 f.write(shdr)
200 f.write(img_digest)
201 f.write(sig)
202 f.write(shdr_uuid)
203 f.write(shdr_version)
Sumit Garg2de17fd2019-10-23 12:47:24 +0530204 if args.enc_key:
205 f.write(ehdr)
206 f.write(cipher.nonce)
207 f.write(tag)
208 f.write(ciphertext)
209 else:
210 f.write(img)
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200211
Sumit Garg2de17fd2019-10-23 12:47:24 +0530212 def sign_encrypt_ta():
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200213 if not key.has_private():
214 logger.error('Provided key cannot be used for signing, ' +
215 'please use offline-signing mode.')
Markus S. Wamser6ff2e3f2019-08-02 14:48:46 +0200216 sys.exit(1)
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200217 else:
Jens Wiklanderababd722019-11-25 16:00:51 +0100218 signer = pss.new(key)
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200219 sig = signer.sign(h)
Volodymyr Babchuk90ad2452019-08-21 21:00:32 +0300220 if len(sig) != sig_len:
221 raise Exception(("Actual signature length is not equal to ",
222 "the computed one: {} != {}").
223 format(len(sig), sig_len))
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200224 write_image_with_signature(sig)
225 logger.info('Successfully signed application.')
226
227 def generate_digest():
228 with open(args.digf, 'wb+') as digfile:
229 digfile.write(base64.b64encode(img_digest))
230
231 def stitch_ta():
232 try:
233 with open(args.sigf, 'r') as sigfile:
234 sig = base64.b64decode(sigfile.read())
235 except IOError:
Markus S. Wamser6ff2e3f2019-08-02 14:48:46 +0200236 if not os.path.exists(args.digf):
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200237 generate_digest()
238 logger.error('No signature file found. Please sign\n %s\n' +
239 'offline and place the signature at \n %s\n' +
240 'or pass a different location ' +
241 'using the --sig argument.\n',
242 args.digf, args.sigf)
Markus S. Wamser6ff2e3f2019-08-02 14:48:46 +0200243 sys.exit(1)
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200244 else:
Jens Wiklanderababd722019-11-25 16:00:51 +0100245 verifier = pss.new(key)
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200246 if verifier.verify(h, sig):
247 write_image_with_signature(sig)
248 logger.info('Successfully applied signature.')
249 else:
250 logger.error('Verification failed, ignoring given signature.')
Markus S. Wamser6ff2e3f2019-08-02 14:48:46 +0200251 sys.exit(1)
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200252
Markus S. Wamser6ff2e3f2019-08-02 14:48:46 +0200253 # dispatch command
254 {
Sumit Garg2de17fd2019-10-23 12:47:24 +0530255 'sign-enc': sign_encrypt_ta,
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200256 'digest': generate_digest,
257 'generate-digest': generate_digest,
258 'stitch': stitch_ta,
259 'stitch-ta': stitch_ta
Sumit Garg2de17fd2019-10-23 12:47:24 +0530260 }.get(args.command, 'sign_encrypt_ta')()
Jens Wiklandercd5cf432017-11-28 16:59:15 +0100261
Jens Wiklanderbc420742015-05-05 14:59:15 +0200262
263if __name__ == "__main__":
Jens Wiklandercd5cf432017-11-28 16:59:15 +0100264 main()