| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 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 |   -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 | 
| Doug Zongker | ad88c7c | 2009-04-14 12:34:27 -0700 | [diff] [blame] | 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. | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 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. | 
| Doug Zongker | 8e931bf | 2009-04-06 15:21:45 -0700 | [diff] [blame] | 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. | 
| Doug Zongker | 17aa944 | 2009-04-17 10:15:58 -0700 | [diff] [blame] | 56 |  | 
| Doug Zongker | ae87701 | 2009-04-21 10:04:51 -0700 | [diff] [blame] | 57 |   -t  (--tag_changes)  <+tag>,<-tag>,... | 
 | 58 |       Comma-separated list of changes to make to the set of tags (in | 
 | 59 |       the last component of the build fingerprint).  Prefix each with | 
 | 60 |       '+' or '-' to indicate whether that tag should be added or | 
 | 61 |       removed.  Changes are processed in the order they appear. | 
 | 62 |       Default value is "-test-keys,+ota-rel-keys,+release-keys". | 
 | 63 |  | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 64 | """ | 
 | 65 |  | 
 | 66 | import sys | 
 | 67 |  | 
 | 68 | if sys.hexversion < 0x02040000: | 
 | 69 |   print >> sys.stderr, "Python 2.4 or newer is required." | 
 | 70 |   sys.exit(1) | 
 | 71 |  | 
| Doug Zongker | 8e931bf | 2009-04-06 15:21:45 -0700 | [diff] [blame] | 72 | import cStringIO | 
 | 73 | import copy | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 74 | import os | 
 | 75 | import re | 
 | 76 | import subprocess | 
 | 77 | import tempfile | 
 | 78 | import zipfile | 
 | 79 |  | 
 | 80 | import common | 
 | 81 |  | 
 | 82 | OPTIONS = common.OPTIONS | 
 | 83 |  | 
 | 84 | OPTIONS.extra_apks = {} | 
 | 85 | OPTIONS.key_map = {} | 
| Doug Zongker | 8e931bf | 2009-04-06 15:21:45 -0700 | [diff] [blame] | 86 | OPTIONS.replace_ota_keys = False | 
| Doug Zongker | ae87701 | 2009-04-21 10:04:51 -0700 | [diff] [blame] | 87 | OPTIONS.tag_changes = ("-test-keys", "+ota-rel-keys", "+release-keys") | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 88 |  | 
 | 89 | def GetApkCerts(tf_zip): | 
| Doug Zongker | ad88c7c | 2009-04-14 12:34:27 -0700 | [diff] [blame] | 90 |   certmap = {} | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 91 |   for line in tf_zip.read("META/apkcerts.txt").split("\n"): | 
 | 92 |     line = line.strip() | 
 | 93 |     if not line: continue | 
 | 94 |     m = re.match(r'^name="(.*)"\s+certificate="(.*)\.x509\.pem"\s+' | 
 | 95 |                  r'private_key="\2\.pk8"$', line) | 
 | 96 |     if not m: | 
 | 97 |       raise SigningError("failed to parse line from apkcerts.txt:\n" + line) | 
 | 98 |     certmap[m.group(1)] = OPTIONS.key_map.get(m.group(2), m.group(2)) | 
| Doug Zongker | ad88c7c | 2009-04-14 12:34:27 -0700 | [diff] [blame] | 99 |   for apk, cert in OPTIONS.extra_apks.iteritems(): | 
 | 100 |     certmap[apk] = OPTIONS.key_map.get(cert, cert) | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 101 |   return certmap | 
 | 102 |  | 
 | 103 |  | 
| Doug Zongker | eb338ef | 2009-05-20 16:50:49 -0700 | [diff] [blame] | 104 | def CheckAllApksSigned(input_tf_zip, apk_key_map): | 
 | 105 |   """Check that all the APKs we want to sign have keys specified, and | 
 | 106 |   error out if they don't.""" | 
 | 107 |   unknown_apks = [] | 
 | 108 |   for info in input_tf_zip.infolist(): | 
 | 109 |     if info.filename.endswith(".apk"): | 
 | 110 |       name = os.path.basename(info.filename) | 
 | 111 |       if name not in apk_key_map: | 
 | 112 |         unknown_apks.append(name) | 
 | 113 |   if unknown_apks: | 
 | 114 |     print "ERROR: no key specified for:\n\n ", | 
 | 115 |     print "\n  ".join(unknown_apks) | 
 | 116 |     print "\nUse '-e <apkname>=' to specify a key (which may be an" | 
 | 117 |     print "empty string to not sign this apk)." | 
 | 118 |     sys.exit(1) | 
 | 119 |  | 
 | 120 |  | 
 | 121 | def SharedUserForApk(data): | 
 | 122 |   tmp = tempfile.NamedTemporaryFile() | 
 | 123 |   tmp.write(data) | 
 | 124 |   tmp.flush() | 
 | 125 |  | 
 | 126 |   p = common.Run(["aapt", "dump", "xmltree", tmp.name, "AndroidManifest.xml"], | 
 | 127 |                  stdout=subprocess.PIPE) | 
 | 128 |   data, _ = p.communicate() | 
 | 129 |   if p.returncode != 0: | 
 | 130 |     raise ExternalError("failed to run aapt dump") | 
 | 131 |   lines = data.split("\n") | 
 | 132 |   for i in lines: | 
 | 133 |     m = re.match(r'^\s*A: android:sharedUserId\([0-9a-fx]*\)="([^"]*)" .*$', i) | 
 | 134 |     if m: | 
 | 135 |       return m.group(1) | 
 | 136 |   return None | 
 | 137 |  | 
 | 138 |  | 
 | 139 | def CheckSharedUserIdsConsistent(input_tf_zip, apk_key_map): | 
 | 140 |   """Check that all packages that request the same shared user id are | 
 | 141 |   going to be signed with the same key.""" | 
 | 142 |  | 
 | 143 |   shared_user_apks = {} | 
