整个流程 大概分为下面几步

  1. 先编译出来一个完整的 xxxx-target_files-eng.magesfc.zip 文件。

  2. 执行./build/tools/releasetools/sign_target_files_apks -v -o -d build/target/product/security xxx-target_files-eng.magesfc.zip sign-xxx-target_files-eng.magesfc.zip

  3. 执行 ./build/tools/releasetools/ota_from_target_files -v --path Bins/linux-x86 -kreleasekey sign-xxxx-target_files-eng.magesfc.zip sign-xxx-ota-eng.magesfc.zip 生成ota全包。

其中涉及到的代码都在 ./build/tools/releasetools 下面, 主要有2个文件 sign_target_files_apks
和 ota_from_target_files 。这2个都是python写的。

其中也会调用到外部工具命令,在编译的out目录下面的 out/host 下面找到。

先来分析 sign_target_files_apks脚本

1.先看调用的参数

1
2
3
4
5
6
7
8


./build/tools/releasetools/sign_target_files_apks -v -o -d build/target/product/security xxx-target_files-eng.magesfc.zip sign-xxx-target_files-eng.magesfc.zip

#-v 是 显示详情的,执行的时候打印的log会多一些

#-o "-o", "--replace_ota_keys"
#-d "-d", "--default_key_mappings"

2.再看入口方法

通常python的入口都是这样的。这个脚本也不例外。

入口很简单, 利用了一个try 执行了main()方法, 发生异常最好脚本返回值就是1了。表示失败了。最后的finally 还调用了一个cleanup()方法,
看名称应该就是做了一些清理动作,例如删除创建的临时文件,临时目录等等。

1
2
3
4
5
6
7
8
9
10
if __name__ == '__main__':
try:
main(sys.argv[1:])
except common.ExternalError as e:
print("\n ERROR: %s\n" % (e,))
sys.exit(1)
finally:
common.Cleanup()


当然除了入口是先执行的,但是文件最开始的 定义的全局变量 更是最开始执行的地方,

这里我们看到定义了全局变量 OPTIONS ,它用来保持解析后的命令行参

数选项。要特别注意其中的一些默认值,在后续的代码中会使用到这些变量来做if else的判断的,通过取值不同 执行了不同的代码段。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
logger = logging.getLogger(__name__)

OPTIONS = common.OPTIONS

OPTIONS.extra_apks = {}
OPTIONS.extra_apex_payload_keys = {}
OPTIONS.skip_apks_with_path_prefix = set()
OPTIONS.key_map = {}
OPTIONS.rebuild_recovery = False
OPTIONS.replace_ota_keys = False
OPTIONS.replace_verity_public_key = False
OPTIONS.replace_verity_private_key = False
OPTIONS.replace_verity_keyid = False
OPTIONS.tag_changes = ("-test-keys", "-dev-keys", "+release-keys")
OPTIONS.avb_keys = {}
OPTIONS.avb_algorithms = {}
OPTIONS.avb_extra_args = {}

0.分析main() 方法

main()方法接收了一个参数,这个参数是sys.argv[1:],这个表示命令行上除了第一个后面所有的参数。

也就是除了脚本路径 后面的所有的。

这里就可能是 -v -o -d build/target/product/security xxx-target_files-eng.magesfc.zip sign-xxx-target_files-eng.magesfc.zip

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71

def main(argv):

key_mapping_options = []
#开头这里定义了一个处理命令行参数的方法,
def option_handler(o, a):
# 中间省略
return True

args = common.ParseOptions(
argv, __doc__,
extra_opts="e:d:k:ot:",
extra_long_opts=[
# 中间省略
],
extra_option_handler=option_handler)#这个处理命令行参数方法会传给 common.ParseOptions 使用。

if len(args) != 2: # 判断给的 命令参数 是否合法, 至少长度是2啊。
common.Usage(__doc__)# 这里args是经过 解析之后的, 解析命令行参数,选项的 都放到OPTIONS变量中了。剩下的2个作为args拉。
sys.exit(1)

