Merge "releasetools: Add support for compressed APKs." into oc-mr1-dev

am: 1d2518a649

Change-Id: If80c340c6680362568ad30866686b3a60047fa14
This commit is contained in:
Narayan Kamath 2017-08-23 14:28:53 +00:00 committed by android-build-merger
commit b5c7de2c6b
3 changed files with 121 additions and 21 deletions

View File

@ -235,12 +235,40 @@ class TargetFiles(object):
self.certmap = None self.certmap = None
def LoadZipFile(self, filename): def LoadZipFile(self, filename):
d, z = common.UnzipTemp(filename, ['*.apk']) # First read the APK certs file to figure out whether there are compressed
# APKs in the archive. If we do have compressed APKs in the archive, then we
# must decompress them individually before we perform any analysis.
# This is the list of wildcards of files we extract from |filename|.
apk_extensions = ['*.apk']
self.certmap, compressed_extension = common.ReadApkCerts(zipfile.ZipFile(filename, "r"))
if compressed_extension:
apk_extensions.append("*.apk" + compressed_extension)
d, z = common.UnzipTemp(filename, apk_extensions)
try: try:
self.apks = {} self.apks = {}
self.apks_by_basename = {} self.apks_by_basename = {}
for dirpath, _, filenames in os.walk(d): for dirpath, _, filenames in os.walk(d):
for fn in filenames: for fn in filenames:
# Decompress compressed APKs before we begin processing them.
if compressed_extension and fn.endswith(compressed_extension):
# First strip the compressed extension from the file.
uncompressed_fn = fn[:-len(compressed_extension)]
# Decompress the compressed file to the output file.
common.Gunzip(os.path.join(dirpath, fn),
os.path.join(dirpath, uncompressed_fn))
# Finally, delete the compressed file and use the uncompressed file
# for further processing. Note that the deletion is not strictly required,
# but is done here to ensure that we're not using too much space in
# the temporary directory.
os.remove(os.path.join(dirpath, fn))
fn = uncompressed_fn
if fn.endswith(".apk"): if fn.endswith(".apk"):
fullname = os.path.join(dirpath, fn) fullname = os.path.join(dirpath, fn)
displayname = fullname[len(d)+1:] displayname = fullname[len(d)+1:]
@ -253,7 +281,6 @@ class TargetFiles(object):
finally: finally:
shutil.rmtree(d) shutil.rmtree(d)
self.certmap = common.ReadApkCerts(z)
z.close() z.close()
def CheckSharedUids(self): def CheckSharedUids(self):

View File

@ -18,6 +18,7 @@ import copy
import errno import errno
import getopt import getopt
import getpass import getpass
import gzip
import imp import imp
import os import os
import platform import platform
@ -552,6 +553,13 @@ def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
return None return None
def Gunzip(in_filename, out_filename):
"""Gunzip the given gzip compressed file to a given output file.
"""
with gzip.open(in_filename, "rb") as in_file, open(out_filename, "wb") as out_file:
shutil.copyfileobj(in_file, out_file)
def UnzipTemp(filename, pattern=None): def UnzipTemp(filename, pattern=None):
"""Unzip the given archive into a temporary directory and return the name. """Unzip the given archive into a temporary directory and return the name.
@ -757,16 +765,26 @@ def CheckSize(data, target, info_dict):
def ReadApkCerts(tf_zip): def ReadApkCerts(tf_zip):
"""Given a target_files ZipFile, parse the META/apkcerts.txt file """Given a target_files ZipFile, parse the META/apkcerts.txt file
and return a {package: cert} dict.""" and return a tuple with the following elements: (1) a dictionary that maps
packages to certs (based on the "certificate" and "private_key" attributes
in the file. (2) A string representing the extension of compressed APKs in
the target files (e.g ".gz" ".bro")."""
certmap = {} certmap = {}
compressed_extension = None
for line in tf_zip.read("META/apkcerts.txt").split("\n"): for line in tf_zip.read("META/apkcerts.txt").split("\n"):
line = line.strip() line = line.strip()
if not line: if not line:
continue continue
m = re.match(r'^name="(.*)"\s+certificate="(.*)"\s+' m = re.match(r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
r'private_key="(.*)"$', line) r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*)")?$',
line)
if m: if m:
name, cert, privkey = m.groups() matches = m.groupdict()
cert = matches["CERT"]
privkey = matches["PRIVKEY"]
name = matches["NAME"]
this_compressed_extension = matches["COMPRESSED"]
public_key_suffix_len = len(OPTIONS.public_key_suffix) public_key_suffix_len = len(OPTIONS.public_key_suffix)
private_key_suffix_len = len(OPTIONS.private_key_suffix) private_key_suffix_len = len(OPTIONS.private_key_suffix)
if cert in SPECIAL_CERT_STRINGS and not privkey: if cert in SPECIAL_CERT_STRINGS and not privkey:
@ -777,7 +795,18 @@ def ReadApkCerts(tf_zip):
certmap[name] = cert[:-public_key_suffix_len] certmap[name] = cert[:-public_key_suffix_len]
else: else:
raise ValueError("failed to parse line from apkcerts.txt:\n" + line) raise ValueError("failed to parse line from apkcerts.txt:\n" + line)
return certmap if this_compressed_extension:
# Make sure that all the values in the compression map have the same
# extension. We don't support multiple compression methods in the same
# system image.
if compressed_extension:
if this_compressed_extension != compressed_extension:
raise ValueError("multiple compressed extensions : %s vs %s",
(compressed_extension, this_compressed_extension))
else:
compressed_extension = this_compressed_extension
return (certmap, ("." + compressed_extension) if compressed_extension else None)
COMMON_DOCSTRING = """ COMMON_DOCSTRING = """