| Doug Zongker | 8ce7c25 | 2009-05-22 13:34:54 -0700 | [diff] [blame] | 144 |   maxlen = len("(unknown key)") | 
| Doug Zongker | eb338ef | 2009-05-20 16:50:49 -0700 | [diff] [blame] | 145 |  | 
 | 146 |   for info in input_tf_zip.infolist(): | 
 | 147 |     if info.filename.endswith(".apk"): | 
 | 148 |       data = input_tf_zip.read(info.filename) | 
 | 149 |  | 
 | 150 |       name = os.path.basename(info.filename) | 
 | 151 |       shared_user = SharedUserForApk(data) | 
 | 152 |       key = apk_key_map[name] | 
 | 153 |       maxlen = max(maxlen, len(key)) | 
 | 154 |  | 
 | 155 |       if shared_user is not None: | 
 | 156 |         shared_user_apks.setdefault( | 
 | 157 |             shared_user, {}).setdefault(key, []).append(name) | 
 | 158 |  | 
 | 159 |   errors = [] | 
 | 160 |   for k, v in shared_user_apks.iteritems(): | 
 | 161 |     # each shared user should have exactly one key used for all the | 
 | 162 |     # apks that want that user. | 
 | 163 |     if len(v) > 1: | 
 | 164 |       errors.append((k, v)) | 
 | 165 |  | 
 | 166 |   if not errors: return | 
 | 167 |  | 
 | 168 |   print "ERROR:  shared user inconsistency.  All apks wanting to use" | 
 | 169 |   print "        a given shared user must be signed with the same key." | 
 | 170 |   print | 
 | 171 |   errors.sort() | 
 | 172 |   for user, keys in errors: | 
 | 173 |     print 'shared user id "%s":' % (user,) | 
 | 174 |     for key, apps in keys.iteritems(): | 
| Doug Zongker | 8ce7c25 | 2009-05-22 13:34:54 -0700 | [diff] [blame] | 175 |       print '  %-*s   %s' % (maxlen, key or "(unknown key)", apps[0]) | 
| Doug Zongker | eb338ef | 2009-05-20 16:50:49 -0700 | [diff] [blame] | 176 |       for a in apps[1:]: | 
 | 177 |         print (' ' * (maxlen+5)) + a | 
 | 178 |     print | 
 | 179 |  | 
 | 180 |   sys.exit(1) | 
 | 181 |  | 
 | 182 |  | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 183 | def SignApk(data, keyname, pw): | 
 | 184 |   unsigned = tempfile.NamedTemporaryFile() | 
 | 185 |   unsigned.write(data) | 
 | 186 |   unsigned.flush() | 
 | 187 |  | 
 | 188 |   signed = tempfile.NamedTemporaryFile() | 
 | 189 |  | 
 | 190 |   common.SignFile(unsigned.name, signed.name, keyname, pw, align=4) | 
 | 191 |  | 
 | 192 |   data = signed.read() | 
 | 193 |   unsigned.close() | 
 | 194 |   signed.close() | 
 | 195 |  | 
 | 196 |   return data | 
 | 197 |  | 
 | 198 |  | 