common.InitLogging() # 初始化 log 方法

# 这里的args[0] 就是 xxx-target_files-eng.magesfc.zip
# args[1] 就是 sign-xxx-target_files-eng.magesfc.zip
input_zip = zipfile.ZipFile(args[0], "r")
output_zip = zipfile.ZipFile(args[1], "w",
compression=zipfile.ZIP_DEFLATED,
allowZip64=True)
# 这里打开输入的zip文件作来读取。打开输出的zip文件来保存处理后的内容,也就是sign后的
misc_info = common.LoadInfoDict(input_zip)

BuildKeyMap(misc_info, key_mapping_options)

apk_keys_info, compressed_extension = common.ReadApkCerts(input_zip)
apk_keys = GetApkCerts(apk_keys_info)

apex_keys_info = ReadApexKeysInfo(input_zip)
apex_keys = GetApexKeys(apex_keys_info, apk_keys)

CheckApkAndApexKeysAvailable(
input_zip,
set(apk_keys.keys()) | set(apex_keys.keys()),
compressed_extension,
apex_keys)

key_passwords = common.GetKeyPasswords(
set(apk_keys.values()) | set(itertools.chain(*apex_keys.values())))
platform_api_level, _ = GetApiLevelAndCodename(input_zip)
codename_to_api_level_map = GetCodenameToApiLevelMap(input_zip)

ProcessTargetFiles(input_zip, output_zip, misc_info,
apk_keys, apex_keys, key_passwords,
platform_api_level, codename_to_api_level_map,
compressed_extension)

common.ZipClose(input_zip)
common.ZipClose(output_zip)

# Skip building userdata.img and cache.img when signing the target files.
new_args = ["--is_signing", "--add_missing"]
# add_img_to_target_files builds the system image from scratch, so the
# recovery patch is guaranteed to be regenerated there.
if OPTIONS.rebuild_recovery:
new_args.append("--rebuild_recovery")
new_args.append(args[1])
add_img_to_target_files.main(new_args)

print("done.")


0.1 common.LoadInfoDict(input_zip)分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#Loads the key/value pairs from the given input target_files.
# 该方法就是读取 "META/misc_info.txt" 文件内容 按行解析 键值对。返回一个字典
def LoadInfoDict(input_file, repacking=False):
pass
这里给出一个misc_info.txt 文件内容
recovery_api_version=3
fstab_version=2
blocksize=131072
boot_size=0x06000000
no_recovery=true
include_recovery_dtbo=true
recovery_mount_options=ext4=max_batch_time=0,commit=1,data=ordered,barrier=1,errors=panic,nodelalloc
tool_extensions=device/xiaomi/sm8150_common
default_system_dev_certificate=build/target/product/security/testkey
mkbootimg_args=--header_version 1
mkbootimg_version_args=--os_version 10 --os_patch_level 2019-10-01
multistage_support=1
blockimgdiff_versions=3,4
avb_enable=true
avb_vbmeta_key_path=external/avb/test/data/testkey_rsa4096.pem
avb_vbmeta_algorithm=SHA256_RSA4096
avb_vbmeta_args=--padding_size 4096
avb_boot_add_hash_footer_args=--prop com.android.build.boot.os_version:10 --prop com.android.build.boot.security_patch:2019-10-01 --prop com.android.build.boot.security_patch:2019-10-01
avb_recovery_add_hash_footer_args=
system_size=3221225472
userdata_size=12884901888
vendor_fs_type=ext4
vendor_size=1073741824
oem_size=0
ext_mkuserimg=mkuserimg_mke2fs
fs_type=ext4
extfs_sparse_flag=-s
squashfs_sparse_flag=-s
cust_fs_type=ext4
cust_size=134217728
selinux_fc=out/target/product/skywalker/obj/ETC/file_contexts.bin_intermediates/file_contexts.bin
avb_avbtool=avbtool
avb_system_hashtree_enable=true
avb_system_add_hashtree_footer_args=--prop com.android.build.system.os_version:10 --prop com.android.build.system.security_patch:2019-10-01 --prop com.android.build.system.security_patch:2019-10-01 --rollback_index 0 --setup_as_rootfs_from_kernel
avb_system_key_path=external/avb/test/data/testkey_rsa2048.pem
avb_system_algorithm=SHA256_RSA2048
avb_system_rollback_index_location=1
avb_system_other_hashtree_enable=true
avb_system_other_add_hashtree_footer_args=
avb_vendor_hashtree_enable=true
avb_vendor_add_hashtree_footer_args=--prop com.android.build.vendor.os_version:10 --prop com.android.build.vendor.security_patch:2019-10-01 --prop com.android.build.vendor.security_patch:2019-10-01
avb_product_hashtree_enable=true
avb_product_add_hashtree_footer_args=--prop com.android.build.product.os_version:10 --prop com.android.build.product.security_patch:2019-10-01 --prop com.android.build.product.security_patch:2019-10-01
avb_product_services_hashtree_enable=true
avb_product_services_add_hashtree_footer_args=--prop com.android.build.product_services.os_version:10 --prop com.android.build.product_services.security_patch:2019-10-01 --prop com.android.build.product_services.security_patch:2019-10-01
avb_odm_hashtree_enable=true
avb_odm_add_hashtree_footer_args=--prop com.android.build.odm.os_version:10
recovery_as_boot=true
system_root_image=true
root_dir=out/target/product/skywalker/root
build_type=user
ab_update=true
has_dtbo=true
dtbo_size=0x0800000
avb_dtbo_add_hash_footer_args=
lpmake=lpmake
super_metadata_device=

