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 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 68 import sys 69 70 if sys.hexversion < 0x02040000: 71 print >> sys.stderr, "Python 2.4 or newer is required." 72 sys.exit(1) 73 74 import cStringIO 75 import copy 76 import os 77 import re 78 import subprocess 79 import tempfile 80 import zipfile 81 82 import common 83 84 OPTIONS = common.OPTIONS 85 86 OPTIONS.extra_apks = {} 87 OPTIONS.key_map = {} 88 OPTIONS.replace_ota_keys = False 89 OPTIONS.tag_changes = ("-test-keys", "-dev-keys", "+release-keys") 90 91 def 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 107 def 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 124 def 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 140 def 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 169 def 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 181 def 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 line = key + "=" + value 200 if line != original_line: 201 print " replace: ", original_line 202 print " with: ", line 203 output.append(line) 204 return "\n".join(output) + "\n" 205 206 207 def ReplaceOtaKeys(input_tf_zip, output_tf_zip, misc_info): 208 try: 209 keylist = input_tf_zip.read("META/otakeys.txt").split() 210 except KeyError: 211 raise ExternalError("can't read META/otakeys.txt from input") 212 213 extra_recovery_keys = misc_info.get("extra_recovery_keys", None) 214 if extra_recovery_keys: 215 extra_recovery_keys = [OPTIONS.key_map.get(k, k) + ".x509.pem" 216 for k in extra_recovery_keys.split()] 217 if extra_recovery_keys: 218 print "extra recovery-only key(s): " + ", ".join(extra_recovery_keys) 219 else: 220 extra_recovery_keys = [] 221 222 mapped_keys = [] 223 for k in keylist: 224 m = re.match(r"^(.*)\.x509\.pem$", k) 225 if not m: 226 raise ExternalError("can't parse \"%s\" from META/otakeys.txt" % (k,)) 227 k = m.group(1) 228 mapped_keys.append(OPTIONS.key_map.get(k, k) + ".x509.pem") 229 230 if mapped_keys: 231 print "using:\n ", "\n ".join(mapped_keys) 232 print "for OTA package verification" 233 else: 234 devkey = misc_info.get("default_system_dev_certificate", 235 "build/target/product/security/testkey") 236 mapped_keys.append( 237 OPTIONS.key_map.get(devkey, devkey) + ".x509.pem") 238 print "META/otakeys.txt has no keys; using", mapped_keys[0] 239 240 # recovery uses a version of the key that has been slightly 241 # predigested (by DumpPublicKey.java) and put in res/keys. 242 # extra_recovery_keys are used only in recovery. 243 244 p = common.Run(["java", "-jar", 245 os.path.join(OPTIONS.search_path, "framework", "dumpkey.jar")] 246 + mapped_keys + extra_recovery_keys, 247 stdout=subprocess.PIPE) 248 data, _ = p.communicate() 249 if p.returncode != 0: 250 raise ExternalError("failed to run dumpkeys") 251 common.ZipWriteStr(output_tf_zip, "RECOVERY/RAMDISK/res/keys", data) 252 253 # SystemUpdateActivity uses the x509.pem version of the keys, but 254 # put into a zipfile system/etc/security/otacerts.zip. 255 # We DO NOT include the extra_recovery_keys (if any) here. 256 257 tempfile = cStringIO.StringIO() 258 certs_zip = zipfile.ZipFile(tempfile, "w") 259 for k in mapped_keys: 260 certs_zip.write(k) 261 certs_zip.close() 262 common.ZipWriteStr(output_tf_zip, "SYSTEM/etc/security/otacerts.zip", 263 tempfile.getvalue()) 264 265 266 def BuildKeyMap(misc_info, key_mapping_options): 267 for s, d in key_mapping_options: 268 if s is None: # -d option 269 devkey = misc_info.get("default_system_dev_certificate", 270 "build/target/product/security/testkey") 271 devkeydir = os.path.dirname(devkey) 272 273 OPTIONS.key_map.update({ 274 devkeydir + "/testkey": d + "/releasekey", 275 devkeydir + "/devkey": d + "/releasekey", 276 devkeydir + "/media": d + "/media", 277 devkeydir + "/shared": d + "/shared", 278 devkeydir + "/platform": d + "/platform", 279 }) 280 else: 281 OPTIONS.key_map[s] = d 282 283 284 def main(argv): 285 286 key_mapping_options = [] 287 288 def option_handler(o, a): 289 if o in ("-e", "--extra_apks"): 290 names, key = a.split("=") 291 names = names.split(",") 292 for n in names: 293 OPTIONS.extra_apks[n] = key 294 elif o in ("-d", "--default_key_mappings"): 295 key_mapping_options.append((None, a)) 296 elif o in ("-k", "--key_mapping"): 297 key_mapping_options.append(a.split("=", 1)) 298 elif o in ("-o", "--replace_ota_keys"): 299 OPTIONS.replace_ota_keys = True 300 elif o in ("-t", "--tag_changes"): 301 new = [] 302 for i in a.split(","): 303 i = i.strip() 304 if not i or i[0] not in "-+": 305 raise ValueError("Bad tag change '%s'" % (i,)) 306 new.append(i[0] + i[1:].strip()) 307 OPTIONS.tag_changes = tuple(new) 308 else: 309 return False 310 return True 311 312 args = common.ParseOptions(argv, __doc__, 313 extra_opts="e:d:k:ot:", 314 extra_long_opts=["extra_apks=", 315 "default_key_mappings=", 316 "key_mapping=", 317 "replace_ota_keys", 318 "tag_changes="], 319 extra_option_handler=option_handler) 320 321 if len(args) != 2: 322 common.Usage(__doc__) 323 sys.exit(1) 324 325 input_zip = zipfile.ZipFile(args[0], "r") 326 output_zip = zipfile.ZipFile(args[1], "w") 327 328 misc_info = common.LoadInfoDict(input_zip) 329 330 BuildKeyMap(misc_info, key_mapping_options) 331 332 apk_key_map = GetApkCerts(input_zip) 333 CheckAllApksSigned(input_zip, apk_key_map) 334 335 key_passwords = common.GetKeyPasswords(set(apk_key_map.values())) 336 SignApks(input_zip, output_zip, apk_key_map, key_passwords) 337 338 if OPTIONS.replace_ota_keys: 339 ReplaceOtaKeys(input_zip, output_zip, misc_info) 340 341 input_zip.close() 342 output_zip.close() 343 344 print "done." 345 346 347 if __name__ == '__main__': 348 try: 349 main(sys.argv[1:]) 350 except common.ExternalError, e: 351 print 352 print " ERROR: %s" % (e,) 353 print 354 sys.exit(1) 355