From 93c2a012680cc55c69bb56c0493b6dd56789b021 Mon Sep 17 00:00:00 2001 From: Tao Bao Date: Tue, 19 Jun 2018 12:19:35 -0700 Subject: [PATCH] releasetools: Skip signing APKs with given prefixes. We may pack prebuilts that end with ".apk" into target_files zip, via PRODUCT_COPY_FILES. META/apkcerts.txt won't contain the cert info for such files, and we want to keep them as is while signing, despite of the ".apk" extension. This CL adds "--skip_apks_with_path_prefix" option to sign_target_files_apks.py. APKs with matching prefixes will be copied verbatim into the signed images. The prefix should match the entry names in the target_files (e.g. "SYSTEM_OTHER/preloads/"). The option may be repeated to specify multiple prefixes. Note that although we may skip signing an APK file with "-e ApkName=". This would skip *all* the APK files with the matching basename. "--skip_apks_with_path_prefix" allows matching the exact prefix. For example: $ ./build/make/tools/releasetools/sign_target_files_apks.py \ --skip_apks_with_path_prefix SYSTEM_OTHER/preloads/ \ --skip_apks_with_path_prefix PRODUCT/prebuilts/PrebuiltApp1 \ --skip_apks_with_path_prefix VENDOR/app/PrebuiltApp2.apk \ target_files.zip \ signed-target_files.zip Bug: 110201128 Test: Run the command above and check the logs. Test: `python -m unittest test_sign_target_files_apks` Change-Id: I7bd80b360917cef137cf1e7e8cfa796968831f47 --- tools/releasetools/sign_target_files_apks.py | 70 +++++++++--- .../test_sign_target_files_apks.py | 103 +++++++++++++++++- 2 files changed, 152 insertions(+), 21 deletions(-) diff --git a/tools/releasetools/sign_target_files_apks.py b/tools/releasetools/sign_target_files_apks.py index 756bc8a92..393c33d4d 100755 --- a/tools/releasetools/sign_target_files_apks.py +++ b/tools/releasetools/sign_target_files_apks.py @@ -27,6 +27,12 @@ Usage: sign_target_files_apks [flags] input_target_files output_target_files in the apkcerts.txt file. Option may be repeated to give multiple extra packages. + --skip_apks_with_path_prefix + Skip signing an APK if it has the matching prefix in its path. The prefix + should be matching the entry name, which has partition names in upper + case, e.g. "VENDOR/app/", or "SYSTEM_OTHER/preloads/". Option may be + repeated to give multiple prefixes. + -k (--key_mapping) 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 @@ -118,6 +124,7 @@ if sys.hexversion < 0x02070000: OPTIONS = common.OPTIONS OPTIONS.extra_apks = {} +OPTIONS.skip_apks_with_path_prefix = set() OPTIONS.key_map = {} OPTIONS.rebuild_recovery = False OPTIONS.replace_ota_keys = False @@ -144,39 +151,53 @@ def GetApkCerts(certmap): return certmap -def GetApkFileInfo(filename, compressed_extension): +def GetApkFileInfo(filename, compressed_extension, skipped_prefixes): """Returns the APK info based on the given filename. Checks if the given filename (with path) looks like an APK file, by taking the - compressed extension into consideration. + compressed extension into consideration. If it appears to be an APK file, + further checks if the APK file should be skipped when signing, based on the + given path prefixes. Args: filename: Path to the file. compressed_extension: The extension string of compressed APKs (e.g. ".gz"), or None if there's no compressed APKs. + skipped_prefixes: A set/list/tuple of the path prefixes to be skipped. Returns: - (is_apk, is_compressed): is_apk indicates whether the given filename is an - APK file. is_compressed indicates whether the APK file is compressed (only - meaningful when is_apk is True). + (is_apk, is_compressed, should_be_skipped): is_apk indicates whether the + given filename is an APK file. is_compressed indicates whether the APK file + is compressed (only meaningful when is_apk is True). should_be_skipped + indicates whether the filename matches any of the given prefixes to be + skipped. Raises: - AssertionError: On invalid compressed_extension input. + AssertionError: On invalid compressed_extension or skipped_prefixes inputs. """ assert compressed_extension is None or compressed_extension.startswith('.'), \ "Invalid compressed_extension arg: '{}'".format(compressed_extension) + # skipped_prefixes should be one of set/list/tuple types. Other types such as + # str shouldn't be accepted. + assert (isinstance(skipped_prefixes, tuple) or + isinstance(skipped_prefixes, set) or + isinstance(skipped_prefixes, list)), \ + "Invalid skipped_prefixes input type: {}".format( + type(skipped_prefixes)) + compressed_apk_extension = ( ".apk" + compressed_extension if compressed_extension else None) is_apk = (filename.endswith(".apk") or (compressed_apk_extension and filename.endswith(compressed_apk_extension))) if not is_apk: - return (False, False) + return (False, False, False) is_compressed = (compressed_apk_extension and filename.endswith(compressed_apk_extension)) - return (True, is_compressed) + should_be_skipped = filename.startswith(tuple(skipped_prefixes)) + return (True, is_compressed, should_be_skipped) def CheckAllApksSigned(input_tf_zip, apk_key_map, compressed_extension): @@ -193,9 +214,9 @@ def CheckAllApksSigned(input_tf_zip, apk_key_map, compressed_extension): """ unknown_apks = [] for info in input_tf_zip.infolist(): - (is_apk, is_compressed) = GetApkFileInfo( - info.filename, compressed_extension) - if not is_apk: + (is_apk, is_compressed, should_be_skipped) = GetApkFileInfo( + info.filename, compressed_extension, OPTIONS.skip_apks_with_path_prefix) + if not is_apk or should_be_skipped: continue name = os.path.basename(info.filename) if is_compressed: @@ -276,9 +297,11 @@ def ProcessTargetFiles(input_tf_zip, output_tf_zip, misc_info, apk_key_map, key_passwords, platform_api_level, codename_to_api_level_map, compressed_extension): + # maxsize measures the maximum filename length, including the ones to be + # skipped. maxsize = max( [len(os.path.basename(i.filename)) for i in input_tf_zip.infolist() - if GetApkFileInfo(i.filename, compressed_extension)[0]]) + if GetApkFileInfo(i.filename, compressed_extension, [])[0]]) system_root_image = misc_info.get("system_root_image") == "true" for info in input_tf_zip.infolist(): @@ -288,10 +311,18 @@ def ProcessTargetFiles(input_tf_zip, output_tf_zip, misc_info, data = input_tf_zip.read(filename) out_info = copy.copy(info) - (is_apk, is_compressed) = GetApkFileInfo(filename, compressed_extension) + (is_apk, is_compressed, should_be_skipped) = GetApkFileInfo( + filename, compressed_extension, OPTIONS.skip_apks_with_path_prefix) + + if is_apk and should_be_skipped: + # Copy skipped APKs verbatim. + print( + "NOT signing: %s\n" + " (skipped due to matching prefix)" % (filename,)) + common.ZipWriteStr(output_tf_zip, out_info, data) # Sign APKs. - if is_apk: + elif is_apk: name = os.path.basename(filename) if is_compressed: name = name[:-len(compressed_extension)] @@ -304,7 +335,9 @@ def ProcessTargetFiles(input_tf_zip, output_tf_zip, misc_info, common.ZipWriteStr(output_tf_zip, out_info, signed_data) else: # an APK we're not supposed to sign. - print("NOT signing: %s" % (name,)) + print( + "NOT signing: %s\n" + " (skipped due to special cert string)" % (name,)) common.ZipWriteStr(output_tf_zip, out_info, data) # System properties. @@ -794,6 +827,12 @@ def main(argv): names = names.split(",") for n in names: OPTIONS.extra_apks[n] = key + elif o == "--skip_apks_with_path_prefix": + # Sanity check the prefix, which must be in all upper case. + prefix = a.split('/')[0] + if not prefix or prefix != prefix.upper(): + raise ValueError("Invalid path prefix '%s'" % (a,)) + OPTIONS.skip_apks_with_path_prefix.add(a) elif o in ("-d", "--default_key_mappings"): key_mapping_options.append((None, a)) elif o in ("-k", "--key_mapping"): @@ -853,6 +892,7 @@ def main(argv): extra_opts="e:d:k:ot:", extra_long_opts=[ "extra_apks=", + "skip_apks_with_path_prefix=", "default_key_mappings=", "key_mapping=", "replace_ota_keys", diff --git a/tools/releasetools/test_sign_target_files_apks.py b/tools/releasetools/test_sign_target_files_apks.py index 71bd25940..ac1b567ca 100644 --- a/tools/releasetools/test_sign_target_files_apks.py +++ b/tools/releasetools/test_sign_target_files_apks.py @@ -237,25 +237,116 @@ class SignTargetFilesApksTest(unittest.TestCase): AssertionError, CheckAllApksSigned, input_zip, apk_key_map, '.gz') def test_GetApkFileInfo(self): - (is_apk, is_compressed) = GetApkFileInfo("PRODUCT/apps/Chats.apk", None) + (is_apk, is_compressed, should_be_skipped) = GetApkFileInfo( + "PRODUCT/apps/Chats.apk", None, []) self.assertTrue(is_apk) self.assertFalse(is_compressed) + self.assertFalse(should_be_skipped) - (is_apk, is_compressed) = GetApkFileInfo("PRODUCT/apps/Chats.dat", None) + (is_apk, is_compressed, should_be_skipped) = GetApkFileInfo( + "PRODUCT/apps/Chats.apk", None, []) + self.assertTrue(is_apk) + self.assertFalse(is_compressed) + self.assertFalse(should_be_skipped) + + (is_apk, is_compressed, should_be_skipped) = GetApkFileInfo( + "PRODUCT/apps/Chats.dat", None, []) self.assertFalse(is_apk) self.assertFalse(is_compressed) + self.assertFalse(should_be_skipped) def test_GetApkFileInfo_withCompressedApks(self): - (is_apk, is_compressed) = GetApkFileInfo("PRODUCT/apps/Chats.apk.gz", ".gz") + (is_apk, is_compressed, should_be_skipped) = GetApkFileInfo( + "PRODUCT/apps/Chats.apk.gz", ".gz", []) self.assertTrue(is_apk) self.assertTrue(is_compressed) + self.assertFalse(should_be_skipped) - (is_apk, is_compressed) = GetApkFileInfo("PRODUCT/apps/Chats.apk.gz", ".xz") + (is_apk, is_compressed, should_be_skipped) = GetApkFileInfo( + "PRODUCT/apps/Chats.apk.gz", ".xz", []) self.assertFalse(is_apk) self.assertFalse(is_compressed) + self.assertFalse(should_be_skipped) self.assertRaises( - AssertionError, GetApkFileInfo, "PRODUCT/apps/Chats.apk", "") + AssertionError, GetApkFileInfo, "PRODUCT/apps/Chats.apk", "", []) self.assertRaises( - AssertionError, GetApkFileInfo, "PRODUCT/apps/Chats.apk", "apk") + AssertionError, GetApkFileInfo, "PRODUCT/apps/Chats.apk", "apk", []) + + def test_GetApkFileInfo_withSkippedPrefixes(self): + (is_apk, is_compressed, should_be_skipped) = GetApkFileInfo( + "PRODUCT/preloads/apps/Chats.apk", None, set()) + self.assertTrue(is_apk) + self.assertFalse(is_compressed) + self.assertFalse(should_be_skipped) + + (is_apk, is_compressed, should_be_skipped) = GetApkFileInfo( + "PRODUCT/preloads/apps/Chats.apk", + None, + set(["PRODUCT/preloads/"])) + self.assertTrue(is_apk) + self.assertFalse(is_compressed) + self.assertTrue(should_be_skipped) + + (is_apk, is_compressed, should_be_skipped) = GetApkFileInfo( + "SYSTEM_OTHER/preloads/apps/Chats.apk", + None, + set(["SYSTEM/preloads/", "SYSTEM_OTHER/preloads/"])) + self.assertTrue(is_apk) + self.assertFalse(is_compressed) + self.assertTrue(should_be_skipped) + + (is_apk, is_compressed, should_be_skipped) = GetApkFileInfo( + "SYSTEM_OTHER/preloads/apps/Chats.apk.gz", + ".gz", + set(["PRODUCT/prebuilts/", "SYSTEM_OTHER/preloads/"])) + self.assertTrue(is_apk) + self.assertTrue(is_compressed) + self.assertTrue(should_be_skipped) + + (is_apk, is_compressed, should_be_skipped) = GetApkFileInfo( + "SYSTEM_OTHER/preloads/apps/Chats.dat", + None, + set(["SYSTEM_OTHER/preloads/"])) + self.assertFalse(is_apk) + self.assertFalse(is_compressed) + self.assertFalse(should_be_skipped) + + def test_GetApkFileInfo_checkSkippedPrefixesInput(self): + # set + (is_apk, is_compressed, should_be_skipped) = GetApkFileInfo( + "SYSTEM_OTHER/preloads/apps/Chats.apk", + None, + set(["SYSTEM_OTHER/preloads/"])) + self.assertTrue(is_apk) + self.assertFalse(is_compressed) + self.assertTrue(should_be_skipped) + + # tuple + (is_apk, is_compressed, should_be_skipped) = GetApkFileInfo( + "SYSTEM_OTHER/preloads/apps/Chats.apk", + None, + ("SYSTEM_OTHER/preloads/",)) + self.assertTrue(is_apk) + self.assertFalse(is_compressed) + self.assertTrue(should_be_skipped) + + # list + (is_apk, is_compressed, should_be_skipped) = GetApkFileInfo( + "SYSTEM_OTHER/preloads/apps/Chats.apk", + None, + ["SYSTEM_OTHER/preloads/"]) + self.assertTrue(is_apk) + self.assertFalse(is_compressed) + self.assertTrue(should_be_skipped) + + # str is invalid. + self.assertRaises( + AssertionError, GetApkFileInfo, "SYSTEM_OTHER/preloads/apps/Chats.apk", + None, "SYSTEM_OTHER/preloads/") + + # None is invalid. + self.assertRaises( + AssertionError, GetApkFileInfo, "SYSTEM_OTHER/preloads/apps/Chats.apk", + None, None)