sign_target_files_apks revision 817c574d753191c52acd5787da02bb853d4ac090
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 55 verification with the one specified in the input target_files 56 zip (in the META/otakeys.txt file). Key remapping (-k and -d) 57 is performed on this key. 58 59 -t (--tag_changes) <+tag>,<-tag>,... 60 Comma-separated list of changes to make to the set of tags (in 61 the last component of the build fingerprint). Prefix each with 62 '+' or '-' to indicate whether that tag should be added or 63 removed. Changes are processed in the order they appear. 64 Default value is "-test-keys,-dev-keys,+release-keys". 65 66""" 67 68import sys 69 70if sys.hexversion < 0x02040000: 71 print >> sys.stderr, "Python 2.4 or newer is required." 72 sys.exit(1) 73 74import base64 75import cStringIO 76import copy 77import errno 78import os 79import re 80import subprocess 81import tempfile 82import zipfile 83 84import common 85 86OPTIONS = common.OPTIONS 87 88OPTIONS.extra_apks = {} 89OPTIONS.key_map = {} 90OPTIONS.replace_ota_keys = False 91OPTIONS.tag_changes = ("-test-keys", "-dev-keys", "+release-keys") 92 93def GetApkCerts(tf_zip): 94 certmap = common.ReadApkCerts(tf_zip) 95 96 # apply the key remapping to the contents of the file 97 for apk, cert in certmap.iteritems(): 98 certmap[apk] = OPTIONS.key_map.get(cert, cert) 99 100 # apply all the -e options, overriding anything in the file 101 for apk, cert in OPTIONS.extra_apks.iteritems(): 102 if not cert: 103 cert = "PRESIGNED" 104 certmap[apk] = OPTIONS.key_map.get(cert, cert) 105 106 return certmap 107 108 109def CheckAllApksSigned(input_tf_zip, apk_key_map): 110 """Check that all the APKs we want to sign have keys specified, and 111 error out if they don't.""" 112 unknown_apks = [] 113 for info in input_tf_zip.infolist(): 114 if info.filename.endswith(".apk"): 115 name = os.path.basename(info.filename) 116 if name not in apk_key_map: 117 unknown_apks.append(name) 118 if unknown_apks: 119 print "ERROR: no key specified for:\n\n ", 120 print "\n ".join(unknown_apks) 121 print "\nUse '-e <apkname>=' to specify a key (which may be an" 122 print "empty string to not sign this apk)." 123 sys.exit(1) 124 125 126def SignApk(data, keyname, pw): 127 unsigned = tempfile.NamedTemporaryFile() 128 unsigned.write(data) 129 unsigned.flush() 130 131 signed = tempfile.NamedTemporaryFile() 132 133 common.SignFile(unsigned.name, signed.name, keyname, pw, align=4) 134 135 data = signed.read() 136 unsigned.close() 137 signed.close() 138 139 return data 140 141 142def SignApks(input_tf_zip, output_tf_zip, apk_key_map, key_passwords): 143 maxsize = max([len(os.path.basename(i.filename)) 144 for i in input_tf_zip.infolist() 145 if i.filename.endswith('.apk')]) 146 147 for info in input_tf_zip.infolist(): 148 data = input_tf_zip.read(info.filename) 149 out_info = copy.copy(info) 150 if info.filename.endswith(".apk"): 151 name = os.path.basename(info.filename) 152 key = apk_key_map[name] 153 if key not in common.SPECIAL_CERT_STRINGS: 154 print " signing: %-*s (%s)" % (maxsize, name, key) 155 signed_data = SignApk(data, key, key_passwords[key]) 156 output_tf_zip.writestr(out_info, signed_data) 157 else: 158 # an APK we're not supposed to sign. 159 print "NOT signing: %s" % (name,) 160 output_tf_zip.writestr(out_info, data) 161 elif info.filename in ("SYSTEM/build.prop", 162 "RECOVERY/RAMDISK/default.prop"): 163 print "rewriting %s:" % (info.filename,) 164 new_data = RewriteProps(data) 165 output_tf_zip.writestr(out_info, new_data) 166 elif info.filename.endswith("mac_permissions.xml"): 167 print "rewriting %s with new keys." % (info.filename,) 168 new_data = ReplaceCerts(data) 169 output_tf_zip.writestr(out_info, new_data) 170 else: 171 # a non-APK file; copy it verbatim 172 output_tf_zip.writestr(out_info, data) 173 174 175def ReplaceCerts(data): 176 """Given a string of data, replace all occurences of a set 177 of X509 certs with a newer set of X509 certs and return 178 the updated data string.""" 179 for old, new in OPTIONS.key_map.iteritems(): 180 try: 181 if OPTIONS.verbose: 182 print " Replacing %s.x509.pem with %s.x509.pem" % (old, new) 183 f = open(old + ".x509.pem") 184 old_cert16 = base64.b16encode(common.ParseCertificate(f.read())).lower() 185 f.close() 186 f = open(new + ".x509.pem") 187 new_cert16 = base64.b16encode(common.ParseCertificate(f.read())).lower() 188 f.close() 189 # Only match entire certs. 190 pattern = "\\b"+old_cert16+"\\b" 191 (data, num) = re.subn(pattern, new_cert16, data, flags=re.IGNORECASE) 192 if OPTIONS.verbose: 193 print " Replaced %d occurence(s) of %s.x509.pem with " \ 194 "%s.x509.pem" % (num, old, new) 195 except IOError, e: 196 if (e.errno == errno.ENOENT and not OPTIONS.verbose): 197 continue 198 199 print " Error accessing %s. %s. Skip replacing %s.x509.pem " \ 200 "with %s.x509.pem." % (e.filename, e.strerror, old, new) 201 202 return data 203 204 205def EditTags(tags): 206 """Given a string containing comma-separated tags, apply the edits 207 specified in OPTIONS.tag_changes and return the updated string.""" 208 tags = set(tags.split(",")) 209 for ch in OPTIONS.tag_changes: 210 if ch[0] == "-": 211 tags.discard(ch[1:]) 212 elif ch[0] == "+": 213 tags.add(ch[1:]) 214 return ",".join(sorted(tags)) 215 216 217def RewriteProps(data): 218 output = [] 219 for line in data.split("\n"): 220 line = line.strip() 221 original_line = line 222 if line and line[0] != '#': 223 key, value = line.split("=", 1) 224 if key == "ro.build.fingerprint": 225 pieces = value.split("/") 226 pieces[-1] = EditTags(pieces[-1]) 227 value = "/".join(pieces) 228 elif key == "ro.build.description": 229 pieces = value.split(" ") 230 assert len(pieces) == 5 231 pieces[-1] = EditTags(pieces[-1]) 232 value = " ".join(pieces) 233 elif key == "ro.build.tags": 234 value = EditTags(value) 235 line = key + "=" + value 236 if line != original_line: 237 print " replace: ", original_line 238 print " with: ", line 239 output.append(line) 240 return "\n".join(output) + "\n" 241 242 243def ReplaceOtaKeys(input_tf_zip, output_tf_zip, misc_info): 244 try: 245 keylist = input_tf_zip.read("META/otakeys.txt").split() 246 except KeyError: 247 raise common.ExternalError("can't read META/otakeys.txt from input") 248 249 extra_recovery_keys = misc_info.get("extra_recovery_keys", None) 250 if extra_recovery_keys: 251 extra_recovery_keys = [OPTIONS.key_map.get(k, k) + ".x509.pem" 252 for k in extra_recovery_keys.split()] 253 if extra_recovery_keys: 254 print "extra recovery-only key(s): " + ", ".join(extra_recovery_keys) 255 else: 256 extra_recovery_keys = [] 257 258 mapped_keys = [] 259 for k in keylist: 260 m = re.match(r"^(.*)\.x509\.pem$", k) 261 if not m: 262 raise common.ExternalError("can't parse \"%s\" from META/otakeys.txt" % (k,)) 263 k = m.group(1) 264 mapped_keys.append(OPTIONS.key_map.get(k, k) + ".x509.pem") 265 266 if mapped_keys: 267 print "using:\n ", "\n ".join(mapped_keys) 268 print "for OTA package verification" 269 else: 270 devkey = misc_info.get("default_system_dev_certificate", 271 "build/target/product/security/testkey") 272 mapped_keys.append( 273 OPTIONS.key_map.get(devkey, devkey) + ".x509.pem") 274 print "META/otakeys.txt has no keys; using", mapped_keys[0] 275 276 # recovery uses a version of the key that has been slightly 277 # predigested (by DumpPublicKey.java) and put in res/keys. 278 # extra_recovery_keys are used only in recovery. 279 280 p = common.Run(["java", "-jar", 281 os.path.join(OPTIONS.search_path, "framework", "dumpkey.jar")] 282 + mapped_keys + extra_recovery_keys, 283 stdout=subprocess.PIPE) 284 data, _ = p.communicate() 285 if p.returncode != 0: 286 raise common.ExternalError("failed to run dumpkeys") 287 common.ZipWriteStr(output_tf_zip, "RECOVERY/RAMDISK/res/keys", data) 288 289 # SystemUpdateActivity uses the x509.pem version of the keys, but 290 # put into a zipfile system/etc/security/otacerts.zip. 291 # We DO NOT include the extra_recovery_keys (if any) here. 292 293 tempfile = cStringIO.StringIO() 294 certs_zip = zipfile.ZipFile(tempfile, "w") 295 for k in mapped_keys: 296 certs_zip.write(k) 297 certs_zip.close() 298 common.ZipWriteStr(output_tf_zip, "SYSTEM/etc/security/otacerts.zip", 299 tempfile.getvalue()) 300 301 302def BuildKeyMap(misc_info, key_mapping_options): 303 for s, d in key_mapping_options: 304 if s is None: # -d option 305 devkey = misc_info.get("default_system_dev_certificate", 306 "build/target/product/security/testkey") 307 devkeydir = os.path.dirname(devkey) 308 309 OPTIONS.key_map.update({ 310 devkeydir + "/testkey": d + "/releasekey", 311 devkeydir + "/devkey": d + "/releasekey", 312 devkeydir + "/media": d + "/media", 313 devkeydir + "/shared": d + "/shared", 314 devkeydir + "/platform": d + "/platform", 315 }) 316 else: 317 OPTIONS.key_map[s] = d 318 319 320def main(argv): 321 322 key_mapping_options = [] 323 324 def option_handler(o, a): 325 if o in ("-e", "--extra_apks"): 326 names, key = a.split("=") 327 names = names.split(",") 328 for n in names: 329 OPTIONS.extra_apks[n] = key 330 elif o in ("-d", "--default_key_mappings"): 331 key_mapping_options.append((None, a)) 332 elif o in ("-k", "--key_mapping"): 333 key_mapping_options.append(a.split("=", 1)) 334 elif o in ("-o", "--replace_ota_keys"): 335 OPTIONS.replace_ota_keys = True 336 elif o in ("-t", "--tag_changes"): 337 new = [] 338 for i in a.split(","): 339 i = i.strip() 340 if not i or i[0] not in "-+": 341 raise ValueError("Bad tag change '%s'" % (i,)) 342 new.append(i[0] + i[1:].strip()) 343 OPTIONS.tag_changes = tuple(new) 344 else: 345 return False 346 return True 347 348 args = common.ParseOptions(argv, __doc__, 349 extra_opts="e:d:k:ot:", 350 extra_long_opts=["extra_apks=", 351 "default_key_mappings=", 352 "key_mapping=", 353 "replace_ota_keys", 354 "tag_changes="], 355 extra_option_handler=option_handler) 356 357 if len(args) != 2: 358 common.Usage(__doc__) 359 sys.exit(1) 360 361 input_zip = zipfile.ZipFile(args[0], "r") 362 output_zip = zipfile.ZipFile(args[1], "w") 363 364 misc_info = common.LoadInfoDict(input_zip) 365 366 BuildKeyMap(misc_info, key_mapping_options) 367 368 apk_key_map = GetApkCerts(input_zip) 369 CheckAllApksSigned(input_zip, apk_key_map) 370 371 key_passwords = common.GetKeyPasswords(set(apk_key_map.values())) 372 SignApks(input_zip, output_zip, apk_key_map, key_passwords) 373 374 if OPTIONS.replace_ota_keys: 375 ReplaceOtaKeys(input_zip, output_zip, misc_info) 376 377 input_zip.close() 378 output_zip.close() 379 380 print "done." 381 382 383if __name__ == '__main__': 384 try: 385 main(sys.argv[1:]) 386 except common.ExternalError, e: 387 print 388 print " ERROR: %s" % (e,) 389 print 390 sys.exit(1) 391