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