blob: 57df6577a146551938548e7e347d135f459033ac [file] [log] [blame]
Jens Wiklanderbc420742015-05-05 14:59:15 +02001#!/usr/bin/env python
2#
Jens Wiklandercd5cf432017-11-28 16:59:15 +01003# Copyright (c) 2015, 2017, Linaro Limited
Jens Wiklanderbc420742015-05-05 14:59:15 +02004#
Jens Wiklandercd5cf432017-11-28 16:59:15 +01005# SPDX-License-Identifier: BSD-2-Clause
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
22 command_base = ['sign', 'digest', 'stitch']
23 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(
32 description='Sign a Tusted Application for OP-TEE.',
33 usage='\n %(prog)s command [ arguments ]\n\n'
34
35 ' command:\n' +
36 ' sign Generate signed loadable TA image file.\n' +
Etienne Carriere47844622019-08-12 11:33:24 +020037 ' Takes arguments --uuid, --ta-version, --in, --out' +
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +020038 ' and --key.\n' +
39 ' digest Generate loadable TA binary image digest' +
40 ' for offline\n' +
Etienne Carriere47844622019-08-12 11:33:24 +020041 ' signing. Takes arguments --uuid, --ta-version,' +
Etienne Carriere9d8dd732019-08-12 16:15:42 +020042 ' --in, --key and --dig.\n' +
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +020043 ' stitch Generate loadable signed TA binary image' +
44 ' file from\n' +
45 ' TA raw image and its signature. Takes' +
46 ' arguments\n' +
Etienne Carriere9d8dd732019-08-12 16:15:42 +020047 ' --uuid, --in, --key, --out, and --sig.\n\n' +
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +020048 ' %(prog)s --help show available commands and arguments\n\n',
49 formatter_class=RawDescriptionHelpFormatter,
50 epilog=textwrap.dedent('''\
51 If no command is given, the script will default to "sign".
52
53 command aliases:
54 The command \'digest\' can be aliased by ''' + dat + '''
55 The command \'stitch\' can be aliased by ''' + sat + '\n' + '''
56 example offline signing command using OpenSSL:
57 base64 -d <UUID>.dig | \\
58 openssl pkeyutl -sign -inkey <KEYFILE>.pem \\
59 -pkeyopt digest:sha256 \\
60 -pkeyopt rsa_padding_mode:pkcs1 | \\
61 base64 > <UUID>.sig
62 '''))
63
64 parser.add_argument(
65 'command', choices=command_choices, nargs='?',
66 default='sign',
67 help='Command, one of [' + ', '.join(command_base) + ']')
Jens Wiklandercd5cf432017-11-28 16:59:15 +010068 parser.add_argument('--uuid', required=True,
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +020069 type=uuid_parse, help='String UUID of the TA')
70 parser.add_argument('--key', required=True,
71 help='Name of key file (PEM format)')
72 parser.add_argument(
Etienne Carriere47844622019-08-12 11:33:24 +020073 '--ta-version', required=False, type=int_parse, default=0,
74 help='TA version stored as a 32-bit unsigned integer and used for\n' +
75 'rollback protection of TA install in the secure database.\n' +
76 'Defaults to 0.')
77 parser.add_argument(
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +020078 '--sig', required=False, dest='sigf',
79 help='Name of signature input file, defaults to <UUID>.sig')
80 parser.add_argument(
81 '--dig', required=False, dest='digf',
82 help='Name of digest output file, defaults to <UUID>.dig')
83 parser.add_argument(
Etienne Carriere9d8dd732019-08-12 16:15:42 +020084 '--in', required=True, dest='inf',
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +020085 help='Name of application input file, defaults to <UUID>.stripped.elf')
86 parser.add_argument(
87 '--out', required=False, dest='outf',
88 help='Name of application output file, defaults to <UUID>.ta')
89
90 parsed = parser.parse_args()
91
92 # Check parameter combinations
93
94 if parsed.digf is None and \
95 parsed.outf is not None and \
96 parsed.command in ['digest'] + command_aliases_digest:
97 logger.error('A digest was requested, but argument --out was given.' +
98 ' Did you mean:\n ' +
99 parser.prog+' --dig ' + parsed.outf + ' ...')
100 sys.exit(1)
101
102 if parsed.digf is not None \
103 and parsed.outf is not None \
104 and parsed.command in ['digest'] + command_aliases_digest:
105 logger.warn('A digest was requested, but arguments --dig and ' +
106 '--out were given.\n' +
107 ' --out will be ignored.')
108
109 # Set defaults for optional arguments.
110
111 if parsed.sigf is None:
112 parsed.sigf = str(parsed.uuid)+'.sig'
113 if parsed.digf is None:
114 parsed.digf = str(parsed.uuid)+'.dig'
115 if parsed.inf is None:
116 parsed.inf = str(parsed.uuid)+'.stripped.elf'
117 if parsed.outf is None:
118 parsed.outf = str(parsed.uuid)+'.ta'
119
120 return parsed
Jens Wiklandercd5cf432017-11-28 16:59:15 +0100121
Jens Wiklanderbc420742015-05-05 14:59:15 +0200122
123def main():
Jens Wiklandercd5cf432017-11-28 16:59:15 +0100124 from Crypto.Signature import PKCS1_v1_5
125 from Crypto.Hash import SHA256
126 from Crypto.PublicKey import RSA
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200127 from Crypto.Util.number import ceil_div
128 import base64
129 import logging
130 import os
Jens Wiklandercd5cf432017-11-28 16:59:15 +0100131 import struct
Jens Wiklanderbc420742015-05-05 14:59:15 +0200132
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200133 logging.basicConfig()
134 logger = logging.getLogger(os.path.basename(__file__))
Jens Wiklanderbc420742015-05-05 14:59:15 +0200135
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200136 args = get_args(logger)
Jens Wiklanderbc420742015-05-05 14:59:15 +0200137
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200138 with open(args.key, 'rb') as f:
139 key = RSA.importKey(f.read())
Jens Wiklanderbc420742015-05-05 14:59:15 +0200140
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200141 with open(args.inf, 'rb') as f:
142 img = f.read()
143
Jens Wiklandercd5cf432017-11-28 16:59:15 +0100144 h = SHA256.new()
Jens Wiklanderbc420742015-05-05 14:59:15 +0200145
Jens Wiklandercd5cf432017-11-28 16:59:15 +0100146 digest_len = h.digest_size
Volodymyr Babchuk90ad2452019-08-21 21:00:32 +0300147 try:
148 # This works in pycrypto
149 sig_len = ceil_div(key.size() + 1, 8)
150 except NotImplementedError:
151 # ... and this one - in pycryptodome
152 sig_len = key.size_in_bytes()
153
Jens Wiklandercd5cf432017-11-28 16:59:15 +0100154 img_size = len(img)
Jens Wiklanderbc420742015-05-05 14:59:15 +0200155
Etienne Carriere47844622019-08-12 11:33:24 +0200156 hdr_version = args.ta_version # struct shdr_bootstrap_ta::ta_version
157
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200158 magic = 0x4f545348 # SHDR_MAGIC
159 img_type = 1 # SHDR_BOOTSTRAP_TA
Jens Wiklandercd5cf432017-11-28 16:59:15 +0100160 algo = 0x70004830 # TEE_ALG_RSASSA_PKCS1_V1_5_SHA256
Etienne Carriere47844622019-08-12 11:33:24 +0200161
Jens Wiklandercd5cf432017-11-28 16:59:15 +0100162 shdr = struct.pack('<IIIIHH',
163 magic, img_type, img_size, algo, digest_len, sig_len)
164 shdr_uuid = args.uuid.bytes
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200165 shdr_version = struct.pack('<I', hdr_version)
Jens Wiklanderbc420742015-05-05 14:59:15 +0200166
Jens Wiklandercd5cf432017-11-28 16:59:15 +0100167 h.update(shdr)
168 h.update(shdr_uuid)
169 h.update(shdr_version)
170 h.update(img)
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200171 img_digest = h.digest()
Jens Wiklanderbc420742015-05-05 14:59:15 +0200172
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200173 def write_image_with_signature(sig):
174 with open(args.outf, 'wb') as f:
175 f.write(shdr)
176 f.write(img_digest)
177 f.write(sig)
178 f.write(shdr_uuid)
179 f.write(shdr_version)
180 f.write(img)
181
182 def sign_ta():
183 if not key.has_private():
184 logger.error('Provided key cannot be used for signing, ' +
185 'please use offline-signing mode.')
Markus S. Wamser6ff2e3f2019-08-02 14:48:46 +0200186 sys.exit(1)
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200187 else:
188 signer = PKCS1_v1_5.new(key)
189 sig = signer.sign(h)
Volodymyr Babchuk90ad2452019-08-21 21:00:32 +0300190 if len(sig) != sig_len:
191 raise Exception(("Actual signature length is not equal to ",
192 "the computed one: {} != {}").
193 format(len(sig), sig_len))
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200194 write_image_with_signature(sig)
195 logger.info('Successfully signed application.')
196
197 def generate_digest():
198 with open(args.digf, 'wb+') as digfile:
199 digfile.write(base64.b64encode(img_digest))
200
201 def stitch_ta():
202 try:
203 with open(args.sigf, 'r') as sigfile:
204 sig = base64.b64decode(sigfile.read())
205 except IOError:
Markus S. Wamser6ff2e3f2019-08-02 14:48:46 +0200206 if not os.path.exists(args.digf):
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200207 generate_digest()
208 logger.error('No signature file found. Please sign\n %s\n' +
209 'offline and place the signature at \n %s\n' +
210 'or pass a different location ' +
211 'using the --sig argument.\n',
212 args.digf, args.sigf)
Markus S. Wamser6ff2e3f2019-08-02 14:48:46 +0200213 sys.exit(1)
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200214 else:
215 verifier = PKCS1_v1_5.new(key)
216 if verifier.verify(h, sig):
217 write_image_with_signature(sig)
218 logger.info('Successfully applied signature.')
219 else:
220 logger.error('Verification failed, ignoring given signature.')
Markus S. Wamser6ff2e3f2019-08-02 14:48:46 +0200221 sys.exit(1)
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200222
Markus S. Wamser6ff2e3f2019-08-02 14:48:46 +0200223 # dispatch command
224 {
Markus S. Wamser1cdd95a2019-04-30 12:03:12 +0200225 'sign': sign_ta,
226 'digest': generate_digest,
227 'generate-digest': generate_digest,
228 'stitch': stitch_ta,
229 'stitch-ta': stitch_ta
Markus S. Wamser6ff2e3f2019-08-02 14:48:46 +0200230 }.get(args.command, 'sign_ta')()
Jens Wiklandercd5cf432017-11-28 16:59:15 +0100231
Jens Wiklanderbc420742015-05-05 14:59:15 +0200232
233if __name__ == "__main__":
Jens Wiklandercd5cf432017-11-28 16:59:15 +0100234 main()