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