blob: 84721577b08db621dc3d60b926b4d08421dbdddd [file] [log] [blame]
Doug Zongkereef39442009-04-02 12:14:19 -07001#!/usr/bin/env python
2#
3# Copyright (C) 2008 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17"""
18Signs all the APK files in a target-files zipfile, producing a new
19target-files zip.
20
21Usage: sign_target_files_apks [flags] input_target_files output_target_files
22
Doug Zongkereef39442009-04-02 12:14:19 -070023 -e (--extra_apks) <name,name,...=key>
Tao Baoaa7e9932019-03-15 09:37:01 -070024 Add extra APK/APEX name/key pairs as though they appeared in apkcerts.txt
25 or apexkeys.txt (so mappings specified by -k and -d are applied). Keys
26 specified in -e override any value for that app contained in the
27 apkcerts.txt file, or the container key for an APEX. Option may be
28 repeated to give multiple extra packages.
29
30 --extra_apex_payload_key <name=key>
31 Add a mapping for APEX package name to payload signing key, which will
32 override the default payload signing key in apexkeys.txt. Note that the
33 container key should be overridden via the `--extra_apks` flag above.
34 Option may be repeated for multiple APEXes.
Doug Zongkereef39442009-04-02 12:14:19 -070035
Tao Bao93c2a012018-06-19 12:19:35 -070036 --skip_apks_with_path_prefix <prefix>
37 Skip signing an APK if it has the matching prefix in its path. The prefix
38 should be matching the entry name, which has partition names in upper
39 case, e.g. "VENDOR/app/", or "SYSTEM_OTHER/preloads/". Option may be
40 repeated to give multiple prefixes.
41
Doug Zongkereef39442009-04-02 12:14:19 -070042 -k (--key_mapping) <src_key=dest_key>
43 Add a mapping from the key name as specified in apkcerts.txt (the
44 src_key) to the real key you wish to sign the package with
45 (dest_key). Option may be repeated to give multiple key
46 mappings.
47
48 -d (--default_key_mappings) <dir>
49 Set up the following key mappings:
50
Doug Zongker831840e2011-09-22 10:28:04 -070051 $devkey/devkey ==> $dir/releasekey
52 $devkey/testkey ==> $dir/releasekey
53 $devkey/media ==> $dir/media
54 $devkey/shared ==> $dir/shared
55 $devkey/platform ==> $dir/platform
56
57 where $devkey is the directory part of the value of
58 default_system_dev_certificate from the input target-files's
Dan Willemsen0ab1be62019-04-09 21:35:37 -070059 META/misc_info.txt. (Defaulting to "build/make/target/product/security"
Doug Zongker831840e2011-09-22 10:28:04 -070060 if the value is not present in misc_info.
Doug Zongkereef39442009-04-02 12:14:19 -070061
62 -d and -k options are added to the set of mappings in the order
63 in which they appear on the command line.
Doug Zongker8e931bf2009-04-06 15:21:45 -070064
65 -o (--replace_ota_keys)
Tao Baoa80ed222016-06-16 14:41:24 -070066 Replace the certificate (public key) used by OTA package verification
67 with the ones specified in the input target_files zip (in the
68 META/otakeys.txt file). Key remapping (-k and -d) is performed on the
69 keys. For A/B devices, the payload verification key will be replaced
70 as well. If there're multiple OTA keys, only the first one will be used
71 for payload verification.
Doug Zongker17aa9442009-04-17 10:15:58 -070072
Doug Zongkerae877012009-04-21 10:04:51 -070073 -t (--tag_changes) <+tag>,<-tag>,...
74 Comma-separated list of changes to make to the set of tags (in
75 the last component of the build fingerprint). Prefix each with
76 '+' or '-' to indicate whether that tag should be added or
77 removed. Changes are processed in the order they appear.
Doug Zongker831840e2011-09-22 10:28:04 -070078 Default value is "-test-keys,-dev-keys,+release-keys".
Doug Zongkerae877012009-04-21 10:04:51 -070079
Tao Bao8adcfd12016-06-17 17:01:22 -070080 --replace_verity_private_key <key>
81 Replace the private key used for verity signing. It expects a filename
82 WITHOUT the extension (e.g. verity_key).
83
84 --replace_verity_public_key <key>
85 Replace the certificate (public key) used for verity verification. The
86 key file replaces the one at BOOT/RAMDISK/verity_key (or ROOT/verity_key
87 for devices using system_root_image). It expects the key filename WITH
88 the extension (e.g. verity_key.pub).
89
Badhri Jagan Sridharan35c9b122016-06-16 19:58:44 -070090 --replace_verity_keyid <path_to_X509_PEM_cert_file>
91 Replace the veritykeyid in BOOT/cmdline of input_target_file_zip
Tao Bao8adcfd12016-06-17 17:01:22 -070092 with keyid of the cert pointed by <path_to_X509_PEM_cert_file>.
Tao Bao639118f2017-06-19 15:48:02 -070093
Bowgo Tsai2fe786a2020-02-21 17:48:18 +080094 --remove_avb_public_keys <key1>,<key2>,...
95 Remove AVB public keys from the first-stage ramdisk. The key file to
96 remove is located at either of the following dirs:
97 - BOOT/RAMDISK/avb/ or
98 - BOOT/RAMDISK/first_stage_ramdisk/avb/
99 The second dir will be used for lookup if BOARD_USES_RECOVERY_AS_BOOT is
100 set to true.
101
Tao Baod6085d62019-05-06 12:55:42 -0700102 --avb_{boot,system,system_other,vendor,dtbo,vbmeta,vbmeta_system,
103 vbmeta_vendor}_algorithm <algorithm>
104 --avb_{boot,system,system_other,vendor,dtbo,vbmeta,vbmeta_system,
105 vbmeta_vendor}_key <key>
Tao Bao639118f2017-06-19 15:48:02 -0700106 Use the specified algorithm (e.g. SHA256_RSA4096) and the key to AVB-sign
107 the specified image. Otherwise it uses the existing values in info dict.
108
Tao Baod6085d62019-05-06 12:55:42 -0700109 --avb_{apex,boot,system,system_other,vendor,dtbo,vbmeta,vbmeta_system,
110 vbmeta_vendor}_extra_args <args>
Tao Bao639118f2017-06-19 15:48:02 -0700111 Specify any additional args that are needed to AVB-sign the image
112 (e.g. "--signing_helper /path/to/helper"). The args will be appended to
113 the existing ones in info dict.
Tianjie Xu88a759d2020-01-23 10:47:54 -0800114
Hongguang Chenf23364d2020-04-27 18:36:36 -0700115 --avb_extra_custom_image_key <partition=key>
116 --avb_extra_custom_image_algorithm <partition=algorithm>
117 Use the specified algorithm (e.g. SHA256_RSA4096) and the key to AVB-sign
118 the specified custom images mounted on the partition. Otherwise it uses
119 the existing values in info dict.
120
121 --avb_extra_custom_image_extra_args <partition=extra_args>
122 Specify any additional args that are needed to AVB-sign the custom images
123 mounted on the partition (e.g. "--signing_helper /path/to/helper"). The
124 args will be appended to the existing ones in info dict.
125
Bowgo Tsai27c39b02021-03-12 21:40:32 +0800126 --gki_signing_algorithm <algorithm>
127 --gki_signing_key <key>
128 Use the specified algorithm (e.g. SHA256_RSA4096) and the key to generate
129 'boot signature' in a v4 boot.img. Otherwise it uses the existing values
130 in info dict.
131
132 --gki_signing_extra_args <args>
133 Specify any additional args that are needed to generate 'boot signature'
134 (e.g. --prop foo:bar). The args will be appended to the existing ones
135 in info dict.
136
Tianjie Xu88a759d2020-01-23 10:47:54 -0800137 --android_jar_path <path>
138 Path to the android.jar to repack the apex file.
Bowgo Tsai2a781692021-10-13 17:39:33 +0800139
140 --allow_gsi_debug_sepolicy
141 Allow the existence of the file 'userdebug_plat_sepolicy.cil' under
142 (/system/system_ext|/system_ext)/etc/selinux.
143 If not set, error out when the file exists.
Doug Zongkereef39442009-04-02 12:14:19 -0700144"""
145
Tao Bao0c28d2d2017-12-24 10:37:38 -0800146from __future__ import print_function
Doug Zongkereef39442009-04-02 12:14:19 -0700147
Robert Craig817c5742013-04-19 10:59:22 -0400148import base64
Doug Zongker8e931bf2009-04-06 15:21:45 -0700149import copy
Robert Craig817c5742013-04-19 10:59:22 -0400150import errno
Narayan Kamatha07bf042017-08-14 14:49:21 +0100151import gzip
Tao Baobb733882019-07-24 23:31:19 -0700152import io
Tao Baoaa7e9932019-03-15 09:37:01 -0700153import itertools
Tao Baobadceb22019-03-15 09:33:43 -0700154import logging
Doug Zongkereef39442009-04-02 12:14:19 -0700155import os
156import re
Narayan Kamatha07bf042017-08-14 14:49:21 +0100157import shutil
Tao Bao9fdd00f2017-07-12 11:57:05 -0700158import stat
Doug Zongkereef39442009-04-02 12:14:19 -0700159import subprocess
Tao Bao0c28d2d2017-12-24 10:37:38 -0800160import sys
Doug Zongkereef39442009-04-02 12:14:19 -0700161import tempfile
162import zipfile
Tao Bao66472632017-12-04 17:16:36 -0800163from xml.etree import ElementTree
Doug Zongkereef39442009-04-02 12:14:19 -0700164
Doug Zongker3c84f562014-07-31 11:06:30 -0700165import add_img_to_target_files
Tao Baoaa7e9932019-03-15 09:37:01 -0700166import apex_utils
Doug Zongkereef39442009-04-02 12:14:19 -0700167import common
168
Tao Bao0c28d2d2017-12-24 10:37:38 -0800169
170if sys.hexversion < 0x02070000:
171 print("Python 2.7 or newer is required.", file=sys.stderr)
172 sys.exit(1)
173
174
Tao Baobadceb22019-03-15 09:33:43 -0700175logger = logging.getLogger(__name__)
176
Doug Zongkereef39442009-04-02 12:14:19 -0700177OPTIONS = common.OPTIONS
178
179OPTIONS.extra_apks = {}
Tao Baoaa7e9932019-03-15 09:37:01 -0700180OPTIONS.extra_apex_payload_keys = {}
Tao Bao93c2a012018-06-19 12:19:35 -0700181OPTIONS.skip_apks_with_path_prefix = set()
Doug Zongkereef39442009-04-02 12:14:19 -0700182OPTIONS.key_map = {}
Tianjie Xu616fbeb2017-05-23 14:51:02 -0700183OPTIONS.rebuild_recovery = False
Doug Zongker8e931bf2009-04-06 15:21:45 -0700184OPTIONS.replace_ota_keys = False
Geremy Condraf19b3652014-07-29 17:54:54 -0700185OPTIONS.replace_verity_public_key = False
186OPTIONS.replace_verity_private_key = False
Badhri Jagan Sridharan35c9b122016-06-16 19:58:44 -0700187OPTIONS.replace_verity_keyid = False
Bowgo Tsai2fe786a2020-02-21 17:48:18 +0800188OPTIONS.remove_avb_public_keys = None
Doug Zongker831840e2011-09-22 10:28:04 -0700189OPTIONS.tag_changes = ("-test-keys", "-dev-keys", "+release-keys")
Tao Bao639118f2017-06-19 15:48:02 -0700190OPTIONS.avb_keys = {}
191OPTIONS.avb_algorithms = {}
192OPTIONS.avb_extra_args = {}
Bowgo Tsai27c39b02021-03-12 21:40:32 +0800193OPTIONS.gki_signing_key = None
194OPTIONS.gki_signing_algorithm = None
195OPTIONS.gki_signing_extra_args = None
Tianjie Xu88a759d2020-01-23 10:47:54 -0800196OPTIONS.android_jar_path = None
Daniel Norman78554ea2021-09-14 10:29:38 -0700197OPTIONS.vendor_partitions = set()
198OPTIONS.vendor_otatools = None
Bowgo Tsai2a781692021-10-13 17:39:33 +0800199OPTIONS.allow_gsi_debug_sepolicy = False
Doug Zongkereef39442009-04-02 12:14:19 -0700200
Tao Bao0c28d2d2017-12-24 10:37:38 -0800201
Tao Bao19b02fe2019-10-09 00:04:28 -0700202AVB_FOOTER_ARGS_BY_PARTITION = {
Tianjiebf0b8a82021-03-03 17:31:04 -0800203 'boot': 'avb_boot_add_hash_footer_args',
Devin Mooreafdd7c72021-12-13 22:04:08 +0000204 'init_boot': 'avb_init_boot_add_hash_footer_args',
Tianjiebf0b8a82021-03-03 17:31:04 -0800205 'dtbo': 'avb_dtbo_add_hash_footer_args',
206 'product': 'avb_product_add_hashtree_footer_args',
207 'recovery': 'avb_recovery_add_hash_footer_args',
208 'system': 'avb_system_add_hashtree_footer_args',
209 'system_ext': 'avb_system_ext_add_hashtree_footer_args',
210 'system_other': 'avb_system_other_add_hashtree_footer_args',
211 'odm': 'avb_odm_add_hashtree_footer_args',
212 'odm_dlkm': 'avb_odm_dlkm_add_hashtree_footer_args',
213 'pvmfw': 'avb_pvmfw_add_hash_footer_args',
214 'vendor': 'avb_vendor_add_hashtree_footer_args',
215 'vendor_boot': 'avb_vendor_boot_add_hash_footer_args',
216 'vendor_dlkm': "avb_vendor_dlkm_add_hashtree_footer_args",
217 'vbmeta': 'avb_vbmeta_args',
218 'vbmeta_system': 'avb_vbmeta_system_args',
219 'vbmeta_vendor': 'avb_vbmeta_vendor_args',
Tao Bao19b02fe2019-10-09 00:04:28 -0700220}
221
222
Tianjiebf0b8a82021-03-03 17:31:04 -0800223# Check that AVB_FOOTER_ARGS_BY_PARTITION is in sync with AVB_PARTITIONS.
224for partition in common.AVB_PARTITIONS:
225 if partition not in AVB_FOOTER_ARGS_BY_PARTITION:
226 raise RuntimeError("Missing {} in AVB_FOOTER_ARGS".format(partition))
227
Daniel Norman78554ea2021-09-14 10:29:38 -0700228# Partitions that can be regenerated after signing using a separate
229# vendor otatools package.
230ALLOWED_VENDOR_PARTITIONS = set(["vendor", "odm"])
231
Tianjiebf0b8a82021-03-03 17:31:04 -0800232
Tianjie4d48d502021-06-11 17:03:43 -0700233def IsApexFile(filename):
234 return filename.endswith(".apex") or filename.endswith(".capex")
235
236
237def GetApexFilename(filename):
238 name = os.path.basename(filename)
239 # Replace the suffix for compressed apex
240 if name.endswith(".capex"):
241 return name.replace(".capex", ".apex")
242 return name
243
244
Narayan Kamatha07bf042017-08-14 14:49:21 +0100245def GetApkCerts(certmap):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800246 # apply the key remapping to the contents of the file
Tao Baoa3705452019-06-24 15:33:41 -0700247 for apk, cert in certmap.items():
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800248 certmap[apk] = OPTIONS.key_map.get(cert, cert)
249
250 # apply all the -e options, overriding anything in the file
Tao Baoa3705452019-06-24 15:33:41 -0700251 for apk, cert in OPTIONS.extra_apks.items():
Doug Zongkerdecf9952009-12-15 17:27:49 -0800252 if not cert:
253 cert = "PRESIGNED"
Doug Zongkerad88c7c2009-04-14 12:34:27 -0700254 certmap[apk] = OPTIONS.key_map.get(cert, cert)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800255
Doug Zongkereef39442009-04-02 12:14:19 -0700256 return certmap
257
258
Tao Baoaa7e9932019-03-15 09:37:01 -0700259def GetApexKeys(keys_info, key_map):
260 """Gets APEX payload and container signing keys by applying the mapping rules.
261
Tao Baoe1343992019-03-19 12:24:03 -0700262 Presigned payload / container keys will be set accordingly.
Tao Baoaa7e9932019-03-15 09:37:01 -0700263
264 Args:
265 keys_info: A dict that maps from APEX filenames to a tuple of (payload_key,
Jooyung Han8caba5e2021-10-27 03:58:09 +0900266 container_key, sign_tool).
Tao Baoaa7e9932019-03-15 09:37:01 -0700267 key_map: A dict that overrides the keys, specified via command-line input.
268
269 Returns:
270 A dict that contains the updated APEX key mapping, which should be used for
271 the current signing.
Tao Baof98fa102019-04-24 14:51:25 -0700272
273 Raises:
274 AssertionError: On invalid container / payload key overrides.
Tao Baoaa7e9932019-03-15 09:37:01 -0700275 """
276 # Apply all the --extra_apex_payload_key options to override the payload
277 # signing keys in the given keys_info.
278 for apex, key in OPTIONS.extra_apex_payload_keys.items():
Tao Baoe1343992019-03-19 12:24:03 -0700279 if not key:
280 key = 'PRESIGNED'
Tao Bao34223092019-07-11 11:52:52 -0700281 if apex not in keys_info:
282 logger.warning('Failed to find %s in target_files; Ignored', apex)
283 continue
Jooyung Han8caba5e2021-10-27 03:58:09 +0900284 keys_info[apex] = (key, keys_info[apex][1], keys_info[apex][2])
Tao Baoaa7e9932019-03-15 09:37:01 -0700285
286 # Apply the key remapping to container keys.
Jooyung Han8caba5e2021-10-27 03:58:09 +0900287 for apex, (payload_key, container_key, sign_tool) in keys_info.items():
288 keys_info[apex] = (payload_key, key_map.get(container_key, container_key), sign_tool)
Tao Baoaa7e9932019-03-15 09:37:01 -0700289
290 # Apply all the --extra_apks options to override the container keys.
291 for apex, key in OPTIONS.extra_apks.items():
292 # Skip non-APEX containers.
293 if apex not in keys_info:
294 continue
Tao Baoe1343992019-03-19 12:24:03 -0700295 if not key:
296 key = 'PRESIGNED'
Jooyung Han8caba5e2021-10-27 03:58:09 +0900297 keys_info[apex] = (keys_info[apex][0], key_map.get(key, key), keys_info[apex][2])
Tao Baoaa7e9932019-03-15 09:37:01 -0700298
Tao Baof98fa102019-04-24 14:51:25 -0700299 # A PRESIGNED container entails a PRESIGNED payload. Apply this to all the
300 # APEX key pairs. However, a PRESIGNED container with non-PRESIGNED payload
301 # (overridden via commandline) indicates a config error, which should not be
302 # allowed.
Jooyung Han8caba5e2021-10-27 03:58:09 +0900303 for apex, (payload_key, container_key, sign_tool) in keys_info.items():
Tao Baof98fa102019-04-24 14:51:25 -0700304 if container_key != 'PRESIGNED':
305 continue
306 if apex in OPTIONS.extra_apex_payload_keys:
307 payload_override = OPTIONS.extra_apex_payload_keys[apex]
308 assert payload_override == '', \
309 ("Invalid APEX key overrides: {} has PRESIGNED container but "
310 "non-PRESIGNED payload key {}").format(apex, payload_override)
311 if payload_key != 'PRESIGNED':
312 print(
313 "Setting {} payload as PRESIGNED due to PRESIGNED container".format(
314 apex))
Jooyung Han8caba5e2021-10-27 03:58:09 +0900315 keys_info[apex] = ('PRESIGNED', 'PRESIGNED', None)
Tao Baof98fa102019-04-24 14:51:25 -0700316
Tao Baoaa7e9932019-03-15 09:37:01 -0700317 return keys_info
318
319
Tao Bao93c2a012018-06-19 12:19:35 -0700320def GetApkFileInfo(filename, compressed_extension, skipped_prefixes):
Tao Bao11f955c2018-06-19 12:19:35 -0700321 """Returns the APK info based on the given filename.
322
323 Checks if the given filename (with path) looks like an APK file, by taking the
Tao Bao93c2a012018-06-19 12:19:35 -0700324 compressed extension into consideration. If it appears to be an APK file,
325 further checks if the APK file should be skipped when signing, based on the
326 given path prefixes.
Tao Bao11f955c2018-06-19 12:19:35 -0700327
328 Args:
329 filename: Path to the file.
330 compressed_extension: The extension string of compressed APKs (e.g. ".gz"),
331 or None if there's no compressed APKs.
Tao Bao93c2a012018-06-19 12:19:35 -0700332 skipped_prefixes: A set/list/tuple of the path prefixes to be skipped.
Tao Bao11f955c2018-06-19 12:19:35 -0700333
334 Returns:
Tao Bao93c2a012018-06-19 12:19:35 -0700335 (is_apk, is_compressed, should_be_skipped): is_apk indicates whether the
336 given filename is an APK file. is_compressed indicates whether the APK file
337 is compressed (only meaningful when is_apk is True). should_be_skipped
338 indicates whether the filename matches any of the given prefixes to be
339 skipped.
Tao Bao11f955c2018-06-19 12:19:35 -0700340
341 Raises:
Tao Bao93c2a012018-06-19 12:19:35 -0700342 AssertionError: On invalid compressed_extension or skipped_prefixes inputs.
Tao Bao11f955c2018-06-19 12:19:35 -0700343 """
344 assert compressed_extension is None or compressed_extension.startswith('.'), \
345 "Invalid compressed_extension arg: '{}'".format(compressed_extension)
346
Tao Bao93c2a012018-06-19 12:19:35 -0700347 # skipped_prefixes should be one of set/list/tuple types. Other types such as
348 # str shouldn't be accepted.
Tao Baobadceb22019-03-15 09:33:43 -0700349 assert isinstance(skipped_prefixes, (set, list, tuple)), \
350 "Invalid skipped_prefixes input type: {}".format(type(skipped_prefixes))
Tao Bao93c2a012018-06-19 12:19:35 -0700351
Tao Bao11f955c2018-06-19 12:19:35 -0700352 compressed_apk_extension = (
353 ".apk" + compressed_extension if compressed_extension else None)
354 is_apk = (filename.endswith(".apk") or
355 (compressed_apk_extension and
356 filename.endswith(compressed_apk_extension)))
357 if not is_apk:
Tao Bao93c2a012018-06-19 12:19:35 -0700358 return (False, False, False)
Tao Bao11f955c2018-06-19 12:19:35 -0700359
360 is_compressed = (compressed_apk_extension and
361 filename.endswith(compressed_apk_extension))
Tao Bao93c2a012018-06-19 12:19:35 -0700362 should_be_skipped = filename.startswith(tuple(skipped_prefixes))
363 return (True, is_compressed, should_be_skipped)
Tao Bao11f955c2018-06-19 12:19:35 -0700364
365
Tao Baoaa7e9932019-03-15 09:37:01 -0700366def CheckApkAndApexKeysAvailable(input_tf_zip, known_keys,
Tao Baoe1343992019-03-19 12:24:03 -0700367 compressed_extension, apex_keys):
Tao Baoaa7e9932019-03-15 09:37:01 -0700368 """Checks that all the APKs and APEXes have keys specified.
Tao Bao11f955c2018-06-19 12:19:35 -0700369
370 Args:
371 input_tf_zip: An open target_files zip file.
Tao Baoaa7e9932019-03-15 09:37:01 -0700372 known_keys: A set of APKs and APEXes that have known signing keys.
Tao Bao11f955c2018-06-19 12:19:35 -0700373 compressed_extension: The extension string of compressed APKs, such as
Tao Baoaa7e9932019-03-15 09:37:01 -0700374 '.gz', or None if there's no compressed APKs.
Tao Baoe1343992019-03-19 12:24:03 -0700375 apex_keys: A dict that contains the key mapping from APEX name to
Jooyung Han8caba5e2021-10-27 03:58:09 +0900376 (payload_key, container_key, sign_tool).
Tao Bao11f955c2018-06-19 12:19:35 -0700377
378 Raises:
Tao Baoaa7e9932019-03-15 09:37:01 -0700379 AssertionError: On finding unknown APKs and APEXes.
Tao Bao11f955c2018-06-19 12:19:35 -0700380 """
Tao Baoaa7e9932019-03-15 09:37:01 -0700381 unknown_files = []
Doug Zongkereb338ef2009-05-20 16:50:49 -0700382 for info in input_tf_zip.infolist():
Tianjie5bd03952021-02-18 23:02:36 -0800383 # Handle APEXes on all partitions
Tianjie4d48d502021-06-11 17:03:43 -0700384 if IsApexFile(info.filename):
385 name = GetApexFilename(info.filename)
Tao Baoaa7e9932019-03-15 09:37:01 -0700386 if name not in known_keys:
387 unknown_files.append(name)
388 continue
389
390 # And APKs.
Tao Bao93c2a012018-06-19 12:19:35 -0700391 (is_apk, is_compressed, should_be_skipped) = GetApkFileInfo(
392 info.filename, compressed_extension, OPTIONS.skip_apks_with_path_prefix)
393 if not is_apk or should_be_skipped:
Tao Bao11f955c2018-06-19 12:19:35 -0700394 continue
Tao Baoaa7e9932019-03-15 09:37:01 -0700395
Tao Bao11f955c2018-06-19 12:19:35 -0700396 name = os.path.basename(info.filename)
397 if is_compressed:
398 name = name[:-len(compressed_extension)]
Tao Baoaa7e9932019-03-15 09:37:01 -0700399 if name not in known_keys:
400 unknown_files.append(name)
Tao Bao11f955c2018-06-19 12:19:35 -0700401
Tao Baoaa7e9932019-03-15 09:37:01 -0700402 assert not unknown_files, \
Tao Bao11f955c2018-06-19 12:19:35 -0700403 ("No key specified for:\n {}\n"
404 "Use '-e <apkname>=' to specify a key (which may be an empty string to "
Tao Baoaa7e9932019-03-15 09:37:01 -0700405 "not sign this apk).".format("\n ".join(unknown_files)))
Doug Zongkereb338ef2009-05-20 16:50:49 -0700406
Tao Baoe1343992019-03-19 12:24:03 -0700407 # For all the APEXes, double check that we won't have an APEX that has only
Tao Baof98fa102019-04-24 14:51:25 -0700408 # one of the payload / container keys set. Note that non-PRESIGNED container
409 # with PRESIGNED payload could be allowed but currently unsupported. It would
410 # require changing SignApex implementation.
Tao Baoe1343992019-03-19 12:24:03 -0700411 if not apex_keys:
412 return
413
414 invalid_apexes = []
415 for info in input_tf_zip.infolist():
Tianjie4d48d502021-06-11 17:03:43 -0700416 if not IsApexFile(info.filename):
Tao Baoe1343992019-03-19 12:24:03 -0700417 continue
418
Tianjie4d48d502021-06-11 17:03:43 -0700419 name = GetApexFilename(info.filename)
420
Jooyung Han8caba5e2021-10-27 03:58:09 +0900421 (payload_key, container_key, _) = apex_keys[name]
Tao Baoe1343992019-03-19 12:24:03 -0700422 if ((payload_key in common.SPECIAL_CERT_STRINGS and
423 container_key not in common.SPECIAL_CERT_STRINGS) or
424 (payload_key not in common.SPECIAL_CERT_STRINGS and
425 container_key in common.SPECIAL_CERT_STRINGS)):
426 invalid_apexes.append(
427 "{}: payload_key {}, container_key {}".format(
428 name, payload_key, container_key))
429
430 assert not invalid_apexes, \
431 "Invalid APEX keys specified:\n {}\n".format(
432 "\n ".join(invalid_apexes))
433
Doug Zongkereb338ef2009-05-20 16:50:49 -0700434
Narayan Kamatha07bf042017-08-14 14:49:21 +0100435def SignApk(data, keyname, pw, platform_api_level, codename_to_api_level_map,
Oleg Aravin8046cb02020-06-02 16:02:38 -0700436 is_compressed, apk_name):
437 unsigned = tempfile.NamedTemporaryFile(suffix='_' + apk_name)
Doug Zongkereef39442009-04-02 12:14:19 -0700438 unsigned.write(data)
439 unsigned.flush()
440
Narayan Kamatha07bf042017-08-14 14:49:21 +0100441 if is_compressed:
442 uncompressed = tempfile.NamedTemporaryFile()
Tao Bao0c28d2d2017-12-24 10:37:38 -0800443 with gzip.open(unsigned.name, "rb") as in_file, \
Kelvin Zhang7cab7502021-08-02 19:58:14 -0400444 open(uncompressed.name, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +0100445 shutil.copyfileobj(in_file, out_file)
446
447 # Finally, close the "unsigned" file (which is gzip compressed), and then
448 # replace it with the uncompressed version.
449 #
450 # TODO(narayan): All this nastiness can be avoided if python 3.2 is in use,
451 # we could just gzip / gunzip in-memory buffers instead.
452 unsigned.close()
453 unsigned = uncompressed
454
Oleg Aravin8046cb02020-06-02 16:02:38 -0700455 signed = tempfile.NamedTemporaryFile(suffix='_' + apk_name)
Doug Zongkereef39442009-04-02 12:14:19 -0700456
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800457 # For pre-N builds, don't upgrade to SHA-256 JAR signatures based on the APK's
458 # minSdkVersion to avoid increasing incremental OTA update sizes. If an APK
459 # didn't change, we don't want its signature to change due to the switch
460 # from SHA-1 to SHA-256.
461 # By default, APK signer chooses SHA-256 signatures if the APK's minSdkVersion
462 # is 18 or higher. For pre-N builds we disable this mechanism by pretending
463 # that the APK's minSdkVersion is 1.
464 # For N+ builds, we let APK signer rely on the APK's minSdkVersion to
465 # determine whether to use SHA-256.
466 min_api_level = None
467 if platform_api_level > 23:
468 # Let APK signer choose whether to use SHA-1 or SHA-256, based on the APK's
469 # minSdkVersion attribute
470 min_api_level = None
471 else:
472 # Force APK signer to use SHA-1
473 min_api_level = 1
474
475 common.SignFile(unsigned.name, signed.name, keyname, pw,
Tao Bao0c28d2d2017-12-24 10:37:38 -0800476 min_api_level=min_api_level,
477 codename_to_api_level_map=codename_to_api_level_map)
Doug Zongkereef39442009-04-02 12:14:19 -0700478
Tao Bao0c28d2d2017-12-24 10:37:38 -0800479 data = None
Narayan Kamatha07bf042017-08-14 14:49:21 +0100480 if is_compressed:
481 # Recompress the file after it has been signed.
482 compressed = tempfile.NamedTemporaryFile()
Tao Bao0c28d2d2017-12-24 10:37:38 -0800483 with open(signed.name, "rb") as in_file, \
Kelvin Zhang7cab7502021-08-02 19:58:14 -0400484 gzip.open(compressed.name, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +0100485 shutil.copyfileobj(in_file, out_file)
486
487 data = compressed.read()
488 compressed.close()
489 else:
490 data = signed.read()
491
Doug Zongkereef39442009-04-02 12:14:19 -0700492 unsigned.close()
493 signed.close()
494
495 return data
496
Tianjie5bd03952021-02-18 23:02:36 -0800497
Kelvin Zhang119f2792021-02-10 12:45:24 -0500498def IsBuildPropFile(filename):
499 return filename in (
Kelvin Zhang7cab7502021-08-02 19:58:14 -0400500 "SYSTEM/etc/prop.default",
501 "BOOT/RAMDISK/prop.default",
502 "RECOVERY/RAMDISK/prop.default",
Kelvin Zhang119f2792021-02-10 12:45:24 -0500503
Kelvin Zhang7cab7502021-08-02 19:58:14 -0400504 "VENDOR_BOOT/RAMDISK/default.prop",
505 "VENDOR_BOOT/RAMDISK/prop.default",
Kelvin Zhang119f2792021-02-10 12:45:24 -0500506
Kelvin Zhang7cab7502021-08-02 19:58:14 -0400507 # ROOT/default.prop is a legacy path, but may still exist for upgrading
508 # devices that don't support `property_overrides_split_enabled`.
509 "ROOT/default.prop",
Kelvin Zhang119f2792021-02-10 12:45:24 -0500510
Kelvin Zhang7cab7502021-08-02 19:58:14 -0400511 # RECOVERY/RAMDISK/default.prop is a legacy path, but will always exist
512 # as a symlink in the current code. So it's a no-op here. Keeping the
513 # path here for clarity.
514 "RECOVERY/RAMDISK/default.prop") or filename.endswith("build.prop")
Doug Zongkereef39442009-04-02 12:14:19 -0700515
Tianjie5bd03952021-02-18 23:02:36 -0800516
Doug Zongker412c02f2014-02-13 10:58:24 -0800517def ProcessTargetFiles(input_tf_zip, output_tf_zip, misc_info,
Tao Baoaa7e9932019-03-15 09:37:01 -0700518 apk_keys, apex_keys, key_passwords,
519 platform_api_level, codename_to_api_level_map,
Narayan Kamatha07bf042017-08-14 14:49:21 +0100520 compressed_extension):
Tao Bao93c2a012018-06-19 12:19:35 -0700521 # maxsize measures the maximum filename length, including the ones to be
522 # skipped.
Tao Bao0c28d2d2017-12-24 10:37:38 -0800523 maxsize = max(
524 [len(os.path.basename(i.filename)) for i in input_tf_zip.infolist()
Tao Bao93c2a012018-06-19 12:19:35 -0700525 if GetApkFileInfo(i.filename, compressed_extension, [])[0]])
Tao Baoa80ed222016-06-16 14:41:24 -0700526 system_root_image = misc_info.get("system_root_image") == "true"
Doug Zongker412c02f2014-02-13 10:58:24 -0800527
Doug Zongkereef39442009-04-02 12:14:19 -0700528 for info in input_tf_zip.infolist():
Tao Bao11f955c2018-06-19 12:19:35 -0700529 filename = info.filename
530 if filename.startswith("IMAGES/"):
Dan Albert8b72aef2015-03-23 19:13:21 -0700531 continue
Doug Zongker3c84f562014-07-31 11:06:30 -0700532
Tao Bao04808502019-07-25 23:11:41 -0700533 # Skip OTA-specific images (e.g. split super images), which will be
534 # re-generated during signing.
Tao Bao33bf2682019-01-11 12:37:35 -0800535 if filename.startswith("OTA/") and filename.endswith(".img"):
536 continue
537
Tao Bao11f955c2018-06-19 12:19:35 -0700538 data = input_tf_zip.read(filename)
Doug Zongker8e931bf2009-04-06 15:21:45 -0700539 out_info = copy.copy(info)
Tao Bao93c2a012018-06-19 12:19:35 -0700540 (is_apk, is_compressed, should_be_skipped) = GetApkFileInfo(
541 filename, compressed_extension, OPTIONS.skip_apks_with_path_prefix)
542
543 if is_apk and should_be_skipped:
544 # Copy skipped APKs verbatim.
545 print(
546 "NOT signing: %s\n"
547 " (skipped due to matching prefix)" % (filename,))
548 common.ZipWriteStr(output_tf_zip, out_info, data)
Doug Zongker412c02f2014-02-13 10:58:24 -0800549
Tao Baof2cffbd2015-07-22 12:33:18 -0700550 # Sign APKs.
Tao Bao93c2a012018-06-19 12:19:35 -0700551 elif is_apk:
Tao Bao11f955c2018-06-19 12:19:35 -0700552 name = os.path.basename(filename)
Narayan Kamatha07bf042017-08-14 14:49:21 +0100553 if is_compressed:
554 name = name[:-len(compressed_extension)]
555
Tao Baoaa7e9932019-03-15 09:37:01 -0700556 key = apk_keys[name]
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800557 if key not in common.SPECIAL_CERT_STRINGS:
Tao Bao0c28d2d2017-12-24 10:37:38 -0800558 print(" signing: %-*s (%s)" % (maxsize, name, key))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800559 signed_data = SignApk(data, key, key_passwords[key], platform_api_level,
Oleg Aravin8046cb02020-06-02 16:02:38 -0700560 codename_to_api_level_map, is_compressed, name)
Tao Bao2ed665a2015-04-01 11:21:55 -0700561 common.ZipWriteStr(output_tf_zip, out_info, signed_data)
Doug Zongkereef39442009-04-02 12:14:19 -0700562 else:
563 # an APK we're not supposed to sign.
Tao Bao93c2a012018-06-19 12:19:35 -0700564 print(
565 "NOT signing: %s\n"
566 " (skipped due to special cert string)" % (name,))
Tao Bao2ed665a2015-04-01 11:21:55 -0700567 common.ZipWriteStr(output_tf_zip, out_info, data)
Tao Baoa80ed222016-06-16 14:41:24 -0700568
Tianjie5bd03952021-02-18 23:02:36 -0800569 # Sign bundled APEX files on all partitions
Tianjie4d48d502021-06-11 17:03:43 -0700570 elif IsApexFile(filename):
571 name = GetApexFilename(filename)
572
Jooyung Han8caba5e2021-10-27 03:58:09 +0900573 payload_key, container_key, sign_tool = apex_keys[name]
Tao Baoaa7e9932019-03-15 09:37:01 -0700574
Tao Baoe1343992019-03-19 12:24:03 -0700575 # We've asserted not having a case with only one of them PRESIGNED.
576 if (payload_key not in common.SPECIAL_CERT_STRINGS and
Kelvin Zhang7cab7502021-08-02 19:58:14 -0400577 container_key not in common.SPECIAL_CERT_STRINGS):
Tao Baoe1343992019-03-19 12:24:03 -0700578 print(" signing: %-*s container (%s)" % (
579 maxsize, name, container_key))
580 print(" : %-*s payload (%s)" % (
581 maxsize, name, payload_key))
Tao Baoaa7e9932019-03-15 09:37:01 -0700582
Tao Baoe7354ba2019-05-09 16:54:15 -0700583 signed_apex = apex_utils.SignApex(
Tao Bao1ac886e2019-06-26 11:58:22 -0700584 misc_info['avb_avbtool'],
Tao Baoe1343992019-03-19 12:24:03 -0700585 data,
586 payload_key,
587 container_key,
Oleh Cherpake555ab12020-10-05 17:04:59 +0300588 key_passwords,
Tianjie Xu88a759d2020-01-23 10:47:54 -0800589 apk_keys,
Tao Baoe1343992019-03-19 12:24:03 -0700590 codename_to_api_level_map,
Kelvin Zhang7cab7502021-08-02 19:58:14 -0400591 no_hashtree=None, # Let apex_util determine if hash tree is needed
Jooyung Han8caba5e2021-10-27 03:58:09 +0900592 signing_args=OPTIONS.avb_extra_args.get('apex'),
593 sign_tool=sign_tool)
Tao Baoe1343992019-03-19 12:24:03 -0700594 common.ZipWrite(output_tf_zip, signed_apex, filename)
Tao Baoaa7e9932019-03-15 09:37:01 -0700595
Tao Baoe1343992019-03-19 12:24:03 -0700596 else:
597 print(
598 "NOT signing: %s\n"
599 " (skipped due to special cert string)" % (name,))
600 common.ZipWriteStr(output_tf_zip, out_info, data)
Tao Baoaa7e9932019-03-15 09:37:01 -0700601
Tao Baoa80ed222016-06-16 14:41:24 -0700602 # System properties.
Kelvin Zhang119f2792021-02-10 12:45:24 -0500603 elif IsBuildPropFile(filename):
Tao Bao11f955c2018-06-19 12:19:35 -0700604 print("Rewriting %s:" % (filename,))
Hung-ying Tyan7eb6a922017-05-01 21:56:26 +0800605 if stat.S_ISLNK(info.external_attr >> 16):
606 new_data = data
607 else:
Tao Baoa3705452019-06-24 15:33:41 -0700608 new_data = RewriteProps(data.decode())
Tao Bao2ed665a2015-04-01 11:21:55 -0700609 common.ZipWriteStr(output_tf_zip, out_info, new_data)
Tao Baoa80ed222016-06-16 14:41:24 -0700610
Tao Bao66472632017-12-04 17:16:36 -0800611 # Replace the certs in *mac_permissions.xml (there could be multiple, such
Inseob Kime7b222a2021-12-21 15:57:03 +0900612 # as {system,vendor}/etc/selinux/{plat,vendor}_mac_permissions.xml).
Tao Bao11f955c2018-06-19 12:19:35 -0700613 elif filename.endswith("mac_permissions.xml"):
614 print("Rewriting %s with new keys." % (filename,))
Tao Baoa3705452019-06-24 15:33:41 -0700615 new_data = ReplaceCerts(data.decode())
Tao Bao2ed665a2015-04-01 11:21:55 -0700616 common.ZipWriteStr(output_tf_zip, out_info, new_data)
Tao Baoa80ed222016-06-16 14:41:24 -0700617
Tianjie Xu616fbeb2017-05-23 14:51:02 -0700618 # Ask add_img_to_target_files to rebuild the recovery patch if needed.
Tao Bao11f955c2018-06-19 12:19:35 -0700619 elif filename in ("SYSTEM/recovery-from-boot.p",
Robin Leeda427de2020-01-03 19:16:32 +0100620 "VENDOR/recovery-from-boot.p",
621
Tao Bao11f955c2018-06-19 12:19:35 -0700622 "SYSTEM/etc/recovery.img",
Robin Leeda427de2020-01-03 19:16:32 +0100623 "VENDOR/etc/recovery.img",
624
625 "SYSTEM/bin/install-recovery.sh",
626 "VENDOR/bin/install-recovery.sh"):
Tianjie Xu616fbeb2017-05-23 14:51:02 -0700627 OPTIONS.rebuild_recovery = True
Tao Baoa80ed222016-06-16 14:41:24 -0700628
Tianjie Xuffbe6b92018-10-19 14:34:15 -0700629 # Don't copy OTA certs if we're replacing them.
Tianjie Xu2df23d72019-10-15 18:06:25 -0700630 # Replacement of update-payload-key.pub.pem was removed in b/116660991.
Kelvin Zhang9f781ff2021-02-11 19:10:44 -0500631 elif OPTIONS.replace_ota_keys and filename.endswith("/otacerts.zip"):
Doug Zongker412c02f2014-02-13 10:58:24 -0800632 pass
Tao Baoa80ed222016-06-16 14:41:24 -0700633
Tao Bao46a59992017-06-05 11:55:16 -0700634 # Skip META/misc_info.txt since we will write back the new values later.
Tao Bao11f955c2018-06-19 12:19:35 -0700635 elif filename == "META/misc_info.txt":
Geremy Condraf19b3652014-07-29 17:54:54 -0700636 pass
Tao Bao8adcfd12016-06-17 17:01:22 -0700637
638 # Skip verity public key if we will replace it.
Michael Runge947894f2014-10-14 20:58:38 -0700639 elif (OPTIONS.replace_verity_public_key and
Tao Bao11f955c2018-06-19 12:19:35 -0700640 filename in ("BOOT/RAMDISK/verity_key",
641 "ROOT/verity_key")):
Geremy Condraf19b3652014-07-29 17:54:54 -0700642 pass
Bowgo Tsai2fe786a2020-02-21 17:48:18 +0800643 elif (OPTIONS.remove_avb_public_keys and
644 (filename.startswith("BOOT/RAMDISK/avb/") or
645 filename.startswith("BOOT/RAMDISK/first_stage_ramdisk/avb/"))):
Kelvin Zhang0876c412020-06-23 15:06:58 -0400646 matched_removal = False
647 for key_to_remove in OPTIONS.remove_avb_public_keys:
648 if filename.endswith(key_to_remove):
649 matched_removal = True
650 print("Removing AVB public key from ramdisk: %s" % filename)
651 break
652 if not matched_removal:
653 # Copy it verbatim if we don't want to remove it.
654 common.ZipWriteStr(output_tf_zip, out_info, data)
Tao Baoa80ed222016-06-16 14:41:24 -0700655
Tao Bao8adcfd12016-06-17 17:01:22 -0700656 # Skip verity keyid (for system_root_image use) if we will replace it.
Tao Bao11f955c2018-06-19 12:19:35 -0700657 elif OPTIONS.replace_verity_keyid and filename == "BOOT/cmdline":
Badhri Jagan Sridharan35c9b122016-06-16 19:58:44 -0700658 pass
659
Tianjiebbde59f2021-05-03 21:18:56 -0700660 # Skip the vbmeta digest as we will recalculate it.
661 elif filename == "META/vbmeta_digest.txt":
662 pass
663
Tianjie Xu4f099002016-08-11 18:04:27 -0700664 # Skip the care_map as we will regenerate the system/vendor images.
Kelvin Zhang0876c412020-06-23 15:06:58 -0400665 elif filename in ["META/care_map.pb", "META/care_map.txt"]:
Tianjie Xu4f099002016-08-11 18:04:27 -0700666 pass
667
Kelvin Zhang5f0fcee2021-01-19 15:30:46 -0500668 # Skip apex_info.pb because we sign/modify apexes
669 elif filename == "META/apex_info.pb":
670 pass
671
Bowgo Tsaie4544b12019-02-27 10:15:51 +0800672 # Updates system_other.avbpubkey in /product/etc/.
673 elif filename in (
674 "PRODUCT/etc/security/avb/system_other.avbpubkey",
Bowgo Tsai2a781692021-10-13 17:39:33 +0800675 "SYSTEM/product/etc/security/avb/system_other.avbpubkey"):
Bowgo Tsaie4544b12019-02-27 10:15:51 +0800676 # Only update system_other's public key, if the corresponding signing
677 # key is specified via --avb_system_other_key.
678 signing_key = OPTIONS.avb_keys.get("system_other")
679 if signing_key:
Tao Bao1ac886e2019-06-26 11:58:22 -0700680 public_key = common.ExtractAvbPublicKey(
681 misc_info['avb_avbtool'], signing_key)
Bowgo Tsaie4544b12019-02-27 10:15:51 +0800682 print(" Rewriting AVB public key of system_other in /product")
683 common.ZipWrite(output_tf_zip, public_key, filename)
684
Bowgo Tsai78369eb2019-04-23 12:28:44 +0800685 # Should NOT sign boot-debug.img.
686 elif filename in (
687 "BOOT/RAMDISK/force_debuggable",
Bowgo Tsai2a781692021-10-13 17:39:33 +0800688 "BOOT/RAMDISK/first_stage_ramdisk/force_debuggable"):
Bowgo Tsai78369eb2019-04-23 12:28:44 +0800689 raise common.ExternalError("debuggable boot.img cannot be signed")
690
Bowgo Tsai2a781692021-10-13 17:39:33 +0800691 # Should NOT sign userdebug sepolicy file.
692 elif filename in (
693 "SYSTEM_EXT/etc/selinux/userdebug_plat_sepolicy.cil",
694 "SYSTEM/system_ext/etc/selinux/userdebug_plat_sepolicy.cil"):
695 if not OPTIONS.allow_gsi_debug_sepolicy:
696 raise common.ExternalError("debug sepolicy shouldn't be included")
697 else:
698 # Copy it verbatim if we allow the file to exist.
699 common.ZipWriteStr(output_tf_zip, out_info, data)
700
Tao Baoa80ed222016-06-16 14:41:24 -0700701 # A non-APK file; copy it verbatim.
Doug Zongkereef39442009-04-02 12:14:19 -0700702 else:
Tao Bao2ed665a2015-04-01 11:21:55 -0700703 common.ZipWriteStr(output_tf_zip, out_info, data)
Doug Zongker8e931bf2009-04-06 15:21:45 -0700704
Doug Zongker412c02f2014-02-13 10:58:24 -0800705 if OPTIONS.replace_ota_keys:
Tianjie Xu616fbeb2017-05-23 14:51:02 -0700706 ReplaceOtaKeys(input_tf_zip, output_tf_zip, misc_info)
Doug Zongker412c02f2014-02-13 10:58:24 -0800707
Tao Bao46a59992017-06-05 11:55:16 -0700708 # Replace the keyid string in misc_info dict.
Tao Bao8adcfd12016-06-17 17:01:22 -0700709 if OPTIONS.replace_verity_private_key:
Tao Bao46a59992017-06-05 11:55:16 -0700710 ReplaceVerityPrivateKey(misc_info, OPTIONS.replace_verity_private_key[1])
Tao Bao8adcfd12016-06-17 17:01:22 -0700711
712 if OPTIONS.replace_verity_public_key:
Tao Baoc9981932019-09-16 12:10:43 -0700713 # Replace the one in root dir in system.img.
Tianjie Xu616fbeb2017-05-23 14:51:02 -0700714 ReplaceVerityPublicKey(
Tao Baoc9981932019-09-16 12:10:43 -0700715 output_tf_zip, 'ROOT/verity_key', OPTIONS.replace_verity_public_key[1])
716
717 if not system_root_image:
718 # Additionally replace the copy in ramdisk if not using system-as-root.
719 ReplaceVerityPublicKey(
720 output_tf_zip,
721 'BOOT/RAMDISK/verity_key',
722 OPTIONS.replace_verity_public_key[1])
Tao Bao8adcfd12016-06-17 17:01:22 -0700723
724 # Replace the keyid string in BOOT/cmdline.
725 if OPTIONS.replace_verity_keyid:
Tianjie Xu616fbeb2017-05-23 14:51:02 -0700726 ReplaceVerityKeyId(input_tf_zip, output_tf_zip,
727 OPTIONS.replace_verity_keyid[1])
Doug Zongker412c02f2014-02-13 10:58:24 -0800728
Tao Bao639118f2017-06-19 15:48:02 -0700729 # Replace the AVB signing keys, if any.
730 ReplaceAvbSigningKeys(misc_info)
731
Tao Bao19b02fe2019-10-09 00:04:28 -0700732 # Rewrite the props in AVB signing args.
733 if misc_info.get('avb_enable') == 'true':
734 RewriteAvbProps(misc_info)
735
Bowgo Tsai27c39b02021-03-12 21:40:32 +0800736 # Replace the GKI signing key for boot.img, if any.
737 ReplaceGkiSigningKey(misc_info)
738
Tao Bao46a59992017-06-05 11:55:16 -0700739 # Write back misc_info with the latest values.
740 ReplaceMiscInfoTxt(input_tf_zip, output_tf_zip, misc_info)
741
Doug Zongker8e931bf2009-04-06 15:21:45 -0700742
Robert Craig817c5742013-04-19 10:59:22 -0400743def ReplaceCerts(data):
Tao Bao66472632017-12-04 17:16:36 -0800744 """Replaces all the occurences of X.509 certs with the new ones.
Robert Craig817c5742013-04-19 10:59:22 -0400745
Tao Bao66472632017-12-04 17:16:36 -0800746 The mapping info is read from OPTIONS.key_map. Non-existent certificate will
747 be skipped. After the replacement, it additionally checks for duplicate
748 entries, which would otherwise fail the policy loading code in
749 frameworks/base/services/core/java/com/android/server/pm/SELinuxMMAC.java.
750
751 Args:
752 data: Input string that contains a set of X.509 certs.
753
754 Returns:
755 A string after the replacement.
756
757 Raises:
758 AssertionError: On finding duplicate entries.
759 """
Tao Baoa3705452019-06-24 15:33:41 -0700760 for old, new in OPTIONS.key_map.items():
Tao Bao66472632017-12-04 17:16:36 -0800761 if OPTIONS.verbose:
762 print(" Replacing %s.x509.pem with %s.x509.pem" % (old, new))
763
764 try:
765 with open(old + ".x509.pem") as old_fp:
766 old_cert16 = base64.b16encode(
Tao Baoa3705452019-06-24 15:33:41 -0700767 common.ParseCertificate(old_fp.read())).decode().lower()
Tao Bao66472632017-12-04 17:16:36 -0800768 with open(new + ".x509.pem") as new_fp:
769 new_cert16 = base64.b16encode(
Tao Baoa3705452019-06-24 15:33:41 -0700770 common.ParseCertificate(new_fp.read())).decode().lower()
Tao Bao66472632017-12-04 17:16:36 -0800771 except IOError as e:
772 if OPTIONS.verbose or e.errno != errno.ENOENT:
773 print(" Error accessing %s: %s.\nSkip replacing %s.x509.pem with "
774 "%s.x509.pem." % (e.filename, e.strerror, old, new))
775 continue
776
777 # Only match entire certs.
778 pattern = "\\b" + old_cert16 + "\\b"
779 (data, num) = re.subn(pattern, new_cert16, data, flags=re.IGNORECASE)
780
781 if OPTIONS.verbose:
782 print(" Replaced %d occurence(s) of %s.x509.pem with %s.x509.pem" % (
783 num, old, new))
784
785 # Verify that there're no duplicate entries after the replacement. Note that
786 # it's only checking entries with global seinfo at the moment (i.e. ignoring
787 # the ones with inner packages). (Bug: 69479366)
788 root = ElementTree.fromstring(data)
Kelvin Zhang7cab7502021-08-02 19:58:14 -0400789 signatures = [signer.attrib['signature']
790 for signer in root.findall('signer')]
Tao Bao66472632017-12-04 17:16:36 -0800791 assert len(signatures) == len(set(signatures)), \
792 "Found duplicate entries after cert replacement: {}".format(data)
Robert Craig817c5742013-04-19 10:59:22 -0400793
794 return data
795
796
Doug Zongkerc09abc82010-01-11 13:09:15 -0800797def EditTags(tags):
Tao Baoa7054ee2017-12-08 14:42:16 -0800798 """Applies the edits to the tag string as specified in OPTIONS.tag_changes.
799
800 Args:
801 tags: The input string that contains comma-separated tags.
802
803 Returns:
804 The updated tags (comma-separated and sorted).
805 """
Doug Zongkerc09abc82010-01-11 13:09:15 -0800806 tags = set(tags.split(","))
807 for ch in OPTIONS.tag_changes:
808 if ch[0] == "-":
809 tags.discard(ch[1:])
810 elif ch[0] == "+":
811 tags.add(ch[1:])
812 return ",".join(sorted(tags))
813
814
Tao Baoa7054ee2017-12-08 14:42:16 -0800815def RewriteProps(data):
816 """Rewrites the system properties in the given string.
817
818 Each property is expected in 'key=value' format. The properties that contain
819 build tags (i.e. test-keys, dev-keys) will be updated accordingly by calling
820 EditTags().
821
822 Args:
823 data: Input string, separated by newlines.
824
825 Returns:
826 The string with modified properties.
827 """
Doug Zongker17aa9442009-04-17 10:15:58 -0700828 output = []
829 for line in data.split("\n"):
830 line = line.strip()
831 original_line = line
Michael Rungedc2661a2014-06-03 14:43:11 -0700832 if line and line[0] != '#' and "=" in line:
Doug Zongker17aa9442009-04-17 10:15:58 -0700833 key, value = line.split("=", 1)
Magnus Strandh234f4b42019-05-01 23:09:30 +0200834 if (key.startswith("ro.") and
Kelvin Zhang7cab7502021-08-02 19:58:14 -0400835 key.endswith((".build.fingerprint", ".build.thumbprint"))):
Doug Zongkerc09abc82010-01-11 13:09:15 -0800836 pieces = value.split("/")
837 pieces[-1] = EditTags(pieces[-1])
838 value = "/".join(pieces)
Tao Baocb7ff772015-09-11 15:27:56 -0700839 elif key == "ro.bootimage.build.fingerprint":
840 pieces = value.split("/")
841 pieces[-1] = EditTags(pieces[-1])
842 value = "/".join(pieces)
Doug Zongker17aa9442009-04-17 10:15:58 -0700843 elif key == "ro.build.description":
Doug Zongkerc09abc82010-01-11 13:09:15 -0800844 pieces = value.split(" ")
Stefen Wakefield4260fc12021-03-23 04:58:22 -0500845 assert pieces[-1].endswith("-keys")
Doug Zongkerc09abc82010-01-11 13:09:15 -0800846 pieces[-1] = EditTags(pieces[-1])
847 value = " ".join(pieces)
Magnus Strandh234f4b42019-05-01 23:09:30 +0200848 elif key.startswith("ro.") and key.endswith(".build.tags"):
Doug Zongkerc09abc82010-01-11 13:09:15 -0800849 value = EditTags(value)
Doug Zongkera8608a72013-07-23 11:51:04 -0700850 elif key == "ro.build.display.id":
851 # change, eg, "JWR66N dev-keys" to "JWR66N"
852 value = value.split()
Michael Rungedc2661a2014-06-03 14:43:11 -0700853 if len(value) > 1 and value[-1].endswith("-keys"):
Andrew Boie73d5abb2013-12-11 12:42:03 -0800854 value.pop()
855 value = " ".join(value)
Doug Zongkerc09abc82010-01-11 13:09:15 -0800856 line = key + "=" + value
Doug Zongker17aa9442009-04-17 10:15:58 -0700857 if line != original_line:
Tao Bao0c28d2d2017-12-24 10:37:38 -0800858 print(" replace: ", original_line)
859 print(" with: ", line)
Doug Zongker17aa9442009-04-17 10:15:58 -0700860 output.append(line)
861 return "\n".join(output) + "\n"
862
863
Tianjie Xuffbe6b92018-10-19 14:34:15 -0700864def WriteOtacerts(output_zip, filename, keys):
865 """Constructs a zipfile from given keys; and writes it to output_zip.
866
867 Args:
868 output_zip: The output target_files zip.
869 filename: The archive name in the output zip.
870 keys: A list of public keys to use during OTA package verification.
871 """
Tao Baobb733882019-07-24 23:31:19 -0700872 temp_file = io.BytesIO()
Kelvin Zhang928c2342020-09-22 16:15:57 -0400873 certs_zip = zipfile.ZipFile(temp_file, "w", allowZip64=True)
Tianjie Xuffbe6b92018-10-19 14:34:15 -0700874 for k in keys:
875 common.ZipWrite(certs_zip, k)
876 common.ZipClose(certs_zip)
877 common.ZipWriteStr(output_zip, filename, temp_file.getvalue())
878
879
Doug Zongker831840e2011-09-22 10:28:04 -0700880def ReplaceOtaKeys(input_tf_zip, output_tf_zip, misc_info):
Doug Zongker8e931bf2009-04-06 15:21:45 -0700881 try:
882 keylist = input_tf_zip.read("META/otakeys.txt").split()
883 except KeyError:
T.R. Fullharta28acc62013-03-18 10:31:26 -0700884 raise common.ExternalError("can't read META/otakeys.txt from input")
Doug Zongker8e931bf2009-04-06 15:21:45 -0700885
Jacky Liubeb0b692021-12-29 16:29:05 +0800886 extra_ota_keys_info = misc_info.get("extra_ota_keys")
887 if extra_ota_keys_info:
888 extra_ota_keys = [OPTIONS.key_map.get(k, k) + ".x509.pem"
889 for k in extra_ota_keys_info.split()]
890 print("extra ota key(s): " + ", ".join(extra_ota_keys))
891 else:
892 extra_ota_keys = []
893 for k in extra_ota_keys:
894 if not os.path.isfile(k):
895 raise common.ExternalError(k + " does not exist or is not a file")
896
897 extra_recovery_keys_info = misc_info.get("extra_recovery_keys")
898 if extra_recovery_keys_info:
Doug Zongkere121d6a2011-02-01 14:13:52 -0800899 extra_recovery_keys = [OPTIONS.key_map.get(k, k) + ".x509.pem"
Jacky Liubeb0b692021-12-29 16:29:05 +0800900 for k in extra_recovery_keys_info.split()]
901 print("extra recovery-only key(s): " + ", ".join(extra_recovery_keys))
Doug Zongkere121d6a2011-02-01 14:13:52 -0800902 else:
903 extra_recovery_keys = []
Jacky Liubeb0b692021-12-29 16:29:05 +0800904 for k in extra_recovery_keys:
905 if not os.path.isfile(k):
906 raise common.ExternalError(k + " does not exist or is not a file")
Doug Zongkere121d6a2011-02-01 14:13:52 -0800907
Doug Zongker8e931bf2009-04-06 15:21:45 -0700908 mapped_keys = []
909 for k in keylist:
910 m = re.match(r"^(.*)\.x509\.pem$", k)
911 if not m:
Doug Zongker412c02f2014-02-13 10:58:24 -0800912 raise common.ExternalError(
913 "can't parse \"%s\" from META/otakeys.txt" % (k,))
Doug Zongker8e931bf2009-04-06 15:21:45 -0700914 k = m.group(1)
915 mapped_keys.append(OPTIONS.key_map.get(k, k) + ".x509.pem")
916
Doug Zongkere05628c2009-08-20 17:38:42 -0700917 if mapped_keys:
Tao Bao0c28d2d2017-12-24 10:37:38 -0800918 print("using:\n ", "\n ".join(mapped_keys))
919 print("for OTA package verification")
Doug Zongkere05628c2009-08-20 17:38:42 -0700920 else:
Doug Zongker831840e2011-09-22 10:28:04 -0700921 devkey = misc_info.get("default_system_dev_certificate",
Dan Willemsen0ab1be62019-04-09 21:35:37 -0700922 "build/make/target/product/security/testkey")
Tao Baof718f902017-11-09 10:10:10 -0800923 mapped_devkey = OPTIONS.key_map.get(devkey, devkey)
924 if mapped_devkey != devkey:
925 misc_info["default_system_dev_certificate"] = mapped_devkey
926 mapped_keys.append(mapped_devkey + ".x509.pem")
Tao Baoa80ed222016-06-16 14:41:24 -0700927 print("META/otakeys.txt has no keys; using %s for OTA package"
928 " verification." % (mapped_keys[0],))
Jacky Liubeb0b692021-12-29 16:29:05 +0800929 for k in mapped_keys:
930 if not os.path.isfile(k):
931 raise common.ExternalError(k + " does not exist or is not a file")
Doug Zongker8e931bf2009-04-06 15:21:45 -0700932
Kelvin Zhang9f781ff2021-02-11 19:10:44 -0500933 otacerts = [info
934 for info in input_tf_zip.infolist()
935 if info.filename.endswith("/otacerts.zip")]
936 for info in otacerts:
Jacky Liubeb0b692021-12-29 16:29:05 +0800937 if info.filename.startswith(("BOOT/", "RECOVERY/", "VENDOR_BOOT/")):
938 extra_keys = extra_recovery_keys
939 else:
940 extra_keys = extra_ota_keys
941 print("Rewriting OTA key:", info.filename, mapped_keys + extra_keys)
942 WriteOtacerts(output_tf_zip, info.filename, mapped_keys + extra_keys)
Doug Zongkereef39442009-04-02 12:14:19 -0700943
Tao Baoa80ed222016-06-16 14:41:24 -0700944
Tao Bao0c28d2d2017-12-24 10:37:38 -0800945def ReplaceVerityPublicKey(output_zip, filename, key_path):
946 """Replaces the verity public key at the given path in the given zip.
947
948 Args:
949 output_zip: The output target_files zip.
950 filename: The archive name in the output zip.
951 key_path: The path to the public key.
952 """
953 print("Replacing verity public key with %s" % (key_path,))
954 common.ZipWrite(output_zip, key_path, arcname=filename)
Geremy Condraf19b3652014-07-29 17:54:54 -0700955
Tao Bao8adcfd12016-06-17 17:01:22 -0700956
Tao Bao46a59992017-06-05 11:55:16 -0700957def ReplaceVerityPrivateKey(misc_info, key_path):
Tao Bao0c28d2d2017-12-24 10:37:38 -0800958 """Replaces the verity private key in misc_info dict.
959
960 Args:
961 misc_info: The info dict.
962 key_path: The path to the private key in PKCS#8 format.
963 """
964 print("Replacing verity private key with %s" % (key_path,))
Andrew Boied083f0b2014-09-15 16:01:07 -0700965 misc_info["verity_key"] = key_path
Doug Zongkereef39442009-04-02 12:14:19 -0700966
Tao Bao8adcfd12016-06-17 17:01:22 -0700967
Tao Baoe838d142017-12-23 23:44:48 -0800968def ReplaceVerityKeyId(input_zip, output_zip, key_path):
969 """Replaces the veritykeyid parameter in BOOT/cmdline.
970
971 Args:
972 input_zip: The input target_files zip, which should be already open.
973 output_zip: The output target_files zip, which should be already open and
974 writable.
975 key_path: The path to the PEM encoded X.509 certificate.
976 """
Tao Baoa3705452019-06-24 15:33:41 -0700977 in_cmdline = input_zip.read("BOOT/cmdline").decode()
Tao Baoe838d142017-12-23 23:44:48 -0800978 # Copy in_cmdline to output_zip if veritykeyid is not present.
Badhri Jagan Sridharan35c9b122016-06-16 19:58:44 -0700979 if "veritykeyid" not in in_cmdline:
Tao Baoe838d142017-12-23 23:44:48 -0800980 common.ZipWriteStr(output_zip, "BOOT/cmdline", in_cmdline)
981 return
982
Tao Bao0c28d2d2017-12-24 10:37:38 -0800983 out_buffer = []
Badhri Jagan Sridharan35c9b122016-06-16 19:58:44 -0700984 for param in in_cmdline.split():
Tao Baoe838d142017-12-23 23:44:48 -0800985 if "veritykeyid" not in param:
Tao Bao0c28d2d2017-12-24 10:37:38 -0800986 out_buffer.append(param)
Tao Baoe838d142017-12-23 23:44:48 -0800987 continue
Badhri Jagan Sridharan35c9b122016-06-16 19:58:44 -0700988
Tao Baoe838d142017-12-23 23:44:48 -0800989 # Extract keyid using openssl command.
990 p = common.Run(["openssl", "x509", "-in", key_path, "-text"],
Tao Baode1d4792018-02-20 10:05:46 -0800991 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Tao Baoe838d142017-12-23 23:44:48 -0800992 keyid, stderr = p.communicate()
993 assert p.returncode == 0, "Failed to dump certificate: {}".format(stderr)
994 keyid = re.search(
995 r'keyid:([0-9a-fA-F:]*)', keyid).group(1).replace(':', '').lower()
996 print("Replacing verity keyid with {}".format(keyid))
997 out_buffer.append("veritykeyid=id:%s" % (keyid,))
998
999 out_cmdline = ' '.join(out_buffer).strip() + '\n'
1000 common.ZipWriteStr(output_zip, "BOOT/cmdline", out_cmdline)
Tao Bao46a59992017-06-05 11:55:16 -07001001
1002
1003def ReplaceMiscInfoTxt(input_zip, output_zip, misc_info):
1004 """Replaces META/misc_info.txt.
1005
1006 Only writes back the ones in the original META/misc_info.txt. Because the
1007 current in-memory dict contains additional items computed at runtime.
1008 """
1009 misc_info_old = common.LoadDictionaryFromLines(
Tao Baoa3705452019-06-24 15:33:41 -07001010 input_zip.read('META/misc_info.txt').decode().split('\n'))
Tao Bao46a59992017-06-05 11:55:16 -07001011 items = []
1012 for key in sorted(misc_info):
1013 if key in misc_info_old:
1014 items.append('%s=%s' % (key, misc_info[key]))
1015 common.ZipWriteStr(output_zip, "META/misc_info.txt", '\n'.join(items))
Badhri Jagan Sridharan35c9b122016-06-16 19:58:44 -07001016
Tao Bao8adcfd12016-06-17 17:01:22 -07001017
Tao Bao639118f2017-06-19 15:48:02 -07001018def ReplaceAvbSigningKeys(misc_info):
1019 """Replaces the AVB signing keys."""
1020
Tao Bao639118f2017-06-19 15:48:02 -07001021 def ReplaceAvbPartitionSigningKey(partition):
1022 key = OPTIONS.avb_keys.get(partition)
1023 if not key:
1024 return
1025
1026 algorithm = OPTIONS.avb_algorithms.get(partition)
1027 assert algorithm, 'Missing AVB signing algorithm for %s' % (partition,)
1028
Tao Bao0c28d2d2017-12-24 10:37:38 -08001029 print('Replacing AVB signing key for %s with "%s" (%s)' % (
1030 partition, key, algorithm))
Tao Bao639118f2017-06-19 15:48:02 -07001031 misc_info['avb_' + partition + '_algorithm'] = algorithm
1032 misc_info['avb_' + partition + '_key_path'] = key
1033
1034 extra_args = OPTIONS.avb_extra_args.get(partition)
1035 if extra_args:
Tao Bao0c28d2d2017-12-24 10:37:38 -08001036 print('Setting extra AVB signing args for %s to "%s"' % (
1037 partition, extra_args))
Kelvin Zhang0876c412020-06-23 15:06:58 -04001038 args_key = AVB_FOOTER_ARGS_BY_PARTITION.get(
1039 partition,
1040 # custom partition
1041 "avb_{}_add_hashtree_footer_args".format(partition))
Tao Bao639118f2017-06-19 15:48:02 -07001042 misc_info[args_key] = (misc_info.get(args_key, '') + ' ' + extra_args)
1043
1044 for partition in AVB_FOOTER_ARGS_BY_PARTITION:
1045 ReplaceAvbPartitionSigningKey(partition)
1046
Hongguang Chenf23364d2020-04-27 18:36:36 -07001047 for custom_partition in misc_info.get(
Kelvin Zhang7cab7502021-08-02 19:58:14 -04001048 "avb_custom_images_partition_list", "").strip().split():
Hongguang Chenf23364d2020-04-27 18:36:36 -07001049 ReplaceAvbPartitionSigningKey(custom_partition)
1050
Tao Bao639118f2017-06-19 15:48:02 -07001051
Tao Bao19b02fe2019-10-09 00:04:28 -07001052def RewriteAvbProps(misc_info):
1053 """Rewrites the props in AVB signing args."""
1054 for partition, args_key in AVB_FOOTER_ARGS_BY_PARTITION.items():
1055 args = misc_info.get(args_key)
1056 if not args:
1057 continue
1058
1059 tokens = []
1060 changed = False
1061 for token in args.split(' '):
1062 fingerprint_key = 'com.android.build.{}.fingerprint'.format(partition)
1063 if not token.startswith(fingerprint_key):
1064 tokens.append(token)
1065 continue
1066 prefix, tag = token.rsplit('/', 1)
1067 tokens.append('{}/{}'.format(prefix, EditTags(tag)))
1068 changed = True
1069
1070 if changed:
1071 result = ' '.join(tokens)
1072 print('Rewriting AVB prop for {}:\n'.format(partition))
1073 print(' replace: {}'.format(args))
1074 print(' with: {}'.format(result))
1075 misc_info[args_key] = result
1076
1077
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001078def ReplaceGkiSigningKey(misc_info):
1079 """Replaces the GKI signing key."""
1080
1081 key = OPTIONS.gki_signing_key
1082 if not key:
1083 return
1084
1085 algorithm = OPTIONS.gki_signing_algorithm
1086 if not algorithm:
1087 raise ValueError("Missing --gki_signing_algorithm")
1088
1089 print('Replacing GKI signing key with "%s" (%s)' % (key, algorithm))
1090 misc_info["gki_signing_algorithm"] = algorithm
1091 misc_info["gki_signing_key_path"] = key
1092
1093 extra_args = OPTIONS.gki_signing_extra_args
1094 if extra_args:
Bowgo Tsaibcae74d2021-05-10 17:35:37 +08001095 print('Setting GKI signing args: "%s"' % (extra_args))
1096 misc_info["gki_signing_signature_args"] = extra_args
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001097
1098
Doug Zongker831840e2011-09-22 10:28:04 -07001099def BuildKeyMap(misc_info, key_mapping_options):
1100 for s, d in key_mapping_options:
1101 if s is None: # -d option
1102 devkey = misc_info.get("default_system_dev_certificate",
Dan Willemsen0ab1be62019-04-09 21:35:37 -07001103 "build/make/target/product/security/testkey")
Doug Zongker831840e2011-09-22 10:28:04 -07001104 devkeydir = os.path.dirname(devkey)
1105
1106 OPTIONS.key_map.update({
1107 devkeydir + "/testkey": d + "/releasekey",
1108 devkeydir + "/devkey": d + "/releasekey",
1109 devkeydir + "/media": d + "/media",
1110 devkeydir + "/shared": d + "/shared",
1111 devkeydir + "/platform": d + "/platform",
Oleh Cherpak982e6082019-12-10 12:58:54 +02001112 devkeydir + "/networkstack": d + "/networkstack",
Kelvin Zhang7cab7502021-08-02 19:58:14 -04001113 })
Doug Zongker831840e2011-09-22 10:28:04 -07001114 else:
1115 OPTIONS.key_map[s] = d
1116
1117
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001118def GetApiLevelAndCodename(input_tf_zip):
Tao Baoa3705452019-06-24 15:33:41 -07001119 data = input_tf_zip.read("SYSTEM/build.prop").decode()
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001120 api_level = None
1121 codename = None
1122 for line in data.split("\n"):
1123 line = line.strip()
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001124 if line and line[0] != '#' and "=" in line:
1125 key, value = line.split("=", 1)
1126 key = key.strip()
1127 if key == "ro.build.version.sdk":
1128 api_level = int(value.strip())
1129 elif key == "ro.build.version.codename":
1130 codename = value.strip()
1131
1132 if api_level is None:
1133 raise ValueError("No ro.build.version.sdk in SYSTEM/build.prop")
1134 if codename is None:
1135 raise ValueError("No ro.build.version.codename in SYSTEM/build.prop")
1136
1137 return (api_level, codename)
1138
1139
1140def GetCodenameToApiLevelMap(input_tf_zip):
Tao Baoa3705452019-06-24 15:33:41 -07001141 data = input_tf_zip.read("SYSTEM/build.prop").decode()
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001142 api_level = None
1143 codenames = None
1144 for line in data.split("\n"):
1145 line = line.strip()
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001146 if line and line[0] != '#' and "=" in line:
1147 key, value = line.split("=", 1)
1148 key = key.strip()
1149 if key == "ro.build.version.sdk":
1150 api_level = int(value.strip())
1151 elif key == "ro.build.version.all_codenames":
1152 codenames = value.strip().split(",")
1153
1154 if api_level is None:
1155 raise ValueError("No ro.build.version.sdk in SYSTEM/build.prop")
1156 if codenames is None:
1157 raise ValueError("No ro.build.version.all_codenames in SYSTEM/build.prop")
1158
Tao Baoa3705452019-06-24 15:33:41 -07001159 result = {}
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001160 for codename in codenames:
1161 codename = codename.strip()
Tao Baobadceb22019-03-15 09:33:43 -07001162 if codename:
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001163 result[codename] = api_level
1164 return result
1165
1166
Tao Baoaa7e9932019-03-15 09:37:01 -07001167def ReadApexKeysInfo(tf_zip):
1168 """Parses the APEX keys info from a given target-files zip.
1169
1170 Given a target-files ZipFile, parses the META/apexkeys.txt entry and returns a
1171 dict that contains the mapping from APEX names (e.g. com.android.tzdata) to a
Jooyung Han8caba5e2021-10-27 03:58:09 +09001172 tuple of (payload_key, container_key, sign_tool).
Tao Baoaa7e9932019-03-15 09:37:01 -07001173
1174 Args:
1175 tf_zip: The input target_files ZipFile (already open).
1176
1177 Returns:
Jooyung Han8caba5e2021-10-27 03:58:09 +09001178 (payload_key, container_key, sign_tool):
1179 - payload_key contains the path to the payload signing key
1180 - container_key contains the path to the container signing key
1181 - sign_tool is an apex-specific signing tool for its payload contents
Tao Baoaa7e9932019-03-15 09:37:01 -07001182 """
1183 keys = {}
Tao Baoa3705452019-06-24 15:33:41 -07001184 for line in tf_zip.read('META/apexkeys.txt').decode().split('\n'):
Tao Baoaa7e9932019-03-15 09:37:01 -07001185 line = line.strip()
1186 if not line:
1187 continue
1188 matches = re.match(
1189 r'^name="(?P<NAME>.*)"\s+'
1190 r'public_key="(?P<PAYLOAD_PUBLIC_KEY>.*)"\s+'
1191 r'private_key="(?P<PAYLOAD_PRIVATE_KEY>.*)"\s+'
1192 r'container_certificate="(?P<CONTAINER_CERT>.*)"\s+'
Bill Peckham5c7b0342020-04-03 15:36:23 -07001193 r'container_private_key="(?P<CONTAINER_PRIVATE_KEY>.*?)"'
Jooyung Han8caba5e2021-10-27 03:58:09 +09001194 r'(\s+partition="(?P<PARTITION>.*?)")?'
1195 r'(\s+sign_tool="(?P<SIGN_TOOL>.*?)")?$',
Tao Baoaa7e9932019-03-15 09:37:01 -07001196 line)
1197 if not matches:
1198 continue
1199
1200 name = matches.group('NAME')
Tao Baoaa7e9932019-03-15 09:37:01 -07001201 payload_private_key = matches.group("PAYLOAD_PRIVATE_KEY")
1202
1203 def CompareKeys(pubkey, pubkey_suffix, privkey, privkey_suffix):
1204 pubkey_suffix_len = len(pubkey_suffix)
1205 privkey_suffix_len = len(privkey_suffix)
1206 return (pubkey.endswith(pubkey_suffix) and
1207 privkey.endswith(privkey_suffix) and
1208 pubkey[:-pubkey_suffix_len] == privkey[:-privkey_suffix_len])
1209
Ivan Lozanob021b2a2020-07-28 09:31:06 -04001210 # Check the container key names, as we'll carry them without the
Tao Bao6d9e3da2019-03-26 12:59:25 -07001211 # extensions. This doesn't apply to payload keys though, which we will use
1212 # full names only.
Tao Baoaa7e9932019-03-15 09:37:01 -07001213 container_cert = matches.group("CONTAINER_CERT")
1214 container_private_key = matches.group("CONTAINER_PRIVATE_KEY")
Tao Baof454c3a2019-04-24 23:53:42 -07001215 if container_cert == 'PRESIGNED' and container_private_key == 'PRESIGNED':
1216 container_key = 'PRESIGNED'
1217 elif CompareKeys(
Kelvin Zhang7cab7502021-08-02 19:58:14 -04001218 container_cert, OPTIONS.public_key_suffix,
1219 container_private_key, OPTIONS.private_key_suffix):
Tao Baof454c3a2019-04-24 23:53:42 -07001220 container_key = container_cert[:-len(OPTIONS.public_key_suffix)]
1221 else:
Tao Baoaa7e9932019-03-15 09:37:01 -07001222 raise ValueError("Failed to parse container keys: \n{}".format(line))
1223
Jooyung Han8caba5e2021-10-27 03:58:09 +09001224 sign_tool = matches.group("SIGN_TOOL")
1225 keys[name] = (payload_private_key, container_key, sign_tool)
Tao Baoaa7e9932019-03-15 09:37:01 -07001226
1227 return keys
1228
1229
Daniel Norman78554ea2021-09-14 10:29:38 -07001230def BuildVendorPartitions(output_zip_path):
1231 """Builds OPTIONS.vendor_partitions using OPTIONS.vendor_otatools."""
1232 if OPTIONS.vendor_partitions.difference(ALLOWED_VENDOR_PARTITIONS):
1233 logger.warning("Allowed --vendor_partitions: %s",
1234 ",".join(ALLOWED_VENDOR_PARTITIONS))
1235 OPTIONS.vendor_partitions = ALLOWED_VENDOR_PARTITIONS.intersection(
1236 OPTIONS.vendor_partitions)
1237
1238 logger.info("Building vendor partitions using vendor otatools.")
1239 vendor_tempdir = common.UnzipTemp(output_zip_path, [
1240 "META/*",
1241 ] + ["{}/*".format(p.upper()) for p in OPTIONS.vendor_partitions])
1242
1243 # Disable various partitions that build based on misc_info fields.
1244 # Only partitions in ALLOWED_VENDOR_PARTITIONS can be rebuilt using
1245 # vendor otatools. These other partitions will be rebuilt using the main
1246 # otatools if necessary.
1247 vendor_misc_info_path = os.path.join(vendor_tempdir, "META/misc_info.txt")
1248 vendor_misc_info = common.LoadDictionaryFromFile(vendor_misc_info_path)
1249 vendor_misc_info["no_boot"] = "true" # boot
1250 vendor_misc_info["vendor_boot"] = "false" # vendor_boot
1251 vendor_misc_info["no_recovery"] = "true" # recovery
1252 vendor_misc_info["board_bpt_enable"] = "false" # partition-table
1253 vendor_misc_info["has_dtbo"] = "false" # dtbo
1254 vendor_misc_info["has_pvmfw"] = "false" # pvmfw
1255 vendor_misc_info["avb_custom_images_partition_list"] = "" # custom images
1256 vendor_misc_info["avb_enable"] = "false" # vbmeta
1257 vendor_misc_info["use_dynamic_partitions"] = "false" # super_empty
1258 vendor_misc_info["build_super_partition"] = "false" # super split
1259 with open(vendor_misc_info_path, "w") as output:
1260 for key in sorted(vendor_misc_info):
1261 output.write("{}={}\n".format(key, vendor_misc_info[key]))
1262
1263 # Disable care_map.pb as not all ab_partitions are available when
1264 # vendor otatools regenerates vendor images.
1265 os.remove(os.path.join(vendor_tempdir, "META/ab_partitions.txt"))
1266
1267 # Build vendor images using vendor otatools.
1268 vendor_otatools_dir = common.MakeTempDir(prefix="vendor_otatools_")
1269 common.UnzipToDir(OPTIONS.vendor_otatools, vendor_otatools_dir)
1270 cmd = [
1271 os.path.join(vendor_otatools_dir, "bin", "add_img_to_target_files"),
1272 "--is_signing",
1273 "--verbose",
1274 vendor_tempdir,
1275 ]
1276 common.RunAndCheckOutput(cmd, verbose=True)
1277
1278 logger.info("Writing vendor partitions to output archive.")
1279 with zipfile.ZipFile(
1280 output_zip_path, "a", compression=zipfile.ZIP_DEFLATED,
1281 allowZip64=True) as output_zip:
1282 for p in OPTIONS.vendor_partitions:
1283 path = "IMAGES/{}.img".format(p)
1284 common.ZipWrite(output_zip, os.path.join(vendor_tempdir, path), path)
1285
1286
Doug Zongkereef39442009-04-02 12:14:19 -07001287def main(argv):
1288
Doug Zongker831840e2011-09-22 10:28:04 -07001289 key_mapping_options = []
1290
Doug Zongkereef39442009-04-02 12:14:19 -07001291 def option_handler(o, a):
Doug Zongker05d3dea2009-06-22 11:32:31 -07001292 if o in ("-e", "--extra_apks"):
Doug Zongkereef39442009-04-02 12:14:19 -07001293 names, key = a.split("=")
1294 names = names.split(",")
1295 for n in names:
1296 OPTIONS.extra_apks[n] = key
Tao Baoaa7e9932019-03-15 09:37:01 -07001297 elif o == "--extra_apex_payload_key":
1298 apex_name, key = a.split("=")
1299 OPTIONS.extra_apex_payload_keys[apex_name] = key
Tao Bao93c2a012018-06-19 12:19:35 -07001300 elif o == "--skip_apks_with_path_prefix":
Tianjiea85bdf02020-07-29 11:56:19 -07001301 # Check the prefix, which must be in all upper case.
Tao Bao93c2a012018-06-19 12:19:35 -07001302 prefix = a.split('/')[0]
1303 if not prefix or prefix != prefix.upper():
1304 raise ValueError("Invalid path prefix '%s'" % (a,))
1305 OPTIONS.skip_apks_with_path_prefix.add(a)
Doug Zongkereef39442009-04-02 12:14:19 -07001306 elif o in ("-d", "--default_key_mappings"):
Doug Zongker831840e2011-09-22 10:28:04 -07001307 key_mapping_options.append((None, a))
Doug Zongkereef39442009-04-02 12:14:19 -07001308 elif o in ("-k", "--key_mapping"):
Doug Zongker831840e2011-09-22 10:28:04 -07001309 key_mapping_options.append(a.split("=", 1))
Doug Zongker8e931bf2009-04-06 15:21:45 -07001310 elif o in ("-o", "--replace_ota_keys"):
1311 OPTIONS.replace_ota_keys = True
Doug Zongkerae877012009-04-21 10:04:51 -07001312 elif o in ("-t", "--tag_changes"):
1313 new = []
1314 for i in a.split(","):
1315 i = i.strip()
1316 if not i or i[0] not in "-+":
1317 raise ValueError("Bad tag change '%s'" % (i,))
1318 new.append(i[0] + i[1:].strip())
1319 OPTIONS.tag_changes = tuple(new)
Geremy Condraf19b3652014-07-29 17:54:54 -07001320 elif o == "--replace_verity_public_key":
1321 OPTIONS.replace_verity_public_key = (True, a)
1322 elif o == "--replace_verity_private_key":
1323 OPTIONS.replace_verity_private_key = (True, a)
Badhri Jagan Sridharan35c9b122016-06-16 19:58:44 -07001324 elif o == "--replace_verity_keyid":
1325 OPTIONS.replace_verity_keyid = (True, a)
Bowgo Tsai2fe786a2020-02-21 17:48:18 +08001326 elif o == "--remove_avb_public_keys":
1327 OPTIONS.remove_avb_public_keys = a.split(",")
Tao Bao639118f2017-06-19 15:48:02 -07001328 elif o == "--avb_vbmeta_key":
1329 OPTIONS.avb_keys['vbmeta'] = a
1330 elif o == "--avb_vbmeta_algorithm":
1331 OPTIONS.avb_algorithms['vbmeta'] = a
1332 elif o == "--avb_vbmeta_extra_args":
1333 OPTIONS.avb_extra_args['vbmeta'] = a
1334 elif o == "--avb_boot_key":
1335 OPTIONS.avb_keys['boot'] = a
1336 elif o == "--avb_boot_algorithm":
1337 OPTIONS.avb_algorithms['boot'] = a
1338 elif o == "--avb_boot_extra_args":
1339 OPTIONS.avb_extra_args['boot'] = a
1340 elif o == "--avb_dtbo_key":
1341 OPTIONS.avb_keys['dtbo'] = a
1342 elif o == "--avb_dtbo_algorithm":
1343 OPTIONS.avb_algorithms['dtbo'] = a
1344 elif o == "--avb_dtbo_extra_args":
1345 OPTIONS.avb_extra_args['dtbo'] = a
1346 elif o == "--avb_system_key":
1347 OPTIONS.avb_keys['system'] = a
1348 elif o == "--avb_system_algorithm":
1349 OPTIONS.avb_algorithms['system'] = a
1350 elif o == "--avb_system_extra_args":
1351 OPTIONS.avb_extra_args['system'] = a
Bowgo Tsaie4544b12019-02-27 10:15:51 +08001352 elif o == "--avb_system_other_key":
1353 OPTIONS.avb_keys['system_other'] = a
1354 elif o == "--avb_system_other_algorithm":
1355 OPTIONS.avb_algorithms['system_other'] = a
1356 elif o == "--avb_system_other_extra_args":
1357 OPTIONS.avb_extra_args['system_other'] = a
Tao Bao639118f2017-06-19 15:48:02 -07001358 elif o == "--avb_vendor_key":
1359 OPTIONS.avb_keys['vendor'] = a
1360 elif o == "--avb_vendor_algorithm":
1361 OPTIONS.avb_algorithms['vendor'] = a
1362 elif o == "--avb_vendor_extra_args":
1363 OPTIONS.avb_extra_args['vendor'] = a
Tao Baod6085d62019-05-06 12:55:42 -07001364 elif o == "--avb_vbmeta_system_key":
1365 OPTIONS.avb_keys['vbmeta_system'] = a
1366 elif o == "--avb_vbmeta_system_algorithm":
1367 OPTIONS.avb_algorithms['vbmeta_system'] = a
1368 elif o == "--avb_vbmeta_system_extra_args":
1369 OPTIONS.avb_extra_args['vbmeta_system'] = a
1370 elif o == "--avb_vbmeta_vendor_key":
1371 OPTIONS.avb_keys['vbmeta_vendor'] = a
1372 elif o == "--avb_vbmeta_vendor_algorithm":
1373 OPTIONS.avb_algorithms['vbmeta_vendor'] = a
1374 elif o == "--avb_vbmeta_vendor_extra_args":
1375 OPTIONS.avb_extra_args['vbmeta_vendor'] = a
Tao Baoaa7e9932019-03-15 09:37:01 -07001376 elif o == "--avb_apex_extra_args":
1377 OPTIONS.avb_extra_args['apex'] = a
Hongguang Chenf23364d2020-04-27 18:36:36 -07001378 elif o == "--avb_extra_custom_image_key":
1379 partition, key = a.split("=")
1380 OPTIONS.avb_keys[partition] = key
1381 elif o == "--avb_extra_custom_image_algorithm":
1382 partition, algorithm = a.split("=")
1383 OPTIONS.avb_algorithms[partition] = algorithm
1384 elif o == "--avb_extra_custom_image_extra_args":
Hongguang Chen883eecb2020-05-23 22:20:19 -07001385 # Setting the maxsplit parameter to one, which will return a list with
1386 # two elements. e.g., the second '=' should not be splitted for
1387 # 'oem=--signing_helper_with_files=/tmp/avbsigner.sh'.
1388 partition, extra_args = a.split("=", 1)
Hongguang Chenf23364d2020-04-27 18:36:36 -07001389 OPTIONS.avb_extra_args[partition] = extra_args
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001390 elif o == "--gki_signing_key":
1391 OPTIONS.gki_signing_key = a
1392 elif o == "--gki_signing_algorithm":
1393 OPTIONS.gki_signing_algorithm = a
1394 elif o == "--gki_signing_extra_args":
1395 OPTIONS.gki_signing_extra_args = a
Daniel Norman78554ea2021-09-14 10:29:38 -07001396 elif o == "--vendor_otatools":
1397 OPTIONS.vendor_otatools = a
1398 elif o == "--vendor_partitions":
1399 OPTIONS.vendor_partitions = set(a.split(","))
Bowgo Tsai2a781692021-10-13 17:39:33 +08001400 elif o == "--allow_gsi_debug_sepolicy":
1401 OPTIONS.allow_gsi_debug_sepolicy = True
Doug Zongkereef39442009-04-02 12:14:19 -07001402 else:
1403 return False
1404 return True
1405
Tao Bao639118f2017-06-19 15:48:02 -07001406 args = common.ParseOptions(
1407 argv, __doc__,
1408 extra_opts="e:d:k:ot:",
1409 extra_long_opts=[
Tao Bao0c28d2d2017-12-24 10:37:38 -08001410 "extra_apks=",
Tao Baoaa7e9932019-03-15 09:37:01 -07001411 "extra_apex_payload_key=",
Tao Bao93c2a012018-06-19 12:19:35 -07001412 "skip_apks_with_path_prefix=",
Tao Bao0c28d2d2017-12-24 10:37:38 -08001413 "default_key_mappings=",
1414 "key_mapping=",
1415 "replace_ota_keys",
1416 "tag_changes=",
1417 "replace_verity_public_key=",
1418 "replace_verity_private_key=",
1419 "replace_verity_keyid=",
Bowgo Tsai2fe786a2020-02-21 17:48:18 +08001420 "remove_avb_public_keys=",
Tao Baoaa7e9932019-03-15 09:37:01 -07001421 "avb_apex_extra_args=",
Tao Bao0c28d2d2017-12-24 10:37:38 -08001422 "avb_vbmeta_algorithm=",
1423 "avb_vbmeta_key=",
1424 "avb_vbmeta_extra_args=",
1425 "avb_boot_algorithm=",
1426 "avb_boot_key=",
1427 "avb_boot_extra_args=",
1428 "avb_dtbo_algorithm=",
1429 "avb_dtbo_key=",
1430 "avb_dtbo_extra_args=",
1431 "avb_system_algorithm=",
1432 "avb_system_key=",
1433 "avb_system_extra_args=",
Bowgo Tsaie4544b12019-02-27 10:15:51 +08001434 "avb_system_other_algorithm=",
1435 "avb_system_other_key=",
1436 "avb_system_other_extra_args=",
Tao Bao0c28d2d2017-12-24 10:37:38 -08001437 "avb_vendor_algorithm=",
1438 "avb_vendor_key=",
1439 "avb_vendor_extra_args=",
Tao Baod6085d62019-05-06 12:55:42 -07001440 "avb_vbmeta_system_algorithm=",
1441 "avb_vbmeta_system_key=",
1442 "avb_vbmeta_system_extra_args=",
1443 "avb_vbmeta_vendor_algorithm=",
1444 "avb_vbmeta_vendor_key=",
1445 "avb_vbmeta_vendor_extra_args=",
Hongguang Chenf23364d2020-04-27 18:36:36 -07001446 "avb_extra_custom_image_key=",
1447 "avb_extra_custom_image_algorithm=",
1448 "avb_extra_custom_image_extra_args=",
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001449 "gki_signing_key=",
1450 "gki_signing_algorithm=",
1451 "gki_signing_extra_args=",
Daniel Norman78554ea2021-09-14 10:29:38 -07001452 "vendor_partitions=",
1453 "vendor_otatools=",
Bowgo Tsai2a781692021-10-13 17:39:33 +08001454 "allow_gsi_debug_sepolicy",
Tao Bao639118f2017-06-19 15:48:02 -07001455 ],
1456 extra_option_handler=option_handler)
Doug Zongkereef39442009-04-02 12:14:19 -07001457
1458 if len(args) != 2:
1459 common.Usage(__doc__)
1460 sys.exit(1)
1461
Tao Baobadceb22019-03-15 09:33:43 -07001462 common.InitLogging()
1463
Kelvin Zhang928c2342020-09-22 16:15:57 -04001464 input_zip = zipfile.ZipFile(args[0], "r", allowZip64=True)
Tao Bao2b8f4892017-06-13 12:54:58 -07001465 output_zip = zipfile.ZipFile(args[1], "w",
1466 compression=zipfile.ZIP_DEFLATED,
1467 allowZip64=True)
Doug Zongkereef39442009-04-02 12:14:19 -07001468
Doug Zongker831840e2011-09-22 10:28:04 -07001469 misc_info = common.LoadInfoDict(input_zip)
1470
1471 BuildKeyMap(misc_info, key_mapping_options)
1472
Tao Baoaa7e9932019-03-15 09:37:01 -07001473 apk_keys_info, compressed_extension = common.ReadApkCerts(input_zip)
1474 apk_keys = GetApkCerts(apk_keys_info)
Doug Zongkereb338ef2009-05-20 16:50:49 -07001475
Tao Baoaa7e9932019-03-15 09:37:01 -07001476 apex_keys_info = ReadApexKeysInfo(input_zip)
1477 apex_keys = GetApexKeys(apex_keys_info, apk_keys)
1478
Tianjie Xu88a759d2020-01-23 10:47:54 -08001479 # TODO(xunchang) check for the apks inside the apex files, and abort early if
1480 # the keys are not available.
Tao Baoaa7e9932019-03-15 09:37:01 -07001481 CheckApkAndApexKeysAvailable(
1482 input_zip,
1483 set(apk_keys.keys()) | set(apex_keys.keys()),
Tao Baoe1343992019-03-19 12:24:03 -07001484 compressed_extension,
1485 apex_keys)
Tao Baoaa7e9932019-03-15 09:37:01 -07001486
1487 key_passwords = common.GetKeyPasswords(
1488 set(apk_keys.values()) | set(itertools.chain(*apex_keys.values())))
Tao Bao9aa4b9b2016-09-29 17:53:56 -07001489 platform_api_level, _ = GetApiLevelAndCodename(input_zip)
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001490 codename_to_api_level_map = GetCodenameToApiLevelMap(input_zip)
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001491
Doug Zongker412c02f2014-02-13 10:58:24 -08001492 ProcessTargetFiles(input_zip, output_zip, misc_info,
Tao Baoaa7e9932019-03-15 09:37:01 -07001493 apk_keys, apex_keys, key_passwords,
1494 platform_api_level, codename_to_api_level_map,
Narayan Kamatha07bf042017-08-14 14:49:21 +01001495 compressed_extension)
Doug Zongker8e931bf2009-04-06 15:21:45 -07001496
Tao Bao2ed665a2015-04-01 11:21:55 -07001497 common.ZipClose(input_zip)
1498 common.ZipClose(output_zip)
Doug Zongkereef39442009-04-02 12:14:19 -07001499
Daniel Norman78554ea2021-09-14 10:29:38 -07001500 if OPTIONS.vendor_partitions and OPTIONS.vendor_otatools:
1501 BuildVendorPartitions(args[1])
1502
Tianjie Xub48589a2016-08-03 19:21:52 -07001503 # Skip building userdata.img and cache.img when signing the target files.
Daniel Norman78554ea2021-09-14 10:29:38 -07001504 new_args = ["--is_signing", "--add_missing", "--verbose"]
Tianjie Xu616fbeb2017-05-23 14:51:02 -07001505 # add_img_to_target_files builds the system image from scratch, so the
1506 # recovery patch is guaranteed to be regenerated there.
1507 if OPTIONS.rebuild_recovery:
1508 new_args.append("--rebuild_recovery")
1509 new_args.append(args[1])
Tianjie Xub48589a2016-08-03 19:21:52 -07001510 add_img_to_target_files.main(new_args)
Doug Zongker3c84f562014-07-31 11:06:30 -07001511
Tao Bao0c28d2d2017-12-24 10:37:38 -08001512 print("done.")
Doug Zongkereef39442009-04-02 12:14:19 -07001513
1514
1515if __name__ == '__main__':
1516 try:
1517 main(sys.argv[1:])
Tao Bao0c28d2d2017-12-24 10:37:38 -08001518 except common.ExternalError as e:
1519 print("\n ERROR: %s\n" % (e,))
Kelvin Zhang6c17ed32021-04-07 14:56:09 -04001520 raise
Tao Bao639118f2017-06-19 15:48:02 -07001521 finally:
1522 common.Cleanup()