| """ |
| pbp is a tool that signs/re-signs bootloader and generate data for root public key authentication. |
| """ |
| import os |
| import shutil |
| import argparse |
| from lib import gfh |
| from lib import cert |
| |
| |
| def get_file_sizeb(file_path): |
| """ |
| Get size of binary file |
| """ |
| if not os.path.isfile(file_path): |
| return 0 |
| file_handle = open(file_path, "rb") |
| file_handle.seek(0, 2) |
| file_size = file_handle.tell() |
| file_handle.close() |
| return file_size |
| |
| |
| def concatb(file1_path, file2_path): |
| """ |
| Concatenate binary files |
| """ |
| file2_size = get_file_sizeb(file2_path) |
| file1 = open(file1_path, "ab+") |
| file2 = open(file2_path, "rb") |
| file1.write(file2.read(file2_size)) |
| file2.close() |
| file1.close() |
| |
| |
| class Bl(object): |
| """ |
| Bl, which stands for preloader in Mediatek solution. |
| Mediatek preloader is loaded/verified by BootROM and its format is determined by BootROM |
| and is different from other images due to several reasons. |
| It has basic format as follows: |
| ======================= |
| GFH |
| ======================= |
| Preloader_NO_GFH.bin |
| ======================= |
| Sig |
| ======================= |
| Where Preloader_NO_GFH.bin is converted from preloader.elf. |
| """ |
| def __init__(self, out_path, in_bootloader_file_path, out_bootloader_file_path): |
| self.m_gfh = gfh.ImageGFH() |
| self.m_out_path = out_path |
| if not os.path.exists(self.m_out_path): |
| os.makedirs(self.m_out_path) |
| self.m_in_bl_file_path = in_bootloader_file_path |
| self.m_out_bl_file_path = out_bootloader_file_path |
| self.m_bl_is_signed = False |
| self.m_bl_content_offset = 0 |
| # initialize content size to bl file size |
| self.m_bl_content_length = get_file_sizeb(self.m_in_bl_file_path) |
| self.m_bl_sig_size = 0 |
| # generate file path for bl without gfh and signature |
| bl_path = os.path.splitext(in_bootloader_file_path) |
| self.m_bl_no_gfh_file_path = bl_path[0] + "_plain.bin" |
| self.m_sig_ver = 0 |
| self.m_sw_ver = 0 |
| self.m_root_prvk_path = "" |
| self.m_img_prvk_path = "" |
| self.m_ac_key = 0 |
| self.m_sig_handler = None |
| |
| def is_signed(self): |
| """ |
| GFH and signature are added after bootloader image has been processed by pbp. |
| We use this fact to determine whether bootloader image is signed. |
| """ |
| if self.m_in_bl_file_path: |
| bl_file = open(self.m_in_bl_file_path, "rb") |
| gfh_hdr_obj = gfh.GFHHeader() |
| gfh_hdr_size = gfh_hdr_obj.get_size() |
| gfh_hdr_buf = bl_file.read(gfh_hdr_size) |
| self.m_bl_is_signed = gfh_hdr_obj.is_gfh(gfh_hdr_buf) |
| bl_file.close() |
| return self.m_bl_is_signed |
| |
| def parse(self): |
| """ |
| If image is signed, we remove GFH and signature. Removed GFH is parsed and |
| stored. Stored GFH will be used later if GFH ini file is not given. |
| """ |
| print "===parse bootloader===" |
| # image will be decomposed if it's signed |
| if self.is_signed(): |
| gfh_total_size = self.m_gfh.parse(self.m_in_bl_file_path) |
| self.m_bl_content_offset = gfh_total_size |
| self.m_bl_content_length -= gfh_total_size |
| self.m_bl_content_length -= self.m_gfh.get_sig_size() |
| self.m_bl_sig_size = self.m_gfh.get_sig_size() |
| in_file = open(self.m_in_bl_file_path, "rb") |
| out_file = open(self.m_bl_no_gfh_file_path, "wb") |
| in_file.seek(self.m_bl_content_offset) |
| out_file.write(in_file.read(self.m_bl_content_length)) |
| out_file.close() |
| in_file.close() |
| else: |
| shutil.copyfile(self.m_in_bl_file_path, self.m_bl_no_gfh_file_path) |
| print "bootloader content size = " + hex(self.m_bl_content_length) |
| |
| def create_gfh(self, gfh_config): |
| """ |
| GFH creation. GFH may be created from parsed/stored GFH config or from GFH config file |
| provided by user. |
| """ |
| self.parse() |
| if gfh_config: |
| if self.is_signed(): |
| del self.m_gfh.gfhs[:] |
| self.m_gfh.load_ini(gfh_config) |
| elif not self.is_signed(): |
| print "GFH_CONFIG.ini does not exist!!" |
| return -1 |
| # self.m_gfh.dump() |
| return 0 |
| |
| def sign(self, key_ini_path, key_cert_path, content_config_file_path): |
| """ |
| Sign bootloader according to its signature type, which is stored in GFH. |
| """ |
| self.m_gfh.finalize(self.m_bl_content_length, key_ini_path) |
| # create tbs_bootloader.bin |
| tbs_bl_file_path = os.path.join(self.m_out_path, "tbs_preloader.bin") |
| tbs_bl_file = open(tbs_bl_file_path, "wb") |
| tbs_bl_file.write(self.m_gfh.pack()) |
| bl_no_gfh_file = open(self.m_bl_no_gfh_file_path, "rb") |
| tbs_bl_file.write(bl_no_gfh_file.read(self.m_bl_content_length)) |
| bl_no_gfh_file.close() |
| tbs_bl_file.close() |
| print "===sign===" |
| if self.m_gfh.get_sig_type() == "CERT_CHAIN": |
| self.m_sig_handler = cert.CertChainV2() |
| # create key cert if key cert does not exist |
| if key_cert_path == "": |
| key_cert_path = os.path.join(self.m_out_path, "key_cert.bin") |
| if not os.path.isfile(key_cert_path): |
| key_cert_file_name = os.path.basename(os.path.abspath(key_cert_path)) |
| self.m_sig_handler.create_key_cert(key_ini_path, |
| self.m_out_path, |
| key_cert_file_name) |
| key_cert_path = os.path.join(self.m_out_path, key_cert_file_name) |
| else: |
| self.m_sig_handler.set_key_cert(key_cert_path) |
| # create content cert |
| content_cert_name = "content_cert.bin" |
| self.m_sig_handler.create_content_cert(content_config_file_path, |
| tbs_bl_file_path, |
| self.m_out_path, |
| content_cert_name) |
| # create final cert chain |
| sig_name = "preloader.sig" |
| sig_file_path = os.path.join(self.m_out_path, sig_name) |
| self.m_sig_handler.output(self.m_out_path, sig_name) |
| # output final cert chain size |
| sig_size_name = "sig_size.txt" |
| sig_size_file_path = os.path.join(self.m_out_path, sig_size_name) |
| sig_size_file = open(sig_size_file_path, 'w') |
| sig_size_file.write(hex(get_file_sizeb(sig_file_path))) |
| sig_size_file.close() |
| # create final preloader image |
| if os.path.isfile(self.m_out_bl_file_path): |
| os.remove(self.m_out_bl_file_path) |
| concatb(self.m_out_bl_file_path, tbs_bl_file_path) |
| concatb(self.m_out_bl_file_path, sig_file_path) |
| # clean up |
| os.remove(os.path.join(self.m_out_path, content_cert_name)) |
| elif self.m_gfh.get_sig_type() == "SINGLE_AND_PHASH": |
| self.m_sig_handler = cert.SigSingleAndPhash(self.m_gfh.get_pad_type()) |
| self.m_sig_handler.set_out_path(self.m_out_path) |
| self.m_sig_handler.create(key_ini_path, tbs_bl_file_path) |
| # signature generation |
| self.m_sig_handler.sign() |
| sig_name = "preloader.sig" |
| sig_file_path = os.path.join(self.m_out_path, sig_name) |
| self.m_sig_handler.output(self.m_out_path, sig_name) |
| # output signature size |
| sig_size_name = "sig_size.txt" |
| sig_size_file_path = os.path.join(self.m_out_path, sig_size_name) |
| sig_size_file = open(sig_size_file_path, 'w') |
| sig_size_file.write(hex(get_file_sizeb(sig_file_path))) |
| sig_size_file.close() |
| # create final preloader image |
| if os.path.isfile(self.m_out_bl_file_path): |
| os.remove(self.m_out_bl_file_path) |
| concatb(self.m_out_bl_file_path, tbs_bl_file_path) |
| concatb(self.m_out_bl_file_path, sig_file_path) |
| else: |
| print "unknown signature type" |
| # clean up |
| os.remove(self.m_bl_no_gfh_file_path) |
| os.remove(tbs_bl_file_path) |
| os.remove(sig_file_path) |
| return |
| |
| class PbpArgs(object): |
| """ |
| PbpArgs is used to pass parameter to pbp. |
| This structure is both used when user executes this python script directly or imports |
| this module and use exported method. |
| """ |
| def __init__(self): |
| self.op = None |
| self.padding = None |
| self.key_ini_path = None |
| self.key_path = None |
| self.gfh_cfg_ini_path = None |
| self.cnt_cfg_ini_path = None |
| self.key_cert_path = None |
| self.input_bl_path = None |
| self.tmp_output_path = None |
| self.output_path = None |
| def reset(self): |
| self.__init__() |
| def dump(self): |
| """ |
| dump parameters. |
| """ |
| f = lambda arg: 'Not Set' if arg is None else arg |
| print "op = " + f(self.op) |
| print "padding = " + f(self.padding) |
| print "key_ini_path = " + f(self.key_ini_path) |
| print "key_path = " + f(self.key_path) |
| print "gfh_cfg_ini_path = " + f(self.gfh_cfg_ini_path) |
| print "cnt_cfg_ini_path = " + f(self.cnt_cfg_ini_path) |
| print "key_cert_path = " + f(self.key_cert_path) |
| print "input_bl_path = " + f(self.input_bl_path) |
| print "tmp_output_path = " + f(self.tmp_output_path) |
| print "output_path = " + f(self.output_path) |
| |
| def _op_sign(args): |
| """ |
| Sign/re-sign operation |
| """ |
| bl_obj = Bl(args.tmp_output_path, args.input_bl_path, args.output_path) |
| bl_obj.create_gfh(args.gfh_cfg_ini_path) |
| bl_obj.sign(args.key_ini_path, args.key_cert_path, args.cnt_cfg_ini_path) |
| return 0 |
| |
| def _op_keybin(args): |
| """ |
| Generate root key data structure for root public key authentication. |
| """ |
| key = cert.CtKey(args.padding) |
| key.create(args.key_path) |
| key_bin = key.pack() |
| out_file = open(args.output_path, "wb") |
| out_file.write(key_bin) |
| out_file.close() |
| return 0 |
| |
| def _op_keybin_pss(args): |
| """ |
| Root key data structures are different for different padding. Here we handles pss padding. |
| """ |
| args.padding = 'pss' |
| return _op_keybin(args) |
| |
| def _op_keybin_legacy(args): |
| """ |
| Root key data structures are different for different padding. Here we handles legacy padding. |
| """ |
| args.padding = 'legacy' |
| return _op_keybin(args) |
| |
| def _op_keyhash(args): |
| """ |
| Generate hash of root key data structure, which is dependent on padding used. |
| """ |
| key = cert.CtKey(args.padding) |
| key.create(args.key_path) |
| key_bin = key.pack() |
| tmp_key_bin_path = os.path.join(args.tmp_output_path, "tmp_keybin.bin") |
| out_file = open(tmp_key_bin_path, "wb") |
| out_file.write(key_bin) |
| out_file.close() |
| cert.hash_gen(tmp_key_bin_path, args.output_path) |
| os.remove(tmp_key_bin_path) |
| return 0 |
| |
| def _op_keyhash_pss(args): |
| """ |
| Root key data struture hash for pss padding. |
| """ |
| args.padding = 'pss' |
| return _op_keyhash(args) |
| |
| def _op_keyhash_legacy(args): |
| """ |
| Root key data struture hash for legacy padding. |
| """ |
| args.padding = 'legacy' |
| return _op_keyhash(args) |
| |
| def pbp_op(args): |
| """ |
| Handles and dispatches all operations supported by pbp. |
| """ |
| supported_ops = { |
| 'sign': _op_sign, |
| 'keybin_pss': _op_keybin_pss, |
| 'keybin_legacy': _op_keybin_legacy, |
| 'keyhash_pss': _op_keyhash_pss, |
| 'keyhash_legacy': _op_keyhash_legacy |
| } |
| |
| if args.output_path is None: |
| print "output path is not given!" |
| return -1 |
| |
| if args.op is None: |
| print "op is not given!" |
| return -1 |
| |
| if args.op == 'sign': |
| if not args.input_bl_path: |
| print "bootloader path is not given!" |
| return -1 |
| if (args.key_ini_path is None) and (args.key_cert_path is None): |
| print "key path is not given!" |
| return -1 |
| else: |
| if (args.key_ini_path is None) and (args.key_path is None): |
| print "key path is not given!" |
| return -1 |
| |
| args.tmp_output_path = os.path.dirname(os.path.abspath(args.output_path)) |
| if not os.path.exists(args.tmp_output_path): |
| os.makedirs(args.tmp_output_path) |
| |
| op_f = supported_ops.get(args.op) |
| return op_f(args) |
| |
| |
| def main(): |
| """ |
| Main function for pbp, which is used when pbp.py is executed directly. |
| Note that we changed input bootloader parameter to -in_bl $BL_PATH. |
| Please remember to add -in_bl if you're migrating from previous version. |
| """ |
| parser = argparse.ArgumentParser(description='pbp tool for preloader gfh \ |
| creation/replacement and signing/re-signing') |
| parser.add_argument('-i', dest='key_ini_path', help='key configuartion path') |
| parser.add_argument('-j', dest='key_path', help='key path (with pem format)') |
| parser.add_argument('-g', dest='gfh_cfg_ini_path', help='gfh(generaic file header) \ |
| configuration path') |
| parser.add_argument('-c', dest='cnt_cfg_ini_path', help='content certificate \ |
| configuration path') |
| parser.add_argument('-k', dest='key_cert_path', help='key certificate path') |
| parser.add_argument('-func', dest='op', help='operation to be performed', required=True) |
| parser.add_argument('-o', dest='output_path', help='output file path') |
| parser.add_argument('input_bl_path', nargs='?', help='input file path') |
| |
| pbp_args = parser.parse_args() |
| return pbp_op(pbp_args) |
| |
| |
| if __name__ == '__main__': |
| main() |