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