blob: 6ca7326c4cbab674f25e9be649f2ca9c1bae6fe6 [file] [log] [blame]
"""
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()