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