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