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 build/target/product/security/testkey ==> $dir/releasekey 40 build/target/product/security/media ==> $dir/media 41 build/target/product/security/shared ==> $dir/shared 42 build/target/product/security/platform ==> $dir/platform 43 44 -d and -k options are added to the set of mappings in the order 45 in which they appear on the command line. 46 47 -o (--replace_ota_keys) 48 Replace the certificate (public key) used by OTA package 49 verification with the one specified in the input target_files 50 zip (in the META/otakeys.txt file). Key remapping (-k and -d) 51 is performed on this key. 52 53 -t (--tag_changes) <+tag>,<-tag>,... 54 Comma-separated list of changes to make to the set of tags (in 55 the last component of the build fingerprint). Prefix each with 56 '+' or '-' to indicate whether that tag should be added or 57 removed. Changes are processed in the order they appear. 58 Default value is "-test-keys,+release-keys". 59 60 """ 61 62 import sys 63 64 if sys.hexversion < 0x02040000: 65 print >> sys.stderr, "Python 2.4 or newer is required." 66 sys.exit(1) 67 68 import cStringIO 69 import copy 70 import os 71 import re 72 import subprocess 73 import tempfile 74 import zipfile 75 76 import common 77 78 OPTIONS = common.OPTIONS 79 80 OPTIONS.extra_apks = {} 81 OPTIONS.key_map = {} 82 OPTIONS.replace_ota_keys = False 83 OPTIONS.tag_changes = ("-test-keys", "+release-keys") 84 85 def GetApkCerts(tf_zip): 86 certmap = common.ReadApkCerts(tf_zip) 87 88 # apply the key remapping to the contents of the file 89 for apk, cert in certmap.iteritems(): 90 certmap[apk] = OPTIONS.key_map.get(cert, cert) 91 92 # apply all the -e options, overriding anything in the file 93 for apk, cert in OPTIONS.extra_apks.iteritems(): 94 if not cert: 95 cert = "PRESIGNED" 96 certmap[apk] = OPTIONS.key_map.get(cert, cert) 97 98 return certmap 99 100 101 def CheckAllApksSigned(input_tf_zip, apk_key_map): 102 """Check that all the APKs we want to sign have keys specified, and 103 error out if they don't.""" 104 unknown_apks = [] 105 for info in input_tf_zip.infolist(): 106 if info.filename.endswith(".apk"): 107 name = os.path.basename(info.filename) 108 if name not in apk_key_map: 109 unknown_apks.append(name) 110 if unknown_apks: 111 print "ERROR: no key specified for:\n\n ", 112 print "\n ".join(unknown_apks) 113 print "\nUse '-e <apkname>=' to specify a key (which may be an" 114 print "empty string to not sign this apk)." 115 sys.exit(1) 116 117 118 def SignApk(data, keyname, pw): 119 unsigned = tempfile.NamedTemporaryFile() 120 unsigned.write(data) 121 unsigned.flush() 122 123 signed = tempfile.NamedTemporaryFile() 124 125 common.SignFile(unsigned.name, signed.name, keyname, pw, align=4) 126 127 data = signed.read() 128 unsigned.close() 129 signed.close() 130 131 return data 132 133 134 def SignApks(input_tf_zip, output_tf_zip, apk_key_map, key_passwords): 135 maxsize = max([len(os.path.basename(i.filename)) 136 for i in input_tf_zip.infolist() 137 if i.filename.endswith('.apk')]) 138 139 for info in input_tf_zip.infolist(): 140 data = input_tf_zip.read(info.filename) 141 out_info = copy.copy(info) 142 if info.filename.endswith(".apk"): 143 name = os.path.basename(info.filename) 144 key = apk_key_map[name] 145 if key not in common.SPECIAL_CERT_STRINGS: 146 print " signing: %-*s (%s)" % (maxsize, name, key) 147 signed_data = SignApk(data, key, key_passwords[key]) 148 output_tf_zip.writestr(out_info, signed_data) 149 else: 150 # an APK we're not supposed to sign. 151 print "NOT signing: %s" % (name,) 152 output_tf_zip.writestr(out_info, data) 153 elif info.filename in ("SYSTEM/build.prop", 154 "RECOVERY/RAMDISK/default.prop"): 155 print "rewriting %s:" % (info.filename,) 156 new_data = RewriteProps(data) 157 output_tf_zip.writestr(out_info, new_data) 158 else: 159 # a non-APK file; copy it verbatim 160 output_tf_zip.writestr(out_info, data) 161 162 163 def EditTags(tags): 164 """Given a string containing comma-separated tags, apply the edits 165 specified in OPTIONS.tag_changes and return the updated string.""" 166 tags = set(tags.split(",")) 167 for ch in OPTIONS.tag_changes: 168 if ch[0] == "-": 169 tags.discard(ch[1:]) 170 elif ch[0] == "+": 171 tags.add(ch[1:]) 172 return ",".join(sorted(tags)) 173 174 175 def RewriteProps(data): 176 output = [] 177 for line in data.split("\n"): 178 line = line.strip() 179 original_line = line 180 if line and line[0] != '#': 181 key, value = line.split("=", 1) 182 if key == "ro.build.fingerprint": 183 pieces = value.split("/") 184 pieces[-1] = EditTags(pieces[-1]) 185 value = "/".join(pieces) 186 elif key == "ro.build.description": 187 pieces = value.split(" ") 188 assert len(pieces) == 5 189 pieces[-1] = EditTags(pieces[-1]) 190 value = " ".join(pieces) 191 elif key == "ro.build.tags": 192 value = EditTags(value) 193 line = key + "=" + value 194 if line != original_line: 195 print " replace: ", original_line 196 print " with: ", line 197 output.append(line) 198 return "\n".join(output) + "\n" 199 200 201 def ReplaceOtaKeys(input_tf_zip, output_tf_zip): 202 try: 203 keylist = input_tf_zip.read("META/otakeys.txt").split() 204 except KeyError: 205 raise ExternalError("can't read META/otakeys.txt from input") 206 207 mapped_keys = [] 208 for k in keylist: 209 m = re.match(r"^(.*)\.x509\.pem$", k) 210 if not m: 211 raise ExternalError("can't parse \"%s\" from META/otakeys.txt" % (k,)) 212 k = m.group(1) 213 mapped_keys.append(OPTIONS.key_map.get(k, k) + ".x509.pem") 214 215 if mapped_keys: 216 print "using:\n ", "\n ".join(mapped_keys) 217 print "for OTA package verification" 218 else: 219 mapped_keys.append( 220 OPTIONS.key_map["build/target/product/security/testkey"] + ".x509.pem") 221 print "META/otakeys.txt has no keys; using", mapped_keys[0] 222 223 # recovery uses a version of the key that has been slightly 224 # predigested (by DumpPublicKey.java) and put in res/keys. 225 226 p = common.Run(["java", "-jar", 227 os.path.join(OPTIONS.search_path, "framework", "dumpkey.jar")] 228 + mapped_keys, 229 stdout=subprocess.PIPE) 230 data, _ = p.communicate() 231 if p.returncode != 0: 232 raise ExternalError("failed to run dumpkeys") 233 common.ZipWriteStr(output_tf_zip, "RECOVERY/RAMDISK/res/keys", data) 234 235 # SystemUpdateActivity uses the x509.pem version of the keys, but 236 # put into a zipfile system/etc/security/otacerts.zip. 237 238 tempfile = cStringIO.StringIO() 239 certs_zip = zipfile.ZipFile(tempfile, "w") 240 for k in mapped_keys: 241 certs_zip.write(k) 242 certs_zip.close() 243 common.ZipWriteStr(output_tf_zip, "SYSTEM/etc/security/otacerts.zip", 244 tempfile.getvalue()) 245 246 247 def main(argv): 248 249 def option_handler(o, a): 250 if o in ("-e", "--extra_apks"): 251 names, key = a.split("=") 252 names = names.split(",") 253 for n in names: 254 OPTIONS.extra_apks[n] = key 255 elif o in ("-d", "--default_key_mappings"): 256 OPTIONS.key_map.update({ 257 "build/target/product/security/testkey": "%s/releasekey" % (a,), 258 "build/target/product/security/media": "%s/media" % (a,), 259 "build/target/product/security/shared": "%s/shared" % (a,), 260 "build/target/product/security/platform": "%s/platform" % (a,), 261 }) 262 elif o in ("-k", "--key_mapping"): 263 s, d = a.split("=") 264 OPTIONS.key_map[s] = d 265 elif o in ("-o", "--replace_ota_keys"): 266 OPTIONS.replace_ota_keys = True 267 elif o in ("-t", "--tag_changes"): 268 new = [] 269 for i in a.split(","): 270 i = i.strip() 271 if not i or i[0] not in "-+": 272 raise ValueError("Bad tag change '%s'" % (i,)) 273 new.append(i[0] + i[1:].strip()) 274 OPTIONS.tag_changes = tuple(new) 275 else: 276 return False 277 return True 278 279 args = common.ParseOptions(argv, __doc__, 280 extra_opts="e:d:k:ot:", 281 extra_long_opts=["extra_apks=", 282 "default_key_mappings=", 283 "key_mapping=", 284 "replace_ota_keys", 285 "tag_changes="], 286 extra_option_handler=option_handler) 287 288 if len(args) != 2: 289 common.Usage(__doc__) 290 sys.exit(1) 291 292 input_zip = zipfile.ZipFile(args[0], "r") 293 output_zip = zipfile.ZipFile(args[1], "w") 294 295 apk_key_map = GetApkCerts(input_zip) 296 CheckAllApksSigned(input_zip, apk_key_map) 297 298 key_passwords = common.GetKeyPasswords(set(apk_key_map.values())) 299 SignApks(input_zip, output_zip, apk_key_map, key_passwords) 300 301 if OPTIONS.replace_ota_keys: 302 ReplaceOtaKeys(input_zip, output_zip) 303 304 input_zip.close() 305 output_zip.close() 306 307 print "done." 308 309 310 if __name__ == '__main__': 311 try: 312 main(sys.argv[1:]) 313 except common.ExternalError, e: 314 print 315 print " ERROR: %s" % (e,) 316 print 317 sys.exit(1) 318