1*33f37583SAndroid Build Coastguard Worker#!/usr/bin/env python 2*33f37583SAndroid Build Coastguard Worker# 3*33f37583SAndroid Build Coastguard Worker# Copyright (C) 2020 The Android Open Source Project 4*33f37583SAndroid Build Coastguard Worker# 5*33f37583SAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License"); 6*33f37583SAndroid Build Coastguard Worker# you may not use this file except in compliance with the License. 7*33f37583SAndroid Build Coastguard Worker# You may obtain a copy of the License at 8*33f37583SAndroid Build Coastguard Worker# 9*33f37583SAndroid Build Coastguard Worker# http://www.apache.org/licenses/LICENSE-2.0 10*33f37583SAndroid Build Coastguard Worker# 11*33f37583SAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software 12*33f37583SAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS, 13*33f37583SAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14*33f37583SAndroid Build Coastguard Worker# See the License for the specific language governing permissions and 15*33f37583SAndroid Build Coastguard Worker# limitations under the License. 16*33f37583SAndroid Build Coastguard Worker# 17*33f37583SAndroid Build Coastguard Worker"""Unit tests for apexer.""" 18*33f37583SAndroid Build Coastguard Worker 19*33f37583SAndroid Build Coastguard Workerimport hashlib 20*33f37583SAndroid Build Coastguard Workerimport json 21*33f37583SAndroid Build Coastguard Workerimport logging 22*33f37583SAndroid Build Coastguard Workerimport os 23*33f37583SAndroid Build Coastguard Workerimport shutil 24*33f37583SAndroid Build Coastguard Workerimport stat 25*33f37583SAndroid Build Coastguard Workerimport subprocess 26*33f37583SAndroid Build Coastguard Workerimport tempfile 27*33f37583SAndroid Build Coastguard Workerimport unittest 28*33f37583SAndroid Build Coastguard Workerfrom importlib import resources 29*33f37583SAndroid Build Coastguard Workerfrom zipfile import ZipFile 30*33f37583SAndroid Build Coastguard Worker 31*33f37583SAndroid Build Coastguard Workerfrom apex_manifest import ValidateApexManifest 32*33f37583SAndroid Build Coastguard Workerfrom apex_manifest import ParseApexManifest 33*33f37583SAndroid Build Coastguard Worker 34*33f37583SAndroid Build Coastguard Workerlogger = logging.getLogger(__name__) 35*33f37583SAndroid Build Coastguard Worker 36*33f37583SAndroid Build Coastguard WorkerTEST_APEX = "com.android.example.apex" 37*33f37583SAndroid Build Coastguard WorkerTEST_APEX_LEGACY = "com.android.example-legacy.apex" 38*33f37583SAndroid Build Coastguard WorkerTEST_APEX_WITH_LOGGING_PARENT = "com.android.example-logging_parent.apex" 39*33f37583SAndroid Build Coastguard WorkerTEST_APEX_WITH_OVERRIDDEN_PACKAGE_NAME = "com.android.example-overridden_package_name.apex" 40*33f37583SAndroid Build Coastguard Worker 41*33f37583SAndroid Build Coastguard WorkerTEST_PRIVATE_KEY = os.path.join("testdata", "com.android.example.apex.pem") 42*33f37583SAndroid Build Coastguard WorkerTEST_X509_KEY = os.path.join("testdata", "com.android.example.apex.x509.pem") 43*33f37583SAndroid Build Coastguard WorkerTEST_PK8_KEY = os.path.join("testdata", "com.android.example.apex.pk8") 44*33f37583SAndroid Build Coastguard WorkerTEST_AVB_PUBLIC_KEY = os.path.join("testdata", "com.android.example.apex.avbpubkey") 45*33f37583SAndroid Build Coastguard WorkerTEST_MANIFEST_JSON = os.path.join("testdata", "manifest.json") 46*33f37583SAndroid Build Coastguard Worker 47*33f37583SAndroid Build Coastguard Workerdef run(args, verbose=None, **kwargs): 48*33f37583SAndroid Build Coastguard Worker """Creates and returns a subprocess.Popen object. 49*33f37583SAndroid Build Coastguard Worker 50*33f37583SAndroid Build Coastguard Worker Args: 51*33f37583SAndroid Build Coastguard Worker args: The command represented as a list of strings. 52*33f37583SAndroid Build Coastguard Worker verbose: Whether the commands should be shown. Default to the global 53*33f37583SAndroid Build Coastguard Worker verbosity if unspecified. 54*33f37583SAndroid Build Coastguard Worker kwargs: Any additional args to be passed to subprocess.Popen(), such as env, 55*33f37583SAndroid Build Coastguard Worker stdin, etc. stdout and stderr will default to subprocess.PIPE and 56*33f37583SAndroid Build Coastguard Worker subprocess.STDOUT respectively unless caller specifies any of them. 57*33f37583SAndroid Build Coastguard Worker universal_newlines will default to True, as most of the users in 58*33f37583SAndroid Build Coastguard Worker releasetools expect string output. 59*33f37583SAndroid Build Coastguard Worker 60*33f37583SAndroid Build Coastguard Worker Returns: 61*33f37583SAndroid Build Coastguard Worker A subprocess.Popen object. 62*33f37583SAndroid Build Coastguard Worker """ 63*33f37583SAndroid Build Coastguard Worker if 'stdout' not in kwargs and 'stderr' not in kwargs: 64*33f37583SAndroid Build Coastguard Worker kwargs['stdout'] = subprocess.PIPE 65*33f37583SAndroid Build Coastguard Worker kwargs['stderr'] = subprocess.STDOUT 66*33f37583SAndroid Build Coastguard Worker if 'universal_newlines' not in kwargs: 67*33f37583SAndroid Build Coastguard Worker kwargs['universal_newlines'] = True 68*33f37583SAndroid Build Coastguard Worker # Don't log any if caller explicitly says so. 69*33f37583SAndroid Build Coastguard Worker if DEBUG_TEST: 70*33f37583SAndroid Build Coastguard Worker print("\nRunning: \n%s\n" % " ".join(args)) 71*33f37583SAndroid Build Coastguard Worker if verbose: 72*33f37583SAndroid Build Coastguard Worker logger.info(" Running: \"%s\"", " ".join(args)) 73*33f37583SAndroid Build Coastguard Worker return subprocess.Popen(args, **kwargs) 74*33f37583SAndroid Build Coastguard Worker 75*33f37583SAndroid Build Coastguard Worker 76*33f37583SAndroid Build Coastguard Workerdef run_host_command(args, verbose=None, **kwargs): 77*33f37583SAndroid Build Coastguard Worker host_build_top = os.environ.get("ANDROID_BUILD_TOP") 78*33f37583SAndroid Build Coastguard Worker if host_build_top: 79*33f37583SAndroid Build Coastguard Worker host_command_dir = os.path.join(host_build_top, "out/host/linux-x86/bin") 80*33f37583SAndroid Build Coastguard Worker args[0] = os.path.join(host_command_dir, args[0]) 81*33f37583SAndroid Build Coastguard Worker return run_and_check_output(args, verbose, **kwargs) 82*33f37583SAndroid Build Coastguard Worker 83*33f37583SAndroid Build Coastguard Worker 84*33f37583SAndroid Build Coastguard Workerdef run_and_check_output(args, verbose=None, **kwargs): 85*33f37583SAndroid Build Coastguard Worker """Runs the given command and returns the output. 86*33f37583SAndroid Build Coastguard Worker 87*33f37583SAndroid Build Coastguard Worker Args: 88*33f37583SAndroid Build Coastguard Worker args: The command represented as a list of strings. 89*33f37583SAndroid Build Coastguard Worker verbose: Whether the commands should be shown. Default to the global 90*33f37583SAndroid Build Coastguard Worker verbosity if unspecified. 91*33f37583SAndroid Build Coastguard Worker kwargs: Any additional args to be passed to subprocess.Popen(), such as env, 92*33f37583SAndroid Build Coastguard Worker stdin, etc. stdout and stderr will default to subprocess.PIPE and 93*33f37583SAndroid Build Coastguard Worker subprocess.STDOUT respectively unless caller specifies any of them. 94*33f37583SAndroid Build Coastguard Worker 95*33f37583SAndroid Build Coastguard Worker Returns: 96*33f37583SAndroid Build Coastguard Worker The output string. 97*33f37583SAndroid Build Coastguard Worker 98*33f37583SAndroid Build Coastguard Worker Raises: 99*33f37583SAndroid Build Coastguard Worker ExternalError: On non-zero exit from the command. 100*33f37583SAndroid Build Coastguard Worker """ 101*33f37583SAndroid Build Coastguard Worker proc = run(args, verbose=verbose, **kwargs) 102*33f37583SAndroid Build Coastguard Worker output, _ = proc.communicate() 103*33f37583SAndroid Build Coastguard Worker if output is None: 104*33f37583SAndroid Build Coastguard Worker output = "" 105*33f37583SAndroid Build Coastguard Worker # Don't log any if caller explicitly says so. 106*33f37583SAndroid Build Coastguard Worker if verbose: 107*33f37583SAndroid Build Coastguard Worker logger.info("%s", output.rstrip()) 108*33f37583SAndroid Build Coastguard Worker if proc.returncode != 0: 109*33f37583SAndroid Build Coastguard Worker raise RuntimeError( 110*33f37583SAndroid Build Coastguard Worker "Failed to run command '{}' (exit code {}):\n{}".format( 111*33f37583SAndroid Build Coastguard Worker args, proc.returncode, output)) 112*33f37583SAndroid Build Coastguard Worker return output 113*33f37583SAndroid Build Coastguard Worker 114*33f37583SAndroid Build Coastguard Worker 115*33f37583SAndroid Build Coastguard Workerdef get_sha1sum(file_path): 116*33f37583SAndroid Build Coastguard Worker h = hashlib.sha256() 117*33f37583SAndroid Build Coastguard Worker 118*33f37583SAndroid Build Coastguard Worker with open(file_path, 'rb') as file: 119*33f37583SAndroid Build Coastguard Worker while True: 120*33f37583SAndroid Build Coastguard Worker # Reading is buffered, so we can read smaller chunks. 121*33f37583SAndroid Build Coastguard Worker chunk = file.read(h.block_size) 122*33f37583SAndroid Build Coastguard Worker if not chunk: 123*33f37583SAndroid Build Coastguard Worker break 124*33f37583SAndroid Build Coastguard Worker h.update(chunk) 125*33f37583SAndroid Build Coastguard Worker 126*33f37583SAndroid Build Coastguard Worker return h.hexdigest() 127*33f37583SAndroid Build Coastguard Worker 128*33f37583SAndroid Build Coastguard Worker 129*33f37583SAndroid Build Coastguard Workerdef round_up(size, unit): 130*33f37583SAndroid Build Coastguard Worker assert unit & (unit - 1) == 0 131*33f37583SAndroid Build Coastguard Worker return (size + unit - 1) & (~(unit - 1)) 132*33f37583SAndroid Build Coastguard Worker 133*33f37583SAndroid Build Coastguard Worker# In order to debug test failures, set DEBUG_TEST to True and run the test from 134*33f37583SAndroid Build Coastguard Worker# local workstation bypassing atest, e.g.: 135*33f37583SAndroid Build Coastguard Worker# $ m apexer_test && out/host/linux-x86/nativetest64/apexer_test/apexer_test 136*33f37583SAndroid Build Coastguard Worker# 137*33f37583SAndroid Build Coastguard Worker# the test will print out the command used, and the temporary files used by the 138*33f37583SAndroid Build Coastguard Worker# test. You need to compare e.g. /tmp/test_simple_apex_input_XXXXXXXX.apex with 139*33f37583SAndroid Build Coastguard Worker# /tmp/test_simple_apex_repacked_YYYYYYYY.apex to check where they are 140*33f37583SAndroid Build Coastguard Worker# different. 141*33f37583SAndroid Build Coastguard Worker# A simple script to analyze the differences: 142*33f37583SAndroid Build Coastguard Worker# 143*33f37583SAndroid Build Coastguard Worker# FILE_INPUT=/tmp/test_simple_apex_input_XXXXXXXX.apex 144*33f37583SAndroid Build Coastguard Worker# FILE_OUTPUT=/tmp/test_simple_apex_repacked_YYYYYYYY.apex 145*33f37583SAndroid Build Coastguard Worker# 146*33f37583SAndroid Build Coastguard Worker# cd ~/tmp/ 147*33f37583SAndroid Build Coastguard Worker# rm -rf input output 148*33f37583SAndroid Build Coastguard Worker# mkdir input output 149*33f37583SAndroid Build Coastguard Worker# unzip ${FILE_INPUT} -d input/ 150*33f37583SAndroid Build Coastguard Worker# unzip ${FILE_OUTPUT} -d output/ 151*33f37583SAndroid Build Coastguard Worker# 152*33f37583SAndroid Build Coastguard Worker# diff -r input/ output/ 153*33f37583SAndroid Build Coastguard Worker# 154*33f37583SAndroid Build Coastguard Worker# For analyzing binary diffs I had mild success using the vbindiff utility. 155*33f37583SAndroid Build Coastguard WorkerDEBUG_TEST = False 156*33f37583SAndroid Build Coastguard Worker 157*33f37583SAndroid Build Coastguard Worker 158*33f37583SAndroid Build Coastguard Workerclass ApexerRebuildTest(unittest.TestCase): 159*33f37583SAndroid Build Coastguard Worker def setUp(self): 160*33f37583SAndroid Build Coastguard Worker self._to_cleanup = [] 161*33f37583SAndroid Build Coastguard Worker self._get_host_tools() 162*33f37583SAndroid Build Coastguard Worker 163*33f37583SAndroid Build Coastguard Worker def tearDown(self): 164*33f37583SAndroid Build Coastguard Worker if not DEBUG_TEST: 165*33f37583SAndroid Build Coastguard Worker for i in self._to_cleanup: 166*33f37583SAndroid Build Coastguard Worker if os.path.isdir(i): 167*33f37583SAndroid Build Coastguard Worker shutil.rmtree(i, ignore_errors=True) 168*33f37583SAndroid Build Coastguard Worker else: 169*33f37583SAndroid Build Coastguard Worker os.remove(i) 170*33f37583SAndroid Build Coastguard Worker del self._to_cleanup[:] 171*33f37583SAndroid Build Coastguard Worker else: 172*33f37583SAndroid Build Coastguard Worker print(self._to_cleanup) 173*33f37583SAndroid Build Coastguard Worker 174*33f37583SAndroid Build Coastguard Worker def _get_host_tools(self): 175*33f37583SAndroid Build Coastguard Worker dir_name = tempfile.mkdtemp(prefix=self._testMethodName+"_host_tools_") 176*33f37583SAndroid Build Coastguard Worker self._to_cleanup.append(dir_name) 177*33f37583SAndroid Build Coastguard Worker with resources.files("apexer_test").joinpath("apexer_test_host_tools.zip").open('rb') as f: 178*33f37583SAndroid Build Coastguard Worker with ZipFile(f, 'r') as zip_obj: 179*33f37583SAndroid Build Coastguard Worker zip_obj.extractall(path=dir_name) 180*33f37583SAndroid Build Coastguard Worker 181*33f37583SAndroid Build Coastguard Worker files = {} 182*33f37583SAndroid Build Coastguard Worker for i in ["apexer", "deapexer", "avbtool", "mke2fs", "sefcontext_compile", "e2fsdroid", 183*33f37583SAndroid Build Coastguard Worker "resize2fs", "soong_zip", "aapt2", "merge_zips", "zipalign", "debugfs_static", 184*33f37583SAndroid Build Coastguard Worker "signapk.jar", "android.jar", "make_erofs", "fsck.erofs", "conv_apex_manifest"]: 185*33f37583SAndroid Build Coastguard Worker file_path = os.path.join(dir_name, "bin", i) 186*33f37583SAndroid Build Coastguard Worker if os.path.exists(file_path): 187*33f37583SAndroid Build Coastguard Worker os.chmod(file_path, stat.S_IRUSR | stat.S_IXUSR) 188*33f37583SAndroid Build Coastguard Worker files[i] = file_path 189*33f37583SAndroid Build Coastguard Worker else: 190*33f37583SAndroid Build Coastguard Worker files[i] = i 191*33f37583SAndroid Build Coastguard Worker self.host_tools = files 192*33f37583SAndroid Build Coastguard Worker self.host_tools_path = os.path.join(dir_name, "bin") 193*33f37583SAndroid Build Coastguard Worker 194*33f37583SAndroid Build Coastguard Worker path = self.host_tools_path 195*33f37583SAndroid Build Coastguard Worker if "PATH" in os.environ: 196*33f37583SAndroid Build Coastguard Worker path += ":" + os.environ["PATH"] 197*33f37583SAndroid Build Coastguard Worker os.environ["PATH"] = path 198*33f37583SAndroid Build Coastguard Worker 199*33f37583SAndroid Build Coastguard Worker ld_library_path = os.path.join(dir_name, "lib64") 200*33f37583SAndroid Build Coastguard Worker if "LD_LIBRARY_PATH" in os.environ: 201*33f37583SAndroid Build Coastguard Worker ld_library_path += ":" + os.environ["LD_LIBRARY_PATH"] 202*33f37583SAndroid Build Coastguard Worker if "ANDROID_HOST_OUT" in os.environ: 203*33f37583SAndroid Build Coastguard Worker ld_library_path += ":" + os.path.join(os.environ["ANDROID_HOST_OUT"], "lib64") 204*33f37583SAndroid Build Coastguard Worker os.environ["LD_LIBRARY_PATH"] = ld_library_path 205*33f37583SAndroid Build Coastguard Worker 206*33f37583SAndroid Build Coastguard Worker def _extract_resource(self, resource_name): 207*33f37583SAndroid Build Coastguard Worker with ( 208*33f37583SAndroid Build Coastguard Worker resources.files("apexer_test").joinpath(resource_name).open('rb') as f, 209*33f37583SAndroid Build Coastguard Worker tempfile.NamedTemporaryFile(prefix=resource_name.replace('/', '_'), delete=False) as f2, 210*33f37583SAndroid Build Coastguard Worker ): 211*33f37583SAndroid Build Coastguard Worker self._to_cleanup.append(f2.name) 212*33f37583SAndroid Build Coastguard Worker shutil.copyfileobj(f, f2) 213*33f37583SAndroid Build Coastguard Worker return f2.name 214*33f37583SAndroid Build Coastguard Worker 215*33f37583SAndroid Build Coastguard Worker def _get_container_files(self, apex_file_path): 216*33f37583SAndroid Build Coastguard Worker dir_name = tempfile.mkdtemp(prefix=self._testMethodName+"_container_files_") 217*33f37583SAndroid Build Coastguard Worker self._to_cleanup.append(dir_name) 218*33f37583SAndroid Build Coastguard Worker with ZipFile(apex_file_path, 'r') as zip_obj: 219*33f37583SAndroid Build Coastguard Worker zip_obj.extractall(path=dir_name) 220*33f37583SAndroid Build Coastguard Worker files = {} 221*33f37583SAndroid Build Coastguard Worker for i in ["apex_manifest.json", "apex_manifest.pb", 222*33f37583SAndroid Build Coastguard Worker "apex_build_info.pb", "assets", 223*33f37583SAndroid Build Coastguard Worker "apex_payload.img", "apex_payload.zip"]: 224*33f37583SAndroid Build Coastguard Worker file_path = os.path.join(dir_name, i) 225*33f37583SAndroid Build Coastguard Worker if os.path.exists(file_path): 226*33f37583SAndroid Build Coastguard Worker files[i] = file_path 227*33f37583SAndroid Build Coastguard Worker self.assertIn("apex_manifest.pb", files) 228*33f37583SAndroid Build Coastguard Worker self.assertIn("apex_build_info.pb", files) 229*33f37583SAndroid Build Coastguard Worker 230*33f37583SAndroid Build Coastguard Worker image_file = None 231*33f37583SAndroid Build Coastguard Worker if "apex_payload.img" in files: 232*33f37583SAndroid Build Coastguard Worker image_file = files["apex_payload.img"] 233*33f37583SAndroid Build Coastguard Worker elif "apex_payload.zip" in files: 234*33f37583SAndroid Build Coastguard Worker image_file = files["apex_payload.zip"] 235*33f37583SAndroid Build Coastguard Worker self.assertIsNotNone(image_file) 236*33f37583SAndroid Build Coastguard Worker files["apex_payload"] = image_file 237*33f37583SAndroid Build Coastguard Worker 238*33f37583SAndroid Build Coastguard Worker return files 239*33f37583SAndroid Build Coastguard Worker 240*33f37583SAndroid Build Coastguard Worker def _extract_payload_from_img(self, img_file_path): 241*33f37583SAndroid Build Coastguard Worker dir_name = tempfile.mkdtemp(prefix=self._testMethodName+"_extracted_payload_") 242*33f37583SAndroid Build Coastguard Worker self._to_cleanup.append(dir_name) 243*33f37583SAndroid Build Coastguard Worker cmd = ["debugfs_static", '-R', 'rdump ./ %s' % dir_name, img_file_path] 244*33f37583SAndroid Build Coastguard Worker run_host_command(cmd) 245*33f37583SAndroid Build Coastguard Worker 246*33f37583SAndroid Build Coastguard Worker # Remove payload files added by apexer and e2fs tools. 247*33f37583SAndroid Build Coastguard Worker for i in ["apex_manifest.json", "apex_manifest.pb"]: 248*33f37583SAndroid Build Coastguard Worker if os.path.exists(os.path.join(dir_name, i)): 249*33f37583SAndroid Build Coastguard Worker os.remove(os.path.join(dir_name, i)) 250*33f37583SAndroid Build Coastguard Worker if os.path.isdir(os.path.join(dir_name, "lost+found")): 251*33f37583SAndroid Build Coastguard Worker shutil.rmtree(os.path.join(dir_name, "lost+found")) 252*33f37583SAndroid Build Coastguard Worker return dir_name 253*33f37583SAndroid Build Coastguard Worker 254*33f37583SAndroid Build Coastguard Worker def _extract_payload(self, apex_file_path): 255*33f37583SAndroid Build Coastguard Worker dir_name = tempfile.mkdtemp(prefix=self._testMethodName+"_extracted_payload_") 256*33f37583SAndroid Build Coastguard Worker self._to_cleanup.append(dir_name) 257*33f37583SAndroid Build Coastguard Worker cmd = ["deapexer", "--debugfs_path", self.host_tools["debugfs_static"], 258*33f37583SAndroid Build Coastguard Worker "--fsckerofs_path", self.host_tools["fsck.erofs"], "extract", 259*33f37583SAndroid Build Coastguard Worker apex_file_path, dir_name] 260*33f37583SAndroid Build Coastguard Worker run_host_command(cmd) 261*33f37583SAndroid Build Coastguard Worker 262*33f37583SAndroid Build Coastguard Worker # Remove payload files added by apexer and e2fs tools. 263*33f37583SAndroid Build Coastguard Worker for i in ["apex_manifest.json", "apex_manifest.pb"]: 264*33f37583SAndroid Build Coastguard Worker if os.path.exists(os.path.join(dir_name, i)): 265*33f37583SAndroid Build Coastguard Worker os.remove(os.path.join(dir_name, i)) 266*33f37583SAndroid Build Coastguard Worker if os.path.isdir(os.path.join(dir_name, "lost+found")): 267*33f37583SAndroid Build Coastguard Worker shutil.rmtree(os.path.join(dir_name, "lost+found")) 268*33f37583SAndroid Build Coastguard Worker return dir_name 269*33f37583SAndroid Build Coastguard Worker 270*33f37583SAndroid Build Coastguard Worker def _run_apexer(self, container_files, payload_dir, args=[]): 271*33f37583SAndroid Build Coastguard Worker unsigned_payload_only = False 272*33f37583SAndroid Build Coastguard Worker payload_only = False 273*33f37583SAndroid Build Coastguard Worker if "--unsigned_payload_only" in args: 274*33f37583SAndroid Build Coastguard Worker unsigned_payload_only = True 275*33f37583SAndroid Build Coastguard Worker if unsigned_payload_only or "--payload_only" in args: 276*33f37583SAndroid Build Coastguard Worker payload_only = True 277*33f37583SAndroid Build Coastguard Worker 278*33f37583SAndroid Build Coastguard Worker os.environ["APEXER_TOOL_PATH"] = (self.host_tools_path + 279*33f37583SAndroid Build Coastguard Worker ":out/host/linux-x86/bin:prebuilts/sdk/tools/linux/bin") 280*33f37583SAndroid Build Coastguard Worker cmd = ["apexer", "--force", "--include_build_info", "--do_not_check_keyname"] 281*33f37583SAndroid Build Coastguard Worker if DEBUG_TEST: 282*33f37583SAndroid Build Coastguard Worker cmd.append('-v') 283*33f37583SAndroid Build Coastguard Worker cmd.extend(["--apexer_tool_path", os.environ["APEXER_TOOL_PATH"]]) 284*33f37583SAndroid Build Coastguard Worker cmd.extend(["--android_jar_path", self.host_tools["android.jar"]]) 285*33f37583SAndroid Build Coastguard Worker cmd.extend(["--manifest", container_files["apex_manifest.pb"]]) 286*33f37583SAndroid Build Coastguard Worker if "apex_manifest.json" in container_files: 287*33f37583SAndroid Build Coastguard Worker cmd.extend(["--manifest_json", container_files["apex_manifest.json"]]) 288*33f37583SAndroid Build Coastguard Worker cmd.extend(["--build_info", container_files["apex_build_info.pb"]]) 289*33f37583SAndroid Build Coastguard Worker if not payload_only and "assets" in container_files: 290*33f37583SAndroid Build Coastguard Worker cmd.extend(["--assets_dir", container_files["assets"]]) 291*33f37583SAndroid Build Coastguard Worker if not unsigned_payload_only: 292*33f37583SAndroid Build Coastguard Worker cmd.extend(["--key", self._extract_resource(TEST_PRIVATE_KEY)]) 293*33f37583SAndroid Build Coastguard Worker cmd.extend(["--pubkey", self._extract_resource(TEST_AVB_PUBLIC_KEY)]) 294*33f37583SAndroid Build Coastguard Worker cmd.extend(args) 295*33f37583SAndroid Build Coastguard Worker 296*33f37583SAndroid Build Coastguard Worker # Decide on output file name 297*33f37583SAndroid Build Coastguard Worker apex_suffix = ".apex.unsigned" 298*33f37583SAndroid Build Coastguard Worker if payload_only: 299*33f37583SAndroid Build Coastguard Worker apex_suffix = ".payload" 300*33f37583SAndroid Build Coastguard Worker fd, fn = tempfile.mkstemp(prefix=self._testMethodName+"_repacked_", suffix=apex_suffix) 301*33f37583SAndroid Build Coastguard Worker os.close(fd) 302*33f37583SAndroid Build Coastguard Worker self._to_cleanup.append(fn) 303*33f37583SAndroid Build Coastguard Worker cmd.extend([payload_dir, fn]) 304*33f37583SAndroid Build Coastguard Worker 305*33f37583SAndroid Build Coastguard Worker run_host_command(cmd) 306*33f37583SAndroid Build Coastguard Worker return fn 307*33f37583SAndroid Build Coastguard Worker 308*33f37583SAndroid Build Coastguard Worker def _get_java_toolchain(self): 309*33f37583SAndroid Build Coastguard Worker java_toolchain = "java" 310*33f37583SAndroid Build Coastguard Worker if os.path.isfile("prebuilts/jdk/jdk21/linux-x86/bin/java"): 311*33f37583SAndroid Build Coastguard Worker java_toolchain = "prebuilts/jdk/jdk21/linux-x86/bin/java" 312*33f37583SAndroid Build Coastguard Worker elif os.path.isfile("/jdk/jdk21/linux-x86/bin/java"): 313*33f37583SAndroid Build Coastguard Worker java_toolchain = "/jdk/jdk21/linux-x86/bin/java" 314*33f37583SAndroid Build Coastguard Worker elif "ANDROID_JAVA_TOOLCHAIN" in os.environ: 315*33f37583SAndroid Build Coastguard Worker java_toolchain = os.path.join(os.environ["ANDROID_JAVA_TOOLCHAIN"], "java") 316*33f37583SAndroid Build Coastguard Worker elif "ANDROID_JAVA_HOME" in os.environ: 317*33f37583SAndroid Build Coastguard Worker java_toolchain = os.path.join(os.environ["ANDROID_JAVA_HOME"], "bin", "java") 318*33f37583SAndroid Build Coastguard Worker elif "JAVA_HOME" in os.environ: 319*33f37583SAndroid Build Coastguard Worker java_toolchain = os.path.join(os.environ["JAVA_HOME"], "bin", "java") 320*33f37583SAndroid Build Coastguard Worker 321*33f37583SAndroid Build Coastguard Worker java_dep_lib = os.environ["LD_LIBRARY_PATH"] 322*33f37583SAndroid Build Coastguard Worker if "ANDROID_HOST_OUT" in os.environ: 323*33f37583SAndroid Build Coastguard Worker java_dep_lib += ":" + os.path.join(os.environ["ANDROID_HOST_OUT"], "lib64") 324*33f37583SAndroid Build Coastguard Worker if "ANDROID_BUILD_TOP" in os.environ: 325*33f37583SAndroid Build Coastguard Worker java_dep_lib += ":" + os.path.join(os.environ["ANDROID_BUILD_TOP"], 326*33f37583SAndroid Build Coastguard Worker "out/host/linux-x86/lib64") 327*33f37583SAndroid Build Coastguard Worker 328*33f37583SAndroid Build Coastguard Worker return [java_toolchain, java_dep_lib] 329*33f37583SAndroid Build Coastguard Worker 330*33f37583SAndroid Build Coastguard Worker def _sign_apk_container(self, unsigned_apex): 331*33f37583SAndroid Build Coastguard Worker fd, fn = tempfile.mkstemp(prefix=self._testMethodName+"_repacked_", suffix=".apex") 332*33f37583SAndroid Build Coastguard Worker os.close(fd) 333*33f37583SAndroid Build Coastguard Worker self._to_cleanup.append(fn) 334*33f37583SAndroid Build Coastguard Worker java_toolchain, java_dep_lib = self._get_java_toolchain() 335*33f37583SAndroid Build Coastguard Worker cmd = [ 336*33f37583SAndroid Build Coastguard Worker java_toolchain, 337*33f37583SAndroid Build Coastguard Worker "-Djava.library.path=" + java_dep_lib, 338*33f37583SAndroid Build Coastguard Worker "-jar", self.host_tools['signapk.jar'], 339*33f37583SAndroid Build Coastguard Worker "-a", "4096", "--align-file-size", 340*33f37583SAndroid Build Coastguard Worker self._extract_resource(TEST_X509_KEY), 341*33f37583SAndroid Build Coastguard Worker self._extract_resource(TEST_PK8_KEY), 342*33f37583SAndroid Build Coastguard Worker unsigned_apex, fn] 343*33f37583SAndroid Build Coastguard Worker run_and_check_output(cmd) 344*33f37583SAndroid Build Coastguard Worker return fn 345*33f37583SAndroid Build Coastguard Worker 346*33f37583SAndroid Build Coastguard Worker def _sign_payload(self, container_files, unsigned_payload): 347*33f37583SAndroid Build Coastguard Worker fd, signed_payload = \ 348*33f37583SAndroid Build Coastguard Worker tempfile.mkstemp(prefix=self._testMethodName+"_repacked_", suffix=".payload") 349*33f37583SAndroid Build Coastguard Worker os.close(fd) 350*33f37583SAndroid Build Coastguard Worker self._to_cleanup.append(signed_payload) 351*33f37583SAndroid Build Coastguard Worker shutil.copyfile(unsigned_payload, signed_payload) 352*33f37583SAndroid Build Coastguard Worker 353*33f37583SAndroid Build Coastguard Worker cmd = ['avbtool'] 354*33f37583SAndroid Build Coastguard Worker cmd.append('add_hashtree_footer') 355*33f37583SAndroid Build Coastguard Worker cmd.append('--do_not_generate_fec') 356*33f37583SAndroid Build Coastguard Worker cmd.extend(['--algorithm', 'SHA256_RSA4096']) 357*33f37583SAndroid Build Coastguard Worker cmd.extend(['--hash_algorithm', 'sha256']) 358*33f37583SAndroid Build Coastguard Worker cmd.extend(['--key', self._extract_resource(TEST_PRIVATE_KEY)]) 359*33f37583SAndroid Build Coastguard Worker manifest_apex = ParseApexManifest(container_files["apex_manifest.pb"]) 360*33f37583SAndroid Build Coastguard Worker ValidateApexManifest(manifest_apex) 361*33f37583SAndroid Build Coastguard Worker cmd.extend(['--prop', 'apex.key:' + manifest_apex.name]) 362*33f37583SAndroid Build Coastguard Worker # Set up the salt based on manifest content which includes name 363*33f37583SAndroid Build Coastguard Worker # and version 364*33f37583SAndroid Build Coastguard Worker salt = hashlib.sha256(manifest_apex.SerializeToString()).hexdigest() 365*33f37583SAndroid Build Coastguard Worker cmd.extend(['--salt', salt]) 366*33f37583SAndroid Build Coastguard Worker cmd.extend(['--image', signed_payload]) 367*33f37583SAndroid Build Coastguard Worker run_and_check_output(cmd) 368*33f37583SAndroid Build Coastguard Worker 369*33f37583SAndroid Build Coastguard Worker return signed_payload 370*33f37583SAndroid Build Coastguard Worker 371*33f37583SAndroid Build Coastguard Worker def _verify_payload(self, payload): 372*33f37583SAndroid Build Coastguard Worker """Verifies that the payload is properly signed by avbtool""" 373*33f37583SAndroid Build Coastguard Worker cmd = ["avbtool", "verify_image", "--image", payload, "--accept_zeroed_hashtree"] 374*33f37583SAndroid Build Coastguard Worker run_and_check_output(cmd) 375*33f37583SAndroid Build Coastguard Worker 376*33f37583SAndroid Build Coastguard Worker def _run_build_test(self, apex_name): 377*33f37583SAndroid Build Coastguard Worker apex_file_path = self._extract_resource(apex_name + ".apex") 378*33f37583SAndroid Build Coastguard Worker if DEBUG_TEST: 379*33f37583SAndroid Build Coastguard Worker fd, fn = tempfile.mkstemp(prefix=self._testMethodName+"_input_", suffix=".apex") 380*33f37583SAndroid Build Coastguard Worker os.close(fd) 381*33f37583SAndroid Build Coastguard Worker shutil.copyfile(apex_file_path, fn) 382*33f37583SAndroid Build Coastguard Worker self._to_cleanup.append(fn) 383*33f37583SAndroid Build Coastguard Worker container_files = self._get_container_files(apex_file_path) 384*33f37583SAndroid Build Coastguard Worker payload_dir = self._extract_payload(apex_file_path) 385*33f37583SAndroid Build Coastguard Worker repack_apex_file_path = self._run_apexer(container_files, payload_dir) 386*33f37583SAndroid Build Coastguard Worker resigned_apex_file_path = self._sign_apk_container(repack_apex_file_path) 387*33f37583SAndroid Build Coastguard Worker self.assertEqual(get_sha1sum(apex_file_path), get_sha1sum(resigned_apex_file_path)) 388*33f37583SAndroid Build Coastguard Worker 389*33f37583SAndroid Build Coastguard Worker def test_simple_apex(self): 390*33f37583SAndroid Build Coastguard Worker self._run_build_test(TEST_APEX) 391*33f37583SAndroid Build Coastguard Worker 392*33f37583SAndroid Build Coastguard Worker def test_legacy_apex(self): 393*33f37583SAndroid Build Coastguard Worker self._run_build_test(TEST_APEX_LEGACY) 394*33f37583SAndroid Build Coastguard Worker 395*33f37583SAndroid Build Coastguard Worker def test_output_payload_only(self): 396*33f37583SAndroid Build Coastguard Worker """Assert that payload-only output from apexer is same as the payload we get by unzipping 397*33f37583SAndroid Build Coastguard Worker apex. 398*33f37583SAndroid Build Coastguard Worker """ 399*33f37583SAndroid Build Coastguard Worker apex_file_path = self._extract_resource(TEST_APEX + ".apex") 400*33f37583SAndroid Build Coastguard Worker container_files = self._get_container_files(apex_file_path) 401*33f37583SAndroid Build Coastguard Worker payload_dir = self._extract_payload(apex_file_path) 402*33f37583SAndroid Build Coastguard Worker payload_only_file_path = self._run_apexer(container_files, payload_dir, ["--payload_only"]) 403*33f37583SAndroid Build Coastguard Worker self._verify_payload(payload_only_file_path) 404*33f37583SAndroid Build Coastguard Worker self.assertEqual(get_sha1sum(payload_only_file_path), 405*33f37583SAndroid Build Coastguard Worker get_sha1sum(container_files["apex_payload"])) 406*33f37583SAndroid Build Coastguard Worker 407*33f37583SAndroid Build Coastguard Worker def test_output_unsigned_payload_only(self): 408*33f37583SAndroid Build Coastguard Worker """Assert that when unsigned-payload-only output from apexer is signed by the avb key, it is 409*33f37583SAndroid Build Coastguard Worker same as the payload we get by unzipping apex. 410*33f37583SAndroid Build Coastguard Worker """ 411*33f37583SAndroid Build Coastguard Worker apex_file_path = self._extract_resource(TEST_APEX + ".apex") 412*33f37583SAndroid Build Coastguard Worker container_files = self._get_container_files(apex_file_path) 413*33f37583SAndroid Build Coastguard Worker payload_dir = self._extract_payload(apex_file_path) 414*33f37583SAndroid Build Coastguard Worker unsigned_payload_only_file_path = self._run_apexer(container_files, payload_dir, 415*33f37583SAndroid Build Coastguard Worker ["--unsigned_payload_only"]) 416*33f37583SAndroid Build Coastguard Worker with self.assertRaises(RuntimeError) as error: 417*33f37583SAndroid Build Coastguard Worker self._verify_payload(unsigned_payload_only_file_path) 418*33f37583SAndroid Build Coastguard Worker self.assertIn("Given image does not look like a vbmeta image", str(error.exception)) 419*33f37583SAndroid Build Coastguard Worker signed_payload = self._sign_payload(container_files, unsigned_payload_only_file_path) 420*33f37583SAndroid Build Coastguard Worker self.assertEqual(get_sha1sum(signed_payload), 421*33f37583SAndroid Build Coastguard Worker get_sha1sum(container_files["apex_payload"])) 422*33f37583SAndroid Build Coastguard Worker 423*33f37583SAndroid Build Coastguard Worker def test_apex_with_logging_parent(self): 424*33f37583SAndroid Build Coastguard Worker self._run_build_test(TEST_APEX_WITH_LOGGING_PARENT) 425*33f37583SAndroid Build Coastguard Worker 426*33f37583SAndroid Build Coastguard Worker def test_apex_with_overridden_package_name(self): 427*33f37583SAndroid Build Coastguard Worker self._run_build_test(TEST_APEX_WITH_OVERRIDDEN_PACKAGE_NAME) 428*33f37583SAndroid Build Coastguard Worker 429*33f37583SAndroid Build Coastguard Worker def test_conv_apex_manifest(self): 430*33f37583SAndroid Build Coastguard Worker # .pb generation from json 431*33f37583SAndroid Build Coastguard Worker manifest_json_path = self._extract_resource(TEST_MANIFEST_JSON) 432*33f37583SAndroid Build Coastguard Worker 433*33f37583SAndroid Build Coastguard Worker fd, fn = tempfile.mkstemp(prefix=self._testMethodName + "_manifest_", suffix=".pb") 434*33f37583SAndroid Build Coastguard Worker os.close(fd) 435*33f37583SAndroid Build Coastguard Worker self._to_cleanup.append(fn) 436*33f37583SAndroid Build Coastguard Worker cmd = [ 437*33f37583SAndroid Build Coastguard Worker "conv_apex_manifest", 438*33f37583SAndroid Build Coastguard Worker "proto", 439*33f37583SAndroid Build Coastguard Worker manifest_json_path, 440*33f37583SAndroid Build Coastguard Worker "-o", fn] 441*33f37583SAndroid Build Coastguard Worker run_and_check_output(cmd) 442*33f37583SAndroid Build Coastguard Worker 443*33f37583SAndroid Build Coastguard Worker with open(manifest_json_path) as fd_json: 444*33f37583SAndroid Build Coastguard Worker manifest_json = json.load(fd_json) 445*33f37583SAndroid Build Coastguard Worker manifest_apex = ParseApexManifest(fn) 446*33f37583SAndroid Build Coastguard Worker ValidateApexManifest(manifest_apex) 447*33f37583SAndroid Build Coastguard Worker 448*33f37583SAndroid Build Coastguard Worker self.assertEqual(manifest_apex.name, manifest_json["name"]) 449*33f37583SAndroid Build Coastguard Worker self.assertEqual(manifest_apex.version, manifest_json["version"]) 450*33f37583SAndroid Build Coastguard Worker 451*33f37583SAndroid Build Coastguard Worker # setprop check on already generated .pb 452*33f37583SAndroid Build Coastguard Worker next_version = 20 453*33f37583SAndroid Build Coastguard Worker cmd = [ 454*33f37583SAndroid Build Coastguard Worker "conv_apex_manifest", 455*33f37583SAndroid Build Coastguard Worker "setprop", 456*33f37583SAndroid Build Coastguard Worker "version", str(next_version), 457*33f37583SAndroid Build Coastguard Worker fn] 458*33f37583SAndroid Build Coastguard Worker run_and_check_output(cmd) 459*33f37583SAndroid Build Coastguard Worker 460*33f37583SAndroid Build Coastguard Worker manifest_apex = ParseApexManifest(fn) 461*33f37583SAndroid Build Coastguard Worker ValidateApexManifest(manifest_apex) 462*33f37583SAndroid Build Coastguard Worker 463*33f37583SAndroid Build Coastguard Worker self.assertEqual(manifest_apex.version, next_version) 464*33f37583SAndroid Build Coastguard Worker 465*33f37583SAndroid Build Coastguard Worker 466*33f37583SAndroid Build Coastguard Worker 467*33f37583SAndroid Build Coastguard Workerif __name__ == '__main__': 468*33f37583SAndroid Build Coastguard Worker unittest.main(verbosity=2) 469