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 name/key pairs as though they appeared in
25      apkcerts.txt (so mappings specified by -k and -d are applied).
26      Keys specified in -e override any value for that app contained
27      in the apkcerts.txt file.  Option may be repeated to give
28      multiple extra packages.
29
30  -k  (--key_mapping)  <src_key=dest_key>
31      Add a mapping from the key name as specified in apkcerts.txt (the
32      src_key) to the real key you wish to sign the package with
33      (dest_key).  Option may be repeated to give multiple key
34      mappings.
35
36  -d  (--default_key_mappings)  <dir>
37      Set up the following key mappings:
38
39        $devkey/devkey    ==>  $dir/releasekey
40        $devkey/testkey   ==>  $dir/releasekey
41        $devkey/media     ==>  $dir/media
42        $devkey/shared    ==>  $dir/shared
43        $devkey/platform  ==>  $dir/platform
44
45      where $devkey is the directory part of the value of
46      default_system_dev_certificate from the input target-files's
47      META/misc_info.txt.  (Defaulting to "build/target/product/security"
48      if the value is not present in misc_info.
49
50      -d and -k options are added to the set of mappings in the order
51      in which they appear on the command line.
52
53  -o  (--replace_ota_keys)
54      Replace the certificate (public key) used by OTA package verification
55      with the ones specified in the input target_files zip (in the
56      META/otakeys.txt file). Key remapping (-k and -d) is performed on the
57      keys. For A/B devices, the payload verification key will be replaced
58      as well. If there're multiple OTA keys, only the first one will be used
59      for payload verification.
60
61  -t  (--tag_changes)  <+tag>,<-tag>,...
62      Comma-separated list of changes to make to the set of tags (in
63      the last component of the build fingerprint).  Prefix each with
64      '+' or '-' to indicate whether that tag should be added or
65      removed.  Changes are processed in the order they appear.
66      Default value is "-test-keys,-dev-keys,+release-keys".
67
68  --replace_verity_private_key <key>
69      Replace the private key used for verity signing. It expects a filename
70      WITHOUT the extension (e.g. verity_key).
71
72  --replace_verity_public_key <key>
73      Replace the certificate (public key) used for verity verification. The
74      key file replaces the one at BOOT/RAMDISK/verity_key (or ROOT/verity_key
75      for devices using system_root_image). It expects the key filename WITH
76      the extension (e.g. verity_key.pub).
77
78  --replace_verity_keyid <path_to_X509_PEM_cert_file>
79      Replace the veritykeyid in BOOT/cmdline of input_target_file_zip
80      with keyid of the cert pointed by <path_to_X509_PEM_cert_file>.
81
82  --avb_{boot,system,vendor,dtbo,vbmeta}_algorithm <algorithm>
83  --avb_{boot,system,vendor,dtbo,vbmeta}_key <key>
84      Use the specified algorithm (e.g. SHA256_RSA4096) and the key to AVB-sign
85      the specified image. Otherwise it uses the existing values in info dict.
86
87  --avb_{boot,system,vendor,dtbo,vbmeta}_extra_args <args>
88      Specify any additional args that are needed to AVB-sign the image
89      (e.g. "--signing_helper /path/to/helper"). The args will be appended to
90      the existing ones in info dict.
91"""
92
93from __future__ import print_function
94
95import base64
96import copy
97import errno
98import gzip
99import os
100import re
101import shutil
102import stat
103import subprocess
104import sys
105import tempfile
106import zipfile
107from xml.etree import ElementTree
108
109import add_img_to_target_files
110import common
111
112
113if sys.hexversion < 0x02070000:
114  print("Python 2.7 or newer is required.", file=sys.stderr)
115  sys.exit(1)
116
117
118OPTIONS = common.OPTIONS
119
120OPTIONS.extra_apks = {}
121OPTIONS.key_map = {}
122OPTIONS.rebuild_recovery = False
123OPTIONS.replace_ota_keys = False
124OPTIONS.replace_verity_public_key = False
125OPTIONS.replace_verity_private_key = False
126OPTIONS.replace_verity_keyid = False
127OPTIONS.tag_changes = ("-test-keys", "-dev-keys", "+release-keys")
128OPTIONS.avb_keys = {}
129OPTIONS.avb_algorithms = {}
130OPTIONS.avb_extra_args = {}
131
132
133def GetApkCerts(certmap):
134  # apply the key remapping to the contents of the file
135  for apk, cert in certmap.iteritems():
136    certmap[apk] = OPTIONS.key_map.get(cert, cert)
137
138  # apply all the -e options, overriding anything in the file
139  for apk, cert in OPTIONS.extra_apks.iteritems():
140    if not cert:
141      cert = "PRESIGNED"
142    certmap[apk] = OPTIONS.key_map.get(cert, cert)
143
144  return certmap
145
146
147def CheckAllApksSigned(input_tf_zip, apk_key_map, compressed_extension):
148  """Check that all the APKs we want to sign have keys specified, and
149  error out if they don't."""
150  unknown_apks = []
151  compressed_apk_extension = None
152  if compressed_extension:
153    compressed_apk_extension = ".apk" + compressed_extension
154  for info in input_tf_zip.infolist():
155    if (info.filename.endswith(".apk") or
156        (compressed_apk_extension and
157         info.filename.endswith(compressed_apk_extension))):
158      name = os.path.basename(info.filename)
159      if compressed_apk_extension and name.endswith(compressed_apk_extension):
160        name = name[:-len(compressed_extension)]
161      if name not in apk_key_map:
162        unknown_apks.append(name)
163  if unknown_apks:
164    print("ERROR: no key specified for:\n")
165    print("  " + "\n  ".join(unknown_apks))
166    print("\nUse '-e <apkname>=' to specify a key (which may be an empty "
167          "string to not sign this apk).")
168    sys.exit(1)
169
170
171def SignApk(data, keyname, pw, platform_api_level, codename_to_api_level_map,
172            is_compressed):
173  unsigned = tempfile.NamedTemporaryFile()
174  unsigned.write(data)
175  unsigned.flush()
176
177  if is_compressed:
178    uncompressed = tempfile.NamedTemporaryFile()
179    with gzip.open(unsigned.name, "rb") as in_file, \
180         open(uncompressed.name, "wb") as out_file:
181      shutil.copyfileobj(in_file, out_file)
182
183    # Finally, close the "unsigned" file (which is gzip compressed), and then
184    # replace it with the uncompressed version.
185    #
186    # TODO(narayan): All this nastiness can be avoided if python 3.2 is in use,
187    # we could just gzip / gunzip in-memory buffers instead.
188    unsigned.close()
189    unsigned = uncompressed
190
191  signed = tempfile.NamedTemporaryFile()
192
193  # For pre-N builds, don't upgrade to SHA-256 JAR signatures based on the APK's
194  # minSdkVersion to avoid increasing incremental OTA update sizes. If an APK
195  # didn't change, we don't want its signature to change due to the switch
196  # from SHA-1 to SHA-256.
197  # By default, APK signer chooses SHA-256 signatures if the APK's minSdkVersion
198  # is 18 or higher. For pre-N builds we disable this mechanism by pretending
199  # that the APK's minSdkVersion is 1.
200  # For N+ builds, we let APK signer rely on the APK's minSdkVersion to
201  # determine whether to use SHA-256.
202  min_api_level = None
203  if platform_api_level > 23:
204    # Let APK signer choose whether to use SHA-1 or SHA-256, based on the APK's
205    # minSdkVersion attribute
206    min_api_level = None
207  else:
208    # Force APK signer to use SHA-1
209    min_api_level = 1
210
211  common.SignFile(unsigned.name, signed.name, keyname, pw,
212                  min_api_level=min_api_level,
213                  codename_to_api_level_map=codename_to_api_level_map)
214
215  data = None
216  if is_compressed:
217    # Recompress the file after it has been signed.
218    compressed = tempfile.NamedTemporaryFile()
219    with open(signed.name, "rb") as in_file, \
220         gzip.open(compressed.name, "wb") as out_file:
221      shutil.copyfileobj(in_file, out_file)
222
223    data = compressed.read()
224    compressed.close()
225  else:
226    data = signed.read()
227
228  unsigned.close()
229  signed.close()
230
231  return data
232
233
234def ProcessTargetFiles(input_tf_zip, output_tf_zip, misc_info,
235                       apk_key_map, key_passwords, platform_api_level,
236                       codename_to_api_level_map,
237                       compressed_extension):
238
239  compressed_apk_extension = None
240  if compressed_extension:
241    compressed_apk_extension = ".apk" + compressed_extension
242
243  maxsize = max(
244      [len(os.path.basename(i.filename)) for i in input_tf_zip.infolist()
245       if (i.filename.endswith('.apk') or
246           (compressed_apk_extension and
247            i.filename.endswith(compressed_apk_extension)))])
248  system_root_image = misc_info.get("system_root_image") == "true"
249
250  for info in input_tf_zip.infolist():
251    if info.filename.startswith("IMAGES/"):
252      continue
253
254    data = input_tf_zip.read(info.filename)
255    out_info = copy.copy(info)
256
257    # Sign APKs.
258    if (info.filename.endswith(".apk") or
259        (compressed_apk_extension and
260         info.filename.endswith(compressed_apk_extension))):
261      is_compressed = (compressed_extension and
262                       info.filename.endswith(compressed_apk_extension))
263      name = os.path.basename(info.filename)
264      if is_compressed:
265        name = name[:-len(compressed_extension)]
266
267      key = apk_key_map[name]
268      if key not in common.SPECIAL_CERT_STRINGS:
269        print("    signing: %-*s (%s)" % (maxsize, name, key))
270        signed_data = SignApk(data, key, key_passwords[key], platform_api_level,
271                              codename_to_api_level_map, is_compressed)
272        common.ZipWriteStr(output_tf_zip, out_info, signed_data)
273      else:
274        # an APK we're not supposed to sign.
275        print("NOT signing: %s" % (name,))
276        common.ZipWriteStr(output_tf_zip, out_info, data)
277
278    # System properties.
279    elif info.filename in ("SYSTEM/build.prop",
280                           "VENDOR/build.prop",
281                           "SYSTEM/etc/prop.default",
282                           "BOOT/RAMDISK/prop.default",
283                           "BOOT/RAMDISK/default.prop",  # legacy
284                           "ROOT/default.prop",  # legacy
285                           "RECOVERY/RAMDISK/prop.default",
286                           "RECOVERY/RAMDISK/default.prop"):  # legacy
287      print("Rewriting %s:" % (info.filename,))
288      if stat.S_ISLNK(info.external_attr >> 16):
289        new_data = data
290      else:
291        new_data = RewriteProps(data)
292      common.ZipWriteStr(output_tf_zip, out_info, new_data)
293
294    # Replace the certs in *mac_permissions.xml (there could be multiple, such
295    # as {system,vendor}/etc/selinux/{plat,nonplat}_mac_permissions.xml).
296    elif info.filename.endswith("mac_permissions.xml"):
297      print("Rewriting %s with new keys." % (info.filename,))
298      new_data = ReplaceCerts(data)
299      common.ZipWriteStr(output_tf_zip, out_info, new_data)
300
301    # Ask add_img_to_target_files to rebuild the recovery patch if needed.
302    elif info.filename in ("SYSTEM/recovery-from-boot.p",
303                           "SYSTEM/etc/recovery.img",
304                           "SYSTEM/bin/install-recovery.sh"):
305      OPTIONS.rebuild_recovery = True
306
307    # Don't copy OTA keys if we're replacing them.
308    elif (OPTIONS.replace_ota_keys and
309          info.filename in (
310              "BOOT/RAMDISK/res/keys",
311              "BOOT/RAMDISK/etc/update_engine/update-payload-key.pub.pem",
312              "RECOVERY/RAMDISK/res/keys",
313              "SYSTEM/etc/security/otacerts.zip",
314              "SYSTEM/etc/update_engine/update-payload-key.pub.pem")):
315      pass
316
317    # Skip META/misc_info.txt since we will write back the new values later.
318    elif info.filename == "META/misc_info.txt":
319      pass
320
321    # Skip verity public key if we will replace it.
322    elif (OPTIONS.replace_verity_public_key and
323          info.filename in ("BOOT/RAMDISK/verity_key",
324                            "ROOT/verity_key")):
325      pass
326
327    # Skip verity keyid (for system_root_image use) if we will replace it.
328    elif (OPTIONS.replace_verity_keyid and
329          info.filename == "BOOT/cmdline"):
330      pass
331
332    # Skip the care_map as we will regenerate the system/vendor images.
333    elif info.filename == "META/care_map.txt":
334      pass
335
336    # A non-APK file; copy it verbatim.
337    else:
338      common.ZipWriteStr(output_tf_zip, out_info, data)
339
340  if OPTIONS.replace_ota_keys:
341    ReplaceOtaKeys(input_tf_zip, output_tf_zip, misc_info)
342
343  # Replace the keyid string in misc_info dict.
344  if OPTIONS.replace_verity_private_key:
345    ReplaceVerityPrivateKey(misc_info, OPTIONS.replace_verity_private_key[1])
346
347  if OPTIONS.replace_verity_public_key:
348    dest = "ROOT/verity_key" if system_root_image else "BOOT/RAMDISK/verity_key"
349    # We are replacing the one in boot image only, since the one under
350    # recovery won't ever be needed.
351    ReplaceVerityPublicKey(
352        output_tf_zip, dest, OPTIONS.replace_verity_public_key[1])
353
354  # Replace the keyid string in BOOT/cmdline.
355  if OPTIONS.replace_verity_keyid:
356    ReplaceVerityKeyId(input_tf_zip, output_tf_zip,
357                       OPTIONS.replace_verity_keyid[1])
358
359  # Replace the AVB signing keys, if any.
360  ReplaceAvbSigningKeys(misc_info)
361
362  # Write back misc_info with the latest values.
363  ReplaceMiscInfoTxt(input_tf_zip, output_tf_zip, misc_info)
364
365
366def ReplaceCerts(data):
367  """Replaces all the occurences of X.509 certs with the new ones.
368
369  The mapping info is read from OPTIONS.key_map. Non-existent certificate will
370  be skipped. After the replacement, it additionally checks for duplicate
371  entries, which would otherwise fail the policy loading code in
372  frameworks/base/services/core/java/com/android/server/pm/SELinuxMMAC.java.
373
374  Args:
375    data: Input string that contains a set of X.509 certs.
376
377  Returns:
378    A string after the replacement.
379
380  Raises:
381    AssertionError: On finding duplicate entries.
382  """
383  for old, new in OPTIONS.key_map.iteritems():
384    if OPTIONS.verbose:
385      print("    Replacing %s.x509.pem with %s.x509.pem" % (old, new))
386
387    try:
388      with open(old + ".x509.pem") as old_fp:
389        old_cert16 = base64.b16encode(
390            common.ParseCertificate(old_fp.read())).lower()
391      with open(new + ".x509.pem") as new_fp:
392        new_cert16 = base64.b16encode(
393            common.ParseCertificate(new_fp.read())).lower()
394    except IOError as e:
395      if OPTIONS.verbose or e.errno != errno.ENOENT:
396        print("    Error accessing %s: %s.\nSkip replacing %s.x509.pem with "
397              "%s.x509.pem." % (e.filename, e.strerror, old, new))
398      continue
399
400    # Only match entire certs.
401    pattern = "\\b" + old_cert16 + "\\b"
402    (data, num) = re.subn(pattern, new_cert16, data, flags=re.IGNORECASE)
403
404    if OPTIONS.verbose:
405      print("    Replaced %d occurence(s) of %s.x509.pem with %s.x509.pem" % (
406          num, old, new))
407
408  # Verify that there're no duplicate entries after the replacement. Note that
409  # it's only checking entries with global seinfo at the moment (i.e. ignoring
410  # the ones with inner packages). (Bug: 69479366)
411  root = ElementTree.fromstring(data)
412  signatures = [signer.attrib['signature'] for signer in root.findall('signer')]
413  assert len(signatures) == len(set(signatures)), \
414      "Found duplicate entries after cert replacement: {}".format(data)
415
416  return data
417
418
419def EditTags(tags):
420  """Applies the edits to the tag string as specified in OPTIONS.tag_changes.
421
422  Args:
423    tags: The input string that contains comma-separated tags.
424
425  Returns:
426    The updated tags (comma-separated and sorted).
427  """
428  tags = set(tags.split(","))
429  for ch in OPTIONS.tag_changes:
430    if ch[0] == "-":
431      tags.discard(ch[1:])
432    elif ch[0] == "+":
433      tags.add(ch[1:])
434  return ",".join(sorted(tags))
435
436
437def RewriteProps(data):
438  """Rewrites the system properties in the given string.
439
440  Each property is expected in 'key=value' format. The properties that contain
441  build tags (i.e. test-keys, dev-keys) will be updated accordingly by calling
442  EditTags().
443
444  Args:
445    data: Input string, separated by newlines.
446
447  Returns:
448    The string with modified properties.
449  """
450  output = []
451  for line in data.split("\n"):
452    line = line.strip()
453    original_line = line
454    if line and line[0] != '#' and "=" in line:
455      key, value = line.split("=", 1)
456      if key in ("ro.build.fingerprint", "ro.build.thumbprint",
457                 "ro.vendor.build.fingerprint", "ro.vendor.build.thumbprint"):
458        pieces = value.split("/")
459        pieces[-1] = EditTags(pieces[-1])
460        value = "/".join(pieces)
461      elif key == "ro.bootimage.build.fingerprint":
462        pieces = value.split("/")
463        pieces[-1] = EditTags(pieces[-1])
464        value = "/".join(pieces)
465      elif key == "ro.build.description":
466        pieces = value.split(" ")
467        assert len(pieces) == 5
468        pieces[-1] = EditTags(pieces[-1])
469        value = " ".join(pieces)
470      elif key == "ro.build.tags":
471        value = EditTags(value)
472      elif key == "ro.build.display.id":
473        # change, eg, "JWR66N dev-keys" to "JWR66N"
474        value = value.split()
475        if len(value) > 1 and value[-1].endswith("-keys"):
476          value.pop()
477        value = " ".join(value)
478      line = key + "=" + value
479    if line != original_line:
480      print("  replace: ", original_line)
481      print("     with: ", line)
482    output.append(line)
483  return "\n".join(output) + "\n"
484
485
486def ReplaceOtaKeys(input_tf_zip, output_tf_zip, misc_info):
487  try:
488    keylist = input_tf_zip.read("META/otakeys.txt").split()
489  except KeyError:
490    raise common.ExternalError("can't read META/otakeys.txt from input")
491
492  extra_recovery_keys = misc_info.get("extra_recovery_keys")
493  if extra_recovery_keys:
494    extra_recovery_keys = [OPTIONS.key_map.get(k, k) + ".x509.pem"
495                           for k in extra_recovery_keys.split()]
496    if extra_recovery_keys:
497      print("extra recovery-only key(s): " + ", ".join(extra_recovery_keys))
498  else:
499    extra_recovery_keys = []
500
501  mapped_keys = []
502  for k in keylist:
503    m = re.match(r"^(.*)\.x509\.pem$", k)
504    if not m:
505      raise common.ExternalError(
506          "can't parse \"%s\" from META/otakeys.txt" % (k,))
507    k = m.group(1)
508    mapped_keys.append(OPTIONS.key_map.get(k, k) + ".x509.pem")
509
510  if mapped_keys:
511    print("using:\n   ", "\n   ".join(mapped_keys))
512    print("for OTA package verification")
513  else:
514    devkey = misc_info.get("default_system_dev_certificate",
515                           "build/target/product/security/testkey")
516    mapped_devkey = OPTIONS.key_map.get(devkey, devkey)
517    if mapped_devkey != devkey:
518      misc_info["default_system_dev_certificate"] = mapped_devkey
519    mapped_keys.append(mapped_devkey + ".x509.pem")
520    print("META/otakeys.txt has no keys; using %s for OTA package"
521          " verification." % (mapped_keys[0],))
522
523  # recovery uses a version of the key that has been slightly
524  # predigested (by DumpPublicKey.java) and put in res/keys.
525  # extra_recovery_keys are used only in recovery.
526  cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
527         ["-jar",
528          os.path.join(OPTIONS.search_path, "framework", "dumpkey.jar")] +
529         mapped_keys + extra_recovery_keys)
530  p = common.Run(cmd, stdout=subprocess.PIPE)
531  new_recovery_keys, _ = p.communicate()
532  if p.returncode != 0:
533    raise common.ExternalError("failed to run dumpkeys")
534
535  # system_root_image puts the recovery keys at BOOT/RAMDISK.
536  if misc_info.get("system_root_image") == "true":
537    recovery_keys_location = "BOOT/RAMDISK/res/keys"
538  else:
539    recovery_keys_location = "RECOVERY/RAMDISK/res/keys"
540  common.ZipWriteStr(output_tf_zip, recovery_keys_location, new_recovery_keys)
541
542  # SystemUpdateActivity uses the x509.pem version of the keys, but
543  # put into a zipfile system/etc/security/otacerts.zip.
544  # We DO NOT include the extra_recovery_keys (if any) here.
545
546  try:
547    from StringIO import StringIO
548  except ImportError:
549    from io import StringIO
550  temp_file = StringIO()
551  certs_zip = zipfile.ZipFile(temp_file, "w")
552  for k in mapped_keys:
553    common.ZipWrite(certs_zip, k)
554  common.ZipClose(certs_zip)
555  common.ZipWriteStr(output_tf_zip, "SYSTEM/etc/security/otacerts.zip",
556                     temp_file.getvalue())
557
558  # For A/B devices, update the payload verification key.
559  if misc_info.get("ab_update") == "true":
560    # Unlike otacerts.zip that may contain multiple keys, we can only specify
561    # ONE payload verification key.
562    if len(mapped_keys) > 1:
563      print("\n  WARNING: Found more than one OTA keys; Using the first one"
564            " as payload verification key.\n\n")
565
566    print("Using %s for payload verification." % (mapped_keys[0],))
567    pubkey = common.ExtractPublicKey(mapped_keys[0])
568    common.ZipWriteStr(
569        output_tf_zip,
570        "SYSTEM/etc/update_engine/update-payload-key.pub.pem",
571        pubkey)
572    common.ZipWriteStr(
573        output_tf_zip,
574        "BOOT/RAMDISK/etc/update_engine/update-payload-key.pub.pem",
575        pubkey)
576
577  return new_recovery_keys
578
579
580def ReplaceVerityPublicKey(output_zip, filename, key_path):
581  """Replaces the verity public key at the given path in the given zip.
582
583  Args:
584    output_zip: The output target_files zip.
585    filename: The archive name in the output zip.
586    key_path: The path to the public key.
587  """
588  print("Replacing verity public key with %s" % (key_path,))
589  common.ZipWrite(output_zip, key_path, arcname=filename)
590
591
592def ReplaceVerityPrivateKey(misc_info, key_path):
593  """Replaces the verity private key in misc_info dict.
594
595  Args:
596    misc_info: The info dict.
597    key_path: The path to the private key in PKCS#8 format.
598  """
599  print("Replacing verity private key with %s" % (key_path,))
600  misc_info["verity_key"] = key_path
601
602
603def ReplaceVerityKeyId(input_zip, output_zip, key_path):
604  """Replaces the veritykeyid parameter in BOOT/cmdline.
605
606  Args:
607    input_zip: The input target_files zip, which should be already open.
608    output_zip: The output target_files zip, which should be already open and
609        writable.
610    key_path: The path to the PEM encoded X.509 certificate.
611  """
612  in_cmdline = input_zip.read("BOOT/cmdline")
613  # Copy in_cmdline to output_zip if veritykeyid is not present.
614  if "veritykeyid" not in in_cmdline:
615    common.ZipWriteStr(output_zip, "BOOT/cmdline", in_cmdline)
616    return
617
618  out_buffer = []
619  for param in in_cmdline.split():
620    if "veritykeyid" not in param:
621      out_buffer.append(param)
622      continue
623
624    # Extract keyid using openssl command.
625    p = common.Run(["openssl", "x509", "-in", key_path, "-text"],
626                   stdout=subprocess.PIPE, stderr=subprocess.PIPE)
627    keyid, stderr = p.communicate()
628    assert p.returncode == 0, "Failed to dump certificate: {}".format(stderr)
629    keyid = re.search(
630        r'keyid:([0-9a-fA-F:]*)', keyid).group(1).replace(':', '').lower()
631    print("Replacing verity keyid with {}".format(keyid))
632    out_buffer.append("veritykeyid=id:%s" % (keyid,))
633
634  out_cmdline = ' '.join(out_buffer).strip() + '\n'
635  common.ZipWriteStr(output_zip, "BOOT/cmdline", out_cmdline)
636
637
638def ReplaceMiscInfoTxt(input_zip, output_zip, misc_info):
639  """Replaces META/misc_info.txt.
640
641  Only writes back the ones in the original META/misc_info.txt. Because the
642  current in-memory dict contains additional items computed at runtime.
643  """
644  misc_info_old = common.LoadDictionaryFromLines(
645      input_zip.read('META/misc_info.txt').split('\n'))
646  items = []
647  for key in sorted(misc_info):
648    if key in misc_info_old:
649      items.append('%s=%s' % (key, misc_info[key]))
650  common.ZipWriteStr(output_zip, "META/misc_info.txt", '\n'.join(items))
651
652
653def ReplaceAvbSigningKeys(misc_info):
654  """Replaces the AVB signing keys."""
655
656  AVB_FOOTER_ARGS_BY_PARTITION = {
657      'boot' : 'avb_boot_add_hash_footer_args',
658      'dtbo' : 'avb_dtbo_add_hash_footer_args',
659      'recovery' : 'avb_recovery_add_hash_footer_args',
660      'system' : 'avb_system_add_hashtree_footer_args',
661      'vendor' : 'avb_vendor_add_hashtree_footer_args',
662      'vbmeta' : 'avb_vbmeta_args',
663  }
664
665  def ReplaceAvbPartitionSigningKey(partition):
666    key = OPTIONS.avb_keys.get(partition)
667    if not key:
668      return
669
670    algorithm = OPTIONS.avb_algorithms.get(partition)
671    assert algorithm, 'Missing AVB signing algorithm for %s' % (partition,)
672
673    print('Replacing AVB signing key for %s with "%s" (%s)' % (
674        partition, key, algorithm))
675    misc_info['avb_' + partition + '_algorithm'] = algorithm
676    misc_info['avb_' + partition + '_key_path'] = key
677
678    extra_args = OPTIONS.avb_extra_args.get(partition)
679    if extra_args:
680      print('Setting extra AVB signing args for %s to "%s"' % (
681          partition, extra_args))
682      args_key = AVB_FOOTER_ARGS_BY_PARTITION[partition]
683      misc_info[args_key] = (misc_info.get(args_key, '') + ' ' + extra_args)
684
685  for partition in AVB_FOOTER_ARGS_BY_PARTITION:
686    ReplaceAvbPartitionSigningKey(partition)
687
688
689def BuildKeyMap(misc_info, key_mapping_options):
690  for s, d in key_mapping_options:
691    if s is None:   # -d option
692      devkey = misc_info.get("default_system_dev_certificate",
693                             "build/target/product/security/testkey")
694      devkeydir = os.path.dirname(devkey)
695
696      OPTIONS.key_map.update({
697          devkeydir + "/testkey":  d + "/releasekey",
698          devkeydir + "/devkey":   d + "/releasekey",
699          devkeydir + "/media":    d + "/media",
700          devkeydir + "/shared":   d + "/shared",
701          devkeydir + "/platform": d + "/platform",
702          })
703    else:
704      OPTIONS.key_map[s] = d
705
706
707def GetApiLevelAndCodename(input_tf_zip):
708  data = input_tf_zip.read("SYSTEM/build.prop")
709  api_level = None
710  codename = None
711  for line in data.split("\n"):
712    line = line.strip()
713    if line and line[0] != '#' and "=" in line:
714      key, value = line.split("=", 1)
715      key = key.strip()
716      if key == "ro.build.version.sdk":
717        api_level = int(value.strip())
718      elif key == "ro.build.version.codename":
719        codename = value.strip()
720
721  if api_level is None:
722    raise ValueError("No ro.build.version.sdk in SYSTEM/build.prop")
723  if codename is None:
724    raise ValueError("No ro.build.version.codename in SYSTEM/build.prop")
725
726  return (api_level, codename)
727
728
729def GetCodenameToApiLevelMap(input_tf_zip):
730  data = input_tf_zip.read("SYSTEM/build.prop")
731  api_level = None
732  codenames = None
733  for line in data.split("\n"):
734    line = line.strip()
735    if line and line[0] != '#' and "=" in line:
736      key, value = line.split("=", 1)
737      key = key.strip()
738      if key == "ro.build.version.sdk":
739        api_level = int(value.strip())
740      elif key == "ro.build.version.all_codenames":
741        codenames = value.strip().split(",")
742
743  if api_level is None:
744    raise ValueError("No ro.build.version.sdk in SYSTEM/build.prop")
745  if codenames is None:
746    raise ValueError("No ro.build.version.all_codenames in SYSTEM/build.prop")
747
748  result = dict()
749  for codename in codenames:
750    codename = codename.strip()
751    if len(codename) > 0:
752      result[codename] = api_level
753  return result
754
755
756def main(argv):
757
758  key_mapping_options = []
759
760  def option_handler(o, a):
761    if o in ("-e", "--extra_apks"):
762      names, key = a.split("=")
763      names = names.split(",")
764      for n in names:
765        OPTIONS.extra_apks[n] = key
766    elif o in ("-d", "--default_key_mappings"):
767      key_mapping_options.append((None, a))
768    elif o in ("-k", "--key_mapping"):
769      key_mapping_options.append(a.split("=", 1))
770    elif o in ("-o", "--replace_ota_keys"):
771      OPTIONS.replace_ota_keys = True
772    elif o in ("-t", "--tag_changes"):
773      new = []
774      for i in a.split(","):
775        i = i.strip()
776        if not i or i[0] not in "-+":
777          raise ValueError("Bad tag change '%s'" % (i,))
778        new.append(i[0] + i[1:].strip())
779      OPTIONS.tag_changes = tuple(new)
780    elif o == "--replace_verity_public_key":
781      OPTIONS.replace_verity_public_key = (True, a)
782    elif o == "--replace_verity_private_key":
783      OPTIONS.replace_verity_private_key = (True, a)
784    elif o == "--replace_verity_keyid":
785      OPTIONS.replace_verity_keyid = (True, a)
786    elif o == "--avb_vbmeta_key":
787      OPTIONS.avb_keys['vbmeta'] = a
788    elif o == "--avb_vbmeta_algorithm":
789      OPTIONS.avb_algorithms['vbmeta'] = a
790    elif o == "--avb_vbmeta_extra_args":
791      OPTIONS.avb_extra_args['vbmeta'] = a
792    elif o == "--avb_boot_key":
793      OPTIONS.avb_keys['boot'] = a
794    elif o == "--avb_boot_algorithm":
795      OPTIONS.avb_algorithms['boot'] = a
796    elif o == "--avb_boot_extra_args":
797      OPTIONS.avb_extra_args['boot'] = a
798    elif o == "--avb_dtbo_key":
799      OPTIONS.avb_keys['dtbo'] = a
800    elif o == "--avb_dtbo_algorithm":
801      OPTIONS.avb_algorithms['dtbo'] = a
802    elif o == "--avb_dtbo_extra_args":
803      OPTIONS.avb_extra_args['dtbo'] = a
804    elif o == "--avb_system_key":
805      OPTIONS.avb_keys['system'] = a
806    elif o == "--avb_system_algorithm":
807      OPTIONS.avb_algorithms['system'] = a
808    elif o == "--avb_system_extra_args":
809      OPTIONS.avb_extra_args['system'] = a
810    elif o == "--avb_vendor_key":
811      OPTIONS.avb_keys['vendor'] = a
812    elif o == "--avb_vendor_algorithm":
813      OPTIONS.avb_algorithms['vendor'] = a
814    elif o == "--avb_vendor_extra_args":
815      OPTIONS.avb_extra_args['vendor'] = a
816    else:
817      return False
818    return True
819
820  args = common.ParseOptions(
821      argv, __doc__,
822      extra_opts="e:d:k:ot:",
823      extra_long_opts=[
824          "extra_apks=",
825          "default_key_mappings=",
826          "key_mapping=",
827          "replace_ota_keys",
828          "tag_changes=",
829          "replace_verity_public_key=",
830          "replace_verity_private_key=",
831          "replace_verity_keyid=",
832          "avb_vbmeta_algorithm=",
833          "avb_vbmeta_key=",
834          "avb_vbmeta_extra_args=",
835          "avb_boot_algorithm=",
836          "avb_boot_key=",
837          "avb_boot_extra_args=",
838          "avb_dtbo_algorithm=",
839          "avb_dtbo_key=",
840          "avb_dtbo_extra_args=",
841          "avb_system_algorithm=",
842          "avb_system_key=",
843          "avb_system_extra_args=",
844          "avb_vendor_algorithm=",
845          "avb_vendor_key=",
846          "avb_vendor_extra_args=",
847      ],
848      extra_option_handler=option_handler)
849
850  if len(args) != 2:
851    common.Usage(__doc__)
852    sys.exit(1)
853
854  input_zip = zipfile.ZipFile(args[0], "r")
855  output_zip = zipfile.ZipFile(args[1], "w",
856                               compression=zipfile.ZIP_DEFLATED,
857                               allowZip64=True)
858
859  misc_info = common.LoadInfoDict(input_zip)
860
861  BuildKeyMap(misc_info, key_mapping_options)
862
863  certmap, compressed_extension = common.ReadApkCerts(input_zip)
864  apk_key_map = GetApkCerts(certmap)
865  CheckAllApksSigned(input_zip, apk_key_map, compressed_extension)
866
867  key_passwords = common.GetKeyPasswords(set(apk_key_map.values()))
868  platform_api_level, _ = GetApiLevelAndCodename(input_zip)
869  codename_to_api_level_map = GetCodenameToApiLevelMap(input_zip)
870
871  ProcessTargetFiles(input_zip, output_zip, misc_info,
872                     apk_key_map, key_passwords,
873                     platform_api_level,
874                     codename_to_api_level_map,
875                     compressed_extension)
876
877  common.ZipClose(input_zip)
878  common.ZipClose(output_zip)
879
880  # Skip building userdata.img and cache.img when signing the target files.
881  new_args = ["--is_signing"]
882  # add_img_to_target_files builds the system image from scratch, so the
883  # recovery patch is guaranteed to be regenerated there.
884  if OPTIONS.rebuild_recovery:
885    new_args.append("--rebuild_recovery")
886  new_args.append(args[1])
887  add_img_to_target_files.main(new_args)
888
889  print("done.")
890
891
892if __name__ == '__main__':
893  try:
894    main(sys.argv[1:])
895  except common.ExternalError as e:
896    print("\n   ERROR: %s\n" % (e,))
897    sys.exit(1)
898  finally:
899    common.Cleanup()
900