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