sign_target_files_apks revision 17aa944001e7ae2425beec75d3ebc280413631ee
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 -s (--signapk_jar) <path> 24 Path of the signapks.jar file used to sign an individual APK 25 file. 26 27 -e (--extra_apks) <name,name,...=key> 28 Add extra APK name/key pairs as though they appeared in 29 apkcerts.txt (so mappings specified by -k and -d are applied). 30 Keys specified in -e override any value for that app contained 31 in the apkcerts.txt file. Option may be repeated to give 32 multiple extra packages. 33 34 -k (--key_mapping) <src_key=dest_key> 35 Add a mapping from the key name as specified in apkcerts.txt (the 36 src_key) to the real key you wish to sign the package with 37 (dest_key). Option may be repeated to give multiple key 38 mappings. 39 40 -d (--default_key_mappings) <dir> 41 Set up the following key mappings: 42 43 build/target/product/security/testkey ==> $dir/releasekey 44 build/target/product/security/media ==> $dir/media 45 build/target/product/security/shared ==> $dir/shared 46 build/target/product/security/platform ==> $dir/platform 47 48 -d and -k options are added to the set of mappings in the order 49 in which they appear on the command line. 50 51 -o (--replace_ota_keys) 52 Replace the certificate (public key) used by OTA package 53 verification with the one specified in the input target_files 54 zip (in the META/otakeys.txt file). Key remapping (-k and -d) 55 is performed on this key. 56 57 -t (--extra_tag) <tag> 58 A string which is added to the set of tags in the last component 59 of the build fingerprint. Option may be repeated to give 60 multiple extra tags. 61""" 62 63import sys 64 65if sys.hexversion < 0x02040000: 66 print >> sys.stderr, "Python 2.4 or newer is required." 67 sys.exit(1) 68 69import cStringIO 70import copy 71import os 72import re 73import subprocess 74import tempfile 75import zipfile 76 77import common 78 79OPTIONS = common.OPTIONS 80 81OPTIONS.extra_apks = {} 82OPTIONS.key_map = {} 83OPTIONS.replace_ota_keys = False 84OPTIONS.extra_tags = [] 85 86def GetApkCerts(tf_zip): 87 certmap = {} 88 for line in tf_zip.read("META/apkcerts.txt").split("\n"): 89 line = line.strip() 90 if not line: continue 91 m = re.match(r'^name="(.*)"\s+certificate="(.*)\.x509\.pem"\s+' 92 r'private_key="\2\.pk8"$', line) 93 if not m: 94 raise SigningError("failed to parse line from apkcerts.txt:\n" + line) 95 certmap[m.group(1)] = OPTIONS.key_map.get(m.group(2), m.group(2)) 96 for apk, cert in OPTIONS.extra_apks.iteritems(): 97 certmap[apk] = OPTIONS.key_map.get(cert, cert) 98 return certmap 99 100 101def SignApk(data, keyname, pw): 102 unsigned = tempfile.NamedTemporaryFile() 103 unsigned.write(data) 104 unsigned.flush() 105 106 signed = tempfile.NamedTemporaryFile() 107 108 common.SignFile(unsigned.name, signed.name, keyname, pw, align=4) 109 110 data = signed.read() 111 unsigned.close() 112 signed.close() 113 114 return data 115 116 117def SignApks(input_tf_zip, output_tf_zip): 118 apk_key_map = GetApkCerts(input_tf_zip) 119 120 maxsize = max([len(os.path.basename(i.filename)) 121 for i in input_tf_zip.infolist() 122 if i.filename.endswith('.apk')]) 123 124 # Check that all the APKs we want to sign have keys specified, and 125 # error out if they don't. Do this before prompting for key 126 # passwords in case we're going to fail anyway. 127 unknown_apks = [] 128 for info in input_tf_zip.infolist(): 129 if info.filename.endswith(".apk"): 130 name = os.path.basename(info.filename) 131 if name not in apk_key_map: 132 unknown_apks.append(name) 133 if unknown_apks: 134 print "ERROR: no key specified for:\n\n ", 135 print "\n ".join(unknown_apks) 136 print "\nUse '-e <apkname>=' to specify a key (which may be an" 137 print "empty string to not sign this apk)." 138 sys.exit(1) 139 140 key_passwords = common.GetKeyPasswords(set(apk_key_map.values())) 141 142 for info in input_tf_zip.infolist(): 143 data = input_tf_zip.read(info.filename) 144 out_info = copy.copy(info) 145 if info.filename.endswith(".apk"): 146 name = os.path.basename(info.filename) 147 key = apk_key_map[name] 148 if key: 149 print " signing: %-*s (%s)" % (maxsize, name, key) 150 signed_data = SignApk(data, key, key_passwords[key]) 151 output_tf_zip.writestr(out_info, signed_data) 152 else: 153 # an APK we're not supposed to sign. 154 print "NOT signing: %s" % (name,) 155 output_tf_zip.writestr(out_info, data) 156 elif info.filename in ("SYSTEM/build.prop", 157 "RECOVERY/RAMDISK/default.prop"): 158 print "rewriting %s:" % (info.filename,) 159 new_data = RewriteProps(data) 160 output_tf_zip.writestr(out_info, new_data) 161 else: 162 # a non-APK file; copy it verbatim 163 output_tf_zip.writestr(out_info, data) 164 165 166def RewriteProps(data): 167 output = [] 168 for line in data.split("\n"): 169 line = line.strip() 170 original_line = line 171 if line and line[0] != '#': 172 key, value = line.split("=", 1) 173 if key == "ro.build.fingerprint": 174 pieces = line.split("/") 175 tags = set(pieces[-1].split(",")) 176 if "test-keys" in tags: 177 tags.remove("test-keys") 178 tags.add("release-keys") 179 # TODO: from donut onwards, only add ota-rel-keys if -o is given. 180 tags.add("ota-rel-keys") 181 tags.update(OPTIONS.extra_tags) 182 line = "/".join(pieces[:-1] + [",".join(sorted(tags))]) 183 elif key == "ro.build.description": 184 pieces = line.split(" ") 185 assert len(pieces) == 5 186 tags = set(pieces[-1].split(",")) 187 if "test-keys" in tags: 188 tags.remove("test-keys") 189 tags.add("release-keys") 190 # TODO: from donut onwards, only add ota-rel-keys if -o is given. 191 tags.add("ota-rel-keys") 192 tags.update(OPTIONS.extra_tags) 193 line = " ".join(pieces[:-1] + [",".join(sorted(tags))]) 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 201def 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 print "using:\n ", "\n ".join(mapped_keys) 216 print "for OTA package verification" 217 218 # recovery uses a version of the key that has been slightly 219 # predigested (by DumpPublicKey.java) and put in res/keys. 220 221 p = common.Run(["java", "-jar", OPTIONS.dumpkey_jar] + mapped_keys, 222 stdout=subprocess.PIPE) 223 data, _ = p.communicate() 224 if p.returncode != 0: 225 raise ExternalError("failed to run dumpkeys") 226 output_tf_zip.writestr("RECOVERY/RAMDISK/res/keys", data) 227 228 # SystemUpdateActivity uses the x509.pem version of the keys, but 229 # put into a zipfile system/etc/security/otacerts.zip. 230 231 tempfile = cStringIO.StringIO() 232 certs_zip = zipfile.ZipFile(tempfile, "w") 233 for k in mapped_keys: 234 certs_zip.write(k) 235 certs_zip.close() 236 output_tf_zip.writestr("SYSTEM/etc/security/otacerts.zip", 237 tempfile.getvalue()) 238 239 240def main(argv): 241 242 def option_handler(o, a): 243 if o in ("-s", "--signapk_jar"): 244 OPTIONS.signapk_jar = a 245 elif o in ("-e", "--extra_apks"): 246 names, key = a.split("=") 247 names = names.split(",") 248 for n in names: 249 OPTIONS.extra_apks[n] = key 250 elif o in ("-d", "--default_key_mappings"): 251 OPTIONS.key_map.update({ 252 "build/target/product/security/testkey": "%s/releasekey" % (a,), 253 "build/target/product/security/media": "%s/media" % (a,), 254 "build/target/product/security/shared": "%s/shared" % (a,), 255 "build/target/product/security/platform": "%s/platform" % (a,), 256 }) 257 elif o in ("-k", "--key_mapping"): 258 s, d = a.split("=") 259 OPTIONS.key_map[s] = d 260 elif o in ("-o", "--replace_ota_keys"): 261 OPTIONS.replace_ota_keys = True 262 elif o in ("-t", "--extra_tags"): 263 OPTIONS.extra_tags.append(a) 264 else: 265 return False 266 return True 267 268 args = common.ParseOptions(argv, __doc__, 269 extra_opts="s:e:d:k:ot:", 270 extra_long_opts=["signapk_jar=", 271 "extra_apks=", 272 "default_key_mappings=", 273 "key_mapping=", 274 "replace_ota_keys", 275 "extra_tag="], 276 extra_option_handler=option_handler) 277 278 if len(args) != 2: 279 common.Usage(__doc__) 280 sys.exit(1) 281 282 input_zip = zipfile.ZipFile(args[0], "r") 283 output_zip = zipfile.ZipFile(args[1], "w") 284 285 SignApks(input_zip, output_zip) 286 287 if OPTIONS.replace_ota_keys: 288 ReplaceOtaKeys(input_zip, output_zip) 289 290 input_zip.close() 291 output_zip.close() 292 293 print "done." 294 295 296if __name__ == '__main__': 297 try: 298 main(sys.argv[1:]) 299 except common.ExternalError, e: 300 print 301 print " ERROR: %s" % (e,) 302 print 303 sys.exit(1) 304