From f6a53aa5f24878ad9098409ed3d3f41bb5c63fb5 Mon Sep 17 00:00:00 2001 From: Doug Zongker Date: Tue, 15 Dec 2009 15:06:55 -0800 Subject: [PATCH] add "EXTERNAL" as special value of LOCAL_CERTIFICATE Setting LOCAL_CERTIFICATE to "EXTERNAL" now marks an apk (either a prebuilt or otherwise) as needing the default test key within the system, but one that should be signed after the target_files is produced but before sign_target_files_apks does the rest of the signing. (We use this to ship apps on the system that are signed by third parties, like Facebook.) --- core/Makefile | 7 ++++-- core/package.mk | 9 +++++++ core/prebuilt.mk | 10 ++++++++ tools/releasetools/check_target_files_signatures | 21 ++++++++++++++++ tools/releasetools/common.py | 32 +++++++++++++++++++++--- tools/releasetools/sign_target_files_apks | 25 +++++++----------- 6 files changed, 83 insertions(+), 21 deletions(-) diff --git a/core/Makefile b/core/Makefile index deb9f585c..dfba6ceaa 100644 --- a/core/Makefile +++ b/core/Makefile @@ -208,8 +208,11 @@ $(APKCERTS_FILE): $(all_built_packages) @mkdir -p $(dir $@) @rm -f $@ $(hide) $(foreach p,$(PACKAGES),\ - echo 'name="$(p).apk" certificate="$(PACKAGES.$(p).CERTIFICATE)" \ - private_key="$(PACKAGES.$(p).PRIVATE_KEY)"' >> $@;) + $(if $(PACKAGES.$(p).EXTERNAL_KEY),\ + echo 'name="$(p).apk" certificate="EXTERNAL" \ + private_key=""' >> $@;,\ + echo 'name="$(p).apk" certificate="$(PACKAGES.$(p).CERTIFICATE)" \ + private_key="$(PACKAGES.$(p).PRIVATE_KEY)"' >> $@;)) .PHONY: apkcerts-list apkcerts-list: $(APKCERTS_FILE) diff --git a/core/package.mk b/core/package.mk index d92a8b879..70f10e07f 100644 --- a/core/package.mk +++ b/core/package.mk @@ -244,6 +244,15 @@ jni_shared_libraries := \ ifeq ($(LOCAL_CERTIFICATE),) LOCAL_CERTIFICATE := testkey endif + +ifeq ($(LOCAL_CERTIFICATE),EXTERNAL) + # The special value "EXTERNAL" means that we will sign it with the + # default testkey, apply predexopt, but then expect the final .apk + # (after dexopting) to be signed by an outside tool. + LOCAL_CERTIFICATE := testkey + PACKAGES.$(LOCAL_PACKAGE_NAME).EXTERNAL_KEY := 1 +endif + # If this is not an absolute certificate, assign it to a generic one. ifeq ($(dir $(strip $(LOCAL_CERTIFICATE))),./) LOCAL_CERTIFICATE := $(SRC_TARGET_DIR)/product/security/$(LOCAL_CERTIFICATE) diff --git a/core/prebuilt.mk b/core/prebuilt.mk index 2693f5d41..b03f2af2e 100644 --- a/core/prebuilt.mk +++ b/core/prebuilt.mk @@ -42,6 +42,16 @@ $(LOCAL_BUILT_MODULE) : $(LOCAL_PATH)/$(LOCAL_SRC_FILES) | $(ACP) endif endif +ifeq ($(LOCAL_CERTIFICATE),EXTERNAL) + # The magic string "EXTERNAL" means this package will be signed with + # the test key throughout the build process, but we expect the final + # package to be signed with a different key. + # + # This can be used for packages where we don't have access to the + # keys, but want the package to be predexopt'ed. + LOCAL_CERTIFICATE := testkey + PACKAGES.$(LOCAL_MODULE).EXTERNAL_KEY := 1 +endif ifeq ($(LOCAL_CERTIFICATE),) ifneq ($(filter APPS,$(LOCAL_MODULE_CLASS)),) # It is now a build error to add a prebuilt .apk without diff --git a/tools/releasetools/check_target_files_signatures b/tools/releasetools/check_target_files_signatures index b91f3d4c0..17aebdc40 100755 --- a/tools/releasetools/check_target_files_signatures +++ b/tools/releasetools/check_target_files_signatures @@ -248,6 +248,7 @@ class TargetFiles(object): d = common.UnzipTemp(filename, '*.apk') try: self.apks = {} + self.apks_by_basename = {} for dirpath, dirnames, filenames in os.walk(d): for fn in filenames: if fn.endswith(".apk"): @@ -255,12 +256,17 @@ class TargetFiles(object): displayname = fullname[len(d)+1:] apk = APK(fullname, displayname) self.apks[apk.package] = apk + self.apks_by_basename[os.path.basename(apk.filename)] = apk self.max_pkg_len = max(self.max_pkg_len, len(apk.package)) self.max_fn_len = max(self.max_fn_len, len(apk.filename)) finally: shutil.rmtree(d) + z = zipfile.ZipFile(open(filename, "rb")) + self.certmap = common.ReadApkCerts(z) + z.close() + def CheckSharedUids(self): """Look for any instances where packages signed with different certs request the same sharedUserId.""" @@ -292,6 +298,20 @@ class TargetFiles(object): apk.package, apk.filename) print + def CheckExternalSignatures(self): + for apk_filename, certname in self.certmap.iteritems(): + if certname == "EXTERNAL": + # Apps marked EXTERNAL should be signed with the test key + # during development, then manually re-signed after + # predexopting. Consider it an error if this app is now + # signed with any key that is present in our tree. + apk = self.apks_by_basename[apk_filename] + name = ALL_CERTS.Get(apk.cert) + if not name.startswith("unknown "): + Push(apk.filename) + AddProblem("hasn't been signed with EXTERNAL cert") + Pop() + def PrintCerts(self): """Display a table of packages grouped by cert.""" by_cert = {} @@ -402,6 +422,7 @@ def main(argv): Banner("target files") target_files.PrintCerts() target_files.CheckSharedUids() + target_files.CheckExternalSignatures() if compare_files: if OPTIONS.text: Banner("comparison files") diff --git a/tools/releasetools/common.py b/tools/releasetools/common.py index 0e17a5fa7..ab6678ac6 100644 --- a/tools/releasetools/common.py +++ b/tools/releasetools/common.py @@ -37,6 +37,11 @@ OPTIONS.tempfiles = [] OPTIONS.device_specific = None OPTIONS.extras = {} + +# Values for "certificate" in apkcerts that mean special things. +SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL") + + class ExternalError(RuntimeError): pass @@ -166,9 +171,8 @@ def GetKeyPasswords(keylist): need_passwords = [] devnull = open("/dev/null", "w+b") for k in sorted(keylist): - # An empty-string key is used to mean don't re-sign this package. - # Obviously we don't need a password for this non-key. - if not k: + # We don't need a password for things that aren't really keys. + if k in SPECIAL_CERT_STRINGS: no_passwords.append(k) continue @@ -254,6 +258,28 @@ def CheckSize(data, target): print " ", msg +def ReadApkCerts(tf_zip): + """Given a target_files ZipFile, parse the META/apkcerts.txt file + and return a {package: cert} dict.""" + 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="(.*)"\s+' + r'private_key="(.*)"$', line) + if m: + name, cert, privkey = m.groups() + if cert in SPECIAL_CERT_STRINGS and not privkey: + certmap[name] = cert + elif (cert.endswith(".x509.pem") and + privkey.endswith(".pk8") and + cert[:-9] == privkey[:-4]): + certmap[name] = cert[:-9] + else: + raise ValueError("failed to parse line from apkcerts.txt:\n" + line) + return certmap + + COMMON_DOCSTRING = """ -p (--path) Prepend /bin to the list of places to search for binaries diff --git a/tools/releasetools/sign_target_files_apks b/tools/releasetools/sign_target_files_apks index 03610b2f0..4ec934709 100755 --- a/tools/releasetools/sign_target_files_apks +++ b/tools/releasetools/sign_target_files_apks @@ -83,23 +83,16 @@ OPTIONS.replace_ota_keys = False OPTIONS.tag_changes = ("-test-keys", "+release-keys") 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 m: - certmap[m.group(1)] = OPTIONS.key_map.get(m.group(2), m.group(2)) - else: - m = re.match(r'^name="(.*)"\s+certificate="PRESIGNED"\s+' - r'private_key=""$', line) - if m: - certmap[m.group(1)] = None - else: - raise ValueError("failed to parse line from apkcerts.txt:\n" + line) + certmap = common.ReadApkCerts(tf_zip) + + # apply the key remapping to the contents of the file + for apk, cert in certmap.iteritems(): + certmap[apk] = OPTIONS.key_map.get(cert, cert) + + # apply all the -e options, overriding anything in the file for apk, cert in OPTIONS.extra_apks.iteritems(): certmap[apk] = OPTIONS.key_map.get(cert, cert) + return certmap @@ -147,7 +140,7 @@ def SignApks(input_tf_zip, output_tf_zip, apk_key_map, key_passwords): if info.filename.endswith(".apk"): name = os.path.basename(info.filename) key = apk_key_map[name] - if key: + if key not in common.SPECIAL_CERT_STRINGS: print " signing: %-*s (%s)" % (maxsize, name, key) signed_data = SignApk(data, key, key_passwords[key]) output_tf_zip.writestr(out_info, signed_data) -- 2.11.0