• 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=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/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 (or ROOT/verity_key
87      for devices using system_root_image). It expects the key filename WITH
88      the extension (e.g. verity_key.pub).
89
90  --replace_verity_keyid <path_to_X509_PEM_cert_file>
91      Replace the veritykeyid in BOOT/cmdline of input_target_file_zip
92      with keyid of the cert pointed by <path_to_X509_PEM_cert_file>.
93
94  --avb_{boot,system,system_other,vendor,dtbo,vbmeta,vbmeta_system,
95         vbmeta_vendor}_algorithm <algorithm>
96  --avb_{boot,system,system_other,vendor,dtbo,vbmeta,vbmeta_system,
97         vbmeta_vendor}_key <key>
98      Use the specified algorithm (e.g. SHA256_RSA4096) and the key to AVB-sign
99      the specified image. Otherwise it uses the existing values in info dict.
100
101  --avb_{apex,boot,system,system_other,vendor,dtbo,vbmeta,vbmeta_system,
102         vbmeta_vendor}_extra_args <args>
103      Specify any additional args that are needed to AVB-sign the image
104      (e.g. "--signing_helper /path/to/helper"). The args will be appended to
105      the existing ones in info dict.
106"""
107
108from __future__ import print_function
109
110import base64
111import copy
112import errno
113import gzip
114import itertools
115import logging
116import os
117import re
118import shutil
119import stat
120import subprocess
121import sys
122import tempfile
123import zipfile
124from xml.etree import ElementTree
125
126import add_img_to_target_files
127import apex_utils
128import common
129
130
131if sys.hexversion < 0x02070000:
132  print("Python 2.7 or newer is required.", file=sys.stderr)
133  sys.exit(1)
134
135
136logger = logging.getLogger(__name__)
137
138OPTIONS = common.OPTIONS
139
140OPTIONS.extra_apks = {}
141OPTIONS.extra_apex_payload_keys = {}
142OPTIONS.skip_apks_with_path_prefix = set()
143OPTIONS.key_map = {}
144OPTIONS.rebuild_recovery = False
145OPTIONS.replace_ota_keys = False
146OPTIONS.replace_verity_public_key = False
147OPTIONS.replace_verity_private_key = False
148OPTIONS.replace_verity_keyid = False
149OPTIONS.tag_changes = ("-test-keys", "-dev-keys", "+release-keys")
150OPTIONS.avb_keys = {}
151OPTIONS.avb_algorithms = {}
152OPTIONS.avb_extra_args = {}
153
154
155def GetApkCerts(certmap):
156  # apply the key remapping to the contents of the file
157  for apk, cert in certmap.iteritems():
158    certmap[apk] = OPTIONS.key_map.get(cert, cert)
159
160  # apply all the -e options, overriding anything in the file
161  for apk, cert in OPTIONS.extra_apks.iteritems():
162    if not cert:
163      cert = "PRESIGNED"
164    certmap[apk] = OPTIONS.key_map.get(cert, cert)
165
166  return certmap
167
168
169def GetApexKeys(keys_info, key_map):
170  """Gets APEX payload and container signing keys by applying the mapping rules.
171
172  Presigned payload / container keys will be set accordingly.
173
174  Args:
175    keys_info: A dict that maps from APEX filenames to a tuple of (payload_key,
176        container_key).
177    key_map: A dict that overrides the keys, specified via command-line input.
178
179  Returns:
180    A dict that contains the updated APEX key mapping, which should be used for
181    the current signing.
182  """
183  # Apply all the --extra_apex_payload_key options to override the payload
184  # signing keys in the given keys_info.
185  for apex, key in OPTIONS.extra_apex_payload_keys.items():
186    if not key:
187      key = 'PRESIGNED'
188    if apex not in keys_info:
189      logger.warning('Failed to find %s in target_files; Ignored', apex)
190      continue
191    keys_info[apex] = (key, keys_info[apex][1])
192
193  # Apply the key remapping to container keys.
194  for apex, (payload_key, container_key) in keys_info.items():
195    keys_info[apex] = (payload_key, key_map.get(container_key, container_key))
196
197  # Apply all the --extra_apks options to override the container keys.
198  for apex, key in OPTIONS.extra_apks.items():
199    # Skip non-APEX containers.
200    if apex not in keys_info:
201      continue
202    if not key:
203      key = 'PRESIGNED'
204    keys_info[apex] = (keys_info[apex][0], key_map.get(key, key))
205
206  return keys_info
207
208
209def GetApkFileInfo(filename, compressed_extension, skipped_prefixes):
210  """Returns the APK info based on the given filename.
211
212  Checks if the given filename (with path) looks like an APK file, by taking the
213  compressed extension into consideration. If it appears to be an APK file,
214  further checks if the APK file should be skipped when signing, based on the
215  given path prefixes.
216
217  Args:
218    filename: Path to the file.
219    compressed_extension: The extension string of compressed APKs (e.g. ".gz"),
220        or None if there's no compressed APKs.
221    skipped_prefixes: A set/list/tuple of the path prefixes to be skipped.
222
223  Returns:
224    (is_apk, is_compressed, should_be_skipped): is_apk indicates whether the
225    given filename is an APK file. is_compressed indicates whether the APK file
226    is compressed (only meaningful when is_apk is True). should_be_skipped
227    indicates whether the filename matches any of the given prefixes to be
228    skipped.
229
230  Raises:
231    AssertionError: On invalid compressed_extension or skipped_prefixes inputs.
232  """
233  assert compressed_extension is None or compressed_extension.startswith('.'), \
234      "Invalid compressed_extension arg: '{}'".format(compressed_extension)
235
236  # skipped_prefixes should be one of set/list/tuple types. Other types such as
237  # str shouldn't be accepted.
238  assert isinstance(skipped_prefixes, (set, list, tuple)), \
239      "Invalid skipped_prefixes input type: {}".format(type(skipped_prefixes))
240
241  compressed_apk_extension = (
242      ".apk" + compressed_extension if compressed_extension else None)
243  is_apk = (filename.endswith(".apk") or
244            (compressed_apk_extension and
245             filename.endswith(compressed_apk_extension)))
246  if not is_apk:
247    return (False, False, False)
248
249  is_compressed = (compressed_apk_extension and
250                   filename.endswith(compressed_apk_extension))
251  should_be_skipped = filename.startswith(tuple(skipped_prefixes))
252  return (True, is_compressed, should_be_skipped)
253
254
255def CheckApkAndApexKeysAvailable(input_tf_zip, known_keys,
256                                 compressed_extension, apex_keys):
257  """Checks that all the APKs and APEXes have keys specified.
258
259  Args:
260    input_tf_zip: An open target_files zip file.
261    known_keys: A set of APKs and APEXes that have known signing keys.
262    compressed_extension: The extension string of compressed APKs, such as
263        '.gz', or None if there's no compressed APKs.
264    apex_keys: A dict that contains the key mapping from APEX name to
265        (payload_key, container_key).
266
267  Raises:
268    AssertionError: On finding unknown APKs and APEXes.
269  """
270  unknown_files = []
271  for info in input_tf_zip.infolist():
272    # Handle APEXes first, e.g. SYSTEM/apex/com.android.tzdata.apex.
273    if (info.filename.startswith('SYSTEM/apex') and
274        info.filename.endswith('.apex')):
275      name = os.path.basename(info.filename)
276      if name not in known_keys:
277        unknown_files.append(name)
278      continue
279
280    # And APKs.
281    (is_apk, is_compressed, should_be_skipped) = GetApkFileInfo(
282        info.filename, compressed_extension, OPTIONS.skip_apks_with_path_prefix)
283    if not is_apk or should_be_skipped:
284      continue
285
286    name = os.path.basename(info.filename)
287    if is_compressed:
288      name = name[:-len(compressed_extension)]
289    if name not in known_keys:
290      unknown_files.append(name)
291
292  assert not unknown_files, \
293      ("No key specified for:\n  {}\n"
294       "Use '-e <apkname>=' to specify a key (which may be an empty string to "
295       "not sign this apk).".format("\n  ".join(unknown_files)))
296
297  # For all the APEXes, double check that we won't have an APEX that has only
298  # one of the payload / container keys set.
299  if not apex_keys:
300    return
301
302  invalid_apexes = []
303  for info in input_tf_zip.infolist():
304    if (not info.filename.startswith('SYSTEM/apex') or
305        not info.filename.endswith('.apex')):
306      continue
307
308    name = os.path.basename(info.filename)
309    (payload_key, container_key) = apex_keys[name]
310    if ((payload_key in common.SPECIAL_CERT_STRINGS and
311         container_key not in common.SPECIAL_CERT_STRINGS) or
312        (payload_key not in common.SPECIAL_CERT_STRINGS and
313         container_key in common.SPECIAL_CERT_STRINGS)):
314      invalid_apexes.append(
315          "{}: payload_key {}, container_key {}".format(
316              name, payload_key, container_key))
317
318  assert not invalid_apexes, \
319      "Invalid APEX keys specified:\n  {}\n".format(
320          "\n  ".join(invalid_apexes))
321
322
323def SignApk(data, keyname, pw, platform_api_level, codename_to_api_level_map,
324            is_compressed):
325  unsigned = tempfile.NamedTemporaryFile()
326  unsigned.write(data)
327  unsigned.flush()
328
329  if is_compressed:
330    uncompressed = tempfile.NamedTemporaryFile()
331    with gzip.open(unsigned.name, "rb") as in_file, \
332         open(uncompressed.name, "wb") as out_file:
333      shutil.copyfileobj(in_file, out_file)
334
335    # Finally, close the "unsigned" file (which is gzip compressed), and then
336    # replace it with the uncompressed version.
337    #
338    # TODO(narayan): All this nastiness can be avoided if python 3.2 is in use,
339    # we could just gzip / gunzip in-memory buffers instead.
340    unsigned.close()
341    unsigned = uncompressed
342
343  signed = tempfile.NamedTemporaryFile()
344
345  # For pre-N builds, don't upgrade to SHA-256 JAR signatures based on the APK's
346  # minSdkVersion to avoid increasing incremental OTA update sizes. If an APK
347  # didn't change, we don't want its signature to change due to the switch
348  # from SHA-1 to SHA-256.
349  # By default, APK signer chooses SHA-256 signatures if the APK's minSdkVersion
350  # is 18 or higher. For pre-N builds we disable this mechanism by pretending
351  # that the APK's minSdkVersion is 1.
352  # For N+ builds, we let APK signer rely on the APK's minSdkVersion to
353  # determine whether to use SHA-256.
354  min_api_level = None
355  if platform_api_level > 23:
356    # Let APK signer choose whether to use SHA-1 or SHA-256, based on the APK's
357    # minSdkVersion attribute
358    min_api_level = None
359  else:
360    # Force APK signer to use SHA-1
361    min_api_level = 1
362
363  common.SignFile(unsigned.name, signed.name, keyname, pw,
364                  min_api_level=min_api_level,
365                  codename_to_api_level_map=codename_to_api_level_map)
366
367  data = None
368  if is_compressed:
369    # Recompress the file after it has been signed.
370    compressed = tempfile.NamedTemporaryFile()
371    with open(signed.name, "rb") as in_file, \
372         gzip.open(compressed.name, "wb") as out_file:
373      shutil.copyfileobj(in_file, out_file)
374
375    data = compressed.read()
376    compressed.close()
377  else:
378    data = signed.read()
379
380  unsigned.close()
381  signed.close()
382
383  return data
384
385
386def ProcessTargetFiles(input_tf_zip, output_tf_zip, misc_info,
387                       apk_keys, apex_keys, key_passwords,
388                       platform_api_level, codename_to_api_level_map,
389                       compressed_extension):
390  # maxsize measures the maximum filename length, including the ones to be
391  # skipped.
392  maxsize = max(
393      [len(os.path.basename(i.filename)) for i in input_tf_zip.infolist()
394       if GetApkFileInfo(i.filename, compressed_extension, [])[0]])
395  system_root_image = misc_info.get("system_root_image") == "true"
396
397  for info in input_tf_zip.infolist():
398    filename = info.filename
399    if filename.startswith("IMAGES/"):
400      continue
401
402    # Skip split super images, which will be re-generated during signing.
403    if filename.startswith("OTA/") and filename.endswith(".img"):
404      continue
405
406    data = input_tf_zip.read(filename)
407    out_info = copy.copy(info)
408    (is_apk, is_compressed, should_be_skipped) = GetApkFileInfo(
409        filename, compressed_extension, OPTIONS.skip_apks_with_path_prefix)
410
411    if is_apk and should_be_skipped:
412      # Copy skipped APKs verbatim.
413      print(
414          "NOT signing: %s\n"
415          "        (skipped due to matching prefix)" % (filename,))
416      common.ZipWriteStr(output_tf_zip, out_info, data)
417
418    # Sign APKs.
419    elif is_apk:
420      name = os.path.basename(filename)
421      if is_compressed:
422        name = name[:-len(compressed_extension)]
423
424      key = apk_keys[name]
425      if key not in common.SPECIAL_CERT_STRINGS:
426        print("    signing: %-*s (%s)" % (maxsize, name, key))
427        signed_data = SignApk(data, key, key_passwords[key], platform_api_level,
428                              codename_to_api_level_map, is_compressed)
429        common.ZipWriteStr(output_tf_zip, out_info, signed_data)
430      else:
431        # an APK we're not supposed to sign.
432        print(
433            "NOT signing: %s\n"
434            "        (skipped due to special cert string)" % (name,))
435        common.ZipWriteStr(output_tf_zip, out_info, data)
436
437    # Sign bundled APEX files.
438    elif filename.startswith("SYSTEM/apex") and filename.endswith(".apex"):
439      name = os.path.basename(filename)
440      payload_key, container_key = apex_keys[name]
441
442      # We've asserted not having a case with only one of them PRESIGNED.
443      if (payload_key not in common.SPECIAL_CERT_STRINGS and
444          container_key not in common.SPECIAL_CERT_STRINGS):
445        print("    signing: %-*s container (%s)" % (
446            maxsize, name, container_key))
447        print("           : %-*s payload   (%s)" % (
448            maxsize, name, payload_key))
449
450        signed_apex = apex_utils.SignApex(
451            data,
452            payload_key,
453            container_key,
454            key_passwords[container_key],
455            codename_to_api_level_map,
456            OPTIONS.avb_extra_args.get('apex'))
457        common.ZipWrite(output_tf_zip, signed_apex, filename)
458
459      else:
460        print(
461            "NOT signing: %s\n"
462            "        (skipped due to special cert string)" % (name,))
463        common.ZipWriteStr(output_tf_zip, out_info, data)
464
465    # AVB public keys for the installed APEXes, which will be updated later.
466    elif (os.path.dirname(filename) == 'SYSTEM/etc/security/apex' and
467          filename != 'SYSTEM/etc/security/apex/'):
468      continue
469
470    # System properties.
471    elif filename in ("SYSTEM/build.prop",
472                      "VENDOR/build.prop",
473                      "SYSTEM/vendor/build.prop",
474                      "ODM/build.prop",  # legacy
475                      "ODM/etc/build.prop",
476                      "VENDOR/odm/build.prop",  # legacy
477                      "VENDOR/odm/etc/build.prop",
478                      "PRODUCT/build.prop",
479                      "SYSTEM/product/build.prop",
480                      "PRODUCT_SERVICES/build.prop",
481                      "SYSTEM/product_services/build.prop",
482                      "SYSTEM/etc/prop.default",
483                      "BOOT/RAMDISK/prop.default",
484                      "BOOT/RAMDISK/default.prop",  # legacy
485                      "ROOT/default.prop",  # legacy
486                      "RECOVERY/RAMDISK/prop.default",
487                      "RECOVERY/RAMDISK/default.prop"):  # legacy
488      print("Rewriting %s:" % (filename,))
489      if stat.S_ISLNK(info.external_attr >> 16):
490        new_data = data
491      else:
492        new_data = RewriteProps(data)
493      common.ZipWriteStr(output_tf_zip, out_info, new_data)
494
495    # Replace the certs in *mac_permissions.xml (there could be multiple, such
496    # as {system,vendor}/etc/selinux/{plat,nonplat}_mac_permissions.xml).
497    elif filename.endswith("mac_permissions.xml"):
498      print("Rewriting %s with new keys." % (filename,))
499      new_data = ReplaceCerts(data)
500      common.ZipWriteStr(output_tf_zip, out_info, new_data)
501
502    # Ask add_img_to_target_files to rebuild the recovery patch if needed.
503    elif filename in ("SYSTEM/recovery-from-boot.p",
504                      "SYSTEM/etc/recovery.img",
505                      "SYSTEM/bin/install-recovery.sh"):
506      OPTIONS.rebuild_recovery = True
507
508    # Don't copy OTA certs if we're replacing them.
509    elif (
510        OPTIONS.replace_ota_keys and
511        filename in (
512            "BOOT/RAMDISK/system/etc/security/otacerts.zip",
513            "BOOT/RAMDISK/system/etc/update_engine/update-payload-key.pub.pem",
514            "RECOVERY/RAMDISK/system/etc/security/otacerts.zip",
515            "SYSTEM/etc/security/otacerts.zip",
516            "SYSTEM/etc/update_engine/update-payload-key.pub.pem")):
517      pass
518
519    # Skip META/misc_info.txt since we will write back the new values later.
520    elif filename == "META/misc_info.txt":
521      pass
522
523    # Skip verity public key if we will replace it.
524    elif (OPTIONS.replace_verity_public_key and
525          filename in ("BOOT/RAMDISK/verity_key",
526                       "ROOT/verity_key")):
527      pass
528
529    # Skip verity keyid (for system_root_image use) if we will replace it.
530    elif OPTIONS.replace_verity_keyid and filename == "BOOT/cmdline":
531      pass
532
533    # Skip the care_map as we will regenerate the system/vendor images.
534    elif filename == "META/care_map.pb" or filename == "META/care_map.txt":
535      pass
536
537    # Updates system_other.avbpubkey in /product/etc/.
538    elif filename in (
539        "PRODUCT/etc/security/avb/system_other.avbpubkey",
540        "SYSTEM/product/etc/security/avb/system_other.avbpubkey"):
541      # Only update system_other's public key, if the corresponding signing
542      # key is specified via --avb_system_other_key.
543      signing_key = OPTIONS.avb_keys.get("system_other")
544      if signing_key:
545        public_key = common.ExtractAvbPublicKey(signing_key)
546        print("    Rewriting AVB public key of system_other in /product")
547        common.ZipWrite(output_tf_zip, public_key, filename)
548
549    # Should NOT sign boot-debug.img.
550    elif filename in (
551        "BOOT/RAMDISK/force_debuggable",
552        "RECOVERY/RAMDISK/force_debuggable"
553        "RECOVERY/RAMDISK/first_stage_ramdisk/force_debuggable"):
554      raise common.ExternalError("debuggable boot.img cannot be signed")
555
556    # A non-APK file; copy it verbatim.
557    else:
558      common.ZipWriteStr(output_tf_zip, out_info, data)
559
560  if OPTIONS.replace_ota_keys:
561    ReplaceOtaKeys(input_tf_zip, output_tf_zip, misc_info)
562
563  # Replace the keyid string in misc_info dict.
564  if OPTIONS.replace_verity_private_key:
565    ReplaceVerityPrivateKey(misc_info, OPTIONS.replace_verity_private_key[1])
566
567  if OPTIONS.replace_verity_public_key:
568    dest = "ROOT/verity_key" if system_root_image else "BOOT/RAMDISK/verity_key"
569    # We are replacing the one in boot image only, since the one under
570    # recovery won't ever be needed.
571    ReplaceVerityPublicKey(
572        output_tf_zip, dest, OPTIONS.replace_verity_public_key[1])
573
574  # Replace the keyid string in BOOT/cmdline.
575  if OPTIONS.replace_verity_keyid:
576    ReplaceVerityKeyId(input_tf_zip, output_tf_zip,
577                       OPTIONS.replace_verity_keyid[1])
578
579  # Replace the AVB signing keys, if any.
580  ReplaceAvbSigningKeys(misc_info)
581
582  # Write back misc_info with the latest values.
583  ReplaceMiscInfoTxt(input_tf_zip, output_tf_zip, misc_info)
584
585
586def ReplaceCerts(data):
587  """Replaces all the occurences of X.509 certs with the new ones.
588
589  The mapping info is read from OPTIONS.key_map. Non-existent certificate will
590  be skipped. After the replacement, it additionally checks for duplicate
591  entries, which would otherwise fail the policy loading code in
592  frameworks/base/services/core/java/com/android/server/pm/SELinuxMMAC.java.
593
594  Args:
595    data: Input string that contains a set of X.509 certs.
596
597  Returns:
598    A string after the replacement.
599
600  Raises:
601    AssertionError: On finding duplicate entries.
602  """
603  for old, new in OPTIONS.key_map.iteritems():
604    if OPTIONS.verbose:
605      print("    Replacing %s.x509.pem with %s.x509.pem" % (old, new))
606
607    try:
608      with open(old + ".x509.pem") as old_fp:
609        old_cert16 = base64.b16encode(
610            common.ParseCertificate(old_fp.read())).lower()
611      with open(new + ".x509.pem") as new_fp:
612        new_cert16 = base64.b16encode(
613            common.ParseCertificate(new_fp.read())).lower()
614    except IOError as e:
615      if OPTIONS.verbose or e.errno != errno.ENOENT:
616        print("    Error accessing %s: %s.\nSkip replacing %s.x509.pem with "
617              "%s.x509.pem." % (e.filename, e.strerror, old, new))
618      continue
619
620    # Only match entire certs.
621    pattern = "\\b" + old_cert16 + "\\b"
622    (data, num) = re.subn(pattern, new_cert16, data, flags=re.IGNORECASE)
623
624    if OPTIONS.verbose:
625      print("    Replaced %d occurence(s) of %s.x509.pem with %s.x509.pem" % (
626          num, old, new))
627
628  # Verify that there're no duplicate entries after the replacement. Note that
629  # it's only checking entries with global seinfo at the moment (i.e. ignoring
630  # the ones with inner packages). (Bug: 69479366)
631  root = ElementTree.fromstring(data)
632  signatures = [signer.attrib['signature'] for signer in root.findall('signer')]
633  assert len(signatures) == len(set(signatures)), \
634      "Found duplicate entries after cert replacement: {}".format(data)
635
636  return data
637
638
639def EditTags(tags):
640  """Applies the edits to the tag string as specified in OPTIONS.tag_changes.
641
642  Args:
643    tags: The input string that contains comma-separated tags.
644
645  Returns:
646    The updated tags (comma-separated and sorted).
647  """
648  tags = set(tags.split(","))
649  for ch in OPTIONS.tag_changes:
650    if ch[0] == "-":
651      tags.discard(ch[1:])
652    elif ch[0] == "+":
653      tags.add(ch[1:])
654  return ",".join(sorted(tags))
655
656
657def RewriteProps(data):
658  """Rewrites the system properties in the given string.
659
660  Each property is expected in 'key=value' format. The properties that contain
661  build tags (i.e. test-keys, dev-keys) will be updated accordingly by calling
662  EditTags().
663
664  Args:
665    data: Input string, separated by newlines.
666
667  Returns:
668    The string with modified properties.
669  """
670  output = []
671  for line in data.split("\n"):
672    line = line.strip()
673    original_line = line
674    if line and line[0] != '#' and "=" in line:
675      key, value = line.split("=", 1)
676      if (key.startswith("ro.") and
677          key.endswith((".build.fingerprint", ".build.thumbprint"))):
678        pieces = value.split("/")
679        pieces[-1] = EditTags(pieces[-1])
680        value = "/".join(pieces)
681      elif key == "ro.bootimage.build.fingerprint":
682        pieces = value.split("/")
683        pieces[-1] = EditTags(pieces[-1])
684        value = "/".join(pieces)
685      elif key == "ro.build.description":
686        pieces = value.split(" ")
687        assert len(pieces) == 5
688        pieces[-1] = EditTags(pieces[-1])
689        value = " ".join(pieces)
690      elif key.startswith("ro.") and key.endswith(".build.tags"):
691        value = EditTags(value)
692      elif key == "ro.build.display.id":
693        # change, eg, "JWR66N dev-keys" to "JWR66N"
694        value = value.split()
695        if len(value) > 1 and value[-1].endswith("-keys"):
696          value.pop()
697        value = " ".join(value)
698      line = key + "=" + value
699    if line != original_line:
700      print("  replace: ", original_line)
701      print("     with: ", line)
702    output.append(line)
703  return "\n".join(output) + "\n"
704
705
706def WriteOtacerts(output_zip, filename, keys):
707  """Constructs a zipfile from given keys; and writes it to output_zip.
708
709  Args:
710    output_zip: The output target_files zip.
711    filename: The archive name in the output zip.
712    keys: A list of public keys to use during OTA package verification.
713  """
714
715  try:
716    from StringIO import StringIO
717  except ImportError:
718    from io import StringIO
719  temp_file = StringIO()
720  certs_zip = zipfile.ZipFile(temp_file, "w")
721  for k in keys:
722    common.ZipWrite(certs_zip, k)
723  common.ZipClose(certs_zip)
724  common.ZipWriteStr(output_zip, filename, temp_file.getvalue())
725
726
727def ReplaceOtaKeys(input_tf_zip, output_tf_zip, misc_info):
728  try:
729    keylist = input_tf_zip.read("META/otakeys.txt").split()
730  except KeyError:
731    raise common.ExternalError("can't read META/otakeys.txt from input")
732
733  extra_recovery_keys = misc_info.get("extra_recovery_keys")
734  if extra_recovery_keys:
735    extra_recovery_keys = [OPTIONS.key_map.get(k, k) + ".x509.pem"
736                           for k in extra_recovery_keys.split()]
737    if extra_recovery_keys:
738      print("extra recovery-only key(s): " + ", ".join(extra_recovery_keys))
739  else:
740    extra_recovery_keys = []
741
742  mapped_keys = []
743  for k in keylist:
744    m = re.match(r"^(.*)\.x509\.pem$", k)
745    if not m:
746      raise common.ExternalError(
747          "can't parse \"%s\" from META/otakeys.txt" % (k,))
748    k = m.group(1)
749    mapped_keys.append(OPTIONS.key_map.get(k, k) + ".x509.pem")
750
751  if mapped_keys:
752    print("using:\n   ", "\n   ".join(mapped_keys))
753    print("for OTA package verification")
754  else:
755    devkey = misc_info.get("default_system_dev_certificate",
756                           "build/target/product/security/testkey")
757    mapped_devkey = OPTIONS.key_map.get(devkey, devkey)
758    if mapped_devkey != devkey:
759      misc_info["default_system_dev_certificate"] = mapped_devkey
760    mapped_keys.append(mapped_devkey + ".x509.pem")
761    print("META/otakeys.txt has no keys; using %s for OTA package"
762          " verification." % (mapped_keys[0],))
763
764  # recovery now uses the same x509.pem version of the keys.
765  # extra_recovery_keys are used only in recovery.
766  if misc_info.get("recovery_as_boot") == "true":
767    recovery_keys_location = "BOOT/RAMDISK/system/etc/security/otacerts.zip"
768  else:
769    recovery_keys_location = "RECOVERY/RAMDISK/system/etc/security/otacerts.zip"
770
771  WriteOtacerts(output_tf_zip, recovery_keys_location,
772                mapped_keys + extra_recovery_keys)
773
774  # SystemUpdateActivity uses the x509.pem version of the keys, but
775  # put into a zipfile system/etc/security/otacerts.zip.
776  # We DO NOT include the extra_recovery_keys (if any) here.
777  WriteOtacerts(output_tf_zip, "SYSTEM/etc/security/otacerts.zip", mapped_keys)
778
779  # For A/B devices, update the payload verification key.
780  if misc_info.get("ab_update") == "true":
781    # Unlike otacerts.zip that may contain multiple keys, we can only specify
782    # ONE payload verification key.
783    if len(mapped_keys) > 1:
784      print("\n  WARNING: Found more than one OTA keys; Using the first one"
785            " as payload verification key.\n\n")
786
787    print("Using %s for payload verification." % (mapped_keys[0],))
788    pubkey = common.ExtractPublicKey(mapped_keys[0])
789    common.ZipWriteStr(
790        output_tf_zip,
791        "SYSTEM/etc/update_engine/update-payload-key.pub.pem",
792        pubkey)
793    common.ZipWriteStr(
794        output_tf_zip,
795        "BOOT/RAMDISK/system/etc/update_engine/update-payload-key.pub.pem",
796        pubkey)
797
798
799def ReplaceVerityPublicKey(output_zip, filename, key_path):
800  """Replaces the verity public key at the given path in the given zip.
801
802  Args:
803    output_zip: The output target_files zip.
804    filename: The archive name in the output zip.
805    key_path: The path to the public key.
806  """
807  print("Replacing verity public key with %s" % (key_path,))
808  common.ZipWrite(output_zip, key_path, arcname=filename)
809
810
811def ReplaceVerityPrivateKey(misc_info, key_path):
812  """Replaces the verity private key in misc_info dict.
813
814  Args:
815    misc_info: The info dict.
816    key_path: The path to the private key in PKCS#8 format.
817  """
818  print("Replacing verity private key with %s" % (key_path,))
819  misc_info["verity_key"] = key_path
820
821
822def ReplaceVerityKeyId(input_zip, output_zip, key_path):
823  """Replaces the veritykeyid parameter in BOOT/cmdline.
824
825  Args:
826    input_zip: The input target_files zip, which should be already open.
827    output_zip: The output target_files zip, which should be already open and
828        writable.
829    key_path: The path to the PEM encoded X.509 certificate.
830  """
831  in_cmdline = input_zip.read("BOOT/cmdline")
832  # Copy in_cmdline to output_zip if veritykeyid is not present.
833  if "veritykeyid" not in in_cmdline:
834    common.ZipWriteStr(output_zip, "BOOT/cmdline", in_cmdline)
835    return
836
837  out_buffer = []
838  for param in in_cmdline.split():
839    if "veritykeyid" not in param:
840      out_buffer.append(param)
841      continue
842
843    # Extract keyid using openssl command.
844    p = common.Run(["openssl", "x509", "-in", key_path, "-text"],
845                   stdout=subprocess.PIPE, stderr=subprocess.PIPE)
846    keyid, stderr = p.communicate()
847    assert p.returncode == 0, "Failed to dump certificate: {}".format(stderr)
848    keyid = re.search(
849        r'keyid:([0-9a-fA-F:]*)', keyid).group(1).replace(':', '').lower()
850    print("Replacing verity keyid with {}".format(keyid))
851    out_buffer.append("veritykeyid=id:%s" % (keyid,))
852
853  out_cmdline = ' '.join(out_buffer).strip() + '\n'
854  common.ZipWriteStr(output_zip, "BOOT/cmdline", out_cmdline)
855
856
857def ReplaceMiscInfoTxt(input_zip, output_zip, misc_info):
858  """Replaces META/misc_info.txt.
859
860  Only writes back the ones in the original META/misc_info.txt. Because the
861  current in-memory dict contains additional items computed at runtime.
862  """
863  misc_info_old = common.LoadDictionaryFromLines(
864      input_zip.read('META/misc_info.txt').split('\n'))
865  items = []
866  for key in sorted(misc_info):
867    if key in misc_info_old:
868      items.append('%s=%s' % (key, misc_info[key]))
869  common.ZipWriteStr(output_zip, "META/misc_info.txt", '\n'.join(items))
870
871
872def ReplaceAvbSigningKeys(misc_info):
873  """Replaces the AVB signing keys."""
874
875  AVB_FOOTER_ARGS_BY_PARTITION = {
876      'boot' : 'avb_boot_add_hash_footer_args',
877      'dtbo' : 'avb_dtbo_add_hash_footer_args',
878      'recovery' : 'avb_recovery_add_hash_footer_args',
879      'system' : 'avb_system_add_hashtree_footer_args',
880      'system_other' : 'avb_system_other_add_hashtree_footer_args',
881      'vendor' : 'avb_vendor_add_hashtree_footer_args',
882      'vbmeta' : 'avb_vbmeta_args',
883      'vbmeta_system' : 'avb_vbmeta_system_args',
884      'vbmeta_vendor' : 'avb_vbmeta_vendor_args',
885  }
886
887  def ReplaceAvbPartitionSigningKey(partition):
888    key = OPTIONS.avb_keys.get(partition)
889    if not key:
890      return
891
892    algorithm = OPTIONS.avb_algorithms.get(partition)
893    assert algorithm, 'Missing AVB signing algorithm for %s' % (partition,)
894
895    print('Replacing AVB signing key for %s with "%s" (%s)' % (
896        partition, key, algorithm))
897    misc_info['avb_' + partition + '_algorithm'] = algorithm
898    misc_info['avb_' + partition + '_key_path'] = key
899
900    extra_args = OPTIONS.avb_extra_args.get(partition)
901    if extra_args:
902      print('Setting extra AVB signing args for %s to "%s"' % (
903          partition, extra_args))
904      args_key = AVB_FOOTER_ARGS_BY_PARTITION[partition]
905      misc_info[args_key] = (misc_info.get(args_key, '') + ' ' + extra_args)
906
907  for partition in AVB_FOOTER_ARGS_BY_PARTITION:
908    ReplaceAvbPartitionSigningKey(partition)
909
910
911def BuildKeyMap(misc_info, key_mapping_options):
912  for s, d in key_mapping_options:
913    if s is None:   # -d option
914      devkey = misc_info.get("default_system_dev_certificate",
915                             "build/target/product/security/testkey")
916      devkeydir = os.path.dirname(devkey)
917
918      OPTIONS.key_map.update({
919          devkeydir + "/testkey":  d + "/releasekey",
920          devkeydir + "/devkey":   d + "/releasekey",
921          devkeydir + "/media":    d + "/media",
922          devkeydir + "/shared":   d + "/shared",
923          devkeydir + "/platform": d + "/platform",
924          })
925    else:
926      OPTIONS.key_map[s] = d
927
928
929def GetApiLevelAndCodename(input_tf_zip):
930  data = input_tf_zip.read("SYSTEM/build.prop")
931  api_level = None
932  codename = None
933  for line in data.split("\n"):
934    line = line.strip()
935    if line and line[0] != '#' and "=" in line:
936      key, value = line.split("=", 1)
937      key = key.strip()
938      if key == "ro.build.version.sdk":
939        api_level = int(value.strip())
940      elif key == "ro.build.version.codename":
941        codename = value.strip()
942
943  if api_level is None:
944    raise ValueError("No ro.build.version.sdk in SYSTEM/build.prop")
945  if codename is None:
946    raise ValueError("No ro.build.version.codename in SYSTEM/build.prop")
947
948  return (api_level, codename)
949
950
951def GetCodenameToApiLevelMap(input_tf_zip):
952  data = input_tf_zip.read("SYSTEM/build.prop")
953  api_level = None
954  codenames = None
955  for line in data.split("\n"):
956    line = line.strip()
957    if line and line[0] != '#' and "=" in line:
958      key, value = line.split("=", 1)
959      key = key.strip()
960      if key == "ro.build.version.sdk":
961        api_level = int(value.strip())
962      elif key == "ro.build.version.all_codenames":
963        codenames = value.strip().split(",")
964
965  if api_level is None:
966    raise ValueError("No ro.build.version.sdk in SYSTEM/build.prop")
967  if codenames is None:
968    raise ValueError("No ro.build.version.all_codenames in SYSTEM/build.prop")
969
970  result = dict()
971  for codename in codenames:
972    codename = codename.strip()
973    if codename:
974      result[codename] = api_level
975  return result
976
977
978def ReadApexKeysInfo(tf_zip):
979  """Parses the APEX keys info from a given target-files zip.
980
981  Given a target-files ZipFile, parses the META/apexkeys.txt entry and returns a
982  dict that contains the mapping from APEX names (e.g. com.android.tzdata) to a
983  tuple of (payload_key, container_key).
984
985  Args:
986    tf_zip: The input target_files ZipFile (already open).
987
988  Returns:
989    (payload_key, container_key): payload_key contains the path to the payload
990        signing key; container_key contains the path to the container signing
991        key.
992  """
993  keys = {}
994  for line in tf_zip.read("META/apexkeys.txt").split("\n"):
995    line = line.strip()
996    if not line:
997      continue
998    matches = re.match(
999        r'^name="(?P<NAME>.*)"\s+'
1000        r'public_key="(?P<PAYLOAD_PUBLIC_KEY>.*)"\s+'
1001        r'private_key="(?P<PAYLOAD_PRIVATE_KEY>.*)"\s+'
1002        r'container_certificate="(?P<CONTAINER_CERT>.*)"\s+'
1003        r'container_private_key="(?P<CONTAINER_PRIVATE_KEY>.*)"$',
1004        line)
1005    if not matches:
1006      continue
1007
1008    name = matches.group('NAME')
1009    payload_private_key = matches.group("PAYLOAD_PRIVATE_KEY")
1010
1011    def CompareKeys(pubkey, pubkey_suffix, privkey, privkey_suffix):
1012      pubkey_suffix_len = len(pubkey_suffix)
1013      privkey_suffix_len = len(privkey_suffix)
1014      return (pubkey.endswith(pubkey_suffix) and
1015              privkey.endswith(privkey_suffix) and
1016              pubkey[:-pubkey_suffix_len] == privkey[:-privkey_suffix_len])
1017
1018    # Sanity check on the container key names, as we'll carry them without the
1019    # extensions. This doesn't apply to payload keys though, which we will use
1020    # full names only.
1021    container_cert = matches.group("CONTAINER_CERT")
1022    container_private_key = matches.group("CONTAINER_PRIVATE_KEY")
1023    if container_cert == 'PRESIGNED' and container_private_key == 'PRESIGNED':
1024      container_key = 'PRESIGNED'
1025    elif CompareKeys(
1026        container_cert, OPTIONS.public_key_suffix,
1027        container_private_key, OPTIONS.private_key_suffix):
1028      container_key = container_cert[:-len(OPTIONS.public_key_suffix)]
1029    else:
1030      raise ValueError("Failed to parse container keys: \n{}".format(line))
1031
1032    keys[name] = (payload_private_key, container_key)
1033
1034  return keys
1035
1036
1037def main(argv):
1038
1039  key_mapping_options = []
1040
1041  def option_handler(o, a):
1042    if o in ("-e", "--extra_apks"):
1043      names, key = a.split("=")
1044      names = names.split(",")
1045      for n in names:
1046        OPTIONS.extra_apks[n] = key
1047    elif o == "--extra_apex_payload_key":
1048      apex_name, key = a.split("=")
1049      OPTIONS.extra_apex_payload_keys[apex_name] = key
1050    elif o == "--skip_apks_with_path_prefix":
1051      # Sanity check the prefix, which must be in all upper case.
1052      prefix = a.split('/')[0]
1053      if not prefix or prefix != prefix.upper():
1054        raise ValueError("Invalid path prefix '%s'" % (a,))
1055      OPTIONS.skip_apks_with_path_prefix.add(a)
1056    elif o in ("-d", "--default_key_mappings"):
1057      key_mapping_options.append((None, a))
1058    elif o in ("-k", "--key_mapping"):
1059      key_mapping_options.append(a.split("=", 1))
1060    elif o in ("-o", "--replace_ota_keys"):
1061      OPTIONS.replace_ota_keys = True
1062    elif o in ("-t", "--tag_changes"):
1063      new = []
1064      for i in a.split(","):
1065        i = i.strip()
1066        if not i or i[0] not in "-+":
1067          raise ValueError("Bad tag change '%s'" % (i,))
1068        new.append(i[0] + i[1:].strip())
1069      OPTIONS.tag_changes = tuple(new)
1070    elif o == "--replace_verity_public_key":
1071      OPTIONS.replace_verity_public_key = (True, a)
1072    elif o == "--replace_verity_private_key":
1073      OPTIONS.replace_verity_private_key = (True, a)
1074    elif o == "--replace_verity_keyid":
1075      OPTIONS.replace_verity_keyid = (True, a)
1076    elif o == "--avb_vbmeta_key":
1077      OPTIONS.avb_keys['vbmeta'] = a
1078    elif o == "--avb_vbmeta_algorithm":
1079      OPTIONS.avb_algorithms['vbmeta'] = a
1080    elif o == "--avb_vbmeta_extra_args":
1081      OPTIONS.avb_extra_args['vbmeta'] = a
1082    elif o == "--avb_boot_key":
1083      OPTIONS.avb_keys['boot'] = a
1084    elif o == "--avb_boot_algorithm":
1085      OPTIONS.avb_algorithms['boot'] = a
1086    elif o == "--avb_boot_extra_args":
1087      OPTIONS.avb_extra_args['boot'] = a
1088    elif o == "--avb_dtbo_key":
1089      OPTIONS.avb_keys['dtbo'] = a
1090    elif o == "--avb_dtbo_algorithm":
1091      OPTIONS.avb_algorithms['dtbo'] = a
1092    elif o == "--avb_dtbo_extra_args":
1093      OPTIONS.avb_extra_args['dtbo'] = a
1094    elif o == "--avb_system_key":
1095      OPTIONS.avb_keys['system'] = a
1096    elif o == "--avb_system_algorithm":
1097      OPTIONS.avb_algorithms['system'] = a
1098    elif o == "--avb_system_extra_args":
1099      OPTIONS.avb_extra_args['system'] = a
1100    elif o == "--avb_system_other_key":
1101      OPTIONS.avb_keys['system_other'] = a
1102    elif o == "--avb_system_other_algorithm":
1103      OPTIONS.avb_algorithms['system_other'] = a
1104    elif o == "--avb_system_other_extra_args":
1105      OPTIONS.avb_extra_args['system_other'] = a
1106    elif o == "--avb_vendor_key":
1107      OPTIONS.avb_keys['vendor'] = a
1108    elif o == "--avb_vendor_algorithm":
1109      OPTIONS.avb_algorithms['vendor'] = a
1110    elif o == "--avb_vendor_extra_args":
1111      OPTIONS.avb_extra_args['vendor'] = a
1112    elif o == "--avb_vbmeta_system_key":
1113      OPTIONS.avb_keys['vbmeta_system'] = a
1114    elif o == "--avb_vbmeta_system_algorithm":
1115      OPTIONS.avb_algorithms['vbmeta_system'] = a
1116    elif o == "--avb_vbmeta_system_extra_args":
1117      OPTIONS.avb_extra_args['vbmeta_system'] = a
1118    elif o == "--avb_vbmeta_vendor_key":
1119      OPTIONS.avb_keys['vbmeta_vendor'] = a
1120    elif o == "--avb_vbmeta_vendor_algorithm":
1121      OPTIONS.avb_algorithms['vbmeta_vendor'] = a
1122    elif o == "--avb_vbmeta_vendor_extra_args":
1123      OPTIONS.avb_extra_args['vbmeta_vendor'] = a
1124    elif o == "--avb_apex_extra_args":
1125      OPTIONS.avb_extra_args['apex'] = a
1126    else:
1127      return False
1128    return True
1129
1130  args = common.ParseOptions(
1131      argv, __doc__,
1132      extra_opts="e:d:k:ot:",
1133      extra_long_opts=[
1134          "extra_apks=",
1135          "extra_apex_payload_key=",
1136          "skip_apks_with_path_prefix=",
1137          "default_key_mappings=",
1138          "key_mapping=",
1139          "replace_ota_keys",
1140          "tag_changes=",
1141          "replace_verity_public_key=",
1142          "replace_verity_private_key=",
1143          "replace_verity_keyid=",
1144          "avb_apex_extra_args=",
1145          "avb_vbmeta_algorithm=",
1146          "avb_vbmeta_key=",
1147          "avb_vbmeta_extra_args=",
1148          "avb_boot_algorithm=",
1149          "avb_boot_key=",
1150          "avb_boot_extra_args=",
1151          "avb_dtbo_algorithm=",
1152          "avb_dtbo_key=",
1153          "avb_dtbo_extra_args=",
1154          "avb_system_algorithm=",
1155          "avb_system_key=",
1156          "avb_system_extra_args=",
1157          "avb_system_other_algorithm=",
1158          "avb_system_other_key=",
1159          "avb_system_other_extra_args=",
1160          "avb_vendor_algorithm=",
1161          "avb_vendor_key=",
1162          "avb_vendor_extra_args=",
1163          "avb_vbmeta_system_algorithm=",
1164          "avb_vbmeta_system_key=",
1165          "avb_vbmeta_system_extra_args=",
1166          "avb_vbmeta_vendor_algorithm=",
1167          "avb_vbmeta_vendor_key=",
1168          "avb_vbmeta_vendor_extra_args=",
1169      ],
1170      extra_option_handler=option_handler)
1171
1172  if len(args) != 2:
1173    common.Usage(__doc__)
1174    sys.exit(1)
1175
1176  common.InitLogging()
1177
1178  input_zip = zipfile.ZipFile(args[0], "r")
1179  output_zip = zipfile.ZipFile(args[1], "w",
1180                               compression=zipfile.ZIP_DEFLATED,
1181                               allowZip64=True)
1182
1183  misc_info = common.LoadInfoDict(input_zip)
1184
1185  BuildKeyMap(misc_info, key_mapping_options)
1186
1187  apk_keys_info, compressed_extension = common.ReadApkCerts(input_zip)
1188  apk_keys = GetApkCerts(apk_keys_info)
1189
1190  apex_keys_info = ReadApexKeysInfo(input_zip)
1191  apex_keys = GetApexKeys(apex_keys_info, apk_keys)
1192
1193  CheckApkAndApexKeysAvailable(
1194      input_zip,
1195      set(apk_keys.keys()) | set(apex_keys.keys()),
1196      compressed_extension,
1197      apex_keys)
1198
1199  key_passwords = common.GetKeyPasswords(
1200      set(apk_keys.values()) | set(itertools.chain(*apex_keys.values())))
1201  platform_api_level, _ = GetApiLevelAndCodename(input_zip)
1202  codename_to_api_level_map = GetCodenameToApiLevelMap(input_zip)
1203
1204  ProcessTargetFiles(input_zip, output_zip, misc_info,
1205                     apk_keys, apex_keys, key_passwords,
1206                     platform_api_level, codename_to_api_level_map,
1207                     compressed_extension)
1208
1209  common.ZipClose(input_zip)
1210  common.ZipClose(output_zip)
1211
1212  # Skip building userdata.img and cache.img when signing the target files.
1213  new_args = ["--is_signing"]
1214  # add_img_to_target_files builds the system image from scratch, so the
1215  # recovery patch is guaranteed to be regenerated there.
1216  if OPTIONS.rebuild_recovery:
1217    new_args.append("--rebuild_recovery")
1218  new_args.append(args[1])
1219  add_img_to_target_files.main(new_args)
1220
1221  print("done.")
1222
1223
1224if __name__ == '__main__':
1225  try:
1226    main(sys.argv[1:])
1227  except common.ExternalError as e:
1228    print("\n   ERROR: %s\n" % (e,))
1229    sys.exit(1)
1230  finally:
1231    common.Cleanup()
1232