0.2 BuildKeyMap(misc_info, key_mapping_options)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# 重新处理 OPTIONS.key_map 这个字典。 这个和 -d 选项, -k选项有些关系。
elif o in ("-d", "--default_key_mappings"):
key_mapping_options.append((None, a))
elif o in ("-k", "--key_mapping"):
key_mapping_options.append(a.split("=", 1))
如果有这2个选项了。在main方法中的 option_handler 方法会更新这个list的,本来这个list是空的,
经过这个2个选项的处理,这个list就有了值了。

-d给的是一个路径, 这里追加到 key_mapping_options 的值就是 一个元组 (None"build/target/product/security"

如果需要使用-k选项 估计要这样填写了 -k platform=build/target/product/security 然后追加进key_mapping_options的
def BuildKeyMap(misc_info, key_mapping_options):
for s, d in key_mapping_options:
if s is None: # -d option
devkey = misc_info.get("default_system_dev_certificate",
"build/target/product/security/testkey")
devkeydir = os.path.dirname(devkey)

OPTIONS.key_map.update({
devkeydir + "/testkey": d + "/releasekey",
devkeydir + "/devkey": d + "/releasekey",
devkeydir + "/releasekey": d + "/releasekey",
devkeydir + "/media": d + "/media",
devkeydir + "/shared": d + "/shared",
devkeydir + "/platform": d + "/platform",
"device/qcom/sepolicy/generic/vendor/timeservice" + "/timeservice_app_cert" : d + "/timeservice",
})
else:
OPTIONS.key_map[s] = d

如果 选项 -k platform=build/target/product/security 这里 就会走到else这里,
OPTIONS.key_map[s] = d 就会是这样的 OPTIONS.key_map[“platform”] = “build/target/product/security”

如果选项 -d build/target/product/security 就会走到if 下面。
首先 devkey = misc_info.get("default_system_dev_certificate",
"build/target/product/security/testkey")
获取编译这个 zip 使用的是testkey 还是 其他的什么key, 这里从misc_info 文件中读取的信息,如果没用找到 default_system_dev_certificate
文件信息,这里默认选择使用testkey。
最后会更新 OPTIONS.key_map 这个字典, 这里就是坐了个映射, testkey 替换 为release key, devkey 替换为 releasekey。
这个后续签名 apk 时候 就会用到这个字典映射关系。
默认编译出来的 zip 文件 里面的apk 有的是 testkey的,坐了这样一个映射之后 重新 签名 之后就会替换为releasekey。

0.3 apk_keys_info, compressed_extension = common.ReadApkCerts(input_zip)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
  apk_keys_info, compressed_extension = common.ReadApkCerts(input_zip)

Parses the APK certs info from a given target-files zip
解析 target files zip文件。获取apk的certs 信息

其实就是 读取META/apkcerts.txt ,解析其中的每一行

def ReadApkCerts(tf_zip):
"""Parses the APK certs info from a given target-files zip.

Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
tuple with the following elements: (1) a dictionary that maps packages to
certs (based on the "certificate" and "private_key" attributes in the file;
(2) a string representing the extension of compressed APKs in the target files
(e.g ".gz", ".bro").

Args:
tf_zip: The input target_files ZipFile (already open).

Returns:
(certmap, ext): certmap is a dictionary that maps packages to certs; ext is
the extension string of compressed APKs (e.g. ".gz"), or None if there's
no compressed APKs.
"""
certmap = {}
compressed_extension = None

# META/apkcerts.txt contains the info for _all_ the packages known at build
# time. Filter out the ones that are not installed.
installed_files = set()
for name in tf_zip.namelist():
basename = os.path.basename(name)
if basename:
installed_files.add(basename) # 遍历zip文件中的所有文件,放到一个 set中。这样就不会有重复的文件名了。

for line in tf_zip.read("META/apkcerts.txt").split("\n"): # 直接读取 META/apkcerts.txt 文件按行分割然后遍历没一行
line = line.strip()# 去除多余空格指令的字符
if not line: # 如果上空行 跳过
continue
m = re.match( # 这里初始化 匹配 每一行 内容的正则, 其中就是用这个正则来 解析了每一行的对应内容了
r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*)")?$',
line)
if not m: # 如果没用匹配 就跳过
continue
# name="AaptAutoVersionTest.apk" certificate="build/target/product/security/testkey.x509.pem" private_key="build/target/product/security/testkey.pk8"