| Doug Zongker | eb338ef | 2009-05-20 16:50:49 -0700 | [diff] [blame] | 199 | def SignApks(input_tf_zip, output_tf_zip, apk_key_map, key_passwords): | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 200 |   maxsize = max([len(os.path.basename(i.filename)) | 
 | 201 |                  for i in input_tf_zip.infolist() | 
 | 202 |                  if i.filename.endswith('.apk')]) | 
 | 203 |  | 
 | 204 |   for info in input_tf_zip.infolist(): | 
 | 205 |     data = input_tf_zip.read(info.filename) | 
| Doug Zongker | 8e931bf | 2009-04-06 15:21:45 -0700 | [diff] [blame] | 206 |     out_info = copy.copy(info) | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 207 |     if info.filename.endswith(".apk"): | 
 | 208 |       name = os.path.basename(info.filename) | 
| Doug Zongker | 43874f8 | 2009-04-14 14:05:15 -0700 | [diff] [blame] | 209 |       key = apk_key_map[name] | 
 | 210 |       if key: | 
 | 211 |         print "    signing: %-*s (%s)" % (maxsize, name, key) | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 212 |         signed_data = SignApk(data, key, key_passwords[key]) | 
| Doug Zongker | 8e931bf | 2009-04-06 15:21:45 -0700 | [diff] [blame] | 213 |         output_tf_zip.writestr(out_info, signed_data) | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 214 |       else: | 
 | 215 |         # an APK we're not supposed to sign. | 
| Doug Zongker | 43874f8 | 2009-04-14 14:05:15 -0700 | [diff] [blame] | 216 |         print "NOT signing: %s" % (name,) | 
| Doug Zongker | 8e931bf | 2009-04-06 15:21:45 -0700 | [diff] [blame] | 217 |         output_tf_zip.writestr(out_info, data) | 
 | 218 |     elif info.filename in ("SYSTEM/build.prop", | 
 | 219 |                            "RECOVERY/RAMDISK/default.prop"): | 
| Doug Zongker | 17aa944 | 2009-04-17 10:15:58 -0700 | [diff] [blame] | 220 |       print "rewriting %s:" % (info.filename,) | 
 | 221 |       new_data = RewriteProps(data) | 
 | 222 |       output_tf_zip.writestr(out_info, new_data) | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 223 |     else: | 
 | 224 |       # a non-APK file; copy it verbatim | 
| Doug Zongker | 8e931bf | 2009-04-06 15:21:45 -0700 | [diff] [blame] | 225 |       output_tf_zip.writestr(out_info, data) | 
 | 226 |  | 
 | 227 |  | 
| Doug Zongker | 17aa944 | 2009-04-17 10:15:58 -0700 | [diff] [blame] | 228 | def RewriteProps(data): | 
 | 229 |   output = [] | 
 | 230 |   for line in data.split("\n"): | 
 | 231 |     line = line.strip() | 
 | 232 |     original_line = line | 
 | 233 |     if line and line[0] != '#': | 
 | 234 |       key, value = line.split("=", 1) | 
 | 235 |       if key == "ro.build.fingerprint": | 
 | 236 |         pieces = line.split("/") | 
 | 237 |         tags = set(pieces[-1].split(",")) | 
| Doug Zongker | ae87701 | 2009-04-21 10:04:51 -0700 | [diff] [blame] | 238 |         for ch in OPTIONS.tag_changes: | 
 | 239 |           if ch[0] == "-": | 
 | 240 |             tags.discard(ch[1:]) | 
 | 241 |           elif ch[0] == "+": | 
 | 242 |             tags.add(ch[1:]) | 
| Doug Zongker | 17aa944 | 2009-04-17 10:15:58 -0700 | [diff] [blame] | 243 |         line = "/".join(pieces[:-1] + [",".join(sorted(tags))]) | 
 | 244 |       elif key == "ro.build.description": | 
 | 245 |         pieces = line.split(" ") | 
 | 246 |         assert len(pieces) == 5 | 
 | 247 |         tags = set(pieces[-1].split(",")) | 
