Merge "releasetools: Factor out the check for (compressed) APK file."
This commit is contained in:
commit
2ba547a1c7
2 changed files with 136 additions and 57 deletions
|
@ -144,28 +144,69 @@ def GetApkCerts(certmap):
|
||||||
return certmap
|
return certmap
|
||||||
|
|
||||||
|
|
||||||
|
def GetApkFileInfo(filename, compressed_extension):
|
||||||
|
"""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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
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).
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
AssertionError: On invalid compressed_extension input.
|
||||||
|
"""
|
||||||
|
assert compressed_extension is None or compressed_extension.startswith('.'), \
|
||||||
|
"Invalid compressed_extension arg: '{}'".format(compressed_extension)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
is_compressed = (compressed_apk_extension and
|
||||||
|
filename.endswith(compressed_apk_extension))
|
||||||
|
return (True, is_compressed)
|
||||||
|
|
||||||
|
|
||||||
def CheckAllApksSigned(input_tf_zip, apk_key_map, compressed_extension):
|
def CheckAllApksSigned(input_tf_zip, apk_key_map, compressed_extension):
|
||||||
"""Check that all the APKs we want to sign have keys specified, and
|
"""Checks that all the APKs have keys specified, otherwise errors out.
|
||||||
error out if they don't."""
|
|
||||||
|
Args:
|
||||||
|
input_tf_zip: An open target_files zip file.
|
||||||
|
apk_key_map: A dict of known signing keys key'd by APK names.
|
||||||
|
compressed_extension: The extension string of compressed APKs, such as
|
||||||
|
".gz", or None if there's no compressed APKs.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
AssertionError: On finding unknown APKs.
|
||||||
|
"""
|
||||||
unknown_apks = []
|
unknown_apks = []
|
||||||
compressed_apk_extension = None
|
|
||||||
if compressed_extension:
|
|
||||||
compressed_apk_extension = ".apk" + compressed_extension
|
|
||||||
for info in input_tf_zip.infolist():
|
for info in input_tf_zip.infolist():
|
||||||
if (info.filename.endswith(".apk") or
|
(is_apk, is_compressed) = GetApkFileInfo(
|
||||||
(compressed_apk_extension and
|
info.filename, compressed_extension)
|
||||||
info.filename.endswith(compressed_apk_extension))):
|
if not is_apk:
|
||||||
name = os.path.basename(info.filename)
|
continue
|
||||||
if compressed_apk_extension and name.endswith(compressed_apk_extension):
|
name = os.path.basename(info.filename)
|
||||||
name = name[:-len(compressed_extension)]
|
if is_compressed:
|
||||||
if name not in apk_key_map:
|
name = name[:-len(compressed_extension)]
|
||||||
unknown_apks.append(name)
|
if name not in apk_key_map:
|
||||||
if unknown_apks:
|
unknown_apks.append(name)
|
||||||
print("ERROR: no key specified for:\n")
|
|
||||||
print(" " + "\n ".join(unknown_apks))
|
assert not unknown_apks, \
|
||||||
print("\nUse '-e <apkname>=' to specify a key (which may be an empty "
|
("No key specified for:\n {}\n"
|
||||||
"string to not sign this apk).")
|
"Use '-e <apkname>=' to specify a key (which may be an empty string to "
|
||||||
sys.exit(1)
|
"not sign this apk).".format("\n ".join(unknown_apks)))
|
||||||
|
|
||||||
|
|
||||||
def SignApk(data, keyname, pw, platform_api_level, codename_to_api_level_map,
|
def SignApk(data, keyname, pw, platform_api_level, codename_to_api_level_map,
|
||||||
|
@ -235,32 +276,23 @@ def ProcessTargetFiles(input_tf_zip, output_tf_zip, misc_info,
|
||||||
apk_key_map, key_passwords, platform_api_level,
|
apk_key_map, key_passwords, platform_api_level,
|
||||||
codename_to_api_level_map,
|
codename_to_api_level_map,
|
||||||
compressed_extension):
|
compressed_extension):
|
||||||
|
|
||||||
compressed_apk_extension = None
|
|
||||||
if compressed_extension:
|
|
||||||
compressed_apk_extension = ".apk" + compressed_extension
|
|
||||||
|
|
||||||
maxsize = max(
|
maxsize = max(
|
||||||
[len(os.path.basename(i.filename)) for i in input_tf_zip.infolist()
|
[len(os.path.basename(i.filename)) for i in input_tf_zip.infolist()
|
||||||
if (i.filename.endswith('.apk') or
|
if GetApkFileInfo(i.filename, compressed_extension)[0]])
|
||||||
(compressed_apk_extension and
|
|
||||||
i.filename.endswith(compressed_apk_extension)))])
|
|
||||||
system_root_image = misc_info.get("system_root_image") == "true"
|
system_root_image = misc_info.get("system_root_image") == "true"
|
||||||
|
|
||||||
for info in input_tf_zip.infolist():
|
for info in input_tf_zip.infolist():
|
||||||
if info.filename.startswith("IMAGES/"):
|
filename = info.filename
|
||||||
|
if filename.startswith("IMAGES/"):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
data = input_tf_zip.read(info.filename)
|
data = input_tf_zip.read(filename)
|
||||||
out_info = copy.copy(info)
|
out_info = copy.copy(info)
|
||||||
|
(is_apk, is_compressed) = GetApkFileInfo(filename, compressed_extension)
|
||||||
|
|
||||||
# Sign APKs.
|
# Sign APKs.
|
||||||
if (info.filename.endswith(".apk") or
|
if is_apk:
|
||||||
(compressed_apk_extension and
|
name = os.path.basename(filename)
|
||||||
info.filename.endswith(compressed_apk_extension))):
|
|
||||||
is_compressed = (compressed_extension and
|
|
||||||
info.filename.endswith(compressed_apk_extension))
|
|
||||||
name = os.path.basename(info.filename)
|
|
||||||
if is_compressed:
|
if is_compressed:
|
||||||
name = name[:-len(compressed_extension)]
|
name = name[:-len(compressed_extension)]
|
||||||
|
|
||||||
|
@ -276,15 +308,15 @@ def ProcessTargetFiles(input_tf_zip, output_tf_zip, misc_info,
|
||||||
common.ZipWriteStr(output_tf_zip, out_info, data)
|
common.ZipWriteStr(output_tf_zip, out_info, data)
|
||||||
|
|
||||||
# System properties.
|
# System properties.
|
||||||
elif info.filename in ("SYSTEM/build.prop",
|
elif filename in ("SYSTEM/build.prop",
|
||||||
"VENDOR/build.prop",
|
"VENDOR/build.prop",
|
||||||
"SYSTEM/etc/prop.default",
|
"SYSTEM/etc/prop.default",
|
||||||
"BOOT/RAMDISK/prop.default",
|
"BOOT/RAMDISK/prop.default",
|
||||||
"BOOT/RAMDISK/default.prop", # legacy
|
"BOOT/RAMDISK/default.prop", # legacy
|
||||||
"ROOT/default.prop", # legacy
|
"ROOT/default.prop", # legacy
|
||||||
"RECOVERY/RAMDISK/prop.default",
|
"RECOVERY/RAMDISK/prop.default",
|
||||||
"RECOVERY/RAMDISK/default.prop"): # legacy
|
"RECOVERY/RAMDISK/default.prop"): # legacy
|
||||||
print("Rewriting %s:" % (info.filename,))
|
print("Rewriting %s:" % (filename,))
|
||||||
if stat.S_ISLNK(info.external_attr >> 16):
|
if stat.S_ISLNK(info.external_attr >> 16):
|
||||||
new_data = data
|
new_data = data
|
||||||
else:
|
else:
|
||||||
|
@ -293,20 +325,20 @@ def ProcessTargetFiles(input_tf_zip, output_tf_zip, misc_info,
|
||||||
|
|
||||||
# Replace the certs in *mac_permissions.xml (there could be multiple, such
|
# Replace the certs in *mac_permissions.xml (there could be multiple, such
|
||||||
# as {system,vendor}/etc/selinux/{plat,nonplat}_mac_permissions.xml).
|
# as {system,vendor}/etc/selinux/{plat,nonplat}_mac_permissions.xml).
|
||||||
elif info.filename.endswith("mac_permissions.xml"):
|
elif filename.endswith("mac_permissions.xml"):
|
||||||
print("Rewriting %s with new keys." % (info.filename,))
|
print("Rewriting %s with new keys." % (filename,))
|
||||||
new_data = ReplaceCerts(data)
|
new_data = ReplaceCerts(data)
|
||||||
common.ZipWriteStr(output_tf_zip, out_info, new_data)
|
common.ZipWriteStr(output_tf_zip, out_info, new_data)
|
||||||
|
|
||||||
# Ask add_img_to_target_files to rebuild the recovery patch if needed.
|
# Ask add_img_to_target_files to rebuild the recovery patch if needed.
|
||||||
elif info.filename in ("SYSTEM/recovery-from-boot.p",
|
elif filename in ("SYSTEM/recovery-from-boot.p",
|
||||||
"SYSTEM/etc/recovery.img",
|
"SYSTEM/etc/recovery.img",
|
||||||
"SYSTEM/bin/install-recovery.sh"):
|
"SYSTEM/bin/install-recovery.sh"):
|
||||||
OPTIONS.rebuild_recovery = True
|
OPTIONS.rebuild_recovery = True
|
||||||
|
|
||||||
# Don't copy OTA keys if we're replacing them.
|
# Don't copy OTA keys if we're replacing them.
|
||||||
elif (OPTIONS.replace_ota_keys and
|
elif (OPTIONS.replace_ota_keys and
|
||||||
info.filename in (
|
filename in (
|
||||||
"BOOT/RAMDISK/res/keys",
|
"BOOT/RAMDISK/res/keys",
|
||||||
"BOOT/RAMDISK/etc/update_engine/update-payload-key.pub.pem",
|
"BOOT/RAMDISK/etc/update_engine/update-payload-key.pub.pem",
|
||||||
"RECOVERY/RAMDISK/res/keys",
|
"RECOVERY/RAMDISK/res/keys",
|
||||||
|
@ -315,22 +347,21 @@ def ProcessTargetFiles(input_tf_zip, output_tf_zip, misc_info,
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Skip META/misc_info.txt since we will write back the new values later.
|
# Skip META/misc_info.txt since we will write back the new values later.
|
||||||
elif info.filename == "META/misc_info.txt":
|
elif filename == "META/misc_info.txt":
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Skip verity public key if we will replace it.
|
# Skip verity public key if we will replace it.
|
||||||
elif (OPTIONS.replace_verity_public_key and
|
elif (OPTIONS.replace_verity_public_key and
|
||||||
info.filename in ("BOOT/RAMDISK/verity_key",
|
filename in ("BOOT/RAMDISK/verity_key",
|
||||||
"ROOT/verity_key")):
|
"ROOT/verity_key")):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Skip verity keyid (for system_root_image use) if we will replace it.
|
# Skip verity keyid (for system_root_image use) if we will replace it.
|
||||||
elif (OPTIONS.replace_verity_keyid and
|
elif OPTIONS.replace_verity_keyid and filename == "BOOT/cmdline":
|
||||||
info.filename == "BOOT/cmdline"):
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Skip the care_map as we will regenerate the system/vendor images.
|
# Skip the care_map as we will regenerate the system/vendor images.
|
||||||
elif info.filename == "META/care_map.txt":
|
elif filename == "META/care_map.txt":
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# A non-APK file; copy it verbatim.
|
# A non-APK file; copy it verbatim.
|
||||||
|
|
|
@ -24,7 +24,8 @@ import zipfile
|
||||||
import common
|
import common
|
||||||
import test_utils
|
import test_utils
|
||||||
from sign_target_files_apks import (
|
from sign_target_files_apks import (
|
||||||
EditTags, ReplaceCerts, ReplaceVerityKeyId, RewriteProps)
|
CheckAllApksSigned, EditTags, GetApkFileInfo, ReplaceCerts,
|
||||||
|
ReplaceVerityKeyId, RewriteProps)
|
||||||
|
|
||||||
|
|
||||||
class SignTargetFilesApksTest(unittest.TestCase):
|
class SignTargetFilesApksTest(unittest.TestCase):
|
||||||
|
@ -211,3 +212,50 @@ class SignTargetFilesApksTest(unittest.TestCase):
|
||||||
cert2_path[:-9] : 'non-existent',
|
cert2_path[:-9] : 'non-existent',
|
||||||
}
|
}
|
||||||
self.assertEqual(output_xml, ReplaceCerts(input_xml))
|
self.assertEqual(output_xml, ReplaceCerts(input_xml))
|
||||||
|
|
||||||
|
def test_CheckAllApksSigned(self):
|
||||||
|
input_file = common.MakeTempFile(suffix='.zip')
|
||||||
|
with zipfile.ZipFile(input_file, 'w') as input_zip:
|
||||||
|
input_zip.writestr('SYSTEM/app/App1.apk', "App1-content")
|
||||||
|
input_zip.writestr('SYSTEM/app/App2.apk.gz', "App2-content")
|
||||||
|
|
||||||
|
apk_key_map = {
|
||||||
|
'App1.apk' : 'key1',
|
||||||
|
'App2.apk' : 'key2',
|
||||||
|
'App3.apk' : 'key3',
|
||||||
|
}
|
||||||
|
with zipfile.ZipFile(input_file) as input_zip:
|
||||||
|
CheckAllApksSigned(input_zip, apk_key_map, None)
|
||||||
|
CheckAllApksSigned(input_zip, apk_key_map, '.gz')
|
||||||
|
|
||||||
|
# 'App2.apk.gz' won't be considered as an APK.
|
||||||
|
CheckAllApksSigned(input_zip, apk_key_map, None)
|
||||||
|
CheckAllApksSigned(input_zip, apk_key_map, '.xz')
|
||||||
|
|
||||||
|
del apk_key_map['App2.apk']
|
||||||
|
self.assertRaises(
|
||||||
|
AssertionError, CheckAllApksSigned, input_zip, apk_key_map, '.gz')
|
||||||
|
|
||||||
|
def test_GetApkFileInfo(self):
|
||||||
|
(is_apk, is_compressed) = GetApkFileInfo("PRODUCT/apps/Chats.apk", None)
|
||||||
|
self.assertTrue(is_apk)
|
||||||
|
self.assertFalse(is_compressed)
|
||||||
|
|
||||||
|
(is_apk, is_compressed) = GetApkFileInfo("PRODUCT/apps/Chats.dat", None)
|
||||||
|
self.assertFalse(is_apk)
|
||||||
|
self.assertFalse(is_compressed)
|
||||||
|
|
||||||
|
def test_GetApkFileInfo_withCompressedApks(self):
|
||||||
|
(is_apk, is_compressed) = GetApkFileInfo("PRODUCT/apps/Chats.apk.gz", ".gz")
|
||||||
|
self.assertTrue(is_apk)
|
||||||
|
self.assertTrue(is_compressed)
|
||||||
|
|
||||||
|
(is_apk, is_compressed) = GetApkFileInfo("PRODUCT/apps/Chats.apk.gz", ".xz")
|
||||||
|
self.assertFalse(is_apk)
|
||||||
|
self.assertFalse(is_compressed)
|
||||||
|
|
||||||
|
self.assertRaises(
|
||||||
|
AssertionError, GetApkFileInfo, "PRODUCT/apps/Chats.apk", "")
|
||||||
|
|
||||||
|
self.assertRaises(
|
||||||
|
AssertionError, GetApkFileInfo, "PRODUCT/apps/Chats.apk", "apk")
|
||||||
|
|
Loading…
Reference in a new issue