android_build/tools/releasetools/sign_target_files_apks

304 lines
9.8 KiB
Python
Executable File

#!/usr/bin/env python
#
# Copyright (C) 2008 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Signs all the APK files in a target-files zipfile, producing a new
target-files zip.
Usage: sign_target_files_apks [flags] input_target_files output_target_files
-s (--signapk_jar) <path>
Path of the signapks.jar file used to sign an individual APK
file.
-e (--extra_apks) <name,name,...=key>
Add extra APK name/key pairs as though they appeared in
apkcerts.txt (so mappings specified by -k and -d are applied).
Keys specified in -e override any value for that app contained
in the apkcerts.txt file. Option may be repeated to give
multiple extra packages.
-k (--key_mapping) <src_key=dest_key>
Add a mapping from the key name as specified in apkcerts.txt (the
src_key) to the real key you wish to sign the package with
(dest_key). Option may be repeated to give multiple key
mappings.
-d (--default_key_mappings) <dir>
Set up the following key mappings:
build/target/product/security/testkey ==> $dir/releasekey
build/target/product/security/media ==> $dir/media
build/target/product/security/shared ==> $dir/shared
build/target/product/security/platform ==> $dir/platform
-d and -k options are added to the set of mappings in the order
in which they appear on the command line.
-o (--replace_ota_keys)
Replace the certificate (public key) used by OTA package
verification with the one specified in the input target_files
zip (in the META/otakeys.txt file). Key remapping (-k and -d)
is performed on this key.
-t (--extra_tag) <tag>
A string which is added to the set of tags in the last component
of the build fingerprint. Option may be repeated to give
multiple extra tags.
"""
import sys
if sys.hexversion < 0x02040000:
print >> sys.stderr, "Python 2.4 or newer is required."
sys.exit(1)
import cStringIO
import copy
import os
import re
import subprocess
import tempfile
import zipfile
import common
OPTIONS = common.OPTIONS
OPTIONS.extra_apks = {}
OPTIONS.key_map = {}
OPTIONS.replace_ota_keys = False
OPTIONS.extra_tags = []
def GetApkCerts(tf_zip):
certmap = {}
for line in tf_zip.read("META/apkcerts.txt").split("\n"):
line = line.strip()
if not line: continue
m = re.match(r'^name="(.*)"\s+certificate="(.*)\.x509\.pem"\s+'
r'private_key="\2\.pk8"$', line)
if not m:
raise SigningError("failed to parse line from apkcerts.txt:\n" + line)
certmap[m.group(1)] = OPTIONS.key_map.get(m.group(2), m.group(2))
for apk, cert in OPTIONS.extra_apks.iteritems():
certmap[apk] = OPTIONS.key_map.get(cert, cert)
return certmap
def SignApk(data, keyname, pw):
unsigned = tempfile.NamedTemporaryFile()
unsigned.write(data)
unsigned.flush()
signed = tempfile.NamedTemporaryFile()
common.SignFile(unsigned.name, signed.name, keyname, pw, align=4)
data = signed.read()
unsigned.close()
signed.close()
return data
def SignApks(input_tf_zip, output_tf_zip):
apk_key_map = GetApkCerts(input_tf_zip)
maxsize = max([len(os.path.basename(i.filename))
for i in input_tf_zip.infolist()
if i.filename.endswith('.apk')])
# Check that all the APKs we want to sign have keys specified, and
# error out if they don't. Do this before prompting for key
# passwords in case we're going to fail anyway.
unknown_apks = []
for info in input_tf_zip.infolist():
if info.filename.endswith(".apk"):
name = os.path.basename(info.filename)
if name not in apk_key_map:
unknown_apks.append(name)
if unknown_apks:
print "ERROR: no key specified for:\n\n ",
print "\n ".join(unknown_apks)
print "\nUse '-e <apkname>=' to specify a key (which may be an"
print "empty string to not sign this apk)."
sys.exit(1)
key_passwords = common.GetKeyPasswords(set(apk_key_map.values()))
for info in input_tf_zip.infolist():
data = input_tf_zip.read(info.filename)
out_info = copy.copy(info)
if info.filename.endswith(".apk"):
name = os.path.basename(info.filename)
key = apk_key_map[name]
if key:
print " signing: %-*s (%s)" % (maxsize, name, key)
signed_data = SignApk(data, key, key_passwords[key])
output_tf_zip.writestr(out_info, signed_data)
else:
# an APK we're not supposed to sign.
print "NOT signing: %s" % (name,)
output_tf_zip.writestr(out_info, data)
elif info.filename in ("SYSTEM/build.prop",
"RECOVERY/RAMDISK/default.prop"):
print "rewriting %s:" % (info.filename,)
new_data = RewriteProps(data)
output_tf_zip.writestr(out_info, new_data)
else:
# a non-APK file; copy it verbatim
output_tf_zip.writestr(out_info, data)
def RewriteProps(data):
output = []
for line in data.split("\n"):
line = line.strip()
original_line = line
if line and line[0] != '#':
key, value = line.split("=", 1)
if key == "ro.build.fingerprint":
pieces = line.split("/")
tags = set(pieces[-1].split(","))
if "test-keys" in tags:
tags.remove("test-keys")
tags.add("release-keys")
# TODO: from donut onwards, only add ota-rel-keys if -o is given.
tags.add("ota-rel-keys")
tags.update(OPTIONS.extra_tags)
line = "/".join(pieces[:-1] + [",".join(sorted(tags))])
elif key == "ro.build.description":
pieces = line.split(" ")
assert len(pieces) == 5
tags = set(pieces[-1].split(","))
if "test-keys" in tags:
tags.remove("test-keys")
tags.add("release-keys")
# TODO: from donut onwards, only add ota-rel-keys if -o is given.
tags.add("ota-rel-keys")
tags.update(OPTIONS.extra_tags)
line = " ".join(pieces[:-1] + [",".join(sorted(tags))])
if line != original_line:
print " replace: ", original_line
print " with: ", line
output.append(line)
return "\n".join(output) + "\n"
def ReplaceOtaKeys(input_tf_zip, output_tf_zip):
try:
keylist = input_tf_zip.read("META/otakeys.txt").split()
except KeyError:
raise ExternalError("can't read META/otakeys.txt from input")
mapped_keys = []
for k in keylist:
m = re.match(r"^(.*)\.x509\.pem$", k)
if not m:
raise ExternalError("can't parse \"%s\" from META/otakeys.txt" % (k,))
k = m.group(1)
mapped_keys.append(OPTIONS.key_map.get(k, k) + ".x509.pem")
print "using:\n ", "\n ".join(mapped_keys)
print "for OTA package verification"
# recovery uses a version of the key that has been slightly
# predigested (by DumpPublicKey.java) and put in res/keys.
p = common.Run(["java", "-jar", OPTIONS.dumpkey_jar] + mapped_keys,
stdout=subprocess.PIPE)
data, _ = p.communicate()
if p.returncode != 0:
raise ExternalError("failed to run dumpkeys")
output_tf_zip.writestr("RECOVERY/RAMDISK/res/keys", data)
# SystemUpdateActivity uses the x509.pem version of the keys, but
# put into a zipfile system/etc/security/otacerts.zip.
tempfile = cStringIO.StringIO()
certs_zip = zipfile.ZipFile(tempfile, "w")
for k in mapped_keys:
certs_zip.write(k)
certs_zip.close()
output_tf_zip.writestr("SYSTEM/etc/security/otacerts.zip",
tempfile.getvalue())
def main(argv):
def option_handler(o, a):
if o in ("-s", "--signapk_jar"):
OPTIONS.signapk_jar = a
elif o in ("-e", "--extra_apks"):
names, key = a.split("=")
names = names.split(",")
for n in names:
OPTIONS.extra_apks[n] = key
elif o in ("-d", "--default_key_mappings"):
OPTIONS.key_map.update({
"build/target/product/security/testkey": "%s/releasekey" % (a,),
"build/target/product/security/media": "%s/media" % (a,),
"build/target/product/security/shared": "%s/shared" % (a,),
"build/target/product/security/platform": "%s/platform" % (a,),
})
elif o in ("-k", "--key_mapping"):
s, d = a.split("=")
OPTIONS.key_map[s] = d
elif o in ("-o", "--replace_ota_keys"):
OPTIONS.replace_ota_keys = True
elif o in ("-t", "--extra_tags"):
OPTIONS.extra_tags.append(a)
else:
return False
return True
args = common.ParseOptions(argv, __doc__,
extra_opts="s:e:d:k:ot:",
extra_long_opts=["signapk_jar=",
"extra_apks=",
"default_key_mappings=",
"key_mapping=",
"replace_ota_keys",
"extra_tag="],
extra_option_handler=option_handler)
if len(args) != 2:
common.Usage(__doc__)
sys.exit(1)
input_zip = zipfile.ZipFile(args[0], "r")
output_zip = zipfile.ZipFile(args[1], "w")
SignApks(input_zip, output_zip)
if OPTIONS.replace_ota_keys:
ReplaceOtaKeys(input_zip, output_zip)
input_zip.close()
output_zip.close()
print "done."
if __name__ == '__main__':
try:
main(sys.argv[1:])
except common.ExternalError, e:
print
print " ERROR: %s" % (e,)
print
sys.exit(1)