matches = m.groupdict()
cert = matches["CERT"] # 这个匹配 certificate= 的值
privkey = matches["PRIVKEY"] # 这个匹配 private_key= 的值
name = matches["NAME"] # 这个匹配 name= 的值,也就是apk名称
this_compressed_extension = matches["COMPRESSED"] # 这个匹配

public_key_suffix_len = len(OPTIONS.public_key_suffix)
private_key_suffix_len = len(OPTIONS.private_key_suffix)
if cert in SPECIAL_CERT_STRINGS and not privkey:
certmap[name] = cert
elif (cert.endswith(OPTIONS.public_key_suffix) and
privkey.endswith(OPTIONS.private_key_suffix) and
cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
certmap[name] = cert[:-public_key_suffix_len]
else:
raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)

if not this_compressed_extension:
continue

# Only count the installed files.
filename = name + '.' + this_compressed_extension
if filename not in installed_files:
continue

# Make sure that all the values in the compression map have the same
# extension. We don't support multiple compression methods in the same
# system image.
if compressed_extension:
if this_compressed_extension != compressed_extension:
raise ValueError(
"Multiple compressed extensions: {} vs {}".format(
compressed_extension, this_compressed_extension))
else:
compressed_extension = this_compressed_extension

return (certmap,
("." + compressed_extension) if compressed_extension else None)

0.4 apk_keys = GetApkCerts(apk_keys_info)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
  apk_keys = GetApkCerts(apk_keys_info)
这里调用 GetApkCerts 方法, apk_keys_info 就是由上一步 生成的 一个 字典,
由 apk 名 对应 apk签名的key名称。

def GetApkCerts(certmap):
# apply the key remapping to the contents of the file
for apk, cert in certmap.iteritems():
certmap[apk] = OPTIONS.key_map.get(cert, cert)

