blob: 6ca7326c4cbab674f25e9be649f2ca9c1bae6fe6 [file] [log] [blame]
rjw1f884582022-01-06 17:20:42 +08001"""
2pbp is a tool that signs/re-signs bootloader and generate data for root public key authentication.
3"""
4import os
5import shutil
6import argparse
7from lib import gfh
8from lib import cert
9
10
11def get_file_sizeb(file_path):
12 """
13 Get size of binary file
14 """
15 if not os.path.isfile(file_path):
16 return 0
17 file_handle = open(file_path, "rb")
18 file_handle.seek(0, 2)
19 file_size = file_handle.tell()
20 file_handle.close()
21 return file_size
22
23
24def concatb(file1_path, file2_path):
25 """
26 Concatenate binary files
27 """
28 file2_size = get_file_sizeb(file2_path)
29 file1 = open(file1_path, "ab+")
30 file2 = open(file2_path, "rb")
31 file1.write(file2.read(file2_size))
32 file2.close()
33 file1.close()
34
35
36class Bl(object):
37 """
38 Bl, which stands for preloader in Mediatek solution.
39 Mediatek preloader is loaded/verified by BootROM and its format is determined by BootROM
40 and is different from other images due to several reasons.
41 It has basic format as follows:
42 =======================
43 GFH
44 =======================
45 Preloader_NO_GFH.bin
46 =======================
47 Sig
48 =======================
49 Where Preloader_NO_GFH.bin is converted from preloader.elf.
50 """
51 def __init__(self, out_path, in_bootloader_file_path, out_bootloader_file_path):
52 self.m_gfh = gfh.ImageGFH()
53 self.m_out_path = out_path
54 if not os.path.exists(self.m_out_path):
55 os.makedirs(self.m_out_path)
56 self.m_in_bl_file_path = in_bootloader_file_path
57 self.m_out_bl_file_path = out_bootloader_file_path
58 self.m_bl_is_signed = False
59 self.m_bl_content_offset = 0
60 # initialize content size to bl file size
61 self.m_bl_content_length = get_file_sizeb(self.m_in_bl_file_path)
62 self.m_bl_sig_size = 0
63 # generate file path for bl without gfh and signature
64 bl_path = os.path.splitext(in_bootloader_file_path)
65 self.m_bl_no_gfh_file_path = bl_path[0] + "_plain.bin"
66 self.m_sig_ver = 0
67 self.m_sw_ver = 0
68 self.m_root_prvk_path = ""
69 self.m_img_prvk_path = ""
70 self.m_ac_key = 0
71 self.m_sig_handler = None
72
73 def is_signed(self):
74 """
75 GFH and signature are added after bootloader image has been processed by pbp.
76 We use this fact to determine whether bootloader image is signed.
77 """
78 if self.m_in_bl_file_path:
79 bl_file = open(self.m_in_bl_file_path, "rb")
80 gfh_hdr_obj = gfh.GFHHeader()
81 gfh_hdr_size = gfh_hdr_obj.get_size()
82 gfh_hdr_buf = bl_file.read(gfh_hdr_size)
83 self.m_bl_is_signed = gfh_hdr_obj.is_gfh(gfh_hdr_buf)
84 bl_file.close()
85 return self.m_bl_is_signed
86
87 def parse(self):
88 """
89 If image is signed, we remove GFH and signature. Removed GFH is parsed and
90 stored. Stored GFH will be used later if GFH ini file is not given.
91 """
92 print "===parse bootloader==="
93 # image will be decomposed if it's signed
94 if self.is_signed():
95 gfh_total_size = self.m_gfh.parse(self.m_in_bl_file_path)
96 self.m_bl_content_offset = gfh_total_size
97 self.m_bl_content_length -= gfh_total_size
98 self.m_bl_content_length -= self.m_gfh.get_sig_size()
99 self.m_bl_sig_size = self.m_gfh.get_sig_size()
100 in_file = open(self.m_in_bl_file_path, "rb")
101 out_file = open(self.m_bl_no_gfh_file_path, "wb")
102 in_file.seek(self.m_bl_content_offset)
103 out_file.write(in_file.read(self.m_bl_content_length))
104 out_file.close()
105 in_file.close()
106 else:
107 shutil.copyfile(self.m_in_bl_file_path, self.m_bl_no_gfh_file_path)
108 print "bootloader content size = " + hex(self.m_bl_content_length)
109
110 def create_gfh(self, gfh_config):
111 """
112 GFH creation. GFH may be created from parsed/stored GFH config or from GFH config file
113 provided by user.
114 """
115 self.parse()
116 if gfh_config:
117 if self.is_signed():
118 del self.m_gfh.gfhs[:]
119 self.m_gfh.load_ini(gfh_config)
120 elif not self.is_signed():
121 print "GFH_CONFIG.ini does not exist!!"
122 return -1
123 # self.m_gfh.dump()
124 return 0
125
126 def sign(self, key_ini_path, key_cert_path, content_config_file_path):
127 """
128 Sign bootloader according to its signature type, which is stored in GFH.
129 """
130 self.m_gfh.finalize(self.m_bl_content_length, key_ini_path)
131 # create tbs_bootloader.bin
132 tbs_bl_file_path = os.path.join(self.m_out_path, "tbs_preloader.bin")
133 tbs_bl_file = open(tbs_bl_file_path, "wb")
134 tbs_bl_file.write(self.m_gfh.pack())
135 bl_no_gfh_file = open(self.m_bl_no_gfh_file_path, "rb")
136 tbs_bl_file.write(bl_no_gfh_file.read(self.m_bl_content_length))
137 bl_no_gfh_file.close()
138 tbs_bl_file.close()
139 print "===sign==="
140 if self.m_gfh.get_sig_type() == "CERT_CHAIN":
141 self.m_sig_handler = cert.CertChainV2()
142 # create key cert if key cert does not exist
143 if key_cert_path == "":
144 key_cert_path = os.path.join(self.m_out_path, "key_cert.bin")
145 if not os.path.isfile(key_cert_path):
146 key_cert_file_name = os.path.basename(os.path.abspath(key_cert_path))
147 self.m_sig_handler.create_key_cert(key_ini_path,
148 self.m_out_path,
149 key_cert_file_name)
150 key_cert_path = os.path.join(self.m_out_path, key_cert_file_name)
151 else:
152 self.m_sig_handler.set_key_cert(key_cert_path)
153 # create content cert
154 content_cert_name = "content_cert.bin"
155 self.m_sig_handler.create_content_cert(content_config_file_path,
156 tbs_bl_file_path,
157 self.m_out_path,
158 content_cert_name)
159 # create final cert chain
160 sig_name = "preloader.sig"
161 sig_file_path = os.path.join(self.m_out_path, sig_name)
162 self.m_sig_handler.output(self.m_out_path, sig_name)
163 # output final cert chain size
164 sig_size_name = "sig_size.txt"
165 sig_size_file_path = os.path.join(self.m_out_path, sig_size_name)
166 sig_size_file = open(sig_size_file_path, 'w')
167 sig_size_file.write(hex(get_file_sizeb(sig_file_path)))
168 sig_size_file.close()
169 # create final preloader image
170 if os.path.isfile(self.m_out_bl_file_path):
171 os.remove(self.m_out_bl_file_path)
172 concatb(self.m_out_bl_file_path, tbs_bl_file_path)
173 concatb(self.m_out_bl_file_path, sig_file_path)
174 # clean up
175 os.remove(os.path.join(self.m_out_path, content_cert_name))
176 elif self.m_gfh.get_sig_type() == "SINGLE_AND_PHASH":
177 self.m_sig_handler = cert.SigSingleAndPhash(self.m_gfh.get_pad_type())
178 self.m_sig_handler.set_out_path(self.m_out_path)
179 self.m_sig_handler.create(key_ini_path, tbs_bl_file_path)
180 # signature generation
181 self.m_sig_handler.sign()
182 sig_name = "preloader.sig"
183 sig_file_path = os.path.join(self.m_out_path, sig_name)
184 self.m_sig_handler.output(self.m_out_path, sig_name)
185 # output signature size
186 sig_size_name = "sig_size.txt"
187 sig_size_file_path = os.path.join(self.m_out_path, sig_size_name)
188 sig_size_file = open(sig_size_file_path, 'w')
189 sig_size_file.write(hex(get_file_sizeb(sig_file_path)))
190 sig_size_file.close()
191 # create final preloader image
192 if os.path.isfile(self.m_out_bl_file_path):
193 os.remove(self.m_out_bl_file_path)
194 concatb(self.m_out_bl_file_path, tbs_bl_file_path)
195 concatb(self.m_out_bl_file_path, sig_file_path)
196 else:
197 print "unknown signature type"
198 # clean up
199 os.remove(self.m_bl_no_gfh_file_path)
200 os.remove(tbs_bl_file_path)
201 os.remove(sig_file_path)
202 return
203
204class PbpArgs(object):
205 """
206 PbpArgs is used to pass parameter to pbp.
207 This structure is both used when user executes this python script directly or imports
208 this module and use exported method.
209 """
210 def __init__(self):
211 self.op = None
212 self.padding = None
213 self.key_ini_path = None
214 self.key_path = None
215 self.gfh_cfg_ini_path = None
216 self.cnt_cfg_ini_path = None
217 self.key_cert_path = None
218 self.input_bl_path = None
219 self.tmp_output_path = None
220 self.output_path = None
221 def reset(self):
222 self.__init__()
223 def dump(self):
224 """
225 dump parameters.
226 """
227 f = lambda arg: 'Not Set' if arg is None else arg
228 print "op = " + f(self.op)
229 print "padding = " + f(self.padding)
230 print "key_ini_path = " + f(self.key_ini_path)
231 print "key_path = " + f(self.key_path)
232 print "gfh_cfg_ini_path = " + f(self.gfh_cfg_ini_path)
233 print "cnt_cfg_ini_path = " + f(self.cnt_cfg_ini_path)
234 print "key_cert_path = " + f(self.key_cert_path)
235 print "input_bl_path = " + f(self.input_bl_path)
236 print "tmp_output_path = " + f(self.tmp_output_path)
237 print "output_path = " + f(self.output_path)
238
239def _op_sign(args):
240 """
241 Sign/re-sign operation
242 """
243 bl_obj = Bl(args.tmp_output_path, args.input_bl_path, args.output_path)
244 bl_obj.create_gfh(args.gfh_cfg_ini_path)
245 bl_obj.sign(args.key_ini_path, args.key_cert_path, args.cnt_cfg_ini_path)
246 return 0
247
248def _op_keybin(args):
249 """
250 Generate root key data structure for root public key authentication.
251 """
252 key = cert.CtKey(args.padding)
253 key.create(args.key_path)
254 key_bin = key.pack()
255 out_file = open(args.output_path, "wb")
256 out_file.write(key_bin)
257 out_file.close()
258 return 0
259
260def _op_keybin_pss(args):
261 """
262 Root key data structures are different for different padding. Here we handles pss padding.
263 """
264 args.padding = 'pss'
265 return _op_keybin(args)
266
267def _op_keybin_legacy(args):
268 """
269 Root key data structures are different for different padding. Here we handles legacy padding.
270 """
271 args.padding = 'legacy'
272 return _op_keybin(args)
273
274def _op_keyhash(args):
275 """
276 Generate hash of root key data structure, which is dependent on padding used.
277 """
278 key = cert.CtKey(args.padding)
279 key.create(args.key_path)
280 key_bin = key.pack()
281 tmp_key_bin_path = os.path.join(args.tmp_output_path, "tmp_keybin.bin")
282 out_file = open(tmp_key_bin_path, "wb")
283 out_file.write(key_bin)
284 out_file.close()
285 cert.hash_gen(tmp_key_bin_path, args.output_path)
286 os.remove(tmp_key_bin_path)
287 return 0
288
289def _op_keyhash_pss(args):
290 """
291 Root key data struture hash for pss padding.
292 """
293 args.padding = 'pss'
294 return _op_keyhash(args)
295
296def _op_keyhash_legacy(args):
297 """
298 Root key data struture hash for legacy padding.
299 """
300 args.padding = 'legacy'
301 return _op_keyhash(args)
302
303def pbp_op(args):
304 """
305 Handles and dispatches all operations supported by pbp.
306 """
307 supported_ops = {
308 'sign': _op_sign,
309 'keybin_pss': _op_keybin_pss,
310 'keybin_legacy': _op_keybin_legacy,
311 'keyhash_pss': _op_keyhash_pss,
312 'keyhash_legacy': _op_keyhash_legacy
313 }
314
315 if args.output_path is None:
316 print "output path is not given!"
317 return -1
318
319 if args.op is None:
320 print "op is not given!"
321 return -1
322
323 if args.op == 'sign':
324 if not args.input_bl_path:
325 print "bootloader path is not given!"
326 return -1
327 if (args.key_ini_path is None) and (args.key_cert_path is None):
328 print "key path is not given!"
329 return -1
330 else:
331 if (args.key_ini_path is None) and (args.key_path is None):
332 print "key path is not given!"
333 return -1
334
335 args.tmp_output_path = os.path.dirname(os.path.abspath(args.output_path))
336 if not os.path.exists(args.tmp_output_path):
337 os.makedirs(args.tmp_output_path)
338
339 op_f = supported_ops.get(args.op)
340 return op_f(args)
341
342
343def main():
344 """
345 Main function for pbp, which is used when pbp.py is executed directly.
346 Note that we changed input bootloader parameter to -in_bl $BL_PATH.
347 Please remember to add -in_bl if you're migrating from previous version.
348 """
349 parser = argparse.ArgumentParser(description='pbp tool for preloader gfh \
350creation/replacement and signing/re-signing')
351 parser.add_argument('-i', dest='key_ini_path', help='key configuartion path')
352 parser.add_argument('-j', dest='key_path', help='key path (with pem format)')
353 parser.add_argument('-g', dest='gfh_cfg_ini_path', help='gfh(generaic file header) \
354configuration path')
355 parser.add_argument('-c', dest='cnt_cfg_ini_path', help='content certificate \
356configuration path')
357 parser.add_argument('-k', dest='key_cert_path', help='key certificate path')
358 parser.add_argument('-func', dest='op', help='operation to be performed', required=True)
359 parser.add_argument('-o', dest='output_path', help='output file path')
360 parser.add_argument('input_bl_path', nargs='?', help='input file path')
361
362 pbp_args = parser.parse_args()
363 return pbp_op(pbp_args)
364
365
366if __name__ == '__main__':
367 main()