xref: /aosp_15_r20/build/make/tools/releasetools/sign_target_files_apks.py (revision 9e94795a3d4ef5c1d47486f9a02bb378756cea8a)
1#!/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
23  -e  (--extra_apks)  <name,name,...=key>
24      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,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.
35
36  --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
42  -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
51        $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
59      META/misc_info.txt.  (Defaulting to "build/make/target/product/security"
60      if the value is not present in misc_info.
61
62      -d and -k options are added to the set of mappings in the order
63      in which they appear on the command line.
64
65  -o  (--replace_ota_keys)
66      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.
72
73  -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.
78      Default value is "-test-keys,-dev-keys,+release-keys".
79
80  --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. It expects the key
87      filename WITH the extension (e.g. verity_key.pub).
88
89  --replace_verity_keyid <path_to_X509_PEM_cert_file>
90      Replace the veritykeyid in BOOT/cmdline of input_target_file_zip
91      with keyid of the cert pointed by <path_to_X509_PEM_cert_file>.
92
93  --remove_avb_public_keys <key1>,<key2>,...
94      Remove AVB public keys from the first-stage ramdisk. The key file to
95      remove is located at either of the following dirs:
96        - BOOT/RAMDISK/avb/ or
97        - BOOT/RAMDISK/first_stage_ramdisk/avb/
98      The second dir will be used for lookup if BOARD_USES_RECOVERY_AS_BOOT is
99      set to true.
100
101  --avb_{boot,init_boot,recovery,system,system_other,vendor,dtbo,vbmeta,
102         vbmeta_system,vbmeta_vendor}_algorithm <algorithm>
103  --avb_{boot,init_boot,recovery,system,system_other,vendor,dtbo,vbmeta,
104         vbmeta_system,vbmeta_vendor}_key <key>
105      Use the specified algorithm (e.g. SHA256_RSA4096) and the key to AVB-sign
106      the specified image. Otherwise it uses the existing values in info dict.
107
108  --avb_{apex,init_boot,boot,recovery,system,system_other,vendor,dtbo,vbmeta,
109         vbmeta_system,vbmeta_vendor}_extra_args <args>
110      Specify any additional args that are needed to AVB-sign the image
111      (e.g. "--signing_helper /path/to/helper"). The args will be appended to
112      the existing ones in info dict.
113
114  --avb_extra_custom_image_key <partition=key>
115  --avb_extra_custom_image_algorithm <partition=algorithm>
116      Use the specified algorithm (e.g. SHA256_RSA4096) and the key to AVB-sign
117      the specified custom images mounted on the partition. Otherwise it uses
118      the existing values in info dict.
119
120  --avb_extra_custom_image_extra_args <partition=extra_args>
121      Specify any additional args that are needed to AVB-sign the custom images
122      mounted on the partition (e.g. "--signing_helper /path/to/helper"). The
123      args will be appended to the existing ones in info dict.
124
125  --gki_signing_algorithm <algorithm>
126  --gki_signing_key <key>
127  --gki_signing_extra_args <args>
128      DEPRECATED Does nothing.
129
130  --android_jar_path <path>
131      Path to the android.jar to repack the apex file.
132
133  --allow_gsi_debug_sepolicy
134      Allow the existence of the file 'userdebug_plat_sepolicy.cil' under
135      (/system/system_ext|/system_ext)/etc/selinux.
136      If not set, error out when the file exists.
137
138  --override_apk_keys <path>
139      Replace all APK keys with this private key
140
141  --override_apex_keys <path>
142      Replace all APEX keys with this private key
143
144  -k  (--package_key) <key>
145      Key to use to sign the package (default is the value of
146      default_system_dev_certificate from the input target-files's
147      META/misc_info.txt, or "build/make/target/product/security/testkey" if
148      that value is not specified).
149
150      For incremental OTAs, the default value is based on the source
151      target-file, not the target build.
152
153  --payload_signer <signer>
154      Specify the signer when signing the payload and metadata for A/B OTAs.
155      By default (i.e. without this flag), it calls 'openssl pkeyutl' to sign
156      with the package private key. If the private key cannot be accessed
157      directly, a payload signer that knows how to do that should be specified.
158      The signer will be supplied with "-inkey <path_to_key>",
159      "-in <input_file>" and "-out <output_file>" parameters.
160
161  --payload_signer_args <args>
162      Specify the arguments needed for payload signer.
163
164  --payload_signer_maximum_signature_size <signature_size>
165      The maximum signature size (in bytes) that would be generated by the given
166      payload signer. Only meaningful when custom payload signer is specified
167      via '--payload_signer'.
168      If the signer uses a RSA key, this should be the number of bytes to
169      represent the modulus. If it uses an EC key, this is the size of a
170      DER-encoded ECDSA signature.
171"""
172
173from __future__ import print_function
174
175import base64
176import copy
177import errno
178import gzip
179import io
180import itertools
181import logging
182import os
183import re
184import shutil
185import stat
186import sys
187import shlex
188import tempfile
189import zipfile
190from xml.etree import ElementTree
191
192import add_img_to_target_files
193import ota_from_raw_img
194import apex_utils
195import common
196import payload_signer
197import update_payload
198from payload_signer import SignOtaPackage, PAYLOAD_BIN
199
200
201if sys.hexversion < 0x02070000:
202  print("Python 2.7 or newer is required.", file=sys.stderr)
203  sys.exit(1)
204
205
206logger = logging.getLogger(__name__)
207
208OPTIONS = common.OPTIONS
209
210OPTIONS.extra_apks = {}
211OPTIONS.extra_apex_payload_keys = {}
212OPTIONS.skip_apks_with_path_prefix = set()
213OPTIONS.key_map = {}
214OPTIONS.rebuild_recovery = False
215OPTIONS.replace_ota_keys = False
216OPTIONS.remove_avb_public_keys = None
217OPTIONS.tag_changes = ("-test-keys", "-dev-keys", "+release-keys")
218OPTIONS.avb_keys = {}
219OPTIONS.avb_algorithms = {}
220OPTIONS.avb_extra_args = {}
221OPTIONS.android_jar_path = None
222OPTIONS.vendor_partitions = set()
223OPTIONS.vendor_otatools = None
224OPTIONS.allow_gsi_debug_sepolicy = False
225OPTIONS.override_apk_keys = None
226OPTIONS.override_apex_keys = None
227OPTIONS.input_tmp = None
228
229
230AVB_FOOTER_ARGS_BY_PARTITION = {
231    'boot': 'avb_boot_add_hash_footer_args',
232    'init_boot': 'avb_init_boot_add_hash_footer_args',
233    'dtbo': 'avb_dtbo_add_hash_footer_args',
234    'product': 'avb_product_add_hashtree_footer_args',
235    'recovery': 'avb_recovery_add_hash_footer_args',
236    'system': 'avb_system_add_hashtree_footer_args',
237    'system_dlkm': "avb_system_dlkm_add_hashtree_footer_args",
238    'system_ext': 'avb_system_ext_add_hashtree_footer_args',
239    'system_other': 'avb_system_other_add_hashtree_footer_args',
240    'odm': 'avb_odm_add_hashtree_footer_args',
241    'odm_dlkm': 'avb_odm_dlkm_add_hashtree_footer_args',
242    'pvmfw': 'avb_pvmfw_add_hash_footer_args',
243    'vendor': 'avb_vendor_add_hashtree_footer_args',
244    'vendor_boot': 'avb_vendor_boot_add_hash_footer_args',
245    'vendor_kernel_boot': 'avb_vendor_kernel_boot_add_hash_footer_args',
246    'vendor_dlkm': "avb_vendor_dlkm_add_hashtree_footer_args",
247    'vbmeta': 'avb_vbmeta_args',
248    'vbmeta_system': 'avb_vbmeta_system_args',
249    'vbmeta_vendor': 'avb_vbmeta_vendor_args',
250}
251
252
253# Check that AVB_FOOTER_ARGS_BY_PARTITION is in sync with AVB_PARTITIONS.
254for partition in common.AVB_PARTITIONS:
255  if partition not in AVB_FOOTER_ARGS_BY_PARTITION:
256    raise RuntimeError("Missing {} in AVB_FOOTER_ARGS".format(partition))
257
258# Partitions that can be regenerated after signing using a separate
259# vendor otatools package.
260ALLOWED_VENDOR_PARTITIONS = set(["vendor", "odm"])
261
262
263def IsApexFile(filename):
264  return filename.endswith(".apex") or filename.endswith(".capex")
265
266
267def IsOtaPackage(fp):
268  with zipfile.ZipFile(fp) as zfp:
269    if not PAYLOAD_BIN in zfp.namelist():
270      return False
271    with zfp.open(PAYLOAD_BIN, "r") as payload:
272      magic = payload.read(4)
273      return magic == b"CrAU"
274
275
276def IsEntryOtaPackage(input_zip, filename):
277  with input_zip.open(filename, "r") as fp:
278    external_attr = input_zip.getinfo(filename).external_attr
279    if stat.S_ISLNK(external_attr >> 16):
280      return IsEntryOtaPackage(input_zip,
281          os.path.join(os.path.dirname(filename), fp.read().decode()))
282    return IsOtaPackage(fp)
283
284
285def GetApexFilename(filename):
286  name = os.path.basename(filename)
287  # Replace the suffix for compressed apex
288  if name.endswith(".capex"):
289    return name.replace(".capex", ".apex")
290  return name
291
292
293def GetApkCerts(certmap):
294  if OPTIONS.override_apk_keys is not None:
295    for apk in certmap.keys():
296      certmap[apk] = OPTIONS.override_apk_keys
297
298  # apply the key remapping to the contents of the file
299  for apk, cert in certmap.items():
300    certmap[apk] = OPTIONS.key_map.get(cert, cert)
301
302  # apply all the -e options, overriding anything in the file
303  for apk, cert in OPTIONS.extra_apks.items():
304    if not cert:
305      cert = "PRESIGNED"
306    certmap[apk] = OPTIONS.key_map.get(cert, cert)
307
308  return certmap
309
310
311def GetApexKeys(keys_info, key_map):
312  """Gets APEX payload and container signing keys by applying the mapping rules.
313
314  Presigned payload / container keys will be set accordingly.
315
316  Args:
317    keys_info: A dict that maps from APEX filenames to a tuple of (payload_key,
318        container_key, sign_tool).
319    key_map: A dict that overrides the keys, specified via command-line input.
320
321  Returns:
322    A dict that contains the updated APEX key mapping, which should be used for
323    the current signing.
324
325  Raises:
326    AssertionError: On invalid container / payload key overrides.
327  """
328  if OPTIONS.override_apex_keys is not None:
329    for apex in keys_info.keys():
330      keys_info[apex] = (OPTIONS.override_apex_keys, keys_info[apex][1], keys_info[apex][2])
331
332  if OPTIONS.override_apk_keys is not None:
333    key = key_map.get(OPTIONS.override_apk_keys, OPTIONS.override_apk_keys)
334    for apex in keys_info.keys():
335      keys_info[apex] = (keys_info[apex][0], key, keys_info[apex][2])
336
337  # Apply all the --extra_apex_payload_key options to override the payload
338  # signing keys in the given keys_info.
339  for apex, key in OPTIONS.extra_apex_payload_keys.items():
340    if not key:
341      key = 'PRESIGNED'
342    if apex not in keys_info:
343      logger.warning('Failed to find %s in target_files; Ignored', apex)
344      continue
345    keys_info[apex] = (key, keys_info[apex][1], keys_info[apex][2])
346
347  # Apply the key remapping to container keys.
348  for apex, (payload_key, container_key, sign_tool) in keys_info.items():
349    keys_info[apex] = (payload_key, key_map.get(container_key, container_key), sign_tool)
350
351  # Apply all the --extra_apks options to override the container keys.
352  for apex, key in OPTIONS.extra_apks.items():
353    # Skip non-APEX containers.
354    if apex not in keys_info:
355      continue
356    if not key:
357      key = 'PRESIGNED'
358    keys_info[apex] = (keys_info[apex][0], key_map.get(key, key), keys_info[apex][2])
359
360  # A PRESIGNED container entails a PRESIGNED payload. Apply this to all the
361  # APEX key pairs. However, a PRESIGNED container with non-PRESIGNED payload
362  # (overridden via commandline) indicates a config error, which should not be
363  # allowed.
364  for apex, (payload_key, container_key, sign_tool) in keys_info.items():
365    if container_key != 'PRESIGNED':
366      continue
367    if apex in OPTIONS.extra_apex_payload_keys:
368      payload_override = OPTIONS.extra_apex_payload_keys[apex]
369      assert payload_override == '', \
370          ("Invalid APEX key overrides: {} has PRESIGNED container but "
371           "non-PRESIGNED payload key {}").format(apex, payload_override)
372    if payload_key != 'PRESIGNED':
373      print(
374          "Setting {} payload as PRESIGNED due to PRESIGNED container".format(
375              apex))
376    keys_info[apex] = ('PRESIGNED', 'PRESIGNED', None)
377
378  return keys_info
379
380
381def GetApkFileInfo(filename, compressed_extension, skipped_prefixes):
382  """Returns the APK info based on the given filename.
383
384  Checks if the given filename (with path) looks like an APK file, by taking the
385  compressed extension into consideration. If it appears to be an APK file,
386  further checks if the APK file should be skipped when signing, based on the
387  given path prefixes.
388
389  Args:
390    filename: Path to the file.
391    compressed_extension: The extension string of compressed APKs (e.g. ".gz"),
392        or None if there's no compressed APKs.
393    skipped_prefixes: A set/list/tuple of the path prefixes to be skipped.
394
395  Returns:
396    (is_apk, is_compressed, should_be_skipped): is_apk indicates whether the
397    given filename is an APK file. is_compressed indicates whether the APK file
398    is compressed (only meaningful when is_apk is True). should_be_skipped
399    indicates whether the filename matches any of the given prefixes to be
400    skipped.
401
402  Raises:
403    AssertionError: On invalid compressed_extension or skipped_prefixes inputs.
404  """
405  assert compressed_extension is None or compressed_extension.startswith('.'), \
406      "Invalid compressed_extension arg: '{}'".format(compressed_extension)
407
408  # skipped_prefixes should be one of set/list/tuple types. Other types such as
409  # str shouldn't be accepted.
410  assert isinstance(skipped_prefixes, (set, list, tuple)), \
411      "Invalid skipped_prefixes input type: {}".format(type(skipped_prefixes))
412
413  compressed_apk_extension = (
414      ".apk" + compressed_extension if compressed_extension else None)
415  is_apk = (filename.endswith(".apk") or
416            (compressed_apk_extension and
417             filename.endswith(compressed_apk_extension)))
418  if not is_apk:
419    return (False, False, False)
420
421  is_compressed = (compressed_apk_extension and
422                   filename.endswith(compressed_apk_extension))
423  should_be_skipped = filename.startswith(tuple(skipped_prefixes))
424  return (True, is_compressed, should_be_skipped)
425
426
427def CheckApkAndApexKeysAvailable(input_tf_zip, known_keys,
428                                 compressed_extension, apex_keys):
429  """Checks that all the APKs and APEXes have keys specified.
430
431  Args:
432    input_tf_zip: An open target_files zip file.
433    known_keys: A set of APKs and APEXes that have known signing keys.
434    compressed_extension: The extension string of compressed APKs, such as
435        '.gz', or None if there's no compressed APKs.
436    apex_keys: A dict that contains the key mapping from APEX name to
437        (payload_key, container_key, sign_tool).
438
439  Raises:
440    AssertionError: On finding unknown APKs and APEXes.
441  """
442  unknown_files = []
443  for info in input_tf_zip.infolist():
444    # Handle APEXes on all partitions
445    if IsApexFile(info.filename):
446      name = GetApexFilename(info.filename)
447      if name not in known_keys:
448        unknown_files.append(name)
449      continue
450
451    # And APKs.
452    (is_apk, is_compressed, should_be_skipped) = GetApkFileInfo(
453        info.filename, compressed_extension, OPTIONS.skip_apks_with_path_prefix)
454    if not is_apk or should_be_skipped:
455      continue
456
457    name = os.path.basename(info.filename)
458    if is_compressed:
459      name = name[:-len(compressed_extension)]
460    if name not in known_keys:
461      unknown_files.append(name)
462
463  assert not unknown_files, \
464      ("No key specified for:\n  {}\n"
465       "Use '-e <apkname>=' to specify a key (which may be an empty string to "
466       "not sign this apk).".format("\n  ".join(unknown_files)))
467
468  # For all the APEXes, double check that we won't have an APEX that has only
469  # one of the payload / container keys set. Note that non-PRESIGNED container
470  # with PRESIGNED payload could be allowed but currently unsupported. It would
471  # require changing SignApex implementation.
472  if not apex_keys:
473    return
474
475  invalid_apexes = []
476  for info in input_tf_zip.infolist():
477    if not IsApexFile(info.filename):
478      continue
479
480    name = GetApexFilename(info.filename)
481
482    (payload_key, container_key, _) = apex_keys[name]
483    if ((payload_key in common.SPECIAL_CERT_STRINGS and
484         container_key not in common.SPECIAL_CERT_STRINGS) or
485        (payload_key not in common.SPECIAL_CERT_STRINGS and
486         container_key in common.SPECIAL_CERT_STRINGS)):
487      invalid_apexes.append(
488          "{}: payload_key {}, container_key {}".format(
489              name, payload_key, container_key))
490
491  assert not invalid_apexes, \
492      "Invalid APEX keys specified:\n  {}\n".format(
493          "\n  ".join(invalid_apexes))
494
495
496def SignApk(data, keyname, pw, platform_api_level, codename_to_api_level_map,
497            is_compressed, apk_name):
498  unsigned = tempfile.NamedTemporaryFile(suffix='_' + apk_name)
499  unsigned.write(data)
500  unsigned.flush()
501
502  if is_compressed:
503    uncompressed = tempfile.NamedTemporaryFile()
504    with gzip.open(unsigned.name, "rb") as in_file, \
505            open(uncompressed.name, "wb") as out_file:
506      shutil.copyfileobj(in_file, out_file)
507
508    # Finally, close the "unsigned" file (which is gzip compressed), and then
509    # replace it with the uncompressed version.
510    #
511    # TODO(narayan): All this nastiness can be avoided if python 3.2 is in use,
512    # we could just gzip / gunzip in-memory buffers instead.
513    unsigned.close()
514    unsigned = uncompressed
515
516  signed = tempfile.NamedTemporaryFile(suffix='_' + apk_name)
517
518  # For pre-N builds, don't upgrade to SHA-256 JAR signatures based on the APK's
519  # minSdkVersion to avoid increasing incremental OTA update sizes. If an APK
520  # didn't change, we don't want its signature to change due to the switch
521  # from SHA-1 to SHA-256.
522  # By default, APK signer chooses SHA-256 signatures if the APK's minSdkVersion
523  # is 18 or higher. For pre-N builds we disable this mechanism by pretending
524  # that the APK's minSdkVersion is 1.
525  # For N+ builds, we let APK signer rely on the APK's minSdkVersion to
526  # determine whether to use SHA-256.
527  min_api_level = None
528  if platform_api_level > 23:
529    # Let APK signer choose whether to use SHA-1 or SHA-256, based on the APK's
530    # minSdkVersion attribute
531    min_api_level = None
532  else:
533    # Force APK signer to use SHA-1
534    min_api_level = 1
535
536  common.SignFile(unsigned.name, signed.name, keyname, pw,
537                  min_api_level=min_api_level,
538                  codename_to_api_level_map=codename_to_api_level_map)
539
540  data = None
541  if is_compressed:
542    # Recompress the file after it has been signed.
543    compressed = tempfile.NamedTemporaryFile()
544    with open(signed.name, "rb") as in_file, \
545            gzip.open(compressed.name, "wb") as out_file:
546      shutil.copyfileobj(in_file, out_file)
547
548    data = compressed.read()
549    compressed.close()
550  else:
551    data = signed.read()
552
553  unsigned.close()
554  signed.close()
555
556  return data
557
558
559
560def IsBuildPropFile(filename):
561  return filename in (
562      "SYSTEM/etc/prop.default",
563      "BOOT/RAMDISK/prop.default",
564      "RECOVERY/RAMDISK/prop.default",
565
566      "VENDOR_BOOT/RAMDISK/default.prop",
567      "VENDOR_BOOT/RAMDISK/prop.default",
568
569      # ROOT/default.prop is a legacy path, but may still exist for upgrading
570      # devices that don't support `property_overrides_split_enabled`.
571      "ROOT/default.prop",
572
573      # RECOVERY/RAMDISK/default.prop is a legacy path, but will always exist
574      # as a symlink in the current code. So it's a no-op here. Keeping the
575      # path here for clarity.
576      # Some build props might be stored under path
577      # VENDOR_BOOT/RAMDISK_FRAGMENTS/recovery/RAMDISK/default.prop, and
578      # default.prop can be a symbolic link to prop.default, so overwrite all
579      # files that ends with build.prop, default.prop or prop.default
580      "RECOVERY/RAMDISK/default.prop") or \
581        filename.endswith("build.prop") or \
582        filename.endswith("/default.prop") or \
583        filename.endswith("/prop.default")
584
585
586def GetOtaSigningArgs():
587  args = []
588  if OPTIONS.package_key:
589    args.extend(["--package_key", OPTIONS.package_key])
590  if OPTIONS.payload_signer:
591    args.extend(["--payload_signer=" + OPTIONS.payload_signer])
592  if OPTIONS.payload_signer_args:
593    args.extend(["--payload_signer_args=" + shlex.join(OPTIONS.payload_signer_args)])
594  if OPTIONS.search_path:
595    args.extend(["--search_path", OPTIONS.search_path])
596  if OPTIONS.payload_signer_maximum_signature_size:
597    args.extend(["--payload_signer_maximum_signature_size",
598                OPTIONS.payload_signer_maximum_signature_size])
599  if OPTIONS.private_key_suffix:
600    args.extend(["--private_key_suffix", OPTIONS.private_key_suffix])
601  return args
602
603
604def RegenerateKernelPartitions(input_tf_zip: zipfile.ZipFile, output_tf_zip: zipfile.ZipFile, misc_info):
605  """Re-generate boot and dtbo partitions using new signing configuration"""
606  files_to_unzip = [
607      "PREBUILT_IMAGES/*", "BOOTABLE_IMAGES/*.img", "*/boot_16k.img", "*/dtbo_16k.img"]
608  if OPTIONS.input_tmp is None:
609    OPTIONS.input_tmp = common.UnzipTemp(input_tf_zip.filename, files_to_unzip)
610  else:
611    common.UnzipToDir(input_tf_zip.filename, OPTIONS.input_tmp, files_to_unzip)
612  unzip_dir = OPTIONS.input_tmp
613  os.makedirs(os.path.join(unzip_dir, "IMAGES"), exist_ok=True)
614
615  boot_image = common.GetBootableImage(
616      "IMAGES/boot.img", "boot.img", unzip_dir, "BOOT", misc_info)
617  if boot_image:
618    boot_image.WriteToDir(unzip_dir)
619    boot_image = os.path.join(unzip_dir, boot_image.name)
620    common.ZipWrite(output_tf_zip, boot_image, "IMAGES/boot.img",
621                    compress_type=zipfile.ZIP_STORED)
622  if misc_info.get("has_dtbo") == "true":
623    add_img_to_target_files.AddDtbo(output_tf_zip)
624  return unzip_dir
625
626
627def RegenerateBootOTA(input_tf_zip: zipfile.ZipFile, filename, input_ota):
628  with input_tf_zip.open(filename, "r") as in_fp:
629    payload = update_payload.Payload(in_fp)
630  is_incremental = any([part.HasField('old_partition_info')
631                        for part in payload.manifest.partitions])
632  is_boot_ota = filename.startswith(
633      "VENDOR/boot_otas/") or filename.startswith("SYSTEM/boot_otas/")
634  if not is_boot_ota:
635    return
636  is_4k_boot_ota = filename in [
637      "VENDOR/boot_otas/boot_ota_4k.zip", "SYSTEM/boot_otas/boot_ota_4k.zip"]
638  # Only 4K boot image is re-generated, so if 16K boot ota isn't incremental,
639  # we do not need to re-generate
640  if not is_4k_boot_ota and not is_incremental:
641    return
642
643  timestamp = str(payload.manifest.max_timestamp)
644  partitions = [part.partition_name for part in payload.manifest.partitions]
645  unzip_dir = OPTIONS.input_tmp
646  signed_boot_image = os.path.join(unzip_dir, "IMAGES", "boot.img")
647  if not os.path.exists(signed_boot_image):
648    logger.warn("Need to re-generate boot OTA {} but failed to get signed boot image. 16K dev option will be impacted, after rolling back to 4K user would need to sideload/flash their device to continue receiving OTAs.")
649    return
650  signed_dtbo_image = os.path.join(unzip_dir, "IMAGES", "dtbo.img")
651  if "dtbo" in partitions and not os.path.exists(signed_dtbo_image):
652    raise ValueError(
653        "Boot OTA {} has dtbo partition, but no dtbo image found in target files.".format(filename))
654  if is_incremental:
655    signed_16k_boot_image = os.path.join(
656        unzip_dir, "IMAGES", "boot_16k.img")
657    signed_16k_dtbo_image = os.path.join(
658        unzip_dir, "IMAGES", "dtbo_16k.img")
659    if is_4k_boot_ota:
660      if os.path.exists(signed_16k_boot_image):
661        signed_boot_image = signed_16k_boot_image + ":" + signed_boot_image
662      if os.path.exists(signed_16k_dtbo_image):
663        signed_dtbo_image = signed_16k_dtbo_image + ":" + signed_dtbo_image
664    else:
665      if os.path.exists(signed_16k_boot_image):
666        signed_boot_image += ":" + signed_16k_boot_image
667      if os.path.exists(signed_16k_dtbo_image):
668        signed_dtbo_image += ":" + signed_16k_dtbo_image
669
670  args = ["ota_from_raw_img",
671          "--max_timestamp", timestamp, "--output", input_ota.name]
672  args.extend(GetOtaSigningArgs())
673  if "dtbo" in partitions:
674    args.extend(["--partition_name", "boot,dtbo",
675                signed_boot_image, signed_dtbo_image])
676  else:
677    args.extend(["--partition_name", "boot", signed_boot_image])
678  logger.info(
679      "Re-generating boot OTA {} using cmd {}".format(filename, args))
680  ota_from_raw_img.main(args)
681
682
683def ProcessTargetFiles(input_tf_zip: zipfile.ZipFile, output_tf_zip: zipfile.ZipFile, misc_info,
684                       apk_keys, apex_keys, key_passwords,
685                       platform_api_level, codename_to_api_level_map,
686                       compressed_extension):
687  # maxsize measures the maximum filename length, including the ones to be
688  # skipped.
689  try:
690    maxsize = max(
691        [len(os.path.basename(i.filename)) for i in input_tf_zip.infolist()
692         if GetApkFileInfo(i.filename, compressed_extension, [])[0]])
693  except ValueError:
694    # Sets this to zero for targets without APK files.
695    maxsize = 0
696
697  # Replace the AVB signing keys, if any.
698  ReplaceAvbSigningKeys(misc_info)
699  OPTIONS.info_dict = misc_info
700
701  # Rewrite the props in AVB signing args.
702  if misc_info.get('avb_enable') == 'true':
703    RewriteAvbProps(misc_info)
704
705  RegenerateKernelPartitions(input_tf_zip, output_tf_zip, misc_info)
706
707  for info in input_tf_zip.infolist():
708    filename = info.filename
709    if filename.startswith("IMAGES/"):
710      continue
711
712    # Skip OTA-specific images (e.g. split super images), which will be
713    # re-generated during signing.
714    if filename.startswith("OTA/") and filename.endswith(".img"):
715      continue
716
717    (is_apk, is_compressed, should_be_skipped) = GetApkFileInfo(
718        filename, compressed_extension, OPTIONS.skip_apks_with_path_prefix)
719    data = input_tf_zip.read(filename)
720    out_info = copy.copy(info)
721
722    if is_apk and should_be_skipped:
723      # Copy skipped APKs verbatim.
724      print(
725          "NOT signing: %s\n"
726          "        (skipped due to matching prefix)" % (filename,))
727      common.ZipWriteStr(output_tf_zip, out_info, data)
728
729    # Sign APKs.
730    elif is_apk:
731      name = os.path.basename(filename)
732      if is_compressed:
733        name = name[:-len(compressed_extension)]
734
735      key = apk_keys[name]
736      if key not in common.SPECIAL_CERT_STRINGS:
737        print("    signing: %-*s (%s)" % (maxsize, name, key))
738        signed_data = SignApk(data, key, key_passwords[key], platform_api_level,
739                              codename_to_api_level_map, is_compressed, name)
740        common.ZipWriteStr(output_tf_zip, out_info, signed_data)
741      else:
742        # an APK we're not supposed to sign.
743        print(
744            "NOT signing: %s\n"
745            "        (skipped due to special cert string)" % (name,))
746        common.ZipWriteStr(output_tf_zip, out_info, data)
747
748    # Sign bundled APEX files on all partitions
749    elif IsApexFile(filename):
750      name = GetApexFilename(filename)
751
752      payload_key, container_key, sign_tool = apex_keys[name]
753
754      # We've asserted not having a case with only one of them PRESIGNED.
755      if (payload_key not in common.SPECIAL_CERT_STRINGS and
756              container_key not in common.SPECIAL_CERT_STRINGS):
757        print("    signing: %-*s container (%s)" % (
758            maxsize, name, container_key))
759        print("           : %-*s payload   (%s)" % (
760            maxsize, name, payload_key))
761
762        signed_apex = apex_utils.SignApex(
763            misc_info['avb_avbtool'],
764            data,
765            payload_key,
766            container_key,
767            key_passwords,
768            apk_keys,
769            codename_to_api_level_map,
770            no_hashtree=None,  # Let apex_util determine if hash tree is needed
771            signing_args=OPTIONS.avb_extra_args.get('apex'),
772            sign_tool=sign_tool)
773        common.ZipWrite(output_tf_zip, signed_apex, filename)
774
775      else:
776        print(
777            "NOT signing: %s\n"
778            "        (skipped due to special cert string)" % (name,))
779        common.ZipWriteStr(output_tf_zip, out_info, data)
780
781    elif filename.endswith(".zip") and IsEntryOtaPackage(input_tf_zip, filename):
782      logger.info("Re-signing OTA package {}".format(filename))
783      with tempfile.NamedTemporaryFile() as input_ota, tempfile.NamedTemporaryFile() as output_ota:
784        RegenerateBootOTA(input_tf_zip, filename, input_ota)
785
786        SignOtaPackage(input_ota.name, output_ota.name)
787        common.ZipWrite(output_tf_zip, output_ota.name, filename,
788                        compress_type=zipfile.ZIP_STORED)
789    # System properties.
790    elif IsBuildPropFile(filename):
791      print("Rewriting %s:" % (filename,))
792      if stat.S_ISLNK(info.external_attr >> 16):
793        new_data = data
794      else:
795        new_data = RewriteProps(data.decode())
796      common.ZipWriteStr(output_tf_zip, out_info, new_data)
797
798    # Replace the certs in *mac_permissions.xml (there could be multiple, such
799    # as {system,vendor}/etc/selinux/{plat,vendor}_mac_permissions.xml).
800    elif filename.endswith("mac_permissions.xml"):
801      print("Rewriting %s with new keys." % (filename,))
802      new_data = ReplaceCerts(data.decode())
803      common.ZipWriteStr(output_tf_zip, out_info, new_data)
804
805    # Ask add_img_to_target_files to rebuild the recovery patch if needed.
806    elif filename in ("SYSTEM/recovery-from-boot.p",
807                      "VENDOR/recovery-from-boot.p",
808
809                      "SYSTEM/etc/recovery.img",
810                      "VENDOR/etc/recovery.img",
811
812                      "SYSTEM/bin/install-recovery.sh",
813                      "VENDOR/bin/install-recovery.sh"):
814      OPTIONS.rebuild_recovery = True
815
816    # Don't copy OTA certs if we're replacing them.
817    # Replacement of update-payload-key.pub.pem was removed in b/116660991.
818    elif OPTIONS.replace_ota_keys and filename.endswith("/otacerts.zip"):
819      pass
820
821    # Skip META/misc_info.txt since we will write back the new values later.
822    elif filename == "META/misc_info.txt":
823      pass
824
825    elif (OPTIONS.remove_avb_public_keys and
826          (filename.startswith("BOOT/RAMDISK/avb/") or
827           filename.startswith("BOOT/RAMDISK/first_stage_ramdisk/avb/"))):
828      matched_removal = False
829      for key_to_remove in OPTIONS.remove_avb_public_keys:
830        if filename.endswith(key_to_remove):
831          matched_removal = True
832          print("Removing AVB public key from ramdisk: %s" % filename)
833          break
834      if not matched_removal:
835        # Copy it verbatim if we don't want to remove it.
836        common.ZipWriteStr(output_tf_zip, out_info, data)
837
838    # Skip the vbmeta digest as we will recalculate it.
839    elif filename == "META/vbmeta_digest.txt":
840      pass
841
842    # Skip the care_map as we will regenerate the system/vendor images.
843    elif filename in ["META/care_map.pb", "META/care_map.txt"]:
844      pass
845
846    # Skip apex_info.pb because we sign/modify apexes
847    elif filename == "META/apex_info.pb":
848      pass
849
850    # Updates system_other.avbpubkey in /product/etc/.
851    elif filename in (
852        "PRODUCT/etc/security/avb/system_other.avbpubkey",
853        "SYSTEM/product/etc/security/avb/system_other.avbpubkey"):
854      # Only update system_other's public key, if the corresponding signing
855      # key is specified via --avb_system_other_key.
856      signing_key = OPTIONS.avb_keys.get("system_other")
857      if signing_key:
858        public_key = common.ExtractAvbPublicKey(
859            misc_info['avb_avbtool'], signing_key)
860        print("    Rewriting AVB public key of system_other in /product")
861        common.ZipWrite(output_tf_zip, public_key, filename)
862
863    # Updates pvmfw embedded public key with the virt APEX payload key.
864    elif filename == "PREBUILT_IMAGES/pvmfw.img":
865      # Find the name of the virt APEX in the target files.
866      namelist = input_tf_zip.namelist()
867      apex_gen = (GetApexFilename(f) for f in namelist if IsApexFile(f))
868      virt_apex_re = re.compile("^com\.([^\.]+\.)?android\.virt\.apex$")
869      virt_apex = next((a for a in apex_gen if virt_apex_re.match(a)), None)
870      if not virt_apex:
871        print("Removing %s from ramdisk: virt APEX not found" % filename)
872      else:
873        print("Replacing %s embedded key with %s key" % (filename, virt_apex))
874        # Get the current and new embedded keys.
875        payload_key, container_key, sign_tool = apex_keys[virt_apex]
876        new_pubkey_path = common.ExtractAvbPublicKey(
877            misc_info['avb_avbtool'], payload_key)
878        with open(new_pubkey_path, 'rb') as f:
879          new_pubkey = f.read()
880        pubkey_info = copy.copy(
881            input_tf_zip.getinfo("PREBUILT_IMAGES/pvmfw_embedded.avbpubkey"))
882        old_pubkey = input_tf_zip.read(pubkey_info.filename)
883        # Validate the keys and image.
884        if len(old_pubkey) != len(new_pubkey):
885          raise common.ExternalError("pvmfw embedded public key size mismatch")
886        pos = data.find(old_pubkey)
887        if pos == -1:
888          raise common.ExternalError("pvmfw embedded public key not found")
889        # Replace the key and copy new files.
890        new_data = data[:pos] + new_pubkey + data[pos+len(old_pubkey):]
891        common.ZipWriteStr(output_tf_zip, out_info, new_data)
892        common.ZipWriteStr(output_tf_zip, pubkey_info, new_pubkey)
893    elif filename == "PREBUILT_IMAGES/pvmfw_embedded.avbpubkey":
894      pass
895
896    # Should NOT sign boot-debug.img.
897    elif filename in (
898        "BOOT/RAMDISK/force_debuggable",
899        "BOOT/RAMDISK/first_stage_ramdisk/force_debuggable"):
900      raise common.ExternalError("debuggable boot.img cannot be signed")
901
902    # Should NOT sign userdebug sepolicy file.
903    elif filename in (
904        "SYSTEM_EXT/etc/selinux/userdebug_plat_sepolicy.cil",
905        "SYSTEM/system_ext/etc/selinux/userdebug_plat_sepolicy.cil"):
906      if not OPTIONS.allow_gsi_debug_sepolicy:
907        raise common.ExternalError("debug sepolicy shouldn't be included")
908      else:
909        # Copy it verbatim if we allow the file to exist.
910        common.ZipWriteStr(output_tf_zip, out_info, data)
911
912    # Sign microdroid_vendor.img.
913    elif filename == "VENDOR/etc/avf/microdroid/microdroid_vendor.img":
914      vendor_key = OPTIONS.avb_keys.get("vendor")
915      vendor_algorithm = OPTIONS.avb_algorithms.get("vendor")
916      with tempfile.NamedTemporaryFile() as image:
917        image.write(data)
918        image.flush()
919        ReplaceKeyInAvbHashtreeFooter(image, vendor_key, vendor_algorithm,
920            misc_info)
921        common.ZipWrite(output_tf_zip, image.name, filename)
922    # A non-APK file; copy it verbatim.
923    else:
924      try:
925        entry = output_tf_zip.getinfo(filename)
926        if output_tf_zip.read(entry) != data:
927          logger.warn(
928              "Output zip contains duplicate entries for %s with different contents", filename)
929        continue
930      except KeyError:
931        common.ZipWriteStr(output_tf_zip, out_info, data)
932
933  if OPTIONS.replace_ota_keys:
934    ReplaceOtaKeys(input_tf_zip, output_tf_zip, misc_info)
935
936
937  # Write back misc_info with the latest values.
938  ReplaceMiscInfoTxt(input_tf_zip, output_tf_zip, misc_info)
939
940# Parse string output of `avbtool info_image`.
941def ParseAvbInfo(info_raw):
942  # line_matcher is for parsing each output line of `avbtool info_image`.
943  # example string input: "      Hash Algorithm:        sha1"
944  # example matched input: ("      ", "Hash Algorithm", "sha1")
945  line_matcher = re.compile(r'^(\s*)([^:]+):\s*(.*)$')
946  # prop_matcher is for parsing value part of 'Prop' in `avbtool info_image`.
947  # example string input: "example_prop_key -> 'example_prop_value'"
948  # example matched output: ("example_prop_key", "example_prop_value")
949  prop_matcher = re.compile(r"(.+)\s->\s'(.+)'")
950  info = {}
951  indent_stack = [[-1, info]]
952  for line_info_raw in info_raw.split('\n'):
953    # Parse the line
954    line_info_parsed = line_matcher.match(line_info_raw)
955    if not line_info_parsed:
956      continue
957    indent = len(line_info_parsed.group(1))
958    key = line_info_parsed.group(2).strip()
959    value = line_info_parsed.group(3).strip()
960
961    # Pop indentation stack
962    while indent <= indent_stack[-1][0]:
963      del indent_stack[-1]
964
965    # Insert information into 'info'.
966    cur_info = indent_stack[-1][1]
967    if value == "":
968      if key == "Descriptors":
969        empty_list = []
970        cur_info[key] = empty_list
971        indent_stack.append([indent, empty_list])
972      else:
973        empty_dict = {}
974        cur_info.append({key:empty_dict})
975        indent_stack.append([indent, empty_dict])
976    elif key == "Prop":
977      prop_parsed = prop_matcher.match(value)
978      if not prop_parsed:
979        raise ValueError(
980            "Failed to parse prop while getting avb information.")
981      cur_info.append({key:{prop_parsed.group(1):prop_parsed.group(2)}})
982    else:
983      cur_info[key] = value
984  return info
985
986def ReplaceKeyInAvbHashtreeFooter(image, new_key, new_algorithm, misc_info):
987  # Get avb information about the image by parsing avbtool info_image.
988  def GetAvbInfo(avbtool, image_name):
989    # Get information with raw string by `avbtool info_image`.
990    info_raw = common.RunAndCheckOutput([
991      avbtool, 'info_image',
992      '--image', image_name
993    ])
994    return ParseAvbInfo(info_raw)
995
996  # Get hashtree descriptor from info
997  def GetAvbHashtreeDescriptor(avb_info):
998    hashtree_descriptors = tuple(filter(lambda x: "Hashtree descriptor" in x,
999        info.get('Descriptors')))
1000    if len(hashtree_descriptors) != 1:
1001      raise ValueError("The number of hashtree descriptor is not 1.")
1002    return hashtree_descriptors[0]["Hashtree descriptor"]
1003
1004  # Get avb info
1005  avbtool = misc_info['avb_avbtool']
1006  info = GetAvbInfo(avbtool, image.name)
1007  hashtree_descriptor = GetAvbHashtreeDescriptor(info)
1008
1009  # Generate command
1010  cmd = [avbtool, 'add_hashtree_footer',
1011    '--key', new_key,
1012    '--algorithm', new_algorithm,
1013    '--partition_name', hashtree_descriptor.get("Partition Name"),
1014    '--partition_size', info.get("Image size").removesuffix(" bytes"),
1015    '--hash_algorithm', hashtree_descriptor.get("Hash Algorithm"),
1016    '--salt', hashtree_descriptor.get("Salt"),
1017    '--do_not_generate_fec',
1018    '--image', image.name
1019  ]
1020
1021  # Append properties into command
1022  props = map(lambda x: x.get("Prop"), filter(lambda x: "Prop" in x,
1023      info.get('Descriptors')))
1024  for prop_wrapped in props:
1025    prop = tuple(prop_wrapped.items())
1026    if len(prop) != 1:
1027      raise ValueError("The number of property is not 1.")
1028    cmd.append('--prop')
1029    cmd.append(prop[0][0] + ':' + prop[0][1])
1030
1031  # Replace Hashtree Footer with new key
1032  common.RunAndCheckOutput(cmd)
1033
1034  # Check root digest is not changed
1035  new_info = GetAvbInfo(avbtool, image.name)
1036  new_hashtree_descriptor = GetAvbHashtreeDescriptor(info)
1037  root_digest = hashtree_descriptor.get("Root Digest")
1038  new_root_digest = new_hashtree_descriptor.get("Root Digest")
1039  assert root_digest == new_root_digest, \
1040      ("Root digest in hashtree descriptor shouldn't be changed. Old: {}, New: "
1041       "{}").format(root_digest, new_root_digest)
1042
1043def ReplaceCerts(data):
1044  """Replaces all the occurences of X.509 certs with the new ones.
1045
1046  The mapping info is read from OPTIONS.key_map. Non-existent certificate will
1047  be skipped. After the replacement, it additionally checks for duplicate
1048  entries, which would otherwise fail the policy loading code in
1049  frameworks/base/services/core/java/com/android/server/pm/SELinuxMMAC.java.
1050
1051  Args:
1052    data: Input string that contains a set of X.509 certs.
1053
1054  Returns:
1055    A string after the replacement.
1056
1057  Raises:
1058    AssertionError: On finding duplicate entries.
1059  """
1060  for old, new in OPTIONS.key_map.items():
1061    if OPTIONS.verbose:
1062      print("    Replacing %s.x509.pem with %s.x509.pem" % (old, new))
1063
1064    try:
1065      with open(old + ".x509.pem") as old_fp:
1066        old_cert16 = base64.b16encode(
1067            common.ParseCertificate(old_fp.read())).decode().lower()
1068      with open(new + ".x509.pem") as new_fp:
1069        new_cert16 = base64.b16encode(
1070            common.ParseCertificate(new_fp.read())).decode().lower()
1071    except IOError as e:
1072      if OPTIONS.verbose or e.errno != errno.ENOENT:
1073        print("    Error accessing %s: %s.\nSkip replacing %s.x509.pem with "
1074              "%s.x509.pem." % (e.filename, e.strerror, old, new))
1075      continue
1076
1077    # Only match entire certs.
1078    pattern = "\\b" + old_cert16 + "\\b"
1079    (data, num) = re.subn(pattern, new_cert16, data, flags=re.IGNORECASE)
1080
1081    if OPTIONS.verbose:
1082      print("    Replaced %d occurence(s) of %s.x509.pem with %s.x509.pem" % (
1083          num, old, new))
1084
1085  # Verify that there're no duplicate entries after the replacement. Note that
1086  # it's only checking entries with global seinfo at the moment (i.e. ignoring
1087  # the ones with inner packages). (Bug: 69479366)
1088  root = ElementTree.fromstring(data)
1089  signatures = [signer.attrib['signature']
1090                for signer in root.findall('signer')]
1091  assert len(signatures) == len(set(signatures)), \
1092      "Found duplicate entries after cert replacement: {}".format(data)
1093
1094  return data
1095
1096
1097def EditTags(tags):
1098  """Applies the edits to the tag string as specified in OPTIONS.tag_changes.
1099
1100  Args:
1101    tags: The input string that contains comma-separated tags.
1102
1103  Returns:
1104    The updated tags (comma-separated and sorted).
1105  """
1106  tags = set(tags.split(","))
1107  for ch in OPTIONS.tag_changes:
1108    if ch[0] == "-":
1109      tags.discard(ch[1:])
1110    elif ch[0] == "+":
1111      tags.add(ch[1:])
1112  return ",".join(sorted(tags))
1113
1114
1115def RewriteProps(data):
1116  """Rewrites the system properties in the given string.
1117
1118  Each property is expected in 'key=value' format. The properties that contain
1119  build tags (i.e. test-keys, dev-keys) will be updated accordingly by calling
1120  EditTags().
1121
1122  Args:
1123    data: Input string, separated by newlines.
1124
1125  Returns:
1126    The string with modified properties.
1127  """
1128  output = []
1129  for line in data.split("\n"):
1130    line = line.strip()
1131    original_line = line
1132    if line and line[0] != '#' and "=" in line:
1133      key, value = line.split("=", 1)
1134      if (key.startswith("ro.") and
1135              key.endswith((".build.fingerprint", ".build.thumbprint"))):
1136        pieces = value.split("/")
1137        pieces[-1] = EditTags(pieces[-1])
1138        value = "/".join(pieces)
1139      elif key == "ro.bootimage.build.fingerprint":
1140        pieces = value.split("/")
1141        pieces[-1] = EditTags(pieces[-1])
1142        value = "/".join(pieces)
1143      elif key == "ro.build.description":
1144        pieces = value.split()
1145        assert pieces[-1].endswith("-keys")
1146        pieces[-1] = EditTags(pieces[-1])
1147        value = " ".join(pieces)
1148      elif key.startswith("ro.") and key.endswith(".build.tags"):
1149        value = EditTags(value)
1150      elif key == "ro.build.display.id":
1151        # change, eg, "JWR66N dev-keys" to "JWR66N"
1152        value = value.split()
1153        if len(value) > 1 and value[-1].endswith("-keys"):
1154          value.pop()
1155        value = " ".join(value)
1156      line = key + "=" + value
1157    if line != original_line:
1158      print("  replace: ", original_line)
1159      print("     with: ", line)
1160    output.append(line)
1161  return "\n".join(output) + "\n"
1162
1163
1164def WriteOtacerts(output_zip, filename, keys):
1165  """Constructs a zipfile from given keys; and writes it to output_zip.
1166
1167  Args:
1168    output_zip: The output target_files zip.
1169    filename: The archive name in the output zip.
1170    keys: A list of public keys to use during OTA package verification.
1171  """
1172  temp_file = io.BytesIO()
1173  certs_zip = zipfile.ZipFile(temp_file, "w", allowZip64=True)
1174  for k in keys:
1175    common.ZipWrite(certs_zip, k)
1176  common.ZipClose(certs_zip)
1177  common.ZipWriteStr(output_zip, filename, temp_file.getvalue())
1178
1179
1180def ReplaceOtaKeys(input_tf_zip: zipfile.ZipFile, output_tf_zip, misc_info):
1181  try:
1182    keylist = input_tf_zip.read("META/otakeys.txt").decode().split()
1183  except KeyError:
1184    raise common.ExternalError("can't read META/otakeys.txt from input")
1185
1186  extra_ota_keys_info = misc_info.get("extra_ota_keys")
1187  if extra_ota_keys_info:
1188    extra_ota_keys = [OPTIONS.key_map.get(k, k) + ".x509.pem"
1189                      for k in extra_ota_keys_info.split()]
1190    print("extra ota key(s): " + ", ".join(extra_ota_keys))
1191  else:
1192    extra_ota_keys = []
1193  for k in extra_ota_keys:
1194    if not os.path.isfile(k):
1195      raise common.ExternalError(k + " does not exist or is not a file")
1196
1197  extra_recovery_keys_info = misc_info.get("extra_recovery_keys")
1198  if extra_recovery_keys_info:
1199    extra_recovery_keys = [OPTIONS.key_map.get(k, k) + ".x509.pem"
1200                           for k in extra_recovery_keys_info.split()]
1201    print("extra recovery-only key(s): " + ", ".join(extra_recovery_keys))
1202  else:
1203    extra_recovery_keys = []
1204  for k in extra_recovery_keys:
1205    if not os.path.isfile(k):
1206      raise common.ExternalError(k + " does not exist or is not a file")
1207
1208  mapped_keys = []
1209  for k in keylist:
1210    m = re.match(r"^(.*)\.x509\.pem$", k)
1211    if not m:
1212      raise common.ExternalError(
1213          "can't parse \"%s\" from META/otakeys.txt" % (k,))
1214    k = m.group(1)
1215    mapped_keys.append(OPTIONS.key_map.get(k, k) + ".x509.pem")
1216
1217  if mapped_keys:
1218    print("using:\n   ", "\n   ".join(mapped_keys))
1219    print("for OTA package verification")
1220  else:
1221    devkey = misc_info.get("default_system_dev_certificate",
1222                           "build/make/target/product/security/testkey")
1223    mapped_devkey = OPTIONS.key_map.get(devkey, devkey)
1224    if mapped_devkey != devkey:
1225      misc_info["default_system_dev_certificate"] = mapped_devkey
1226    mapped_keys.append(mapped_devkey + ".x509.pem")
1227    print("META/otakeys.txt has no keys; using %s for OTA package"
1228          " verification." % (mapped_keys[0],))
1229  for k in mapped_keys:
1230    if not os.path.isfile(k):
1231      raise common.ExternalError(k + " does not exist or is not a file")
1232
1233  otacerts = [info
1234              for info in input_tf_zip.infolist()
1235              if info.filename.endswith("/otacerts.zip")]
1236  for info in otacerts:
1237    if info.filename.startswith(("BOOT/", "RECOVERY/", "VENDOR_BOOT/")):
1238      extra_keys = extra_recovery_keys
1239    else:
1240      extra_keys = extra_ota_keys
1241    print("Rewriting OTA key:", info.filename, mapped_keys + extra_keys)
1242    WriteOtacerts(output_tf_zip, info.filename, mapped_keys + extra_keys)
1243
1244
1245def ReplaceMiscInfoTxt(input_zip, output_zip, misc_info):
1246  """Replaces META/misc_info.txt.
1247
1248  Only writes back the ones in the original META/misc_info.txt. Because the
1249  current in-memory dict contains additional items computed at runtime.
1250  """
1251  misc_info_old = common.LoadDictionaryFromLines(
1252      input_zip.read('META/misc_info.txt').decode().split('\n'))
1253  items = []
1254  for key in sorted(misc_info):
1255    if key in misc_info_old:
1256      items.append('%s=%s' % (key, misc_info[key]))
1257  common.ZipWriteStr(output_zip, "META/misc_info.txt", '\n'.join(items))
1258
1259
1260def ReplaceAvbSigningKeys(misc_info):
1261  """Replaces the AVB signing keys."""
1262
1263  def ReplaceAvbPartitionSigningKey(partition):
1264    key = OPTIONS.avb_keys.get(partition)
1265    if not key:
1266      return
1267
1268    algorithm = OPTIONS.avb_algorithms.get(partition)
1269    assert algorithm, 'Missing AVB signing algorithm for %s' % (partition,)
1270
1271    print('Replacing AVB signing key for %s with "%s" (%s)' % (
1272        partition, key, algorithm))
1273    misc_info['avb_' + partition + '_algorithm'] = algorithm
1274    misc_info['avb_' + partition + '_key_path'] = key
1275
1276    extra_args = OPTIONS.avb_extra_args.get(partition)
1277    if extra_args:
1278      print('Setting extra AVB signing args for %s to "%s"' % (
1279          partition, extra_args))
1280      args_key = AVB_FOOTER_ARGS_BY_PARTITION.get(
1281          partition,
1282          # custom partition
1283          "avb_{}_add_hashtree_footer_args".format(partition))
1284      misc_info[args_key] = (misc_info.get(args_key, '') + ' ' + extra_args)
1285
1286  for partition in AVB_FOOTER_ARGS_BY_PARTITION:
1287    ReplaceAvbPartitionSigningKey(partition)
1288
1289  for custom_partition in misc_info.get(
1290          "avb_custom_images_partition_list", "").strip().split():
1291    ReplaceAvbPartitionSigningKey(custom_partition)
1292
1293
1294def RewriteAvbProps(misc_info):
1295  """Rewrites the props in AVB signing args."""
1296  for partition, args_key in AVB_FOOTER_ARGS_BY_PARTITION.items():
1297    args = misc_info.get(args_key)
1298    if not args:
1299      continue
1300
1301    tokens = []
1302    changed = False
1303    for token in args.split():
1304      fingerprint_key = 'com.android.build.{}.fingerprint'.format(partition)
1305      if not token.startswith(fingerprint_key):
1306        tokens.append(token)
1307        continue
1308      prefix, tag = token.rsplit('/', 1)
1309      tokens.append('{}/{}'.format(prefix, EditTags(tag)))
1310      changed = True
1311
1312    if changed:
1313      result = ' '.join(tokens)
1314      print('Rewriting AVB prop for {}:\n'.format(partition))
1315      print('  replace: {}'.format(args))
1316      print('     with: {}'.format(result))
1317      misc_info[args_key] = result
1318
1319
1320def BuildKeyMap(misc_info, key_mapping_options):
1321  for s, d in key_mapping_options:
1322    if s is None:   # -d option
1323      devkey = misc_info.get("default_system_dev_certificate",
1324                             "build/make/target/product/security/testkey")
1325      devkeydir = os.path.dirname(devkey)
1326
1327      OPTIONS.key_map.update({
1328          devkeydir + "/testkey":  d + "/releasekey",
1329          devkeydir + "/devkey":   d + "/releasekey",
1330          devkeydir + "/media":    d + "/media",
1331          devkeydir + "/shared":   d + "/shared",
1332          devkeydir + "/platform": d + "/platform",
1333          devkeydir + "/networkstack": d + "/networkstack",
1334          devkeydir + "/sdk_sandbox": d + "/sdk_sandbox",
1335      })
1336    else:
1337      OPTIONS.key_map[s] = d
1338
1339
1340def GetApiLevelAndCodename(input_tf_zip):
1341  data = input_tf_zip.read("SYSTEM/build.prop").decode()
1342  api_level = None
1343  codename = None
1344  for line in data.split("\n"):
1345    line = line.strip()
1346    if line and line[0] != '#' and "=" in line:
1347      key, value = line.split("=", 1)
1348      key = key.strip()
1349      if key == "ro.build.version.sdk":
1350        api_level = int(value.strip())
1351      elif key == "ro.build.version.codename":
1352        codename = value.strip()
1353
1354  if api_level is None:
1355    raise ValueError("No ro.build.version.sdk in SYSTEM/build.prop")
1356  if codename is None:
1357    raise ValueError("No ro.build.version.codename in SYSTEM/build.prop")
1358
1359  return (api_level, codename)
1360
1361
1362def GetCodenameToApiLevelMap(input_tf_zip):
1363  data = input_tf_zip.read("SYSTEM/build.prop").decode()
1364  api_level = None
1365  codenames = None
1366  for line in data.split("\n"):
1367    line = line.strip()
1368    if line and line[0] != '#' and "=" in line:
1369      key, value = line.split("=", 1)
1370      key = key.strip()
1371      if key == "ro.build.version.sdk":
1372        api_level = int(value.strip())
1373      elif key == "ro.build.version.all_codenames":
1374        codenames = value.strip().split(",")
1375
1376  if api_level is None:
1377    raise ValueError("No ro.build.version.sdk in SYSTEM/build.prop")
1378  if codenames is None:
1379    raise ValueError("No ro.build.version.all_codenames in SYSTEM/build.prop")
1380
1381  result = {}
1382  for codename in codenames:
1383    codename = codename.strip()
1384    if codename:
1385      result[codename] = api_level
1386  return result
1387
1388
1389def ReadApexKeysInfo(tf_zip):
1390  """Parses the APEX keys info from a given target-files zip.
1391
1392  Given a target-files ZipFile, parses the META/apexkeys.txt entry and returns a
1393  dict that contains the mapping from APEX names (e.g. com.android.tzdata) to a
1394  tuple of (payload_key, container_key, sign_tool).
1395
1396  Args:
1397    tf_zip: The input target_files ZipFile (already open).
1398
1399  Returns:
1400    (payload_key, container_key, sign_tool):
1401      - payload_key contains the path to the payload signing key
1402      - container_key contains the path to the container signing key
1403      - sign_tool is an apex-specific signing tool for its payload contents
1404  """
1405  keys = {}
1406  for line in tf_zip.read('META/apexkeys.txt').decode().split('\n'):
1407    line = line.strip()
1408    if not line:
1409      continue
1410    matches = re.match(
1411        r'^name="(?P<NAME>.*)"\s+'
1412        r'public_key="(?P<PAYLOAD_PUBLIC_KEY>.*)"\s+'
1413        r'private_key="(?P<PAYLOAD_PRIVATE_KEY>.*)"\s+'
1414        r'container_certificate="(?P<CONTAINER_CERT>.*)"\s+'
1415        r'container_private_key="(?P<CONTAINER_PRIVATE_KEY>.*?)"'
1416        r'(\s+partition="(?P<PARTITION>.*?)")?'
1417        r'(\s+sign_tool="(?P<SIGN_TOOL>.*?)")?$',
1418        line)
1419    if not matches:
1420      continue
1421
1422    name = matches.group('NAME')
1423    payload_private_key = matches.group("PAYLOAD_PRIVATE_KEY")
1424
1425    def CompareKeys(pubkey, pubkey_suffix, privkey, privkey_suffix):
1426      pubkey_suffix_len = len(pubkey_suffix)
1427      privkey_suffix_len = len(privkey_suffix)
1428      return (pubkey.endswith(pubkey_suffix) and
1429              privkey.endswith(privkey_suffix) and
1430              pubkey[:-pubkey_suffix_len] == privkey[:-privkey_suffix_len])
1431
1432    # Check the container key names, as we'll carry them without the
1433    # extensions. This doesn't apply to payload keys though, which we will use
1434    # full names only.
1435    container_cert = matches.group("CONTAINER_CERT")
1436    container_private_key = matches.group("CONTAINER_PRIVATE_KEY")
1437    if container_cert == 'PRESIGNED' and container_private_key == 'PRESIGNED':
1438      container_key = 'PRESIGNED'
1439    elif CompareKeys(
1440            container_cert, OPTIONS.public_key_suffix,
1441            container_private_key, OPTIONS.private_key_suffix):
1442      container_key = container_cert[:-len(OPTIONS.public_key_suffix)]
1443    else:
1444      raise ValueError("Failed to parse container keys: \n{}".format(line))
1445
1446    sign_tool = matches.group("SIGN_TOOL")
1447    keys[name] = (payload_private_key, container_key, sign_tool)
1448
1449  return keys
1450
1451
1452def BuildVendorPartitions(output_zip_path):
1453  """Builds OPTIONS.vendor_partitions using OPTIONS.vendor_otatools."""
1454  if OPTIONS.vendor_partitions.difference(ALLOWED_VENDOR_PARTITIONS):
1455    logger.warning("Allowed --vendor_partitions: %s",
1456                   ",".join(ALLOWED_VENDOR_PARTITIONS))
1457    OPTIONS.vendor_partitions = ALLOWED_VENDOR_PARTITIONS.intersection(
1458        OPTIONS.vendor_partitions)
1459
1460  logger.info("Building vendor partitions using vendor otatools.")
1461  vendor_tempdir = common.UnzipTemp(output_zip_path, [
1462      "META/*",
1463      "SYSTEM/build.prop",
1464      "RECOVERY/*",
1465      "BOOT/*",
1466      "OTA/",
1467  ] + ["{}/*".format(p.upper()) for p in OPTIONS.vendor_partitions])
1468
1469  # Disable various partitions that build based on misc_info fields.
1470  # Only partitions in ALLOWED_VENDOR_PARTITIONS can be rebuilt using
1471  # vendor otatools. These other partitions will be rebuilt using the main
1472  # otatools if necessary.
1473  vendor_misc_info_path = os.path.join(vendor_tempdir, "META/misc_info.txt")
1474  vendor_misc_info = common.LoadDictionaryFromFile(vendor_misc_info_path)
1475  # Ignore if not rebuilding recovery
1476  if not OPTIONS.rebuild_recovery:
1477    vendor_misc_info["no_boot"] = "true"  # boot
1478    vendor_misc_info["vendor_boot"] = "false"  # vendor_boot
1479    vendor_misc_info["no_recovery"] = "true"  # recovery
1480    vendor_misc_info["avb_enable"] = "false"  # vbmeta
1481
1482  vendor_misc_info["has_dtbo"] = "false"  # dtbo
1483  vendor_misc_info["has_pvmfw"] = "false"  # pvmfw
1484  vendor_misc_info["avb_custom_images_partition_list"] = ""  # avb custom images
1485  vendor_misc_info["avb_building_vbmeta_image"] = "false" # skip building vbmeta
1486  vendor_misc_info["custom_images_partition_list"] = ""  # custom images
1487  vendor_misc_info["use_dynamic_partitions"] = "false"  # super_empty
1488  vendor_misc_info["build_super_partition"] = "false"  # super split
1489  vendor_misc_info["avb_vbmeta_system"] = ""  # skip building vbmeta_system
1490  with open(vendor_misc_info_path, "w") as output:
1491    for key in sorted(vendor_misc_info):
1492      output.write("{}={}\n".format(key, vendor_misc_info[key]))
1493
1494  # Disable system partition by a placeholder of IMAGES/system.img,
1495  # instead of removing SYSTEM folder.
1496  # Because SYSTEM/build.prop is still needed for:
1497  #   add_img_to_target_files.CreateImage ->
1498  #   common.BuildInfo ->
1499  #   common.BuildInfo.CalculateFingerprint
1500  vendor_images_path = os.path.join(vendor_tempdir, "IMAGES")
1501  if not os.path.exists(vendor_images_path):
1502    os.makedirs(vendor_images_path)
1503  with open(os.path.join(vendor_images_path, "system.img"), "w") as output:
1504    pass
1505
1506  # Disable care_map.pb as not all ab_partitions are available when
1507  # vendor otatools regenerates vendor images.
1508  if os.path.exists(os.path.join(vendor_tempdir, "META/ab_partitions.txt")):
1509    os.remove(os.path.join(vendor_tempdir, "META/ab_partitions.txt"))
1510  # Disable RADIO images
1511  if os.path.exists(os.path.join(vendor_tempdir, "META/pack_radioimages.txt")):
1512    os.remove(os.path.join(vendor_tempdir, "META/pack_radioimages.txt"))
1513
1514  # Build vendor images using vendor otatools.
1515  # Accept either a zip file or extracted directory.
1516  if os.path.isfile(OPTIONS.vendor_otatools):
1517    vendor_otatools_dir = common.MakeTempDir(prefix="vendor_otatools_")
1518    common.UnzipToDir(OPTIONS.vendor_otatools, vendor_otatools_dir)
1519  else:
1520    vendor_otatools_dir = OPTIONS.vendor_otatools
1521  cmd = [
1522      os.path.join(vendor_otatools_dir, "bin", "add_img_to_target_files"),
1523      "--is_signing",
1524      "--add_missing",
1525      "--verbose",
1526      vendor_tempdir,
1527  ]
1528  if OPTIONS.rebuild_recovery:
1529    cmd.insert(4, "--rebuild_recovery")
1530
1531  common.RunAndCheckOutput(cmd, verbose=True)
1532
1533  logger.info("Writing vendor partitions to output archive.")
1534  with zipfile.ZipFile(
1535      output_zip_path, "a", compression=zipfile.ZIP_DEFLATED,
1536      allowZip64=True) as output_zip:
1537    for p in OPTIONS.vendor_partitions:
1538      img_file_path = "IMAGES/{}.img".format(p)
1539      map_file_path = "IMAGES/{}.map".format(p)
1540      common.ZipWrite(output_zip, os.path.join(vendor_tempdir, img_file_path), img_file_path)
1541      if os.path.exists(os.path.join(vendor_tempdir, map_file_path)):
1542        common.ZipWrite(output_zip, os.path.join(vendor_tempdir, map_file_path), map_file_path)
1543    # copy recovery.img, boot.img, recovery patch & install.sh
1544    if OPTIONS.rebuild_recovery:
1545      recovery_img = "IMAGES/recovery.img"
1546      boot_img = "IMAGES/boot.img"
1547      common.ZipWrite(output_zip, os.path.join(vendor_tempdir, recovery_img), recovery_img)
1548      common.ZipWrite(output_zip, os.path.join(vendor_tempdir, boot_img), boot_img)
1549      recovery_patch_path = "VENDOR/recovery-from-boot.p"
1550      recovery_sh_path = "VENDOR/bin/install-recovery.sh"
1551      common.ZipWrite(output_zip, os.path.join(vendor_tempdir, recovery_patch_path), recovery_patch_path)
1552      common.ZipWrite(output_zip, os.path.join(vendor_tempdir, recovery_sh_path), recovery_sh_path)
1553
1554
1555def main(argv):
1556
1557  key_mapping_options = []
1558
1559  def option_handler(o, a):
1560    if o in ("-e", "--extra_apks"):
1561      names, key = a.split("=")
1562      names = names.split(",")
1563      for n in names:
1564        OPTIONS.extra_apks[n] = key
1565    elif o == "--extra_apex_payload_key":
1566      apex_names, key = a.split("=")
1567      for name in apex_names.split(","):
1568        OPTIONS.extra_apex_payload_keys[name] = key
1569    elif o == "--skip_apks_with_path_prefix":
1570      # Check the prefix, which must be in all upper case.
1571      prefix = a.split('/')[0]
1572      if not prefix or prefix != prefix.upper():
1573        raise ValueError("Invalid path prefix '%s'" % (a,))
1574      OPTIONS.skip_apks_with_path_prefix.add(a)
1575    elif o in ("-d", "--default_key_mappings"):
1576      key_mapping_options.append((None, a))
1577    elif o in ("-k", "--key_mapping"):
1578      key_mapping_options.append(a.split("=", 1))
1579    elif o in ("-o", "--replace_ota_keys"):
1580      OPTIONS.replace_ota_keys = True
1581    elif o in ("-t", "--tag_changes"):
1582      new = []
1583      for i in a.split(","):
1584        i = i.strip()
1585        if not i or i[0] not in "-+":
1586          raise ValueError("Bad tag change '%s'" % (i,))
1587        new.append(i[0] + i[1:].strip())
1588      OPTIONS.tag_changes = tuple(new)
1589    elif o == "--replace_verity_public_key":
1590      raise ValueError("--replace_verity_public_key is no longer supported,"
1591                       " please switch to AVB")
1592    elif o == "--replace_verity_private_key":
1593      raise ValueError("--replace_verity_private_key is no longer supported,"
1594                       " please switch to AVB")
1595    elif o == "--replace_verity_keyid":
1596      raise ValueError("--replace_verity_keyid is no longer supported, please"
1597                       " switch to AVB")
1598    elif o == "--remove_avb_public_keys":
1599      OPTIONS.remove_avb_public_keys = a.split(",")
1600    elif o == "--avb_vbmeta_key":
1601      OPTIONS.avb_keys['vbmeta'] = a
1602    elif o == "--avb_vbmeta_algorithm":
1603      OPTIONS.avb_algorithms['vbmeta'] = a
1604    elif o == "--avb_vbmeta_extra_args":
1605      OPTIONS.avb_extra_args['vbmeta'] = a
1606    elif o == "--avb_boot_key":
1607      OPTIONS.avb_keys['boot'] = a
1608    elif o == "--avb_boot_algorithm":
1609      OPTIONS.avb_algorithms['boot'] = a
1610    elif o == "--avb_boot_extra_args":
1611      OPTIONS.avb_extra_args['boot'] = a
1612    elif o == "--avb_dtbo_key":
1613      OPTIONS.avb_keys['dtbo'] = a
1614    elif o == "--avb_dtbo_algorithm":
1615      OPTIONS.avb_algorithms['dtbo'] = a
1616    elif o == "--avb_dtbo_extra_args":
1617      OPTIONS.avb_extra_args['dtbo'] = a
1618    elif o == "--avb_init_boot_key":
1619      OPTIONS.avb_keys['init_boot'] = a
1620    elif o == "--avb_init_boot_algorithm":
1621      OPTIONS.avb_algorithms['init_boot'] = a
1622    elif o == "--avb_init_boot_extra_args":
1623      OPTIONS.avb_extra_args['init_boot'] = a
1624    elif o == "--avb_recovery_key":
1625      OPTIONS.avb_keys['recovery'] = a
1626    elif o == "--avb_recovery_algorithm":
1627      OPTIONS.avb_algorithms['recovery'] = a
1628    elif o == "--avb_recovery_extra_args":
1629      OPTIONS.avb_extra_args['recovery'] = a
1630    elif o == "--avb_system_key":
1631      OPTIONS.avb_keys['system'] = a
1632    elif o == "--avb_system_algorithm":
1633      OPTIONS.avb_algorithms['system'] = a
1634    elif o == "--avb_system_extra_args":
1635      OPTIONS.avb_extra_args['system'] = a
1636    elif o == "--avb_system_other_key":
1637      OPTIONS.avb_keys['system_other'] = a
1638    elif o == "--avb_system_other_algorithm":
1639      OPTIONS.avb_algorithms['system_other'] = a
1640    elif o == "--avb_system_other_extra_args":
1641      OPTIONS.avb_extra_args['system_other'] = a
1642    elif o == "--avb_vendor_key":
1643      OPTIONS.avb_keys['vendor'] = a
1644    elif o == "--avb_vendor_algorithm":
1645      OPTIONS.avb_algorithms['vendor'] = a
1646    elif o == "--avb_vendor_extra_args":
1647      OPTIONS.avb_extra_args['vendor'] = a
1648    elif o == "--avb_vbmeta_system_key":
1649      OPTIONS.avb_keys['vbmeta_system'] = a
1650    elif o == "--avb_vbmeta_system_algorithm":
1651      OPTIONS.avb_algorithms['vbmeta_system'] = a
1652    elif o == "--avb_vbmeta_system_extra_args":
1653      OPTIONS.avb_extra_args['vbmeta_system'] = a
1654    elif o == "--avb_vbmeta_vendor_key":
1655      OPTIONS.avb_keys['vbmeta_vendor'] = a
1656    elif o == "--avb_vbmeta_vendor_algorithm":
1657      OPTIONS.avb_algorithms['vbmeta_vendor'] = a
1658    elif o == "--avb_vbmeta_vendor_extra_args":
1659      OPTIONS.avb_extra_args['vbmeta_vendor'] = a
1660    elif o == "--avb_apex_extra_args":
1661      OPTIONS.avb_extra_args['apex'] = a
1662    elif o == "--avb_extra_custom_image_key":
1663      partition, key = a.split("=")
1664      OPTIONS.avb_keys[partition] = key
1665    elif o == "--avb_extra_custom_image_algorithm":
1666      partition, algorithm = a.split("=")
1667      OPTIONS.avb_algorithms[partition] = algorithm
1668    elif o == "--avb_extra_custom_image_extra_args":
1669      # Setting the maxsplit parameter to one, which will return a list with
1670      # two elements. e.g., the second '=' should not be splitted for
1671      # 'oem=--signing_helper_with_files=/tmp/avbsigner.sh'.
1672      partition, extra_args = a.split("=", 1)
1673      OPTIONS.avb_extra_args[partition] = extra_args
1674    elif o == "--vendor_otatools":
1675      OPTIONS.vendor_otatools = a
1676    elif o == "--vendor_partitions":
1677      OPTIONS.vendor_partitions = set(a.split(","))
1678    elif o == "--allow_gsi_debug_sepolicy":
1679      OPTIONS.allow_gsi_debug_sepolicy = True
1680    elif o == "--override_apk_keys":
1681      OPTIONS.override_apk_keys = a
1682    elif o == "--override_apex_keys":
1683      OPTIONS.override_apex_keys = a
1684    elif o in ("--gki_signing_key",  "--gki_signing_algorithm",  "--gki_signing_extra_args"):
1685      print(f"{o} is deprecated and does nothing")
1686    else:
1687      return False
1688    return True
1689
1690  args = common.ParseOptions(
1691      argv, __doc__,
1692      extra_opts="e:d:k:ot:",
1693      extra_long_opts=[
1694          "extra_apks=",
1695          "extra_apex_payload_key=",
1696          "skip_apks_with_path_prefix=",
1697          "default_key_mappings=",
1698          "key_mapping=",
1699          "replace_ota_keys",
1700          "tag_changes=",
1701          "replace_verity_public_key=",
1702          "replace_verity_private_key=",
1703          "replace_verity_keyid=",
1704          "remove_avb_public_keys=",
1705          "avb_apex_extra_args=",
1706          "avb_vbmeta_algorithm=",
1707          "avb_vbmeta_key=",
1708          "avb_vbmeta_extra_args=",
1709          "avb_boot_algorithm=",
1710          "avb_boot_key=",
1711          "avb_boot_extra_args=",
1712          "avb_dtbo_algorithm=",
1713          "avb_dtbo_key=",
1714          "avb_dtbo_extra_args=",
1715          "avb_init_boot_algorithm=",
1716          "avb_init_boot_key=",
1717          "avb_init_boot_extra_args=",
1718          "avb_recovery_algorithm=",
1719          "avb_recovery_key=",
1720          "avb_recovery_extra_args=",
1721          "avb_system_algorithm=",
1722          "avb_system_key=",
1723          "avb_system_extra_args=",
1724          "avb_system_other_algorithm=",
1725          "avb_system_other_key=",
1726          "avb_system_other_extra_args=",
1727          "avb_vendor_algorithm=",
1728          "avb_vendor_key=",
1729          "avb_vendor_extra_args=",
1730          "avb_vbmeta_system_algorithm=",
1731          "avb_vbmeta_system_key=",
1732          "avb_vbmeta_system_extra_args=",
1733          "avb_vbmeta_vendor_algorithm=",
1734          "avb_vbmeta_vendor_key=",
1735          "avb_vbmeta_vendor_extra_args=",
1736          "avb_extra_custom_image_key=",
1737          "avb_extra_custom_image_algorithm=",
1738          "avb_extra_custom_image_extra_args=",
1739          "gki_signing_key=",
1740          "gki_signing_algorithm=",
1741          "gki_signing_extra_args=",
1742          "vendor_partitions=",
1743          "vendor_otatools=",
1744          "allow_gsi_debug_sepolicy",
1745          "override_apk_keys=",
1746          "override_apex_keys=",
1747      ],
1748      extra_option_handler=[option_handler, payload_signer.signer_options])
1749
1750  if len(args) != 2:
1751    common.Usage(__doc__)
1752    sys.exit(1)
1753
1754  common.InitLogging()
1755
1756  input_zip = zipfile.ZipFile(args[0], "r", allowZip64=True)
1757  output_zip = zipfile.ZipFile(args[1], "w",
1758                               compression=zipfile.ZIP_DEFLATED,
1759                               allowZip64=True)
1760
1761  misc_info = common.LoadInfoDict(input_zip)
1762  if OPTIONS.package_key is None:
1763      OPTIONS.package_key = misc_info.get(
1764          "default_system_dev_certificate",
1765          "build/make/target/product/security/testkey")
1766
1767  BuildKeyMap(misc_info, key_mapping_options)
1768
1769  apk_keys_info, compressed_extension = common.ReadApkCerts(input_zip)
1770  apk_keys = GetApkCerts(apk_keys_info)
1771
1772  apex_keys_info = ReadApexKeysInfo(input_zip)
1773  apex_keys = GetApexKeys(apex_keys_info, apk_keys)
1774
1775  # TODO(xunchang) check for the apks inside the apex files, and abort early if
1776  # the keys are not available.
1777  CheckApkAndApexKeysAvailable(
1778      input_zip,
1779      set(apk_keys.keys()) | set(apex_keys.keys()),
1780      compressed_extension,
1781      apex_keys)
1782
1783  key_passwords = common.GetKeyPasswords(
1784      set(apk_keys.values()) | set(itertools.chain(*apex_keys.values())))
1785  platform_api_level, _ = GetApiLevelAndCodename(input_zip)
1786  codename_to_api_level_map = GetCodenameToApiLevelMap(input_zip)
1787
1788  ProcessTargetFiles(input_zip, output_zip, misc_info,
1789                     apk_keys, apex_keys, key_passwords,
1790                     platform_api_level, codename_to_api_level_map,
1791                     compressed_extension)
1792
1793  common.ZipClose(input_zip)
1794  common.ZipClose(output_zip)
1795
1796  if OPTIONS.vendor_partitions and OPTIONS.vendor_otatools:
1797    BuildVendorPartitions(args[1])
1798
1799  # Skip building userdata.img and cache.img when signing the target files.
1800  new_args = ["--is_signing", "--add_missing", "--verbose"]
1801  # add_img_to_target_files builds the system image from scratch, so the
1802  # recovery patch is guaranteed to be regenerated there.
1803  if OPTIONS.rebuild_recovery:
1804    new_args.append("--rebuild_recovery")
1805  new_args.append(args[1])
1806  add_img_to_target_files.main(new_args)
1807
1808  print("done.")
1809
1810
1811if __name__ == '__main__':
1812  try:
1813    main(sys.argv[1:])
1814  finally:
1815    common.Cleanup()
1816