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 """ 18 Signs all the APK files in a target-files zipfile, producing a new 19 target-files zip. 20 21 Usage: 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 83 import sys 84 85 if sys.hexversion < 0x02070000: 86 print >> sys.stderr, "Python 2.7 or newer is required." 87 sys.exit(1) 88 89 import base64 90 import cStringIO 91 import copy 92 import errno 93 import os 94 import re 95 import shutil 96 import stat 97 import subprocess 98 import tempfile 99 import zipfile 100 101 import add_img_to_target_files 102 import common 103 104 OPTIONS = common.OPTIONS 105 106 OPTIONS.extra_apks = {} 107 OPTIONS.key_map = {} 108 OPTIONS.replace_ota_keys = False 109 OPTIONS.replace_verity_public_key = False 110 OPTIONS.replace_verity_private_key = False 111 OPTIONS.replace_verity_keyid = False 112 OPTIONS.tag_changes = ("-test-keys", "-dev-keys", "+release-keys") 113 114 def 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 130 def 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 147 def 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 183 def 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 363 def 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 393 def 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 405 def 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 447 def 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 538 def 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 546 def 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 556 def 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 584 def 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 602 def 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 624 def 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 651 def 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 730 if __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