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 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 207def 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 266def 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 284def 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 347if __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