# apply all the -e options, overriding anything in the file
for apk, cert in OPTIONS.extra_apks.iteritems():
if not cert:
cert = "PRESIGNED"
certmap[apk] = OPTIONS.key_map.get(cert, cert)

return certmap
这里就是处理各个apk 到底 使用 哪个key来签名, 这里使用了 0.2 BuildKeyMap(misc_info, key_mapping_options)
这一步的那个映射 的 OPTIONS.key_map 字典来替换为新的key来签名。

certmap类似下面的:
certmap['AaptAutoVersionTest'] = "build/target/product/security/testkey.x509.pem"
然后经过GetApkCerts的转换就会 转换为新的key了。然后在返回。

0.5 apex_keys_info = ReadApexKeysInfo(input_zip)

1
2
#这个和 0.3   apk_keys_info, compressed_extension = common.ReadApkCerts(input_zip) 这一步类似。

0.6 apex_keys = GetApexKeys(apex_keys_info, apk_keys)

1
#这个和  0.5 apex_keys_info = ReadApexKeysInfo(input_zip) 这一步类似。

0.7 CheckApkAndApexKeysAvailable()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
def CheckApkAndApexKeysAvailable(input_tf_zip, known_keys, compressed_extension, apex_keys):
unknown_files = []
for info in input_tf_zip.infolist():
if (info.filename.startswith('SYSTEM/apex') and info.filename.endswith('.apex')):
name = os.path.basename(info.filename)
if name not in known_keys:
unknown_files.append(name)
continue

# And APKs.
(is_apk, is_compressed, should_be_skipped) = GetApkFileInfo(info.filename, compressed_extension, OPTIONS.skip_apks_with_path_prefix)
if not is_apk or should_be_skipped:
continue

name = os.path.basename(info.filename)
if is_compressed:
name = name[:-len(compressed_extension)]
if name not in known_keys:
unknown_files.append(name)

assert not unknown_files, \
("No key specified for:\n {}\n"
"Use '-e <apkname>=' to specify a key (which may be an empty string to "
"not sign this apk).".format("\n ".join(unknown_files)))

这个方法用来变量zip文件中的所有文件,挑选出其中的 apex 和 apk 结尾的文件,
konwn_keys 是 上面几步生成的 set(apk_keys.keys()) | set(apex_keys.keys()),的一个并集, 也就是
apexkeys.txt 和 apkcerts.txt 文件中有的 所有的apk的名称,
然后遍历zip文件中的所有的 apk 和 这个konwn_keys文件做判断,如果konwn_keys中不存在就会报错了。

这个如果某个apk 打包到 zip文件中是直接复制的方式 apexkeys.txt 和 apkcerts.txt 文件中就不会存在这个apk的信息。
这样这个apk 到这一步就会报错了。


0.8 common.GetKeyPasswords()

1

0.9 platform_api_level, _ = GetApiLevelAndCodename(input_zip)

1

0.10 codename_to_api_level_map = GetCodenameToApiLevelMap(input_zip)

1

0.11 进入到最主要的一个方法ProcessTargetFiles()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 看名称就知道这个方法是用来处理 target-files.zip 文件的
input_zip = zipfile.ZipFile(args[0], "r")
output_zip = zipfile.ZipFile(args[1], "w", compression=zipfile.ZIP_DEFLATED, allowZip64=True)


ProcessTargetFiles(input_zip, output_zip, misc_info,
apk_keys, apex_keys, key_passwords,
platform_api_level, codename_to_api_level_map,
compressed_extension)


common.ZipClose(input_zip)
common.ZipClose(output_zip)

input_zip, output_zip 是命令参数传入的2zip文件路径
misc_info由 common.LoadInfoDict(input_zip) 方法解析获取到的,读取zip文件中的META/misc_info.txt文件解析





1
2
3
4
5
def ProcessTargetFiles(input_tf_zip, output_tf_zip, misc_info,
apk_keys, apex_keys, key_passwords,
platform_api_level, codename_to_api_level_map,
compressed_extension):