View File

@ -100,8 +100,10 @@ import base64
import cStringIO import cStringIO
import copy import copy
import errno import errno
import gzip
import os import os
import re import re
import shutil
import stat import stat
import subprocess import subprocess
import tempfile import tempfile
@ -124,9 +126,7 @@ OPTIONS.avb_keys = {}
OPTIONS.avb_algorithms = {} OPTIONS.avb_algorithms = {}
OPTIONS.avb_extra_args = {} OPTIONS.avb_extra_args = {}
def GetApkCerts(tf_zip): def GetApkCerts(certmap):
certmap = common.ReadApkCerts(tf_zip)
# apply the key remapping to the contents of the file # apply the key remapping to the contents of the file
for apk, cert in certmap.iteritems(): for apk, cert in certmap.iteritems():
certmap[apk] = OPTIONS.key_map.get(cert, cert) certmap[apk] = OPTIONS.key_map.get(cert, cert)
@ -140,13 +140,19 @@ def GetApkCerts(tf_zip):
return certmap return certmap
def CheckAllApksSigned(input_tf_zip, apk_key_map): def CheckAllApksSigned(input_tf_zip, apk_key_map, compressed_extension):
"""Check that all the APKs we want to sign have keys specified, and """Check that all the APKs we want to sign have keys specified, and
error out if they don't.""" error out if they don't."""
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"): if (info.filename.endswith(".apk") or
(compressed_apk_extension and info.filename.endswith(compressed_apk_extension))):
name = os.path.basename(info.filename) name = os.path.basename(info.filename)
if compressed_apk_extension and name.endswith(compressed_apk_extension):
name = name[:-len(compressed_extension)]
if name not in apk_key_map: if name not in apk_key_map:
unknown_apks.append(name) unknown_apks.append(name)
if unknown_apks: if unknown_apks:
@ -157,11 +163,25 @@ def CheckAllApksSigned(input_tf_zip, apk_key_map):
sys.exit(1) sys.exit(1)
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,
is_compressed):
unsigned = tempfile.NamedTemporaryFile() unsigned = tempfile.NamedTemporaryFile()
unsigned.write(data) unsigned.write(data)
unsigned.flush() unsigned.flush()
if is_compressed:
uncompressed = tempfile.NamedTemporaryFile()
with gzip.open(unsigned.name, "rb") as in_file, open(uncompressed.name, "wb") as out_file:
shutil.copyfileobj(in_file, out_file)
# Finally, close the "unsigned" file (which is gzip compressed), and then
# replace it with the uncompressed version.
#
# TODO(narayan): All this nastiness can be avoided if python 3.2 is in use,
# we could just gzip / gunzip in-memory buffers instead.
unsigned.close()
unsigned = uncompressed
signed = tempfile.NamedTemporaryFile() signed = tempfile.NamedTemporaryFile()
# For pre-N builds, don't upgrade to SHA-256 JAR signatures based on the APK's # For pre-N builds, don't upgrade to SHA-256 JAR signatures based on the APK's
@ -186,7 +206,18 @@ def SignApk(data, keyname, pw, platform_api_level, codename_to_api_level_map):
min_api_level=min_api_level, min_api_level=min_api_level,
codename_to_api_level_map=codename_to_api_level_map) codename_to_api_level_map=codename_to_api_level_map)
data = signed.read() data = None;
if is_compressed:
# Recompress the file after it has been signed.
compressed = tempfile.NamedTemporaryFile()
with open(signed.name, "rb") as in_file, gzip.open(compressed.name, "wb") as out_file:
shutil.copyfileobj(in_file, out_file)
data = compressed.read()
compressed.close()
else:
data = signed.read()
unsigned.close() unsigned.close()
signed.close() signed.close()
@ -195,11 +226,17 @@ def SignApk(data, keyname, pw, platform_api_level, codename_to_api_level_map):
def ProcessTargetFiles(input_tf_zip, output_tf_zip, misc_info, 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_apk_extension = None
if compressed_extension:
compressed_apk_extension = ".apk" + compressed_extension
maxsize = max([len(os.path.basename(i.filename)) maxsize = max([len(os.path.basename(i.filename))
for i in input_tf_zip.infolist() for i in input_tf_zip.infolist()
if i.filename.endswith('.apk')]) if i.filename.endswith('.apk') or
(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():
@ -210,13 +247,18 @@ def ProcessTargetFiles(input_tf_zip, output_tf_zip, misc_info,
out_info = copy.copy(info) out_info = copy.copy(info)
# Sign APKs. # Sign APKs.
if info.filename.endswith(".apk"): if (info.filename.endswith(".apk") or
(compressed_apk_extension and info.filename.endswith(compressed_apk_extension))):
is_compressed = compressed_extension and info.filename.endswith(compressed_apk_extension)
name = os.path.basename(info.filename) name = os.path.basename(info.filename)
if is_compressed:
name = name[:-len(compressed_extension)]
key = apk_key_map[name] key = apk_key_map[name]
if key not in common.SPECIAL_CERT_STRINGS: if key not in common.SPECIAL_CERT_STRINGS:
print " signing: %-*s (%s)" % (maxsize, name, key) print " signing: %-*s (%s)" % (maxsize, name, key)
signed_data = SignApk(data, key, key_passwords[key], platform_api_level, signed_data = SignApk(data, key, key_passwords[key], platform_api_level,
codename_to_api_level_map) codename_to_api_level_map, is_compressed)
common.ZipWriteStr(output_tf_zip, out_info, signed_data) common.ZipWriteStr(output_tf_zip, out_info, signed_data)
else: else:
# an APK we're not supposed to sign. # an APK we're not supposed to sign.
@ -748,8 +790,9 @@ def main(argv):
BuildKeyMap(misc_info, key_mapping_options) BuildKeyMap(misc_info, key_mapping_options)
apk_key_map = GetApkCerts(input_zip) certmap, compressed_extension = common.ReadApkCerts(input_zip)
CheckAllApksSigned(input_zip, apk_key_map) apk_key_map = GetApkCerts(certmap)
CheckAllApksSigned(input_zip, apk_key_map, compressed_extension)
key_passwords = common.GetKeyPasswords(set(apk_key_map.values())) key_passwords = common.GetKeyPasswords(set(apk_key_map.values()))
platform_api_level, _ = GetApiLevelAndCodename(input_zip) platform_api_level, _ = GetApiLevelAndCodename(input_zip)
@ -758,7 +801,8 @@ def main(argv):
ProcessTargetFiles(input_zip, output_zip, misc_info, ProcessTargetFiles(input_zip, output_zip, misc_info,
apk_key_map, key_passwords, apk_key_map, key_passwords,
platform_api_level, platform_api_level,
codename_to_api_level_map) codename_to_api_level_map,
compressed_extension)
common.ZipClose(input_zip) common.ZipClose(input_zip)
common.ZipClose(output_zip) common.ZipClose(output_zip)