| Doug Zongker | ae87701 | 2009-04-21 10:04:51 -0700 | [diff] [blame] | 248 |         for ch in OPTIONS.tag_changes: | 
 | 249 |           if ch[0] == "-": | 
 | 250 |             tags.discard(ch[1:]) | 
 | 251 |           elif ch[0] == "+": | 
 | 252 |             tags.add(ch[1:]) | 
| Doug Zongker | 17aa944 | 2009-04-17 10:15:58 -0700 | [diff] [blame] | 253 |         line = " ".join(pieces[:-1] + [",".join(sorted(tags))]) | 
 | 254 |     if line != original_line: | 
 | 255 |       print "  replace: ", original_line | 
 | 256 |       print "     with: ", line | 
 | 257 |     output.append(line) | 
 | 258 |   return "\n".join(output) + "\n" | 
 | 259 |  | 
 | 260 |  | 
| Doug Zongker | 8e931bf | 2009-04-06 15:21:45 -0700 | [diff] [blame] | 261 | def ReplaceOtaKeys(input_tf_zip, output_tf_zip): | 
 | 262 |   try: | 
 | 263 |     keylist = input_tf_zip.read("META/otakeys.txt").split() | 
 | 264 |   except KeyError: | 
 | 265 |     raise ExternalError("can't read META/otakeys.txt from input") | 
 | 266 |  | 
 | 267 |   mapped_keys = [] | 
 | 268 |   for k in keylist: | 
 | 269 |     m = re.match(r"^(.*)\.x509\.pem$", k) | 
 | 270 |     if not m: | 
 | 271 |       raise ExternalError("can't parse \"%s\" from META/otakeys.txt" % (k,)) | 
 | 272 |     k = m.group(1) | 
 | 273 |     mapped_keys.append(OPTIONS.key_map.get(k, k) + ".x509.pem") | 
 | 274 |  | 
 | 275 |   print "using:\n   ", "\n   ".join(mapped_keys) | 
 | 276 |   print "for OTA package verification" | 
 | 277 |  | 
 | 278 |   # recovery uses a version of the key that has been slightly | 
 | 279 |   # predigested (by DumpPublicKey.java) and put in res/keys. | 
 | 280 |  | 
 | 281 |   p = common.Run(["java", "-jar", OPTIONS.dumpkey_jar] + mapped_keys, | 
 | 282 |                  stdout=subprocess.PIPE) | 
 | 283 |   data, _ = p.communicate() | 
 | 284 |   if p.returncode != 0: | 
 | 285 |     raise ExternalError("failed to run dumpkeys") | 
 | 286 |   output_tf_zip.writestr("RECOVERY/RAMDISK/res/keys", data) | 
 | 287 |  | 
 | 288 |   # SystemUpdateActivity uses the x509.pem version of the keys, but | 
 | 289 |   # put into a zipfile system/etc/security/otacerts.zip. | 
 | 290 |  | 
 | 291 |   tempfile = cStringIO.StringIO() | 
 | 292 |   certs_zip = zipfile.ZipFile(tempfile, "w") | 
 | 293 |   for k in mapped_keys: | 
 | 294 |     certs_zip.write(k) | 
 | 295 |   certs_zip.close() | 
 | 296 |   output_tf_zip.writestr("SYSTEM/etc/security/otacerts.zip", | 
 | 297 |                          tempfile.getvalue()) | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 298 |  | 
 | 299 |  | 
 | 300 | def main(argv): | 
 | 301 |  | 
 | 302 |   def option_handler(o, a): | 
 | 303 |     if o in ("-s", "--signapk_jar"): | 
 | 304 |       OPTIONS.signapk_jar = a | 
 | 305 |     elif o in ("-e", "--extra_apks"): | 
 | 306 |       names, key = a.split("=") | 
 | 307 |       names = names.split(",") | 
 | 308 |       for n in names: | 
 | 309 |         OPTIONS.extra_apks[n] = key | 
 | 310 |     elif o in ("-d", "--default_key_mappings"): | 
 | 311 |       OPTIONS.key_map.update({ | 
 | 312 |           "build/target/product/security/testkey": "%s/releasekey" % (a,), | 
 | 313 |           "build/target/product/security/media": "%s/media" % (a,), | 
 | 314 |           "build/target/product/security/shared": "%s/shared" % (a,), | 
 | 315 |           "build/target/product/security/platform": "%s/platform" % (a,), | 
 | 316 |           }) | 
 | 317 |     elif o in ("-k", "--key_mapping"): | 
 | 318 |       s, d = a.split("=") | 
 | 319 |       OPTIONS.key_map[s] = d | 
