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