| Doug Zongker | 8e931bf | 2009-04-06 15:21:45 -0700 | [diff] [blame] | 320 |     elif o in ("-o", "--replace_ota_keys"): | 
 | 321 |       OPTIONS.replace_ota_keys = True | 
| Doug Zongker | ae87701 | 2009-04-21 10:04:51 -0700 | [diff] [blame] | 322 |     elif o in ("-t", "--tag_changes"): | 
 | 323 |       new = [] | 
 | 324 |       for i in a.split(","): | 
 | 325 |         i = i.strip() | 
 | 326 |         if not i or i[0] not in "-+": | 
 | 327 |           raise ValueError("Bad tag change '%s'" % (i,)) | 
 | 328 |         new.append(i[0] + i[1:].strip()) | 
 | 329 |       OPTIONS.tag_changes = tuple(new) | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 330 |     else: | 
 | 331 |       return False | 
 | 332 |     return True | 
 | 333 |  | 
 | 334 |   args = common.ParseOptions(argv, __doc__, | 
| Doug Zongker | 17aa944 | 2009-04-17 10:15:58 -0700 | [diff] [blame] | 335 |                              extra_opts="s:e:d:k:ot:", | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 336 |                              extra_long_opts=["signapk_jar=", | 
 | 337 |                                               "extra_apks=", | 
 | 338 |                                               "default_key_mappings=", | 
| Doug Zongker | 8e931bf | 2009-04-06 15:21:45 -0700 | [diff] [blame] | 339 |                                               "key_mapping=", | 
| Doug Zongker | 17aa944 | 2009-04-17 10:15:58 -0700 | [diff] [blame] | 340 |                                               "replace_ota_keys", | 
| Doug Zongker | ae87701 | 2009-04-21 10:04:51 -0700 | [diff] [blame] | 341 |                                               "tag_changes="], | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 342 |                              extra_option_handler=option_handler) | 
 | 343 |  | 
 | 344 |   if len(args) != 2: | 
 | 345 |     common.Usage(__doc__) | 
 | 346 |     sys.exit(1) | 
 | 347 |  | 
 | 348 |   input_zip = zipfile.ZipFile(args[0], "r") | 
 | 349 |   output_zip = zipfile.ZipFile(args[1], "w") | 
 | 350 |  | 
| Doug Zongker | eb338ef | 2009-05-20 16:50:49 -0700 | [diff] [blame] | 351 |   apk_key_map = GetApkCerts(input_zip) | 
 | 352 |   CheckAllApksSigned(input_zip, apk_key_map) | 
 | 353 |   CheckSharedUserIdsConsistent(input_zip, apk_key_map) | 
 | 354 |  | 
 | 355 |   key_passwords = common.GetKeyPasswords(set(apk_key_map.values())) | 
 | 356 |   SignApks(input_zip, output_zip, apk_key_map, key_passwords) | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 357 |  | 
| Doug Zongker | 8e931bf | 2009-04-06 15:21:45 -0700 | [diff] [blame] | 358 |   if OPTIONS.replace_ota_keys: | 
 | 359 |     ReplaceOtaKeys(input_zip, output_zip) | 
 | 360 |  | 
| Doug Zongker | eef3944 | 2009-04-02 12:14:19 -0700 | [diff] [blame] | 361 |   input_zip.close() | 
 | 362 |   output_zip.close() | 
 | 363 |  | 
 | 364 |   print "done." | 
 | 365 |  | 
 | 366 |  | 
 | 367 | if __name__ == '__main__': | 
 | 368 |   try: | 
 | 369 |     main(sys.argv[1:]) | 
 | 370 |   except common.ExternalError, e: | 
 | 371 |     print | 
 | 372 |     print "   ERROR: %s" % (e,) | 
 | 373 |     print | 
 | 374 |     sys.exit(1) |