Android.mk0100644 0000000 0000000 00000003425 13243353142 011522 0ustar000000000 0000000 # # Copyright (C) 2016 The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # LOCAL_PATH := $(call my-dir) # apksig library, for signing APKs and verifying signatures of APKs # ============================================================ include $(CLEAR_VARS) LOCAL_MODULE := apksig LOCAL_SRC_FILES := $(call all-java-files-under, src/main/java) include $(BUILD_HOST_JAVA_LIBRARY) # apksigner command-line tool for signing APKs and verifying their signatures # ============================================================ include $(CLEAR_VARS) LOCAL_MODULE := apksigner LOCAL_SRC_FILES := $(call all-java-files-under, src/apksigner/java) LOCAL_JAVA_RESOURCE_DIRS = src/apksigner/java LOCAL_JAR_MANIFEST := src/apksigner/apksigner.mf LOCAL_STATIC_JAVA_LIBRARIES := apksig # Output the apksigner.jar library include $(BUILD_HOST_JAVA_LIBRARY) # Output the shell script wrapper around the library include $(CLEAR_VARS) LOCAL_IS_HOST_MODULE := true LOCAL_MODULE_CLASS := EXECUTABLES LOCAL_MODULE := apksigner include $(BUILD_SYSTEM)/base_rules.mk $(LOCAL_BUILT_MODULE): $(HOST_OUT_JAVA_LIBRARIES)/apksigner$(COMMON_JAVA_PACKAGE_SUFFIX) $(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/etc/apksigner | $(ACP) @echo "Copy: $(PRIVATE_MODULE) ($@)" $(copy-file-to-new-target) $(hide) chmod 755 $@ BUILD0100644 0000000 0000000 00000001453 13243353142 010372 0ustar000000000 0000000 # Bazel (https://bazel.io/) BUILD file for apksig library and apksigner tool. licenses(["notice"]) # Apache License 2.0 java_library( name = "apksig", srcs = glob([ "src/main/java/**/*.java", ]), visibility = ["//visibility:public"], ) java_binary( name = "apksigner", srcs = glob([ "src/apksigner/java/**/*.java", ]), main_class = "com.android.apksigner.ApkSignerTool", resources = glob([ "src/apksigner/java/**/*.txt", ]), visibility = ["//visibility:public"], deps = [":apksig"], ) java_test( name = "all", srcs = glob([ "src/test/java/com/android/apksig/**/*.java", ]), resources = glob([ "src/test/resources/**/*", ]), test_class = "com.android.apksig.AllTests", deps = [":apksig"], ) LICENSE0100644 0000000 0000000 00000027141 13243353142 010617 0ustar000000000 0000000 Copyright (c) 2016, The Android Open Source Project Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. OWNERS0100644 0000000 0000000 00000000050 13243353142 010540 0ustar000000000 0000000 cbrubaker@google.com klyubin@google.com README.md0100644 0000000 0000000 00000005144 13243353142 011070 0ustar000000000 0000000 # apksig apksig is a project which aims to simplify APK signing and checking whether APK's signatures should verify on Android. apksig supports [JAR signing](https://docs.oracle.com/javase/8/docs/technotes/guides/jar/jar.html#Signed_JAR_File) (used by Android since day one) and [APK Signature Scheme v2](https://source.android.com/security/apksigning/v2.html) (supported since Android Nougat, API Level 24). The key feature of apksig is that it knows about differences in APK signature verification logic between different versions of the Android platform. apksig can thus check whether a signed APK is expected to verify on all Android platform versions supported by the APK. When signing an APK, apksig will choose the most appropriate cryptographic algorithms based on the Android platform versions supported by the APK being signed. The project consists of two subprojects: * apksig -- a pure Java library, and * apksigner -- a pure Java command-line tool based on the apksig library. ## apksig library apksig library offers three primitives: * `ApkSigner` which signs the provided APK so that it verifies on all Android platform versions supported by the APK. The range of platform versions can be customized if necessary. * `ApkVerifier` which checks whether the provided APK is expected to verify on all Android platform versions supported by the APK. The range of platform versions can be customized if necessary. * `(Default)ApkSignerEngine` which abstracts away signing an APK from parsing and building an APK file. This is useful in optimized APK building pipelines, such as in Android Plugin for Gradle, which need to perform signing while building an APK, instead of after. For simpler use cases where the APK to be signed is available upfront, the `ApkSigner` above is easier to use. _NOTE: Some public classes of the library are in packages having the word "internal" in their name. These are not public API of the library. Do not use \*.internal.\* classes directly._ ## apksigner command-line tool apksigner command-line tool offers two operations: * sign the provided APK so that it verifies on all Android platforms supported by the APK. Run `apksigner sign` for usage information. * check whether the provided APK's signatures are expected to verify on all Android platforms supported by the APK. Run `apksigner verify` for usage information. The tool determines the range of Android platform versions (API Levels) supported by the APK by inspecting the APK's AndroidManifest.xml. This behavior can be overridden by specifying the range of platform versions on the command-line. android_plugin_for_gradle.gradle0100644 0000000 0000000 00000001107 13243353142 016146 0ustar000000000 0000000 // Gradle project used when building the Android Plugin for Gradle apply from: "$rootDir/buildSrc/base/baseJava.gradle" dependencies { testCompile libs.junit } group = "com.android.tools.build" archivesBaseName = 'apksig' version = rootProject.ext.buildVersion project.ext.pomName = 'Android Tools apksig library' project.ext.pomDesc = 'Library for signing APKs and for checking that APK signatures verify on Android' apply from: "$rootDir/buildSrc/base/publish.gradle" apply from: "$rootDir/buildSrc/base/bintray.gradle" apply from: "$rootDir/buildSrc/base/javadoc.gradle" apksig.iml0100644 0000000 0000000 00000000661 13243353142 011571 0ustar000000000 0000000 build.gradle0100644 0000000 0000000 00000000242 13243353142 012062 0ustar000000000 0000000 // Generic Gradle project apply plugin: 'java' sourceCompatibility = '1.8' repositories { jcenter() } dependencies { testCompile 'junit:junit:4.12' } etc/0040755 0000000 0000000 00000000000 13243353142 010363 5ustar000000000 0000000 etc/apksigner0100755 0000000 0000000 00000005064 13243353142 012276 0ustar000000000 0000000 #!/bin/bash # # Copyright (C) 2016 The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Set up prog to be the path of this script, including following symlinks, # and set up progdir to be the fully-qualified pathname of its directory. prog="$0" while [ -h "${prog}" ]; do newProg=`/bin/ls -ld "${prog}"` newProg=`expr "${newProg}" : ".* -> \(.*\)$"` if expr "x${newProg}" : 'x/' >/dev/null; then prog="${newProg}" else progdir=`dirname "${prog}"` prog="${progdir}/${newProg}" fi done oldwd=`pwd` progdir=`dirname "${prog}"` cd "${progdir}" progdir=`pwd` prog="${progdir}"/`basename "${prog}"` cd "${oldwd}" jarfile=apksigner.jar libdir="$progdir" if [ ! -r "$libdir/$jarfile" ]; then # set apksigner.jar location for the SDK case libdir="$libdir/lib" fi if [ ! -r "$libdir/$jarfile" ]; then # set apksigner.jar location for the Android tree case libdir=`dirname "$progdir"`/framework fi if [ ! -r "$libdir/$jarfile" ]; then echo `basename "$prog"`": can't find $jarfile" exit 1 fi # By default, give apksigner a max heap size of 1 gig. This can be overridden # by using a "-J" option (see below). defaultMx="-Xmx1024M" # The following will extract any initial parameters of the form # "-J" from the command line and pass them to the Java # invocation (instead of to apksigner). This makes it possible for you to add # a command-line parameter such as "-JXmx256M" in your scripts, for # example. "java" (with no args) and "java -X" give a summary of # available options. javaOpts="" while expr "x$1" : 'x-J' >/dev/null; do opt=`expr "x$1" : 'x-J\(.*\)'` javaOpts="${javaOpts} -${opt}" if expr "x${opt}" : "xXmx[0-9]" >/dev/null; then defaultMx="no" fi shift done if [ "${defaultMx}" != "no" ]; then javaOpts="${javaOpts} ${defaultMx}" fi if [ "$OSTYPE" = "cygwin" ]; then # For Cygwin, convert the jarfile path into native Windows style. jarpath=`cygpath -w "$libdir/$jarfile"` else jarpath="$libdir/$jarfile" fi exec java $javaOpts -jar "$jarpath" "$@" etc/apksigner.bat0100755 0000000 0000000 00000005373 13243353142 013046 0ustar000000000 0000000 @echo off REM Copyright (C) 2016 The Android Open Source Project REM REM Licensed under the Apache License, Version 2.0 (the "License"); REM you may not use this file except in compliance with the License. REM You may obtain a copy of the License at REM REM http://www.apache.org/licenses/LICENSE-2.0 REM REM Unless required by applicable law or agreed to in writing, software REM distributed under the License is distributed on an "AS IS" BASIS, REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. REM See the License for the specific language governing permissions and REM limitations under the License. REM don't modify the caller's environment setlocal REM Locate apksigner.jar in the directory where apksigner.bat was found and start it. REM Set up prog to be the path of this script, including following symlinks, REM and set up progdir to be the fully-qualified pathname of its directory. set prog=%~f0 rem Check we have a valid Java.exe in the path. set java_exe= if exist "%~dp0..\tools\lib\find_java.bat" call "%~dp0..\tools\lib\find_java.bat" if exist "%~dp0..\..\tools\lib\find_java.bat" call "%~dp0..\..\tools\lib\find_java.bat" if not defined java_exe goto :EOF set jarfile=apksigner.jar set "frameworkdir=%~dp0" rem frameworkdir must not end with a dir sep. set "frameworkdir=%frameworkdir:~0,-1%" if exist "%frameworkdir%\%jarfile%" goto JarFileOk set "frameworkdir=%~dp0lib" if exist "%frameworkdir%\%jarfile%" goto JarFileOk set "frameworkdir=%~dp0..\framework" :JarFileOk set "jarpath=%frameworkdir%\%jarfile%" set javaOpts= set args= REM By default, give apksigner a max heap size of 1 gig and a stack size of 1meg. rem This can be overridden by using "-JXmx..." and "-JXss..." options below. set defaultXmx=-Xmx1024M set defaultXss=-Xss1m REM Capture all arguments that are not -J options. REM Note that when reading the input arguments with %1, the cmd.exe REM automagically converts --name=value arguments into 2 arguments "--name" REM followed by "value". apksigner has been changed to know how to deal with that. set params= :firstArg if [%1]==[] goto endArgs set "a=%~1" if [%defaultXmx%]==[] goto notXmx if "%a:~0,5%" NEQ "-JXmx" goto notXmx set defaultXmx= :notXmx if [%defaultXss%]==[] goto notXss if "%a:~0,5%" NEQ "-JXss" goto notXss set defaultXss= :notXss if "%a:~0,2%" NEQ "-J" goto notJ set javaOpts=%javaOpts% -%a:~2% shift /1 goto firstArg :notJ set params=%params% %1 shift /1 goto firstArg :endArgs set javaOpts=%javaOpts% %defaultXmx% %defaultXss% call "%java_exe%" %javaOpts% -Djava.ext.dirs="%frameworkdir%" -jar "%jarpath%" %params% src/0040755 0000000 0000000 00000000000 13243353142 010377 5ustar000000000 0000000 src/apksigner/0040755 0000000 0000000 00000000000 13243353142 012362 5ustar000000000 0000000 src/apksigner/apksigner.mf0100644 0000000 0000000 00000000060 13243353142 014662 0ustar000000000 0000000 Main-Class: com.android.apksigner.ApkSignerTool src/apksigner/java/0040755 0000000 0000000 00000000000 13243353142 013303 5ustar000000000 0000000 src/apksigner/java/com/0040755 0000000 0000000 00000000000 13243353142 014061 5ustar000000000 0000000 src/apksigner/java/com/android/0040755 0000000 0000000 00000000000 13243353142 015501 5ustar000000000 0000000 src/apksigner/java/com/android/apksigner/0040755 0000000 0000000 00000000000 13243353142 017464 5ustar000000000 0000000 src/apksigner/java/com/android/apksigner/ApkSignerTool.java0100644 0000000 0000000 00000131307 13243353142 023052 0ustar000000000 0000000 /* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksigner; import com.android.apksig.ApkSigner; import com.android.apksig.ApkVerifier; import com.android.apksig.apk.MinSdkVersionException; import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.PrintStream; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.StandardCopyOption; import java.security.InvalidKeyException; import java.security.Key; import java.security.KeyFactory; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.Provider; import java.security.PublicKey; import java.security.Security; import java.security.UnrecoverableKeyException; import java.security.cert.Certificate; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.security.interfaces.DSAKey; import java.security.interfaces.DSAParams; import java.security.interfaces.ECKey; import java.security.interfaces.RSAKey; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Enumeration; import java.util.List; import javax.crypto.EncryptedPrivateKeyInfo; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.PBEKeySpec; /** * Command-line tool for signing APKs and for checking whether an APK's signature are expected to * verify on Android devices. */ public class ApkSignerTool { private static final String VERSION = "0.8"; private static final String HELP_PAGE_GENERAL = "help.txt"; private static final String HELP_PAGE_SIGN = "help_sign.txt"; private static final String HELP_PAGE_VERIFY = "help_verify.txt"; public static void main(String[] params) throws Exception { if ((params.length == 0) || ("--help".equals(params[0])) || ("-h".equals(params[0]))) { printUsage(HELP_PAGE_GENERAL); return; } else if ("--version".equals(params[0])) { System.out.println(VERSION); return; } String cmd = params[0]; try { if ("sign".equals(cmd)) { sign(Arrays.copyOfRange(params, 1, params.length)); return; } else if ("verify".equals(cmd)) { verify(Arrays.copyOfRange(params, 1, params.length)); return; } else if ("help".equals(cmd)) { printUsage(HELP_PAGE_GENERAL); return; } else if ("version".equals(cmd)) { System.out.println(VERSION); return; } else { throw new ParameterException( "Unsupported command: " + cmd + ". See --help for supported commands"); } } catch (ParameterException | OptionsParser.OptionsException e) { System.err.println(e.getMessage()); System.exit(1); return; } } private static void sign(String[] params) throws Exception { if (params.length == 0) { printUsage(HELP_PAGE_SIGN); return; } File outputApk = null; File inputApk = null; boolean verbose = false; boolean v1SigningEnabled = true; boolean v2SigningEnabled = true; int minSdkVersion = 1; boolean minSdkVersionSpecified = false; int maxSdkVersion = Integer.MAX_VALUE; List signers = new ArrayList<>(1); SignerParams signerParams = new SignerParams(); List providers = new ArrayList<>(); ProviderInstallSpec providerParams = new ProviderInstallSpec(); OptionsParser optionsParser = new OptionsParser(params); String optionName; String optionOriginalForm = null; while ((optionName = optionsParser.nextOption()) != null) { optionOriginalForm = optionsParser.getOptionOriginalForm(); if (("help".equals(optionName)) || ("h".equals(optionName))) { printUsage(HELP_PAGE_SIGN); return; } else if ("out".equals(optionName)) { outputApk = new File(optionsParser.getRequiredValue("Output file name")); } else if ("in".equals(optionName)) { inputApk = new File(optionsParser.getRequiredValue("Input file name")); } else if ("min-sdk-version".equals(optionName)) { minSdkVersion = optionsParser.getRequiredIntValue("Mininimum API Level"); minSdkVersionSpecified = true; } else if ("max-sdk-version".equals(optionName)) { maxSdkVersion = optionsParser.getRequiredIntValue("Maximum API Level"); } else if ("v1-signing-enabled".equals(optionName)) { v1SigningEnabled = optionsParser.getOptionalBooleanValue(true); } else if ("v2-signing-enabled".equals(optionName)) { v2SigningEnabled = optionsParser.getOptionalBooleanValue(true); } else if ("next-signer".equals(optionName)) { if (!signerParams.isEmpty()) { signers.add(signerParams); signerParams = new SignerParams(); } } else if ("ks".equals(optionName)) { signerParams.keystoreFile = optionsParser.getRequiredValue("KeyStore file"); } else if ("ks-key-alias".equals(optionName)) { signerParams.keystoreKeyAlias = optionsParser.getRequiredValue("KeyStore key alias"); } else if ("ks-pass".equals(optionName)) { signerParams.keystorePasswordSpec = optionsParser.getRequiredValue("KeyStore password"); } else if ("key-pass".equals(optionName)) { signerParams.keyPasswordSpec = optionsParser.getRequiredValue("Key password"); } else if ("pass-encoding".equals(optionName)) { String charsetName = optionsParser.getRequiredValue("Password character encoding"); try { signerParams.passwordCharset = PasswordRetriever.getCharsetByName(charsetName); } catch (IllegalArgumentException e) { throw new ParameterException( "Unsupported password character encoding requested using" + " --pass-encoding: " + charsetName); } } else if ("v1-signer-name".equals(optionName)) { signerParams.v1SigFileBasename = optionsParser.getRequiredValue("JAR signature file basename"); } else if ("ks-type".equals(optionName)) { signerParams.keystoreType = optionsParser.getRequiredValue("KeyStore type"); } else if ("ks-provider-name".equals(optionName)) { signerParams.keystoreProviderName = optionsParser.getRequiredValue("JCA KeyStore Provider name"); } else if ("ks-provider-class".equals(optionName)) { signerParams.keystoreProviderClass = optionsParser.getRequiredValue("JCA KeyStore Provider class name"); } else if ("ks-provider-arg".equals(optionName)) { signerParams.keystoreProviderArg = optionsParser.getRequiredValue( "JCA KeyStore Provider constructor argument"); } else if ("key".equals(optionName)) { signerParams.keyFile = optionsParser.getRequiredValue("Private key file"); } else if ("cert".equals(optionName)) { signerParams.certFile = optionsParser.getRequiredValue("Certificate file"); } else if (("v".equals(optionName)) || ("verbose".equals(optionName))) { verbose = optionsParser.getOptionalBooleanValue(true); } else if ("next-provider".equals(optionName)) { if (!providerParams.isEmpty()) { providers.add(providerParams); providerParams = new ProviderInstallSpec(); } } else if ("provider-class".equals(optionName)) { providerParams.className = optionsParser.getRequiredValue("JCA Provider class name"); } else if ("provider-arg".equals(optionName)) { providerParams.constructorParam = optionsParser.getRequiredValue("JCA Provider constructor argument"); } else if ("provider-pos".equals(optionName)) { providerParams.position = optionsParser.getRequiredIntValue("JCA Provider position"); } else { throw new ParameterException( "Unsupported option: " + optionOriginalForm + ". See --help for supported" + " options."); } } if (!signerParams.isEmpty()) { signers.add(signerParams); } signerParams = null; if (!providerParams.isEmpty()) { providers.add(providerParams); } providerParams = null; if (signers.isEmpty()) { throw new ParameterException("At least one signer must be specified"); } params = optionsParser.getRemainingParams(); if (inputApk != null) { // Input APK has been specified via preceding parameters. We don't expect any more // parameters. if (params.length > 0) { throw new ParameterException( "Unexpected parameter(s) after " + optionOriginalForm + ": " + params[0]); } } else { // Input APK has not been specified via preceding parameters. The next parameter is // supposed to be the path to input APK. if (params.length < 1) { throw new ParameterException("Missing input APK"); } else if (params.length > 1) { throw new ParameterException( "Unexpected parameter(s) after input APK (" + params[1] + ")"); } inputApk = new File(params[0]); } if ((minSdkVersionSpecified) && (minSdkVersion > maxSdkVersion)) { throw new ParameterException( "Min API Level (" + minSdkVersion + ") > max API Level (" + maxSdkVersion + ")"); } // Install additional JCA Providers for (ProviderInstallSpec providerInstallSpec : providers) { providerInstallSpec.installProvider(); } List signerConfigs = new ArrayList<>(signers.size()); int signerNumber = 0; try (PasswordRetriever passwordRetriever = new PasswordRetriever()) { for (SignerParams signer : signers) { signerNumber++; signer.name = "signer #" + signerNumber; try { signer.loadPrivateKeyAndCerts(passwordRetriever); } catch (ParameterException e) { System.err.println( "Failed to load signer \"" + signer.name + "\": " + e.getMessage()); System.exit(2); return; } catch (Exception e) { System.err.println("Failed to load signer \"" + signer.name + "\""); e.printStackTrace(); System.exit(2); return; } String v1SigBasename; if (signer.v1SigFileBasename != null) { v1SigBasename = signer.v1SigFileBasename; } else if (signer.keystoreKeyAlias != null) { v1SigBasename = signer.keystoreKeyAlias; } else if (signer.keyFile != null) { String keyFileName = new File(signer.keyFile).getName(); int delimiterIndex = keyFileName.indexOf('.'); if (delimiterIndex == -1) { v1SigBasename = keyFileName; } else { v1SigBasename = keyFileName.substring(0, delimiterIndex); } } else { throw new RuntimeException( "Neither KeyStore key alias nor private key file available"); } ApkSigner.SignerConfig signerConfig = new ApkSigner.SignerConfig.Builder( v1SigBasename, signer.privateKey, signer.certs) .build(); signerConfigs.add(signerConfig); } } if (outputApk == null) { outputApk = inputApk; } File tmpOutputApk; if (inputApk.getCanonicalPath().equals(outputApk.getCanonicalPath())) { tmpOutputApk = File.createTempFile("apksigner", ".apk"); tmpOutputApk.deleteOnExit(); } else { tmpOutputApk = outputApk; } ApkSigner.Builder apkSignerBuilder = new ApkSigner.Builder(signerConfigs) .setInputApk(inputApk) .setOutputApk(tmpOutputApk) .setOtherSignersSignaturesPreserved(false) .setV1SigningEnabled(v1SigningEnabled) .setV2SigningEnabled(v2SigningEnabled); if (minSdkVersionSpecified) { apkSignerBuilder.setMinSdkVersion(minSdkVersion); } ApkSigner apkSigner = apkSignerBuilder.build(); try { apkSigner.sign(); } catch (MinSdkVersionException e) { String msg = e.getMessage(); if (!msg.endsWith(".")) { msg += '.'; } throw new MinSdkVersionException( "Failed to determine APK's minimum supported platform version" + ". Use --min-sdk-version to override", e); } if (!tmpOutputApk.getCanonicalPath().equals(outputApk.getCanonicalPath())) { Files.move( tmpOutputApk.toPath(), outputApk.toPath(), StandardCopyOption.REPLACE_EXISTING); } if (verbose) { System.out.println("Signed"); } } private static void verify(String[] params) throws Exception { if (params.length == 0) { printUsage(HELP_PAGE_VERIFY); return; } File inputApk = null; int minSdkVersion = 1; boolean minSdkVersionSpecified = false; int maxSdkVersion = Integer.MAX_VALUE; boolean maxSdkVersionSpecified = false; boolean printCerts = false; boolean verbose = false; boolean warningsTreatedAsErrors = false; OptionsParser optionsParser = new OptionsParser(params); String optionName; String optionOriginalForm = null; while ((optionName = optionsParser.nextOption()) != null) { optionOriginalForm = optionsParser.getOptionOriginalForm(); if ("min-sdk-version".equals(optionName)) { minSdkVersion = optionsParser.getRequiredIntValue("Mininimum API Level"); minSdkVersionSpecified = true; } else if ("max-sdk-version".equals(optionName)) { maxSdkVersion = optionsParser.getRequiredIntValue("Maximum API Level"); maxSdkVersionSpecified = true; } else if ("print-certs".equals(optionName)) { printCerts = optionsParser.getOptionalBooleanValue(true); } else if (("v".equals(optionName)) || ("verbose".equals(optionName))) { verbose = optionsParser.getOptionalBooleanValue(true); } else if ("Werr".equals(optionName)) { warningsTreatedAsErrors = optionsParser.getOptionalBooleanValue(true); } else if (("help".equals(optionName)) || ("h".equals(optionName))) { printUsage(HELP_PAGE_VERIFY); return; } else if ("in".equals(optionName)) { inputApk = new File(optionsParser.getRequiredValue("Input APK file")); } else { throw new ParameterException( "Unsupported option: " + optionOriginalForm + ". See --help for supported" + " options."); } } params = optionsParser.getRemainingParams(); if (inputApk != null) { // Input APK has been specified in preceding parameters. We don't expect any more // parameters. if (params.length > 0) { throw new ParameterException( "Unexpected parameter(s) after " + optionOriginalForm + ": " + params[0]); } } else { // Input APK has not been specified in preceding parameters. The next parameter is // supposed to be the input APK. if (params.length < 1) { throw new ParameterException("Missing APK"); } else if (params.length > 1) { throw new ParameterException( "Unexpected parameter(s) after APK (" + params[1] + ")"); } inputApk = new File(params[0]); } if ((minSdkVersionSpecified) && (maxSdkVersionSpecified) && (minSdkVersion > maxSdkVersion)) { throw new ParameterException( "Min API Level (" + minSdkVersion + ") > max API Level (" + maxSdkVersion + ")"); } ApkVerifier.Builder apkVerifierBuilder = new ApkVerifier.Builder(inputApk); if (minSdkVersionSpecified) { apkVerifierBuilder.setMinCheckedPlatformVersion(minSdkVersion); } if (maxSdkVersionSpecified) { apkVerifierBuilder.setMaxCheckedPlatformVersion(maxSdkVersion); } ApkVerifier apkVerifier = apkVerifierBuilder.build(); ApkVerifier.Result result; try { result = apkVerifier.verify(); } catch (MinSdkVersionException e) { String msg = e.getMessage(); if (!msg.endsWith(".")) { msg += '.'; } throw new MinSdkVersionException( "Failed to determine APK's minimum supported platform version" + ". Use --min-sdk-version to override", e); } boolean verified = result.isVerified(); boolean warningsEncountered = false; if (verified) { List signerCerts = result.getSignerCertificates(); if (verbose) { System.out.println("Verifies"); System.out.println( "Verified using v1 scheme (JAR signing): " + result.isVerifiedUsingV1Scheme()); System.out.println( "Verified using v2 scheme (APK Signature Scheme v2): " + result.isVerifiedUsingV2Scheme()); System.out.println("Number of signers: " + signerCerts.size()); } if (printCerts) { int signerNumber = 0; MessageDigest sha256 = MessageDigest.getInstance("SHA-256"); MessageDigest sha1 = MessageDigest.getInstance("SHA-1"); MessageDigest md5 = MessageDigest.getInstance("MD5"); for (X509Certificate signerCert : signerCerts) { signerNumber++; System.out.println( "Signer #" + signerNumber + " certificate DN" + ": " + signerCert.getSubjectDN()); byte[] encodedCert = signerCert.getEncoded(); System.out.println( "Signer #" + signerNumber + " certificate SHA-256 digest: " + HexEncoding.encode(sha256.digest(encodedCert))); System.out.println( "Signer #" + signerNumber + " certificate SHA-1 digest: " + HexEncoding.encode(sha1.digest(encodedCert))); System.out.println( "Signer #" + signerNumber + " certificate MD5 digest: " + HexEncoding.encode(md5.digest(encodedCert))); if (verbose) { PublicKey publicKey = signerCert.getPublicKey(); System.out.println( "Signer #" + signerNumber + " key algorithm: " + publicKey.getAlgorithm()); int keySize = -1; if (publicKey instanceof RSAKey) { keySize = ((RSAKey) publicKey).getModulus().bitLength(); } else if (publicKey instanceof ECKey) { keySize = ((ECKey) publicKey).getParams() .getOrder().bitLength(); } else if (publicKey instanceof DSAKey) { // DSA parameters may be inherited from the certificate. We // don't handle this case at the moment. DSAParams dsaParams = ((DSAKey) publicKey).getParams(); if (dsaParams != null) { keySize = dsaParams.getP().bitLength(); } } System.out.println( "Signer #" + signerNumber + " key size (bits): " + ((keySize != -1) ? String.valueOf(keySize) : "n/a")); byte[] encodedKey = publicKey.getEncoded(); System.out.println( "Signer #" + signerNumber + " public key SHA-256 digest: " + HexEncoding.encode(sha256.digest(encodedKey))); System.out.println( "Signer #" + signerNumber + " public key SHA-1 digest: " + HexEncoding.encode(sha1.digest(encodedKey))); System.out.println( "Signer #" + signerNumber + " public key MD5 digest: " + HexEncoding.encode(md5.digest(encodedKey))); } } } } else { System.err.println("DOES NOT VERIFY"); } for (ApkVerifier.IssueWithParams error : result.getErrors()) { System.err.println("ERROR: " + error); } @SuppressWarnings("resource") // false positive -- this resource is not opened here PrintStream warningsOut = (warningsTreatedAsErrors) ? System.err : System.out; for (ApkVerifier.IssueWithParams warning : result.getWarnings()) { warningsEncountered = true; warningsOut.println("WARNING: " + warning); } for (ApkVerifier.Result.V1SchemeSignerInfo signer : result.getV1SchemeSigners()) { String signerName = signer.getName(); for (ApkVerifier.IssueWithParams error : signer.getErrors()) { System.err.println("ERROR: JAR signer " + signerName + ": " + error); } for (ApkVerifier.IssueWithParams warning : signer.getWarnings()) { warningsEncountered = true; warningsOut.println("WARNING: JAR signer " + signerName + ": " + warning); } } for (ApkVerifier.Result.V2SchemeSignerInfo signer : result.getV2SchemeSigners()) { String signerName = "signer #" + (signer.getIndex() + 1); for (ApkVerifier.IssueWithParams error : signer.getErrors()) { System.err.println( "ERROR: APK Signature Scheme v2 " + signerName + ": " + error); } for (ApkVerifier.IssueWithParams warning : signer.getWarnings()) { warningsEncountered = true; warningsOut.println( "WARNING: APK Signature Scheme v2 " + signerName + ": " + warning); } } if (!verified) { System.exit(1); return; } if ((warningsTreatedAsErrors) && (warningsEncountered)) { System.exit(1); return; } } private static void printUsage(String page) { try (BufferedReader in = new BufferedReader( new InputStreamReader( ApkSignerTool.class.getResourceAsStream(page), StandardCharsets.UTF_8))) { String line; while ((line = in.readLine()) != null) { System.out.println(line); } } catch (IOException e) { throw new RuntimeException("Failed to read " + page + " resource"); } } private static class ProviderInstallSpec { String className; String constructorParam; Integer position; private boolean isEmpty() { return (className == null) && (constructorParam == null) && (position == null); } private void installProvider() throws Exception { if (className == null) { throw new ParameterException( "JCA Provider class name (--provider-class) must be specified"); } Class providerClass = Class.forName(className); if (!Provider.class.isAssignableFrom(providerClass)) { throw new ParameterException( "JCA Provider class " + providerClass + " not subclass of " + Provider.class.getName()); } Provider provider; if (constructorParam != null) { // Single-arg Provider constructor provider = (Provider) providerClass.getConstructor(String.class) .newInstance(constructorParam); } else { // No-arg Provider constructor provider = (Provider) providerClass.getConstructor().newInstance(); } if (position == null) { Security.addProvider(provider); } else { Security.insertProviderAt(provider, position); } } } private static class SignerParams { String name; String keystoreFile; String keystoreKeyAlias; String keystorePasswordSpec; String keyPasswordSpec; Charset passwordCharset; String keystoreType; String keystoreProviderName; String keystoreProviderClass; String keystoreProviderArg; String keyFile; String certFile; String v1SigFileBasename; PrivateKey privateKey; List certs; private boolean isEmpty() { return (name == null) && (keystoreFile == null) && (keystoreKeyAlias == null) && (keystorePasswordSpec == null) && (keyPasswordSpec == null) && (passwordCharset == null) && (keystoreType == null) && (keystoreProviderName == null) && (keystoreProviderClass == null) && (keystoreProviderArg == null) && (keyFile == null) && (certFile == null) && (v1SigFileBasename == null) && (privateKey == null) && (certs == null); } private void loadPrivateKeyAndCerts(PasswordRetriever passwordRetriever) throws Exception { if (keystoreFile != null) { if (keyFile != null) { throw new ParameterException( "--ks and --key may not be specified at the same time"); } else if (certFile != null) { throw new ParameterException( "--ks and --cert may not be specified at the same time"); } loadPrivateKeyAndCertsFromKeyStore(passwordRetriever); } else if (keyFile != null) { loadPrivateKeyAndCertsFromFiles(passwordRetriever); } else { throw new ParameterException( "KeyStore (--ks) or private key file (--key) must be specified"); } } private void loadPrivateKeyAndCertsFromKeyStore(PasswordRetriever passwordRetriever) throws Exception { if (keystoreFile == null) { throw new ParameterException("KeyStore (--ks) must be specified"); } // 1. Obtain a KeyStore implementation String ksType = (keystoreType != null) ? keystoreType : KeyStore.getDefaultType(); KeyStore ks; if (keystoreProviderName != null) { // Use a named Provider (assumes the provider is already installed) ks = KeyStore.getInstance(ksType, keystoreProviderName); } else if (keystoreProviderClass != null) { // Use a new Provider instance (does not require the provider to be installed) Class ksProviderClass = Class.forName(keystoreProviderClass); if (!Provider.class.isAssignableFrom(ksProviderClass)) { throw new ParameterException( "Keystore Provider class " + keystoreProviderClass + " not subclass of " + Provider.class.getName()); } Provider ksProvider; if (keystoreProviderArg != null) { // Single-arg Provider constructor ksProvider = (Provider) ksProviderClass.getConstructor(String.class) .newInstance(keystoreProviderArg); } else { // No-arg Provider constructor ksProvider = (Provider) ksProviderClass.getConstructor().newInstance(); } ks = KeyStore.getInstance(ksType, ksProvider); } else { // Use the highest-priority Provider which offers the requested KeyStore type ks = KeyStore.getInstance(ksType); } // 2. Load the KeyStore List keystorePasswords; Charset[] additionalPasswordEncodings; { String keystorePasswordSpec = (this.keystorePasswordSpec != null) ? this.keystorePasswordSpec : PasswordRetriever.SPEC_STDIN; additionalPasswordEncodings = (passwordCharset != null) ? new Charset[] {passwordCharset} : new Charset[0]; keystorePasswords = passwordRetriever.getPasswords( keystorePasswordSpec, "Keystore password for " + name, additionalPasswordEncodings); loadKeyStoreFromFile( ks, "NONE".equals(keystoreFile) ? null : keystoreFile, keystorePasswords); } // 3. Load the PrivateKey and cert chain from KeyStore String keyAlias = null; PrivateKey key = null; try { if (keystoreKeyAlias == null) { // Private key entry alias not specified. Find the key entry contained in this // KeyStore. If the KeyStore contains multiple key entries, return an error. Enumeration aliases = ks.aliases(); if (aliases != null) { while (aliases.hasMoreElements()) { String entryAlias = aliases.nextElement(); if (ks.isKeyEntry(entryAlias)) { keyAlias = entryAlias; if (keystoreKeyAlias != null) { throw new ParameterException( keystoreFile + " contains multiple key entries" + ". --ks-key-alias option must be used to specify" + " which entry to use."); } keystoreKeyAlias = keyAlias; } } } if (keystoreKeyAlias == null) { throw new ParameterException( keystoreFile + " does not contain key entries"); } } // Private key entry alias known. Load that entry's private key. keyAlias = keystoreKeyAlias; if (!ks.isKeyEntry(keyAlias)) { throw new ParameterException( keystoreFile + " entry \"" + keyAlias + "\" does not contain a key"); } Key entryKey; if (keyPasswordSpec != null) { // Key password spec is explicitly specified. Use this spec to obtain the // password and then load the key using that password. List keyPasswords = passwordRetriever.getPasswords( keyPasswordSpec, "Key \"" + keyAlias + "\" password for " + name, additionalPasswordEncodings); entryKey = getKeyStoreKey(ks, keyAlias, keyPasswords); } else { // Key password spec is not specified. This means we should assume that key // password is the same as the keystore password and that, if this assumption is // wrong, we should prompt for key password and retry loading the key using that // password. try { entryKey = getKeyStoreKey(ks, keyAlias, keystorePasswords); } catch (UnrecoverableKeyException expected) { List keyPasswords = passwordRetriever.getPasswords( PasswordRetriever.SPEC_STDIN, "Key \"" + keyAlias + "\" password for " + name, additionalPasswordEncodings); entryKey = getKeyStoreKey(ks, keyAlias, keyPasswords); } } if (entryKey == null) { throw new ParameterException( keystoreFile + " entry \"" + keyAlias + "\" does not contain a key"); } else if (!(entryKey instanceof PrivateKey)) { throw new ParameterException( keystoreFile + " entry \"" + keyAlias + "\" does not contain a private" + " key. It contains a key of algorithm: " + entryKey.getAlgorithm()); } key = (PrivateKey) entryKey; } catch (UnrecoverableKeyException e) { throw new IOException( "Failed to obtain key with alias \"" + keyAlias + "\" from " + keystoreFile + ". Wrong password?", e); } this.privateKey = key; Certificate[] certChain = ks.getCertificateChain(keyAlias); if ((certChain == null) || (certChain.length == 0)) { throw new ParameterException( keystoreFile + " entry \"" + keyAlias + "\" does not contain certificates"); } this.certs = new ArrayList<>(certChain.length); for (Certificate cert : certChain) { this.certs.add((X509Certificate) cert); } } /** * Loads the password-protected keystore from storage. * * @param file file backing the keystore or {@code null} if the keystore is not file-backed, * for example, a PKCS #11 KeyStore. */ private static void loadKeyStoreFromFile(KeyStore ks, String file, List passwords) throws Exception { Exception lastFailure = null; for (char[] password : passwords) { try { if (file != null) { try (FileInputStream in = new FileInputStream(file)) { ks.load(in, password); } } else { ks.load(null, password); } return; } catch (Exception e) { lastFailure = e; } } if (lastFailure == null) { throw new RuntimeException("No keystore passwords"); } else { throw lastFailure; } } private static Key getKeyStoreKey(KeyStore ks, String keyAlias, List passwords) throws UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException { UnrecoverableKeyException lastFailure = null; for (char[] password : passwords) { try { return ks.getKey(keyAlias, password); } catch (UnrecoverableKeyException e) { lastFailure = e; } } if (lastFailure == null) { throw new RuntimeException("No key passwords"); } else { throw lastFailure; } } private void loadPrivateKeyAndCertsFromFiles(PasswordRetriever passwordRetriver) throws Exception { if (keyFile == null) { throw new ParameterException("Private key file (--key) must be specified"); } if (certFile == null) { throw new ParameterException("Certificate file (--cert) must be specified"); } byte[] privateKeyBlob = readFully(new File(keyFile)); PKCS8EncodedKeySpec keySpec; // Potentially encrypted key blob try { EncryptedPrivateKeyInfo encryptedPrivateKeyInfo = new EncryptedPrivateKeyInfo(privateKeyBlob); // The blob is indeed an encrypted private key blob String passwordSpec = (keyPasswordSpec != null) ? keyPasswordSpec : PasswordRetriever.SPEC_STDIN; Charset[] additionalPasswordEncodings = (passwordCharset != null) ? new Charset[] {passwordCharset} : new Charset[0]; List keyPasswords = passwordRetriver.getPasswords( passwordSpec, "Private key password for " + name, additionalPasswordEncodings); keySpec = decryptPkcs8EncodedKey(encryptedPrivateKeyInfo, keyPasswords); } catch (IOException e) { // The blob is not an encrypted private key blob if (keyPasswordSpec == null) { // Given that no password was specified, assume the blob is an unencrypted // private key blob keySpec = new PKCS8EncodedKeySpec(privateKeyBlob); } else { throw new InvalidKeySpecException( "Failed to parse encrypted private key blob " + keyFile, e); } } // Load the private key from its PKCS #8 encoded form. try { privateKey = loadPkcs8EncodedPrivateKey(keySpec); } catch (InvalidKeySpecException e) { throw new InvalidKeySpecException( "Failed to load PKCS #8 encoded private key from " + keyFile, e); } // Load certificates Collection certs; try (FileInputStream in = new FileInputStream(certFile)) { certs = CertificateFactory.getInstance("X.509").generateCertificates(in); } List certList = new ArrayList<>(certs.size()); for (Certificate cert : certs) { certList.add((X509Certificate) cert); } this.certs = certList; } private static PKCS8EncodedKeySpec decryptPkcs8EncodedKey( EncryptedPrivateKeyInfo encryptedPrivateKeyInfo, List passwords) throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException { SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(encryptedPrivateKeyInfo.getAlgName()); InvalidKeySpecException lastKeySpecException = null; InvalidKeyException lastKeyException = null; for (char[] password : passwords) { PBEKeySpec decryptionKeySpec = new PBEKeySpec(password); try { SecretKey decryptionKey = keyFactory.generateSecret(decryptionKeySpec); return encryptedPrivateKeyInfo.getKeySpec(decryptionKey); } catch (InvalidKeySpecException e) { lastKeySpecException = e; } catch (InvalidKeyException e) { lastKeyException = e; } } if ((lastKeyException == null) && (lastKeySpecException == null)) { throw new RuntimeException("No passwords"); } else if (lastKeyException != null) { throw lastKeyException; } else { throw lastKeySpecException; } } private static PrivateKey loadPkcs8EncodedPrivateKey(PKCS8EncodedKeySpec spec) throws InvalidKeySpecException, NoSuchAlgorithmException { try { return KeyFactory.getInstance("RSA").generatePrivate(spec); } catch (InvalidKeySpecException expected) { } try { return KeyFactory.getInstance("EC").generatePrivate(spec); } catch (InvalidKeySpecException expected) { } try { return KeyFactory.getInstance("DSA").generatePrivate(spec); } catch (InvalidKeySpecException expected) { } throw new InvalidKeySpecException("Not an RSA, EC, or DSA private key"); } } private static byte[] readFully(File file) throws IOException { ByteArrayOutputStream result = new ByteArrayOutputStream(); try (FileInputStream in = new FileInputStream(file)) { drain(in, result); } return result.toByteArray(); } private static void drain(InputStream in, OutputStream out) throws IOException { byte[] buf = new byte[65536]; int chunkSize; while ((chunkSize = in.read(buf)) != -1) { out.write(buf, 0, chunkSize); } } /** * Indicates that there is an issue with command-line parameters provided to this tool. */ private static class ParameterException extends Exception { private static final long serialVersionUID = 1L; ParameterException(String message) { super(message); } } } src/apksigner/java/com/android/apksigner/HexEncoding.java0100644 0000000 0000000 00000003472 13243353142 022525 0ustar000000000 0000000 /* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksigner; import java.nio.ByteBuffer; /** * Hexadecimal encoding where each byte is represented by two hexadecimal digits. */ class HexEncoding { /** Hidden constructor to prevent instantiation. */ private HexEncoding() {} private static final char[] HEX_DIGITS = "0123456789abcdef".toCharArray(); /** * Encodes the provided data as a hexadecimal string. */ public static String encode(byte[] data, int offset, int length) { StringBuilder result = new StringBuilder(length * 2); for (int i = 0; i < length; i++) { byte b = data[offset + i]; result.append(HEX_DIGITS[(b >>> 4) & 0x0f]); result.append(HEX_DIGITS[b & 0x0f]); } return result.toString(); } /** * Encodes the provided data as a hexadecimal string. */ public static String encode(byte[] data) { return encode(data, 0, data.length); } /** * Encodes the remaining bytes of the provided {@link ByteBuffer} as a hexadecimal string. */ public static String encodeRemaining(ByteBuffer data) { return encode(data.array(), data.arrayOffset() + data.position(), data.remaining()); } } src/apksigner/java/com/android/apksigner/OptionsParser.java0100644 0000000 0000000 00000015310 13243353142 023134 0ustar000000000 0000000 /* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksigner; import java.util.Arrays; /** * Parser of command-line options/switches/flags. * *

Supported option formats: *

    *
  • {@code --name value}
  • *
  • {@code --name=value}
  • *
  • {@code -name value}
  • *
  • {@code --name} (boolean options only)
  • *
* *

To use the parser, create an instance, providing it with the command-line parameters, then * iterate over options by invoking {@link #nextOption()} until it returns {@code null}. */ class OptionsParser { private final String[] mParams; private int mIndex; private String mLastOptionValue; private String mLastOptionOriginalForm; /** * Constructs a new {@code OptionsParser} initialized with the provided command-line. */ public OptionsParser(String[] params) { mParams = params.clone(); } /** * Returns the name (without leading dashes) of the next option (starting with the very first * option) or {@code null} if there are no options left. * *

The value of this option can be obtained via {@link #getRequiredValue(String)}, * {@link #getRequiredIntValue(String)}, and {@link #getOptionalBooleanValue(boolean)}. */ public String nextOption() { if (mIndex >= mParams.length) { // No more parameters left return null; } String param = mParams[mIndex]; if (!param.startsWith("-")) { // Not an option return null; } mIndex++; mLastOptionOriginalForm = param; mLastOptionValue = null; if (param.startsWith("--")) { // FORMAT: --name value OR --name=value if ("--".equals(param)) { // End of options marker return null; } int valueDelimiterIndex = param.indexOf('='); if (valueDelimiterIndex != -1) { mLastOptionValue = param.substring(valueDelimiterIndex + 1); mLastOptionOriginalForm = param.substring(0, valueDelimiterIndex); return param.substring("--".length(), valueDelimiterIndex); } else { return param.substring("--".length()); } } else { // FORMAT: -name value return param.substring("-".length()); } } /** * Returns the original form of the current option. The original form includes the leading dash * or dashes. This is intended to be used for referencing the option in error messages. */ public String getOptionOriginalForm() { return mLastOptionOriginalForm; } /** * Returns the value of the current option, throwing an exception if the value is missing. */ public String getRequiredValue(String valueDescription) throws OptionsException { if (mLastOptionValue != null) { String result = mLastOptionValue; mLastOptionValue = null; return result; } if (mIndex >= mParams.length) { // No more parameters left throw new OptionsException( valueDescription + " missing after " + mLastOptionOriginalForm); } String param = mParams[mIndex]; if ("--".equals(param)) { // End of options marker throw new OptionsException( valueDescription + " missing after " + mLastOptionOriginalForm); } mIndex++; return param; } /** * Returns the value of the current numeric option, throwing an exception if the value is * missing or is not numeric. */ public int getRequiredIntValue(String valueDescription) throws OptionsException { String value = getRequiredValue(valueDescription); try { return Integer.parseInt(value); } catch (NumberFormatException e) { throw new OptionsException( valueDescription + " (" + mLastOptionOriginalForm + ") must be a decimal number: " + value); } } /** * Gets the value of the current boolean option. Boolean options are not required to have * explicitly specified values. */ public boolean getOptionalBooleanValue(boolean defaultValue) throws OptionsException { if (mLastOptionValue != null) { // --option=value form String stringValue = mLastOptionValue; mLastOptionValue = null; if ("true".equals(stringValue)) { return true; } else if ("false".equals(stringValue)) { return false; } throw new OptionsException( "Unsupported value for " + mLastOptionOriginalForm + ": " + stringValue + ". Only true or false supported."); } // --option (true|false) form OR just --option if (mIndex >= mParams.length) { return defaultValue; } String stringValue = mParams[mIndex]; if ("true".equals(stringValue)) { mIndex++; return true; } else if ("false".equals(stringValue)) { mIndex++; return false; } else { return defaultValue; } } /** * Returns the remaining command-line parameters. This is intended to be invoked once * {@link #nextOption()} returns {@code null}. */ public String[] getRemainingParams() { if (mIndex >= mParams.length) { return new String[0]; } String param = mParams[mIndex]; if ("--".equals(param)) { // Skip end of options marker return Arrays.copyOfRange(mParams, mIndex + 1, mParams.length); } else { return Arrays.copyOfRange(mParams, mIndex, mParams.length); } } /** * Indicates that an error was encountered while parsing command-line options. */ public static class OptionsException extends Exception { private static final long serialVersionUID = 1L; public OptionsException(String message) { super(message); } } } src/apksigner/java/com/android/apksigner/PasswordRetriever.java0100644 0000000 0000000 00000043422 13243353142 024023 0ustar000000000 0000000 /* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksigner; import java.io.ByteArrayOutputStream; import java.io.Console; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.PushbackInputStream; import java.lang.reflect.Method; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.Charset; import java.nio.charset.CodingErrorAction; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; /** * Retriever of passwords based on password specs supported by {@code apksigner} tool. * *

apksigner supports retrieving multiple passwords from the same source (e.g., file, standard * input) which adds the need to keep some sources open across password retrievals. This class * addresses the need. * *

To use this retriever, construct a new instance, use * {@link #getPasswords(String, String, Charset...)} to retrieve passwords, and then invoke * {@link #close()} on the instance when done, enabling the instance to release any held resources. */ class PasswordRetriever implements AutoCloseable { public static final String SPEC_STDIN = "stdin"; /** Character encoding used by the console or {@code null} if not known. */ private final Charset mConsoleEncoding; private final Map mFileInputStreams = new HashMap<>(); private boolean mClosed; PasswordRetriever() { mConsoleEncoding = getConsoleEncoding(); } /** * Returns the passwords described by the provided spec. The reason there may be more than one * password is compatibility with {@code keytool} and {@code jarsigner} which in certain cases * use the form of passwords encoded using the console's character encoding or the JVM default * encoding. * *

Supported specs: *

    *
  • stdin -- read password as a line from console, if available, or standard * input if console is not available
  • *
  • pass:password -- password specified inside the spec, starting after * {@code pass:}
  • *
  • file:path -- read password as a line from the specified file
  • *
  • env:name -- password is in the specified environment variable
  • *
* *

When the same file (including standard input) is used for providing multiple passwords, * the passwords are read from the file one line at a time. * * @param additionalPwdEncodings additional encodings for converting the password into KeyStore * or PKCS #8 encrypted key password. These encoding are used in addition to using the * password verbatim or encoded using JVM default character encoding. A useful encoding * to provide is the console character encoding on Windows machines where the console * may be different from the JVM default encoding. Unfortunately, there is no public API * to obtain the console's character encoding. */ public List getPasswords( String spec, String description, Charset... additionalPwdEncodings) throws IOException { // IMPLEMENTATION NOTE: Java KeyStore and PBEKeySpec APIs take passwords as arrays of // Unicode characters (char[]). Unfortunately, it appears that Sun/Oracle keytool and // jarsigner in some cases use passwords which are the encoded form obtained using the // console's character encoding. For example, if the encoding is UTF-8, keytool and // jarsigner will use the password which is obtained by upcasting each byte of the UTF-8 // encoded form to char. This occurs only when the password is read from stdin/console, and // does not occur when the password is read from a command-line parameter. // There are other tools which use the Java KeyStore API correctly. // Thus, for each password spec, a valid password is typically one of these three: // * Unicode characters, // * characters (upcast bytes) obtained from encoding the password using the console's // character encoding of the console used on the environment where the KeyStore was // created, // * characters (upcast bytes) obtained from encoding the password using the JVM's default // character encoding of the machine where the KeyStore was created. // // For a sample password "\u0061\u0062\u00a1\u00e4\u044e\u0031": // On Windows 10 with English US as the UI language, IBM437 is used as console encoding and // windows-1252 is used as the JVM default encoding: // * keytool -genkey -v -keystore native.jks -keyalg RSA -keysize 2048 -validity 10000 // -alias test // generates a keystore and key which decrypt only with // "\u0061\u0062\u00ad\u0084\u003f\u0031" // * keytool -genkey -v -keystore native.jks -keyalg RSA -keysize 2048 -validity 10000 // -alias test -storepass // generates a keystore and key which decrypt only with // "\u0061\u0062\u00a1\u00e4\u003f\u0031" // On modern OSX/Linux UTF-8 is used as the console and JVM default encoding: // * keytool -genkey -v -keystore native.jks -keyalg RSA -keysize 2048 -validity 10000 // -alias test // generates a keystore and key which decrypt only with // "\u0061\u0062\u00c2\u00a1\u00c3\u00a4\u00d1\u008e\u0031" // * keytool -genkey -v -keystore native.jks -keyalg RSA -keysize 2048 -validity 10000 // -alias test -storepass // generates a keystore and key which decrypt only with // "\u0061\u0062\u00a1\u00e4\u044e\u0031" // // We optimize for the case where the KeyStore was created on the same machine where // apksigner is executed. Thus, we can assume the JVM default encoding used for creating the // KeyStore is the same as the current JVM's default encoding. We can make a similar // assumption about the console's encoding. However, there is no public API for obtaining // the console's character encoding. Prior to Java 9, we could cheat by using Reflection API // to access Console.encoding field. However, in the official Java 9 JVM this field is not // only inaccessible, but results in warnings being spewed to stdout during access attempts. // As a result, we cannot auto-detect the console's encoding and thus rely on the user to // explicitly provide it to apksigner as a command-line parameter (and passed into this // method as additionalPwdEncodings), if the password is using non-ASCII characters. assertNotClosed(); if (spec.startsWith("pass:")) { char[] pwd = spec.substring("pass:".length()).toCharArray(); return getPasswords(pwd, additionalPwdEncodings); } else if (SPEC_STDIN.equals(spec)) { Console console = System.console(); if (console != null) { // Reading from console char[] pwd = console.readPassword(description + ": "); if (pwd == null) { throw new IOException("Failed to read " + description + ": console closed"); } return getPasswords(pwd, additionalPwdEncodings); } else { // Console not available -- reading from standard input System.out.println(description + ": "); byte[] encodedPwd = readEncodedPassword(System.in); if (encodedPwd.length == 0) { throw new IOException( "Failed to read " + description + ": standard input closed"); } // By default, textual input obtained via standard input is supposed to be decoded // using the in JVM default character encoding. return getPasswords(encodedPwd, Charset.defaultCharset(), additionalPwdEncodings); } } else if (spec.startsWith("file:")) { String name = spec.substring("file:".length()); File file = new File(name).getCanonicalFile(); InputStream in = mFileInputStreams.get(file); if (in == null) { in = new FileInputStream(file); mFileInputStreams.put(file, in); } byte[] encodedPwd = readEncodedPassword(in); if (encodedPwd.length == 0) { throw new IOException( "Failed to read " + description + " : end of file reached in " + file); } // By default, textual input from files is supposed to be treated as encoded using JVM's // default character encoding. return getPasswords(encodedPwd, Charset.defaultCharset(), additionalPwdEncodings); } else if (spec.startsWith("env:")) { String name = spec.substring("env:".length()); String value = System.getenv(name); if (value == null) { throw new IOException( "Failed to read " + description + ": environment variable " + value + " not specified"); } return getPasswords(value.toCharArray(), additionalPwdEncodings); } else { throw new IOException("Unsupported password spec for " + description + ": " + spec); } } /** * Returns the provided password and all password variants derived from the password. The * resulting list is guaranteed to contain at least one element. */ private List getPasswords(char[] pwd, Charset... additionalEncodings) { List passwords = new ArrayList<>(3); addPasswords(passwords, pwd, additionalEncodings); return passwords; } /** * Returns the provided password and all password variants derived from the password. The * resulting list is guaranteed to contain at least one element. * * @param encodedPwd password encoded using {@code encodingForDecoding}. */ private List getPasswords( byte[] encodedPwd, Charset encodingForDecoding, Charset... additionalEncodings) { List passwords = new ArrayList<>(4); // Decode password and add it and its variants to the list try { char[] pwd = decodePassword(encodedPwd, encodingForDecoding); addPasswords(passwords, pwd, additionalEncodings); } catch (IOException ignored) {} // Add the original encoded form addPassword(passwords, castBytesToChars(encodedPwd)); return passwords; } /** * Adds the provided password and its variants to the provided list of passwords. * *

NOTE: This method adds only the passwords/variants which are not yet in the list. */ private void addPasswords(List passwords, char[] pwd, Charset... additionalEncodings) { if ((additionalEncodings != null) && (additionalEncodings.length > 0)) { for (Charset encoding : additionalEncodings) { // Password encoded using provided encoding (usually the console's character // encoding) and upcast into char[] try { char[] encodedPwd = castBytesToChars(encodePassword(pwd, encoding)); addPassword(passwords, encodedPwd); } catch (IOException ignored) {} } } // Verbatim password addPassword(passwords, pwd); // Password encoded using the console encoding and upcast into char[] if (mConsoleEncoding != null) { try { char[] encodedPwd = castBytesToChars(encodePassword(pwd, mConsoleEncoding)); addPassword(passwords, encodedPwd); } catch (IOException ignored) {} } // Password encoded using the JVM default character encoding and upcast into char[] try { char[] encodedPwd = castBytesToChars(encodePassword(pwd, Charset.defaultCharset())); addPassword(passwords, encodedPwd); } catch (IOException ignored) {} } /** * Adds the provided password to the provided list. Does nothing if the password is already in * the list. */ private static void addPassword(List passwords, char[] password) { for (char[] existingPassword : passwords) { if (Arrays.equals(password, existingPassword)) { return; } } passwords.add(password); } private static byte[] encodePassword(char[] pwd, Charset cs) throws IOException { ByteBuffer pwdBytes = cs.newEncoder() .onMalformedInput(CodingErrorAction.REPLACE) .onUnmappableCharacter(CodingErrorAction.REPLACE) .encode(CharBuffer.wrap(pwd)); byte[] encoded = new byte[pwdBytes.remaining()]; pwdBytes.get(encoded); return encoded; } private static char[] decodePassword(byte[] pwdBytes, Charset encoding) throws IOException { CharBuffer pwdChars = encoding.newDecoder() .onMalformedInput(CodingErrorAction.REPLACE) .onUnmappableCharacter(CodingErrorAction.REPLACE) .decode(ByteBuffer.wrap(pwdBytes)); char[] result = new char[pwdChars.remaining()]; pwdChars.get(result); return result; } /** * Upcasts each {@code byte} in the provided array of bytes to a {@code char} and returns the * resulting array of characters. */ private static char[] castBytesToChars(byte[] bytes) { if (bytes == null) { return null; } char[] chars = new char[bytes.length]; for (int i = 0; i < bytes.length; i++) { chars[i] = (char) (bytes[i] & 0xff); } return chars; } private static boolean isJava9OrHigherErrOnTheSideOfCaution() { // Before Java 9, this string is of major.minor form, such as "1.8" for Java 8. // From Java 9 onwards, this is a single number: major, such as "9" for Java 9. // See JEP 223: New Version-String Scheme. String versionString = System.getProperty("java.specification.version"); if (versionString == null) { // Better safe than sorry return true; } return !versionString.startsWith("1."); } /** * Returns the character encoding used by the console or {@code null} if the encoding is not * known. */ private static Charset getConsoleEncoding() { // IMPLEMENTATION NOTE: There is no public API for obtaining the console's character // encoding. We thus cheat by using implementation details of the most popular JVMs. // Unfortunately, this doesn't work on Java 9 JVMs where access to Console.encoding is // restricted by default and leads to spewing to stdout at runtime. if (isJava9OrHigherErrOnTheSideOfCaution()) { return null; } String consoleCharsetName = null; try { Method encodingMethod = Console.class.getDeclaredMethod("encoding"); encodingMethod.setAccessible(true); consoleCharsetName = (String) encodingMethod.invoke(null); } catch (ReflectiveOperationException ignored) { return null; } if (consoleCharsetName == null) { // Console encoding is the same as this JVM's default encoding return Charset.defaultCharset(); } try { return getCharsetByName(consoleCharsetName); } catch (IllegalArgumentException e) { return null; } } public static Charset getCharsetByName(String charsetName) throws IllegalArgumentException { // On Windows 10, cp65001 is the UTF-8 code page. For some reason, popular JVMs don't // have a mapping for cp65001... if ("cp65001".equalsIgnoreCase(charsetName)) { return StandardCharsets.UTF_8; } return Charset.forName(charsetName); } private static byte[] readEncodedPassword(InputStream in) throws IOException { ByteArrayOutputStream result = new ByteArrayOutputStream(); int b; while ((b = in.read()) != -1) { if (b == '\n') { break; } else if (b == '\r') { int next = in.read(); if ((next == -1) || (next == '\n')) { break; } if (!(in instanceof PushbackInputStream)) { in = new PushbackInputStream(in); } ((PushbackInputStream) in).unread(next); } result.write(b); } return result.toByteArray(); } private void assertNotClosed() { if (mClosed) { throw new IllegalStateException("Closed"); } } @Override public void close() { for (InputStream in : mFileInputStreams.values()) { try { in.close(); } catch (IOException ignored) {} } mFileInputStreams.clear(); mClosed = true; } } src/apksigner/java/com/android/apksigner/help.txt0100644 0000000 0000000 00000001142 13243353142 021150 0ustar000000000 0000000 USAGE: apksigner [options] apksigner --version apksigner --help EXAMPLE: apksigner sign --ks release.jks app.apk apksigner verify --verbose app.apk apksigner is a tool for signing Android APK files and for checking whether signatures of APK files will verify on Android devices. COMMANDS sign Sign the provided APK verify Check whether the provided APK is expected to verify on Android version Show this tool's version number and exit help Show this usage page and exit src/apksigner/java/com/android/apksigner/help_sign.txt0100644 0000000 0000000 00000024651 13243353142 022202 0ustar000000000 0000000 USAGE: apksigner sign [options] apk This signs the provided APK, stripping out any pre-existing signatures. Signing is performed using one or more signers, each represented by an asymmetric key pair and a corresponding certificate. Typically, an APK is signed by just one signer. For each signer, you need to provide the signer's private key and certificate. GENERAL OPTIONS --in Input APK file to sign. This is an alternative to specifying the APK as the very last parameter, after all options. Unless --out is specified, this file will be overwritten with the resulting signed APK. --out File into which to output the signed APK. By default, the APK is signed in-place, overwriting the input file. -v, --verbose Verbose output mode --v1-signing-enabled Whether to enable signing using JAR signing scheme (aka v1 signing scheme) used in Android since day one. By default, signing using this scheme is enabled based on min and max SDK version (see --min-sdk-version and --max-sdk-version). --v2-signing-enabled Whether to enable signing using APK Signature Scheme v2 (aka v2 signing scheme) introduced in Android Nougat, API Level 24. By default, signing using this scheme is enabled based on min and max SDK version (see --min-sdk-version and --max-sdk-version). --min-sdk-version Lowest API Level on which this APK's signatures will be verified. By default, the value from AndroidManifest.xml is used. The higher the value, the stronger security parameters are used when signing. --max-sdk-version Highest API Level on which this APK's signatures will be verified. By default, the highest possible value is used. -h, --help Show help about this command and exit PER-SIGNER OPTIONS These options specify the configuration of a particular signer. To delimit options of different signers, use --next-signer. --next-signer Delimits options of two different signers. There is no need to use this option when only one signer is used. --v1-signer-name Basename for files comprising the JAR signature scheme (aka v1 scheme) signature of this signer. By default, KeyStore key alias or basename of key file is used. PER-SIGNER SIGNING KEY & CERTIFICATE OPTIONS There are two ways to provide the signer's private key and certificate: (1) Java KeyStore (see --ks), or (2) private key file in PKCS #8 format and certificate file in X.509 format (see --key and --cert). --ks Load private key and certificate chain from the Java KeyStore initialized from the specified file. NONE means no file is needed by KeyStore, which is the case for some PKCS #11 KeyStores. --ks-key-alias Alias under which the private key and certificate are stored in the KeyStore. This must be specified if the KeyStore contains multiple keys. --ks-pass KeyStore password (see --ks). The following formats are supported: pass: password provided inline env: password provided in the named environment variable file: password provided in the named file, as a single line stdin password provided on standard input, as a single line A password is required to open a KeyStore. By default, the tool will prompt for password via console or standard input. When the same file (including standard input) is used for providing multiple passwords, the passwords are read from the file one line at a time. Passwords are read in the order in which signers are specified and, within each signer, KeyStore password is read before the key password is read. --key-pass Password with which the private key is protected. The following formats are supported: pass: password provided inline env: password provided in the named environment variable file: password provided in the named file, as a single line stdin password provided on standard input, as a single line If --key-pass is not specified for a KeyStore key, this tool will attempt to load the key using the KeyStore password and, if that fails, will prompt for key password and attempt to load the key using that password. If --key-pass is not specified for a private key file key, this tool will prompt for key password only if a password is required. When the same file (including standard input) is used for providing multiple passwords, the passwords are read from the file one line at a time. Passwords are read in the order in which signers are specified and, within each signer, KeyStore password is read before the key password is read. --pass-encoding Additional character encoding (e.g., ibm437 or utf-8) to try for passwords containing non-ASCII characters. KeyStores created by keytool are often encrypted not using the Unicode form of the password but rather using the form produced by encoding the password using the console's character encoding. apksigner by default tries to decrypt using several forms of the password: the Unicode form, the form encoded using the JVM default charset, and, on Java 8 and older, the form encoded using the console's charset. On Java 9, apksigner cannot detect the console's charset and may need to be provided with --pass-encoding when a non-ASCII password is used. --pass-encoding may also need to be provided for a KeyStore created by keytool on a different OS or in a different locale. --ks-type Type/algorithm of KeyStore to use. By default, the default type is used. --ks-provider-name Name of the JCA Provider from which to request the KeyStore implementation. By default, the highest priority provider is used. See --ks-provider-class for the alternative way to specify a provider. --ks-provider-class Fully-qualified class name of the JCA Provider from which to request the KeyStore implementation. By default, the provider is chosen based on --ks-provider-name. --ks-provider-arg Value to pass into the constructor of the JCA Provider class specified by --ks-provider-class. The value is passed into the constructor as java.lang.String. By default, the no-arg provider's constructor is used. --key Load private key from the specified file. If the key is password-protected, the password will be prompted via standard input unless specified otherwise using --key-pass. The file must be in PKCS #8 DER format. --cert Load certificate chain from the specified file. The file must be in X.509 PEM or DER format. JCA PROVIDER INSTALLATION OPTIONS These options enable you to install additional Java Crypto Architecture (JCA) Providers, such as PKCS #11 providers. Use --next-provider to delimit options of different providers. Providers are installed in the order in which they appear on the command-line. --provider-class Fully-qualified class name of the JCA Provider. --provider-arg Value to pass into the constructor of the JCA Provider class specified by --provider-class. The value is passed into the constructor as java.lang.String. By default, the no-arg provider's constructor is used. --provider-pos Position / priority at which to install this provider in the JCA provider list. By default, the provider is installed as the lowest priority provider. See java.security.Security.insertProviderAt. EXAMPLES 1. Sign an APK, in-place, using the one and only key in keystore release.jks: $ apksigner sign --ks release.jks app.apk 1. Sign an APK, without overwriting, using the one and only key in keystore release.jks: $ apksigner sign --ks release.jks --in app.apk --out app-signed.apk 3. Sign an APK using a private key and certificate stored as individual files: $ apksigner sign --key release.pk8 --cert release.x509.pem app.apk 4. Sign an APK using two keys: $ apksigner sign --ks release.jks --next-signer --ks magic.jks app.apk 5. Sign an APK using PKCS #11 JCA Provider: $ apksigner sign --provider-class sun.security.pkcs11.SunPKCS11 \ --provider-arg token.cfg --ks NONE --ks-type PKCS11 app.apk 6. Sign an APK using a non-ASCII password KeyStore created on English Windows. The --pass-encoding parameter is not needed if apksigner is being run on English Windows with Java 8 or older. $ apksigner sign --ks release.jks --pass-encoding ibm437 app.apk 7. Sign an APK on Windows using a non-ASCII password KeyStore created on a modern OSX or Linux machine: $ apksigner sign --ks release.jks --pass-encoding utf-8 app.apk src/apksigner/java/com/android/apksigner/help_verify.txt0100644 0000000 0000000 00000002657 13243353142 022550 0ustar000000000 0000000 USAGE: apksigner verify [options] apk This checks whether the provided APK will verify on Android. By default, this checks whether the APK will verify on all Android platform versions supported by the APK (as declared using minSdkVersion in AndroidManifest.xml). Use --min-sdk-version and/or --max-sdk-version to verify the APK against a custom range of API Levels. OPTIONS --print-certs Show information about the APK's signing certificates -v, --verbose Verbose output mode --min-sdk-version Lowest API Level on which this APK's signatures will be verified. By default, the value from AndroidManifest.xml is used. --max-sdk-version Highest API Level on which this APK's signatures will be verified. By default, the highest possible value is used. -Werr Treat warnings as errors --in APK file to verify. This is an alternative to specifying the APK as the very last parameter, after all options. -h, --help Show help about this command and exit EXAMPLES 1. Check whether the APK's signatures are expected to verify on all Android platforms declared as supported by this APK: $ apksigner verify app.apk 2. Check whether the APK's signatures are expected to verify on Android platforms with API Level 15 and higher: $ apksigner verify --min-sdk-version 15 app.apk src/main/0040755 0000000 0000000 00000000000 13243353142 011323 5ustar000000000 0000000 src/main/java/0040755 0000000 0000000 00000000000 13243353142 012244 5ustar000000000 0000000 src/main/java/com/0040755 0000000 0000000 00000000000 13243353142 013022 5ustar000000000 0000000 src/main/java/com/android/0040755 0000000 0000000 00000000000 13243353142 014442 5ustar000000000 0000000 src/main/java/com/android/apksig/0040755 0000000 0000000 00000000000 13243353142 015720 5ustar000000000 0000000 src/main/java/com/android/apksig/ApkSigner.java0100644 0000000 0000000 00000137061 13243353142 020453 0ustar000000000 0000000 /* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig; import com.android.apksig.apk.ApkFormatException; import com.android.apksig.apk.ApkUtils; import com.android.apksig.apk.MinSdkVersionException; import com.android.apksig.internal.apk.v2.V2SchemeVerifier; import com.android.apksig.internal.util.ByteBufferDataSource; import com.android.apksig.internal.util.Pair; import com.android.apksig.internal.zip.CentralDirectoryRecord; import com.android.apksig.internal.zip.EocdRecord; import com.android.apksig.internal.zip.LocalFileRecord; import com.android.apksig.internal.zip.ZipUtils; import com.android.apksig.util.DataSink; import com.android.apksig.util.DataSinks; import com.android.apksig.util.DataSource; import com.android.apksig.util.DataSources; import com.android.apksig.util.ReadableDataSink; import com.android.apksig.zip.ZipFormatException; import java.io.Closeable; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.SignatureException; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; /** * APK signer. * *

The signer preserves as much of the input APK as possible. For example, it preserves the * order of APK entries and preserves their contents, including compressed form and alignment of * data. * *

Use {@link Builder} to obtain instances of this signer. * * @see Application Signing */ public class ApkSigner { /** * Extensible data block/field header ID used for storing information about alignment of * uncompressed entries as well as for aligning the entries's data. See ZIP appnote.txt section * 4.5 Extensible data fields. */ private static final short ALIGNMENT_ZIP_EXTRA_DATA_FIELD_HEADER_ID = (short) 0xd935; /** * Minimum size (in bytes) of the extensible data block/field used for alignment of uncompressed * entries. */ private static final short ALIGNMENT_ZIP_EXTRA_DATA_FIELD_MIN_SIZE_BYTES = 6; /** * Name of the Android manifest ZIP entry in APKs. */ private static final String ANDROID_MANIFEST_ZIP_ENTRY_NAME = "AndroidManifest.xml"; private final List mSignerConfigs; private final Integer mMinSdkVersion; private final boolean mV1SigningEnabled; private final boolean mV2SigningEnabled; private final boolean mOtherSignersSignaturesPreserved; private final String mCreatedBy; private final ApkSignerEngine mSignerEngine; private final File mInputApkFile; private final DataSource mInputApkDataSource; private final File mOutputApkFile; private final DataSink mOutputApkDataSink; private final DataSource mOutputApkDataSource; private ApkSigner( List signerConfigs, Integer minSdkVersion, boolean v1SigningEnabled, boolean v2SigningEnabled, boolean otherSignersSignaturesPreserved, String createdBy, ApkSignerEngine signerEngine, File inputApkFile, DataSource inputApkDataSource, File outputApkFile, DataSink outputApkDataSink, DataSource outputApkDataSource) { mSignerConfigs = signerConfigs; mMinSdkVersion = minSdkVersion; mV1SigningEnabled = v1SigningEnabled; mV2SigningEnabled = v2SigningEnabled; mOtherSignersSignaturesPreserved = otherSignersSignaturesPreserved; mCreatedBy = createdBy; mSignerEngine = signerEngine; mInputApkFile = inputApkFile; mInputApkDataSource = inputApkDataSource; mOutputApkFile = outputApkFile; mOutputApkDataSink = outputApkDataSink; mOutputApkDataSource = outputApkDataSource; } /** * Signs the input APK and outputs the resulting signed APK. The input APK is not modified. * * @throws IOException if an I/O error is encountered while reading or writing the APKs * @throws ApkFormatException if the input APK is malformed * @throws NoSuchAlgorithmException if the APK signatures cannot be produced or verified because * a required cryptographic algorithm implementation is missing * @throws InvalidKeyException if a signature could not be generated because a signing key is * not suitable for generating the signature * @throws SignatureException if an error occurred while generating or verifying a signature * @throws IllegalStateException if this signer's configuration is missing required information * or if the signing engine is in an invalid state. */ public void sign() throws IOException, ApkFormatException, NoSuchAlgorithmException, InvalidKeyException, SignatureException, IllegalStateException { Closeable in = null; DataSource inputApk; try { if (mInputApkDataSource != null) { inputApk = mInputApkDataSource; } else if (mInputApkFile != null) { RandomAccessFile inputFile = new RandomAccessFile(mInputApkFile, "r"); in = inputFile; inputApk = DataSources.asDataSource(inputFile); } else { throw new IllegalStateException("Input APK not specified"); } Closeable out = null; try { DataSink outputApkOut; DataSource outputApkIn; if (mOutputApkDataSink != null) { outputApkOut = mOutputApkDataSink; outputApkIn = mOutputApkDataSource; } else if (mOutputApkFile != null) { RandomAccessFile outputFile = new RandomAccessFile(mOutputApkFile, "rw"); out = outputFile; outputFile.setLength(0); outputApkOut = DataSinks.asDataSink(outputFile); outputApkIn = DataSources.asDataSource(outputFile); } else { throw new IllegalStateException("Output APK not specified"); } sign(inputApk, outputApkOut, outputApkIn); } finally { if (out != null) { out.close(); } } } finally { if (in != null) { in.close(); } } } private void sign( DataSource inputApk, DataSink outputApkOut, DataSource outputApkIn) throws IOException, ApkFormatException, NoSuchAlgorithmException, InvalidKeyException, SignatureException { // Step 1. Find input APK's main ZIP sections ApkUtils.ZipSections inputZipSections; try { inputZipSections = ApkUtils.findZipSections(inputApk); } catch (ZipFormatException e) { throw new ApkFormatException("Malformed APK: not a ZIP archive", e); } long inputApkSigningBlockOffset = -1; DataSource inputApkSigningBlock = null; try { Pair apkSigningBlockAndOffset = V2SchemeVerifier.findApkSigningBlock(inputApk, inputZipSections); inputApkSigningBlock = apkSigningBlockAndOffset.getFirst(); inputApkSigningBlockOffset = apkSigningBlockAndOffset.getSecond(); } catch (V2SchemeVerifier.SignatureNotFoundException e) { // Input APK does not contain an APK Signing Block. That's OK. APKs are not required to // contain this block. It's only needed if the APK is signed using APK Signature Scheme // v2. } DataSource inputApkLfhSection = inputApk.slice( 0, (inputApkSigningBlockOffset != -1) ? inputApkSigningBlockOffset : inputZipSections.getZipCentralDirectoryOffset()); // Step 2. Parse the input APK's ZIP Central Directory ByteBuffer inputCd = getZipCentralDirectory(inputApk, inputZipSections); List inputCdRecords = parseZipCentralDirectory(inputCd, inputZipSections); // Step 3. Obtain a signer engine instance ApkSignerEngine signerEngine; if (mSignerEngine != null) { // Use the provided signer engine signerEngine = mSignerEngine; } else { // Construct a signer engine from the provided parameters int minSdkVersion; if (mMinSdkVersion != null) { // No need to extract minSdkVersion from the APK's AndroidManifest.xml minSdkVersion = mMinSdkVersion; } else { // Need to extract minSdkVersion from the APK's AndroidManifest.xml minSdkVersion = getMinSdkVersionFromApk(inputCdRecords, inputApkLfhSection); } List engineSignerConfigs = new ArrayList<>(mSignerConfigs.size()); for (SignerConfig signerConfig : mSignerConfigs) { engineSignerConfigs.add( new DefaultApkSignerEngine.SignerConfig.Builder( signerConfig.getName(), signerConfig.getPrivateKey(), signerConfig.getCertificates()) .build()); } DefaultApkSignerEngine.Builder signerEngineBuilder = new DefaultApkSignerEngine.Builder(engineSignerConfigs, minSdkVersion) .setV1SigningEnabled(mV1SigningEnabled) .setV2SigningEnabled(mV2SigningEnabled) .setOtherSignersSignaturesPreserved(mOtherSignersSignaturesPreserved); if (mCreatedBy != null) { signerEngineBuilder.setCreatedBy(mCreatedBy); } signerEngine = signerEngineBuilder.build(); } // Step 4. Provide the signer engine with the input APK's APK Signing Block (if any) if (inputApkSigningBlock != null) { signerEngine.inputApkSigningBlock(inputApkSigningBlock); } // Step 5. Iterate over input APK's entries and output the Local File Header + data of those // entries which need to be output. Entries are iterated in the order in which their Local // File Header records are stored in the file. This is to achieve better data locality in // case Central Directory entries are in the wrong order. List inputCdRecordsSortedByLfhOffset = new ArrayList<>(inputCdRecords); Collections.sort( inputCdRecordsSortedByLfhOffset, CentralDirectoryRecord.BY_LOCAL_FILE_HEADER_OFFSET_COMPARATOR); int lastModifiedDateForNewEntries = -1; int lastModifiedTimeForNewEntries = -1; long inputOffset = 0; long outputOffset = 0; Map outputCdRecordsByName = new HashMap<>(inputCdRecords.size()); for (final CentralDirectoryRecord inputCdRecord : inputCdRecordsSortedByLfhOffset) { String entryName = inputCdRecord.getName(); ApkSignerEngine.InputJarEntryInstructions entryInstructions = signerEngine.inputJarEntry(entryName); boolean shouldOutput; switch (entryInstructions.getOutputPolicy()) { case OUTPUT: shouldOutput = true; break; case OUTPUT_BY_ENGINE: case SKIP: shouldOutput = false; break; default: throw new RuntimeException( "Unknown output policy: " + entryInstructions.getOutputPolicy()); } long inputLocalFileHeaderStartOffset = inputCdRecord.getLocalFileHeaderOffset(); if (inputLocalFileHeaderStartOffset > inputOffset) { // Unprocessed data in input starting at inputOffset and ending and the start of // this record's LFH. We output this data verbatim because this signer is supposed // to preserve as much of input as possible. long chunkSize = inputLocalFileHeaderStartOffset - inputOffset; inputApkLfhSection.feed(inputOffset, chunkSize, outputApkOut); outputOffset += chunkSize; inputOffset = inputLocalFileHeaderStartOffset; } LocalFileRecord inputLocalFileRecord; try { inputLocalFileRecord = LocalFileRecord.getRecord( inputApkLfhSection, inputCdRecord, inputApkLfhSection.size()); } catch (ZipFormatException e) { throw new ApkFormatException("Malformed ZIP entry: " + inputCdRecord.getName(), e); } inputOffset += inputLocalFileRecord.getSize(); ApkSignerEngine.InspectJarEntryRequest inspectEntryRequest = entryInstructions.getInspectJarEntryRequest(); if (inspectEntryRequest != null) { fulfillInspectInputJarEntryRequest( inputApkLfhSection, inputLocalFileRecord, inspectEntryRequest); } if (shouldOutput) { // Find the max value of last modified, to be used for new entries added by the // signer. int lastModifiedDate = inputCdRecord.getLastModificationDate(); int lastModifiedTime = inputCdRecord.getLastModificationTime(); if ((lastModifiedDateForNewEntries == -1) || (lastModifiedDate > lastModifiedDateForNewEntries) || ((lastModifiedDate == lastModifiedDateForNewEntries) && (lastModifiedTime > lastModifiedTimeForNewEntries))) { lastModifiedDateForNewEntries = lastModifiedDate; lastModifiedTimeForNewEntries = lastModifiedTime; } inspectEntryRequest = signerEngine.outputJarEntry(entryName); if (inspectEntryRequest != null) { fulfillInspectInputJarEntryRequest( inputApkLfhSection, inputLocalFileRecord, inspectEntryRequest); } // Output entry's Local File Header + data long outputLocalFileHeaderOffset = outputOffset; long outputLocalFileRecordSize = outputInputJarEntryLfhRecordPreservingDataAlignment( inputApkLfhSection, inputLocalFileRecord, outputApkOut, outputLocalFileHeaderOffset); outputOffset += outputLocalFileRecordSize; // Enqueue entry's Central Directory record for output CentralDirectoryRecord outputCdRecord; if (outputLocalFileHeaderOffset == inputLocalFileRecord.getStartOffsetInArchive()) { outputCdRecord = inputCdRecord; } else { outputCdRecord = inputCdRecord.createWithModifiedLocalFileHeaderOffset( outputLocalFileHeaderOffset); } outputCdRecordsByName.put(entryName, outputCdRecord); } } long inputLfhSectionSize = inputApkLfhSection.size(); if (inputOffset < inputLfhSectionSize) { // Unprocessed data in input starting at inputOffset and ending and the end of the input // APK's LFH section. We output this data verbatim because this signer is supposed // to preserve as much of input as possible. long chunkSize = inputLfhSectionSize - inputOffset; inputApkLfhSection.feed(inputOffset, chunkSize, outputApkOut); outputOffset += chunkSize; inputOffset = inputLfhSectionSize; } // Step 6. Sort output APK's Central Directory records in the order in which they should // appear in the output List outputCdRecords = new ArrayList<>(inputCdRecords.size() + 10); for (CentralDirectoryRecord inputCdRecord : inputCdRecords) { String entryName = inputCdRecord.getName(); CentralDirectoryRecord outputCdRecord = outputCdRecordsByName.get(entryName); if (outputCdRecord != null) { outputCdRecords.add(outputCdRecord); } } // Step 7. Generate and output JAR signatures, if necessary. This may output more Local File // Header + data entries and add to the list of output Central Directory records. ApkSignerEngine.OutputJarSignatureRequest outputJarSignatureRequest = signerEngine.outputJarEntries(); if (outputJarSignatureRequest != null) { if (lastModifiedDateForNewEntries == -1) { lastModifiedDateForNewEntries = 0x3a21; // Jan 1 2009 (DOS) lastModifiedTimeForNewEntries = 0; } for (ApkSignerEngine.OutputJarSignatureRequest.JarEntry entry : outputJarSignatureRequest.getAdditionalJarEntries()) { String entryName = entry.getName(); byte[] uncompressedData = entry.getData(); ZipUtils.DeflateResult deflateResult = ZipUtils.deflate(ByteBuffer.wrap(uncompressedData)); byte[] compressedData = deflateResult.output; long uncompressedDataCrc32 = deflateResult.inputCrc32; ApkSignerEngine.InspectJarEntryRequest inspectEntryRequest = signerEngine.outputJarEntry(entryName); if (inspectEntryRequest != null) { inspectEntryRequest.getDataSink().consume( uncompressedData, 0, uncompressedData.length); inspectEntryRequest.done(); } long localFileHeaderOffset = outputOffset; outputOffset += LocalFileRecord.outputRecordWithDeflateCompressedData( entryName, lastModifiedTimeForNewEntries, lastModifiedDateForNewEntries, compressedData, uncompressedDataCrc32, uncompressedData.length, outputApkOut); outputCdRecords.add( CentralDirectoryRecord.createWithDeflateCompressedData( entryName, lastModifiedTimeForNewEntries, lastModifiedDateForNewEntries, uncompressedDataCrc32, compressedData.length, uncompressedData.length, localFileHeaderOffset)); } outputJarSignatureRequest.done(); } // Step 8. Construct output ZIP Central Directory in an in-memory buffer long outputCentralDirSizeBytes = 0; for (CentralDirectoryRecord record : outputCdRecords) { outputCentralDirSizeBytes += record.getSize(); } if (outputCentralDirSizeBytes > Integer.MAX_VALUE) { throw new IOException( "Output ZIP Central Directory too large: " + outputCentralDirSizeBytes + " bytes"); } ByteBuffer outputCentralDir = ByteBuffer.allocate((int) outputCentralDirSizeBytes); for (CentralDirectoryRecord record : outputCdRecords) { record.copyTo(outputCentralDir); } outputCentralDir.flip(); DataSource outputCentralDirDataSource = new ByteBufferDataSource(outputCentralDir); long outputCentralDirStartOffset = outputOffset; int outputCentralDirRecordCount = outputCdRecords.size(); // Step 9. Construct output ZIP End of Central Directory record in an in-memory buffer ByteBuffer outputEocd = EocdRecord.createWithModifiedCentralDirectoryInfo( inputZipSections.getZipEndOfCentralDirectory(), outputCentralDirRecordCount, outputCentralDirDataSource.size(), outputCentralDirStartOffset); // Step 10. Generate and output APK Signature Scheme v2 signatures, if necessary. This may // insert an APK Signing Block just before the output's ZIP Central Directory ApkSignerEngine.OutputApkSigningBlockRequest outputApkSigingBlockRequest = signerEngine.outputZipSections( outputApkIn, outputCentralDirDataSource, DataSources.asDataSource(outputEocd)); if (outputApkSigingBlockRequest != null) { byte[] outputApkSigningBlock = outputApkSigingBlockRequest.getApkSigningBlock(); outputApkOut.consume(outputApkSigningBlock, 0, outputApkSigningBlock.length); ZipUtils.setZipEocdCentralDirectoryOffset( outputEocd, outputCentralDirStartOffset + outputApkSigningBlock.length); outputApkSigingBlockRequest.done(); } // Step 11. Output ZIP Central Directory and ZIP End of Central Directory outputCentralDirDataSource.feed(0, outputCentralDirDataSource.size(), outputApkOut); outputApkOut.consume(outputEocd); signerEngine.outputDone(); } private static void fulfillInspectInputJarEntryRequest( DataSource lfhSection, LocalFileRecord localFileRecord, ApkSignerEngine.InspectJarEntryRequest inspectEntryRequest) throws IOException, ApkFormatException { try { localFileRecord.outputUncompressedData(lfhSection, inspectEntryRequest.getDataSink()); } catch (ZipFormatException e) { throw new ApkFormatException("Malformed ZIP entry: " + localFileRecord.getName(), e); } inspectEntryRequest.done(); } private static long outputInputJarEntryLfhRecordPreservingDataAlignment( DataSource inputLfhSection, LocalFileRecord inputRecord, DataSink outputLfhSection, long outputOffset) throws IOException { long inputOffset = inputRecord.getStartOffsetInArchive(); if (inputOffset == outputOffset) { // This record's data will be aligned same as in the input APK. return inputRecord.outputRecord(inputLfhSection, outputLfhSection); } int dataAlignmentMultiple = getInputJarEntryDataAlignmentMultiple(inputRecord); if ((dataAlignmentMultiple <= 1) || ((inputOffset % dataAlignmentMultiple) == (outputOffset % dataAlignmentMultiple))) { // This record's data will be aligned same as in the input APK. return inputRecord.outputRecord(inputLfhSection, outputLfhSection); } long inputDataStartOffset = inputOffset + inputRecord.getDataStartOffsetInRecord(); if ((inputDataStartOffset % dataAlignmentMultiple) != 0) { // This record's data is not aligned in the input APK. No need to align it in the // output. return inputRecord.outputRecord(inputLfhSection, outputLfhSection); } // This record's data needs to be re-aligned in the output. This is achieved using the // record's extra field. ByteBuffer aligningExtra = createExtraFieldToAlignData( inputRecord.getExtra(), outputOffset + inputRecord.getExtraFieldStartOffsetInsideRecord(), dataAlignmentMultiple); return inputRecord.outputRecordWithModifiedExtra( inputLfhSection, aligningExtra, outputLfhSection); } private static int getInputJarEntryDataAlignmentMultiple(LocalFileRecord entry) { if (entry.isDataCompressed()) { // Compressed entries don't need to be aligned return 1; } // Attempt to obtain the alignment multiple from the entry's extra field. ByteBuffer extra = entry.getExtra(); if (extra.hasRemaining()) { extra.order(ByteOrder.LITTLE_ENDIAN); // FORMAT: sequence of fields. Each field consists of: // * uint16 ID // * uint16 size // * 'size' bytes: payload while (extra.remaining() >= 4) { short headerId = extra.getShort(); int dataSize = ZipUtils.getUnsignedInt16(extra); if (dataSize > extra.remaining()) { // Malformed field -- insufficient input remaining break; } if (headerId != ALIGNMENT_ZIP_EXTRA_DATA_FIELD_HEADER_ID) { // Skip this field extra.position(extra.position() + dataSize); continue; } // This is APK alignment field. // FORMAT: // * uint16 alignment multiple (in bytes) // * remaining bytes -- padding to achieve alignment of data which starts after // the extra field if (dataSize < 2) { // Malformed break; } return ZipUtils.getUnsignedInt16(extra); } } // Fall back to filename-based defaults return (entry.getName().endsWith(".so")) ? 4096 : 4; } private static ByteBuffer createExtraFieldToAlignData( ByteBuffer original, long extraStartOffset, int dataAlignmentMultiple) { if (dataAlignmentMultiple <= 1) { return original; } // In the worst case scenario, we'll increase the output size by 6 + dataAlignment - 1. ByteBuffer result = ByteBuffer.allocate(original.remaining() + 5 + dataAlignmentMultiple); result.order(ByteOrder.LITTLE_ENDIAN); // Step 1. Output all extra fields other than the one which is to do with alignment // FORMAT: sequence of fields. Each field consists of: // * uint16 ID // * uint16 size // * 'size' bytes: payload while (original.remaining() >= 4) { short headerId = original.getShort(); int dataSize = ZipUtils.getUnsignedInt16(original); if (dataSize > original.remaining()) { // Malformed field -- insufficient input remaining break; } if (((headerId == 0) && (dataSize == 0)) || (headerId == ALIGNMENT_ZIP_EXTRA_DATA_FIELD_HEADER_ID)) { // Ignore the field if it has to do with the old APK data alignment method (filling // the extra field with 0x00 bytes) or the new APK data alignment method. original.position(original.position() + dataSize); continue; } // Copy this field (including header) to the output original.position(original.position() - 4); int originalLimit = original.limit(); original.limit(original.position() + 4 + dataSize); result.put(original); original.limit(originalLimit); } // Step 2. Add alignment field // FORMAT: // * uint16 extra header ID // * uint16 extra data size // Payload ('data size' bytes) // * uint16 alignment multiple (in bytes) // * remaining bytes -- padding to achieve alignment of data which starts after the // extra field long dataMinStartOffset = extraStartOffset + result.position() + ALIGNMENT_ZIP_EXTRA_DATA_FIELD_MIN_SIZE_BYTES; int paddingSizeBytes = (dataAlignmentMultiple - ((int) (dataMinStartOffset % dataAlignmentMultiple))) % dataAlignmentMultiple; result.putShort(ALIGNMENT_ZIP_EXTRA_DATA_FIELD_HEADER_ID); ZipUtils.putUnsignedInt16(result, 2 + paddingSizeBytes); ZipUtils.putUnsignedInt16(result, dataAlignmentMultiple); result.position(result.position() + paddingSizeBytes); result.flip(); return result; } private static ByteBuffer getZipCentralDirectory( DataSource apk, ApkUtils.ZipSections apkSections) throws IOException, ApkFormatException { long cdSizeBytes = apkSections.getZipCentralDirectorySizeBytes(); if (cdSizeBytes > Integer.MAX_VALUE) { throw new ApkFormatException("ZIP Central Directory too large: " + cdSizeBytes); } long cdOffset = apkSections.getZipCentralDirectoryOffset(); ByteBuffer cd = apk.getByteBuffer(cdOffset, (int) cdSizeBytes); cd.order(ByteOrder.LITTLE_ENDIAN); return cd; } private static List parseZipCentralDirectory( ByteBuffer cd, ApkUtils.ZipSections apkSections) throws ApkFormatException { long cdOffset = apkSections.getZipCentralDirectoryOffset(); int expectedCdRecordCount = apkSections.getZipCentralDirectoryRecordCount(); List cdRecords = new ArrayList<>(expectedCdRecordCount); Set entryNames = new HashSet<>(expectedCdRecordCount); for (int i = 0; i < expectedCdRecordCount; i++) { CentralDirectoryRecord cdRecord; int offsetInsideCd = cd.position(); try { cdRecord = CentralDirectoryRecord.getRecord(cd); } catch (ZipFormatException e) { throw new ApkFormatException( "Malformed ZIP Central Directory record #" + (i + 1) + " at file offset " + (cdOffset + offsetInsideCd), e); } String entryName = cdRecord.getName(); if (!entryNames.add(entryName)) { throw new ApkFormatException( "Multiple ZIP entries with the same name: " + entryName); } cdRecords.add(cdRecord); } if (cd.hasRemaining()) { throw new ApkFormatException( "Unused space at the end of ZIP Central Directory: " + cd.remaining() + " bytes starting at file offset " + (cdOffset + cd.position())); } return cdRecords; } /** * Returns the contents of the APK's {@code AndroidManifest.xml} or {@code null} if this entry * is not present in the APK. */ static ByteBuffer getAndroidManifestFromApk( List cdRecords, DataSource lhfSection) throws IOException, ApkFormatException, ZipFormatException { CentralDirectoryRecord androidManifestCdRecord = null; for (CentralDirectoryRecord cdRecord : cdRecords) { if (ANDROID_MANIFEST_ZIP_ENTRY_NAME.equals(cdRecord.getName())) { androidManifestCdRecord = cdRecord; break; } } if (androidManifestCdRecord == null) { throw new ApkFormatException("Missing " + ANDROID_MANIFEST_ZIP_ENTRY_NAME); } return ByteBuffer.wrap( LocalFileRecord.getUncompressedData( lhfSection, androidManifestCdRecord, lhfSection.size())); } /** * Returns the minimum Android version (API Level) supported by the provided APK. This is based * on the {@code android:minSdkVersion} attributes of the APK's {@code AndroidManifest.xml}. */ private static int getMinSdkVersionFromApk( List cdRecords, DataSource lhfSection) throws IOException, MinSdkVersionException { ByteBuffer androidManifest; try { androidManifest = getAndroidManifestFromApk(cdRecords, lhfSection); } catch (ZipFormatException | ApkFormatException e) { throw new MinSdkVersionException( "Failed to determine APK's minimum supported Android platform version", e); } return ApkUtils.getMinSdkVersionFromBinaryAndroidManifest(androidManifest); } /** * Configuration of a signer. * *

Use {@link Builder} to obtain configuration instances. */ public static class SignerConfig { private final String mName; private final PrivateKey mPrivateKey; private final List mCertificates; private SignerConfig( String name, PrivateKey privateKey, List certificates) { mName = name; mPrivateKey = privateKey; mCertificates = Collections.unmodifiableList(new ArrayList<>(certificates)); } /** * Returns the name of this signer. */ public String getName() { return mName; } /** * Returns the signing key of this signer. */ public PrivateKey getPrivateKey() { return mPrivateKey; } /** * Returns the certificate(s) of this signer. The first certificate's public key corresponds * to this signer's private key. */ public List getCertificates() { return mCertificates; } /** * Builder of {@link SignerConfig} instances. */ public static class Builder { private final String mName; private final PrivateKey mPrivateKey; private final List mCertificates; /** * Constructs a new {@code Builder}. * * @param name signer's name. The name is reflected in the name of files comprising the * JAR signature of the APK. * @param privateKey signing key * @param certificates list of one or more X.509 certificates. The subject public key of * the first certificate must correspond to the {@code privateKey}. */ public Builder( String name, PrivateKey privateKey, List certificates) { if (name.isEmpty()) { throw new IllegalArgumentException("Empty name"); } mName = name; mPrivateKey = privateKey; mCertificates = new ArrayList<>(certificates); } /** * Returns a new {@code SignerConfig} instance configured based on the configuration of * this builder. */ public SignerConfig build() { return new SignerConfig( mName, mPrivateKey, mCertificates); } } } /** * Builder of {@link ApkSigner} instances. * *

The builder requires the following information to construct a working {@code ApkSigner}: *

    *
  • Signer configs or {@link ApkSignerEngine} -- provided in the constructor,
  • *
  • APK to be signed -- see {@link #setInputApk(File) setInputApk} variants,
  • *
  • where to store the signed APK -- see {@link #setOutputApk(File) setOutputApk} variants. *
  • *
*/ public static class Builder { private final List mSignerConfigs; private boolean mV1SigningEnabled = true; private boolean mV2SigningEnabled = true; private boolean mOtherSignersSignaturesPreserved; private String mCreatedBy; private Integer mMinSdkVersion; private final ApkSignerEngine mSignerEngine; private File mInputApkFile; private DataSource mInputApkDataSource; private File mOutputApkFile; private DataSink mOutputApkDataSink; private DataSource mOutputApkDataSource; /** * Constructs a new {@code Builder} for an {@code ApkSigner} which signs using the provided * signer configurations. The resulting signer may be further customized through this * builder's setters, such as {@link #setMinSdkVersion(int)}, * {@link #setV1SigningEnabled(boolean)}, {@link #setV2SigningEnabled(boolean)}, * {@link #setOtherSignersSignaturesPreserved(boolean)}, {@link #setCreatedBy(String)}. * *

{@link #Builder(ApkSignerEngine)} is an alternative for advanced use cases where * more control over low-level details of signing is desired. */ public Builder(List signerConfigs) { if (signerConfigs.isEmpty()) { throw new IllegalArgumentException("At least one signer config must be provided"); } mSignerConfigs = new ArrayList<>(signerConfigs); mSignerEngine = null; } /** * Constructs a new {@code Builder} for an {@code ApkSigner} which signs using the * provided signing engine. This is meant for advanced use cases where more control is * needed over the lower-level details of signing. For typical use cases, * {@link #Builder(List)} is more appropriate. */ public Builder(ApkSignerEngine signerEngine) { if (signerEngine == null) { throw new NullPointerException("signerEngine == null"); } mSignerEngine = signerEngine; mSignerConfigs = null; } /** * Sets the APK to be signed. * * @see #setInputApk(DataSource) */ public Builder setInputApk(File inputApk) { if (inputApk == null) { throw new NullPointerException("inputApk == null"); } mInputApkFile = inputApk; mInputApkDataSource = null; return this; } /** * Sets the APK to be signed. * * @see #setInputApk(File) */ public Builder setInputApk(DataSource inputApk) { if (inputApk == null) { throw new NullPointerException("inputApk == null"); } mInputApkDataSource = inputApk; mInputApkFile = null; return this; } /** * Sets the location of the output (signed) APK. {@code ApkSigner} will create this file if * it doesn't exist. * * @see #setOutputApk(ReadableDataSink) * @see #setOutputApk(DataSink, DataSource) */ public Builder setOutputApk(File outputApk) { if (outputApk == null) { throw new NullPointerException("outputApk == null"); } mOutputApkFile = outputApk; mOutputApkDataSink = null; mOutputApkDataSource = null; return this; } /** * Sets the readable data sink which will receive the output (signed) APK. After signing, * the contents of the output APK will be available via the {@link DataSource} interface of * the sink. * *

This variant of {@code setOutputApk} is useful for avoiding writing the output APK to * a file. For example, an in-memory data sink, such as * {@link DataSinks#newInMemoryDataSink()}, could be used instead of a file. * * @see #setOutputApk(File) * @see #setOutputApk(DataSink, DataSource) */ public Builder setOutputApk(ReadableDataSink outputApk) { if (outputApk == null) { throw new NullPointerException("outputApk == null"); } return setOutputApk(outputApk, outputApk); } /** * Sets the sink which will receive the output (signed) APK. Data received by the * {@code outputApkOut} sink must be visible through the {@code outputApkIn} data source. * *

This is an advanced variant of {@link #setOutputApk(ReadableDataSink)}, enabling the * sink and the source to be different objects. * * @see #setOutputApk(ReadableDataSink) * @see #setOutputApk(File) */ public Builder setOutputApk(DataSink outputApkOut, DataSource outputApkIn) { if (outputApkOut == null) { throw new NullPointerException("outputApkOut == null"); } if (outputApkIn == null) { throw new NullPointerException("outputApkIn == null"); } mOutputApkFile = null; mOutputApkDataSink = outputApkOut; mOutputApkDataSource = outputApkIn; return this; } /** * Sets the minimum Android platform version (API Level) on which APK signatures produced * by the signer being built must verify. This method is useful for overriding the default * behavior where the minimum API Level is obtained from the {@code android:minSdkVersion} * attribute of the APK's {@code AndroidManifest.xml}. * *

Note: This method may result in APK signatures which don't verify on some * Android platform versions supported by the APK. * *

Note: This method may only be invoked when this builder is not initialized * with an {@link ApkSignerEngine}. * * @throws IllegalStateException if this builder was initialized with an * {@link ApkSignerEngine} */ public Builder setMinSdkVersion(int minSdkVersion) { checkInitializedWithoutEngine(); mMinSdkVersion = minSdkVersion; return this; } /** * Sets whether the APK should be signed using JAR signing (aka v1 signature scheme). * *

By default, whether APK is signed using JAR signing is determined by * {@code ApkSigner}, based on the platform versions supported by the APK or specified using * {@link #setMinSdkVersion(int)}. Disabling JAR signing will result in APK signatures which * don't verify on Android Marshmallow (Android 6.0, API Level 23) and lower. * *

Note: This method may only be invoked when this builder is not initialized * with an {@link ApkSignerEngine}. * * @param enabled {@code true} to require the APK to be signed using JAR signing, * {@code false} to require the APK to not be signed using JAR signing. * * @throws IllegalStateException if this builder was initialized with an * {@link ApkSignerEngine} * * @see JAR signing */ public Builder setV1SigningEnabled(boolean enabled) { checkInitializedWithoutEngine(); mV1SigningEnabled = enabled; return this; } /** * Sets whether the APK should be signed using APK Signature Scheme v2 (aka v2 signature * scheme). * *

By default, whether APK is signed using APK Signature Scheme v2 is determined by * {@code ApkSigner} based on the platform versions supported by the APK or specified using * {@link #setMinSdkVersion(int)}. * *

Note: This method may only be invoked when this builder is not initialized * with an {@link ApkSignerEngine}. * * @param enabled {@code true} to require the APK to be signed using APK Signature Scheme * v2, {@code false} to require the APK to not be signed using APK Signature Scheme * v2. * * @throws IllegalStateException if this builder was initialized with an * {@link ApkSignerEngine} * * @see APK Signature Scheme v2 */ public Builder setV2SigningEnabled(boolean enabled) { checkInitializedWithoutEngine(); mV2SigningEnabled = enabled; return this; } /** * Sets whether signatures produced by signers other than the ones configured in this engine * should be copied from the input APK to the output APK. * *

By default, signatures of other signers are omitted from the output APK. * *

Note: This method may only be invoked when this builder is not initialized * with an {@link ApkSignerEngine}. * * @throws IllegalStateException if this builder was initialized with an * {@link ApkSignerEngine} */ public Builder setOtherSignersSignaturesPreserved(boolean preserved) { checkInitializedWithoutEngine(); mOtherSignersSignaturesPreserved = preserved; return this; } /** * Sets the value of the {@code Created-By} field in JAR signature files. * *

Note: This method may only be invoked when this builder is not initialized * with an {@link ApkSignerEngine}. * * @throws IllegalStateException if this builder was initialized with an * {@link ApkSignerEngine} */ public Builder setCreatedBy(String createdBy) { checkInitializedWithoutEngine(); if (createdBy == null) { throw new NullPointerException(); } mCreatedBy = createdBy; return this; } private void checkInitializedWithoutEngine() { if (mSignerEngine != null) { throw new IllegalStateException( "Operation is not available when builder initialized with an engine"); } } /** * Returns a new {@code ApkSigner} instance initialized according to the configuration of * this builder. */ public ApkSigner build() { return new ApkSigner( mSignerConfigs, mMinSdkVersion, mV1SigningEnabled, mV2SigningEnabled, mOtherSignersSignaturesPreserved, mCreatedBy, mSignerEngine, mInputApkFile, mInputApkDataSource, mOutputApkFile, mOutputApkDataSink, mOutputApkDataSource); } } } src/main/java/com/android/apksig/ApkSignerEngine.java0100644 0000000 0000000 00000045574 13243353142 021610 0ustar000000000 0000000 /* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig; import com.android.apksig.apk.ApkFormatException; import com.android.apksig.util.DataSink; import com.android.apksig.util.DataSource; import java.io.Closeable; import java.io.IOException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.SignatureException; import java.util.List; /** * APK signing logic which is independent of how input and output APKs are stored, parsed, and * generated. * *

Operating Model

* * The abstract operating model is that there is an input APK which is being signed, thus producing * an output APK. In reality, there may be just an output APK being built from scratch, or the input * APK and the output APK may be the same file. Because this engine does not deal with reading and * writing files, it can handle all of these scenarios. * *

The engine is stateful and thus cannot be used for signing multiple APKs. However, once * the engine signed an APK, the engine can be used to re-sign the APK after it has been modified. * This may be more efficient than signing the APK using a new instance of the engine. See * Incremental Operation. * *

In the engine's operating model, a signed APK is produced as follows. *

    *
  1. JAR entries to be signed are output,
  2. *
  3. JAR archive is signed using JAR signing, thus adding the so-called v1 signature to the * output,
  4. *
  5. JAR archive is signed using APK Signature Scheme v2, thus adding the so-called v2 signature * to the output.
  6. *
* *

The input APK may contain JAR entries which, depending on the engine's configuration, may or * may not be output (e.g., existing signatures may need to be preserved or stripped) or which the * engine will overwrite as part of signing. The engine thus offers {@link #inputJarEntry(String)} * which tells the client whether the input JAR entry needs to be output. This avoids the need for * the client to hard-code the aspects of APK signing which determine which parts of input must be * ignored. Similarly, the engine offers {@link #inputApkSigningBlock(DataSource)} to help the * client avoid dealing with preserving or stripping APK Signature Scheme v2 signature of the input * APK. * *

To use the engine to sign an input APK (or a collection of JAR entries), follow these * steps: *

    *
  1. Obtain a new instance of the engine -- engine instances are stateful and thus cannot be used * for signing multiple APKs.
  2. *
  3. Locate the input APK's APK Signing Block and provide it to * {@link #inputApkSigningBlock(DataSource)}.
  4. *
  5. For each JAR entry in the input APK, invoke {@link #inputJarEntry(String)} to determine * whether this entry should be output. The engine may request to inspect the entry.
  6. *
  7. For each output JAR entry, invoke {@link #outputJarEntry(String)} which may request to * inspect the entry.
  8. *
  9. Once all JAR entries have been output, invoke {@link #outputJarEntries()} which may request * that additional JAR entries are output. These entries comprise the output APK's JAR * signature.
  10. *
  11. Locate the ZIP Central Directory and ZIP End of Central Directory sections in the output and * invoke {@link #outputZipSections(DataSource, DataSource, DataSource)} which may request that * an APK Signature Block is inserted before the ZIP Central Directory. The block contains the * output APK's APK Signature Scheme v2 signature.
  12. *
  13. Invoke {@link #outputDone()} to signal that the APK was output in full. The engine will * confirm that the output APK is signed.
  14. *
  15. Invoke {@link #close()} to signal that the engine will no longer be used. This lets the * engine free any resources it no longer needs. *
* *

Some invocations of the engine may provide the client with a task to perform. The client is * expected to perform all requested tasks before proceeding to the next stage of signing. See * documentation of each method about the deadlines for performing the tasks requested by the * method. * *

Incremental Operation

* * The engine supports incremental operation where a signed APK is produced, then modified and * re-signed. This may be useful for IDEs, where an app is frequently re-signed after small changes * by the developer. Re-signing may be more efficient than signing from scratch. * *

To use the engine in incremental mode, keep notifying the engine of changes to the APK through * {@link #inputApkSigningBlock(DataSource)}, {@link #inputJarEntry(String)}, * {@link #inputJarEntryRemoved(String)}, {@link #outputJarEntry(String)}, * and {@link #outputJarEntryRemoved(String)}, perform the tasks requested by the engine through * these methods, and, when a new signed APK is desired, run through steps 5 onwards to re-sign the * APK. * *

Output-only Operation

* * The engine's abstract operating model consists of an input APK and an output APK. However, it is * possible to use the engine in output-only mode where the engine's {@code input...} methods are * not invoked. In this mode, the engine has less control over output because it cannot request that * some JAR entries are not output. Nevertheless, the engine will attempt to make the output APK * signed and will report an error if cannot do so. * * @see Application Signing */ public interface ApkSignerEngine extends Closeable { /** * Indicates to this engine that the input APK contains the provided APK Signing Block. The * block may contain signatures of the input APK, such as APK Signature Scheme v2 signatures. * * @param apkSigningBlock APK signing block of the input APK. The provided data source is * guaranteed to not be used by the engine after this method terminates. * * @throws IOException if an I/O error occurs while reading the APK Signing Block * @throws ApkFormatException if the APK Signing Block is malformed * @throws IllegalStateException if this engine is closed */ void inputApkSigningBlock(DataSource apkSigningBlock) throws IOException, ApkFormatException, IllegalStateException; /** * Indicates to this engine that the specified JAR entry was encountered in the input APK. * *

When an input entry is updated/changed, it's OK to not invoke * {@link #inputJarEntryRemoved(String)} before invoking this method. * * @return instructions about how to proceed with this entry * * @throws IllegalStateException if this engine is closed */ InputJarEntryInstructions inputJarEntry(String entryName) throws IllegalStateException; /** * Indicates to this engine that the specified JAR entry was output. * *

It is unnecessary to invoke this method for entries added to output by this engine (e.g., * requested by {@link #outputJarEntries()}) provided the entries were output with exactly the * data requested by the engine. * *

When an already output entry is updated/changed, it's OK to not invoke * {@link #outputJarEntryRemoved(String)} before invoking this method. * * @return request to inspect the entry or {@code null} if the engine does not need to inspect * the entry. The request must be fulfilled before {@link #outputJarEntries()} is * invoked. * * @throws IllegalStateException if this engine is closed */ InspectJarEntryRequest outputJarEntry(String entryName) throws IllegalStateException; /** * Indicates to this engine that the specified JAR entry was removed from the input. It's safe * to invoke this for entries for which {@link #inputJarEntry(String)} hasn't been invoked. * * @return output policy of this JAR entry. The policy indicates how this input entry affects * the output APK. The client of this engine should use this information to determine * how the removal of this input APK's JAR entry affects the output APK. * * @throws IllegalStateException if this engine is closed */ InputJarEntryInstructions.OutputPolicy inputJarEntryRemoved(String entryName) throws IllegalStateException; /** * Indicates to this engine that the specified JAR entry was removed from the output. It's safe * to invoke this for entries for which {@link #outputJarEntry(String)} hasn't been invoked. * * @throws IllegalStateException if this engine is closed */ void outputJarEntryRemoved(String entryName) throws IllegalStateException; /** * Indicates to this engine that all JAR entries have been output. * * @return request to add JAR signature to the output or {@code null} if there is no need to add * a JAR signature. The request will contain additional JAR entries to be output. The * request must be fulfilled before * {@link #outputZipSections(DataSource, DataSource, DataSource)} is invoked. * * @throws ApkFormatException if the APK is malformed in a way which is preventing this engine * from producing a valid signature. For example, if the engine uses the provided * {@code META-INF/MANIFEST.MF} as a template and the file is malformed. * @throws NoSuchAlgorithmException if a signature could not be generated because a required * cryptographic algorithm implementation is missing * @throws InvalidKeyException if a signature could not be generated because a signing key is * not suitable for generating the signature * @throws SignatureException if an error occurred while generating a signature * @throws IllegalStateException if there are unfulfilled requests, such as to inspect some JAR * entries, or if the engine is closed */ OutputJarSignatureRequest outputJarEntries() throws ApkFormatException, NoSuchAlgorithmException, InvalidKeyException, SignatureException, IllegalStateException; /** * Indicates to this engine that the ZIP sections comprising the output APK have been output. * *

The provided data sources are guaranteed to not be used by the engine after this method * terminates. * * @param zipEntries the section of ZIP archive containing Local File Header records and data of * the ZIP entries. In a well-formed archive, this section starts at the start of the * archive and extends all the way to the ZIP Central Directory. * @param zipCentralDirectory ZIP Central Directory section * @param zipEocd ZIP End of Central Directory (EoCD) record * * @return request to add an APK Signing Block to the output or {@code null} if the output must * not contain an APK Signing Block. The request must be fulfilled before * {@link #outputDone()} is invoked. * * @throws IOException if an I/O error occurs while reading the provided ZIP sections * @throws ApkFormatException if the provided APK is malformed in a way which prevents this * engine from producing a valid signature. For example, if the APK Signing Block * provided to the engine is malformed. * @throws NoSuchAlgorithmException if a signature could not be generated because a required * cryptographic algorithm implementation is missing * @throws InvalidKeyException if a signature could not be generated because a signing key is * not suitable for generating the signature * @throws SignatureException if an error occurred while generating a signature * @throws IllegalStateException if there are unfulfilled requests, such as to inspect some JAR * entries or to output JAR signature, or if the engine is closed */ OutputApkSigningBlockRequest outputZipSections( DataSource zipEntries, DataSource zipCentralDirectory, DataSource zipEocd) throws IOException, ApkFormatException, NoSuchAlgorithmException, InvalidKeyException, SignatureException, IllegalStateException; /** * Indicates to this engine that the signed APK was output. * *

This does not change the output APK. The method helps the client confirm that the current * output is signed. * * @throws IllegalStateException if there are unfulfilled requests, such as to inspect some JAR * entries or to output signatures, or if the engine is closed */ void outputDone() throws IllegalStateException; /** * Indicates to this engine that it will no longer be used. Invoking this on an already closed * engine is OK. * *

This does not change the output APK. For example, if the output APK is not yet fully * signed, it will remain so after this method terminates. */ @Override void close(); /** * Instructions about how to handle an input APK's JAR entry. * *

The instructions indicate whether to output the entry (see {@link #getOutputPolicy()}) and * may contain a request to inspect the entry (see {@link #getInspectJarEntryRequest()}), in * which case the request must be fulfilled before {@link ApkSignerEngine#outputJarEntries()} is * invoked. */ public static class InputJarEntryInstructions { private final OutputPolicy mOutputPolicy; private final InspectJarEntryRequest mInspectJarEntryRequest; /** * Constructs a new {@code InputJarEntryInstructions} instance with the provided entry * output policy and without a request to inspect the entry. */ public InputJarEntryInstructions(OutputPolicy outputPolicy) { this(outputPolicy, null); } /** * Constructs a new {@code InputJarEntryInstructions} instance with the provided entry * output mode and with the provided request to inspect the entry. * * @param inspectJarEntryRequest request to inspect the entry or {@code null} if there's no * need to inspect the entry. */ public InputJarEntryInstructions( OutputPolicy outputPolicy, InspectJarEntryRequest inspectJarEntryRequest) { mOutputPolicy = outputPolicy; mInspectJarEntryRequest = inspectJarEntryRequest; } /** * Returns the output policy for this entry. */ public OutputPolicy getOutputPolicy() { return mOutputPolicy; } /** * Returns the request to inspect the JAR entry or {@code null} if there is no need to * inspect the entry. */ public InspectJarEntryRequest getInspectJarEntryRequest() { return mInspectJarEntryRequest; } /** * Output policy for an input APK's JAR entry. */ public static enum OutputPolicy { /** Entry must not be output. */ SKIP, /** Entry should be output. */ OUTPUT, /** Entry will be output by the engine. The client can thus ignore this input entry. */ OUTPUT_BY_ENGINE, } } /** * Request to inspect the specified JAR entry. * *

The entry's uncompressed data must be provided to the data sink returned by * {@link #getDataSink()}. Once the entry's data has been provided to the sink, {@link #done()} * must be invoked. */ interface InspectJarEntryRequest { /** * Returns the data sink into which the entry's uncompressed data should be sent. */ DataSink getDataSink(); /** * Indicates that entry's data has been provided in full. */ void done(); /** * Returns the name of the JAR entry. */ String getEntryName(); } /** * Request to add JAR signature (aka v1 signature) to the output APK. * *

Entries listed in {@link #getAdditionalJarEntries()} must be added to the output APK after * which {@link #done()} must be invoked. */ interface OutputJarSignatureRequest { /** * Returns JAR entries that must be added to the output APK. */ List getAdditionalJarEntries(); /** * Indicates that the JAR entries contained in this request were added to the output APK. */ void done(); /** * JAR entry. */ public static class JarEntry { private final String mName; private final byte[] mData; /** * Constructs a new {@code JarEntry} with the provided name and data. * * @param data uncompressed data of the entry. Changes to this array will not be * reflected in {@link #getData()}. */ public JarEntry(String name, byte[] data) { mName = name; mData = data.clone(); } /** * Returns the name of this ZIP entry. */ public String getName() { return mName; } /** * Returns the uncompressed data of this JAR entry. */ public byte[] getData() { return mData.clone(); } } } /** * Request to add the specified APK Signing Block to the output APK. APK Signature Scheme v2 * signature(s) of the APK are contained in this block. * *

The APK Signing Block returned by {@link #getApkSigningBlock()} must be placed into the * output APK such that the block is immediately before the ZIP Central Directory, the offset of * ZIP Central Directory in the ZIP End of Central Directory record must be adjusted * accordingly, and then {@link #done()} must be invoked. * *

If the output contains an APK Signing Block, that block must be replaced by the block * contained in this request. */ interface OutputApkSigningBlockRequest { /** * Returns the APK Signing Block. */ byte[] getApkSigningBlock(); /** * Indicates that the APK Signing Block was output as requested. */ void done(); } } src/main/java/com/android/apksig/ApkVerifier.java0100644 0000000 0000000 00000151644 13243353142 021002 0ustar000000000 0000000 /* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig; import com.android.apksig.apk.ApkFormatException; import com.android.apksig.apk.ApkUtils; import com.android.apksig.internal.apk.AndroidBinXmlParser; import com.android.apksig.internal.apk.v1.V1SchemeVerifier; import com.android.apksig.internal.apk.v2.ContentDigestAlgorithm; import com.android.apksig.internal.apk.v2.SignatureAlgorithm; import com.android.apksig.internal.apk.v2.V2SchemeVerifier; import com.android.apksig.internal.util.AndroidSdkVersion; import com.android.apksig.internal.zip.CentralDirectoryRecord; import com.android.apksig.util.DataSource; import com.android.apksig.util.DataSources; import com.android.apksig.zip.ZipFormatException; import java.io.Closeable; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; /** * APK signature verifier which mimics the behavior of the Android platform. * *

The verifier is designed to closely mimic the behavior of Android platforms. This is to enable * the verifier to be used for checking whether an APK's signatures will verify on Android. * *

Use {@link Builder} to obtain instances of this verifier. * * @see Application Signing */ public class ApkVerifier { private static final int APK_SIGNATURE_SCHEME_V2_ID = 2; private static final Map SUPPORTED_APK_SIG_SCHEME_NAMES = Collections.singletonMap(APK_SIGNATURE_SCHEME_V2_ID, "APK Signature Scheme v2"); private final File mApkFile; private final DataSource mApkDataSource; private final Integer mMinSdkVersion; private final int mMaxSdkVersion; private ApkVerifier( File apkFile, DataSource apkDataSource, Integer minSdkVersion, int maxSdkVersion) { mApkFile = apkFile; mApkDataSource = apkDataSource; mMinSdkVersion = minSdkVersion; mMaxSdkVersion = maxSdkVersion; } /** * Verifies the APK's signatures and returns the result of verification. The APK can be * considered verified iff the result's {@link Result#isVerified()} returns {@code true}. * The verification result also includes errors, warnings, and information about signers. * * @throws IOException if an I/O error is encountered while reading the APK * @throws ApkFormatException if the APK is malformed * @throws NoSuchAlgorithmException if the APK's signatures cannot be verified because a * required cryptographic algorithm implementation is missing * @throws IllegalStateException if this verifier's configuration is missing required * information. */ public Result verify() throws IOException, ApkFormatException, NoSuchAlgorithmException, IllegalStateException { Closeable in = null; try { DataSource apk; if (mApkDataSource != null) { apk = mApkDataSource; } else if (mApkFile != null) { RandomAccessFile f = new RandomAccessFile(mApkFile, "r"); in = f; apk = DataSources.asDataSource(f, 0, f.length()); } else { throw new IllegalStateException("APK not provided"); } return verify(apk); } finally { if (in != null) { in.close(); } } } /** * Verifies the APK's signatures and returns the result of verification. The APK can be * considered verified iff the result's {@link Result#isVerified()} returns {@code true}. * The verification result also includes errors, warnings, and information about signers. * * @param apk APK file contents * * @throws IOException if an I/O error is encountered while reading the APK * @throws ApkFormatException if the APK is malformed * @throws NoSuchAlgorithmException if the APK's signatures cannot be verified because a * required cryptographic algorithm implementation is missing */ private Result verify(DataSource apk) throws IOException, ApkFormatException, NoSuchAlgorithmException { if (mMinSdkVersion != null) { if (mMinSdkVersion < 0) { throw new IllegalArgumentException( "minSdkVersion must not be negative: " + mMinSdkVersion); } if ((mMinSdkVersion != null) && (mMinSdkVersion > mMaxSdkVersion)) { throw new IllegalArgumentException( "minSdkVersion (" + mMinSdkVersion + ") > maxSdkVersion (" + mMaxSdkVersion + ")"); } } int maxSdkVersion = mMaxSdkVersion; ApkUtils.ZipSections zipSections; try { zipSections = ApkUtils.findZipSections(apk); } catch (ZipFormatException e) { throw new ApkFormatException("Malformed APK: not a ZIP archive", e); } Result result = new Result(); // Android N and newer attempts to verify APK Signature Scheme v2 signature of the APK. // If the signature is not found, it falls back to JAR signature verification. If the // signature is found but does not verify, the APK is rejected. Set foundApkSigSchemeIds; if (maxSdkVersion >= AndroidSdkVersion.N) { foundApkSigSchemeIds = new HashSet<>(1); try { V2SchemeVerifier.Result v2Result = V2SchemeVerifier.verify(apk, zipSections); foundApkSigSchemeIds.add(APK_SIGNATURE_SCHEME_V2_ID); result.mergeFrom(v2Result); } catch (V2SchemeVerifier.SignatureNotFoundException ignored) {} if (result.containsErrors()) { return result; } } else { foundApkSigSchemeIds = Collections.emptySet(); } ByteBuffer androidManifest = null; int minSdkVersion; if (mMinSdkVersion != null) { // No need to obtain minSdkVersion from the APK's AndroidManifest.xml minSdkVersion = mMinSdkVersion; } else { // Need to obtain minSdkVersion from the APK's AndroidManifest.xml if (androidManifest == null) { androidManifest = getAndroidManifestFromApk(apk, zipSections); } minSdkVersion = ApkUtils.getMinSdkVersionFromBinaryAndroidManifest(androidManifest.slice()); if (minSdkVersion > mMaxSdkVersion) { throw new IllegalArgumentException( "minSdkVersion from APK (" + minSdkVersion + ") > maxSdkVersion (" + mMaxSdkVersion + ")"); } } // Android O and newer requires that APKs targeting security sandbox version 2 and higher // are signed using APK Signature Scheme v2 or newer. if (maxSdkVersion >= AndroidSdkVersion.O) { if (androidManifest == null) { androidManifest = getAndroidManifestFromApk(apk, zipSections); } int targetSandboxVersion = getTargetSandboxVersionFromBinaryAndroidManifest(androidManifest.slice()); if (targetSandboxVersion > 1) { if (foundApkSigSchemeIds.isEmpty()) { result.addError( Issue.NO_SIG_FOR_TARGET_SANDBOX_VERSION, targetSandboxVersion); } } } // Attempt to verify the APK using JAR signing if necessary. Platforms prior to Android N // ignore APK Signature Scheme v2 signatures and always attempt to verify JAR signatures. // Android N onwards verifies JAR signatures only if no APK Signature Scheme v2 (or newer // scheme) signatures were found. if ((minSdkVersion < AndroidSdkVersion.N) || (foundApkSigSchemeIds.isEmpty())) { V1SchemeVerifier.Result v1Result = V1SchemeVerifier.verify( apk, zipSections, SUPPORTED_APK_SIG_SCHEME_NAMES, foundApkSigSchemeIds, minSdkVersion, maxSdkVersion); result.mergeFrom(v1Result); } if (result.containsErrors()) { return result; } // Check whether v1 and v2 scheme signer identifies match, provided both v1 and v2 // signatures verified. if ((result.isVerifiedUsingV1Scheme()) && (result.isVerifiedUsingV2Scheme())) { ArrayList v1Signers = new ArrayList<>(result.getV1SchemeSigners()); ArrayList v2Signers = new ArrayList<>(result.getV2SchemeSigners()); ArrayList v1SignerCerts = new ArrayList<>(); ArrayList v2SignerCerts = new ArrayList<>(); for (Result.V1SchemeSignerInfo signer : v1Signers) { try { v1SignerCerts.add(new ByteArray(signer.getCertificate().getEncoded())); } catch (CertificateEncodingException e) { throw new RuntimeException( "Failed to encode JAR signer " + signer.getName() + " certs", e); } } for (Result.V2SchemeSignerInfo signer : v2Signers) { try { v2SignerCerts.add(new ByteArray(signer.getCertificate().getEncoded())); } catch (CertificateEncodingException e) { throw new RuntimeException( "Failed to encode APK Signature Scheme v2 signer (index: " + signer.getIndex() + ") certs", e); } } for (int i = 0; i < v1SignerCerts.size(); i++) { ByteArray v1Cert = v1SignerCerts.get(i); if (!v2SignerCerts.contains(v1Cert)) { Result.V1SchemeSignerInfo v1Signer = v1Signers.get(i); v1Signer.addError(Issue.V2_SIG_MISSING); break; } } for (int i = 0; i < v2SignerCerts.size(); i++) { ByteArray v2Cert = v2SignerCerts.get(i); if (!v1SignerCerts.contains(v2Cert)) { Result.V2SchemeSignerInfo v2Signer = v2Signers.get(i); v2Signer.addError(Issue.JAR_SIG_MISSING); break; } } } if (result.containsErrors()) { return result; } // Verified result.setVerified(); if (result.isVerifiedUsingV2Scheme()) { for (Result.V2SchemeSignerInfo signerInfo : result.getV2SchemeSigners()) { result.addSignerCertificate(signerInfo.getCertificate()); } } else if (result.isVerifiedUsingV1Scheme()) { for (Result.V1SchemeSignerInfo signerInfo : result.getV1SchemeSigners()) { result.addSignerCertificate(signerInfo.getCertificate()); } } else { throw new RuntimeException( "APK considered verified, but has not verified using either v1 or v2 schemes"); } return result; } private static ByteBuffer getAndroidManifestFromApk( DataSource apk, ApkUtils.ZipSections zipSections) throws IOException, ApkFormatException { List cdRecords = V1SchemeVerifier.parseZipCentralDirectory(apk, zipSections); try { return ApkSigner.getAndroidManifestFromApk( cdRecords, apk.slice(0, zipSections.getZipCentralDirectoryOffset())); } catch (ZipFormatException e) { throw new ApkFormatException("Failed to read AndroidManifest.xml", e); } } /** * Android resource ID of the {@code android:targetSandboxVersion} attribute in * AndroidManifest.xml. */ private static final int TARGET_SANDBOX_VERSION_ATTR_ID = 0x0101054c; /** * Returns the security sandbox version targeted by an APK with the provided * {@code AndroidManifest.xml}. * * @param androidManifestContents contents of {@code AndroidManifest.xml} in binary Android * resource format * * @throws ApkFormatException if an error occurred while determining the version */ private static int getTargetSandboxVersionFromBinaryAndroidManifest( ByteBuffer androidManifestContents) throws ApkFormatException { // Return the value of the android:targetSandboxVersion attribute of the top-level manifest // element try { AndroidBinXmlParser parser = new AndroidBinXmlParser(androidManifestContents); int eventType = parser.getEventType(); while (eventType != AndroidBinXmlParser.EVENT_END_DOCUMENT) { if ((eventType == AndroidBinXmlParser.EVENT_START_ELEMENT) && (parser.getDepth() == 1) && ("manifest".equals(parser.getName())) && (parser.getNamespace().isEmpty())) { // In each manifest element, targetSandboxVersion defaults to 1 int result = 1; for (int i = 0; i < parser.getAttributeCount(); i++) { if (parser.getAttributeNameResourceId(i) == TARGET_SANDBOX_VERSION_ATTR_ID) { int valueType = parser.getAttributeValueType(i); switch (valueType) { case AndroidBinXmlParser.VALUE_TYPE_INT: result = parser.getAttributeIntValue(i); break; default: throw new ApkFormatException( "Failed to determine APK's target sandbox version" + ": unsupported value type of" + " AndroidManifest.xml" + " android:targetSandboxVersion" + ". Only integer values supported."); } break; } } return result; } eventType = parser.next(); } throw new ApkFormatException( "Failed to determine APK's target sandbox version" + " : no manifest element in AndroidManifest.xml"); } catch (AndroidBinXmlParser.XmlParserException e) { throw new ApkFormatException( "Failed to determine APK's target sandbox version" + ": malformed AndroidManifest.xml", e); } } /** * Result of verifying an APKs signatures. The APK can be considered verified iff * {@link #isVerified()} returns {@code true}. */ public static class Result { private final List mErrors = new ArrayList<>(); private final List mWarnings = new ArrayList<>(); private final List mSignerCerts = new ArrayList<>(); private final List mV1SchemeSigners = new ArrayList<>(); private final List mV1SchemeIgnoredSigners = new ArrayList<>(); private final List mV2SchemeSigners = new ArrayList<>(); private boolean mVerified; private boolean mVerifiedUsingV1Scheme; private boolean mVerifiedUsingV2Scheme; /** * Returns {@code true} if the APK's signatures verified. */ public boolean isVerified() { return mVerified; } private void setVerified() { mVerified = true; } /** * Returns {@code true} if the APK's JAR signatures verified. */ public boolean isVerifiedUsingV1Scheme() { return mVerifiedUsingV1Scheme; } /** * Returns {@code true} if the APK's APK Signature Scheme v2 signatures verified. */ public boolean isVerifiedUsingV2Scheme() { return mVerifiedUsingV2Scheme; } /** * Returns the verified signers' certificates, one per signer. */ public List getSignerCertificates() { return mSignerCerts; } private void addSignerCertificate(X509Certificate cert) { mSignerCerts.add(cert); } /** * Returns information about JAR signers associated with the APK's signature. These are the * signers used by Android. * * @see #getV1SchemeIgnoredSigners() */ public List getV1SchemeSigners() { return mV1SchemeSigners; } /** * Returns information about JAR signers ignored by the APK's signature verification * process. These signers are ignored by Android. However, each signer's errors or warnings * will contain information about why they are ignored. * * @see #getV1SchemeSigners() */ public List getV1SchemeIgnoredSigners() { return mV1SchemeIgnoredSigners; } /** * Returns information about APK Signature Scheme v2 signers associated with the APK's * signature. */ public List getV2SchemeSigners() { return mV2SchemeSigners; } void addError(Issue msg, Object... parameters) { mErrors.add(new IssueWithParams(msg, parameters)); } /** * Returns errors encountered while verifying the APK's signatures. */ public List getErrors() { return mErrors; } /** * Returns warnings encountered while verifying the APK's signatures. */ public List getWarnings() { return mWarnings; } private void mergeFrom(V1SchemeVerifier.Result source) { mVerifiedUsingV1Scheme = source.verified; mErrors.addAll(source.getErrors()); mWarnings.addAll(source.getWarnings()); for (V1SchemeVerifier.Result.SignerInfo signer : source.signers) { mV1SchemeSigners.add(new V1SchemeSignerInfo(signer)); } for (V1SchemeVerifier.Result.SignerInfo signer : source.ignoredSigners) { mV1SchemeIgnoredSigners.add(new V1SchemeSignerInfo(signer)); } } private void mergeFrom(V2SchemeVerifier.Result source) { mVerifiedUsingV2Scheme = source.verified; mErrors.addAll(source.getErrors()); mWarnings.addAll(source.getWarnings()); for (V2SchemeVerifier.Result.SignerInfo signer : source.signers) { mV2SchemeSigners.add(new V2SchemeSignerInfo(signer)); } } /** * Returns {@code true} if an error was encountered while verifying the APK. Any error * prevents the APK from being considered verified. */ public boolean containsErrors() { if (!mErrors.isEmpty()) { return true; } if (!mV1SchemeSigners.isEmpty()) { for (V1SchemeSignerInfo signer : mV1SchemeSigners) { if (signer.containsErrors()) { return true; } } } if (!mV2SchemeSigners.isEmpty()) { for (V2SchemeSignerInfo signer : mV2SchemeSigners) { if (signer.containsErrors()) { return true; } } } return false; } /** * Information about a JAR signer associated with the APK's signature. */ public static class V1SchemeSignerInfo { private final String mName; private final List mCertChain; private final String mSignatureBlockFileName; private final String mSignatureFileName; private final List mErrors; private final List mWarnings; private V1SchemeSignerInfo(V1SchemeVerifier.Result.SignerInfo result) { mName = result.name; mCertChain = result.certChain; mSignatureBlockFileName = result.signatureBlockFileName; mSignatureFileName = result.signatureFileName; mErrors = result.getErrors(); mWarnings = result.getWarnings(); } /** * Returns a user-friendly name of the signer. */ public String getName() { return mName; } /** * Returns the name of the JAR entry containing this signer's JAR signature block file. */ public String getSignatureBlockFileName() { return mSignatureBlockFileName; } /** * Returns the name of the JAR entry containing this signer's JAR signature file. */ public String getSignatureFileName() { return mSignatureFileName; } /** * Returns this signer's signing certificate or {@code null} if not available. The * certificate is guaranteed to be available if no errors were encountered during * verification (see {@link #containsErrors()}. * *

This certificate contains the signer's public key. */ public X509Certificate getCertificate() { return mCertChain.isEmpty() ? null : mCertChain.get(0); } /** * Returns the certificate chain for the signer's public key. The certificate containing * the public key is first, followed by the certificate (if any) which issued the * signing certificate, and so forth. An empty list may be returned if an error was * encountered during verification (see {@link #containsErrors()}). */ public List getCertificateChain() { return mCertChain; } /** * Returns {@code true} if an error was encountered while verifying this signer's JAR * signature. Any error prevents the signer's signature from being considered verified. */ public boolean containsErrors() { return !mErrors.isEmpty(); } /** * Returns errors encountered while verifying this signer's JAR signature. Any error * prevents the signer's signature from being considered verified. */ public List getErrors() { return mErrors; } /** * Returns warnings encountered while verifying this signer's JAR signature. Warnings * do not prevent the signer's signature from being considered verified. */ public List getWarnings() { return mWarnings; } private void addError(Issue msg, Object... parameters) { mErrors.add(new IssueWithParams(msg, parameters)); } } /** * Information about an APK Signature Scheme v2 signer associated with the APK's signature. */ public static class V2SchemeSignerInfo { private final int mIndex; private final List mCerts; private final List mErrors; private final List mWarnings; private V2SchemeSignerInfo(V2SchemeVerifier.Result.SignerInfo result) { mIndex = result.index; mCerts = result.certs; mErrors = result.getErrors(); mWarnings = result.getWarnings(); } /** * Returns this signer's {@code 0}-based index in the list of signers contained in the * APK's APK Signature Scheme v2 signature. */ public int getIndex() { return mIndex; } /** * Returns this signer's signing certificate or {@code null} if not available. The * certificate is guaranteed to be available if no errors were encountered during * verification (see {@link #containsErrors()}. * *

This certificate contains the signer's public key. */ public X509Certificate getCertificate() { return mCerts.isEmpty() ? null : mCerts.get(0); } /** * Returns this signer's certificates. The first certificate is for the signer's public * key. An empty list may be returned if an error was encountered during verification * (see {@link #containsErrors()}). */ public List getCertificates() { return mCerts; } private void addError(Issue msg, Object... parameters) { mErrors.add(new IssueWithParams(msg, parameters)); } public boolean containsErrors() { return !mErrors.isEmpty(); } public List getErrors() { return mErrors; } public List getWarnings() { return mWarnings; } } } /** * Error or warning encountered while verifying an APK's signatures. */ public static enum Issue { /** * APK is not JAR-signed. */ JAR_SIG_NO_SIGNATURES("No JAR signatures"), /** * APK does not contain any entries covered by JAR signatures. */ JAR_SIG_NO_SIGNED_ZIP_ENTRIES("No JAR entries covered by JAR signatures"), /** * APK contains multiple entries with the same name. * *

    *
  • Parameter 1: name ({@code String})
  • *
*/ JAR_SIG_DUPLICATE_ZIP_ENTRY("Duplicate entry: %1$s"), /** * JAR manifest contains a section with a duplicate name. * *
    *
  • Parameter 1: section name ({@code String})
  • *
*/ JAR_SIG_DUPLICATE_MANIFEST_SECTION("Duplicate section in META-INF/MANIFEST.MF: %1$s"), /** * JAR manifest contains a section without a name. * *
    *
  • Parameter 1: section index (1-based) ({@code Integer})
  • *
*/ JAR_SIG_UNNNAMED_MANIFEST_SECTION( "Malformed META-INF/MANIFEST.MF: invidual section #%1$d does not have a name"), /** * JAR signature file contains a section without a name. * *
    *
  • Parameter 1: signature file name ({@code String})
  • *
  • Parameter 2: section index (1-based) ({@code Integer})
  • *
*/ JAR_SIG_UNNNAMED_SIG_FILE_SECTION( "Malformed %1$s: invidual section #%2$d does not have a name"), /** APK is missing the JAR manifest entry (META-INF/MANIFEST.MF). */ JAR_SIG_NO_MANIFEST("Missing META-INF/MANIFEST.MF"), /** * JAR manifest references an entry which is not there in the APK. * *
    *
  • Parameter 1: entry name ({@code String})
  • *
*/ JAR_SIG_MISSING_ZIP_ENTRY_REFERENCED_IN_MANIFEST( "%1$s entry referenced by META-INF/MANIFEST.MF not found in the APK"), /** * JAR manifest does not list a digest for the specified entry. * *
    *
  • Parameter 1: entry name ({@code String})
  • *
*/ JAR_SIG_NO_ZIP_ENTRY_DIGEST_IN_MANIFEST("No digest for %1$s in META-INF/MANIFEST.MF"), /** * JAR signature does not list a digest for the specified entry. * *
    *
  • Parameter 1: entry name ({@code String})
  • *
  • Parameter 2: signature file name ({@code String})
  • *
*/ JAR_SIG_NO_ZIP_ENTRY_DIGEST_IN_SIG_FILE("No digest for %1$s in %2$s"), /** * The specified JAR entry is not covered by JAR signature. * *
    *
  • Parameter 1: entry name ({@code String})
  • *
*/ JAR_SIG_ZIP_ENTRY_NOT_SIGNED("%1$s entry not signed"), /** * JAR signature uses different set of signers to protect the two specified ZIP entries. * *
    *
  • Parameter 1: first entry name ({@code String})
  • *
  • Parameter 2: first entry signer names ({@code List})
  • *
  • Parameter 3: second entry name ({@code String})
  • *
  • Parameter 4: second entry signer names ({@code List})
  • *
*/ JAR_SIG_ZIP_ENTRY_SIGNERS_MISMATCH( "Entries %1$s and %3$s are signed with different sets of signers" + " : <%2$s> vs <%4$s>"), /** * Digest of the specified ZIP entry's data does not match the digest expected by the JAR * signature. * *
    *
  • Parameter 1: entry name ({@code String})
  • *
  • Parameter 2: digest algorithm (e.g., SHA-256) ({@code String})
  • *
  • Parameter 3: name of the entry in which the expected digest is specified * ({@code String})
  • *
  • Parameter 4: base64-encoded actual digest ({@code String})
  • *
  • Parameter 5: base64-encoded expected digest ({@code String})
  • *
*/ JAR_SIG_ZIP_ENTRY_DIGEST_DID_NOT_VERIFY( "%2$s digest of %1$s does not match the digest specified in %3$s" + ". Expected: <%5$s>, actual: <%4$s>"), /** * Digest of the JAR manifest main section did not verify. * *
    *
  • Parameter 1: digest algorithm (e.g., SHA-256) ({@code String})
  • *
  • Parameter 2: name of the entry in which the expected digest is specified * ({@code String})
  • *
  • Parameter 3: base64-encoded actual digest ({@code String})
  • *
  • Parameter 4: base64-encoded expected digest ({@code String})
  • *
*/ JAR_SIG_MANIFEST_MAIN_SECTION_DIGEST_DID_NOT_VERIFY( "%1$s digest of META-INF/MANIFEST.MF main section does not match the digest" + " specified in %2$s. Expected: <%4$s>, actual: <%3$s>"), /** * Digest of the specified JAR manifest section does not match the digest expected by the * JAR signature. * *
    *
  • Parameter 1: section name ({@code String})
  • *
  • Parameter 2: digest algorithm (e.g., SHA-256) ({@code String})
  • *
  • Parameter 3: name of the signature file in which the expected digest is specified * ({@code String})
  • *
  • Parameter 4: base64-encoded actual digest ({@code String})
  • *
  • Parameter 5: base64-encoded expected digest ({@code String})
  • *
*/ JAR_SIG_MANIFEST_SECTION_DIGEST_DID_NOT_VERIFY( "%2$s digest of META-INF/MANIFEST.MF section for %1$s does not match the digest" + " specified in %3$s. Expected: <%5$s>, actual: <%4$s>"), /** * JAR signature file does not contain the whole-file digest of the JAR manifest file. The * digest speeds up verification of JAR signature. * *
    *
  • Parameter 1: name of the signature file ({@code String})
  • *
*/ JAR_SIG_NO_MANIFEST_DIGEST_IN_SIG_FILE( "%1$s does not specify digest of META-INF/MANIFEST.MF" + ". This slows down verification."), /** * APK is signed using APK Signature Scheme v2 or newer, but JAR signature file does not * contain protections against stripping of these newer scheme signatures. * *
    *
  • Parameter 1: name of the signature file ({@code String})
  • *
*/ JAR_SIG_NO_APK_SIG_STRIP_PROTECTION( "APK is signed using APK Signature Scheme v2 but these signatures may be stripped" + " without being detected because %1$s does not contain anti-stripping" + " protections."), /** * JAR signature of the signer is missing a file/entry. * *
    *
  • Parameter 1: name of the encountered file ({@code String})
  • *
  • Parameter 2: name of the missing file ({@code String})
  • *
*/ JAR_SIG_MISSING_FILE("Partial JAR signature. Found: %1$s, missing: %2$s"), /** * An exception was encountered while verifying JAR signature contained in a signature block * against the signature file. * *
    *
  • Parameter 1: name of the signature block file ({@code String})
  • *
  • Parameter 2: name of the signature file ({@code String})
  • *
  • Parameter 3: exception ({@code Throwable})
  • *
*/ JAR_SIG_VERIFY_EXCEPTION("Failed to verify JAR signature %1$s against %2$s: %3$s"), /** * JAR signature contains unsupported digest algorithm. * *
    *
  • Parameter 1: name of the signature block file ({@code String})
  • *
  • Parameter 2: digest algorithm OID ({@code String})
  • *
  • Parameter 3: signature algorithm OID ({@code String})
  • *
  • Parameter 4: API Levels on which this combination of algorithms is not supported * ({@code String})
  • *
  • Parameter 5: user-friendly variant of digest algorithm ({@code String})
  • *
  • Parameter 6: user-friendly variant of signature algorithm ({@code String})
  • *
*/ JAR_SIG_UNSUPPORTED_SIG_ALG( "JAR signature %1$s uses digest algorithm %5$s and signature algorithm %6$s which" + " is not supported on API Level(s) %4$s for which this APK is being" + " verified"), /** * An exception was encountered while parsing JAR signature contained in a signature block. * *
    *
  • Parameter 1: name of the signature block file ({@code String})
  • *
  • Parameter 2: exception ({@code Throwable})
  • *
*/ JAR_SIG_PARSE_EXCEPTION("Failed to parse JAR signature %1$s: %2$s"), /** * An exception was encountered while parsing a certificate contained in the JAR signature * block. * *
    *
  • Parameter 1: name of the signature block file ({@code String})
  • *
  • Parameter 2: exception ({@code Throwable})
  • *
*/ JAR_SIG_MALFORMED_CERTIFICATE("Malformed certificate in JAR signature %1$s: %2$s"), /** * JAR signature contained in a signature block file did not verify against the signature * file. * *
    *
  • Parameter 1: name of the signature block file ({@code String})
  • *
  • Parameter 2: name of the signature file ({@code String})
  • *
*/ JAR_SIG_DID_NOT_VERIFY("JAR signature %1$s did not verify against %2$s"), /** * JAR signature contains no verified signers. * *
    *
  • Parameter 1: name of the signature block file ({@code String})
  • *
*/ JAR_SIG_NO_SIGNERS("JAR signature %1$s contains no signers"), /** * JAR signature file contains a section with a duplicate name. * *
    *
  • Parameter 1: signature file name ({@code String})
  • *
  • Parameter 1: section name ({@code String})
  • *
*/ JAR_SIG_DUPLICATE_SIG_FILE_SECTION("Duplicate section in %1$s: %2$s"), /** * JAR signature file's main section doesn't contain the mandatory Signature-Version * attribute. * *
    *
  • Parameter 1: signature file name ({@code String})
  • *
*/ JAR_SIG_MISSING_VERSION_ATTR_IN_SIG_FILE( "Malformed %1$s: missing Signature-Version attribute"), /** * JAR signature file references an unknown APK signature scheme ID. * *
    *
  • Parameter 1: name of the signature file ({@code String})
  • *
  • Parameter 2: unknown APK signature scheme ID ({@code} Integer)
  • *
*/ JAR_SIG_UNKNOWN_APK_SIG_SCHEME_ID( "JAR signature %1$s references unknown APK signature scheme ID: %2$d"), /** * JAR signature file indicates that the APK is supposed to be signed with a supported APK * signature scheme (in addition to the JAR signature) but no such signature was found in * the APK. * *
    *
  • Parameter 1: name of the signature file ({@code String})
  • *
  • Parameter 2: APK signature scheme ID ({@code} Integer)
  • *
  • Parameter 3: APK signature scheme English name ({@code} String)
  • *
*/ JAR_SIG_MISSING_APK_SIG_REFERENCED( "JAR signature %1$s indicates the APK is signed using %3$s but no such signature" + " was found. Signature stripped?"), /** * JAR entry is not covered by signature and thus unauthorized modifications to its contents * will not be detected. * *
    *
  • Parameter 1: entry name ({@code String})
  • *
*/ JAR_SIG_UNPROTECTED_ZIP_ENTRY( "%1$s not protected by signature. Unauthorized modifications to this JAR entry" + " will not be detected. Delete or move the entry outside of META-INF/."), /** * APK which is both JAR-signed and signed using APK Signature Scheme v2 contains an APK * Signature Scheme v2 signature from this signer, but does not contain a JAR signature * from this signer. */ JAR_SIG_MISSING("No JAR signature from this signer"), /** * APK is targeting a sandbox version which requires APK Signature Scheme v2 signature but * no such signature was found. * *
    *
  • Parameter 1: target sandbox version ({@code Integer})
  • *
*/ NO_SIG_FOR_TARGET_SANDBOX_VERSION( "Missing APK Signature Scheme v2 signature required for target sandbox version" + " %1$d"), /** * APK which is both JAR-signed and signed using APK Signature Scheme v2 contains a JAR * signature from this signer, but does not contain an APK Signature Scheme v2 signature * from this signer. */ V2_SIG_MISSING("No APK Signature Scheme v2 signature from this signer"), /** * Failed to parse the list of signers contained in the APK Signature Scheme v2 signature. */ V2_SIG_MALFORMED_SIGNERS("Malformed list of signers"), /** * Failed to parse this signer's signer block contained in the APK Signature Scheme v2 * signature. */ V2_SIG_MALFORMED_SIGNER("Malformed signer block"), /** * Public key embedded in the APK Signature Scheme v2 signature of this signer could not be * parsed. * *
    *
  • Parameter 1: error details ({@code Throwable})
  • *
*/ V2_SIG_MALFORMED_PUBLIC_KEY("Malformed public key: %1$s"), /** * This APK Signature Scheme v2 signer's certificate could not be parsed. * *
    *
  • Parameter 1: index ({@code 0}-based) of the certificate in the signer's list of * certificates ({@code Integer})
  • *
  • Parameter 2: sequence number ({@code 1}-based) of the certificate in the signer's * list of certificates ({@code Integer})
  • *
  • Parameter 3: error details ({@code Throwable})
  • *
*/ V2_SIG_MALFORMED_CERTIFICATE("Malformed certificate #%2$d: %3$s"), /** * Failed to parse this signer's signature record contained in the APK Signature Scheme v2 * signature. * *
    *
  • Parameter 1: record number (first record is {@code 1}) ({@code Integer})
  • *
*/ V2_SIG_MALFORMED_SIGNATURE("Malformed APK Signature Scheme v2 signature record #%1$d"), /** * Failed to parse this signer's digest record contained in the APK Signature Scheme v2 * signature. * *
    *
  • Parameter 1: record number (first record is {@code 1}) ({@code Integer})
  • *
*/ V2_SIG_MALFORMED_DIGEST("Malformed APK Signature Scheme v2 digest record #%1$d"), /** * This APK Signature Scheme v2 signer contains a malformed additional attribute. * *
    *
  • Parameter 1: attribute number (first attribute is {@code 1}) {@code Integer})
  • *
*/ V2_SIG_MALFORMED_ADDITIONAL_ATTRIBUTE("Malformed additional attribute #%1$d"), /** * APK Signature Scheme v2 signature contains no signers. */ V2_SIG_NO_SIGNERS("No signers in APK Signature Scheme v2 signature"), /** * This APK Signature Scheme v2 signer contains a signature produced using an unknown * algorithm. * *
    *
  • Parameter 1: algorithm ID ({@code Integer})
  • *
*/ V2_SIG_UNKNOWN_SIG_ALGORITHM("Unknown signature algorithm: %1$#x"), /** * This APK Signature Scheme v2 signer contains an unknown additional attribute. * *
    *
  • Parameter 1: attribute ID ({@code Integer})
  • *
*/ V2_SIG_UNKNOWN_ADDITIONAL_ATTRIBUTE("Unknown additional attribute: ID %1$#x"), /** * An exception was encountered while verifying APK Signature Scheme v2 signature of this * signer. * *
    *
  • Parameter 1: signature algorithm ({@link SignatureAlgorithm})
  • *
  • Parameter 2: exception ({@code Throwable})
  • *
*/ V2_SIG_VERIFY_EXCEPTION("Failed to verify %1$s signature: %2$s"), /** * APK Signature Scheme v2 signature over this signer's signed-data block did not verify. * *
    *
  • Parameter 1: signature algorithm ({@link SignatureAlgorithm})
  • *
*/ V2_SIG_DID_NOT_VERIFY("%1$s signature over signed-data did not verify"), /** * This APK Signature Scheme v2 signer offers no signatures. */ V2_SIG_NO_SIGNATURES("No signatures"), /** * This APK Signature Scheme v2 signer offers signatures but none of them are supported. */ V2_SIG_NO_SUPPORTED_SIGNATURES("No supported signatures"), /** * This APK Signature Scheme v2 signer offers no certificates. */ V2_SIG_NO_CERTIFICATES("No certificates"), /** * This APK Signature Scheme v2 signer's public key listed in the signer's certificate does * not match the public key listed in the signatures record. * *
    *
  • Parameter 1: hex-encoded public key from certificate ({@code String})
  • *
  • Parameter 2: hex-encoded public key from signatures record ({@code String})
  • *
*/ V2_SIG_PUBLIC_KEY_MISMATCH_BETWEEN_CERTIFICATE_AND_SIGNATURES_RECORD( "Public key mismatch between certificate and signature record: <%1$s> vs <%2$s>"), /** * This APK Signature Scheme v2 signer's signature algorithms listed in the signatures * record do not match the signature algorithms listed in the signatures record. * *
    *
  • Parameter 1: signature algorithms from signatures record ({@code List})
  • *
  • Parameter 2: signature algorithms from digests record ({@code List})
  • *
*/ V2_SIG_SIG_ALG_MISMATCH_BETWEEN_SIGNATURES_AND_DIGESTS_RECORDS( "Signature algorithms mismatch between signatures and digests records" + ": %1$s vs %2$s"), /** * The APK's digest does not match the digest contained in the APK Signature Scheme v2 * signature. * *
    *
  • Parameter 1: content digest algorithm ({@link ContentDigestAlgorithm})
  • *
  • Parameter 2: hex-encoded expected digest of the APK ({@code String})
  • *
  • Parameter 3: hex-encoded actual digest of the APK ({@code String})
  • *
*/ V2_SIG_APK_DIGEST_DID_NOT_VERIFY( "APK integrity check failed. %1$s digest mismatch." + " Expected: <%2$s>, actual: <%3$s>"), /** * APK Signing Block contains an unknown entry. * *
    *
  • Parameter 1: entry ID ({@code Integer})
  • *
*/ APK_SIG_BLOCK_UNKNOWN_ENTRY_ID("APK Signing Block contains unknown entry: ID %1$#x"); private final String mFormat; private Issue(String format) { mFormat = format; } /** * Returns the format string suitable for combining the parameters of this issue into a * readable string. See {@link java.util.Formatter} for format. */ private String getFormat() { return mFormat; } } /** * {@link Issue} with associated parameters. {@link #toString()} produces a readable formatted * form. */ public static class IssueWithParams { private final Issue mIssue; private final Object[] mParams; /** * Constructs a new {@code IssueWithParams} of the specified type and with provided * parameters. */ public IssueWithParams(Issue issue, Object[] params) { mIssue = issue; mParams = params; } /** * Returns the type of this issue. */ public Issue getIssue() { return mIssue; } /** * Returns the parameters of this issue. */ public Object[] getParams() { return mParams.clone(); } /** * Returns a readable form of this issue. */ @Override public String toString() { return String.format(mIssue.getFormat(), mParams); } } /** * Wrapped around {@code byte[]} which ensures that {@code equals} and {@code hashCode} operate * on the contents of the arrays rather than on references. */ private static class ByteArray { private final byte[] mArray; private final int mHashCode; private ByteArray(byte[] arr) { mArray = arr; mHashCode = Arrays.hashCode(mArray); } @Override public int hashCode() { return mHashCode; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } ByteArray other = (ByteArray) obj; if (hashCode() != other.hashCode()) { return false; } if (!Arrays.equals(mArray, other.mArray)) { return false; } return true; } } /** * Builder of {@link ApkVerifier} instances. * *

The resulting verifier by default checks whether the APK will verify on all platform * versions supported by the APK, as specified by {@code android:minSdkVersion} attributes in * the APK's {@code AndroidManifest.xml}. The range of platform versions can be customized using * {@link #setMinCheckedPlatformVersion(int)} and {@link #setMaxCheckedPlatformVersion(int)}. */ public static class Builder { private final File mApkFile; private final DataSource mApkDataSource; private Integer mMinSdkVersion; private int mMaxSdkVersion = Integer.MAX_VALUE; /** * Constructs a new {@code Builder} for verifying the provided APK file. */ public Builder(File apk) { if (apk == null) { throw new NullPointerException("apk == null"); } mApkFile = apk; mApkDataSource = null; } /** * Constructs a new {@code Builder} for verifying the provided APK. */ public Builder(DataSource apk) { if (apk == null) { throw new NullPointerException("apk == null"); } mApkDataSource = apk; mApkFile = null; } /** * Sets the oldest Android platform version for which the APK is verified. APK verification * will confirm that the APK is expected to install successfully on all known Android * platforms starting from the platform version with the provided API Level. The upper end * of the platform versions range can be modified via * {@link #setMaxCheckedPlatformVersion(int)}. * *

This method is useful for overriding the default behavior which checks that the APK * will verify on all platform versions supported by the APK, as specified by * {@code android:minSdkVersion} attributes in the APK's {@code AndroidManifest.xml}. * * @param minSdkVersion API Level of the oldest platform for which to verify the APK * * @see #setMinCheckedPlatformVersion(int) */ public Builder setMinCheckedPlatformVersion(int minSdkVersion) { mMinSdkVersion = minSdkVersion; return this; } /** * Sets the newest Android platform version for which the APK is verified. APK verification * will confirm that the APK is expected to install successfully on all platform versions * supported by the APK up until and including the provided version. The lower end * of the platform versions range can be modified via * {@link #setMinCheckedPlatformVersion(int)}. * * @param maxSdkVersion API Level of the newest platform for which to verify the APK * * @see #setMinCheckedPlatformVersion(int) */ public Builder setMaxCheckedPlatformVersion(int maxSdkVersion) { mMaxSdkVersion = maxSdkVersion; return this; } /** * Returns an {@link ApkVerifier} initialized according to the configuration of this * builder. */ public ApkVerifier build() { return new ApkVerifier( mApkFile, mApkDataSource, mMinSdkVersion, mMaxSdkVersion); } } } src/main/java/com/android/apksig/DefaultApkSignerEngine.java0100644 0000000 0000000 00000111153 13243353142 023100 0ustar000000000 0000000 /* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig; import com.android.apksig.apk.ApkFormatException; import com.android.apksig.internal.apk.v1.DigestAlgorithm; import com.android.apksig.internal.apk.v1.V1SchemeSigner; import com.android.apksig.internal.apk.v2.V2SchemeSigner; import com.android.apksig.internal.util.MessageDigestSink; import com.android.apksig.internal.util.Pair; import com.android.apksig.util.DataSink; import com.android.apksig.util.DataSinks; import com.android.apksig.util.DataSource; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.security.InvalidKeyException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.SignatureException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; /** * Default implementation of {@link ApkSignerEngine}. * *

Use {@link Builder} to obtain instances of this engine. */ public class DefaultApkSignerEngine implements ApkSignerEngine { // IMPLEMENTATION NOTE: This engine generates a signed APK as follows: // 1. The engine asks its client to output input JAR entries which are not part of JAR // signature. // 2. If JAR signing (v1 signing) is enabled, the engine inspects the output JAR entries to // compute their digests, to be placed into output META-INF/MANIFEST.MF. It also inspects // the contents of input and output META-INF/MANIFEST.MF to borrow the main section of the // file. It does not care about individual (i.e., JAR entry-specific) sections. It then // emits the v1 signature (a set of JAR entries) and asks the client to output them. // 3. If APK Signature Scheme v2 (v2 signing) is enabled, the engine emits an APK Signing Block // from outputZipSections() and asks its client to insert this block into the output. private final boolean mV1SigningEnabled; private final boolean mV2SigningEnabled; private final boolean mOtherSignersSignaturesPreserved; private final String mCreatedBy; private final List mV1SignerConfigs; private final DigestAlgorithm mV1ContentDigestAlgorithm; private final List mV2SignerConfigs; private boolean mClosed; private boolean mV1SignaturePending; /** * Names of JAR entries which this engine is expected to output as part of v1 signing. */ private final Set mSignatureExpectedOutputJarEntryNames; /** Requests for digests of output JAR entries. */ private final Map mOutputJarEntryDigestRequests = new HashMap<>(); /** Digests of output JAR entries. */ private final Map mOutputJarEntryDigests = new HashMap<>(); /** Data of JAR entries emitted by this engine as v1 signature. */ private final Map mEmittedSignatureJarEntryData = new HashMap<>(); /** Requests for data of output JAR entries which comprise the v1 signature. */ private final Map mOutputSignatureJarEntryDataRequests = new HashMap<>(); /** * Request to obtain the data of MANIFEST.MF or {@code null} if the request hasn't been issued. */ private GetJarEntryDataRequest mInputJarManifestEntryDataRequest; /** * Request to output the emitted v1 signature or {@code null} if the request hasn't been issued. */ private OutputJarSignatureRequestImpl mAddV1SignatureRequest; private boolean mV2SignaturePending; /** * Request to output the emitted v2 signature or {@code null} if the request hasn't been issued. */ private OutputApkSigningBlockRequestImpl mAddV2SignatureRequest; private DefaultApkSignerEngine( List signerConfigs, int minSdkVersion, boolean v1SigningEnabled, boolean v2SigningEnabled, boolean otherSignersSignaturesPreserved, String createdBy) throws InvalidKeyException { if (signerConfigs.isEmpty()) { throw new IllegalArgumentException("At least one signer config must be provided"); } if (otherSignersSignaturesPreserved) { throw new UnsupportedOperationException( "Preserving other signer's signatures is not yet implemented"); } mV1SigningEnabled = v1SigningEnabled; mV2SigningEnabled = v2SigningEnabled; mV1SignaturePending = v1SigningEnabled; mV2SignaturePending = v2SigningEnabled; mOtherSignersSignaturesPreserved = otherSignersSignaturesPreserved; mCreatedBy = createdBy; mV1SignerConfigs = (v1SigningEnabled) ? new ArrayList<>(signerConfigs.size()) : Collections.emptyList(); mV2SignerConfigs = (v2SigningEnabled) ? new ArrayList<>(signerConfigs.size()) : Collections.emptyList(); Map v1SignerNameToSignerIndex = (v1SigningEnabled) ? new HashMap<>(signerConfigs.size()) : Collections.emptyMap(); DigestAlgorithm v1ContentDigestAlgorithm = null; for (int i = 0; i < signerConfigs.size(); i++) { SignerConfig signerConfig = signerConfigs.get(i); List certificates = signerConfig.getCertificates(); PublicKey publicKey = certificates.get(0).getPublicKey(); if (v1SigningEnabled) { String v1SignerName = V1SchemeSigner.getSafeSignerName(signerConfig.getName()); // Check whether the signer's name is unique among all v1 signers Integer indexOfOtherSignerWithSameName = v1SignerNameToSignerIndex.put(v1SignerName, i); if (indexOfOtherSignerWithSameName != null) { throw new IllegalArgumentException( "Signers #" + (indexOfOtherSignerWithSameName + 1) + " and #" + (i + 1) + " have the same name: " + v1SignerName + ". v1 signer names must be unique"); } DigestAlgorithm v1SignatureDigestAlgorithm = V1SchemeSigner.getSuggestedSignatureDigestAlgorithm( publicKey, minSdkVersion); V1SchemeSigner.SignerConfig v1SignerConfig = new V1SchemeSigner.SignerConfig(); v1SignerConfig.name = v1SignerName; v1SignerConfig.privateKey = signerConfig.getPrivateKey(); v1SignerConfig.certificates = certificates; v1SignerConfig.signatureDigestAlgorithm = v1SignatureDigestAlgorithm; // For digesting contents of APK entries and of MANIFEST.MF, pick the algorithm // of comparable strength to the digest algorithm used for computing the signature. // When there are multiple signers, pick the strongest digest algorithm out of their // signature digest algorithms. This avoids reducing the digest strength used by any // of the signers to protect APK contents. if (v1ContentDigestAlgorithm == null) { v1ContentDigestAlgorithm = v1SignatureDigestAlgorithm; } else { if (DigestAlgorithm.BY_STRENGTH_COMPARATOR.compare( v1SignatureDigestAlgorithm, v1ContentDigestAlgorithm) > 0) { v1ContentDigestAlgorithm = v1SignatureDigestAlgorithm; } } mV1SignerConfigs.add(v1SignerConfig); } if (v2SigningEnabled) { V2SchemeSigner.SignerConfig v2SignerConfig = new V2SchemeSigner.SignerConfig(); v2SignerConfig.privateKey = signerConfig.getPrivateKey(); v2SignerConfig.certificates = certificates; v2SignerConfig.signatureAlgorithms = V2SchemeSigner.getSuggestedSignatureAlgorithms(publicKey, minSdkVersion); mV2SignerConfigs.add(v2SignerConfig); } } mV1ContentDigestAlgorithm = v1ContentDigestAlgorithm; mSignatureExpectedOutputJarEntryNames = (v1SigningEnabled) ? V1SchemeSigner.getOutputEntryNames(mV1SignerConfigs) : Collections.emptySet(); } @Override public void inputApkSigningBlock(DataSource apkSigningBlock) { checkNotClosed(); if ((apkSigningBlock == null) || (apkSigningBlock.size() == 0)) { return; } if (mOtherSignersSignaturesPreserved) { // TODO: Preserve blocks other than APK Signature Scheme v2 blocks of signers configured // in this engine. return; } // TODO: Preserve blocks other than APK Signature Scheme v2 blocks. } @Override public InputJarEntryInstructions inputJarEntry(String entryName) { checkNotClosed(); InputJarEntryInstructions.OutputPolicy outputPolicy = getInputJarEntryOutputPolicy(entryName); switch (outputPolicy) { case SKIP: return new InputJarEntryInstructions(InputJarEntryInstructions.OutputPolicy.SKIP); case OUTPUT: return new InputJarEntryInstructions(InputJarEntryInstructions.OutputPolicy.OUTPUT); case OUTPUT_BY_ENGINE: if (V1SchemeSigner.MANIFEST_ENTRY_NAME.equals(entryName)) { // We copy the main section of the JAR manifest from input to output. Thus, this // invalidates v1 signature and we need to see the entry's data. mInputJarManifestEntryDataRequest = new GetJarEntryDataRequest(entryName); return new InputJarEntryInstructions( InputJarEntryInstructions.OutputPolicy.OUTPUT_BY_ENGINE, mInputJarManifestEntryDataRequest); } return new InputJarEntryInstructions( InputJarEntryInstructions.OutputPolicy.OUTPUT_BY_ENGINE); default: throw new RuntimeException("Unsupported output policy: " + outputPolicy); } } @Override public InspectJarEntryRequest outputJarEntry(String entryName) { checkNotClosed(); invalidateV2Signature(); if (!mV1SigningEnabled) { // No need to inspect JAR entries when v1 signing is not enabled. return null; } // v1 signing is enabled if (V1SchemeSigner.isJarEntryDigestNeededInManifest(entryName)) { // This entry is covered by v1 signature. We thus need to inspect the entry's data to // compute its digest(s) for v1 signature. // TODO: Handle the case where other signer's v1 signatures are present and need to be // preserved. In that scenario we can't modify MANIFEST.MF and add/remove JAR entries // covered by v1 signature. invalidateV1Signature(); GetJarEntryDataDigestRequest dataDigestRequest = new GetJarEntryDataDigestRequest( entryName, V1SchemeSigner.getJcaMessageDigestAlgorithm(mV1ContentDigestAlgorithm)); mOutputJarEntryDigestRequests.put(entryName, dataDigestRequest); mOutputJarEntryDigests.remove(entryName); return dataDigestRequest; } if (mSignatureExpectedOutputJarEntryNames.contains(entryName)) { // This entry is part of v1 signature generated by this engine. We need to check whether // the entry's data is as output by the engine. invalidateV1Signature(); GetJarEntryDataRequest dataRequest; if (V1SchemeSigner.MANIFEST_ENTRY_NAME.equals(entryName)) { dataRequest = new GetJarEntryDataRequest(entryName); mInputJarManifestEntryDataRequest = dataRequest; } else { // If this entry is part of v1 signature which has been emitted by this engine, // check whether the output entry's data matches what the engine emitted. dataRequest = (mEmittedSignatureJarEntryData.containsKey(entryName)) ? new GetJarEntryDataRequest(entryName) : null; } if (dataRequest != null) { mOutputSignatureJarEntryDataRequests.put(entryName, dataRequest); } return dataRequest; } // This entry is not covered by v1 signature and isn't part of v1 signature. return null; } @Override public InputJarEntryInstructions.OutputPolicy inputJarEntryRemoved(String entryName) { checkNotClosed(); return getInputJarEntryOutputPolicy(entryName); } @Override public void outputJarEntryRemoved(String entryName) { checkNotClosed(); invalidateV2Signature(); if (!mV1SigningEnabled) { return; } if (V1SchemeSigner.isJarEntryDigestNeededInManifest(entryName)) { // This entry is covered by v1 signature. invalidateV1Signature(); mOutputJarEntryDigests.remove(entryName); mOutputJarEntryDigestRequests.remove(entryName); mOutputSignatureJarEntryDataRequests.remove(entryName); return; } if (mSignatureExpectedOutputJarEntryNames.contains(entryName)) { // This entry is part of the v1 signature generated by this engine. invalidateV1Signature(); return; } } @Override public OutputJarSignatureRequest outputJarEntries() throws ApkFormatException, InvalidKeyException, SignatureException, NoSuchAlgorithmException { checkNotClosed(); if (!mV1SignaturePending) { return null; } if ((mInputJarManifestEntryDataRequest != null) && (!mInputJarManifestEntryDataRequest.isDone())) { throw new IllegalStateException( "Still waiting to inspect input APK's " + mInputJarManifestEntryDataRequest.getEntryName()); } for (GetJarEntryDataDigestRequest digestRequest : mOutputJarEntryDigestRequests.values()) { String entryName = digestRequest.getEntryName(); if (!digestRequest.isDone()) { throw new IllegalStateException( "Still waiting to inspect output APK's " + entryName); } mOutputJarEntryDigests.put(entryName, digestRequest.getDigest()); } mOutputJarEntryDigestRequests.clear(); for (GetJarEntryDataRequest dataRequest : mOutputSignatureJarEntryDataRequests.values()) { if (!dataRequest.isDone()) { throw new IllegalStateException( "Still waiting to inspect output APK's " + dataRequest.getEntryName()); } } List apkSigningSchemeIds = (mV2SigningEnabled) ? Collections.singletonList(2) : Collections.emptyList(); byte[] inputJarManifest = (mInputJarManifestEntryDataRequest != null) ? mInputJarManifestEntryDataRequest.getData() : null; // Check whether the most recently used signature (if present) is still fine. List> signatureZipEntries; if ((mAddV1SignatureRequest == null) || (!mAddV1SignatureRequest.isDone())) { try { signatureZipEntries = V1SchemeSigner.sign( mV1SignerConfigs, mV1ContentDigestAlgorithm, mOutputJarEntryDigests, apkSigningSchemeIds, inputJarManifest, mCreatedBy); } catch (CertificateException e) { throw new SignatureException("Failed to generate v1 signature", e); } } else { V1SchemeSigner.OutputManifestFile newManifest = V1SchemeSigner.generateManifestFile( mV1ContentDigestAlgorithm, mOutputJarEntryDigests, inputJarManifest); byte[] emittedSignatureManifest = mEmittedSignatureJarEntryData.get(V1SchemeSigner.MANIFEST_ENTRY_NAME); if (!Arrays.equals(newManifest.contents, emittedSignatureManifest)) { // Emitted v1 signature is no longer valid. try { signatureZipEntries = V1SchemeSigner.signManifest( mV1SignerConfigs, mV1ContentDigestAlgorithm, apkSigningSchemeIds, mCreatedBy, newManifest); } catch (CertificateException e) { throw new SignatureException("Failed to generate v1 signature", e); } } else { // Emitted v1 signature is still valid. Check whether the signature is there in the // output. signatureZipEntries = new ArrayList<>(); for (Map.Entry expectedOutputEntry : mEmittedSignatureJarEntryData.entrySet()) { String entryName = expectedOutputEntry.getKey(); byte[] expectedData = expectedOutputEntry.getValue(); GetJarEntryDataRequest actualDataRequest = mOutputSignatureJarEntryDataRequests.get(entryName); if (actualDataRequest == null) { // This signature entry hasn't been output. signatureZipEntries.add(Pair.of(entryName, expectedData)); continue; } byte[] actualData = actualDataRequest.getData(); if (!Arrays.equals(expectedData, actualData)) { signatureZipEntries.add(Pair.of(entryName, expectedData)); } } if (signatureZipEntries.isEmpty()) { // v1 signature in the output is valid return null; } // v1 signature in the output is not valid. } } if (signatureZipEntries.isEmpty()) { // v1 signature in the output is valid mV1SignaturePending = false; return null; } List sigEntries = new ArrayList<>(signatureZipEntries.size()); for (Pair entry : signatureZipEntries) { String entryName = entry.getFirst(); byte[] entryData = entry.getSecond(); sigEntries.add(new OutputJarSignatureRequest.JarEntry(entryName, entryData)); mEmittedSignatureJarEntryData.put(entryName, entryData); } mAddV1SignatureRequest = new OutputJarSignatureRequestImpl(sigEntries); return mAddV1SignatureRequest; } @Override public OutputApkSigningBlockRequest outputZipSections( DataSource zipEntries, DataSource zipCentralDirectory, DataSource zipEocd) throws IOException, InvalidKeyException, SignatureException, NoSuchAlgorithmException { checkNotClosed(); checkV1SigningDoneIfEnabled(); if (!mV2SigningEnabled) { return null; } invalidateV2Signature(); byte[] apkSigningBlock = V2SchemeSigner.generateApkSigningBlock( zipEntries, zipCentralDirectory, zipEocd, mV2SignerConfigs); mAddV2SignatureRequest = new OutputApkSigningBlockRequestImpl(apkSigningBlock); return mAddV2SignatureRequest; } @Override public void outputDone() { checkNotClosed(); checkV1SigningDoneIfEnabled(); checkV2SigningDoneIfEnabled(); } @Override public void close() { mClosed = true; mAddV1SignatureRequest = null; mInputJarManifestEntryDataRequest = null; mOutputJarEntryDigestRequests.clear(); mOutputJarEntryDigests.clear(); mEmittedSignatureJarEntryData.clear(); mOutputSignatureJarEntryDataRequests.clear(); mAddV2SignatureRequest = null; } private void invalidateV1Signature() { if (mV1SigningEnabled) { mV1SignaturePending = true; } invalidateV2Signature(); } private void invalidateV2Signature() { if (mV2SigningEnabled) { mV2SignaturePending = true; mAddV2SignatureRequest = null; } } private void checkNotClosed() { if (mClosed) { throw new IllegalStateException("Engine closed"); } } private void checkV1SigningDoneIfEnabled() { if (!mV1SignaturePending) { return; } if (mAddV1SignatureRequest == null) { throw new IllegalStateException( "v1 signature (JAR signature) not yet generated. Skipped outputJarEntries()?"); } if (!mAddV1SignatureRequest.isDone()) { throw new IllegalStateException( "v1 signature (JAR signature) addition requested by outputJarEntries() hasn't" + " been fulfilled"); } for (Map.Entry expectedOutputEntry : mEmittedSignatureJarEntryData.entrySet()) { String entryName = expectedOutputEntry.getKey(); byte[] expectedData = expectedOutputEntry.getValue(); GetJarEntryDataRequest actualDataRequest = mOutputSignatureJarEntryDataRequests.get(entryName); if (actualDataRequest == null) { throw new IllegalStateException( "APK entry " + entryName + " not yet output despite this having been" + " requested"); } else if (!actualDataRequest.isDone()) { throw new IllegalStateException( "Still waiting to inspect output APK's " + entryName); } byte[] actualData = actualDataRequest.getData(); if (!Arrays.equals(expectedData, actualData)) { throw new IllegalStateException( "Output APK entry " + entryName + " data differs from what was requested"); } } mV1SignaturePending = false; } private void checkV2SigningDoneIfEnabled() { if (!mV2SignaturePending) { return; } if (mAddV2SignatureRequest == null) { throw new IllegalStateException( "v2 signature (APK Signature Scheme v2 signature) not yet generated." + " Skipped outputZipSections()?"); } if (!mAddV2SignatureRequest.isDone()) { throw new IllegalStateException( "v2 signature (APK Signature Scheme v2 signature) addition requested by" + " outputZipSections() hasn't been fulfilled yet"); } mAddV2SignatureRequest = null; mV2SignaturePending = false; } /** * Returns the output policy for the provided input JAR entry. */ private InputJarEntryInstructions.OutputPolicy getInputJarEntryOutputPolicy(String entryName) { if (mSignatureExpectedOutputJarEntryNames.contains(entryName)) { return InputJarEntryInstructions.OutputPolicy.OUTPUT_BY_ENGINE; } if ((mOtherSignersSignaturesPreserved) || (V1SchemeSigner.isJarEntryDigestNeededInManifest(entryName))) { return InputJarEntryInstructions.OutputPolicy.OUTPUT; } return InputJarEntryInstructions.OutputPolicy.SKIP; } private static class OutputJarSignatureRequestImpl implements OutputJarSignatureRequest { private final List mAdditionalJarEntries; private volatile boolean mDone; private OutputJarSignatureRequestImpl(List additionalZipEntries) { mAdditionalJarEntries = Collections.unmodifiableList(new ArrayList<>(additionalZipEntries)); } @Override public List getAdditionalJarEntries() { return mAdditionalJarEntries; } @Override public void done() { mDone = true; } private boolean isDone() { return mDone; } } private static class OutputApkSigningBlockRequestImpl implements OutputApkSigningBlockRequest { private final byte[] mApkSigningBlock; private volatile boolean mDone; private OutputApkSigningBlockRequestImpl(byte[] apkSigingBlock) { mApkSigningBlock = apkSigingBlock.clone(); } @Override public byte[] getApkSigningBlock() { return mApkSigningBlock.clone(); } @Override public void done() { mDone = true; } private boolean isDone() { return mDone; } } /** * JAR entry inspection request which obtain the entry's uncompressed data. */ private static class GetJarEntryDataRequest implements InspectJarEntryRequest { private final String mEntryName; private final Object mLock = new Object(); private boolean mDone; private DataSink mDataSink; private ByteArrayOutputStream mDataSinkBuf; private GetJarEntryDataRequest(String entryName) { mEntryName = entryName; } @Override public String getEntryName() { return mEntryName; } @Override public DataSink getDataSink() { synchronized (mLock) { checkNotDone(); if (mDataSinkBuf == null) { mDataSinkBuf = new ByteArrayOutputStream(); } if (mDataSink == null) { mDataSink = DataSinks.asDataSink(mDataSinkBuf); } return mDataSink; } } @Override public void done() { synchronized (mLock) { if (mDone) { return; } mDone = true; } } private boolean isDone() { synchronized (mLock) { return mDone; } } private void checkNotDone() throws IllegalStateException { synchronized (mLock) { if (mDone) { throw new IllegalStateException("Already done"); } } } private byte[] getData() { synchronized (mLock) { if (!mDone) { throw new IllegalStateException("Not yet done"); } return (mDataSinkBuf != null) ? mDataSinkBuf.toByteArray() : new byte[0]; } } } /** * JAR entry inspection request which obtains the digest of the entry's uncompressed data. */ private static class GetJarEntryDataDigestRequest implements InspectJarEntryRequest { private final String mEntryName; private final String mJcaDigestAlgorithm; private final Object mLock = new Object(); private boolean mDone; private DataSink mDataSink; private MessageDigest mMessageDigest; private byte[] mDigest; private GetJarEntryDataDigestRequest(String entryName, String jcaDigestAlgorithm) { mEntryName = entryName; mJcaDigestAlgorithm = jcaDigestAlgorithm; } @Override public String getEntryName() { return mEntryName; } @Override public DataSink getDataSink() { synchronized (mLock) { checkNotDone(); if (mDataSink == null) { mDataSink = new MessageDigestSink(new MessageDigest[] {getMessageDigest()}); } return mDataSink; } } private MessageDigest getMessageDigest() { synchronized (mLock) { if (mMessageDigest == null) { try { mMessageDigest = MessageDigest.getInstance(mJcaDigestAlgorithm); } catch (NoSuchAlgorithmException e) { throw new RuntimeException( mJcaDigestAlgorithm + " MessageDigest not available", e); } } return mMessageDigest; } } @Override public void done() { synchronized (mLock) { if (mDone) { return; } mDone = true; mDigest = getMessageDigest().digest(); mMessageDigest = null; mDataSink = null; } } private boolean isDone() { synchronized (mLock) { return mDone; } } private void checkNotDone() throws IllegalStateException { synchronized (mLock) { if (mDone) { throw new IllegalStateException("Already done"); } } } private byte[] getDigest() { synchronized (mLock) { if (!mDone) { throw new IllegalStateException("Not yet done"); } return mDigest.clone(); } } } /** * Configuration of a signer. * *

Use {@link Builder} to obtain configuration instances. */ public static class SignerConfig { private final String mName; private final PrivateKey mPrivateKey; private final List mCertificates; private SignerConfig( String name, PrivateKey privateKey, List certificates) { mName = name; mPrivateKey = privateKey; mCertificates = Collections.unmodifiableList(new ArrayList<>(certificates)); } /** * Returns the name of this signer. */ public String getName() { return mName; } /** * Returns the signing key of this signer. */ public PrivateKey getPrivateKey() { return mPrivateKey; } /** * Returns the certificate(s) of this signer. The first certificate's public key corresponds * to this signer's private key. */ public List getCertificates() { return mCertificates; } /** * Builder of {@link SignerConfig} instances. */ public static class Builder { private final String mName; private final PrivateKey mPrivateKey; private final List mCertificates; /** * Constructs a new {@code Builder}. * * @param name signer's name. The name is reflected in the name of files comprising the * JAR signature of the APK. * @param privateKey signing key * @param certificates list of one or more X.509 certificates. The subject public key of * the first certificate must correspond to the {@code privateKey}. */ public Builder( String name, PrivateKey privateKey, List certificates) { if (name.isEmpty()) { throw new IllegalArgumentException("Empty name"); } mName = name; mPrivateKey = privateKey; mCertificates = new ArrayList<>(certificates); } /** * Returns a new {@code SignerConfig} instance configured based on the configuration of * this builder. */ public SignerConfig build() { return new SignerConfig( mName, mPrivateKey, mCertificates); } } } /** * Builder of {@link DefaultApkSignerEngine} instances. */ public static class Builder { private final List mSignerConfigs; private final int mMinSdkVersion; private boolean mV1SigningEnabled = true; private boolean mV2SigningEnabled = true; private boolean mOtherSignersSignaturesPreserved; private String mCreatedBy = "1.0 (Android)"; /** * Constructs a new {@code Builder}. * * @param signerConfigs information about signers with which the APK will be signed. At * least one signer configuration must be provided. * @param minSdkVersion API Level of the oldest Android platform on which the APK is * supposed to be installed. See {@code minSdkVersion} attribute in the APK's * {@code AndroidManifest.xml}. The higher the version, the stronger signing features * will be enabled. */ public Builder( List signerConfigs, int minSdkVersion) { if (signerConfigs.isEmpty()) { throw new IllegalArgumentException("At least one signer config must be provided"); } mSignerConfigs = new ArrayList<>(signerConfigs); mMinSdkVersion = minSdkVersion; } /** * Returns a new {@code DefaultApkSignerEngine} instance configured based on the * configuration of this builder. */ public DefaultApkSignerEngine build() throws InvalidKeyException { return new DefaultApkSignerEngine( mSignerConfigs, mMinSdkVersion, mV1SigningEnabled, mV2SigningEnabled, mOtherSignersSignaturesPreserved, mCreatedBy); } /** * Sets whether the APK should be signed using JAR signing (aka v1 signature scheme). * *

By default, the APK will be signed using this scheme. */ public Builder setV1SigningEnabled(boolean enabled) { mV1SigningEnabled = enabled; return this; } /** * Sets whether the APK should be signed using APK Signature Scheme v2 (aka v2 signature * scheme). * *

By default, the APK will be signed using this scheme. */ public Builder setV2SigningEnabled(boolean enabled) { mV2SigningEnabled = enabled; return this; } /** * Sets whether signatures produced by signers other than the ones configured in this engine * should be copied from the input APK to the output APK. * *

By default, signatures of other signers are omitted from the output APK. */ public Builder setOtherSignersSignaturesPreserved(boolean preserved) { mOtherSignersSignaturesPreserved = preserved; return this; } /** * Sets the value of the {@code Created-By} field in JAR signature files. */ public Builder setCreatedBy(String createdBy) { if (createdBy == null) { throw new NullPointerException(); } mCreatedBy = createdBy; return this; } } } src/main/java/com/android/apksig/apk/0040755 0000000 0000000 00000000000 13243353142 016473 5ustar000000000 0000000 src/main/java/com/android/apksig/apk/ApkFormatException.java0100644 0000000 0000000 00000002363 13243353142 023102 0ustar000000000 0000000 /* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.apk; /** * Indicates that an APK is not well-formed. For example, this may indicate that the APK is not a * well-formed ZIP archive, in which case {@link #getCause()} will return a * {@link com.android.apksig.zip.ZipFormatException ZipFormatException}, or that the APK contains * multiple ZIP entries with the same name. */ public class ApkFormatException extends Exception { private static final long serialVersionUID = 1L; public ApkFormatException(String message) { super(message); } public ApkFormatException(String message, Throwable cause) { super(message, cause); } } src/main/java/com/android/apksig/apk/ApkUtils.java0100644 0000000 0000000 00000035156 13243353142 021101 0ustar000000000 0000000 /* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.apk; import com.android.apksig.internal.apk.AndroidBinXmlParser; import com.android.apksig.internal.util.Pair; import com.android.apksig.internal.zip.ZipUtils; import com.android.apksig.util.DataSource; import com.android.apksig.zip.ZipFormatException; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.Arrays; import java.util.Comparator; /** * APK utilities. */ public abstract class ApkUtils { private ApkUtils() {} /** * Finds the main ZIP sections of the provided APK. * * @throws IOException if an I/O error occurred while reading the APK * @throws ZipFormatException if the APK is malformed */ public static ZipSections findZipSections(DataSource apk) throws IOException, ZipFormatException { Pair eocdAndOffsetInFile = ZipUtils.findZipEndOfCentralDirectoryRecord(apk); if (eocdAndOffsetInFile == null) { throw new ZipFormatException("ZIP End of Central Directory record not found"); } ByteBuffer eocdBuf = eocdAndOffsetInFile.getFirst(); long eocdOffset = eocdAndOffsetInFile.getSecond(); eocdBuf.order(ByteOrder.LITTLE_ENDIAN); long cdStartOffset = ZipUtils.getZipEocdCentralDirectoryOffset(eocdBuf); if (cdStartOffset > eocdOffset) { throw new ZipFormatException( "ZIP Central Directory start offset out of range: " + cdStartOffset + ". ZIP End of Central Directory offset: " + eocdOffset); } long cdSizeBytes = ZipUtils.getZipEocdCentralDirectorySizeBytes(eocdBuf); long cdEndOffset = cdStartOffset + cdSizeBytes; if (cdEndOffset > eocdOffset) { throw new ZipFormatException( "ZIP Central Directory overlaps with End of Central Directory" + ". CD end: " + cdEndOffset + ", EoCD start: " + eocdOffset); } int cdRecordCount = ZipUtils.getZipEocdCentralDirectoryTotalRecordCount(eocdBuf); return new ZipSections( cdStartOffset, cdSizeBytes, cdRecordCount, eocdOffset, eocdBuf); } /** * Information about the ZIP sections of an APK. */ public static class ZipSections { private final long mCentralDirectoryOffset; private final long mCentralDirectorySizeBytes; private final int mCentralDirectoryRecordCount; private final long mEocdOffset; private final ByteBuffer mEocd; public ZipSections( long centralDirectoryOffset, long centralDirectorySizeBytes, int centralDirectoryRecordCount, long eocdOffset, ByteBuffer eocd) { mCentralDirectoryOffset = centralDirectoryOffset; mCentralDirectorySizeBytes = centralDirectorySizeBytes; mCentralDirectoryRecordCount = centralDirectoryRecordCount; mEocdOffset = eocdOffset; mEocd = eocd; } /** * Returns the start offset of the ZIP Central Directory. This value is taken from the * ZIP End of Central Directory record. */ public long getZipCentralDirectoryOffset() { return mCentralDirectoryOffset; } /** * Returns the size (in bytes) of the ZIP Central Directory. This value is taken from the * ZIP End of Central Directory record. */ public long getZipCentralDirectorySizeBytes() { return mCentralDirectorySizeBytes; } /** * Returns the number of records in the ZIP Central Directory. This value is taken from the * ZIP End of Central Directory record. */ public int getZipCentralDirectoryRecordCount() { return mCentralDirectoryRecordCount; } /** * Returns the start offset of the ZIP End of Central Directory record. The record extends * until the very end of the APK. */ public long getZipEndOfCentralDirectoryOffset() { return mEocdOffset; } /** * Returns the contents of the ZIP End of Central Directory. */ public ByteBuffer getZipEndOfCentralDirectory() { return mEocd; } } /** * Sets the offset of the start of the ZIP Central Directory in the APK's ZIP End of Central * Directory record. * * @param zipEndOfCentralDirectory APK's ZIP End of Central Directory record * @param offset offset of the ZIP Central Directory relative to the start of the archive. Must * be between {@code 0} and {@code 2^32 - 1} inclusive. */ public static void setZipEocdCentralDirectoryOffset( ByteBuffer zipEndOfCentralDirectory, long offset) { ByteBuffer eocd = zipEndOfCentralDirectory.slice(); eocd.order(ByteOrder.LITTLE_ENDIAN); ZipUtils.setZipEocdCentralDirectoryOffset(eocd, offset); } /** * Name of the Android manifest ZIP entry in APKs. */ private static final String ANDROID_MANIFEST_ZIP_ENTRY_NAME = "AndroidManifest.xml"; /** * Android resource ID of the {@code android:minSdkVersion} attribute in AndroidManifest.xml. */ private static final int MIN_SDK_VERSION_ATTR_ID = 0x0101020c; /** * Returns the lowest Android platform version (API Level) supported by an APK with the * provided {@code AndroidManifest.xml}. * * @param androidManifestContents contents of {@code AndroidManifest.xml} in binary Android * resource format * * @throws MinSdkVersionException if an error occurred while determining the API Level */ public static int getMinSdkVersionFromBinaryAndroidManifest( ByteBuffer androidManifestContents) throws MinSdkVersionException { // IMPLEMENTATION NOTE: Minimum supported Android platform version number is declared using // uses-sdk elements which are children of the top-level manifest element. uses-sdk element // declares the minimum supported platform version using the android:minSdkVersion attribute // whose default value is 1. // For each encountered uses-sdk element, the Android runtime checks that its minSdkVersion // is not higher than the runtime's API Level and rejects APKs if it is higher. Thus, the // effective minSdkVersion value is the maximum over the encountered minSdkVersion values. try { // If no uses-sdk elements are encountered, Android accepts the APK. We treat this // scenario as though the minimum supported API Level is 1. int result = 1; AndroidBinXmlParser parser = new AndroidBinXmlParser(androidManifestContents); int eventType = parser.getEventType(); while (eventType != AndroidBinXmlParser.EVENT_END_DOCUMENT) { if ((eventType == AndroidBinXmlParser.EVENT_START_ELEMENT) && (parser.getDepth() == 2) && ("uses-sdk".equals(parser.getName())) && (parser.getNamespace().isEmpty())) { // In each uses-sdk element, minSdkVersion defaults to 1 int minSdkVersion = 1; for (int i = 0; i < parser.getAttributeCount(); i++) { if (parser.getAttributeNameResourceId(i) == MIN_SDK_VERSION_ATTR_ID) { int valueType = parser.getAttributeValueType(i); switch (valueType) { case AndroidBinXmlParser.VALUE_TYPE_INT: minSdkVersion = parser.getAttributeIntValue(i); break; case AndroidBinXmlParser.VALUE_TYPE_STRING: minSdkVersion = getMinSdkVersionForCodename( parser.getAttributeStringValue(i)); break; default: throw new MinSdkVersionException( "Unable to determine APK's minimum supported Android" + ": unsupported value type in " + ANDROID_MANIFEST_ZIP_ENTRY_NAME + "'s" + " minSdkVersion" + ". Only integer values supported."); } break; } } result = Math.max(result, minSdkVersion); } eventType = parser.next(); } return result; } catch (AndroidBinXmlParser.XmlParserException e) { throw new MinSdkVersionException( "Unable to determine APK's minimum supported Android platform version" + ": malformed binary resource: " + ANDROID_MANIFEST_ZIP_ENTRY_NAME, e); } } private static class CodenamesLazyInitializer { /** * List of platform codename (first letter of) to API Level mappings. The list must be * sorted by the first letter. For codenames not in the list, the assumption is that the API * Level is incremented by one for every increase in the codename's first letter. */ @SuppressWarnings("unchecked") private static final Pair[] SORTED_CODENAMES_FIRST_CHAR_TO_API_LEVEL = new Pair[] { Pair.of('C', 2), Pair.of('D', 3), Pair.of('E', 4), Pair.of('F', 7), Pair.of('G', 8), Pair.of('H', 10), Pair.of('I', 13), Pair.of('J', 15), Pair.of('K', 18), Pair.of('L', 20), Pair.of('M', 22), Pair.of('N', 23), Pair.of('O', 25), }; private static final Comparator> CODENAME_FIRST_CHAR_COMPARATOR = new ByFirstComparator(); private static class ByFirstComparator implements Comparator> { @Override public int compare(Pair o1, Pair o2) { char c1 = o1.getFirst(); char c2 = o2.getFirst(); return c1 - c2; } } } /** * Returns the API Level corresponding to the provided platform codename. * *

This method is pessimistic. It returns a value one lower than the API Level with which the * platform is actually released (e.g., 23 for N which was released as API Level 24). This is * because new features which first appear in an API Level are not available in the early days * of that platform version's existence, when the platform only has a codename. Moreover, this * method currently doesn't differentiate between initial and MR releases, meaning API Level * returned for MR releases may be more than one lower than the API Level with which the * platform version is actually released. * * @throws CodenameMinSdkVersionException if the {@code codename} is not supported */ static int getMinSdkVersionForCodename(String codename) throws CodenameMinSdkVersionException { char firstChar = codename.isEmpty() ? ' ' : codename.charAt(0); // Codenames are case-sensitive. Only codenames starting with A-Z are supported for now. // We only look at the first letter of the codename as this is the most important letter. if ((firstChar >= 'A') && (firstChar <= 'Z')) { Pair[] sortedCodenamesFirstCharToApiLevel = CodenamesLazyInitializer.SORTED_CODENAMES_FIRST_CHAR_TO_API_LEVEL; int searchResult = Arrays.binarySearch( sortedCodenamesFirstCharToApiLevel, Pair.of(firstChar, null), // second element of the pair is ignored here CodenamesLazyInitializer.CODENAME_FIRST_CHAR_COMPARATOR); if (searchResult >= 0) { // Exact match -- searchResult is the index of the matching element return sortedCodenamesFirstCharToApiLevel[searchResult].getSecond(); } // Not an exact match -- searchResult is negative and is -(insertion index) - 1. // The element at insertionIndex - 1 (if present) is smaller than firstChar and the // element at insertionIndex (if present) is greater than firstChar. int insertionIndex = -1 - searchResult; // insertionIndex is in [0; array length] if (insertionIndex == 0) { // 'A' or 'B' -- never released to public return 1; } else { // The element at insertionIndex - 1 is the newest older codename. // API Level bumped by at least 1 for every change in the first letter of codename Pair newestOlderCodenameMapping = sortedCodenamesFirstCharToApiLevel[insertionIndex - 1]; char newestOlderCodenameFirstChar = newestOlderCodenameMapping.getFirst(); int newestOlderCodenameApiLevel = newestOlderCodenameMapping.getSecond(); return newestOlderCodenameApiLevel + (firstChar - newestOlderCodenameFirstChar); } } throw new CodenameMinSdkVersionException( "Unable to determine APK's minimum supported Android platform version" + " : Unsupported codename in " + ANDROID_MANIFEST_ZIP_ENTRY_NAME + "'s minSdkVersion: \"" + codename + "\"", codename); } } src/main/java/com/android/apksig/apk/CodenameMinSdkVersionException.java0100644 0000000 0000000 00000002707 13243353143 025410 0ustar000000000 0000000 /* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.apk; /** * Indicates that there was an issue determining the minimum Android platform version supported by * an APK because the version is specified as a codename, rather than as API Level number, and the * codename is in an unexpected format. */ public class CodenameMinSdkVersionException extends MinSdkVersionException { private static final long serialVersionUID = 1L; /** Encountered codename. */ private final String mCodename; /** * Constructs a new {@code MinSdkVersionCodenameException} with the provided message and * codename. */ public CodenameMinSdkVersionException(String message, String codename) { super(message); mCodename = codename; } /** * Returns the codename. */ public String getCodename() { return mCodename; } } src/main/java/com/android/apksig/apk/MinSdkVersionException.java0100644 0000000 0000000 00000002411 13243353143 023744 0ustar000000000 0000000 /* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.apk; /** * Indicates that there was an issue determining the minimum Android platform version supported by * an APK. */ public class MinSdkVersionException extends ApkFormatException { private static final long serialVersionUID = 1L; /** * Constructs a new {@code MinSdkVersionException} with the provided message. */ public MinSdkVersionException(String message) { super(message); } /** * Constructs a new {@code MinSdkVersionException} with the provided message and cause. */ public MinSdkVersionException(String message, Throwable cause) { super(message, cause); } } src/main/java/com/android/apksig/internal/0040755 0000000 0000000 00000000000 13243353143 017535 5ustar000000000 0000000 src/main/java/com/android/apksig/internal/apk/0040755 0000000 0000000 00000000000 13243353143 020310 5ustar000000000 0000000 src/main/java/com/android/apksig/internal/apk/AndroidBinXmlParser.java0100644 0000000 0000000 00000102006 13243353143 025016 0ustar000000000 0000000 /* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.internal.apk; import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * XML pull style parser of Android binary XML resources, such as {@code AndroidManifest.xml}. * *

For an input document, the parser outputs an event stream (see {@code EVENT_... constants} via * {@link #getEventType()} and {@link #next()} methods. Additional information about the current * event can be obtained via an assortment of getters, for example, {@link #getName()} or * {@link #getAttributeNameResourceId(int)}. */ public class AndroidBinXmlParser { /** Event: start of document. */ public static final int EVENT_START_DOCUMENT = 1; /** Event: end of document. */ public static final int EVENT_END_DOCUMENT = 2; /** Event: start of an element. */ public static final int EVENT_START_ELEMENT = 3; /** Event: end of an document. */ public static final int EVENT_END_ELEMENT = 4; /** Attribute value type is not supported by this parser. */ public static final int VALUE_TYPE_UNSUPPORTED = 0; /** Attribute value is a string. Use {@link #getAttributeStringValue(int)} to obtain it. */ public static final int VALUE_TYPE_STRING = 1; /** Attribute value is an integer. Use {@link #getAttributeIntValue(int)} to obtain it. */ public static final int VALUE_TYPE_INT = 2; /** * Attribute value is a resource reference. Use {@link #getAttributeIntValue(int)} to obtain it. */ public static final int VALUE_TYPE_REFERENCE = 3; /** Attribute value is a boolean. Use {@link #getAttributeBooleanValue(int)} to obtain it. */ public static final int VALUE_TYPE_BOOLEAN = 4; private static final long NO_NAMESPACE = 0xffffffffL; private final ByteBuffer mXml; private StringPool mStringPool; private ResourceMap mResourceMap; private int mDepth; private int mCurrentEvent = EVENT_START_DOCUMENT; private String mCurrentElementName; private String mCurrentElementNamespace; private int mCurrentElementAttributeCount; private List mCurrentElementAttributes; private ByteBuffer mCurrentElementAttributesContents; private int mCurrentElementAttrSizeBytes; /** * Constructs a new parser for the provided document. */ public AndroidBinXmlParser(ByteBuffer xml) throws XmlParserException { xml.order(ByteOrder.LITTLE_ENDIAN); Chunk resXmlChunk = null; while (xml.hasRemaining()) { Chunk chunk = Chunk.get(xml); if (chunk == null) { break; } if (chunk.getType() == Chunk.TYPE_RES_XML) { resXmlChunk = chunk; break; } } if (resXmlChunk == null) { throw new XmlParserException("No XML chunk in file"); } mXml = resXmlChunk.getContents(); } /** * Returns the depth of the current element. Outside of the root of the document the depth is * {@code 0}. The depth is incremented by {@code 1} before each {@code start element} event and * is decremented by {@code 1} after each {@code end element} event. */ public int getDepth() { return mDepth; } /** * Returns the type of the current event. See {@code EVENT_...} constants. */ public int getEventType() { return mCurrentEvent; } /** * Returns the local name of the current element or {@code null} if the current event does not * pertain to an element. */ public String getName() { if ((mCurrentEvent != EVENT_START_ELEMENT) && (mCurrentEvent != EVENT_END_ELEMENT)) { return null; } return mCurrentElementName; } /** * Returns the namespace of the current element or {@code null} if the current event does not * pertain to an element. Returns an empty string if the element is not associated with a * namespace. */ public String getNamespace() { if ((mCurrentEvent != EVENT_START_ELEMENT) && (mCurrentEvent != EVENT_END_ELEMENT)) { return null; } return mCurrentElementNamespace; } /** * Returns the number of attributes of the element associated with the current event or * {@code -1} if no element is associated with the current event. */ public int getAttributeCount() { if (mCurrentEvent != EVENT_START_ELEMENT) { return -1; } return mCurrentElementAttributeCount; } /** * Returns the resource ID corresponding to the name of the specified attribute of the current * element or {@code 0} if the name is not associated with a resource ID. * * @throws IndexOutOfBoundsException if the index is out of range or the current event is not a * {@code start element} event * @throws XmlParserException if a parsing error is occurred */ public int getAttributeNameResourceId(int index) throws XmlParserException { return getAttribute(index).getNameResourceId(); } /** * Returns the value type of the specified attribute of the current element. See * {@code VALUE_TYPE_...} constants. * * @throws IndexOutOfBoundsException if the index is out of range or the current event is not a * {@code start element} event * @throws XmlParserException if a parsing error is occurred */ public int getAttributeValueType(int index) throws XmlParserException { int type = getAttribute(index).getValueType(); switch (type) { case Attribute.TYPE_STRING: return VALUE_TYPE_STRING; case Attribute.TYPE_INT_DEC: case Attribute.TYPE_INT_HEX: case Attribute.TYPE_REFERENCE: return VALUE_TYPE_INT; case Attribute.TYPE_INT_BOOLEAN: return VALUE_TYPE_BOOLEAN; default: return VALUE_TYPE_UNSUPPORTED; } } /** * Returns the integer value of the specified attribute of the current element. See * {@code VALUE_TYPE_...} constants. * * @throws IndexOutOfBoundsException if the index is out of range or the current event is not a * {@code start element} event. * @throws XmlParserException if a parsing error is occurred */ public int getAttributeIntValue(int index) throws XmlParserException { return getAttribute(index).getIntValue(); } /** * Returns the boolean value of the specified attribute of the current element. See * {@code VALUE_TYPE_...} constants. * * @throws IndexOutOfBoundsException if the index is out of range or the current event is not a * {@code start element} event. * @throws XmlParserException if a parsing error is occurred */ public boolean getAttributeBooleanValue(int index) throws XmlParserException { return getAttribute(index).getBooleanValue(); } /** * Returns the string value of the specified attribute of the current element. See * {@code VALUE_TYPE_...} constants. * * @throws IndexOutOfBoundsException if the index is out of range or the current event is not a * {@code start element} event. * @throws XmlParserException if a parsing error is occurred */ public String getAttributeStringValue(int index) throws XmlParserException { return getAttribute(index).getStringValue(); } private Attribute getAttribute(int index) { if (mCurrentEvent != EVENT_START_ELEMENT) { throw new IndexOutOfBoundsException("Current event not a START_ELEMENT"); } if (index < 0) { throw new IndexOutOfBoundsException("index must be >= 0"); } if (index >= mCurrentElementAttributeCount) { throw new IndexOutOfBoundsException( "index must be <= attr count (" + mCurrentElementAttributeCount + ")"); } parseCurrentElementAttributesIfNotParsed(); return mCurrentElementAttributes.get(index); } /** * Advances to the next parsing event and returns its type. See {@code EVENT_...} constants. */ public int next() throws XmlParserException { // Decrement depth if the previous event was "end element". if (mCurrentEvent == EVENT_END_ELEMENT) { mDepth--; } // Read events from document, ignoring events that we don't report to caller. Stop at the // earliest event which we report to caller. while (mXml.hasRemaining()) { Chunk chunk = Chunk.get(mXml); if (chunk == null) { break; } switch (chunk.getType()) { case Chunk.TYPE_STRING_POOL: if (mStringPool != null) { throw new XmlParserException("Multiple string pools not supported"); } mStringPool = new StringPool(chunk); break; case Chunk.RES_XML_TYPE_START_ELEMENT: { if (mStringPool == null) { throw new XmlParserException( "Named element encountered before string pool"); } ByteBuffer contents = chunk.getContents(); if (contents.remaining() < 20) { throw new XmlParserException( "Start element chunk too short. Need at least 20 bytes. Available: " + contents.remaining() + " bytes"); } long nsId = getUnsignedInt32(contents); long nameId = getUnsignedInt32(contents); int attrStartOffset = getUnsignedInt16(contents); int attrSizeBytes = getUnsignedInt16(contents); int attrCount = getUnsignedInt16(contents); long attrEndOffset = attrStartOffset + ((long) attrCount) * attrSizeBytes; contents.position(0); if (attrStartOffset > contents.remaining()) { throw new XmlParserException( "Attributes start offset out of bounds: " + attrStartOffset + ", max: " + contents.remaining()); } if (attrEndOffset > contents.remaining()) { throw new XmlParserException( "Attributes end offset out of bounds: " + attrEndOffset + ", max: " + contents.remaining()); } mCurrentElementName = mStringPool.getString(nameId); mCurrentElementNamespace = (nsId == NO_NAMESPACE) ? "" : mStringPool.getString(nsId); mCurrentElementAttributeCount = attrCount; mCurrentElementAttributes = null; mCurrentElementAttrSizeBytes = attrSizeBytes; mCurrentElementAttributesContents = sliceFromTo(contents, attrStartOffset, attrEndOffset); mDepth++; mCurrentEvent = EVENT_START_ELEMENT; return mCurrentEvent; } case Chunk.RES_XML_TYPE_END_ELEMENT: { if (mStringPool == null) { throw new XmlParserException( "Named element encountered before string pool"); } ByteBuffer contents = chunk.getContents(); if (contents.remaining() < 8) { throw new XmlParserException( "End element chunk too short. Need at least 8 bytes. Available: " + contents.remaining() + " bytes"); } long nsId = getUnsignedInt32(contents); long nameId = getUnsignedInt32(contents); mCurrentElementName = mStringPool.getString(nameId); mCurrentElementNamespace = (nsId == NO_NAMESPACE) ? "" : mStringPool.getString(nsId); mCurrentEvent = EVENT_END_ELEMENT; mCurrentElementAttributes = null; mCurrentElementAttributesContents = null; return mCurrentEvent; } case Chunk.RES_XML_TYPE_RESOURCE_MAP: if (mResourceMap != null) { throw new XmlParserException("Multiple resource maps not supported"); } mResourceMap = new ResourceMap(chunk); break; } } mCurrentEvent = EVENT_END_DOCUMENT; return mCurrentEvent; } private void parseCurrentElementAttributesIfNotParsed() { if (mCurrentElementAttributes != null) { return; } mCurrentElementAttributes = new ArrayList<>(mCurrentElementAttributeCount); for (int i = 0; i < mCurrentElementAttributeCount; i++) { int startPosition = i * mCurrentElementAttrSizeBytes; ByteBuffer attr = sliceFromTo( mCurrentElementAttributesContents, startPosition, startPosition + mCurrentElementAttrSizeBytes); @SuppressWarnings("unused") long nsId = getUnsignedInt32(attr); long nameId = getUnsignedInt32(attr); attr.position(attr.position() + 7); // skip ignored fields int valueType = getUnsignedInt8(attr); long valueData = getUnsignedInt32(attr); mCurrentElementAttributes.add( new Attribute( nameId, valueType, (int) valueData, mStringPool, mResourceMap)); } } private static class Attribute { private static final int TYPE_REFERENCE = 1; private static final int TYPE_STRING = 3; private static final int TYPE_INT_DEC = 0x10; private static final int TYPE_INT_HEX = 0x11; private static final int TYPE_INT_BOOLEAN = 0x12; private final long mNameId; private final int mValueType; private final int mValueData; private final StringPool mStringPool; private final ResourceMap mResourceMap; private Attribute( long nameId, int valueType, int valueData, StringPool stringPool, ResourceMap resourceMap) { mNameId = nameId; mValueType = valueType; mValueData = valueData; mStringPool = stringPool; mResourceMap = resourceMap; } public int getNameResourceId() { return (mResourceMap != null) ? mResourceMap.getResourceId(mNameId) : 0; } public int getValueType() { return mValueType; } public int getIntValue() throws XmlParserException { switch (mValueType) { case TYPE_REFERENCE: case TYPE_INT_DEC: case TYPE_INT_HEX: case TYPE_INT_BOOLEAN: return mValueData; default: throw new XmlParserException("Cannot coerce to int: value type " + mValueType); } } public boolean getBooleanValue() throws XmlParserException { switch (mValueType) { case TYPE_INT_BOOLEAN: return mValueData != 0; default: throw new XmlParserException( "Cannot coerce to boolean: value type " + mValueType); } } public String getStringValue() throws XmlParserException { switch (mValueType) { case TYPE_STRING: return mStringPool.getString(mValueData & 0xffffffffL); case TYPE_INT_DEC: return Integer.toString(mValueData); case TYPE_INT_HEX: return "0x" + Integer.toHexString(mValueData); case TYPE_INT_BOOLEAN: return Boolean.toString(mValueData != 0); case TYPE_REFERENCE: return "@" + Integer.toHexString(mValueData); default: throw new XmlParserException( "Cannot coerce to string: value type " + mValueType); } } } /** * Chunk of a document. Each chunk is tagged with a type and consists of a header followed by * contents. */ private static class Chunk { public static final int TYPE_STRING_POOL = 1; public static final int TYPE_RES_XML = 3; public static final int RES_XML_TYPE_START_ELEMENT = 0x0102; public static final int RES_XML_TYPE_END_ELEMENT = 0x0103; public static final int RES_XML_TYPE_RESOURCE_MAP = 0x0180; static final int HEADER_MIN_SIZE_BYTES = 8; private final int mType; private final ByteBuffer mHeader; private final ByteBuffer mContents; public Chunk(int type, ByteBuffer header, ByteBuffer contents) { mType = type; mHeader = header; mContents = contents; } public ByteBuffer getContents() { ByteBuffer result = mContents.slice(); result.order(mContents.order()); return result; } public ByteBuffer getHeader() { ByteBuffer result = mHeader.slice(); result.order(mHeader.order()); return result; } public int getType() { return mType; } /** * Consumes the chunk located at the current position of the input and returns the chunk * or {@code null} if there is no chunk left in the input. * * @throws XmlParserException if the chunk is malformed */ public static Chunk get(ByteBuffer input) throws XmlParserException { if (input.remaining() < HEADER_MIN_SIZE_BYTES) { // Android ignores the last chunk if its header is too big to fit into the file input.position(input.limit()); return null; } int originalPosition = input.position(); int type = getUnsignedInt16(input); int headerSize = getUnsignedInt16(input); long chunkSize = getUnsignedInt32(input); long chunkRemaining = chunkSize - 8; if (chunkRemaining > input.remaining()) { // Android ignores the last chunk if it's too big to fit into the file input.position(input.limit()); return null; } if (headerSize < HEADER_MIN_SIZE_BYTES) { throw new XmlParserException( "Malformed chunk: header too short: " + headerSize + " bytes"); } else if (headerSize > chunkSize) { throw new XmlParserException( "Malformed chunk: header too long: " + headerSize + " bytes. Chunk size: " + chunkSize + " bytes"); } int contentStartPosition = originalPosition + headerSize; long chunkEndPosition = originalPosition + chunkSize; Chunk chunk = new Chunk( type, sliceFromTo(input, originalPosition, contentStartPosition), sliceFromTo(input, contentStartPosition, chunkEndPosition)); input.position((int) chunkEndPosition); return chunk; } } /** * String pool of a document. Strings are referenced by their {@code 0}-based index in the pool. */ private static class StringPool { private static final int FLAG_UTF8 = 1 << 8; private final ByteBuffer mChunkContents; private final ByteBuffer mStringsSection; private final int mStringCount; private final boolean mUtf8Encoded; private final Map mCachedStrings = new HashMap<>(); /** * Constructs a new string pool from the provided chunk. * * @throws XmlParserException if a parsing error occurred */ public StringPool(Chunk chunk) throws XmlParserException { ByteBuffer header = chunk.getHeader(); int headerSizeBytes = header.remaining(); header.position(Chunk.HEADER_MIN_SIZE_BYTES); if (header.remaining() < 20) { throw new XmlParserException( "XML chunk's header too short. Required at least 20 bytes. Available: " + header.remaining() + " bytes"); } long stringCount = getUnsignedInt32(header); if (stringCount > Integer.MAX_VALUE) { throw new XmlParserException("Too many strings: " + stringCount); } mStringCount = (int) stringCount; long styleCount = getUnsignedInt32(header); if (styleCount > Integer.MAX_VALUE) { throw new XmlParserException("Too many styles: " + styleCount); } long flags = getUnsignedInt32(header); long stringsStartOffset = getUnsignedInt32(header); long stylesStartOffset = getUnsignedInt32(header); ByteBuffer contents = chunk.getContents(); if (mStringCount > 0) { int stringsSectionStartOffsetInContents = (int) (stringsStartOffset - headerSizeBytes); int stringsSectionEndOffsetInContents; if (styleCount > 0) { // Styles section follows the strings section if (stylesStartOffset < stringsStartOffset) { throw new XmlParserException( "Styles offset (" + stylesStartOffset + ") < strings offset (" + stringsStartOffset + ")"); } stringsSectionEndOffsetInContents = (int) (stylesStartOffset - headerSizeBytes); } else { stringsSectionEndOffsetInContents = contents.remaining(); } mStringsSection = sliceFromTo( contents, stringsSectionStartOffsetInContents, stringsSectionEndOffsetInContents); } else { mStringsSection = ByteBuffer.allocate(0); } mUtf8Encoded = (flags & FLAG_UTF8) != 0; mChunkContents = contents; } /** * Returns the string located at the specified {@code 0}-based index in this pool. * * @throws XmlParserException if the string does not exist or cannot be decoded */ public String getString(long index) throws XmlParserException { if (index < 0) { throw new XmlParserException("Unsuported string index: " + index); } else if (index >= mStringCount) { throw new XmlParserException( "Unsuported string index: " + index + ", max: " + (mStringCount - 1)); } int idx = (int) index; String result = mCachedStrings.get(idx); if (result != null) { return result; } long offsetInStringsSection = getUnsignedInt32(mChunkContents, idx * 4); if (offsetInStringsSection >= mStringsSection.capacity()) { throw new XmlParserException( "Offset of string idx " + idx + " out of bounds: " + offsetInStringsSection + ", max: " + (mStringsSection.capacity() - 1)); } mStringsSection.position((int) offsetInStringsSection); result = (mUtf8Encoded) ? getLengthPrefixedUtf8EncodedString(mStringsSection) : getLengthPrefixedUtf16EncodedString(mStringsSection); mCachedStrings.put(idx, result); return result; } private static String getLengthPrefixedUtf16EncodedString(ByteBuffer encoded) throws XmlParserException { // If the length (in uint16s) is 0x7fff or lower, it is stored as a single uint16. // Otherwise, it is stored as a big-endian uint32 with highest bit set. Thus, the range // of supported values is 0 to 0x7fffffff inclusive. int lengthChars = getUnsignedInt16(encoded); if ((lengthChars & 0x8000) != 0) { lengthChars = ((lengthChars & 0x7fff) << 16) | getUnsignedInt16(encoded); } if (lengthChars > Integer.MAX_VALUE / 2) { throw new XmlParserException("String too long: " + lengthChars + " uint16s"); } int lengthBytes = lengthChars * 2; byte[] arr; int arrOffset; if (encoded.hasArray()) { arr = encoded.array(); arrOffset = encoded.arrayOffset() + encoded.position(); encoded.position(encoded.position() + lengthBytes); } else { arr = new byte[lengthBytes]; arrOffset = 0; encoded.get(arr); } // Reproduce the behavior of Android runtime which requires that the UTF-16 encoded // array of bytes is NULL terminated. if ((arr[arrOffset + lengthBytes] != 0) || (arr[arrOffset + lengthBytes + 1] != 0)) { throw new XmlParserException("UTF-16 encoded form of string not NULL terminated"); } try { return new String(arr, arrOffset, lengthBytes, "UTF-16LE"); } catch (UnsupportedEncodingException e) { throw new RuntimeException("UTF-16LE character encoding not supported", e); } } private static String getLengthPrefixedUtf8EncodedString(ByteBuffer encoded) throws XmlParserException { // If the length (in bytes) is 0x7f or lower, it is stored as a single uint8. Otherwise, // it is stored as a big-endian uint16 with highest bit set. Thus, the range of // supported values is 0 to 0x7fff inclusive. // Skip UTF-16 encoded length (in uint16s) int lengthBytes = getUnsignedInt8(encoded); if ((lengthBytes & 0x80) != 0) { lengthBytes = ((lengthBytes & 0x7f) << 8) | getUnsignedInt8(encoded); } // Read UTF-8 encoded length (in bytes) lengthBytes = getUnsignedInt8(encoded); if ((lengthBytes & 0x80) != 0) { lengthBytes = ((lengthBytes & 0x7f) << 8) | getUnsignedInt8(encoded); } byte[] arr; int arrOffset; if (encoded.hasArray()) { arr = encoded.array(); arrOffset = encoded.arrayOffset() + encoded.position(); encoded.position(encoded.position() + lengthBytes); } else { arr = new byte[lengthBytes]; arrOffset = 0; encoded.get(arr); } // Reproduce the behavior of Android runtime which requires that the UTF-8 encoded array // of bytes is NULL terminated. if (arr[arrOffset + lengthBytes] != 0) { throw new XmlParserException("UTF-8 encoded form of string not NULL terminated"); } try { return new String(arr, arrOffset, lengthBytes, "UTF-8"); } catch (UnsupportedEncodingException e) { throw new RuntimeException("UTF-8 character encoding not supported", e); } } } /** * Resource map of a document. Resource IDs are referenced by their {@code 0}-based index in the * map. */ private static class ResourceMap { private final ByteBuffer mChunkContents; private final int mEntryCount; /** * Constructs a new resource map from the provided chunk. * * @throws XmlParserException if a parsing error occurred */ public ResourceMap(Chunk chunk) throws XmlParserException { mChunkContents = chunk.getContents().slice(); mChunkContents.order(chunk.getContents().order()); // Each entry of the map is four bytes long, containing the int32 resource ID. mEntryCount = mChunkContents.remaining() / 4; } /** * Returns the resource ID located at the specified {@code 0}-based index in this pool or * {@code 0} if the index is out of range. */ public int getResourceId(long index) { if ((index < 0) || (index >= mEntryCount)) { return 0; } int idx = (int) index; // Each entry of the map is four bytes long, containing the int32 resource ID. return mChunkContents.getInt(idx * 4); } } /** * Returns new byte buffer whose content is a shared subsequence of this buffer's content * between the specified start (inclusive) and end (exclusive) positions. As opposed to * {@link ByteBuffer#slice()}, the returned buffer's byte order is the same as the source * buffer's byte order. */ private static ByteBuffer sliceFromTo(ByteBuffer source, long start, long end) { if (start < 0) { throw new IllegalArgumentException("start: " + start); } if (end < start) { throw new IllegalArgumentException("end < start: " + end + " < " + start); } int capacity = source.capacity(); if (end > source.capacity()) { throw new IllegalArgumentException("end > capacity: " + end + " > " + capacity); } return sliceFromTo(source, (int) start, (int) end); } /** * Returns new byte buffer whose content is a shared subsequence of this buffer's content * between the specified start (inclusive) and end (exclusive) positions. As opposed to * {@link ByteBuffer#slice()}, the returned buffer's byte order is the same as the source * buffer's byte order. */ private static ByteBuffer sliceFromTo(ByteBuffer source, int start, int end) { if (start < 0) { throw new IllegalArgumentException("start: " + start); } if (end < start) { throw new IllegalArgumentException("end < start: " + end + " < " + start); } int capacity = source.capacity(); if (end > source.capacity()) { throw new IllegalArgumentException("end > capacity: " + end + " > " + capacity); } int originalLimit = source.limit(); int originalPosition = source.position(); try { source.position(0); source.limit(end); source.position(start); ByteBuffer result = source.slice(); result.order(source.order()); return result; } finally { source.position(0); source.limit(originalLimit); source.position(originalPosition); } } private static int getUnsignedInt8(ByteBuffer buffer) { return buffer.get() & 0xff; } private static int getUnsignedInt16(ByteBuffer buffer) { return buffer.getShort() & 0xffff; } private static long getUnsignedInt32(ByteBuffer buffer) { return buffer.getInt() & 0xffffffffL; } private static long getUnsignedInt32(ByteBuffer buffer, int position) { return buffer.getInt(position) & 0xffffffffL; } /** * Indicates that an error occurred while parsing a document. */ public static class XmlParserException extends Exception { private static final long serialVersionUID = 1L; public XmlParserException(String message) { super(message); } public XmlParserException(String message, Throwable cause) { super(message, cause); } } } src/main/java/com/android/apksig/internal/apk/v1/0040755 0000000 0000000 00000000000 13243353143 020636 5ustar000000000 0000000 src/main/java/com/android/apksig/internal/apk/v1/DigestAlgorithm.java0100644 0000000 0000000 00000004455 13243353143 024574 0ustar000000000 0000000 /* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.internal.apk.v1; import java.util.Comparator; /** * Digest algorithm used with JAR signing (aka v1 signing scheme). */ public enum DigestAlgorithm { /** SHA-1 */ SHA1("SHA-1"), /** SHA2-256 */ SHA256("SHA-256"); private final String mJcaMessageDigestAlgorithm; private DigestAlgorithm(String jcaMessageDigestAlgoritm) { mJcaMessageDigestAlgorithm = jcaMessageDigestAlgoritm; } /** * Returns the {@link java.security.MessageDigest} algorithm represented by this digest * algorithm. */ String getJcaMessageDigestAlgorithm() { return mJcaMessageDigestAlgorithm; } public static Comparator BY_STRENGTH_COMPARATOR = new StrengthComparator(); private static class StrengthComparator implements Comparator { @Override public int compare(DigestAlgorithm a1, DigestAlgorithm a2) { switch (a1) { case SHA1: switch (a2) { case SHA1: return 0; case SHA256: return -1; } throw new RuntimeException("Unsupported algorithm: " + a2); case SHA256: switch (a2) { case SHA1: return 1; case SHA256: return 0; } throw new RuntimeException("Unsupported algorithm: " + a2); default: throw new RuntimeException("Unsupported algorithm: " + a1); } } } } src/main/java/com/android/apksig/internal/apk/v1/V1SchemeSigner.java0100644 0000000 0000000 00000074753 13243353143 024301 0ustar000000000 0000000 /* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.internal.apk.v1; import com.android.apksig.apk.ApkFormatException; import com.android.apksig.internal.asn1.Asn1DerEncoder; import com.android.apksig.internal.asn1.Asn1EncodingException; import com.android.apksig.internal.asn1.Asn1OpaqueObject; import com.android.apksig.internal.asn1.ber.BerEncoding; import com.android.apksig.internal.jar.ManifestWriter; import com.android.apksig.internal.jar.SignatureFileWriter; import com.android.apksig.internal.pkcs7.AlgorithmIdentifier; import com.android.apksig.internal.pkcs7.ContentInfo; import com.android.apksig.internal.pkcs7.EncapsulatedContentInfo; import com.android.apksig.internal.pkcs7.IssuerAndSerialNumber; import com.android.apksig.internal.pkcs7.Pkcs7Constants; import com.android.apksig.internal.pkcs7.SignedData; import com.android.apksig.internal.pkcs7.SignerIdentifier; import com.android.apksig.internal.pkcs7.SignerInfo; import com.android.apksig.internal.util.Pair; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.security.InvalidKeyException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.Signature; import java.security.SignatureException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Base64; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.SortedMap; import java.util.TreeMap; import java.util.jar.Attributes; import java.util.jar.Manifest; import javax.security.auth.x500.X500Principal; /** * APK signer which uses JAR signing (aka v1 signing scheme). * * @see Signed JAR File */ public abstract class V1SchemeSigner { public static final String MANIFEST_ENTRY_NAME = "META-INF/MANIFEST.MF"; private static final Attributes.Name ATTRIBUTE_NAME_CREATED_BY = new Attributes.Name("Created-By"); private static final String ATTRIBUTE_VALUE_MANIFEST_VERSION = "1.0"; private static final String ATTRIBUTE_VALUE_SIGNATURE_VERSION = "1.0"; static final String SF_ATTRIBUTE_NAME_ANDROID_APK_SIGNED_NAME_STR = "X-Android-APK-Signed"; private static final Attributes.Name SF_ATTRIBUTE_NAME_ANDROID_APK_SIGNED_NAME = new Attributes.Name(SF_ATTRIBUTE_NAME_ANDROID_APK_SIGNED_NAME_STR); /** * Signer configuration. */ public static class SignerConfig { /** Name. */ public String name; /** Private key. */ public PrivateKey privateKey; /** * Certificates, with the first certificate containing the public key corresponding to * {@link #privateKey}. */ public List certificates; /** * Digest algorithm used for the signature. */ public DigestAlgorithm signatureDigestAlgorithm; } /** Hidden constructor to prevent instantiation. */ private V1SchemeSigner() {} /** * Gets the JAR signing digest algorithm to be used for signing an APK using the provided key. * * @param minSdkVersion minimum API Level of the platform on which the APK may be installed (see * AndroidManifest.xml minSdkVersion attribute) * * @throws InvalidKeyException if the provided key is not suitable for signing APKs using * JAR signing (aka v1 signature scheme) */ public static DigestAlgorithm getSuggestedSignatureDigestAlgorithm( PublicKey signingKey, int minSdkVersion) throws InvalidKeyException { String keyAlgorithm = signingKey.getAlgorithm(); if ("RSA".equalsIgnoreCase(keyAlgorithm)) { // Prior to API Level 18, only SHA-1 can be used with RSA. if (minSdkVersion < 18) { return DigestAlgorithm.SHA1; } return DigestAlgorithm.SHA256; } else if ("DSA".equalsIgnoreCase(keyAlgorithm)) { // Prior to API Level 21, only SHA-1 can be used with DSA if (minSdkVersion < 21) { return DigestAlgorithm.SHA1; } else { return DigestAlgorithm.SHA256; } } else if ("EC".equalsIgnoreCase(keyAlgorithm)) { if (minSdkVersion < 18) { throw new InvalidKeyException( "ECDSA signatures only supported for minSdkVersion 18 and higher"); } return DigestAlgorithm.SHA256; } else { throw new InvalidKeyException("Unsupported key algorithm: " + keyAlgorithm); } } /** * Returns a safe version of the provided signer name. */ public static String getSafeSignerName(String name) { if (name.isEmpty()) { throw new IllegalArgumentException("Empty name"); } // According to https://docs.oracle.com/javase/tutorial/deployment/jar/signing.html, the // name must not be longer than 8 characters and may contain only A-Z, 0-9, _, and -. StringBuilder result = new StringBuilder(); char[] nameCharsUpperCase = name.toUpperCase(Locale.US).toCharArray(); for (int i = 0; i < Math.min(nameCharsUpperCase.length, 8); i++) { char c = nameCharsUpperCase[i]; if (((c >= 'A') && (c <= 'Z')) || ((c >= '0') && (c <= '9')) || (c == '-') || (c == '_')) { result.append(c); } else { result.append('_'); } } return result.toString(); } /** * Returns a new {@link MessageDigest} instance corresponding to the provided digest algorithm. */ private static MessageDigest getMessageDigestInstance(DigestAlgorithm digestAlgorithm) throws NoSuchAlgorithmException { String jcaAlgorithm = digestAlgorithm.getJcaMessageDigestAlgorithm(); return MessageDigest.getInstance(jcaAlgorithm); } /** * Returns the JCA {@link MessageDigest} algorithm corresponding to the provided digest * algorithm. */ public static String getJcaMessageDigestAlgorithm(DigestAlgorithm digestAlgorithm) { return digestAlgorithm.getJcaMessageDigestAlgorithm(); } /** * Returns {@code true} if the provided JAR entry must be mentioned in signed JAR archive's * manifest. */ public static boolean isJarEntryDigestNeededInManifest(String entryName) { // See https://docs.oracle.com/javase/8/docs/technotes/guides/jar/jar.html#Signed_JAR_File // Entries which represent directories sould not be listed in the manifest. if (entryName.endsWith("/")) { return false; } // Entries outside of META-INF must be listed in the manifest. if (!entryName.startsWith("META-INF/")) { return true; } // Entries in subdirectories of META-INF must be listed in the manifest. if (entryName.indexOf('/', "META-INF/".length()) != -1) { return true; } // Ignored file names (case-insensitive) in META-INF directory: // MANIFEST.MF // *.SF // *.RSA // *.DSA // *.EC // SIG-* String fileNameLowerCase = entryName.substring("META-INF/".length()).toLowerCase(Locale.US); if (("manifest.mf".equals(fileNameLowerCase)) || (fileNameLowerCase.endsWith(".sf")) || (fileNameLowerCase.endsWith(".rsa")) || (fileNameLowerCase.endsWith(".dsa")) || (fileNameLowerCase.endsWith(".ec")) || (fileNameLowerCase.startsWith("sig-"))) { return false; } return true; } /** * Signs the provided APK using JAR signing (aka v1 signature scheme) and returns the list of * JAR entries which need to be added to the APK as part of the signature. * * @param signerConfigs signer configurations, one for each signer. At least one signer config * must be provided. * * @throws ApkFormatException if the source manifest is malformed * @throws NoSuchAlgorithmException if a required cryptographic algorithm implementation is * missing * @throws InvalidKeyException if a signing key is not suitable for this signature scheme or * cannot be used in general * @throws SignatureException if an error occurs when computing digests of generating * signatures */ public static List> sign( List signerConfigs, DigestAlgorithm jarEntryDigestAlgorithm, Map jarEntryDigests, List apkSigningSchemeIds, byte[] sourceManifestBytes, String createdBy) throws NoSuchAlgorithmException, ApkFormatException, InvalidKeyException, CertificateException, SignatureException { if (signerConfigs.isEmpty()) { throw new IllegalArgumentException("At least one signer config must be provided"); } OutputManifestFile manifest = generateManifestFile( jarEntryDigestAlgorithm, jarEntryDigests, sourceManifestBytes); return signManifest( signerConfigs, jarEntryDigestAlgorithm, apkSigningSchemeIds, createdBy, manifest); } /** * Signs the provided APK using JAR signing (aka v1 signature scheme) and returns the list of * JAR entries which need to be added to the APK as part of the signature. * * @param signerConfigs signer configurations, one for each signer. At least one signer config * must be provided. * * @throws InvalidKeyException if a signing key is not suitable for this signature scheme or * cannot be used in general * @throws SignatureException if an error occurs when computing digests of generating * signatures */ public static List> signManifest( List signerConfigs, DigestAlgorithm digestAlgorithm, List apkSigningSchemeIds, String createdBy, OutputManifestFile manifest) throws NoSuchAlgorithmException, InvalidKeyException, CertificateException, SignatureException { if (signerConfigs.isEmpty()) { throw new IllegalArgumentException("At least one signer config must be provided"); } // For each signer output .SF and .(RSA|DSA|EC) file, then output MANIFEST.MF. List> signatureJarEntries = new ArrayList<>(2 * signerConfigs.size() + 1); byte[] sfBytes = generateSignatureFile(apkSigningSchemeIds, digestAlgorithm, createdBy, manifest); for (SignerConfig signerConfig : signerConfigs) { String signerName = signerConfig.name; byte[] signatureBlock; try { signatureBlock = generateSignatureBlock(signerConfig, sfBytes); } catch (InvalidKeyException e) { throw new InvalidKeyException( "Failed to sign using signer \"" + signerName + "\"", e); } catch (CertificateException e) { throw new CertificateException( "Failed to sign using signer \"" + signerName + "\"", e); } catch (SignatureException e) { throw new SignatureException( "Failed to sign using signer \"" + signerName + "\"", e); } signatureJarEntries.add(Pair.of("META-INF/" + signerName + ".SF", sfBytes)); PublicKey publicKey = signerConfig.certificates.get(0).getPublicKey(); String signatureBlockFileName = "META-INF/" + signerName + "." + publicKey.getAlgorithm().toUpperCase(Locale.US); signatureJarEntries.add( Pair.of(signatureBlockFileName, signatureBlock)); } signatureJarEntries.add(Pair.of(MANIFEST_ENTRY_NAME, manifest.contents)); return signatureJarEntries; } /** * Returns the names of JAR entries which this signer will produce as part of v1 signature. */ public static Set getOutputEntryNames(List signerConfigs) { Set result = new HashSet<>(2 * signerConfigs.size() + 1); for (SignerConfig signerConfig : signerConfigs) { String signerName = signerConfig.name; result.add("META-INF/" + signerName + ".SF"); PublicKey publicKey = signerConfig.certificates.get(0).getPublicKey(); String signatureBlockFileName = "META-INF/" + signerName + "." + publicKey.getAlgorithm().toUpperCase(Locale.US); result.add(signatureBlockFileName); } result.add(MANIFEST_ENTRY_NAME); return result; } /** * Generated and returns the {@code META-INF/MANIFEST.MF} file based on the provided (optional) * input {@code MANIFEST.MF} and digests of JAR entries covered by the manifest. */ public static OutputManifestFile generateManifestFile( DigestAlgorithm jarEntryDigestAlgorithm, Map jarEntryDigests, byte[] sourceManifestBytes) throws ApkFormatException { Manifest sourceManifest = null; if (sourceManifestBytes != null) { try { sourceManifest = new Manifest(new ByteArrayInputStream(sourceManifestBytes)); } catch (IOException e) { throw new ApkFormatException("Malformed source META-INF/MANIFEST.MF", e); } } ByteArrayOutputStream manifestOut = new ByteArrayOutputStream(); Attributes mainAttrs = new Attributes(); // Copy the main section from the source manifest (if provided). Otherwise use defaults. // NOTE: We don't output our own Created-By header because this signer did not create the // JAR/APK being signed -- the signer only adds signatures to the already existing // JAR/APK. if (sourceManifest != null) { mainAttrs.putAll(sourceManifest.getMainAttributes()); } else { mainAttrs.put(Attributes.Name.MANIFEST_VERSION, ATTRIBUTE_VALUE_MANIFEST_VERSION); } try { ManifestWriter.writeMainSection(manifestOut, mainAttrs); } catch (IOException e) { throw new RuntimeException("Failed to write in-memory MANIFEST.MF", e); } List sortedEntryNames = new ArrayList<>(jarEntryDigests.keySet()); Collections.sort(sortedEntryNames); SortedMap invidualSectionsContents = new TreeMap<>(); String entryDigestAttributeName = getEntryDigestAttributeName(jarEntryDigestAlgorithm); for (String entryName : sortedEntryNames) { checkEntryNameValid(entryName); byte[] entryDigest = jarEntryDigests.get(entryName); Attributes entryAttrs = new Attributes(); entryAttrs.putValue( entryDigestAttributeName, Base64.getEncoder().encodeToString(entryDigest)); ByteArrayOutputStream sectionOut = new ByteArrayOutputStream(); byte[] sectionBytes; try { ManifestWriter.writeIndividualSection(sectionOut, entryName, entryAttrs); sectionBytes = sectionOut.toByteArray(); manifestOut.write(sectionBytes); } catch (IOException e) { throw new RuntimeException("Failed to write in-memory MANIFEST.MF", e); } invidualSectionsContents.put(entryName, sectionBytes); } OutputManifestFile result = new OutputManifestFile(); result.contents = manifestOut.toByteArray(); result.mainSectionAttributes = mainAttrs; result.individualSectionsContents = invidualSectionsContents; return result; } private static void checkEntryNameValid(String name) throws ApkFormatException { // JAR signing spec says CR, LF, and NUL are not permitted in entry names // CR or LF in entry names will result in malformed MANIFEST.MF and .SF files because there // is no way to escape characters in MANIFEST.MF and .SF files. NUL can, presumably, cause // issues when parsing using C and C++ like languages. for (char c : name.toCharArray()) { if ((c == '\r') || (c == '\n') || (c == 0)) { throw new ApkFormatException( String.format( "Unsupported character 0x%1$02x in ZIP entry name \"%2$s\"", (int) c, name)); } } } public static class OutputManifestFile { public byte[] contents; public SortedMap individualSectionsContents; public Attributes mainSectionAttributes; } private static byte[] generateSignatureFile( List apkSignatureSchemeIds, DigestAlgorithm manifestDigestAlgorithm, String createdBy, OutputManifestFile manifest) throws NoSuchAlgorithmException { Manifest sf = new Manifest(); Attributes mainAttrs = sf.getMainAttributes(); mainAttrs.put(Attributes.Name.SIGNATURE_VERSION, ATTRIBUTE_VALUE_SIGNATURE_VERSION); mainAttrs.put(ATTRIBUTE_NAME_CREATED_BY, createdBy); if (!apkSignatureSchemeIds.isEmpty()) { // Add APK Signature Scheme v2 (and newer) signature stripping protection. // This attribute indicates that this APK is supposed to have been signed using one or // more APK-specific signature schemes in addition to the standard JAR signature scheme // used by this code. APK signature verifier should reject the APK if it does not // contain a signature for the signature scheme the verifier prefers out of this set. StringBuilder attrValue = new StringBuilder(); for (int id : apkSignatureSchemeIds) { if (attrValue.length() > 0) { attrValue.append(", "); } attrValue.append(String.valueOf(id)); } mainAttrs.put( SF_ATTRIBUTE_NAME_ANDROID_APK_SIGNED_NAME, attrValue.toString()); } // Add main attribute containing the digest of MANIFEST.MF. MessageDigest md = getMessageDigestInstance(manifestDigestAlgorithm); mainAttrs.putValue( getManifestDigestAttributeName(manifestDigestAlgorithm), Base64.getEncoder().encodeToString(md.digest(manifest.contents))); ByteArrayOutputStream out = new ByteArrayOutputStream(); try { SignatureFileWriter.writeMainSection(out, mainAttrs); } catch (IOException e) { throw new RuntimeException("Failed to write in-memory .SF file", e); } String entryDigestAttributeName = getEntryDigestAttributeName(manifestDigestAlgorithm); for (Map.Entry manifestSection : manifest.individualSectionsContents.entrySet()) { String sectionName = manifestSection.getKey(); byte[] sectionContents = manifestSection.getValue(); byte[] sectionDigest = md.digest(sectionContents); Attributes attrs = new Attributes(); attrs.putValue( entryDigestAttributeName, Base64.getEncoder().encodeToString(sectionDigest)); try { SignatureFileWriter.writeIndividualSection(out, sectionName, attrs); } catch (IOException e) { throw new RuntimeException("Failed to write in-memory .SF file", e); } } // A bug in the java.util.jar implementation of Android platforms up to version 1.6 will // cause a spurious IOException to be thrown if the length of the signature file is a // multiple of 1024 bytes. As a workaround, add an extra CRLF in this case. if ((out.size() > 0) && ((out.size() % 1024) == 0)) { try { SignatureFileWriter.writeSectionDelimiter(out); } catch (IOException e) { throw new RuntimeException("Failed to write to ByteArrayOutputStream", e); } } return out.toByteArray(); } /** ASN.1 DER-encoded {@code NULL}. */ private static final Asn1OpaqueObject ASN1_DER_NULL = new Asn1OpaqueObject(new byte[] {BerEncoding.TAG_NUMBER_NULL, 0}); /** * Generates the CMS PKCS #7 signature block corresponding to the provided signature file and * signing configuration. */ private static byte[] generateSignatureBlock( SignerConfig signerConfig, byte[] signatureFileBytes) throws NoSuchAlgorithmException, InvalidKeyException, CertificateException, SignatureException { // Obtain relevant bits of signing configuration List signerCerts = signerConfig.certificates; X509Certificate signingCert = signerCerts.get(0); PublicKey publicKey = signingCert.getPublicKey(); DigestAlgorithm digestAlgorithm = signerConfig.signatureDigestAlgorithm; Pair signatureAlgs = getSignerInfoSignatureAlgorithm(publicKey, digestAlgorithm); String jcaSignatureAlgorithm = signatureAlgs.getFirst(); // Generate the cryptographic signature of the signature file byte[] signatureBytes; try { Signature signature = Signature.getInstance(jcaSignatureAlgorithm); signature.initSign(signerConfig.privateKey); signature.update(signatureFileBytes); signatureBytes = signature.sign(); } catch (InvalidKeyException e) { throw new InvalidKeyException("Failed to sign using " + jcaSignatureAlgorithm, e); } catch (SignatureException e) { throw new SignatureException("Failed to sign using " + jcaSignatureAlgorithm, e); } // Verify the signature against the public key in the signing certificate try { Signature signature = Signature.getInstance(jcaSignatureAlgorithm); signature.initVerify(publicKey); signature.update(signatureFileBytes); if (!signature.verify(signatureBytes)) { throw new SignatureException("Signature did not verify"); } } catch (InvalidKeyException e) { throw new InvalidKeyException( "Failed to verify generated " + jcaSignatureAlgorithm + " signature using" + " public key from certificate", e); } catch (SignatureException e) { throw new SignatureException( "Failed to verify generated " + jcaSignatureAlgorithm + " signature using" + " public key from certificate", e); } // Wrap the signature into the JAR signature block which is created according to CMS PKCS #7 // RFC 5652. // The high-level simplified structure is as follows: // ContentInfo // digestAlgorithm // SignedData // bag of certificates // SignerInfo // signing cert issuer and serial number (for locating the cert in the above bag) // digestAlgorithm // signatureAlgorithm // signature try { SignerInfo signerInfo = new SignerInfo(); signerInfo.version = 1; X500Principal signerCertIssuer = signingCert.getIssuerX500Principal(); signerInfo.sid = new SignerIdentifier( new IssuerAndSerialNumber( new Asn1OpaqueObject(signerCertIssuer.getEncoded()), signingCert.getSerialNumber())); AlgorithmIdentifier digestAlgorithmId = getSignerInfoDigestAlgorithmOid(digestAlgorithm); AlgorithmIdentifier signatureAlgorithmId = signatureAlgs.getSecond(); signerInfo.digestAlgorithm = digestAlgorithmId; signerInfo.signatureAlgorithm = signatureAlgorithmId; signerInfo.signature = ByteBuffer.wrap(signatureBytes); SignedData signedData = new SignedData(); signedData.certificates = new ArrayList<>(signerCerts.size()); for (X509Certificate cert : signerCerts) { signedData.certificates.add(new Asn1OpaqueObject(cert.getEncoded())); } signedData.version = 1; signedData.digestAlgorithms = Collections.singletonList(digestAlgorithmId); signedData.encapContentInfo = new EncapsulatedContentInfo(Pkcs7Constants.OID_DATA); signedData.signerInfos = Collections.singletonList(signerInfo); ContentInfo contentInfo = new ContentInfo(); contentInfo.contentType = Pkcs7Constants.OID_SIGNED_DATA; contentInfo.content = new Asn1OpaqueObject(Asn1DerEncoder.encode(signedData)); return Asn1DerEncoder.encode(contentInfo); } catch (Asn1EncodingException e) { throw new SignatureException("Failed to encode signature block", e); } } /** * Returns the PKCS #7 {@code DigestAlgorithm} to use when signing using the specified digest * algorithm. */ private static AlgorithmIdentifier getSignerInfoDigestAlgorithmOid( DigestAlgorithm digestAlgorithm) { switch (digestAlgorithm) { case SHA1: return new AlgorithmIdentifier( V1SchemeVerifier.Signer.OID_DIGEST_SHA1, ASN1_DER_NULL); case SHA256: return new AlgorithmIdentifier( V1SchemeVerifier.Signer.OID_DIGEST_SHA256, ASN1_DER_NULL); default: throw new RuntimeException("Unsupported digest algorithm: " + digestAlgorithm); } } /** * Returns the JCA {@link Signature} algorithm and PKCS #7 {@code SignatureAlgorithm} to use * when signing with the specified key and digest algorithm. */ private static Pair getSignerInfoSignatureAlgorithm( PublicKey publicKey, DigestAlgorithm digestAlgorithm) throws InvalidKeyException { String keyAlgorithm = publicKey.getAlgorithm(); String jcaDigestPrefixForSigAlg; switch (digestAlgorithm) { case SHA1: jcaDigestPrefixForSigAlg = "SHA1"; break; case SHA256: jcaDigestPrefixForSigAlg = "SHA256"; break; default: throw new IllegalArgumentException( "Unexpected digest algorithm: " + digestAlgorithm); } if ("RSA".equalsIgnoreCase(keyAlgorithm)) { return Pair.of( jcaDigestPrefixForSigAlg + "withRSA", new AlgorithmIdentifier(V1SchemeVerifier.Signer.OID_SIG_RSA, ASN1_DER_NULL)); } else if ("DSA".equalsIgnoreCase(keyAlgorithm)) { AlgorithmIdentifier sigAlgId; switch (digestAlgorithm) { case SHA1: sigAlgId = new AlgorithmIdentifier( V1SchemeVerifier.Signer.OID_SIG_DSA, ASN1_DER_NULL); break; case SHA256: // DSA signatures with SHA-256 in SignedData are accepted by Android API Level // 21 and higher. However, there are two ways to specify their SignedData // SignatureAlgorithm: dsaWithSha256 (2.16.840.1.101.3.4.3.2) and // dsa (1.2.840.10040.4.1). The latter works only on API Level 22+. Thus, we use // the former. sigAlgId = new AlgorithmIdentifier( V1SchemeVerifier.Signer.OID_SIG_SHA256_WITH_DSA, ASN1_DER_NULL); break; default: throw new IllegalArgumentException( "Unexpected digest algorithm: " + digestAlgorithm); } return Pair.of(jcaDigestPrefixForSigAlg + "withDSA", sigAlgId); } else if ("EC".equalsIgnoreCase(keyAlgorithm)) { return Pair.of( jcaDigestPrefixForSigAlg + "withECDSA", new AlgorithmIdentifier( V1SchemeVerifier.Signer.OID_SIG_EC_PUBLIC_KEY, ASN1_DER_NULL)); } else { throw new InvalidKeyException("Unsupported key algorithm: " + keyAlgorithm); } } private static String getEntryDigestAttributeName(DigestAlgorithm digestAlgorithm) { switch (digestAlgorithm) { case SHA1: return "SHA1-Digest"; case SHA256: return "SHA-256-Digest"; default: throw new IllegalArgumentException( "Unexpected content digest algorithm: " + digestAlgorithm); } } private static String getManifestDigestAttributeName(DigestAlgorithm digestAlgorithm) { switch (digestAlgorithm) { case SHA1: return "SHA1-Digest-Manifest"; case SHA256: return "SHA-256-Digest-Manifest"; default: throw new IllegalArgumentException( "Unexpected content digest algorithm: " + digestAlgorithm); } } } src/main/java/com/android/apksig/internal/apk/v1/V1SchemeVerifier.java0100644 0000000 0000000 00000275641 13243353143 024624 0ustar000000000 0000000 /* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.internal.apk.v1; import com.android.apksig.ApkVerifier.Issue; import com.android.apksig.ApkVerifier.IssueWithParams; import com.android.apksig.apk.ApkFormatException; import com.android.apksig.apk.ApkUtils; import com.android.apksig.internal.asn1.Asn1BerParser; import com.android.apksig.internal.asn1.Asn1Class; import com.android.apksig.internal.asn1.Asn1DecodingException; import com.android.apksig.internal.asn1.Asn1Field; import com.android.apksig.internal.asn1.Asn1OpaqueObject; import com.android.apksig.internal.asn1.Asn1Type; import com.android.apksig.internal.jar.ManifestParser; import com.android.apksig.internal.pkcs7.Attribute; import com.android.apksig.internal.pkcs7.ContentInfo; import com.android.apksig.internal.pkcs7.IssuerAndSerialNumber; import com.android.apksig.internal.pkcs7.Pkcs7Constants; import com.android.apksig.internal.pkcs7.Pkcs7DecodingException; import com.android.apksig.internal.pkcs7.SignedData; import com.android.apksig.internal.pkcs7.SignerIdentifier; import com.android.apksig.internal.pkcs7.SignerInfo; import com.android.apksig.internal.util.AndroidSdkVersion; import com.android.apksig.internal.util.ByteBufferUtils; import com.android.apksig.internal.util.GuaranteedEncodedFormX509Certificate; import com.android.apksig.internal.util.InclusiveIntRange; import com.android.apksig.internal.util.MessageDigestSink; import com.android.apksig.internal.zip.CentralDirectoryRecord; import com.android.apksig.internal.zip.LocalFileRecord; import com.android.apksig.util.DataSource; import com.android.apksig.zip.ZipFormatException; import java.io.ByteArrayInputStream; import java.io.IOException; import java.math.BigInteger; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.security.InvalidKeyException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.Principal; import java.security.Signature; import java.security.SignatureException; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Arrays; import java.util.Base64; import java.util.Base64.Decoder; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.StringTokenizer; import java.util.jar.Attributes; import javax.security.auth.x500.X500Principal; /** * APK verifier which uses JAR signing (aka v1 signing scheme). * * @see Signed JAR File */ public abstract class V1SchemeVerifier { private static final String MANIFEST_ENTRY_NAME = V1SchemeSigner.MANIFEST_ENTRY_NAME; private V1SchemeVerifier() {} /** * Verifies the provided APK's JAR signatures and returns the result of verification. APK is * considered verified only if {@link Result#verified} is {@code true}. If verification fails, * the result will contain errors -- see {@link Result#getErrors()}. * * @throws ApkFormatException if the APK is malformed * @throws IOException if an I/O error occurs when reading the APK * @throws NoSuchAlgorithmException if the APK's JAR signatures cannot be verified because a * required cryptographic algorithm implementation is missing */ public static Result verify( DataSource apk, ApkUtils.ZipSections apkSections, Map supportedApkSigSchemeNames, Set foundApkSigSchemeIds, int minSdkVersion, int maxSdkVersion) throws IOException, ApkFormatException, NoSuchAlgorithmException { if (minSdkVersion > maxSdkVersion) { throw new IllegalArgumentException( "minSdkVersion (" + minSdkVersion + ") > maxSdkVersion (" + maxSdkVersion + ")"); } Result result = new Result(); // Parse the ZIP Central Directory and check that there are no entries with duplicate names. List cdRecords = parseZipCentralDirectory(apk, apkSections); Set cdEntryNames = checkForDuplicateEntries(cdRecords, result); if (result.containsErrors()) { return result; } // Verify JAR signature(s). Signers.verify( apk, apkSections.getZipCentralDirectoryOffset(), cdRecords, cdEntryNames, supportedApkSigSchemeNames, foundApkSigSchemeIds, minSdkVersion, maxSdkVersion, result); return result; } /** * Returns the set of entry names and reports any duplicate entry names in the {@code result} * as errors. */ private static Set checkForDuplicateEntries( List cdRecords, Result result) { Set cdEntryNames = new HashSet<>(cdRecords.size()); Set duplicateCdEntryNames = null; for (CentralDirectoryRecord cdRecord : cdRecords) { String entryName = cdRecord.getName(); if (!cdEntryNames.add(entryName)) { // This is an error. Report this once per duplicate name. if (duplicateCdEntryNames == null) { duplicateCdEntryNames = new HashSet<>(); } if (duplicateCdEntryNames.add(entryName)) { result.addError(Issue.JAR_SIG_DUPLICATE_ZIP_ENTRY, entryName); } } } return cdEntryNames; } /** * All JAR signers of an APK. */ private static class Signers { /** * Verifies JAR signatures of the provided APK and populates the provided result container * with errors, warnings, and information about signers. The APK is considered verified if * the {@link Result#verified} is {@code true}. */ private static void verify( DataSource apk, long cdStartOffset, List cdRecords, Set cdEntryNames, Map supportedApkSigSchemeNames, Set foundApkSigSchemeIds, int minSdkVersion, int maxSdkVersion, Result result) throws ApkFormatException, IOException, NoSuchAlgorithmException { // Find JAR manifest and signature block files. CentralDirectoryRecord manifestEntry = null; Map sigFileEntries = new HashMap<>(1); List sigBlockEntries = new ArrayList<>(1); for (CentralDirectoryRecord cdRecord : cdRecords) { String entryName = cdRecord.getName(); if (!entryName.startsWith("META-INF/")) { continue; } if ((manifestEntry == null) && (MANIFEST_ENTRY_NAME.equals(entryName))) { manifestEntry = cdRecord; continue; } if (entryName.endsWith(".SF")) { sigFileEntries.put(entryName, cdRecord); continue; } if ((entryName.endsWith(".RSA")) || (entryName.endsWith(".DSA")) || (entryName.endsWith(".EC"))) { sigBlockEntries.add(cdRecord); continue; } } if (manifestEntry == null) { result.addError(Issue.JAR_SIG_NO_MANIFEST); return; } // Parse the JAR manifest and check that all JAR entries it references exist in the APK. byte[] manifestBytes; try { manifestBytes = LocalFileRecord.getUncompressedData(apk, manifestEntry, cdStartOffset); } catch (ZipFormatException e) { throw new ApkFormatException("Malformed ZIP entry: " + manifestEntry.getName(), e); } Map entryNameToManifestSection = null; ManifestParser manifest = new ManifestParser(manifestBytes); ManifestParser.Section manifestMainSection = manifest.readSection(); List manifestIndividualSections = manifest.readAllSections(); entryNameToManifestSection = new HashMap<>(manifestIndividualSections.size()); int manifestSectionNumber = 0; for (ManifestParser.Section manifestSection : manifestIndividualSections) { manifestSectionNumber++; String entryName = manifestSection.getName(); if (entryName == null) { result.addError(Issue.JAR_SIG_UNNNAMED_MANIFEST_SECTION, manifestSectionNumber); continue; } if (entryNameToManifestSection.put(entryName, manifestSection) != null) { result.addError(Issue.JAR_SIG_DUPLICATE_MANIFEST_SECTION, entryName); continue; } if (!cdEntryNames.contains(entryName)) { result.addError( Issue.JAR_SIG_MISSING_ZIP_ENTRY_REFERENCED_IN_MANIFEST, entryName); continue; } } if (result.containsErrors()) { return; } // STATE OF AFFAIRS: // * All JAR entries listed in JAR manifest are present in the APK. // Identify signers List signers = new ArrayList<>(sigBlockEntries.size()); for (CentralDirectoryRecord sigBlockEntry : sigBlockEntries) { String sigBlockEntryName = sigBlockEntry.getName(); int extensionDelimiterIndex = sigBlockEntryName.lastIndexOf('.'); if (extensionDelimiterIndex == -1) { throw new RuntimeException( "Signature block file name does not contain extension: " + sigBlockEntryName); } String sigFileEntryName = sigBlockEntryName.substring(0, extensionDelimiterIndex) + ".SF"; CentralDirectoryRecord sigFileEntry = sigFileEntries.get(sigFileEntryName); if (sigFileEntry == null) { result.addWarning( Issue.JAR_SIG_MISSING_FILE, sigBlockEntryName, sigFileEntryName); continue; } String signerName = sigBlockEntryName.substring("META-INF/".length()); Result.SignerInfo signerInfo = new Result.SignerInfo( signerName, sigBlockEntryName, sigFileEntry.getName()); Signer signer = new Signer(signerName, sigBlockEntry, sigFileEntry, signerInfo); signers.add(signer); } if (signers.isEmpty()) { result.addError(Issue.JAR_SIG_NO_SIGNATURES); return; } // Verify each signer's signature block file .(RSA|DSA|EC) against the corresponding // signature file .SF. Any error encountered for any signer terminates verification, to // mimic Android's behavior. for (Signer signer : signers) { signer.verifySigBlockAgainstSigFile( apk, cdStartOffset, minSdkVersion, maxSdkVersion); if (signer.getResult().containsErrors()) { result.signers.add(signer.getResult()); } } if (result.containsErrors()) { return; } // STATE OF AFFAIRS: // * All JAR entries listed in JAR manifest are present in the APK. // * All signature files (.SF) verify against corresponding block files (.RSA|.DSA|.EC). // Verify each signer's signature file (.SF) against the JAR manifest. List remainingSigners = new ArrayList<>(signers.size()); for (Signer signer : signers) { signer.verifySigFileAgainstManifest( manifestBytes, manifestMainSection, entryNameToManifestSection, supportedApkSigSchemeNames, foundApkSigSchemeIds, minSdkVersion, maxSdkVersion); if (signer.isIgnored()) { result.ignoredSigners.add(signer.getResult()); } else { if (signer.getResult().containsErrors()) { result.signers.add(signer.getResult()); } else { remainingSigners.add(signer); } } } if (result.containsErrors()) { return; } signers = remainingSigners; if (signers.isEmpty()) { result.addError(Issue.JAR_SIG_NO_SIGNATURES); return; } // STATE OF AFFAIRS: // * All signature files (.SF) verify against corresponding block files (.RSA|.DSA|.EC). // * Contents of all JAR manifest sections listed in .SF files verify against .SF files. // * All JAR entries listed in JAR manifest are present in the APK. // Verify data of JAR entries against JAR manifest and .SF files. On Android, an APK's // JAR entry is considered signed by signers associated with an .SF file iff the entry // is mentioned in the .SF file and the entry's digest(s) mentioned in the JAR manifest // match theentry's uncompressed data. Android requires that all such JAR entries are // signed by the same set of signers. This set may be smaller than the set of signers // we've identified so far. Set apkSigners = verifyJarEntriesAgainstManifestAndSigners( apk, cdStartOffset, cdRecords, entryNameToManifestSection, signers, minSdkVersion, maxSdkVersion, result); if (result.containsErrors()) { return; } // STATE OF AFFAIRS: // * All signature files (.SF) verify against corresponding block files (.RSA|.DSA|.EC). // * Contents of all JAR manifest sections listed in .SF files verify against .SF files. // * All JAR entries listed in JAR manifest are present in the APK. // * All JAR entries present in the APK and supposed to be covered by JAR signature // (i.e., reside outside of META-INF/) are covered by signatures from the same set // of signers. // Report any JAR entries which aren't covered by signature. Set signatureEntryNames = new HashSet<>(1 + result.signers.size() * 2); signatureEntryNames.add(manifestEntry.getName()); for (Signer signer : apkSigners) { signatureEntryNames.add(signer.getSignatureBlockEntryName()); signatureEntryNames.add(signer.getSignatureFileEntryName()); } for (CentralDirectoryRecord cdRecord : cdRecords) { String entryName = cdRecord.getName(); if ((entryName.startsWith("META-INF/")) && (!entryName.endsWith("/")) && (!signatureEntryNames.contains(entryName))) { result.addWarning(Issue.JAR_SIG_UNPROTECTED_ZIP_ENTRY, entryName); } } // Reflect the sets of used signers and ignored signers in the result. for (Signer signer : signers) { if (apkSigners.contains(signer)) { result.signers.add(signer.getResult()); } else { result.ignoredSigners.add(signer.getResult()); } } result.verified = true; } } static class Signer { private final String mName; private final Result.SignerInfo mResult; private final CentralDirectoryRecord mSignatureFileEntry; private final CentralDirectoryRecord mSignatureBlockEntry; private boolean mIgnored; private byte[] mSigFileBytes; private Set mSigFileEntryNames; private Signer( String name, CentralDirectoryRecord sigBlockEntry, CentralDirectoryRecord sigFileEntry, Result.SignerInfo result) { mName = name; mResult = result; mSignatureBlockEntry = sigBlockEntry; mSignatureFileEntry = sigFileEntry; } public String getName() { return mName; } public String getSignatureFileEntryName() { return mSignatureFileEntry.getName(); } public String getSignatureBlockEntryName() { return mSignatureBlockEntry.getName(); } void setIgnored() { mIgnored = true; } public boolean isIgnored() { return mIgnored; } public Set getSigFileEntryNames() { return mSigFileEntryNames; } public Result.SignerInfo getResult() { return mResult; } public void verifySigBlockAgainstSigFile( DataSource apk, long cdStartOffset, int minSdkVersion, int maxSdkVersion) throws IOException, ApkFormatException, NoSuchAlgorithmException { // Obtain the signature block from the APK byte[] sigBlockBytes; try { sigBlockBytes = LocalFileRecord.getUncompressedData( apk, mSignatureBlockEntry, cdStartOffset); } catch (ZipFormatException e) { throw new ApkFormatException( "Malformed ZIP entry: " + mSignatureBlockEntry.getName(), e); } // Obtain the signature file from the APK try { mSigFileBytes = LocalFileRecord.getUncompressedData( apk, mSignatureFileEntry, cdStartOffset); } catch (ZipFormatException e) { throw new ApkFormatException( "Malformed ZIP entry: " + mSignatureFileEntry.getName(), e); } // Extract PKCS #7 SignedData from the signature block SignedData signedData; try { ContentInfo contentInfo = Asn1BerParser.parse(ByteBuffer.wrap(sigBlockBytes), ContentInfo.class); if (!Pkcs7Constants.OID_SIGNED_DATA.equals(contentInfo.contentType)) { throw new Asn1DecodingException( "Unsupported ContentInfo.contentType: " + contentInfo.contentType); } signedData = Asn1BerParser.parse(contentInfo.content.getEncoded(), SignedData.class); } catch (Asn1DecodingException e) { e.printStackTrace(); mResult.addError( Issue.JAR_SIG_PARSE_EXCEPTION, mSignatureBlockEntry.getName(), e); return; } if (signedData.signerInfos.isEmpty()) { mResult.addError(Issue.JAR_SIG_NO_SIGNERS, mSignatureBlockEntry.getName()); return; } // Find the first SignedData.SignerInfos element which verifies against the signature // file SignerInfo firstVerifiedSignerInfo = null; X509Certificate firstVerifiedSignerInfoSigningCertificate = null; // Prior to Android N, Android attempts to verify only the first SignerInfo. From N // onwards, Android attempts to verify all SignerInfos and then picks the first verified // SignerInfo. List unverifiedSignerInfosToTry; if (minSdkVersion < AndroidSdkVersion.N) { unverifiedSignerInfosToTry = Collections.singletonList(signedData.signerInfos.get(0)); } else { unverifiedSignerInfosToTry = signedData.signerInfos; } List signedDataCertificates = null; for (SignerInfo unverifiedSignerInfo : unverifiedSignerInfosToTry) { // Parse SignedData.certificates -- they are needed to verify SignerInfo if (signedDataCertificates == null) { try { signedDataCertificates = parseCertificates(signedData.certificates); } catch (CertificateException e) { mResult.addError( Issue.JAR_SIG_PARSE_EXCEPTION, mSignatureBlockEntry.getName(), e); return; } } // Verify SignerInfo X509Certificate signingCertificate; try { signingCertificate = verifySignerInfoAgainstSigFile( signedData, signedDataCertificates, unverifiedSignerInfo, mSigFileBytes, minSdkVersion, maxSdkVersion); if (mResult.containsErrors()) { return; } if (signingCertificate != null) { // SignerInfo verified if (firstVerifiedSignerInfo == null) { firstVerifiedSignerInfo = unverifiedSignerInfo; firstVerifiedSignerInfoSigningCertificate = signingCertificate; } } } catch (Pkcs7DecodingException e) { mResult.addError( Issue.JAR_SIG_PARSE_EXCEPTION, mSignatureBlockEntry.getName(), e); return; } catch (InvalidKeyException | SignatureException e) { mResult.addError( Issue.JAR_SIG_VERIFY_EXCEPTION, mSignatureBlockEntry.getName(), mSignatureFileEntry.getName(), e); return; } } if (firstVerifiedSignerInfo == null) { // No SignerInfo verified mResult.addError( Issue.JAR_SIG_DID_NOT_VERIFY, mSignatureBlockEntry.getName(), mSignatureFileEntry.getName()); return; } // Verified List signingCertChain = getCertificateChain( signedDataCertificates, firstVerifiedSignerInfoSigningCertificate); mResult.certChain.clear(); mResult.certChain.addAll(signingCertChain); } /** * Returns the signing certificate if the provided {@link SignerInfo} verifies against the * contents of the provided signature file, or {@code null} if it does not verify. */ private X509Certificate verifySignerInfoAgainstSigFile( SignedData signedData, Collection signedDataCertificates, SignerInfo signerInfo, byte[] signatureFile, int minSdkVersion, int maxSdkVersion) throws Pkcs7DecodingException, NoSuchAlgorithmException, InvalidKeyException, SignatureException { String digestAlgorithmOid = signerInfo.digestAlgorithm.algorithm; String signatureAlgorithmOid = signerInfo.signatureAlgorithm.algorithm; InclusiveIntRange desiredApiLevels = InclusiveIntRange.fromTo(minSdkVersion, maxSdkVersion); List apiLevelsWhereDigestAndSigAlgorithmSupported = getSigAlgSupportedApiLevels(digestAlgorithmOid, signatureAlgorithmOid); List apiLevelsWhereDigestAlgorithmNotSupported = desiredApiLevels.getValuesNotIn(apiLevelsWhereDigestAndSigAlgorithmSupported); if (!apiLevelsWhereDigestAlgorithmNotSupported.isEmpty()) { String digestAlgorithmUserFriendly = OidToUserFriendlyNameMapper.getUserFriendlyNameForOid( digestAlgorithmOid); if (digestAlgorithmUserFriendly == null) { digestAlgorithmUserFriendly = digestAlgorithmOid; } String signatureAlgorithmUserFriendly = OidToUserFriendlyNameMapper.getUserFriendlyNameForOid( signatureAlgorithmOid); if (signatureAlgorithmUserFriendly == null) { signatureAlgorithmUserFriendly = signatureAlgorithmOid; } StringBuilder apiLevelsUserFriendly = new StringBuilder(); for (InclusiveIntRange range : apiLevelsWhereDigestAlgorithmNotSupported) { if (apiLevelsUserFriendly.length() > 0) { apiLevelsUserFriendly.append(", "); } if (range.getMin() == range.getMax()) { apiLevelsUserFriendly.append(String.valueOf(range.getMin())); } else if (range.getMax() == Integer.MAX_VALUE) { apiLevelsUserFriendly.append(range.getMin() + "+"); } else { apiLevelsUserFriendly.append(range.getMin() + "-" + range.getMax()); } } mResult.addError( Issue.JAR_SIG_UNSUPPORTED_SIG_ALG, mSignatureBlockEntry.getName(), digestAlgorithmOid, signatureAlgorithmOid, apiLevelsUserFriendly.toString(), digestAlgorithmUserFriendly, signatureAlgorithmUserFriendly); return null; } // From the bag of certs, obtain the certificate referenced by the SignerInfo, // and verify the cryptographic signature in the SignerInfo against the certificate. // Locate the signing certificate referenced by the SignerInfo X509Certificate signingCertificate = findCertificate(signedDataCertificates, signerInfo.sid); if (signingCertificate == null) { throw new SignatureException( "Signing certificate referenced in SignerInfo not found in" + " SignedData"); } // Check whether the signing certificate is acceptable. Android performs these // checks explicitly, instead of delegating this to // Signature.initVerify(Certificate). if (signingCertificate.hasUnsupportedCriticalExtension()) { throw new SignatureException( "Signing certificate has unsupported critical extensions"); } boolean[] keyUsageExtension = signingCertificate.getKeyUsage(); if (keyUsageExtension != null) { boolean digitalSignature = (keyUsageExtension.length >= 1) && (keyUsageExtension[0]); boolean nonRepudiation = (keyUsageExtension.length >= 2) && (keyUsageExtension[1]); if ((!digitalSignature) && (!nonRepudiation)) { throw new SignatureException( "Signing certificate not authorized for use in digital signatures" + ": keyUsage extension missing digitalSignature and" + " nonRepudiation"); } } // Verify the cryptographic signature in SignerInfo against the certificate's // public key String jcaSignatureAlgorithm = getJcaSignatureAlgorithm(digestAlgorithmOid, signatureAlgorithmOid); Signature s = Signature.getInstance(jcaSignatureAlgorithm); s.initVerify(signingCertificate.getPublicKey()); if (signerInfo.signedAttrs != null) { // Signed attributes present -- verify signature against the ASN.1 DER encoded form // of signed attributes. This verifies integrity of the signature file because // signed attributes must contain the digest of the signature file. if (minSdkVersion < AndroidSdkVersion.KITKAT) { // Prior to Android KitKat, APKs with signed attributes are unsafe: // * The APK's contents are not protected by the JAR signature because the // digest in signed attributes is not verified. This means an attacker can // arbitrarily modify the APK without invalidating its signature. // * Luckily, the signature over signed attributes was verified incorrectly // (over the verbatim IMPLICIT [0] form rather than over re-encoded // UNIVERSAL SET form) which means that JAR signatures which would verify on // pre-KitKat Android and yet do not protect the APK from modification could // be generated only by broken tools or on purpose by the entity signing the // APK. // // We thus reject such unsafe APKs, even if they verify on platforms before // KitKat. throw new SignatureException( "APKs with Signed Attributes broken on platforms with API Level < " + AndroidSdkVersion.KITKAT); } try { List signedAttributes = Asn1BerParser.parseImplicitSetOf( signerInfo.signedAttrs.getEncoded(), Attribute.class); SignedAttributes signedAttrs = new SignedAttributes(signedAttributes); if (maxSdkVersion >= AndroidSdkVersion.N) { // Content Type attribute is checked only on Android N and newer String contentType = signedAttrs.getSingleObjectIdentifierValue( Pkcs7Constants.OID_CONTENT_TYPE); if (contentType == null) { throw new SignatureException("No Content Type in signed attributes"); } if (!contentType.equals(signedData.encapContentInfo.contentType)) { // Did not verify: Content type signed attribute does not match // SignedData.encapContentInfo.eContentType. This fails verification of // this SignerInfo but should not prevent verification of other // SignerInfos. Hence, no exception is thrown. return null; } } byte[] expectedSignatureFileDigest = signedAttrs.getSingleOctetStringValue( Pkcs7Constants.OID_MESSAGE_DIGEST); if (expectedSignatureFileDigest == null) { throw new SignatureException("No content digest in signed attributes"); } byte[] actualSignatureFileDigest = MessageDigest.getInstance( getJcaDigestAlgorithm(digestAlgorithmOid)) .digest(signatureFile); if (!Arrays.equals( expectedSignatureFileDigest, actualSignatureFileDigest)) { // Skip verification: signature file digest in signed attributes does not // match the signature file. This fails verification of // this SignerInfo but should not prevent verification of other // SignerInfos. Hence, no exception is thrown. return null; } } catch (Asn1DecodingException e) { throw new SignatureException("Failed to parse signed attributes", e); } // PKCS #7 requires that signature is over signed attributes re-encoded as // ASN.1 DER. However, Android does not re-encode except for changing the // first byte of encoded form from IMPLICIT [0] to UNIVERSAL SET. We do the // same for maximum compatibility. ByteBuffer signedAttrsOriginalEncoding = signerInfo.signedAttrs.getEncoded(); s.update((byte) 0x31); // UNIVERSAL SET signedAttrsOriginalEncoding.position(1); s.update(signedAttrsOriginalEncoding); } else { // No signed attributes present -- verify signature against the contents of the // signature file s.update(signatureFile); } byte[] sigBytes = ByteBufferUtils.toByteArray(signerInfo.signature.slice()); if (!s.verify(sigBytes)) { // Cryptographic signature did not verify. This fails verification of this // SignerInfo but should not prevent verification of other SignerInfos. Hence, no // exception is thrown. return null; } // Cryptographic signature verified return signingCertificate; } private static List parseCertificates( List encodedCertificates) throws CertificateException { if (encodedCertificates.isEmpty()) { return Collections.emptyList(); } CertificateFactory certFactory; try { certFactory = CertificateFactory.getInstance("X.509"); } catch (CertificateException e) { throw new RuntimeException("Failed to create X.509 CertificateFactory", e); } List result = new ArrayList<>(encodedCertificates.size()); for (int i = 0; i < encodedCertificates.size(); i++) { Asn1OpaqueObject encodedCertificate = encodedCertificates.get(i); X509Certificate certificate; byte[] encodedForm = ByteBufferUtils.toByteArray(encodedCertificate.getEncoded()); try { certificate = (X509Certificate) certFactory.generateCertificate( new ByteArrayInputStream(encodedForm)); } catch (CertificateException e) { throw new CertificateException("Failed to parse certificate #" + (i + 1), e); } // Wrap the cert so that the result's getEncoded returns exactly the original // encoded form. Without this, getEncoded may return a different form from what was // stored in the signature. This is because some X509Certificate(Factory) // implementations re-encode certificates and/or some implementations of // X509Certificate.getEncoded() re-encode certificates. certificate = new GuaranteedEncodedFormX509Certificate(certificate, encodedForm); result.add(certificate); } return result; } public static X509Certificate findCertificate( Collection certs, SignerIdentifier id) { for (X509Certificate cert : certs) { if (isMatchingCerticicate(cert, id)) { return cert; } } return null; } public static List getCertificateChain( List certs, X509Certificate leaf) { List unusedCerts = new ArrayList<>(certs); List result = new ArrayList<>(1); result.add(leaf); unusedCerts.remove(leaf); X509Certificate root = leaf; while (!root.getSubjectDN().equals(root.getIssuerDN())) { Principal targetDn = root.getIssuerDN(); boolean issuerFound = false; for (int i = 0; i < unusedCerts.size(); i++) { X509Certificate unusedCert = unusedCerts.get(i); if (targetDn.equals(unusedCert.getSubjectDN())) { issuerFound = true; unusedCerts.remove(i); result.add(unusedCert); root = unusedCert; break; } } if (!issuerFound) { break; } } return result; } private static boolean isMatchingCerticicate(X509Certificate cert, SignerIdentifier id) { if (id.issuerAndSerialNumber == null) { // Android doesn't support any other means of identifying the signing certificate return false; } IssuerAndSerialNumber issuerAndSerialNumber = id.issuerAndSerialNumber; byte[] encodedIssuer = ByteBufferUtils.toByteArray(issuerAndSerialNumber.issuer.getEncoded()); X500Principal idIssuer = new X500Principal(encodedIssuer); BigInteger idSerialNumber = issuerAndSerialNumber.certificateSerialNumber; return idSerialNumber.equals(cert.getSerialNumber()) && idIssuer.equals(cert.getIssuerX500Principal()); } private static final String OID_DIGEST_MD5 = "1.2.840.113549.2.5"; static final String OID_DIGEST_SHA1 = "1.3.14.3.2.26"; private static final String OID_DIGEST_SHA224 = "2.16.840.1.101.3.4.2.4"; static final String OID_DIGEST_SHA256 = "2.16.840.1.101.3.4.2.1"; private static final String OID_DIGEST_SHA384 = "2.16.840.1.101.3.4.2.2"; private static final String OID_DIGEST_SHA512 = "2.16.840.1.101.3.4.2.3"; static final String OID_SIG_RSA = "1.2.840.113549.1.1.1"; private static final String OID_SIG_MD5_WITH_RSA = "1.2.840.113549.1.1.4"; private static final String OID_SIG_SHA1_WITH_RSA = "1.2.840.113549.1.1.5"; private static final String OID_SIG_SHA224_WITH_RSA = "1.2.840.113549.1.1.14"; private static final String OID_SIG_SHA256_WITH_RSA = "1.2.840.113549.1.1.11"; private static final String OID_SIG_SHA384_WITH_RSA = "1.2.840.113549.1.1.12"; private static final String OID_SIG_SHA512_WITH_RSA = "1.2.840.113549.1.1.13"; static final String OID_SIG_DSA = "1.2.840.10040.4.1"; private static final String OID_SIG_SHA1_WITH_DSA = "1.2.840.10040.4.3"; private static final String OID_SIG_SHA224_WITH_DSA = "2.16.840.1.101.3.4.3.1"; static final String OID_SIG_SHA256_WITH_DSA = "2.16.840.1.101.3.4.3.2"; static final String OID_SIG_EC_PUBLIC_KEY = "1.2.840.10045.2.1"; private static final String OID_SIG_SHA1_WITH_ECDSA = "1.2.840.10045.4.1"; private static final String OID_SIG_SHA224_WITH_ECDSA = "1.2.840.10045.4.3.1"; private static final String OID_SIG_SHA256_WITH_ECDSA = "1.2.840.10045.4.3.2"; private static final String OID_SIG_SHA384_WITH_ECDSA = "1.2.840.10045.4.3.3"; private static final String OID_SIG_SHA512_WITH_ECDSA = "1.2.840.10045.4.3.4"; private static final Map> SUPPORTED_SIG_ALG_OIDS = new HashMap<>(); { addSupportedSigAlg( OID_DIGEST_MD5, OID_SIG_RSA, InclusiveIntRange.from(0)); addSupportedSigAlg( OID_DIGEST_MD5, OID_SIG_MD5_WITH_RSA, InclusiveIntRange.fromTo(0, 8), InclusiveIntRange.from(21)); addSupportedSigAlg( OID_DIGEST_MD5, OID_SIG_SHA1_WITH_RSA, InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_MD5, OID_SIG_SHA224_WITH_RSA, InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_MD5, OID_SIG_SHA256_WITH_RSA, InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_MD5, OID_SIG_SHA384_WITH_RSA, InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_MD5, OID_SIG_SHA512_WITH_RSA, InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA1, OID_SIG_RSA, InclusiveIntRange.from(0)); addSupportedSigAlg( OID_DIGEST_SHA1, OID_SIG_MD5_WITH_RSA, InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA1, OID_SIG_SHA1_WITH_RSA, InclusiveIntRange.from(0)); addSupportedSigAlg( OID_DIGEST_SHA1, OID_SIG_SHA224_WITH_RSA, InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA1, OID_SIG_SHA256_WITH_RSA, InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA1, OID_SIG_SHA384_WITH_RSA, InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA1, OID_SIG_SHA512_WITH_RSA, InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA224, OID_SIG_RSA, InclusiveIntRange.fromTo(0, 8), InclusiveIntRange.from(21)); addSupportedSigAlg( OID_DIGEST_SHA224, OID_SIG_MD5_WITH_RSA, InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA224, OID_SIG_SHA1_WITH_RSA, InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA224, OID_SIG_SHA224_WITH_RSA, InclusiveIntRange.fromTo(0, 8), InclusiveIntRange.from(21)); addSupportedSigAlg( OID_DIGEST_SHA224, OID_SIG_SHA256_WITH_RSA, InclusiveIntRange.fromTo(21, 21)); addSupportedSigAlg( OID_DIGEST_SHA224, OID_SIG_SHA384_WITH_RSA, InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA224, OID_SIG_SHA512_WITH_RSA, InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA256, OID_SIG_RSA, InclusiveIntRange.fromTo(0, 8), InclusiveIntRange.from(18)); addSupportedSigAlg( OID_DIGEST_SHA256, OID_SIG_MD5_WITH_RSA, InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA256, OID_SIG_SHA1_WITH_RSA, InclusiveIntRange.fromTo(21, 21)); addSupportedSigAlg( OID_DIGEST_SHA256, OID_SIG_SHA224_WITH_RSA, InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA256, OID_SIG_SHA256_WITH_RSA, InclusiveIntRange.fromTo(0, 8), InclusiveIntRange.from(18)); addSupportedSigAlg( OID_DIGEST_SHA256, OID_SIG_SHA384_WITH_RSA, InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA256, OID_SIG_SHA512_WITH_RSA, InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA384, OID_SIG_RSA, InclusiveIntRange.from(18)); addSupportedSigAlg( OID_DIGEST_SHA384, OID_SIG_MD5_WITH_RSA, InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA384, OID_SIG_SHA1_WITH_RSA, InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA384, OID_SIG_SHA224_WITH_RSA, InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA384, OID_SIG_SHA256_WITH_RSA, InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA384, OID_SIG_SHA384_WITH_RSA, InclusiveIntRange.from(21)); addSupportedSigAlg( OID_DIGEST_SHA384, OID_SIG_SHA512_WITH_RSA, InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA512, OID_SIG_RSA, InclusiveIntRange.from(18)); addSupportedSigAlg( OID_DIGEST_SHA512, OID_SIG_MD5_WITH_RSA, InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA512, OID_SIG_SHA1_WITH_RSA, InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA512, OID_SIG_SHA224_WITH_RSA, InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA512, OID_SIG_SHA256_WITH_RSA, InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA512, OID_SIG_SHA384_WITH_RSA, InclusiveIntRange.fromTo(21, 21)); addSupportedSigAlg( OID_DIGEST_SHA512, OID_SIG_SHA512_WITH_RSA, InclusiveIntRange.from(21)); addSupportedSigAlg( OID_DIGEST_MD5, OID_SIG_SHA1_WITH_DSA, InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_MD5, OID_SIG_SHA224_WITH_DSA, InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_MD5, OID_SIG_SHA256_WITH_DSA, InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA1, OID_SIG_DSA, InclusiveIntRange.from(0)); addSupportedSigAlg( OID_DIGEST_SHA1, OID_SIG_SHA1_WITH_DSA, InclusiveIntRange.from(9)); addSupportedSigAlg( OID_DIGEST_SHA1, OID_SIG_SHA224_WITH_DSA, InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA1, OID_SIG_SHA256_WITH_DSA, InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA224, OID_SIG_DSA, InclusiveIntRange.from(22)); addSupportedSigAlg( OID_DIGEST_SHA224, OID_SIG_SHA1_WITH_DSA, InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA224, OID_SIG_SHA224_WITH_DSA, InclusiveIntRange.from(21)); addSupportedSigAlg( OID_DIGEST_SHA224, OID_SIG_SHA256_WITH_DSA, InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA256, OID_SIG_DSA, InclusiveIntRange.from(22)); addSupportedSigAlg( OID_DIGEST_SHA256, OID_SIG_SHA1_WITH_DSA, InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA256, OID_SIG_SHA224_WITH_DSA, InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA256, OID_SIG_SHA256_WITH_DSA, InclusiveIntRange.from(21)); addSupportedSigAlg( OID_DIGEST_SHA384, OID_SIG_SHA1_WITH_DSA, InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA384, OID_SIG_SHA224_WITH_DSA, InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA384, OID_SIG_SHA256_WITH_DSA, InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA512, OID_SIG_SHA1_WITH_DSA, InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA512, OID_SIG_SHA224_WITH_DSA, InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA512, OID_SIG_SHA256_WITH_DSA, InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA1, OID_SIG_EC_PUBLIC_KEY, InclusiveIntRange.from(18)); addSupportedSigAlg( OID_DIGEST_SHA224, OID_SIG_EC_PUBLIC_KEY, InclusiveIntRange.from(21)); addSupportedSigAlg( OID_DIGEST_SHA256, OID_SIG_EC_PUBLIC_KEY, InclusiveIntRange.from(18)); addSupportedSigAlg( OID_DIGEST_SHA384, OID_SIG_EC_PUBLIC_KEY, InclusiveIntRange.from(18)); addSupportedSigAlg( OID_DIGEST_SHA512, OID_SIG_EC_PUBLIC_KEY, InclusiveIntRange.from(18)); addSupportedSigAlg( OID_DIGEST_MD5, OID_SIG_SHA1_WITH_ECDSA, InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_MD5, OID_SIG_SHA224_WITH_ECDSA, InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_MD5, OID_SIG_SHA256_WITH_ECDSA, InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_MD5, OID_SIG_SHA384_WITH_ECDSA, InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_MD5, OID_SIG_SHA512_WITH_ECDSA, InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA1, OID_SIG_SHA1_WITH_ECDSA, InclusiveIntRange.from(18)); addSupportedSigAlg( OID_DIGEST_SHA1, OID_SIG_SHA224_WITH_ECDSA, InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA1, OID_SIG_SHA256_WITH_ECDSA, InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA1, OID_SIG_SHA384_WITH_ECDSA, InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA1, OID_SIG_SHA512_WITH_ECDSA, InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA224, OID_SIG_SHA1_WITH_ECDSA, InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA224, OID_SIG_SHA224_WITH_ECDSA, InclusiveIntRange.from(21)); addSupportedSigAlg( OID_DIGEST_SHA224, OID_SIG_SHA256_WITH_ECDSA, InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA224, OID_SIG_SHA384_WITH_ECDSA, InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA224, OID_SIG_SHA512_WITH_ECDSA, InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA256, OID_SIG_SHA1_WITH_ECDSA, InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA256, OID_SIG_SHA224_WITH_ECDSA, InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA256, OID_SIG_SHA256_WITH_ECDSA, InclusiveIntRange.from(21)); addSupportedSigAlg( OID_DIGEST_SHA256, OID_SIG_SHA384_WITH_ECDSA, InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA256, OID_SIG_SHA512_WITH_ECDSA, InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA384, OID_SIG_SHA1_WITH_ECDSA, InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA384, OID_SIG_SHA224_WITH_ECDSA, InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA384, OID_SIG_SHA256_WITH_ECDSA, InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA384, OID_SIG_SHA384_WITH_ECDSA, InclusiveIntRange.from(21)); addSupportedSigAlg( OID_DIGEST_SHA384, OID_SIG_SHA512_WITH_ECDSA, InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA512, OID_SIG_SHA1_WITH_ECDSA, InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA512, OID_SIG_SHA224_WITH_ECDSA, InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA512, OID_SIG_SHA256_WITH_ECDSA, InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA512, OID_SIG_SHA384_WITH_ECDSA, InclusiveIntRange.fromTo(21, 23)); addSupportedSigAlg( OID_DIGEST_SHA512, OID_SIG_SHA512_WITH_ECDSA, InclusiveIntRange.from(21)); } private static void addSupportedSigAlg( String digestAlgorithmOid, String signatureAlgorithmOid, InclusiveIntRange... supportedApiLevels) { SUPPORTED_SIG_ALG_OIDS.put( digestAlgorithmOid + "with" + signatureAlgorithmOid, Arrays.asList(supportedApiLevels)); } private List getSigAlgSupportedApiLevels( String digestAlgorithmOid, String signatureAlgorithmOid) { List result = SUPPORTED_SIG_ALG_OIDS.get(digestAlgorithmOid + "with" + signatureAlgorithmOid); return (result != null) ? result : Collections.emptyList(); } private static class OidToUserFriendlyNameMapper { private OidToUserFriendlyNameMapper() {} private static final Map OID_TO_USER_FRIENDLY_NAME = new HashMap<>(); static { OID_TO_USER_FRIENDLY_NAME.put(OID_DIGEST_MD5, "MD5"); OID_TO_USER_FRIENDLY_NAME.put(OID_DIGEST_SHA1, "SHA-1"); OID_TO_USER_FRIENDLY_NAME.put(OID_DIGEST_SHA224, "SHA-224"); OID_TO_USER_FRIENDLY_NAME.put(OID_DIGEST_SHA256, "SHA-256"); OID_TO_USER_FRIENDLY_NAME.put(OID_DIGEST_SHA384, "SHA-384"); OID_TO_USER_FRIENDLY_NAME.put(OID_DIGEST_SHA512, "SHA-512"); OID_TO_USER_FRIENDLY_NAME.put(OID_SIG_RSA, "RSA"); OID_TO_USER_FRIENDLY_NAME.put(OID_SIG_MD5_WITH_RSA, "MD5 with RSA"); OID_TO_USER_FRIENDLY_NAME.put(OID_SIG_SHA1_WITH_RSA, "SHA-1 with RSA"); OID_TO_USER_FRIENDLY_NAME.put(OID_SIG_SHA224_WITH_RSA, "SHA-224 with RSA"); OID_TO_USER_FRIENDLY_NAME.put(OID_SIG_SHA256_WITH_RSA, "SHA-256 with RSA"); OID_TO_USER_FRIENDLY_NAME.put(OID_SIG_SHA384_WITH_RSA, "SHA-384 with RSA"); OID_TO_USER_FRIENDLY_NAME.put(OID_SIG_SHA512_WITH_RSA, "SHA-512 with RSA"); OID_TO_USER_FRIENDLY_NAME.put(OID_SIG_DSA, "DSA"); OID_TO_USER_FRIENDLY_NAME.put(OID_SIG_SHA1_WITH_DSA, "SHA-1 with DSA"); OID_TO_USER_FRIENDLY_NAME.put(OID_SIG_SHA224_WITH_DSA, "SHA-224 with DSA"); OID_TO_USER_FRIENDLY_NAME.put(OID_SIG_SHA256_WITH_DSA, "SHA-256 with DSA"); OID_TO_USER_FRIENDLY_NAME.put(OID_SIG_EC_PUBLIC_KEY, "ECDSA"); OID_TO_USER_FRIENDLY_NAME.put(OID_SIG_SHA1_WITH_ECDSA, "SHA-1 with ECDSA"); OID_TO_USER_FRIENDLY_NAME.put(OID_SIG_SHA224_WITH_ECDSA, "SHA-224 with ECDSA"); OID_TO_USER_FRIENDLY_NAME.put(OID_SIG_SHA256_WITH_ECDSA, "SHA-256 with ECDSA"); OID_TO_USER_FRIENDLY_NAME.put(OID_SIG_SHA384_WITH_ECDSA, "SHA-384 with ECDSA"); OID_TO_USER_FRIENDLY_NAME.put(OID_SIG_SHA512_WITH_ECDSA, "SHA-512 with ECDSA"); } public static String getUserFriendlyNameForOid(String oid) { return OID_TO_USER_FRIENDLY_NAME.get(oid); } } private static final Map OID_TO_JCA_DIGEST_ALG = new HashMap<>(); static { OID_TO_JCA_DIGEST_ALG.put(OID_DIGEST_MD5, "MD5"); OID_TO_JCA_DIGEST_ALG.put(OID_DIGEST_SHA1, "SHA-1"); OID_TO_JCA_DIGEST_ALG.put(OID_DIGEST_SHA224, "SHA-224"); OID_TO_JCA_DIGEST_ALG.put(OID_DIGEST_SHA256, "SHA-256"); OID_TO_JCA_DIGEST_ALG.put(OID_DIGEST_SHA384, "SHA-384"); OID_TO_JCA_DIGEST_ALG.put(OID_DIGEST_SHA512, "SHA-512"); } private static String getJcaDigestAlgorithm(String oid) throws SignatureException { String result = OID_TO_JCA_DIGEST_ALG.get(oid); if (result == null) { throw new SignatureException("Unsupported digest algorithm: " + oid); } return result; } private static final Map OID_TO_JCA_SIGNATURE_ALG = new HashMap<>(); static { OID_TO_JCA_SIGNATURE_ALG.put(OID_SIG_MD5_WITH_RSA, "MD5withRSA"); OID_TO_JCA_SIGNATURE_ALG.put(OID_SIG_SHA1_WITH_RSA, "SHA1withRSA"); OID_TO_JCA_SIGNATURE_ALG.put(OID_SIG_SHA224_WITH_RSA, "SHA224withRSA"); OID_TO_JCA_SIGNATURE_ALG.put(OID_SIG_SHA256_WITH_RSA, "SHA256withRSA"); OID_TO_JCA_SIGNATURE_ALG.put(OID_SIG_SHA384_WITH_RSA, "SHA384withRSA"); OID_TO_JCA_SIGNATURE_ALG.put(OID_SIG_SHA512_WITH_RSA, "SHA512withRSA"); OID_TO_JCA_SIGNATURE_ALG.put(OID_SIG_SHA1_WITH_DSA, "SHA1withDSA"); OID_TO_JCA_SIGNATURE_ALG.put(OID_SIG_SHA224_WITH_DSA, "SHA224withDSA"); OID_TO_JCA_SIGNATURE_ALG.put(OID_SIG_SHA256_WITH_DSA, "SHA256withDSA"); OID_TO_JCA_SIGNATURE_ALG.put(OID_SIG_SHA1_WITH_ECDSA, "SHA1withECDSA"); OID_TO_JCA_SIGNATURE_ALG.put(OID_SIG_SHA224_WITH_ECDSA, "SHA224withECDSA"); OID_TO_JCA_SIGNATURE_ALG.put(OID_SIG_SHA256_WITH_ECDSA, "SHA256withECDSA"); OID_TO_JCA_SIGNATURE_ALG.put(OID_SIG_SHA384_WITH_ECDSA, "SHA384withECDSA"); OID_TO_JCA_SIGNATURE_ALG.put(OID_SIG_SHA512_WITH_ECDSA, "SHA512withECDSA"); } private static String getJcaSignatureAlgorithm( String digestAlgorithmOid, String signatureAlgorithmOid) throws SignatureException { // First check whether the signature algorithm OID alone is sufficient String result = OID_TO_JCA_SIGNATURE_ALG.get(signatureAlgorithmOid); if (result != null) { return result; } // Signature algorithm OID alone is insufficient. Need to combine digest algorithm OID // with signature algorithm OID. String suffix; if (OID_SIG_RSA.equals(signatureAlgorithmOid)) { suffix = "RSA"; } else if (OID_SIG_DSA.equals(signatureAlgorithmOid)) { suffix = "DSA"; } else if (OID_SIG_EC_PUBLIC_KEY.equals(signatureAlgorithmOid)) { suffix = "ECDSA"; } else { throw new SignatureException( "Unsupported JCA Signature algorithm" + " . Digest algorithm: " + digestAlgorithmOid + ", signature algorithm: " + signatureAlgorithmOid); } String jcaDigestAlg = getJcaDigestAlgorithm(digestAlgorithmOid); // Canonical name for SHA-1 with ... is SHA1with, rather than SHA1. Same for all other // SHA algorithms. if (jcaDigestAlg.startsWith("SHA-")) { jcaDigestAlg = "SHA" + jcaDigestAlg.substring("SHA-".length()); } return jcaDigestAlg + "with" + suffix; } public void verifySigFileAgainstManifest( byte[] manifestBytes, ManifestParser.Section manifestMainSection, Map entryNameToManifestSection, Map supportedApkSigSchemeNames, Set foundApkSigSchemeIds, int minSdkVersion, int maxSdkVersion) throws NoSuchAlgorithmException { // Inspect the main section of the .SF file. ManifestParser sf = new ManifestParser(mSigFileBytes); ManifestParser.Section sfMainSection = sf.readSection(); if (sfMainSection.getAttributeValue(Attributes.Name.SIGNATURE_VERSION) == null) { mResult.addError( Issue.JAR_SIG_MISSING_VERSION_ATTR_IN_SIG_FILE, mSignatureFileEntry.getName()); setIgnored(); return; } if (maxSdkVersion >= AndroidSdkVersion.N) { // Android N and newer rejects APKs whose .SF file says they were supposed to be // signed with APK Signature Scheme v2 (or newer) and yet no such signature was // found. checkForStrippedApkSignatures( sfMainSection, supportedApkSigSchemeNames, foundApkSigSchemeIds); if (mResult.containsErrors()) { return; } } boolean createdBySigntool = false; String createdBy = sfMainSection.getAttributeValue("Created-By"); if (createdBy != null) { createdBySigntool = createdBy.indexOf("signtool") != -1; } boolean manifestDigestVerified = verifyManifestDigest( sfMainSection, createdBySigntool, manifestBytes, minSdkVersion, maxSdkVersion); if (!createdBySigntool) { verifyManifestMainSectionDigest( sfMainSection, manifestMainSection, manifestBytes, minSdkVersion, maxSdkVersion); } if (mResult.containsErrors()) { return; } // Inspect per-entry sections of .SF file. Technically, if the digest of JAR manifest // verifies, per-entry sections should be ignored. However, most Android platform // implementations require that such sections exist. List sfSections = sf.readAllSections(); Set sfEntryNames = new HashSet<>(sfSections.size()); int sfSectionNumber = 0; for (ManifestParser.Section sfSection : sfSections) { sfSectionNumber++; String entryName = sfSection.getName(); if (entryName == null) { mResult.addError( Issue.JAR_SIG_UNNNAMED_SIG_FILE_SECTION, mSignatureFileEntry.getName(), sfSectionNumber); setIgnored(); return; } if (!sfEntryNames.add(entryName)) { mResult.addError( Issue.JAR_SIG_DUPLICATE_SIG_FILE_SECTION, mSignatureFileEntry.getName(), entryName); setIgnored(); return; } if (manifestDigestVerified) { // No need to verify this entry's corresponding JAR manifest entry because the // JAR manifest verifies in full. continue; } // Whole-file digest of JAR manifest hasn't been verified. Thus, we need to verify // the digest of the JAR manifest section corresponding to this .SF section. ManifestParser.Section manifestSection = entryNameToManifestSection.get(entryName); if (manifestSection == null) { mResult.addError( Issue.JAR_SIG_NO_ZIP_ENTRY_DIGEST_IN_SIG_FILE, entryName, mSignatureFileEntry.getName()); setIgnored(); continue; } verifyManifestIndividualSectionDigest( sfSection, createdBySigntool, manifestSection, manifestBytes, minSdkVersion, maxSdkVersion); } mSigFileEntryNames = sfEntryNames; } /** * Returns {@code true} if the whole-file digest of the manifest against the main section of * the .SF file. */ private boolean verifyManifestDigest( ManifestParser.Section sfMainSection, boolean createdBySigntool, byte[] manifestBytes, int minSdkVersion, int maxSdkVersion) throws NoSuchAlgorithmException { Collection expectedDigests = getDigestsToVerify( sfMainSection, ((createdBySigntool) ? "-Digest" : "-Digest-Manifest"), minSdkVersion, maxSdkVersion); boolean digestFound = !expectedDigests.isEmpty(); if (!digestFound) { mResult.addWarning( Issue.JAR_SIG_NO_MANIFEST_DIGEST_IN_SIG_FILE, mSignatureFileEntry.getName()); return false; } boolean verified = true; for (NamedDigest expectedDigest : expectedDigests) { String jcaDigestAlgorithm = expectedDigest.jcaDigestAlgorithm; byte[] actual = digest(jcaDigestAlgorithm, manifestBytes); byte[] expected = expectedDigest.digest; if (!Arrays.equals(expected, actual)) { mResult.addWarning( Issue.JAR_SIG_ZIP_ENTRY_DIGEST_DID_NOT_VERIFY, V1SchemeSigner.MANIFEST_ENTRY_NAME, jcaDigestAlgorithm, mSignatureFileEntry.getName(), Base64.getEncoder().encodeToString(actual), Base64.getEncoder().encodeToString(expected)); verified = false; } } return verified; } /** * Verifies the digest of the manifest's main section against the main section of the .SF * file. */ private void verifyManifestMainSectionDigest( ManifestParser.Section sfMainSection, ManifestParser.Section manifestMainSection, byte[] manifestBytes, int minSdkVersion, int maxSdkVersion) throws NoSuchAlgorithmException { Collection expectedDigests = getDigestsToVerify( sfMainSection, "-Digest-Manifest-Main-Attributes", minSdkVersion, maxSdkVersion); if (expectedDigests.isEmpty()) { return; } for (NamedDigest expectedDigest : expectedDigests) { String jcaDigestAlgorithm = expectedDigest.jcaDigestAlgorithm; byte[] actual = digest( jcaDigestAlgorithm, manifestBytes, manifestMainSection.getStartOffset(), manifestMainSection.getSizeBytes()); byte[] expected = expectedDigest.digest; if (!Arrays.equals(expected, actual)) { mResult.addError( Issue.JAR_SIG_MANIFEST_MAIN_SECTION_DIGEST_DID_NOT_VERIFY, jcaDigestAlgorithm, mSignatureFileEntry.getName(), Base64.getEncoder().encodeToString(actual), Base64.getEncoder().encodeToString(expected)); } } } /** * Verifies the digest of the manifest's individual section against the corresponding * individual section of the .SF file. */ private void verifyManifestIndividualSectionDigest( ManifestParser.Section sfIndividualSection, boolean createdBySigntool, ManifestParser.Section manifestIndividualSection, byte[] manifestBytes, int minSdkVersion, int maxSdkVersion) throws NoSuchAlgorithmException { String entryName = sfIndividualSection.getName(); Collection expectedDigests = getDigestsToVerify( sfIndividualSection, "-Digest", minSdkVersion, maxSdkVersion); if (expectedDigests.isEmpty()) { mResult.addError( Issue.JAR_SIG_NO_ZIP_ENTRY_DIGEST_IN_SIG_FILE, entryName, mSignatureFileEntry.getName()); return; } int sectionStartIndex = manifestIndividualSection.getStartOffset(); int sectionSizeBytes = manifestIndividualSection.getSizeBytes(); if (createdBySigntool) { int sectionEndIndex = sectionStartIndex + sectionSizeBytes; if ((manifestBytes[sectionEndIndex - 1] == '\n') && (manifestBytes[sectionEndIndex - 2] == '\n')) { sectionSizeBytes--; } } for (NamedDigest expectedDigest : expectedDigests) { String jcaDigestAlgorithm = expectedDigest.jcaDigestAlgorithm; byte[] actual = digest( jcaDigestAlgorithm, manifestBytes, sectionStartIndex, sectionSizeBytes); byte[] expected = expectedDigest.digest; if (!Arrays.equals(expected, actual)) { mResult.addError( Issue.JAR_SIG_MANIFEST_SECTION_DIGEST_DID_NOT_VERIFY, entryName, jcaDigestAlgorithm, mSignatureFileEntry.getName(), Base64.getEncoder().encodeToString(actual), Base64.getEncoder().encodeToString(expected)); } } } private void checkForStrippedApkSignatures( ManifestParser.Section sfMainSection, Map supportedApkSigSchemeNames, Set foundApkSigSchemeIds) { String signedWithApkSchemes = sfMainSection.getAttributeValue( V1SchemeSigner.SF_ATTRIBUTE_NAME_ANDROID_APK_SIGNED_NAME_STR); // This field contains a comma-separated list of APK signature scheme IDs which were // used to sign this APK. Android rejects APKs where an ID is known to the platform but // the APK didn't verify using that scheme. if (signedWithApkSchemes == null) { // APK signature (e.g., v2 scheme) stripping protections not enabled. if (!foundApkSigSchemeIds.isEmpty()) { // APK is signed with an APK signature scheme such as v2 scheme. mResult.addWarning( Issue.JAR_SIG_NO_APK_SIG_STRIP_PROTECTION, mSignatureFileEntry.getName()); } return; } if (supportedApkSigSchemeNames.isEmpty()) { return; } Set supportedApkSigSchemeIds = supportedApkSigSchemeNames.keySet(); Set supportedExpectedApkSigSchemeIds = new HashSet<>(1); StringTokenizer tokenizer = new StringTokenizer(signedWithApkSchemes, ","); while (tokenizer.hasMoreTokens()) { String idText = tokenizer.nextToken().trim(); if (idText.isEmpty()) { continue; } int id; try { id = Integer.parseInt(idText); } catch (Exception ignored) { continue; } // This APK was supposed to be signed with the APK signature scheme having // this ID. if (supportedApkSigSchemeIds.contains(id)) { supportedExpectedApkSigSchemeIds.add(id); } else { mResult.addWarning( Issue.JAR_SIG_UNKNOWN_APK_SIG_SCHEME_ID, mSignatureFileEntry.getName(), id); } } for (int id : supportedExpectedApkSigSchemeIds) { if (!foundApkSigSchemeIds.contains(id)) { String apkSigSchemeName = supportedApkSigSchemeNames.get(id); mResult.addError( Issue.JAR_SIG_MISSING_APK_SIG_REFERENCED, mSignatureFileEntry.getName(), id, apkSigSchemeName); } } } } private static Collection getDigestsToVerify( ManifestParser.Section section, String digestAttrSuffix, int minSdkVersion, int maxSdkVersion) { Decoder base64Decoder = Base64.getDecoder(); List result = new ArrayList<>(1); if (minSdkVersion < AndroidSdkVersion.JELLY_BEAN_MR2) { // Prior to JB MR2, Android platform's logic for picking a digest algorithm to verify is // to rely on the ancient Digest-Algorithms attribute which contains // whitespace-separated list of digest algorithms (defaulting to SHA-1) to try. The // first digest attribute (with supported digest algorithm) found using the list is // used. String algs = section.getAttributeValue("Digest-Algorithms"); if (algs == null) { algs = "SHA SHA1"; } StringTokenizer tokens = new StringTokenizer(algs); while (tokens.hasMoreTokens()) { String alg = tokens.nextToken(); String attrName = alg + digestAttrSuffix; String digestBase64 = section.getAttributeValue(attrName); if (digestBase64 == null) { // Attribute not found continue; } alg = getCanonicalJcaMessageDigestAlgorithm(alg); if ((alg == null) || (getMinSdkVersionFromWhichSupportedInManifestOrSignatureFile(alg) > minSdkVersion)) { // Unsupported digest algorithm continue; } // Supported digest algorithm result.add(new NamedDigest(alg, base64Decoder.decode(digestBase64))); break; } // No supported digests found -- this will fail to verify on pre-JB MR2 Androids. if (result.isEmpty()) { return result; } } if (maxSdkVersion >= AndroidSdkVersion.JELLY_BEAN_MR2) { // On JB MR2 and newer, Android platform picks the strongest algorithm out of: // SHA-512, SHA-384, SHA-256, SHA-1. for (String alg : JB_MR2_AND_NEWER_DIGEST_ALGS) { String attrName = getJarDigestAttributeName(alg, digestAttrSuffix); String digestBase64 = section.getAttributeValue(attrName); if (digestBase64 == null) { // Attribute not found continue; } byte[] digest = base64Decoder.decode(digestBase64); byte[] digestInResult = getDigest(result, alg); if ((digestInResult == null) || (!Arrays.equals(digestInResult, digest))) { result.add(new NamedDigest(alg, digest)); } break; } } return result; } private static final String[] JB_MR2_AND_NEWER_DIGEST_ALGS = { "SHA-512", "SHA-384", "SHA-256", "SHA-1", }; private static String getCanonicalJcaMessageDigestAlgorithm(String algorithm) { return UPPER_CASE_JCA_DIGEST_ALG_TO_CANONICAL.get(algorithm.toUpperCase(Locale.US)); } public static int getMinSdkVersionFromWhichSupportedInManifestOrSignatureFile( String jcaAlgorithmName) { Integer result = MIN_SDK_VESION_FROM_WHICH_DIGEST_SUPPORTED_IN_MANIFEST.get( jcaAlgorithmName.toUpperCase(Locale.US)); return (result != null) ? result : Integer.MAX_VALUE; } private static String getJarDigestAttributeName( String jcaDigestAlgorithm, String attrNameSuffix) { if ("SHA-1".equalsIgnoreCase(jcaDigestAlgorithm)) { return "SHA1" + attrNameSuffix; } else { return jcaDigestAlgorithm + attrNameSuffix; } } private static final Map UPPER_CASE_JCA_DIGEST_ALG_TO_CANONICAL; static { UPPER_CASE_JCA_DIGEST_ALG_TO_CANONICAL = new HashMap<>(8); UPPER_CASE_JCA_DIGEST_ALG_TO_CANONICAL.put("MD5", "MD5"); UPPER_CASE_JCA_DIGEST_ALG_TO_CANONICAL.put("SHA", "SHA-1"); UPPER_CASE_JCA_DIGEST_ALG_TO_CANONICAL.put("SHA1", "SHA-1"); UPPER_CASE_JCA_DIGEST_ALG_TO_CANONICAL.put("SHA-1", "SHA-1"); UPPER_CASE_JCA_DIGEST_ALG_TO_CANONICAL.put("SHA-256", "SHA-256"); UPPER_CASE_JCA_DIGEST_ALG_TO_CANONICAL.put("SHA-384", "SHA-384"); UPPER_CASE_JCA_DIGEST_ALG_TO_CANONICAL.put("SHA-512", "SHA-512"); } private static final Map MIN_SDK_VESION_FROM_WHICH_DIGEST_SUPPORTED_IN_MANIFEST; static { MIN_SDK_VESION_FROM_WHICH_DIGEST_SUPPORTED_IN_MANIFEST = new HashMap<>(5); MIN_SDK_VESION_FROM_WHICH_DIGEST_SUPPORTED_IN_MANIFEST.put("MD5", 0); MIN_SDK_VESION_FROM_WHICH_DIGEST_SUPPORTED_IN_MANIFEST.put("SHA-1", 0); MIN_SDK_VESION_FROM_WHICH_DIGEST_SUPPORTED_IN_MANIFEST.put("SHA-256", 0); MIN_SDK_VESION_FROM_WHICH_DIGEST_SUPPORTED_IN_MANIFEST.put( "SHA-384", AndroidSdkVersion.GINGERBREAD); MIN_SDK_VESION_FROM_WHICH_DIGEST_SUPPORTED_IN_MANIFEST.put( "SHA-512", AndroidSdkVersion.GINGERBREAD); } private static byte[] getDigest(Collection digests, String jcaDigestAlgorithm) { for (NamedDigest digest : digests) { if (digest.jcaDigestAlgorithm.equalsIgnoreCase(jcaDigestAlgorithm)) { return digest.digest; } } return null; } public static List parseZipCentralDirectory( DataSource apk, ApkUtils.ZipSections apkSections) throws IOException, ApkFormatException { // Read the ZIP Central Directory long cdSizeBytes = apkSections.getZipCentralDirectorySizeBytes(); if (cdSizeBytes > Integer.MAX_VALUE) { throw new ApkFormatException("ZIP Central Directory too large: " + cdSizeBytes); } long cdOffset = apkSections.getZipCentralDirectoryOffset(); ByteBuffer cd = apk.getByteBuffer(cdOffset, (int) cdSizeBytes); cd.order(ByteOrder.LITTLE_ENDIAN); // Parse the ZIP Central Directory int expectedCdRecordCount = apkSections.getZipCentralDirectoryRecordCount(); List cdRecords = new ArrayList<>(expectedCdRecordCount); for (int i = 0; i < expectedCdRecordCount; i++) { CentralDirectoryRecord cdRecord; int offsetInsideCd = cd.position(); try { cdRecord = CentralDirectoryRecord.getRecord(cd); } catch (ZipFormatException e) { throw new ApkFormatException( "Malformed ZIP Central Directory record #" + (i + 1) + " at file offset " + (cdOffset + offsetInsideCd), e); } String entryName = cdRecord.getName(); if (entryName.endsWith("/")) { // Ignore directory entries continue; } cdRecords.add(cdRecord); } // There may be more data in Central Directory, but we don't warn or throw because Android // ignores unused CD data. return cdRecords; } /** * Returns {@code true} if the provided JAR entry must be mentioned in signed JAR archive's * manifest for the APK to verify on Android. */ private static boolean isJarEntryDigestNeededInManifest(String entryName) { // NOTE: This logic is different from what's required by the JAR signing scheme. This is // because Android's APK verification logic differs from that spec. In particular, JAR // signing spec includes into JAR manifest all files in subdirectories of META-INF and // any files inside META-INF not related to signatures. if (entryName.startsWith("META-INF/")) { return false; } return !entryName.endsWith("/"); } private static Set verifyJarEntriesAgainstManifestAndSigners( DataSource apk, long cdOffsetInApk, Collection cdRecords, Map entryNameToManifestSection, List signers, int minSdkVersion, int maxSdkVersion, Result result) throws ApkFormatException, IOException, NoSuchAlgorithmException { // Iterate over APK contents as sequentially as possible to improve performance. List cdRecordsSortedByLocalFileHeaderOffset = new ArrayList<>(cdRecords); Collections.sort( cdRecordsSortedByLocalFileHeaderOffset, CentralDirectoryRecord.BY_LOCAL_FILE_HEADER_OFFSET_COMPARATOR); Set manifestEntryNamesMissingFromApk = new HashSet<>(entryNameToManifestSection.keySet()); List firstSignedEntrySigners = null; String firstSignedEntryName = null; for (CentralDirectoryRecord cdRecord : cdRecordsSortedByLocalFileHeaderOffset) { String entryName = cdRecord.getName(); manifestEntryNamesMissingFromApk.remove(entryName); if (!isJarEntryDigestNeededInManifest(entryName)) { continue; } ManifestParser.Section manifestSection = entryNameToManifestSection.get(entryName); if (manifestSection == null) { result.addError(Issue.JAR_SIG_NO_ZIP_ENTRY_DIGEST_IN_MANIFEST, entryName); continue; } List entrySigners = new ArrayList<>(signers.size()); for (Signer signer : signers) { if (signer.getSigFileEntryNames().contains(entryName)) { entrySigners.add(signer); } } if (entrySigners.isEmpty()) { result.addError(Issue.JAR_SIG_ZIP_ENTRY_NOT_SIGNED, entryName); continue; } if (firstSignedEntrySigners == null) { firstSignedEntrySigners = entrySigners; firstSignedEntryName = entryName; } else if (!entrySigners.equals(firstSignedEntrySigners)) { result.addError( Issue.JAR_SIG_ZIP_ENTRY_SIGNERS_MISMATCH, firstSignedEntryName, getSignerNames(firstSignedEntrySigners), entryName, getSignerNames(entrySigners)); continue; } List expectedDigests = new ArrayList<>( getDigestsToVerify( manifestSection, "-Digest", minSdkVersion, maxSdkVersion)); if (expectedDigests.isEmpty()) { result.addError(Issue.JAR_SIG_NO_ZIP_ENTRY_DIGEST_IN_MANIFEST, entryName); continue; } MessageDigest[] mds = new MessageDigest[expectedDigests.size()]; for (int i = 0; i < expectedDigests.size(); i++) { mds[i] = getMessageDigest(expectedDigests.get(i).jcaDigestAlgorithm); } try { LocalFileRecord.outputUncompressedData( apk, cdRecord, cdOffsetInApk, new MessageDigestSink(mds)); } catch (ZipFormatException e) { throw new ApkFormatException("Malformed ZIP entry: " + entryName, e); } catch (IOException e) { throw new IOException("Failed to read entry: " + entryName, e); } for (int i = 0; i < expectedDigests.size(); i++) { NamedDigest expectedDigest = expectedDigests.get(i); byte[] actualDigest = mds[i].digest(); if (!Arrays.equals(expectedDigest.digest, actualDigest)) { result.addError( Issue.JAR_SIG_ZIP_ENTRY_DIGEST_DID_NOT_VERIFY, entryName, expectedDigest.jcaDigestAlgorithm, V1SchemeSigner.MANIFEST_ENTRY_NAME, Base64.getEncoder().encodeToString(actualDigest), Base64.getEncoder().encodeToString(expectedDigest.digest)); } } } if (firstSignedEntrySigners == null) { result.addError(Issue.JAR_SIG_NO_SIGNED_ZIP_ENTRIES); return Collections.emptySet(); } else { return new HashSet<>(firstSignedEntrySigners); } } private static List getSignerNames(List signers) { if (signers.isEmpty()) { return Collections.emptyList(); } List result = new ArrayList<>(signers.size()); for (Signer signer : signers) { result.add(signer.getName()); } return result; } private static MessageDigest getMessageDigest(String algorithm) throws NoSuchAlgorithmException { return MessageDigest.getInstance(algorithm); } private static byte[] digest(String algorithm, byte[] data, int offset, int length) throws NoSuchAlgorithmException { MessageDigest md = getMessageDigest(algorithm); md.update(data, offset, length); return md.digest(); } private static byte[] digest(String algorithm, byte[] data) throws NoSuchAlgorithmException { return getMessageDigest(algorithm).digest(data); } private static class NamedDigest { private final String jcaDigestAlgorithm; private final byte[] digest; private NamedDigest(String jcaDigestAlgorithm, byte[] digest) { this.jcaDigestAlgorithm = jcaDigestAlgorithm; this.digest = digest; } } public static class Result { /** Whether the APK's JAR signature verifies. */ public boolean verified; /** List of APK's signers. These signers are used by Android. */ public final List signers = new ArrayList<>(); /** * Signers encountered in the APK but not included in the set of the APK's signers. These * signers are ignored by Android. */ public final List ignoredSigners = new ArrayList<>(); private final List mWarnings = new ArrayList<>(); private final List mErrors = new ArrayList<>(); private boolean containsErrors() { if (!mErrors.isEmpty()) { return true; } for (SignerInfo signer : signers) { if (signer.containsErrors()) { return true; } } return false; } private void addError(Issue msg, Object... parameters) { mErrors.add(new IssueWithParams(msg, parameters)); } private void addWarning(Issue msg, Object... parameters) { mWarnings.add(new IssueWithParams(msg, parameters)); } public List getErrors() { return mErrors; } public List getWarnings() { return mWarnings; } public static class SignerInfo { public final String name; public final String signatureFileName; public final String signatureBlockFileName; public final List certChain = new ArrayList<>(); private final List mWarnings = new ArrayList<>(); private final List mErrors = new ArrayList<>(); private SignerInfo( String name, String signatureBlockFileName, String signatureFileName) { this.name = name; this.signatureBlockFileName = signatureBlockFileName; this.signatureFileName = signatureFileName; } private boolean containsErrors() { return !mErrors.isEmpty(); } private void addError(Issue msg, Object... parameters) { mErrors.add(new IssueWithParams(msg, parameters)); } private void addWarning(Issue msg, Object... parameters) { mWarnings.add(new IssueWithParams(msg, parameters)); } public List getErrors() { return mErrors; } public List getWarnings() { return mWarnings; } } } private static class SignedAttributes { private Map> mAttrs; public SignedAttributes(Collection attrs) throws Pkcs7DecodingException { Map> result = new HashMap<>(attrs.size()); for (Attribute attr : attrs) { if (result.put(attr.attrType, attr.attrValues) != null) { throw new Pkcs7DecodingException("Duplicate signed attribute: " + attr.attrType); } } mAttrs = result; } private Asn1OpaqueObject getSingleValue(String attrOid) throws Pkcs7DecodingException { List values = mAttrs.get(attrOid); if ((values == null) || (values.isEmpty())) { return null; } if (values.size() > 1) { throw new Pkcs7DecodingException("Attribute " + attrOid + " has multiple values"); } return values.get(0); } public String getSingleObjectIdentifierValue(String attrOid) throws Pkcs7DecodingException { Asn1OpaqueObject value = getSingleValue(attrOid); if (value == null) { return null; } try { return Asn1BerParser.parse(value.getEncoded(), ObjectIdentifierChoice.class).value; } catch (Asn1DecodingException e) { throw new Pkcs7DecodingException("Failed to decode OBJECT IDENTIFIER", e); } } public byte[] getSingleOctetStringValue(String attrOid) throws Pkcs7DecodingException { Asn1OpaqueObject value = getSingleValue(attrOid); if (value == null) { return null; } try { return Asn1BerParser.parse(value.getEncoded(), OctetStringChoice.class).value; } catch (Asn1DecodingException e) { throw new Pkcs7DecodingException("Failed to decode OBJECT IDENTIFIER", e); } } } @Asn1Class(type = Asn1Type.CHOICE) public static class OctetStringChoice { @Asn1Field(type = Asn1Type.OCTET_STRING) public byte[] value; } @Asn1Class(type = Asn1Type.CHOICE) public static class ObjectIdentifierChoice { @Asn1Field(type = Asn1Type.OBJECT_IDENTIFIER) public String value; } } src/main/java/com/android/apksig/internal/apk/v2/0040755 0000000 0000000 00000000000 13243353143 020637 5ustar000000000 0000000 src/main/java/com/android/apksig/internal/apk/v2/ContentDigestAlgorithm.java0100644 0000000 0000000 00000003262 13243353143 026123 0ustar000000000 0000000 /* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.internal.apk.v2; /** * APK Signature Scheme v2 content digest algorithm. */ public enum ContentDigestAlgorithm { /** SHA2-256 over 1 MB chunks. */ CHUNKED_SHA256("SHA-256", 256 / 8), /** SHA2-512 over 1 MB chunks. */ CHUNKED_SHA512("SHA-512", 512 / 8); private final String mJcaMessageDigestAlgorithm; private final int mChunkDigestOutputSizeBytes; private ContentDigestAlgorithm( String jcaMessageDigestAlgorithm, int chunkDigestOutputSizeBytes) { mJcaMessageDigestAlgorithm = jcaMessageDigestAlgorithm; mChunkDigestOutputSizeBytes = chunkDigestOutputSizeBytes; } /** * Returns the {@link java.security.MessageDigest} algorithm used for computing digests of * chunks by this content digest algorithm. */ String getJcaMessageDigestAlgorithm() { return mJcaMessageDigestAlgorithm; } /** * Returns the size (in bytes) of the digest of a chunk of content. */ int getChunkDigestOutputSizeBytes() { return mChunkDigestOutputSizeBytes; } }src/main/java/com/android/apksig/internal/apk/v2/SignatureAlgorithm.java0100644 0000000 0000000 00000011405 13243353143 025310 0ustar000000000 0000000 /* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.internal.apk.v2; import com.android.apksig.internal.util.Pair; import java.security.spec.AlgorithmParameterSpec; import java.security.spec.MGF1ParameterSpec; import java.security.spec.PSSParameterSpec; /** * APK Signature Scheme v2 signature algorithm. */ public enum SignatureAlgorithm { /** * RSASSA-PSS with SHA2-256 digest, SHA2-256 MGF1, 32 bytes of salt, trailer: 0xbc, content * digested using SHA2-256 in 1 MB chunks. */ RSA_PSS_WITH_SHA256( 0x0101, ContentDigestAlgorithm.CHUNKED_SHA256, "RSA", Pair.of("SHA256withRSA/PSS", new PSSParameterSpec( "SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 256 / 8, 1))), /** * RSASSA-PSS with SHA2-512 digest, SHA2-512 MGF1, 64 bytes of salt, trailer: 0xbc, content * digested using SHA2-512 in 1 MB chunks. */ RSA_PSS_WITH_SHA512( 0x0102, ContentDigestAlgorithm.CHUNKED_SHA512, "RSA", Pair.of( "SHA512withRSA/PSS", new PSSParameterSpec( "SHA-512", "MGF1", MGF1ParameterSpec.SHA512, 512 / 8, 1))), /** RSASSA-PKCS1-v1_5 with SHA2-256 digest, content digested using SHA2-256 in 1 MB chunks. */ RSA_PKCS1_V1_5_WITH_SHA256( 0x0103, ContentDigestAlgorithm.CHUNKED_SHA256, "RSA", Pair.of("SHA256withRSA", null)), /** RSASSA-PKCS1-v1_5 with SHA2-512 digest, content digested using SHA2-512 in 1 MB chunks. */ RSA_PKCS1_V1_5_WITH_SHA512( 0x0104, ContentDigestAlgorithm.CHUNKED_SHA512, "RSA", Pair.of("SHA512withRSA", null)), /** ECDSA with SHA2-256 digest, content digested using SHA2-256 in 1 MB chunks. */ ECDSA_WITH_SHA256( 0x0201, ContentDigestAlgorithm.CHUNKED_SHA256, "EC", Pair.of("SHA256withECDSA", null)), /** ECDSA with SHA2-512 digest, content digested using SHA2-512 in 1 MB chunks. */ ECDSA_WITH_SHA512( 0x0202, ContentDigestAlgorithm.CHUNKED_SHA512, "EC", Pair.of("SHA512withECDSA", null)), /** DSA with SHA2-256 digest, content digested using SHA2-256 in 1 MB chunks. */ DSA_WITH_SHA256( 0x0301, ContentDigestAlgorithm.CHUNKED_SHA256, "DSA", Pair.of("SHA256withDSA", null)); private final int mId; private final String mJcaKeyAlgorithm; private final ContentDigestAlgorithm mContentDigestAlgorithm; private final Pair mJcaSignatureAlgAndParams; private SignatureAlgorithm(int id, ContentDigestAlgorithm contentDigestAlgorithm, String jcaKeyAlgorithm, Pair jcaSignatureAlgAndParams) { mId = id; mContentDigestAlgorithm = contentDigestAlgorithm; mJcaKeyAlgorithm = jcaKeyAlgorithm; mJcaSignatureAlgAndParams = jcaSignatureAlgAndParams; } /** * Returns the ID of this signature algorithm as used in APK Signature Scheme v2 wire format. */ int getId() { return mId; } /** * Returns the content digest algorithm associated with this signature algorithm. */ ContentDigestAlgorithm getContentDigestAlgorithm() { return mContentDigestAlgorithm; } /** * Returns the JCA {@link java.security.Key} algorithm used by this signature scheme. */ String getJcaKeyAlgorithm() { return mJcaKeyAlgorithm; } /** * Returns the {@link java.security.Signature} algorithm and the {@link AlgorithmParameterSpec} * (or null if not needed) to parameterize the {@code Signature}. */ Pair getJcaSignatureAlgorithmAndParams() { return mJcaSignatureAlgAndParams; } static SignatureAlgorithm findById(int id) { for (SignatureAlgorithm alg : SignatureAlgorithm.values()) { if (alg.getId() == id) { return alg; } } return null; } } src/main/java/com/android/apksig/internal/apk/v2/V2SchemeSigner.java0100644 0000000 0000000 00000067770 13243353143 024304 0ustar000000000 0000000 /* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.internal.apk.v2; import com.android.apksig.internal.util.MessageDigestSink; import com.android.apksig.internal.util.Pair; import com.android.apksig.internal.zip.ZipUtils; import com.android.apksig.util.DataSource; import com.android.apksig.util.DataSources; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.security.DigestException; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.KeyFactory; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.Signature; import java.security.SignatureException; import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; import java.security.interfaces.ECKey; import java.security.interfaces.RSAKey; import java.security.spec.AlgorithmParameterSpec; import java.security.spec.InvalidKeySpecException; import java.security.spec.X509EncodedKeySpec; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; /** * APK Signature Scheme v2 signer. * *

APK Signature Scheme v2 is a whole-file signature scheme which aims to protect every single * bit of the APK, as opposed to the JAR Signature Scheme which protects only the names and * uncompressed contents of ZIP entries. * * @see APK Signature Scheme v2 */ public abstract class V2SchemeSigner { /* * The two main goals of APK Signature Scheme v2 are: * 1. Detect any unauthorized modifications to the APK. This is achieved by making the signature * cover every byte of the APK being signed. * 2. Enable much faster signature and integrity verification. This is achieved by requiring * only a minimal amount of APK parsing before the signature is verified, thus completely * bypassing ZIP entry decompression and by making integrity verification parallelizable by * employing a hash tree. * * The generated signature block is wrapped into an APK Signing Block and inserted into the * original APK immediately before the start of ZIP Central Directory. This is to ensure that * JAR and ZIP parsers continue to work on the signed APK. The APK Signing Block is designed for * extensibility. For example, a future signature scheme could insert its signatures there as * well. The contract of the APK Signing Block is that all contents outside of the block must be * protected by signatures inside the block. */ private static final int CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES = 1024 * 1024; private static final byte[] APK_SIGNING_BLOCK_MAGIC = new byte[] { 0x41, 0x50, 0x4b, 0x20, 0x53, 0x69, 0x67, 0x20, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x20, 0x34, 0x32, }; private static final int APK_SIGNATURE_SCHEME_V2_BLOCK_ID = 0x7109871a; /** * Signer configuration. */ public static class SignerConfig { /** Private key. */ public PrivateKey privateKey; /** * Certificates, with the first certificate containing the public key corresponding to * {@link #privateKey}. */ public List certificates; /** * List of signature algorithms with which to sign. */ public List signatureAlgorithms; } /** Hidden constructor to prevent instantiation. */ private V2SchemeSigner() {} /** * Gets the APK Signature Scheme v2 signature algorithms to be used for signing an APK using the * provided key. * * @param minSdkVersion minimum API Level of the platform on which the APK may be installed (see * AndroidManifest.xml minSdkVersion attribute). * * @throws InvalidKeyException if the provided key is not suitable for signing APKs using * APK Signature Scheme v2 */ public static List getSuggestedSignatureAlgorithms( PublicKey signingKey, int minSdkVersion) throws InvalidKeyException { String keyAlgorithm = signingKey.getAlgorithm(); if ("RSA".equalsIgnoreCase(keyAlgorithm)) { // Use RSASSA-PKCS1-v1_5 signature scheme instead of RSASSA-PSS to guarantee // deterministic signatures which make life easier for OTA updates (fewer files // changed when deterministic signature schemes are used). // Pick a digest which is no weaker than the key. int modulusLengthBits = ((RSAKey) signingKey).getModulus().bitLength(); if (modulusLengthBits <= 3072) { // 3072-bit RSA is roughly 128-bit strong, meaning SHA-256 is a good fit. return Collections.singletonList(SignatureAlgorithm.RSA_PKCS1_V1_5_WITH_SHA256); } else { // Keys longer than 3072 bit need to be paired with a stronger digest to avoid the // digest being the weak link. SHA-512 is the next strongest supported digest. return Collections.singletonList(SignatureAlgorithm.RSA_PKCS1_V1_5_WITH_SHA512); } } else if ("DSA".equalsIgnoreCase(keyAlgorithm)) { // DSA is supported only with SHA-256. return Collections.singletonList(SignatureAlgorithm.DSA_WITH_SHA256); } else if ("EC".equalsIgnoreCase(keyAlgorithm)) { // Pick a digest which is no weaker than the key. int keySizeBits = ((ECKey) signingKey).getParams().getOrder().bitLength(); if (keySizeBits <= 256) { // 256-bit Elliptic Curve is roughly 128-bit strong, meaning SHA-256 is a good fit. return Collections.singletonList(SignatureAlgorithm.ECDSA_WITH_SHA256); } else { // Keys longer than 256 bit need to be paired with a stronger digest to avoid the // digest being the weak link. SHA-512 is the next strongest supported digest. return Collections.singletonList(SignatureAlgorithm.ECDSA_WITH_SHA512); } } else { throw new InvalidKeyException("Unsupported key algorithm: " + keyAlgorithm); } } /** * Signs the provided APK using APK Signature Scheme v2 and returns the APK Signing Block * containing the signature. * * @param signerConfigs signer configurations, one for each signer At least one signer config * must be provided. * * @throws IOException if an I/O error occurs * @throws NoSuchAlgorithmException if a required cryptographic algorithm implementation is * missing * @throws InvalidKeyException if a signing key is not suitable for this signature scheme or * cannot be used in general * @throws SignatureException if an error occurs when computing digests of generating * signatures */ public static byte[] generateApkSigningBlock( DataSource beforeCentralDir, DataSource centralDir, DataSource eocd, List signerConfigs) throws IOException, NoSuchAlgorithmException, InvalidKeyException, SignatureException { if (signerConfigs.isEmpty()) { throw new IllegalArgumentException( "No signer configs provided. At least one is required"); } // Figure out which digest(s) to use for APK contents. Set contentDigestAlgorithms = new HashSet<>(1); for (SignerConfig signerConfig : signerConfigs) { for (SignatureAlgorithm signatureAlgorithm : signerConfig.signatureAlgorithms) { contentDigestAlgorithms.add(signatureAlgorithm.getContentDigestAlgorithm()); } } // Ensure that, when digesting, ZIP End of Central Directory record's Central Directory // offset field is treated as pointing to the offset at which the APK Signing Block will // start. long centralDirOffsetForDigesting = beforeCentralDir.size(); ByteBuffer eocdBuf = ByteBuffer.allocate((int) eocd.size()); eocdBuf.order(ByteOrder.LITTLE_ENDIAN); eocd.copyTo(0, (int) eocd.size(), eocdBuf); eocdBuf.flip(); ZipUtils.setZipEocdCentralDirectoryOffset(eocdBuf, centralDirOffsetForDigesting); // Compute digests of APK contents. Map contentDigests; // digest algorithm ID -> digest try { contentDigests = computeContentDigests( contentDigestAlgorithms, new DataSource[] { beforeCentralDir, centralDir, DataSources.asDataSource(eocdBuf)}); } catch (IOException e) { throw new IOException("Failed to read APK being signed", e); } catch (DigestException e) { throw new SignatureException("Failed to compute digests of APK", e); } // Sign the digests and wrap the signatures and signer info into an APK Signing Block. return generateApkSigningBlock(signerConfigs, contentDigests); } static Map computeContentDigests( Set digestAlgorithms, DataSource[] contents) throws IOException, NoSuchAlgorithmException, DigestException { // For each digest algorithm the result is computed as follows: // 1. Each segment of contents is split into consecutive chunks of 1 MB in size. // The final chunk will be shorter iff the length of segment is not a multiple of 1 MB. // No chunks are produced for empty (zero length) segments. // 2. The digest of each chunk is computed over the concatenation of byte 0xa5, the chunk's // length in bytes (uint32 little-endian) and the chunk's contents. // 3. The output digest is computed over the concatenation of the byte 0x5a, the number of // chunks (uint32 little-endian) and the concatenation of digests of chunks of all // segments in-order. long chunkCountLong = 0; for (DataSource input : contents) { chunkCountLong += getChunkCount(input.size(), CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES); } if (chunkCountLong > Integer.MAX_VALUE) { throw new DigestException("Input too long: " + chunkCountLong + " chunks"); } int chunkCount = (int) chunkCountLong; ContentDigestAlgorithm[] digestAlgorithmsArray = digestAlgorithms.toArray(new ContentDigestAlgorithm[digestAlgorithms.size()]); MessageDigest[] mds = new MessageDigest[digestAlgorithmsArray.length]; byte[][] digestsOfChunks = new byte[digestAlgorithmsArray.length][]; int[] digestOutputSizes = new int[digestAlgorithmsArray.length]; for (int i = 0; i < digestAlgorithmsArray.length; i++) { ContentDigestAlgorithm digestAlgorithm = digestAlgorithmsArray[i]; int digestOutputSizeBytes = digestAlgorithm.getChunkDigestOutputSizeBytes(); digestOutputSizes[i] = digestOutputSizeBytes; byte[] concatenationOfChunkCountAndChunkDigests = new byte[5 + chunkCount * digestOutputSizeBytes]; concatenationOfChunkCountAndChunkDigests[0] = 0x5a; setUnsignedInt32LittleEndian( chunkCount, concatenationOfChunkCountAndChunkDigests, 1); digestsOfChunks[i] = concatenationOfChunkCountAndChunkDigests; String jcaAlgorithm = digestAlgorithm.getJcaMessageDigestAlgorithm(); mds[i] = MessageDigest.getInstance(jcaAlgorithm); } MessageDigestSink mdSink = new MessageDigestSink(mds); byte[] chunkContentPrefix = new byte[5]; chunkContentPrefix[0] = (byte) 0xa5; int chunkIndex = 0; // Optimization opportunity: digests of chunks can be computed in parallel. However, // determining the number of computations to be performed in parallel is non-trivial. This // depends on a wide range of factors, such as data source type (e.g., in-memory or fetched // from file), CPU/memory/disk cache bandwidth and latency, interconnect architecture of CPU // cores, load on the system from other threads of execution and other processes, size of // input. // For now, we compute these digests sequentially and thus have the luxury of improving // performance by writing the digest of each chunk into a pre-allocated buffer at exactly // the right position. This avoids unnecessary allocations, copying, and enables the final // digest to be more efficient because it's presented with all of its input in one go. for (DataSource input : contents) { long inputOffset = 0; long inputRemaining = input.size(); while (inputRemaining > 0) { int chunkSize = (int) Math.min(inputRemaining, CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES); setUnsignedInt32LittleEndian(chunkSize, chunkContentPrefix, 1); for (int i = 0; i < mds.length; i++) { mds[i].update(chunkContentPrefix); } try { input.feed(inputOffset, chunkSize, mdSink); } catch (IOException e) { throw new IOException("Failed to read chunk #" + chunkIndex, e); } for (int i = 0; i < digestAlgorithmsArray.length; i++) { MessageDigest md = mds[i]; byte[] concatenationOfChunkCountAndChunkDigests = digestsOfChunks[i]; int expectedDigestSizeBytes = digestOutputSizes[i]; int actualDigestSizeBytes = md.digest( concatenationOfChunkCountAndChunkDigests, 5 + chunkIndex * expectedDigestSizeBytes, expectedDigestSizeBytes); if (actualDigestSizeBytes != expectedDigestSizeBytes) { throw new RuntimeException( "Unexpected output size of " + md.getAlgorithm() + " digest: " + actualDigestSizeBytes); } } inputOffset += chunkSize; inputRemaining -= chunkSize; chunkIndex++; } } Map result = new HashMap<>(digestAlgorithmsArray.length); for (int i = 0; i < digestAlgorithmsArray.length; i++) { ContentDigestAlgorithm digestAlgorithm = digestAlgorithmsArray[i]; byte[] concatenationOfChunkCountAndChunkDigests = digestsOfChunks[i]; MessageDigest md = mds[i]; byte[] digest = md.digest(concatenationOfChunkCountAndChunkDigests); result.put(digestAlgorithm, digest); } return result; } private static final long getChunkCount(long inputSize, int chunkSize) { return (inputSize + chunkSize - 1) / chunkSize; } private static void setUnsignedInt32LittleEndian(int value, byte[] result, int offset) { result[offset] = (byte) (value & 0xff); result[offset + 1] = (byte) ((value >> 8) & 0xff); result[offset + 2] = (byte) ((value >> 16) & 0xff); result[offset + 3] = (byte) ((value >> 24) & 0xff); } private static byte[] generateApkSigningBlock( List signerConfigs, Map contentDigests) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { byte[] apkSignatureSchemeV2Block = generateApkSignatureSchemeV2Block(signerConfigs, contentDigests); return generateApkSigningBlock(apkSignatureSchemeV2Block); } private static byte[] generateApkSigningBlock(byte[] apkSignatureSchemeV2Block) { // FORMAT: // uint64: size (excluding this field) // repeated ID-value pairs: // uint64: size (excluding this field) // uint32: ID // (size - 4) bytes: value // uint64: size (same as the one above) // uint128: magic int resultSize = 8 // size + 8 + 4 + apkSignatureSchemeV2Block.length // v2Block as ID-value pair + 8 // size + 16 // magic ; ByteBuffer result = ByteBuffer.allocate(resultSize); result.order(ByteOrder.LITTLE_ENDIAN); long blockSizeFieldValue = resultSize - 8; result.putLong(blockSizeFieldValue); long pairSizeFieldValue = 4 + apkSignatureSchemeV2Block.length; result.putLong(pairSizeFieldValue); result.putInt(APK_SIGNATURE_SCHEME_V2_BLOCK_ID); result.put(apkSignatureSchemeV2Block); result.putLong(blockSizeFieldValue); result.put(APK_SIGNING_BLOCK_MAGIC); return result.array(); } private static byte[] generateApkSignatureSchemeV2Block( List signerConfigs, Map contentDigests) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { // FORMAT: // * length-prefixed sequence of length-prefixed signer blocks. List signerBlocks = new ArrayList<>(signerConfigs.size()); int signerNumber = 0; for (SignerConfig signerConfig : signerConfigs) { signerNumber++; byte[] signerBlock; try { signerBlock = generateSignerBlock(signerConfig, contentDigests); } catch (InvalidKeyException e) { throw new InvalidKeyException("Signer #" + signerNumber + " failed", e); } catch (SignatureException e) { throw new SignatureException("Signer #" + signerNumber + " failed", e); } signerBlocks.add(signerBlock); } return encodeAsSequenceOfLengthPrefixedElements( new byte[][] { encodeAsSequenceOfLengthPrefixedElements(signerBlocks), }); } private static byte[] generateSignerBlock( SignerConfig signerConfig, Map contentDigests) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { if (signerConfig.certificates.isEmpty()) { throw new SignatureException("No certificates configured for signer"); } PublicKey publicKey = signerConfig.certificates.get(0).getPublicKey(); byte[] encodedPublicKey = encodePublicKey(publicKey); V2SignatureSchemeBlock.SignedData signedData = new V2SignatureSchemeBlock.SignedData(); try { signedData.certificates = encodeCertificates(signerConfig.certificates); } catch (CertificateEncodingException e) { throw new SignatureException("Failed to encode certificates", e); } List> digests = new ArrayList<>(signerConfig.signatureAlgorithms.size()); for (SignatureAlgorithm signatureAlgorithm : signerConfig.signatureAlgorithms) { ContentDigestAlgorithm contentDigestAlgorithm = signatureAlgorithm.getContentDigestAlgorithm(); byte[] contentDigest = contentDigests.get(contentDigestAlgorithm); if (contentDigest == null) { throw new RuntimeException( contentDigestAlgorithm + " content digest for " + signatureAlgorithm + " not computed"); } digests.add(Pair.of(signatureAlgorithm.getId(), contentDigest)); } signedData.digests = digests; V2SignatureSchemeBlock.Signer signer = new V2SignatureSchemeBlock.Signer(); // FORMAT: // * length-prefixed sequence of length-prefixed digests: // * uint32: signature algorithm ID // * length-prefixed bytes: digest of contents // * length-prefixed sequence of certificates: // * length-prefixed bytes: X.509 certificate (ASN.1 DER encoded). // * length-prefixed sequence of length-prefixed additional attributes: // * uint32: ID // * (length - 4) bytes: value signer.signedData = encodeAsSequenceOfLengthPrefixedElements(new byte[][] { encodeAsSequenceOfLengthPrefixedPairsOfIntAndLengthPrefixedBytes(signedData.digests), encodeAsSequenceOfLengthPrefixedElements(signedData.certificates), // additional attributes new byte[0], }); signer.publicKey = encodedPublicKey; signer.signatures = new ArrayList<>(signerConfig.signatureAlgorithms.size()); for (SignatureAlgorithm signatureAlgorithm : signerConfig.signatureAlgorithms) { Pair sigAlgAndParams = signatureAlgorithm.getJcaSignatureAlgorithmAndParams(); String jcaSignatureAlgorithm = sigAlgAndParams.getFirst(); AlgorithmParameterSpec jcaSignatureAlgorithmParams = sigAlgAndParams.getSecond(); byte[] signatureBytes; try { Signature signature = Signature.getInstance(jcaSignatureAlgorithm); signature.initSign(signerConfig.privateKey); if (jcaSignatureAlgorithmParams != null) { signature.setParameter(jcaSignatureAlgorithmParams); } signature.update(signer.signedData); signatureBytes = signature.sign(); } catch (InvalidKeyException e) { throw new InvalidKeyException("Failed to sign using " + jcaSignatureAlgorithm, e); } catch (InvalidAlgorithmParameterException | SignatureException e) { throw new SignatureException("Failed to sign using " + jcaSignatureAlgorithm, e); } try { Signature signature = Signature.getInstance(jcaSignatureAlgorithm); signature.initVerify(publicKey); if (jcaSignatureAlgorithmParams != null) { signature.setParameter(jcaSignatureAlgorithmParams); } signature.update(signer.signedData); if (!signature.verify(signatureBytes)) { throw new SignatureException("Signature did not verify"); } } catch (InvalidKeyException e) { throw new InvalidKeyException( "Failed to verify generated " + jcaSignatureAlgorithm + " signature using" + " public key from certificate", e); } catch (InvalidAlgorithmParameterException | SignatureException e) { throw new SignatureException( "Failed to verify generated " + jcaSignatureAlgorithm + " signature using" + " public key from certificate", e); } signer.signatures.add(Pair.of(signatureAlgorithm.getId(), signatureBytes)); } // FORMAT: // * length-prefixed signed data // * length-prefixed sequence of length-prefixed signatures: // * uint32: signature algorithm ID // * length-prefixed bytes: signature of signed data // * length-prefixed bytes: public key (X.509 SubjectPublicKeyInfo, ASN.1 DER encoded) return encodeAsSequenceOfLengthPrefixedElements( new byte[][] { signer.signedData, encodeAsSequenceOfLengthPrefixedPairsOfIntAndLengthPrefixedBytes( signer.signatures), signer.publicKey, }); } private static final class V2SignatureSchemeBlock { private static final class Signer { public byte[] signedData; public List> signatures; public byte[] publicKey; } private static final class SignedData { public List> digests; public List certificates; } } private static byte[] encodePublicKey(PublicKey publicKey) throws InvalidKeyException, NoSuchAlgorithmException { byte[] encodedPublicKey = null; if ("X.509".equals(publicKey.getFormat())) { encodedPublicKey = publicKey.getEncoded(); } if (encodedPublicKey == null) { try { encodedPublicKey = KeyFactory.getInstance(publicKey.getAlgorithm()) .getKeySpec(publicKey, X509EncodedKeySpec.class) .getEncoded(); } catch (InvalidKeySpecException e) { throw new InvalidKeyException( "Failed to obtain X.509 encoded form of public key " + publicKey + " of class " + publicKey.getClass().getName(), e); } } if ((encodedPublicKey == null) || (encodedPublicKey.length == 0)) { throw new InvalidKeyException( "Failed to obtain X.509 encoded form of public key " + publicKey + " of class " + publicKey.getClass().getName()); } return encodedPublicKey; } private static List encodeCertificates(List certificates) throws CertificateEncodingException { List result = new ArrayList<>(certificates.size()); for (X509Certificate certificate : certificates) { result.add(certificate.getEncoded()); } return result; } private static byte[] encodeAsSequenceOfLengthPrefixedElements(List sequence) { return encodeAsSequenceOfLengthPrefixedElements( sequence.toArray(new byte[sequence.size()][])); } private static byte[] encodeAsSequenceOfLengthPrefixedElements(byte[][] sequence) { int payloadSize = 0; for (byte[] element : sequence) { payloadSize += 4 + element.length; } ByteBuffer result = ByteBuffer.allocate(payloadSize); result.order(ByteOrder.LITTLE_ENDIAN); for (byte[] element : sequence) { result.putInt(element.length); result.put(element); } return result.array(); } private static byte[] encodeAsSequenceOfLengthPrefixedPairsOfIntAndLengthPrefixedBytes( List> sequence) { int resultSize = 0; for (Pair element : sequence) { resultSize += 12 + element.getSecond().length; } ByteBuffer result = ByteBuffer.allocate(resultSize); result.order(ByteOrder.LITTLE_ENDIAN); for (Pair element : sequence) { byte[] second = element.getSecond(); result.putInt(8 + second.length); result.putInt(element.getFirst()); result.putInt(second.length); result.put(second); } return result.array(); } } src/main/java/com/android/apksig/internal/apk/v2/V2SchemeVerifier.java0100644 0000000 0000000 00000117035 13243353143 024616 0ustar000000000 0000000 /* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.internal.apk.v2; import com.android.apksig.ApkVerifier.Issue; import com.android.apksig.ApkVerifier.IssueWithParams; import com.android.apksig.apk.ApkFormatException; import com.android.apksig.apk.ApkUtils; import com.android.apksig.internal.util.ByteBufferDataSource; import com.android.apksig.internal.util.GuaranteedEncodedFormX509Certificate; import com.android.apksig.internal.util.Pair; import com.android.apksig.internal.zip.ZipUtils; import com.android.apksig.util.DataSource; import java.io.ByteArrayInputStream; import java.io.IOException; import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.security.DigestException; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; import java.security.PublicKey; import java.security.Signature; import java.security.SignatureException; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.security.spec.AlgorithmParameterSpec; import java.security.spec.X509EncodedKeySpec; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; /** * APK Signature Scheme v2 verifier. * *

APK Signature Scheme v2 is a whole-file signature scheme which aims to protect every single * bit of the APK, as opposed to the JAR Signature Scheme which protects only the names and * uncompressed contents of ZIP entries. * * @see APK Signature Scheme v2 */ public abstract class V2SchemeVerifier { private static final long APK_SIG_BLOCK_MAGIC_HI = 0x3234206b636f6c42L; private static final long APK_SIG_BLOCK_MAGIC_LO = 0x20676953204b5041L; private static final int APK_SIG_BLOCK_MIN_SIZE = 32; private static final int APK_SIGNATURE_SCHEME_V2_BLOCK_ID = 0x7109871a; /** Hidden constructor to prevent instantiation. */ private V2SchemeVerifier() {} /** * Verifies the provided APK's APK Signature Scheme v2 signatures and returns the result of * verification. APK is considered verified only if {@link Result#verified} is {@code true}. If * verification fails, the result will contain errors -- see {@link Result#getErrors()}. * * @throws ApkFormatException if the APK is malformed * @throws NoSuchAlgorithmException if the APK's signatures cannot be verified because a * required cryptographic algorithm implementation is missing * @throws SignatureNotFoundException if no APK Signature Scheme v2 signatures are found * @throws IOException if an I/O error occurs when reading the APK */ public static Result verify(DataSource apk, ApkUtils.ZipSections zipSections) throws IOException, ApkFormatException, NoSuchAlgorithmException, SignatureNotFoundException { Result result = new Result(); SignatureInfo signatureInfo = findSignature(apk, zipSections, result); DataSource beforeApkSigningBlock = apk.slice(0, signatureInfo.apkSigningBlockOffset); DataSource centralDir = apk.slice( signatureInfo.centralDirOffset, signatureInfo.eocdOffset - signatureInfo.centralDirOffset); ByteBuffer eocd = signatureInfo.eocd; verify(beforeApkSigningBlock, signatureInfo.signatureBlock, centralDir, eocd, result); return result; } /** * Verifies the provided APK's v2 signatures and outputs the results into the provided * {@code result}. APK is considered verified only if there are no errors reported in the * {@code result}. */ private static void verify( DataSource beforeApkSigningBlock, ByteBuffer apkSignatureSchemeV2Block, DataSource centralDir, ByteBuffer eocd, Result result) throws IOException, NoSuchAlgorithmException { Set contentDigestsToVerify = new HashSet<>(1); parseSigners(apkSignatureSchemeV2Block, contentDigestsToVerify, result); if (result.containsErrors()) { return; } verifyIntegrity( beforeApkSigningBlock, centralDir, eocd, contentDigestsToVerify, result); if (!result.containsErrors()) { result.verified = true; } } /** * Parses each signer in the provided APK Signature Scheme v2 block and populates * {@code signerInfos} of the provided {@code result}. * *

This verifies signatures over {@code signed-data} block contained in each signer block. * However, this does not verify the integrity of the rest of the APK but rather simply reports * the expected digests of the rest of the APK (see {@code contentDigestsToVerify}). */ private static void parseSigners( ByteBuffer apkSignatureSchemeV2Block, Set contentDigestsToVerify, Result result) throws NoSuchAlgorithmException { ByteBuffer signers; try { signers = getLengthPrefixedSlice(apkSignatureSchemeV2Block); } catch (ApkFormatException e) { result.addError(Issue.V2_SIG_MALFORMED_SIGNERS); return; } if (!signers.hasRemaining()) { result.addError(Issue.V2_SIG_NO_SIGNERS); return; } CertificateFactory certFactory; try { certFactory = CertificateFactory.getInstance("X.509"); } catch (CertificateException e) { throw new RuntimeException("Failed to obtain X.509 CertificateFactory", e); } int signerCount = 0; while (signers.hasRemaining()) { int signerIndex = signerCount; signerCount++; Result.SignerInfo signerInfo = new Result.SignerInfo(); signerInfo.index = signerIndex; result.signers.add(signerInfo); try { ByteBuffer signer = getLengthPrefixedSlice(signers); parseSigner(signer, certFactory, signerInfo, contentDigestsToVerify); } catch (ApkFormatException | BufferUnderflowException e) { signerInfo.addError(Issue.V2_SIG_MALFORMED_SIGNER); return; } } } /** * Parses the provided signer block and populates the {@code result}. * *

This verifies signatures over {@code signed-data} contained in this block but does not * verify the integrity of the rest of the APK. Rather, this method adds to the * {@code contentDigestsToVerify}. */ private static void parseSigner( ByteBuffer signerBlock, CertificateFactory certFactory, Result.SignerInfo result, Set contentDigestsToVerify) throws ApkFormatException, NoSuchAlgorithmException { ByteBuffer signedData = getLengthPrefixedSlice(signerBlock); byte[] signedDataBytes = new byte[signedData.remaining()]; signedData.get(signedDataBytes); signedData.flip(); result.signedData = signedDataBytes; ByteBuffer signatures = getLengthPrefixedSlice(signerBlock); byte[] publicKeyBytes = readLengthPrefixedByteArray(signerBlock); // Parse the signatures block and identify supported signatures int signatureCount = 0; List supportedSignatures = new ArrayList<>(1); while (signatures.hasRemaining()) { signatureCount++; try { ByteBuffer signature = getLengthPrefixedSlice(signatures); int sigAlgorithmId = signature.getInt(); byte[] sigBytes = readLengthPrefixedByteArray(signature); result.signatures.add( new Result.SignerInfo.Signature(sigAlgorithmId, sigBytes)); SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.findById(sigAlgorithmId); if (signatureAlgorithm == null) { result.addWarning(Issue.V2_SIG_UNKNOWN_SIG_ALGORITHM, sigAlgorithmId); continue; } supportedSignatures.add(new SupportedSignature(signatureAlgorithm, sigBytes)); } catch (ApkFormatException | BufferUnderflowException e) { result.addError(Issue.V2_SIG_MALFORMED_SIGNATURE, signatureCount); return; } } if (result.signatures.isEmpty()) { result.addError(Issue.V2_SIG_NO_SIGNATURES); return; } // Verify signatures over signed-data block using the public key List signaturesToVerify = getSignaturesToVerify(supportedSignatures); if (signaturesToVerify.isEmpty()) { result.addError(Issue.V2_SIG_NO_SUPPORTED_SIGNATURES); return; } for (SupportedSignature signature : signaturesToVerify) { SignatureAlgorithm signatureAlgorithm = signature.algorithm; String jcaSignatureAlgorithm = signatureAlgorithm.getJcaSignatureAlgorithmAndParams().getFirst(); AlgorithmParameterSpec jcaSignatureAlgorithmParams = signatureAlgorithm.getJcaSignatureAlgorithmAndParams().getSecond(); String keyAlgorithm = signatureAlgorithm.getJcaKeyAlgorithm(); PublicKey publicKey; try { publicKey = KeyFactory.getInstance(keyAlgorithm).generatePublic( new X509EncodedKeySpec(publicKeyBytes)); } catch (Exception e) { result.addError(Issue.V2_SIG_MALFORMED_PUBLIC_KEY, e); return; } try { Signature sig = Signature.getInstance(jcaSignatureAlgorithm); sig.initVerify(publicKey); if (jcaSignatureAlgorithmParams != null) { sig.setParameter(jcaSignatureAlgorithmParams); } signedData.position(0); sig.update(signedData); byte[] sigBytes = signature.signature; if (!sig.verify(sigBytes)) { result.addError(Issue.V2_SIG_DID_NOT_VERIFY, signatureAlgorithm); return; } result.verifiedSignatures.put(signatureAlgorithm, sigBytes); contentDigestsToVerify.add(signatureAlgorithm.getContentDigestAlgorithm()); } catch (InvalidKeyException | InvalidAlgorithmParameterException | SignatureException e) { result.addError(Issue.V2_SIG_VERIFY_EXCEPTION, signatureAlgorithm, e); return; } } // At least one signature over signedData has verified. We can now parse signed-data. signedData.position(0); ByteBuffer digests = getLengthPrefixedSlice(signedData); ByteBuffer certificates = getLengthPrefixedSlice(signedData); ByteBuffer additionalAttributes = getLengthPrefixedSlice(signedData); // Parse the certificates block int certificateIndex = -1; while (certificates.hasRemaining()) { certificateIndex++; byte[] encodedCert = readLengthPrefixedByteArray(certificates); X509Certificate certificate; try { certificate = (X509Certificate) certFactory.generateCertificate( new ByteArrayInputStream(encodedCert)); } catch (CertificateException e) { result.addError( Issue.V2_SIG_MALFORMED_CERTIFICATE, certificateIndex, certificateIndex + 1, e); return; } // Wrap the cert so that the result's getEncoded returns exactly the original encoded // form. Without this, getEncoded may return a different form from what was stored in // the signature. This is because some X509Certificate(Factory) implementations // re-encode certificates. certificate = new GuaranteedEncodedFormX509Certificate(certificate, encodedCert); result.certs.add(certificate); } if (result.certs.isEmpty()) { result.addError(Issue.V2_SIG_NO_CERTIFICATES); return; } X509Certificate mainCertificate = result.certs.get(0); byte[] certificatePublicKeyBytes = mainCertificate.getPublicKey().getEncoded(); if (!Arrays.equals(publicKeyBytes, certificatePublicKeyBytes)) { result.addError( Issue.V2_SIG_PUBLIC_KEY_MISMATCH_BETWEEN_CERTIFICATE_AND_SIGNATURES_RECORD, toHex(certificatePublicKeyBytes), toHex(publicKeyBytes)); return; } // Parse the digests block int digestCount = 0; while (digests.hasRemaining()) { digestCount++; try { ByteBuffer digest = getLengthPrefixedSlice(digests); int sigAlgorithmId = digest.getInt(); byte[] digestBytes = readLengthPrefixedByteArray(digest); result.contentDigests.add( new Result.SignerInfo.ContentDigest(sigAlgorithmId, digestBytes)); } catch (ApkFormatException | BufferUnderflowException e) { result.addError(Issue.V2_SIG_MALFORMED_DIGEST, digestCount); return; } } List sigAlgsFromSignaturesRecord = new ArrayList<>(result.signatures.size()); for (Result.SignerInfo.Signature signature : result.signatures) { sigAlgsFromSignaturesRecord.add(signature.getAlgorithmId()); } List sigAlgsFromDigestsRecord = new ArrayList<>(result.contentDigests.size()); for (Result.SignerInfo.ContentDigest digest : result.contentDigests) { sigAlgsFromDigestsRecord.add(digest.getSignatureAlgorithmId()); } if (!sigAlgsFromSignaturesRecord.equals(sigAlgsFromDigestsRecord)) { result.addError( Issue.V2_SIG_SIG_ALG_MISMATCH_BETWEEN_SIGNATURES_AND_DIGESTS_RECORDS, sigAlgsFromSignaturesRecord, sigAlgsFromDigestsRecord); return; } // Parse the additional attributes block. int additionalAttributeCount = 0; while (additionalAttributes.hasRemaining()) { additionalAttributeCount++; try { ByteBuffer attribute = getLengthPrefixedSlice(additionalAttributes); int id = attribute.getInt(); byte[] value = readLengthPrefixedByteArray(attribute); result.additionalAttributes.add( new Result.SignerInfo.AdditionalAttribute(id, value)); result.addWarning(Issue.V2_SIG_UNKNOWN_ADDITIONAL_ATTRIBUTE, id); } catch (ApkFormatException | BufferUnderflowException e) { result.addError( Issue.V2_SIG_MALFORMED_ADDITIONAL_ATTRIBUTE, additionalAttributeCount); return; } } } private static List getSignaturesToVerify( List signatures) { // Pick the signature with the strongest algorithm, to mimic Android's behavior. SignatureAlgorithm bestSigAlgorithm = null; byte[] bestSigAlgorithmSignatureBytes = null; for (SupportedSignature sig : signatures) { SignatureAlgorithm sigAlgorithm = sig.algorithm; if ((bestSigAlgorithm == null) || (compareSignatureAlgorithm(sigAlgorithm, bestSigAlgorithm) > 0)) { bestSigAlgorithm = sigAlgorithm; bestSigAlgorithmSignatureBytes = sig.signature; } } if (bestSigAlgorithm == null) { return Collections.emptyList(); } else { return Collections.singletonList( new SupportedSignature(bestSigAlgorithm, bestSigAlgorithmSignatureBytes)); } } private static class SupportedSignature { private final SignatureAlgorithm algorithm; private final byte[] signature; private SupportedSignature(SignatureAlgorithm algorithm, byte[] signature) { this.algorithm = algorithm; this.signature = signature; } } /** * Returns positive number if {@code alg1} is preferred over {@code alg2}, {@code -1} if * {@code alg2} is preferred over {@code alg1}, and {@code 0} if there is no preference. */ private static int compareSignatureAlgorithm(SignatureAlgorithm alg1, SignatureAlgorithm alg2) { ContentDigestAlgorithm digestAlg1 = alg1.getContentDigestAlgorithm(); ContentDigestAlgorithm digestAlg2 = alg2.getContentDigestAlgorithm(); return compareContentDigestAlgorithm(digestAlg1, digestAlg2); } /** * Returns positive number if {@code alg1} is preferred over {@code alg2}, {@code -1} if * {@code alg2} is preferred over {@code alg1}, and {@code 0} if there is no preference. */ private static int compareContentDigestAlgorithm( ContentDigestAlgorithm alg1, ContentDigestAlgorithm alg2) { switch (alg1) { case CHUNKED_SHA256: switch (alg2) { case CHUNKED_SHA256: return 0; case CHUNKED_SHA512: return -1; default: throw new IllegalArgumentException("Unknown alg2: " + alg2); } case CHUNKED_SHA512: switch (alg2) { case CHUNKED_SHA256: return 1; case CHUNKED_SHA512: return 0; default: throw new IllegalArgumentException("Unknown alg2: " + alg2); } default: throw new IllegalArgumentException("Unknown alg1: " + alg1); } } /** * Verifies integrity of the APK outside of the APK Signing Block by computing digests of the * APK and comparing them against the digests listed in APK Signing Block. The expected digests * taken from {@code v2SchemeSignerInfos} of the provided {@code result}. */ private static void verifyIntegrity( DataSource beforeApkSigningBlock, DataSource centralDir, ByteBuffer eocd, Set contentDigestAlgorithms, Result result) throws IOException, NoSuchAlgorithmException { if (contentDigestAlgorithms.isEmpty()) { // This should never occur because this method is invoked once at least one signature // is verified, meaning at least one content digest is known. throw new RuntimeException("No content digests found"); } // For the purposes of verifying integrity, ZIP End of Central Directory (EoCD) must be // treated as though its Central Directory offset points to the start of APK Signing Block. // We thus modify the EoCD accordingly. ByteBuffer modifiedEocd = ByteBuffer.allocate(eocd.remaining()); modifiedEocd.order(ByteOrder.LITTLE_ENDIAN); modifiedEocd.put(eocd); modifiedEocd.flip(); ZipUtils.setZipEocdCentralDirectoryOffset(modifiedEocd, beforeApkSigningBlock.size()); Map actualContentDigests; try { actualContentDigests = V2SchemeSigner.computeContentDigests( contentDigestAlgorithms, new DataSource[] { beforeApkSigningBlock, centralDir, new ByteBufferDataSource(modifiedEocd) }); } catch (DigestException e) { throw new RuntimeException("Failed to compute content digests", e); } if (!contentDigestAlgorithms.equals(actualContentDigests.keySet())) { throw new RuntimeException( "Mismatch between sets of requested and computed content digests" + " . Requested: " + contentDigestAlgorithms + ", computed: " + actualContentDigests.keySet()); } // Compare digests computed over the rest of APK against the corresponding expected digests // in signer blocks. for (Result.SignerInfo signerInfo : result.signers) { for (Result.SignerInfo.ContentDigest expected : signerInfo.contentDigests) { SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.findById(expected.getSignatureAlgorithmId()); if (signatureAlgorithm == null) { continue; } ContentDigestAlgorithm contentDigestAlgorithm = signatureAlgorithm.getContentDigestAlgorithm(); byte[] expectedDigest = expected.getValue(); byte[] actualDigest = actualContentDigests.get(contentDigestAlgorithm); if (!Arrays.equals(expectedDigest, actualDigest)) { signerInfo.addError( Issue.V2_SIG_APK_DIGEST_DID_NOT_VERIFY, contentDigestAlgorithm, toHex(expectedDigest), toHex(actualDigest)); continue; } signerInfo.verifiedContentDigests.put(contentDigestAlgorithm, actualDigest); } } } /** * APK Signature Scheme v2 block and additional information relevant to verifying the signatures * contained in the block against the file. */ private static class SignatureInfo { /** Contents of APK Signature Scheme v2 block. */ private final ByteBuffer signatureBlock; /** Position of the APK Signing Block in the file. */ private final long apkSigningBlockOffset; /** Position of the ZIP Central Directory in the file. */ private final long centralDirOffset; /** Position of the ZIP End of Central Directory (EoCD) in the file. */ private final long eocdOffset; /** Contents of ZIP End of Central Directory (EoCD) of the file. */ private final ByteBuffer eocd; private SignatureInfo( ByteBuffer signatureBlock, long apkSigningBlockOffset, long centralDirOffset, long eocdOffset, ByteBuffer eocd) { this.signatureBlock = signatureBlock; this.apkSigningBlockOffset = apkSigningBlockOffset; this.centralDirOffset = centralDirOffset; this.eocdOffset = eocdOffset; this.eocd = eocd; } } /** * Returns the APK Signature Scheme v2 block contained in the provided APK file and the * additional information relevant for verifying the block against the file. * * @throws SignatureNotFoundException if the APK is not signed using APK Signature Scheme v2 * @throws IOException if an I/O error occurs while reading the APK */ private static SignatureInfo findSignature( DataSource apk, ApkUtils.ZipSections zipSections, Result result) throws IOException, SignatureNotFoundException { // Find the APK Signing Block. The block immediately precedes the Central Directory. ByteBuffer eocd = zipSections.getZipEndOfCentralDirectory(); Pair apkSigningBlockAndOffset = findApkSigningBlock(apk, zipSections); DataSource apkSigningBlock = apkSigningBlockAndOffset.getFirst(); long apkSigningBlockOffset = apkSigningBlockAndOffset.getSecond(); ByteBuffer apkSigningBlockBuf = apkSigningBlock.getByteBuffer(0, (int) apkSigningBlock.size()); apkSigningBlockBuf.order(ByteOrder.LITTLE_ENDIAN); // Find the APK Signature Scheme v2 Block inside the APK Signing Block. ByteBuffer apkSignatureSchemeV2Block = findApkSignatureSchemeV2Block(apkSigningBlockBuf, result); return new SignatureInfo( apkSignatureSchemeV2Block, apkSigningBlockOffset, zipSections.getZipCentralDirectoryOffset(), zipSections.getZipEndOfCentralDirectoryOffset(), eocd); } /** * Returns the APK Signing Block and its offset in the provided APK. * * @throws SignatureNotFoundException if the APK does not contain an APK Signing Block */ public static Pair findApkSigningBlock( DataSource apk, ApkUtils.ZipSections zipSections) throws IOException, SignatureNotFoundException { // FORMAT: // OFFSET DATA TYPE DESCRIPTION // * @+0 bytes uint64: size in bytes (excluding this field) // * @+8 bytes payload // * @-24 bytes uint64: size in bytes (same as the one above) // * @-16 bytes uint128: magic long centralDirStartOffset = zipSections.getZipCentralDirectoryOffset(); long centralDirEndOffset = centralDirStartOffset + zipSections.getZipCentralDirectorySizeBytes(); long eocdStartOffset = zipSections.getZipEndOfCentralDirectoryOffset(); if (centralDirEndOffset != eocdStartOffset) { throw new SignatureNotFoundException( "ZIP Central Directory is not immediately followed by End of Central Directory" + ". CD end: " + centralDirEndOffset + ", EoCD start: " + eocdStartOffset); } if (centralDirStartOffset < APK_SIG_BLOCK_MIN_SIZE) { throw new SignatureNotFoundException( "APK too small for APK Signing Block. ZIP Central Directory offset: " + centralDirStartOffset); } // Read the magic and offset in file from the footer section of the block: // * uint64: size of block // * 16 bytes: magic ByteBuffer footer = apk.getByteBuffer(centralDirStartOffset - 24, 24); footer.order(ByteOrder.LITTLE_ENDIAN); if ((footer.getLong(8) != APK_SIG_BLOCK_MAGIC_LO) || (footer.getLong(16) != APK_SIG_BLOCK_MAGIC_HI)) { throw new SignatureNotFoundException( "No APK Signing Block before ZIP Central Directory"); } // Read and compare size fields long apkSigBlockSizeInFooter = footer.getLong(0); if ((apkSigBlockSizeInFooter < footer.capacity()) || (apkSigBlockSizeInFooter > Integer.MAX_VALUE - 8)) { throw new SignatureNotFoundException( "APK Signing Block size out of range: " + apkSigBlockSizeInFooter); } int totalSize = (int) (apkSigBlockSizeInFooter + 8); long apkSigBlockOffset = centralDirStartOffset - totalSize; if (apkSigBlockOffset < 0) { throw new SignatureNotFoundException( "APK Signing Block offset out of range: " + apkSigBlockOffset); } ByteBuffer apkSigBlock = apk.getByteBuffer(apkSigBlockOffset, 8); apkSigBlock.order(ByteOrder.LITTLE_ENDIAN); long apkSigBlockSizeInHeader = apkSigBlock.getLong(0); if (apkSigBlockSizeInHeader != apkSigBlockSizeInFooter) { throw new SignatureNotFoundException( "APK Signing Block sizes in header and footer do not match: " + apkSigBlockSizeInHeader + " vs " + apkSigBlockSizeInFooter); } return Pair.of(apk.slice(apkSigBlockOffset, totalSize), apkSigBlockOffset); } private static ByteBuffer findApkSignatureSchemeV2Block( ByteBuffer apkSigningBlock, Result result) throws SignatureNotFoundException { checkByteOrderLittleEndian(apkSigningBlock); // FORMAT: // OFFSET DATA TYPE DESCRIPTION // * @+0 bytes uint64: size in bytes (excluding this field) // * @+8 bytes pairs // * @-24 bytes uint64: size in bytes (same as the one above) // * @-16 bytes uint128: magic ByteBuffer pairs = sliceFromTo(apkSigningBlock, 8, apkSigningBlock.capacity() - 24); int entryCount = 0; while (pairs.hasRemaining()) { entryCount++; if (pairs.remaining() < 8) { throw new SignatureNotFoundException( "Insufficient data to read size of APK Signing Block entry #" + entryCount); } long lenLong = pairs.getLong(); if ((lenLong < 4) || (lenLong > Integer.MAX_VALUE)) { throw new SignatureNotFoundException( "APK Signing Block entry #" + entryCount + " size out of range: " + lenLong); } int len = (int) lenLong; int nextEntryPos = pairs.position() + len; if (len > pairs.remaining()) { throw new SignatureNotFoundException( "APK Signing Block entry #" + entryCount + " size out of range: " + len + ", available: " + pairs.remaining()); } int id = pairs.getInt(); if (id == APK_SIGNATURE_SCHEME_V2_BLOCK_ID) { return getByteBuffer(pairs, len - 4); } result.addWarning(Issue.APK_SIG_BLOCK_UNKNOWN_ENTRY_ID, id); pairs.position(nextEntryPos); } throw new SignatureNotFoundException( "No APK Signature Scheme v2 block in APK Signing Block"); } private static void checkByteOrderLittleEndian(ByteBuffer buffer) { if (buffer.order() != ByteOrder.LITTLE_ENDIAN) { throw new IllegalArgumentException("ByteBuffer byte order must be little endian"); } } public static class SignatureNotFoundException extends Exception { private static final long serialVersionUID = 1L; public SignatureNotFoundException(String message) { super(message); } public SignatureNotFoundException(String message, Throwable cause) { super(message, cause); } } /** * Returns new byte buffer whose content is a shared subsequence of this buffer's content * between the specified start (inclusive) and end (exclusive) positions. As opposed to * {@link ByteBuffer#slice()}, the returned buffer's byte order is the same as the source * buffer's byte order. */ private static ByteBuffer sliceFromTo(ByteBuffer source, int start, int end) { if (start < 0) { throw new IllegalArgumentException("start: " + start); } if (end < start) { throw new IllegalArgumentException("end < start: " + end + " < " + start); } int capacity = source.capacity(); if (end > source.capacity()) { throw new IllegalArgumentException("end > capacity: " + end + " > " + capacity); } int originalLimit = source.limit(); int originalPosition = source.position(); try { source.position(0); source.limit(end); source.position(start); ByteBuffer result = source.slice(); result.order(source.order()); return result; } finally { source.position(0); source.limit(originalLimit); source.position(originalPosition); } } /** * Relative get method for reading {@code size} number of bytes from the current * position of this buffer. * *

This method reads the next {@code size} bytes at this buffer's current position, * returning them as a {@code ByteBuffer} with start set to 0, limit and capacity set to * {@code size}, byte order set to this buffer's byte order; and then increments the position by * {@code size}. */ private static ByteBuffer getByteBuffer(ByteBuffer source, int size) throws BufferUnderflowException { if (size < 0) { throw new IllegalArgumentException("size: " + size); } int originalLimit = source.limit(); int position = source.position(); int limit = position + size; if ((limit < position) || (limit > originalLimit)) { throw new BufferUnderflowException(); } source.limit(limit); try { ByteBuffer result = source.slice(); result.order(source.order()); source.position(limit); return result; } finally { source.limit(originalLimit); } } private static ByteBuffer getLengthPrefixedSlice(ByteBuffer source) throws ApkFormatException { if (source.remaining() < 4) { throw new ApkFormatException( "Remaining buffer too short to contain length of length-prefixed field" + ". Remaining: " + source.remaining()); } int len = source.getInt(); if (len < 0) { throw new IllegalArgumentException("Negative length"); } else if (len > source.remaining()) { throw new ApkFormatException( "Length-prefixed field longer than remaining buffer" + ". Field length: " + len + ", remaining: " + source.remaining()); } return getByteBuffer(source, len); } private static byte[] readLengthPrefixedByteArray(ByteBuffer buf) throws ApkFormatException { int len = buf.getInt(); if (len < 0) { throw new ApkFormatException("Negative length"); } else if (len > buf.remaining()) { throw new ApkFormatException( "Underflow while reading length-prefixed value. Length: " + len + ", available: " + buf.remaining()); } byte[] result = new byte[len]; buf.get(result); return result; } private static final char[] HEX_DIGITS = "01234567890abcdef".toCharArray(); private static String toHex(byte[] value) { StringBuilder sb = new StringBuilder(value.length * 2); int len = value.length; for (int i = 0; i < len; i++) { int hi = (value[i] & 0xff) >>> 4; int lo = value[i] & 0x0f; sb.append(HEX_DIGITS[hi]).append(HEX_DIGITS[lo]); } return sb.toString(); } public static class Result { /** Whether the APK's APK Signature Scheme v2 signature verifies. */ public boolean verified; public final List signers = new ArrayList<>(); private final List mWarnings = new ArrayList<>(); private final List mErrors = new ArrayList<>(); public boolean containsErrors() { if (!mErrors.isEmpty()) { return true; } if (!signers.isEmpty()) { for (SignerInfo signer : signers) { if (signer.containsErrors()) { return true; } } } return false; } public void addError(Issue msg, Object... parameters) { mErrors.add(new IssueWithParams(msg, parameters)); } public void addWarning(Issue msg, Object... parameters) { mWarnings.add(new IssueWithParams(msg, parameters)); } public List getErrors() { return mErrors; } public List getWarnings() { return mWarnings; } public static class SignerInfo { public int index; public List certs = new ArrayList<>(); public List contentDigests = new ArrayList<>(); public Map verifiedContentDigests = new HashMap<>(); public List signatures = new ArrayList<>(); public Map verifiedSignatures = new HashMap<>(); public List additionalAttributes = new ArrayList<>(); public byte[] signedData; private final List mWarnings = new ArrayList<>(); private final List mErrors = new ArrayList<>(); public void addError(Issue msg, Object... parameters) { mErrors.add(new IssueWithParams(msg, parameters)); } public void addWarning(Issue msg, Object... parameters) { mWarnings.add(new IssueWithParams(msg, parameters)); } public boolean containsErrors() { return !mErrors.isEmpty(); } public List getErrors() { return mErrors; } public List getWarnings() { return mWarnings; } public static class ContentDigest { private final int mSignatureAlgorithmId; private final byte[] mValue; public ContentDigest(int signatureAlgorithmId, byte[] value) { mSignatureAlgorithmId = signatureAlgorithmId; mValue = value; } public int getSignatureAlgorithmId() { return mSignatureAlgorithmId; } public byte[] getValue() { return mValue; } } public static class Signature { private final int mAlgorithmId; private final byte[] mValue; public Signature(int algorithmId, byte[] value) { mAlgorithmId = algorithmId; mValue = value; } public int getAlgorithmId() { return mAlgorithmId; } public byte[] getValue() { return mValue; } } public static class AdditionalAttribute { private final int mId; private final byte[] mValue; public AdditionalAttribute(int id, byte[] value) { mId = id; mValue = value.clone(); } public int getId() { return mId; } public byte[] getValue() { return mValue.clone(); } } } } } src/main/java/com/android/apksig/internal/asn1/0040755 0000000 0000000 00000000000 13243353143 020377 5ustar000000000 0000000 src/main/java/com/android/apksig/internal/asn1/Asn1BerParser.java0100644 0000000 0000000 00000065176 13243353143 023666 0ustar000000000 0000000 /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.internal.asn1; import com.android.apksig.internal.asn1.ber.BerDataValue; import com.android.apksig.internal.asn1.ber.BerDataValueFormatException; import com.android.apksig.internal.asn1.ber.BerDataValueReader; import com.android.apksig.internal.asn1.ber.BerEncoding; import com.android.apksig.internal.asn1.ber.ByteBufferBerDataValueReader; import com.android.apksig.internal.util.ByteBufferUtils; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.math.BigInteger; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** * Parser of ASN.1 BER-encoded structures. * *

Structure is described to the parser by providing a class annotated with {@link Asn1Class}, * containing fields annotated with {@link Asn1Field}. */ public final class Asn1BerParser { private Asn1BerParser() {} /** * Returns the ASN.1 structure contained in the BER encoded input. * * @param encoded encoded input. If the decoding operation succeeds, the position of this buffer * is advanced to the first position following the end of the consumed structure. * @param containerClass class describing the structure of the input. The class must meet the * following requirements: *

    *
  • The class must be annotated with {@link Asn1Class}.
  • *
  • The class must expose a public no-arg constructor.
  • *
  • Member fields of the class which are populated with parsed input must be * annotated with {@link Asn1Field} and be public and non-final.
  • *
* * @throws Asn1DecodingException if the input could not be decoded into the specified Java * object */ public static T parse(ByteBuffer encoded, Class containerClass) throws Asn1DecodingException { BerDataValue containerDataValue; try { containerDataValue = new ByteBufferBerDataValueReader(encoded).readDataValue(); } catch (BerDataValueFormatException e) { throw new Asn1DecodingException("Failed to decode top-level data value", e); } if (containerDataValue == null) { throw new Asn1DecodingException("Empty input"); } return parse(containerDataValue, containerClass); } /** * Returns the implicit {@code SET OF} contained in the provided ASN.1 BER input. Implicit means * that this method does not care whether the tag number of this data structure is * {@code SET OF} and whether the tag class is {@code UNIVERSAL}. * *

Note: The returned type is {@link List} rather than {@link Set} because ASN.1 SET may * contain duplicate elements. * * @param encoded encoded input. If the decoding operation succeeds, the position of this buffer * is advanced to the first position following the end of the consumed structure. * @param elementClass class describing the structure of the values/elements contained in this * container. The class must meet the following requirements: *

    *
  • The class must be annotated with {@link Asn1Class}.
  • *
  • The class must expose a public no-arg constructor.
  • *
  • Member fields of the class which are populated with parsed input must be * annotated with {@link Asn1Field} and be public and non-final.
  • *
* * @throws Asn1DecodingException if the input could not be decoded into the specified Java * object */ public static List parseImplicitSetOf(ByteBuffer encoded, Class elementClass) throws Asn1DecodingException { BerDataValue containerDataValue; try { containerDataValue = new ByteBufferBerDataValueReader(encoded).readDataValue(); } catch (BerDataValueFormatException e) { throw new Asn1DecodingException("Failed to decode top-level data value", e); } if (containerDataValue == null) { throw new Asn1DecodingException("Empty input"); } return parseSetOf(containerDataValue, elementClass); } private static T parse(BerDataValue container, Class containerClass) throws Asn1DecodingException { if (container == null) { throw new NullPointerException("container == null"); } if (containerClass == null) { throw new NullPointerException("containerClass == null"); } Asn1Type dataType = getContainerAsn1Type(containerClass); switch (dataType) { case CHOICE: return parseChoice(container, containerClass); case SEQUENCE: { int expectedTagClass = BerEncoding.TAG_CLASS_UNIVERSAL; int expectedTagNumber = BerEncoding.getTagNumber(dataType); if ((container.getTagClass() != expectedTagClass) || (container.getTagNumber() != expectedTagNumber)) { throw new Asn1UnexpectedTagException( "Unexpected data value read as " + containerClass.getName() + ". Expected " + BerEncoding.tagClassAndNumberToString( expectedTagClass, expectedTagNumber) + ", but read: " + BerEncoding.tagClassAndNumberToString( container.getTagClass(), container.getTagNumber())); } return parseSequence(container, containerClass); } default: throw new Asn1DecodingException("Parsing container " + dataType + " not supported"); } } private static T parseChoice(BerDataValue dataValue, Class containerClass) throws Asn1DecodingException { List fields = getAnnotatedFields(containerClass); if (fields.isEmpty()) { throw new Asn1DecodingException( "No fields annotated with " + Asn1Field.class.getName() + " in CHOICE class " + containerClass.getName()); } // Check that class + tagNumber don't clash between the choices for (int i = 0; i < fields.size() - 1; i++) { AnnotatedField f1 = fields.get(i); int tagNumber1 = f1.getBerTagNumber(); int tagClass1 = f1.getBerTagClass(); for (int j = i + 1; j < fields.size(); j++) { AnnotatedField f2 = fields.get(j); int tagNumber2 = f2.getBerTagNumber(); int tagClass2 = f2.getBerTagClass(); if ((tagNumber1 == tagNumber2) && (tagClass1 == tagClass2)) { throw new Asn1DecodingException( "CHOICE fields are indistinguishable because they have the same tag" + " class and number: " + containerClass.getName() + "." + f1.getField().getName() + " and ." + f2.getField().getName()); } } } // Instantiate the container object / result T obj; try { obj = containerClass.getConstructor().newInstance(); } catch (IllegalArgumentException | ReflectiveOperationException e) { throw new Asn1DecodingException("Failed to instantiate " + containerClass.getName(), e); } // Set the matching field's value from the data value for (AnnotatedField field : fields) { try { field.setValueFrom(dataValue, obj); return obj; } catch (Asn1UnexpectedTagException expected) { // not a match } } throw new Asn1DecodingException( "No options of CHOICE " + containerClass.getName() + " matched"); } private static T parseSequence(BerDataValue container, Class containerClass) throws Asn1DecodingException { List fields = getAnnotatedFields(containerClass); Collections.sort( fields, (f1, f2) -> f1.getAnnotation().index() - f2.getAnnotation().index()); // Check that there are no fields with the same index if (fields.size() > 1) { AnnotatedField lastField = null; for (AnnotatedField field : fields) { if ((lastField != null) && (lastField.getAnnotation().index() == field.getAnnotation().index())) { throw new Asn1DecodingException( "Fields have the same index: " + containerClass.getName() + "." + lastField.getField().getName() + " and ." + field.getField().getName()); } lastField = field; } } // Instantiate the container object / result T t; try { t = containerClass.getConstructor().newInstance(); } catch (IllegalArgumentException | ReflectiveOperationException e) { throw new Asn1DecodingException("Failed to instantiate " + containerClass.getName(), e); } // Parse fields one by one. A complication is that there may be optional fields. int nextUnreadFieldIndex = 0; BerDataValueReader elementsReader = container.contentsReader(); while (nextUnreadFieldIndex < fields.size()) { BerDataValue dataValue; try { dataValue = elementsReader.readDataValue(); } catch (BerDataValueFormatException e) { throw new Asn1DecodingException("Malformed data value", e); } if (dataValue == null) { break; } for (int i = nextUnreadFieldIndex; i < fields.size(); i++) { AnnotatedField field = fields.get(i); try { if (field.isOptional()) { // Optional field -- might not be present and we may thus be trying to set // it from the wrong tag. try { field.setValueFrom(dataValue, t); nextUnreadFieldIndex = i + 1; break; } catch (Asn1UnexpectedTagException e) { // This field is not present, attempt to use this data value for the // next / iteration of the loop continue; } } else { // Mandatory field -- if we can't set its value from this data value, then // it's an error field.setValueFrom(dataValue, t); nextUnreadFieldIndex = i + 1; break; } } catch (Asn1DecodingException e) { throw new Asn1DecodingException( "Failed to parse " + containerClass.getName() + "." + field.getField().getName(), e); } } } return t; } // NOTE: This method returns List rather than Set because ASN.1 SET_OF does require uniqueness // of elements -- it's an unordered collection. @SuppressWarnings("unchecked") private static List parseSetOf(BerDataValue container, Class elementClass) throws Asn1DecodingException { List result = new ArrayList<>(); BerDataValueReader elementsReader = container.contentsReader(); while (true) { BerDataValue dataValue; try { dataValue = elementsReader.readDataValue(); } catch (BerDataValueFormatException e) { throw new Asn1DecodingException("Malformed data value", e); } if (dataValue == null) { break; } T element; if (ByteBuffer.class.equals(elementClass)) { element = (T) dataValue.getEncodedContents(); } else if (Asn1OpaqueObject.class.equals(elementClass)) { element = (T) new Asn1OpaqueObject(dataValue.getEncoded()); } else { element = parse(dataValue, elementClass); } result.add(element); } return result; } private static Asn1Type getContainerAsn1Type(Class containerClass) throws Asn1DecodingException { Asn1Class containerAnnotation = containerClass.getDeclaredAnnotation(Asn1Class.class); if (containerAnnotation == null) { throw new Asn1DecodingException( containerClass.getName() + " is not annotated with " + Asn1Class.class.getName()); } switch (containerAnnotation.type()) { case CHOICE: case SEQUENCE: return containerAnnotation.type(); default: throw new Asn1DecodingException( "Unsupported ASN.1 container annotation type: " + containerAnnotation.type()); } } private static Class getElementType(Field field) throws Asn1DecodingException, ClassNotFoundException { String type = field.getGenericType().getTypeName(); int delimiterIndex = type.indexOf('<'); if (delimiterIndex == -1) { throw new Asn1DecodingException("Not a container type: " + field.getGenericType()); } int startIndex = delimiterIndex + 1; int endIndex = type.indexOf('>', startIndex); // TODO: handle comma? if (endIndex == -1) { throw new Asn1DecodingException("Not a container type: " + field.getGenericType()); } String elementClassName = type.substring(startIndex, endIndex); return Class.forName(elementClassName); } private static final class AnnotatedField { private final Field mField; private final Asn1Field mAnnotation; private final Asn1Type mDataType; private final Asn1TagClass mTagClass; private final int mBerTagClass; private final int mBerTagNumber; private final Asn1Tagging mTagging; private final boolean mOptional; public AnnotatedField(Field field, Asn1Field annotation) throws Asn1DecodingException { mField = field; mAnnotation = annotation; mDataType = annotation.type(); Asn1TagClass tagClass = annotation.cls(); if (tagClass == Asn1TagClass.AUTOMATIC) { if (annotation.tagNumber() != -1) { tagClass = Asn1TagClass.CONTEXT_SPECIFIC; } else { tagClass = Asn1TagClass.UNIVERSAL; } } mTagClass = tagClass; mBerTagClass = BerEncoding.getTagClass(mTagClass); int tagNumber; if (annotation.tagNumber() != -1) { tagNumber = annotation.tagNumber(); } else if ((mDataType == Asn1Type.CHOICE) || (mDataType == Asn1Type.ANY)) { tagNumber = -1; } else { tagNumber = BerEncoding.getTagNumber(mDataType); } mBerTagNumber = tagNumber; mTagging = annotation.tagging(); if (((mTagging == Asn1Tagging.EXPLICIT) || (mTagging == Asn1Tagging.IMPLICIT)) && (annotation.tagNumber() == -1)) { throw new Asn1DecodingException( "Tag number must be specified when tagging mode is " + mTagging); } mOptional = annotation.optional(); } public Field getField() { return mField; } public Asn1Field getAnnotation() { return mAnnotation; } public boolean isOptional() { return mOptional; } public int getBerTagClass() { return mBerTagClass; } public int getBerTagNumber() { return mBerTagNumber; } public void setValueFrom(BerDataValue dataValue, Object obj) throws Asn1DecodingException { int readTagClass = dataValue.getTagClass(); if (mBerTagNumber != -1) { int readTagNumber = dataValue.getTagNumber(); if ((readTagClass != mBerTagClass) || (readTagNumber != mBerTagNumber)) { throw new Asn1UnexpectedTagException( "Tag mismatch. Expected: " + BerEncoding.tagClassAndNumberToString(mBerTagClass, mBerTagNumber) + ", but found " + BerEncoding.tagClassAndNumberToString(readTagClass, readTagNumber)); } } else { if (readTagClass != mBerTagClass) { throw new Asn1UnexpectedTagException( "Tag mismatch. Expected class: " + BerEncoding.tagClassToString(mBerTagClass) + ", but found " + BerEncoding.tagClassToString(readTagClass)); } } if (mTagging == Asn1Tagging.EXPLICIT) { try { dataValue = dataValue.contentsReader().readDataValue(); } catch (BerDataValueFormatException e) { throw new Asn1DecodingException( "Failed to read contents of EXPLICIT data value", e); } } BerToJavaConverter.setFieldValue(obj, mField, mDataType, dataValue); } } private static class Asn1UnexpectedTagException extends Asn1DecodingException { private static final long serialVersionUID = 1L; public Asn1UnexpectedTagException(String message) { super(message); } } private static String oidToString(ByteBuffer encodedOid) throws Asn1DecodingException { if (!encodedOid.hasRemaining()) { throw new Asn1DecodingException("Empty OBJECT IDENTIFIER"); } // First component encodes the first two nodes, X.Y, as X * 40 + Y, with 0 <= X <= 2 long firstComponent = decodeBase128UnsignedLong(encodedOid); int firstNode = (int) Math.min(firstComponent / 40, 2); long secondNode = firstComponent - firstNode * 40; StringBuilder result = new StringBuilder(); result.append(Long.toString(firstNode)).append('.') .append(Long.toString(secondNode)); // Each consecutive node is encoded as a separate component while (encodedOid.hasRemaining()) { long node = decodeBase128UnsignedLong(encodedOid); result.append('.').append(Long.toString(node)); } return result.toString(); } private static long decodeBase128UnsignedLong(ByteBuffer encoded) throws Asn1DecodingException { if (!encoded.hasRemaining()) { return 0; } long result = 0; while (encoded.hasRemaining()) { if (result > Long.MAX_VALUE >>> 7) { throw new Asn1DecodingException("Base-128 number too large"); } int b = encoded.get() & 0xff; result <<= 7; result |= b & 0x7f; if ((b & 0x80) == 0) { return result; } } throw new Asn1DecodingException( "Truncated base-128 encoded input: missing terminating byte, with highest bit not" + " set"); } private static BigInteger integerToBigInteger(ByteBuffer encoded) { if (!encoded.hasRemaining()) { return BigInteger.ZERO; } return new BigInteger(ByteBufferUtils.toByteArray(encoded)); } private static int integerToInt(ByteBuffer encoded) throws Asn1DecodingException { BigInteger value = integerToBigInteger(encoded); try { return value.intValueExact(); } catch (ArithmeticException e) { throw new Asn1DecodingException( String.format("INTEGER cannot be represented as int: %1$d (0x%1$x)", value), e); } } private static long integerToLong(ByteBuffer encoded) throws Asn1DecodingException { BigInteger value = integerToBigInteger(encoded); try { return value.longValueExact(); } catch (ArithmeticException e) { throw new Asn1DecodingException( String.format("INTEGER cannot be represented as long: %1$d (0x%1$x)", value), e); } } private static List getAnnotatedFields(Class containerClass) throws Asn1DecodingException { Field[] declaredFields = containerClass.getDeclaredFields(); List result = new ArrayList<>(declaredFields.length); for (Field field : declaredFields) { Asn1Field annotation = field.getDeclaredAnnotation(Asn1Field.class); if (annotation == null) { continue; } if (Modifier.isStatic(field.getModifiers())) { throw new Asn1DecodingException( Asn1Field.class.getName() + " used on a static field: " + containerClass.getName() + "." + field.getName()); } AnnotatedField annotatedField; try { annotatedField = new AnnotatedField(field, annotation); } catch (Asn1DecodingException e) { throw new Asn1DecodingException( "Invalid ASN.1 annotation on " + containerClass.getName() + "." + field.getName(), e); } result.add(annotatedField); } return result; } private static final class BerToJavaConverter { private BerToJavaConverter() {} public static void setFieldValue( Object obj, Field field, Asn1Type type, BerDataValue dataValue) throws Asn1DecodingException { try { switch (type) { case SET_OF: case SEQUENCE_OF: if (Asn1OpaqueObject.class.equals(field.getType())) { field.set(obj, convert(type, dataValue, field.getType())); } else { field.set(obj, parseSetOf(dataValue, getElementType(field))); } return; default: field.set(obj, convert(type, dataValue, field.getType())); break; } } catch (ReflectiveOperationException e) { throw new Asn1DecodingException( "Failed to set value of " + obj.getClass().getName() + "." + field.getName(), e); } } private static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; @SuppressWarnings("unchecked") public static T convert( Asn1Type sourceType, BerDataValue dataValue, Class targetType) throws Asn1DecodingException { if (ByteBuffer.class.equals(targetType)) { return (T) dataValue.getEncodedContents(); } else if (byte[].class.equals(targetType)) { ByteBuffer resultBuf = dataValue.getEncodedContents(); if (!resultBuf.hasRemaining()) { return (T) EMPTY_BYTE_ARRAY; } byte[] result = new byte[resultBuf.remaining()]; resultBuf.get(result); return (T) result; } else if (Asn1OpaqueObject.class.equals(targetType)) { return (T) new Asn1OpaqueObject(dataValue.getEncoded()); } ByteBuffer encodedContents = dataValue.getEncodedContents(); switch (sourceType) { case INTEGER: if ((int.class.equals(targetType)) || (Integer.class.equals(targetType))) { return (T) Integer.valueOf(integerToInt(encodedContents)); } else if ((long.class.equals(targetType)) || (Long.class.equals(targetType))) { return (T) Long.valueOf(integerToLong(encodedContents)); } else if (BigInteger.class.equals(targetType)) { return (T) integerToBigInteger(encodedContents); } break; case OBJECT_IDENTIFIER: if (String.class.equals(targetType)) { return (T) oidToString(encodedContents); } break; case SEQUENCE: { Asn1Class containerAnnotation = targetType.getDeclaredAnnotation(Asn1Class.class); if ((containerAnnotation != null) && (containerAnnotation.type() == Asn1Type.SEQUENCE)) { return parseSequence(dataValue, targetType); } break; } case CHOICE: { Asn1Class containerAnnotation = targetType.getDeclaredAnnotation(Asn1Class.class); if ((containerAnnotation != null) && (containerAnnotation.type() == Asn1Type.CHOICE)) { return parseChoice(dataValue, targetType); } break; } default: break; } throw new Asn1DecodingException( "Unsupported conversion: ASN.1 " + sourceType + " to " + targetType.getName()); } } } src/main/java/com/android/apksig/internal/asn1/Asn1Class.java0100644 0000000 0000000 00000001666 13243353143 023040 0ustar000000000 0000000 /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.internal.asn1; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface Asn1Class { public Asn1Type type(); } src/main/java/com/android/apksig/internal/asn1/Asn1DecodingException.java0100644 0000000 0000000 00000002026 13243353143 025355 0ustar000000000 0000000 /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.internal.asn1; /** * Indicates that input could not be decoded into intended ASN.1 structure. */ public class Asn1DecodingException extends Exception { private static final long serialVersionUID = 1L; public Asn1DecodingException(String message) { super(message); } public Asn1DecodingException(String message, Throwable cause) { super(message, cause); } } src/main/java/com/android/apksig/internal/asn1/Asn1DerEncoder.java0100644 0000000 0000000 00000052774 13243353143 024013 0ustar000000000 0000000 /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.internal.asn1; import com.android.apksig.internal.asn1.ber.BerEncoding; import java.io.ByteArrayOutputStream; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.math.BigInteger; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.List; /** * Encoder of ASN.1 structures into DER-encoded form. * *

Structure is described to the encoder by providing a class annotated with {@link Asn1Class}, * containing fields annotated with {@link Asn1Field}. */ public final class Asn1DerEncoder { private Asn1DerEncoder() {} /** * Returns the DER-encoded form of the provided ASN.1 structure. * * @param container container to be encoded. The container's class must meet the following * requirements: *

    *
  • The class must be annotated with {@link Asn1Class}.
  • *
  • Member fields of the class which are to be encoded must be annotated with * {@link Asn1Field} and be public.
  • *
* * @throws Asn1EncodingException if the input could not be encoded */ public static byte[] encode(Object container) throws Asn1EncodingException { Class containerClass = container.getClass(); Asn1Class containerAnnotation = containerClass.getDeclaredAnnotation(Asn1Class.class); if (containerAnnotation == null) { throw new Asn1EncodingException( containerClass.getName() + " not annotated with " + Asn1Class.class.getName()); } Asn1Type containerType = containerAnnotation.type(); switch (containerType) { case CHOICE: return toChoice(container); case SEQUENCE: return toSequence(container); default: throw new Asn1EncodingException("Unsupported container type: " + containerType); } } private static byte[] toChoice(Object container) throws Asn1EncodingException { Class containerClass = container.getClass(); List fields = getAnnotatedFields(container); if (fields.isEmpty()) { throw new Asn1EncodingException( "No fields annotated with " + Asn1Field.class.getName() + " in CHOICE class " + containerClass.getName()); } AnnotatedField resultField = null; for (AnnotatedField field : fields) { Object fieldValue = getMemberFieldValue(container, field.getField()); if (fieldValue != null) { if (resultField != null) { throw new Asn1EncodingException( "Multiple non-null fields in CHOICE class " + containerClass.getName() + ": " + resultField.getField().getName() + ", " + field.getField().getName()); } resultField = field; } } if (resultField == null) { throw new Asn1EncodingException( "No non-null fields in CHOICE class " + containerClass.getName()); } return resultField.toDer(); } private static byte[] toSequence(Object container) throws Asn1EncodingException { Class containerClass = container.getClass(); List fields = getAnnotatedFields(container); Collections.sort( fields, (f1, f2) -> f1.getAnnotation().index() - f2.getAnnotation().index()); if (fields.size() > 1) { AnnotatedField lastField = null; for (AnnotatedField field : fields) { if ((lastField != null) && (lastField.getAnnotation().index() == field.getAnnotation().index())) { throw new Asn1EncodingException( "Fields have the same index: " + containerClass.getName() + "." + lastField.getField().getName() + " and ." + field.getField().getName()); } lastField = field; } } List serializedFields = new ArrayList<>(fields.size()); for (AnnotatedField field : fields) { byte[] serializedField; try { serializedField = field.toDer(); } catch (Asn1EncodingException e) { throw new Asn1EncodingException( "Failed to encode " + containerClass.getName() + "." + field.getField().getName(), e); } if (serializedField != null) { serializedFields.add(serializedField); } } return createTag( BerEncoding.TAG_CLASS_UNIVERSAL, true, BerEncoding.TAG_NUMBER_SEQUENCE, serializedFields.toArray(new byte[0][])); } private static byte[] toSetOf(Collection values, Asn1Type elementType) throws Asn1EncodingException { List serializedValues = new ArrayList<>(values.size()); for (Object value : values) { serializedValues.add(JavaToDerConverter.toDer(value, elementType, null)); } if (serializedValues.size() > 1) { Collections.sort(serializedValues, ByteArrayLexicographicComparator.INSTANCE); } return createTag( BerEncoding.TAG_CLASS_UNIVERSAL, true, BerEncoding.TAG_NUMBER_SET, serializedValues.toArray(new byte[0][])); } /** * Compares two bytes arrays based on their lexicographic order. Corresponding elements of the * two arrays are compared in ascending order. Elements at out of range indices are assumed to * be smaller than the smallest possible value for an element. */ private static class ByteArrayLexicographicComparator implements Comparator { private static final ByteArrayLexicographicComparator INSTANCE = new ByteArrayLexicographicComparator(); @Override public int compare(byte[] arr1, byte[] arr2) { int commonLength = Math.min(arr1.length, arr2.length); for (int i = 0; i < commonLength; i++) { int diff = (arr1[i] & 0xff) - (arr2[i] & 0xff); if (diff != 0) { return diff; } } return arr1.length - arr2.length; } } private static List getAnnotatedFields(Object container) throws Asn1EncodingException { Class containerClass = container.getClass(); Field[] declaredFields = containerClass.getDeclaredFields(); List result = new ArrayList<>(declaredFields.length); for (Field field : declaredFields) { Asn1Field annotation = field.getDeclaredAnnotation(Asn1Field.class); if (annotation == null) { continue; } if (Modifier.isStatic(field.getModifiers())) { throw new Asn1EncodingException( Asn1Field.class.getName() + " used on a static field: " + containerClass.getName() + "." + field.getName()); } AnnotatedField annotatedField; try { annotatedField = new AnnotatedField(container, field, annotation); } catch (Asn1EncodingException e) { throw new Asn1EncodingException( "Invalid ASN.1 annotation on " + containerClass.getName() + "." + field.getName(), e); } result.add(annotatedField); } return result; } private static byte[] toInteger(int value) { return toInteger((long) value); } private static byte[] toInteger(long value) { return toInteger(BigInteger.valueOf(value)); } private static byte[] toInteger(BigInteger value) { return createTag( BerEncoding.TAG_CLASS_UNIVERSAL, false, BerEncoding.TAG_NUMBER_INTEGER, value.toByteArray()); } private static byte[] toOid(String oid) throws Asn1EncodingException { ByteArrayOutputStream encodedValue = new ByteArrayOutputStream(); String[] nodes = oid.split("\\."); if (nodes.length < 2) { throw new Asn1EncodingException( "OBJECT IDENTIFIER must contain at least two nodes: " + oid); } int firstNode; try { firstNode = Integer.parseInt(nodes[0]); } catch (NumberFormatException e) { throw new Asn1EncodingException("Node #1 not numeric: " + nodes[0]); } if ((firstNode > 6) || (firstNode < 0)) { throw new Asn1EncodingException("Invalid value for node #1: " + firstNode); } int secondNode; try { secondNode = Integer.parseInt(nodes[1]); } catch (NumberFormatException e) { throw new Asn1EncodingException("Node #2 not numeric: " + nodes[1]); } if ((secondNode >= 40) || (secondNode < 0)) { throw new Asn1EncodingException("Invalid value for node #2: " + secondNode); } int firstByte = firstNode * 40 + secondNode; if (firstByte > 0xff) { throw new Asn1EncodingException( "First two nodes out of range: " + firstNode + "." + secondNode); } encodedValue.write(firstByte); for (int i = 2; i < nodes.length; i++) { String nodeString = nodes[i]; int node; try { node = Integer.parseInt(nodeString); } catch (NumberFormatException e) { throw new Asn1EncodingException("Node #" + (i + 1) + " not numeric: " + nodeString); } if (node < 0) { throw new Asn1EncodingException("Invalid value for node #" + (i + 1) + ": " + node); } if (node <= 0x7f) { encodedValue.write(node); continue; } if (node < 1 << 14) { encodedValue.write(0x80 | (node >> 7)); encodedValue.write(node & 0x7f); continue; } if (node < 1 << 21) { encodedValue.write(0x80 | (node >> 14)); encodedValue.write(0x80 | ((node >> 7) & 0x7f)); encodedValue.write(node & 0x7f); continue; } throw new Asn1EncodingException("Node #" + (i + 1) + " too large: " + node); } return createTag( BerEncoding.TAG_CLASS_UNIVERSAL, false, BerEncoding.TAG_NUMBER_OBJECT_IDENTIFIER, encodedValue.toByteArray()); } private static Object getMemberFieldValue(Object obj, Field field) throws Asn1EncodingException { try { return field.get(obj); } catch (ReflectiveOperationException e) { throw new Asn1EncodingException( "Failed to read " + obj.getClass().getName() + "." + field.getName(), e); } } private static final class AnnotatedField { private final Field mField; private final Object mObject; private final Asn1Field mAnnotation; private final Asn1Type mDataType; private final Asn1Type mElementDataType; private final Asn1TagClass mTagClass; private final int mDerTagClass; private final int mDerTagNumber; private final Asn1Tagging mTagging; private final boolean mOptional; public AnnotatedField(Object obj, Field field, Asn1Field annotation) throws Asn1EncodingException { mObject = obj; mField = field; mAnnotation = annotation; mDataType = annotation.type(); mElementDataType = annotation.elementType(); Asn1TagClass tagClass = annotation.cls(); if (tagClass == Asn1TagClass.AUTOMATIC) { if (annotation.tagNumber() != -1) { tagClass = Asn1TagClass.CONTEXT_SPECIFIC; } else { tagClass = Asn1TagClass.UNIVERSAL; } } mTagClass = tagClass; mDerTagClass = BerEncoding.getTagClass(mTagClass); int tagNumber; if (annotation.tagNumber() != -1) { tagNumber = annotation.tagNumber(); } else if ((mDataType == Asn1Type.CHOICE) || (mDataType == Asn1Type.ANY)) { tagNumber = -1; } else { tagNumber = BerEncoding.getTagNumber(mDataType); } mDerTagNumber = tagNumber; mTagging = annotation.tagging(); if (((mTagging == Asn1Tagging.EXPLICIT) || (mTagging == Asn1Tagging.IMPLICIT)) && (annotation.tagNumber() == -1)) { throw new Asn1EncodingException( "Tag number must be specified when tagging mode is " + mTagging); } mOptional = annotation.optional(); } public Field getField() { return mField; } public Asn1Field getAnnotation() { return mAnnotation; } public byte[] toDer() throws Asn1EncodingException { Object fieldValue = getMemberFieldValue(mObject, mField); if (fieldValue == null) { if (mOptional) { return null; } throw new Asn1EncodingException("Required field not set"); } byte[] encoded = JavaToDerConverter.toDer(fieldValue, mDataType, mElementDataType); switch (mTagging) { case NORMAL: return encoded; case EXPLICIT: return createTag(mDerTagClass, true, mDerTagNumber, encoded); case IMPLICIT: int originalTagNumber = BerEncoding.getTagNumber(encoded[0]); if (originalTagNumber == 0x1f) { throw new Asn1EncodingException("High-tag-number form not supported"); } if (mDerTagNumber >= 0x1f) { throw new Asn1EncodingException( "Unsupported high tag number: " + mDerTagNumber); } encoded[0] = BerEncoding.setTagNumber(encoded[0], mDerTagNumber); encoded[0] = BerEncoding.setTagClass(encoded[0], mDerTagClass); return encoded; default: throw new RuntimeException("Unknown tagging mode: " + mTagging); } } } private static byte[] createTag( int tagClass, boolean constructed, int tagNumber, byte[]... contents) { if (tagNumber >= 0x1f) { throw new IllegalArgumentException("High tag numbers not supported: " + tagNumber); } // tag class & number fit into the first byte byte firstIdentifierByte = (byte) ((tagClass << 6) | (constructed ? 1 << 5 : 0) | tagNumber); int contentsLength = 0; for (byte[] c : contents) { contentsLength += c.length; } int contentsPosInResult; byte[] result; if (contentsLength < 0x80) { // Length fits into one byte contentsPosInResult = 2; result = new byte[contentsPosInResult + contentsLength]; result[0] = firstIdentifierByte; result[1] = (byte) contentsLength; } else { // Length is represented as multiple bytes // The low 7 bits of the first byte represent the number of length bytes (following the // first byte) in which the length is in big-endian base-256 form if (contentsLength <= 0xff) { contentsPosInResult = 3; result = new byte[contentsPosInResult + contentsLength]; result[1] = (byte) 0x81; // 1 length byte result[2] = (byte) contentsLength; } else if (contentsLength <= 0xffff) { contentsPosInResult = 4; result = new byte[contentsPosInResult + contentsLength]; result[1] = (byte) 0x82; // 2 length bytes result[2] = (byte) (contentsLength >> 8); result[3] = (byte) (contentsLength & 0xff); } else if (contentsLength <= 0xffffff) { contentsPosInResult = 5; result = new byte[contentsPosInResult + contentsLength]; result[1] = (byte) 0x83; // 3 length bytes result[2] = (byte) (contentsLength >> 16); result[3] = (byte) ((contentsLength >> 8) & 0xff); result[4] = (byte) (contentsLength & 0xff); } else { contentsPosInResult = 6; result = new byte[contentsPosInResult + contentsLength]; result[1] = (byte) 0x84; // 4 length bytes result[2] = (byte) (contentsLength >> 24); result[3] = (byte) ((contentsLength >> 16) & 0xff); result[4] = (byte) ((contentsLength >> 8) & 0xff); result[5] = (byte) (contentsLength & 0xff); } result[0] = firstIdentifierByte; } for (byte[] c : contents) { System.arraycopy(c, 0, result, contentsPosInResult, c.length); contentsPosInResult += c.length; } return result; } private static final class JavaToDerConverter { private JavaToDerConverter() {} public static byte[] toDer(Object source, Asn1Type targetType, Asn1Type targetElementType) throws Asn1EncodingException { Class sourceType = source.getClass(); if (Asn1OpaqueObject.class.equals(sourceType)) { ByteBuffer buf = ((Asn1OpaqueObject) source).getEncoded(); byte[] result = new byte[buf.remaining()]; buf.get(result); return result; } if ((targetType == null) || (targetType == Asn1Type.ANY)) { return encode(source); } switch (targetType) { case OCTET_STRING: byte[] value = null; if (source instanceof ByteBuffer) { ByteBuffer buf = (ByteBuffer) source; value = new byte[buf.remaining()]; buf.slice().get(value); } else if (source instanceof byte[]) { value = (byte[]) source; } if (value != null) { return createTag( BerEncoding.TAG_CLASS_UNIVERSAL, false, BerEncoding.TAG_NUMBER_OCTET_STRING, value); } break; case INTEGER: if (source instanceof Integer) { return toInteger((Integer) source); } else if (source instanceof Long) { return toInteger((Long) source); } else if (source instanceof BigInteger) { return toInteger((BigInteger) source); } break; case OBJECT_IDENTIFIER: if (source instanceof String) { return toOid((String) source); } break; case SEQUENCE: { Asn1Class containerAnnotation = sourceType.getDeclaredAnnotation(Asn1Class.class); if ((containerAnnotation != null) && (containerAnnotation.type() == Asn1Type.SEQUENCE)) { return toSequence(source); } break; } case CHOICE: { Asn1Class containerAnnotation = sourceType.getDeclaredAnnotation(Asn1Class.class); if ((containerAnnotation != null) && (containerAnnotation.type() == Asn1Type.CHOICE)) { return toChoice(source); } break; } case SET_OF: return toSetOf((Collection) source, targetElementType); default: break; } throw new Asn1EncodingException( "Unsupported conversion: " + sourceType.getName() + " to ASN.1 " + targetType); } } } src/main/java/com/android/apksig/internal/asn1/Asn1EncodingException.java0100644 0000000 0000000 00000002005 13243353143 025364 0ustar000000000 0000000 /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.internal.asn1; /** * Indicates that an ASN.1 structure could not be encoded. */ public class Asn1EncodingException extends Exception { private static final long serialVersionUID = 1L; public Asn1EncodingException(String message) { super(message); } public Asn1EncodingException(String message, Throwable cause) { super(message, cause); } } src/main/java/com/android/apksig/internal/asn1/Asn1Field.java0100644 0000000 0000000 00000003140 13243353143 023003 0ustar000000000 0000000 /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.internal.asn1; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) public @interface Asn1Field { /** Index used to order fields in a container. Required for fields of SEQUENCE containers. */ public int index() default 0; public Asn1TagClass cls() default Asn1TagClass.AUTOMATIC; public Asn1Type type(); /** Tagging mode. Default: NORMAL. */ public Asn1Tagging tagging() default Asn1Tagging.NORMAL; /** Tag number. Required when IMPLICIT and EXPLICIT tagging mode is used.*/ public int tagNumber() default -1; /** {@code true} if this field is optional. Ignored for fields of CHOICE containers. */ public boolean optional() default false; /** Type of elements. Used only for SET_OF or SEQUENCE_OF. */ public Asn1Type elementType() default Asn1Type.ANY; } src/main/java/com/android/apksig/internal/asn1/Asn1OpaqueObject.java0100644 0000000 0000000 00000002073 13243353143 024345 0ustar000000000 0000000 /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.internal.asn1; import java.nio.ByteBuffer; /** * Opaque holder of encoded ASN.1 stuff. */ public class Asn1OpaqueObject { private final ByteBuffer mEncoded; public Asn1OpaqueObject(ByteBuffer encoded) { mEncoded = encoded.slice(); } public Asn1OpaqueObject(byte[] encoded) { mEncoded = ByteBuffer.wrap(encoded); } public ByteBuffer getEncoded() { return mEncoded.slice(); } } src/main/java/com/android/apksig/internal/asn1/Asn1TagClass.java0100644 0000000 0000000 00000001622 13243353143 023464 0ustar000000000 0000000 /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.internal.asn1; public enum Asn1TagClass { UNIVERSAL, APPLICATION, CONTEXT_SPECIFIC, PRIVATE, /** * Not really an actual tag class: decoder/encoder will attempt to deduce the correct tag class * automatically. */ AUTOMATIC, } src/main/java/com/android/apksig/internal/asn1/Asn1Tagging.java0100644 0000000 0000000 00000001334 13243353143 023343 0ustar000000000 0000000 /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.internal.asn1; public enum Asn1Tagging { NORMAL, EXPLICIT, IMPLICIT, } src/main/java/com/android/apksig/internal/asn1/Asn1Type.java0100644 0000000 0000000 00000001447 13243353143 022711 0ustar000000000 0000000 /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.internal.asn1; public enum Asn1Type { ANY, CHOICE, INTEGER, OBJECT_IDENTIFIER, OCTET_STRING, SEQUENCE, SEQUENCE_OF, SET_OF, } src/main/java/com/android/apksig/internal/asn1/ber/0040755 0000000 0000000 00000000000 13243353143 021147 5ustar000000000 0000000 src/main/java/com/android/apksig/internal/asn1/ber/BerDataValue.java0100644 0000000 0000000 00000006402 13243353143 024310 0ustar000000000 0000000 /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.internal.asn1.ber; import java.nio.ByteBuffer; /** * ASN.1 Basic Encoding Rules (BER) data value -- see {@code X.690}. */ public class BerDataValue { private final ByteBuffer mEncoded; private final ByteBuffer mEncodedContents; private final int mTagClass; private final boolean mConstructed; private final int mTagNumber; BerDataValue( ByteBuffer encoded, ByteBuffer encodedContents, int tagClass, boolean constructed, int tagNumber) { mEncoded = encoded; mEncodedContents = encodedContents; mTagClass = tagClass; mConstructed = constructed; mTagNumber = tagNumber; } /** * Returns the tag class of this data value. See {@link BerEncoding} {@code TAG_CLASS} * constants. */ public int getTagClass() { return mTagClass; } /** * Returns {@code true} if the content octets of this data value are the complete BER encoding * of one or more data values, {@code false} if the content octets of this data value directly * represent the value. */ public boolean isConstructed() { return mConstructed; } /** * Returns the tag number of this data value. See {@link BerEncoding} {@code TAG_NUMBER} * constants. */ public int getTagNumber() { return mTagNumber; } /** * Returns the encoded form of this data value. */ public ByteBuffer getEncoded() { return mEncoded.slice(); } /** * Returns the encoded contents of this data value. */ public ByteBuffer getEncodedContents() { return mEncodedContents.slice(); } /** * Returns a new reader of the contents of this data value. */ public BerDataValueReader contentsReader() { return new ByteBufferBerDataValueReader(getEncodedContents()); } /** * Returns a new reader which returns just this data value. This may be useful for re-reading * this value in different contexts. */ public BerDataValueReader dataValueReader() { return new ParsedValueReader(this); } private static final class ParsedValueReader implements BerDataValueReader { private final BerDataValue mValue; private boolean mValueOutput; public ParsedValueReader(BerDataValue value) { mValue = value; } @Override public BerDataValue readDataValue() throws BerDataValueFormatException { if (mValueOutput) { return null; } mValueOutput = true; return mValue; } } } src/main/java/com/android/apksig/internal/asn1/ber/BerDataValueFormatException.java0100644 0000000 0000000 00000002114 13243353143 027334 0ustar000000000 0000000 /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.internal.asn1.ber; /** * Indicates that an ASN.1 data value being read could not be decoded using * Basic Encoding Rules (BER). */ public class BerDataValueFormatException extends Exception { private static final long serialVersionUID = 1L; public BerDataValueFormatException(String message) { super(message); } public BerDataValueFormatException(String message, Throwable cause) { super(message, cause); } } src/main/java/com/android/apksig/internal/asn1/ber/BerDataValueReader.java0100644 0000000 0000000 00000002430 13243353143 025430 0ustar000000000 0000000 /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.internal.asn1.ber; /** * Reader of ASN.1 Basic Encoding Rules (BER) data values. * *

BER data value reader returns data values, one by one, from a source. The interpretation of * data values (e.g., how to obtain a numeric value from an INTEGER data value, or how to extract * the elements of a SEQUENCE value) is left to clients of the reader. */ public interface BerDataValueReader { /** * Returns the next data value or {@code null} if end of input has been reached. * * @throws BerDataValueFormatException if the value being read is malformed. */ BerDataValue readDataValue() throws BerDataValueFormatException; } src/main/java/com/android/apksig/internal/asn1/ber/BerEncoding.java0100644 0000000 0000000 00000013361 13243353143 024172 0ustar000000000 0000000 /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.internal.asn1.ber; import com.android.apksig.internal.asn1.Asn1Type; import com.android.apksig.internal.asn1.Asn1TagClass; /** * ASN.1 Basic Encoding Rules (BER) constants and helper methods. See {@code X.690}. */ public abstract class BerEncoding { private BerEncoding() {} /** * Constructed vs primitive flag in the first identifier byte. */ public static final int ID_FLAG_CONSTRUCTED_ENCODING = 1 << 5; /** * Tag class: UNIVERSAL */ public static final int TAG_CLASS_UNIVERSAL = 0; /** * Tag class: APPLICATION */ public static final int TAG_CLASS_APPLICATION = 1; /** * Tag class: CONTEXT SPECIFIC */ public static final int TAG_CLASS_CONTEXT_SPECIFIC = 2; /** * Tag class: PRIVATE */ public static final int TAG_CLASS_PRIVATE = 3; /** * Tag number: INTEGER */ public static final int TAG_NUMBER_INTEGER = 0x2; /** * Tag number: OCTET STRING */ public static final int TAG_NUMBER_OCTET_STRING = 0x4; /** * Tag number: NULL */ public static final int TAG_NUMBER_NULL = 0x05; /** * Tag number: OBJECT IDENTIFIER */ public static final int TAG_NUMBER_OBJECT_IDENTIFIER = 0x6; /** * Tag number: SEQUENCE */ public static final int TAG_NUMBER_SEQUENCE = 0x10; /** * Tag number: SET */ public static final int TAG_NUMBER_SET = 0x11; public static int getTagNumber(Asn1Type dataType) { switch (dataType) { case INTEGER: return TAG_NUMBER_INTEGER; case OBJECT_IDENTIFIER: return TAG_NUMBER_OBJECT_IDENTIFIER; case OCTET_STRING: return TAG_NUMBER_OCTET_STRING; case SET_OF: return TAG_NUMBER_SET; case SEQUENCE: case SEQUENCE_OF: return TAG_NUMBER_SEQUENCE; default: throw new IllegalArgumentException("Unsupported data type: " + dataType); } } public static int getTagClass(Asn1TagClass tagClass) { switch (tagClass) { case APPLICATION: return TAG_CLASS_APPLICATION; case CONTEXT_SPECIFIC: return TAG_CLASS_CONTEXT_SPECIFIC; case PRIVATE: return TAG_CLASS_PRIVATE; case UNIVERSAL: return TAG_CLASS_UNIVERSAL; default: throw new IllegalArgumentException("Unsupported tag class: " + tagClass); } } public static String tagClassToString(int typeClass) { switch (typeClass) { case TAG_CLASS_APPLICATION: return "APPLICATION"; case TAG_CLASS_CONTEXT_SPECIFIC: return ""; case TAG_CLASS_PRIVATE: return "PRIVATE"; case TAG_CLASS_UNIVERSAL: return "UNIVERSAL"; default: throw new IllegalArgumentException("Unsupported type class: " + typeClass); } } public static String tagClassAndNumberToString(int tagClass, int tagNumber) { String classString = tagClassToString(tagClass); String numberString = tagNumberToString(tagNumber); return classString.isEmpty() ? numberString : classString + " " + numberString; } public static String tagNumberToString(int tagNumber) { switch (tagNumber) { case TAG_NUMBER_INTEGER: return "INTEGER"; case TAG_NUMBER_OCTET_STRING: return "OCTET STRING"; case TAG_NUMBER_NULL: return "NULL"; case TAG_NUMBER_OBJECT_IDENTIFIER: return "OBJECT IDENTIFIER"; case TAG_NUMBER_SEQUENCE: return "SEQUENCE"; case TAG_NUMBER_SET: return "SET"; default: return "0x" + Integer.toHexString(tagNumber); } } /** * Returns {@code true} if the provided first identifier byte indicates that the data value uses * constructed encoding for its contents, or {@code false} if the data value uses primitive * encoding for its contents. */ public static boolean isConstructed(byte firstIdentifierByte) { return (firstIdentifierByte & ID_FLAG_CONSTRUCTED_ENCODING) != 0; } /** * Returns the tag class encoded in the provided first identifier byte. See {@code TAG_CLASS} * constants. */ public static int getTagClass(byte firstIdentifierByte) { return (firstIdentifierByte & 0xff) >> 6; } public static byte setTagClass(byte firstIdentifierByte, int tagClass) { return (byte) ((firstIdentifierByte & 0x3f) | (tagClass << 6)); } /** * Returns the tag number encoded in the provided first identifier byte. See {@code TAG_NUMBER} * constants. */ public static int getTagNumber(byte firstIdentifierByte) { return firstIdentifierByte & 0x1f; } public static byte setTagNumber(byte firstIdentifierByte, int tagNumber) { return (byte) ((firstIdentifierByte & ~0x1f) | tagNumber); } } src/main/java/com/android/apksig/internal/asn1/ber/ByteBufferBerDataValueReader.java0100644 0000000 0000000 00000020010 13243353143 027400 0ustar000000000 0000000 /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.internal.asn1.ber; import java.nio.ByteBuffer; /** * {@link BerDataValueReader} which reads from a {@link ByteBuffer} containing BER-encoded data * values. See {@code X.690} for the encoding. */ public class ByteBufferBerDataValueReader implements BerDataValueReader { private final ByteBuffer mBuf; public ByteBufferBerDataValueReader(ByteBuffer buf) { if (buf == null) { throw new NullPointerException("buf == null"); } mBuf = buf; } @Override public BerDataValue readDataValue() throws BerDataValueFormatException { int startPosition = mBuf.position(); if (!mBuf.hasRemaining()) { return null; } byte firstIdentifierByte = mBuf.get(); int tagNumber = readTagNumber(firstIdentifierByte); boolean constructed = BerEncoding.isConstructed(firstIdentifierByte); if (!mBuf.hasRemaining()) { throw new BerDataValueFormatException("Missing length"); } int firstLengthByte = mBuf.get() & 0xff; int contentsLength; int contentsOffsetInTag; if ((firstLengthByte & 0x80) == 0) { // short form length contentsLength = readShortFormLength(firstLengthByte); contentsOffsetInTag = mBuf.position() - startPosition; skipDefiniteLengthContents(contentsLength); } else if (firstLengthByte != 0x80) { // long form length contentsLength = readLongFormLength(firstLengthByte); contentsOffsetInTag = mBuf.position() - startPosition; skipDefiniteLengthContents(contentsLength); } else { // indefinite length -- value ends with 0x00 0x00 contentsOffsetInTag = mBuf.position() - startPosition; contentsLength = constructed ? skipConstructedIndefiniteLengthContents() : skipPrimitiveIndefiniteLengthContents(); } // Create the encoded data value ByteBuffer int endPosition = mBuf.position(); mBuf.position(startPosition); int bufOriginalLimit = mBuf.limit(); mBuf.limit(endPosition); ByteBuffer encoded = mBuf.slice(); mBuf.position(mBuf.limit()); mBuf.limit(bufOriginalLimit); // Create the encoded contents ByteBuffer encoded.position(contentsOffsetInTag); encoded.limit(contentsOffsetInTag + contentsLength); ByteBuffer encodedContents = encoded.slice(); encoded.clear(); return new BerDataValue( encoded, encodedContents, BerEncoding.getTagClass(firstIdentifierByte), constructed, tagNumber); } private int readTagNumber(byte firstIdentifierByte) throws BerDataValueFormatException { int tagNumber = BerEncoding.getTagNumber(firstIdentifierByte); if (tagNumber == 0x1f) { // high-tag-number form, where the tag number follows this byte in base-128 // big-endian form, where each byte has the highest bit set, except for the last // byte return readHighTagNumber(); } else { // low-tag-number form return tagNumber; } } private int readHighTagNumber() throws BerDataValueFormatException { // Base-128 big-endian form, where each byte has the highest bit set, except for the last // byte int b; int result = 0; do { if (!mBuf.hasRemaining()) { throw new BerDataValueFormatException("Truncated tag number"); } b = mBuf.get(); if (result > Integer.MAX_VALUE >>> 7) { throw new BerDataValueFormatException("Tag number too large"); } result <<= 7; result |= b & 0x7f; } while ((b & 0x80) != 0); return result; } private int readShortFormLength(int firstLengthByte) { return firstLengthByte & 0x7f; } private int readLongFormLength(int firstLengthByte) throws BerDataValueFormatException { // The low 7 bits of the first byte represent the number of bytes (following the first // byte) in which the length is in big-endian base-256 form int byteCount = firstLengthByte & 0x7f; if (byteCount > 4) { throw new BerDataValueFormatException("Length too large: " + byteCount + " bytes"); } int result = 0; for (int i = 0; i < byteCount; i++) { if (!mBuf.hasRemaining()) { throw new BerDataValueFormatException("Truncated length"); } int b = mBuf.get(); if (result > Integer.MAX_VALUE >>> 8) { throw new BerDataValueFormatException("Length too large"); } result <<= 8; result |= b & 0xff; } return result; } private void skipDefiniteLengthContents(int contentsLength) throws BerDataValueFormatException { if (mBuf.remaining() < contentsLength) { throw new BerDataValueFormatException( "Truncated contents. Need: " + contentsLength + " bytes, available: " + mBuf.remaining()); } mBuf.position(mBuf.position() + contentsLength); } private int skipPrimitiveIndefiniteLengthContents() throws BerDataValueFormatException { // Contents are terminated by 0x00 0x00 boolean prevZeroByte = false; int bytesRead = 0; while (true) { if (!mBuf.hasRemaining()) { throw new BerDataValueFormatException( "Truncated indefinite-length contents: " + bytesRead + " bytes read"); } int b = mBuf.get(); bytesRead++; if (bytesRead < 0) { throw new BerDataValueFormatException("Indefinite-length contents too long"); } if (b == 0) { if (prevZeroByte) { // End of contents reached -- we've read the value and its terminator 0x00 0x00 return bytesRead - 2; } prevZeroByte = true; } else { prevZeroByte = false; } } } private int skipConstructedIndefiniteLengthContents() throws BerDataValueFormatException { // Contents are terminated by 0x00 0x00. However, this data value is constructed, meaning it // can contain data values which are themselves indefinite length encoded. As a result, we // must parse the direct children of this data value to correctly skip over the contents of // this data value. int startPos = mBuf.position(); while (mBuf.hasRemaining()) { // Check whether the 0x00 0x00 terminator is at current position if ((mBuf.remaining() > 1) && (mBuf.getShort(mBuf.position()) == 0)) { int contentsLength = mBuf.position() - startPos; mBuf.position(mBuf.position() + 2); return contentsLength; } // No luck. This must be a BER-encoded data value -- skip over it by parsing it readDataValue(); } throw new BerDataValueFormatException( "Truncated indefinite-length contents: " + (mBuf.position() - startPos) + " bytes read"); } } src/main/java/com/android/apksig/internal/asn1/ber/InputStreamBerDataValueReader.java0100644 0000000 0000000 00000026152 13243353143 027633 0ustar000000000 0000000 /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.internal.asn1.ber; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; /** * {@link BerDataValueReader} which reads from an {@link InputStream} returning BER-encoded data * values. See {@code X.690} for the encoding. */ public class InputStreamBerDataValueReader implements BerDataValueReader { private final InputStream mIn; public InputStreamBerDataValueReader(InputStream in) { if (in == null) { throw new NullPointerException("in == null"); } mIn = in; } @Override public BerDataValue readDataValue() throws BerDataValueFormatException { return readDataValue(mIn); } /** * Returns the next data value or {@code null} if end of input has been reached. * * @throws BerDataValueFormatException if the value being read is malformed. */ @SuppressWarnings("resource") private static BerDataValue readDataValue(InputStream input) throws BerDataValueFormatException { RecordingInputStream in = new RecordingInputStream(input); try { int firstIdentifierByte = in.read(); if (firstIdentifierByte == -1) { // End of input return null; } int tagNumber = readTagNumber(in, firstIdentifierByte); int firstLengthByte = in.read(); if (firstLengthByte == -1) { throw new BerDataValueFormatException("Missing length"); } boolean constructed = BerEncoding.isConstructed((byte) firstIdentifierByte); int contentsLength; int contentsOffsetInDataValue; if ((firstLengthByte & 0x80) == 0) { // short form length contentsLength = readShortFormLength(firstLengthByte); contentsOffsetInDataValue = in.getReadByteCount(); skipDefiniteLengthContents(in, contentsLength); } else if ((firstLengthByte & 0xff) != 0x80) { // long form length contentsLength = readLongFormLength(in, firstLengthByte); contentsOffsetInDataValue = in.getReadByteCount(); skipDefiniteLengthContents(in, contentsLength); } else { // indefinite length contentsOffsetInDataValue = in.getReadByteCount(); contentsLength = constructed ? skipConstructedIndefiniteLengthContents(in) : skipPrimitiveIndefiniteLengthContents(in); } byte[] encoded = in.getReadBytes(); ByteBuffer encodedContents = ByteBuffer.wrap(encoded, contentsOffsetInDataValue, contentsLength); return new BerDataValue( ByteBuffer.wrap(encoded), encodedContents, BerEncoding.getTagClass((byte) firstIdentifierByte), constructed, tagNumber); } catch (IOException e) { throw new BerDataValueFormatException("Failed to read data value", e); } } private static int readTagNumber(InputStream in, int firstIdentifierByte) throws IOException, BerDataValueFormatException { int tagNumber = BerEncoding.getTagNumber((byte) firstIdentifierByte); if (tagNumber == 0x1f) { // high-tag-number form return readHighTagNumber(in); } else { // low-tag-number form return tagNumber; } } private static int readHighTagNumber(InputStream in) throws IOException, BerDataValueFormatException { // Base-128 big-endian form, where each byte has the highest bit set, except for the last // byte where the highest bit is not set int b; int result = 0; do { b = in.read(); if (b == -1) { throw new BerDataValueFormatException("Truncated tag number"); } if (result > Integer.MAX_VALUE >>> 7) { throw new BerDataValueFormatException("Tag number too large"); } result <<= 7; result |= b & 0x7f; } while ((b & 0x80) != 0); return result; } private static int readShortFormLength(int firstLengthByte) { return firstLengthByte & 0x7f; } private static int readLongFormLength(InputStream in, int firstLengthByte) throws IOException, BerDataValueFormatException { // The low 7 bits of the first byte represent the number of bytes (following the first // byte) in which the length is in big-endian base-256 form int byteCount = firstLengthByte & 0x7f; if (byteCount > 4) { throw new BerDataValueFormatException("Length too large: " + byteCount + " bytes"); } int result = 0; for (int i = 0; i < byteCount; i++) { int b = in.read(); if (b == -1) { throw new BerDataValueFormatException("Truncated length"); } if (result > Integer.MAX_VALUE >>> 8) { throw new BerDataValueFormatException("Length too large"); } result <<= 8; result |= b & 0xff; } return result; } private static void skipDefiniteLengthContents(InputStream in, int len) throws IOException, BerDataValueFormatException { long bytesRead = 0; while (len > 0) { int skipped = (int) in.skip(len); if (skipped <= 0) { throw new BerDataValueFormatException( "Truncated definite-length contents: " + bytesRead + " bytes read" + ", " + len + " missing"); } len -= skipped; bytesRead += skipped; } } private static int skipPrimitiveIndefiniteLengthContents(InputStream in) throws IOException, BerDataValueFormatException { // Contents are terminated by 0x00 0x00 boolean prevZeroByte = false; int bytesRead = 0; while (true) { int b = in.read(); if (b == -1) { throw new BerDataValueFormatException( "Truncated indefinite-length contents: " + bytesRead + " bytes read"); } bytesRead++; if (bytesRead < 0) { throw new BerDataValueFormatException("Indefinite-length contents too long"); } if (b == 0) { if (prevZeroByte) { // End of contents reached -- we've read the value and its terminator 0x00 0x00 return bytesRead - 2; } prevZeroByte = true; continue; } else { prevZeroByte = false; } } } private static int skipConstructedIndefiniteLengthContents(RecordingInputStream in) throws BerDataValueFormatException { // Contents are terminated by 0x00 0x00. However, this data value is constructed, meaning it // can contain data values which are indefinite length encoded as well. As a result, we // must parse the direct children of this data value to correctly skip over the contents of // this data value. int readByteCountBefore = in.getReadByteCount(); while (true) { // We can't easily peek for the 0x00 0x00 terminator using the provided InputStream. // Thus, we use the fact that 0x00 0x00 parses as a data value whose encoded form we // then check below to see whether it's 0x00 0x00. BerDataValue dataValue = readDataValue(in); if (dataValue == null) { throw new BerDataValueFormatException( "Truncated indefinite-length contents: " + (in.getReadByteCount() - readByteCountBefore) + " bytes read"); } if (in.getReadByteCount() <= 0) { throw new BerDataValueFormatException("Indefinite-length contents too long"); } ByteBuffer encoded = dataValue.getEncoded(); if ((encoded.remaining() == 2) && (encoded.get(0) == 0) && (encoded.get(1) == 0)) { // 0x00 0x00 encountered return in.getReadByteCount() - readByteCountBefore - 2; } } } private static class RecordingInputStream extends InputStream { private final InputStream mIn; private final ByteArrayOutputStream mBuf; private RecordingInputStream(InputStream in) { mIn = in; mBuf = new ByteArrayOutputStream(); } public byte[] getReadBytes() { return mBuf.toByteArray(); } public int getReadByteCount() { return mBuf.size(); } @Override public int read() throws IOException { int b = mIn.read(); if (b != -1) { mBuf.write(b); } return b; } @Override public int read(byte[] b) throws IOException { int len = mIn.read(b); if (len > 0) { mBuf.write(b, 0, len); } return len; } @Override public int read(byte[] b, int off, int len) throws IOException { len = mIn.read(b, off, len); if (len > 0) { mBuf.write(b, off, len); } return len; } @Override public long skip(long n) throws IOException { if (n <= 0) { return mIn.skip(n); } byte[] buf = new byte[4096]; int len = mIn.read(buf, 0, (int) Math.min(buf.length, n)); if (len > 0) { mBuf.write(buf, 0, len); } return (len < 0) ? 0 : len; } @Override public int available() throws IOException { return super.available(); } @Override public void close() throws IOException { super.close(); } @Override public synchronized void mark(int readlimit) {} @Override public synchronized void reset() throws IOException { throw new IOException("mark/reset not supported"); } @Override public boolean markSupported() { return false; } } } src/main/java/com/android/apksig/internal/jar/0040755 0000000 0000000 00000000000 13243353143 020311 5ustar000000000 0000000 src/main/java/com/android/apksig/internal/jar/ManifestParser.java0100644 0000000 0000000 00000026500 13243353143 024077 0ustar000000000 0000000 /* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.internal.jar; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.jar.Attributes; /** * JAR manifest and signature file parser. * *

These files consist of a main section followed by individual sections. Individual sections * are named, their names referring to JAR entries. * * @see JAR Manifest format */ public class ManifestParser { private final byte[] mManifest; private int mOffset; private int mEndOffset; private byte[] mBufferedLine; /** * Constructs a new {@code ManifestParser} with the provided input. */ public ManifestParser(byte[] data) { this(data, 0, data.length); } /** * Constructs a new {@code ManifestParser} with the provided input. */ public ManifestParser(byte[] data, int offset, int length) { mManifest = data; mOffset = offset; mEndOffset = offset + length; } /** * Returns the remaining sections of this file. */ public List

readAllSections() { List
sections = new ArrayList<>(); Section section; while ((section = readSection()) != null) { sections.add(section); } return sections; } /** * Returns the next section from this file or {@code null} if end of file has been reached. */ public Section readSection() { // Locate the first non-empty line int sectionStartOffset; String attr; do { sectionStartOffset = mOffset; attr = readAttribute(); if (attr == null) { return null; } } while (attr.length() == 0); List attrs = new ArrayList<>(); attrs.add(parseAttr(attr)); // Read attributes until end of section reached while (true) { attr = readAttribute(); if ((attr == null) || (attr.length() == 0)) { // End of section break; } attrs.add(parseAttr(attr)); } int sectionEndOffset = mOffset; int sectionSizeBytes = sectionEndOffset - sectionStartOffset; return new Section(sectionStartOffset, sectionSizeBytes, attrs); } private static Attribute parseAttr(String attr) { // Name is separated from value by a semicolon followed by a single SPACE character. // This permits trailing spaces in names and leading and trailing spaces in values. // Some APK obfuscators take advantage of this fact. We thus need to preserve these unusual // spaces to be able to parse such obfuscated APKs. int delimiterIndex = attr.indexOf(": "); if (delimiterIndex == -1) { return new Attribute(attr, ""); } else { return new Attribute( attr.substring(0, delimiterIndex), attr.substring(delimiterIndex + ": ".length())); } } /** * Returns the next attribute or empty {@code String} if end of section has been reached or * {@code null} if end of input has been reached. */ private String readAttribute() { byte[] bytes = readAttributeBytes(); if (bytes == null) { return null; } else if (bytes.length == 0) { return ""; } else { return new String(bytes, StandardCharsets.UTF_8); } } /** * Returns the next attribute or empty array if end of section has been reached or {@code null} * if end of input has been reached. */ private byte[] readAttributeBytes() { // Check whether end of section was reached during previous invocation if ((mBufferedLine != null) && (mBufferedLine.length == 0)) { mBufferedLine = null; return EMPTY_BYTE_ARRAY; } // Read the next line byte[] line = readLine(); if (line == null) { // End of input if (mBufferedLine != null) { byte[] result = mBufferedLine; mBufferedLine = null; return result; } return null; } // Consume the read line if (line.length == 0) { // End of section if (mBufferedLine != null) { byte[] result = mBufferedLine; mBufferedLine = EMPTY_BYTE_ARRAY; return result; } return EMPTY_BYTE_ARRAY; } byte[] attrLine; if (mBufferedLine == null) { attrLine = line; } else { if ((line.length == 0) || (line[0] != ' ')) { // The most common case: buffered line is a full attribute byte[] result = mBufferedLine; mBufferedLine = line; return result; } attrLine = mBufferedLine; mBufferedLine = null; attrLine = concat(attrLine, line, 1, line.length - 1); } // Everything's buffered in attrLine now. mBufferedLine is null // Read more lines while (true) { line = readLine(); if (line == null) { // End of input return attrLine; } else if (line.length == 0) { // End of section mBufferedLine = EMPTY_BYTE_ARRAY; // return "end of section" next time return attrLine; } if (line[0] == ' ') { // Continuation line attrLine = concat(attrLine, line, 1, line.length - 1); } else { // Next attribute mBufferedLine = line; return attrLine; } } } private static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; private static byte[] concat(byte[] arr1, byte[] arr2, int offset2, int length2) { byte[] result = new byte[arr1.length + length2]; System.arraycopy(arr1, 0, result, 0, arr1.length); System.arraycopy(arr2, offset2, result, arr1.length, length2); return result; } /** * Returns the next line (without line delimiter characters) or {@code null} if end of input has * been reached. */ private byte[] readLine() { if (mOffset >= mEndOffset) { return null; } int startOffset = mOffset; int newlineStartOffset = -1; int newlineEndOffset = -1; for (int i = startOffset; i < mEndOffset; i++) { byte b = mManifest[i]; if (b == '\r') { newlineStartOffset = i; int nextIndex = i + 1; if ((nextIndex < mEndOffset) && (mManifest[nextIndex] == '\n')) { newlineEndOffset = nextIndex + 1; break; } newlineEndOffset = nextIndex; break; } else if (b == '\n') { newlineStartOffset = i; newlineEndOffset = i + 1; break; } } if (newlineStartOffset == -1) { newlineStartOffset = mEndOffset; newlineEndOffset = mEndOffset; } mOffset = newlineEndOffset; if (newlineStartOffset == startOffset) { return EMPTY_BYTE_ARRAY; } return Arrays.copyOfRange(mManifest, startOffset, newlineStartOffset); } /** * Attribute. */ public static class Attribute { private final String mName; private final String mValue; /** * Constructs a new {@code Attribute} with the provided name and value. */ public Attribute(String name, String value) { mName = name; mValue = value; } /** * Returns this attribute's name. */ public String getName() { return mName; } /** * Returns this attribute's value. */ public String getValue() { return mValue; } } /** * Section. */ public static class Section { private final int mStartOffset; private final int mSizeBytes; private final String mName; private final List mAttributes; /** * Constructs a new {@code Section}. * * @param startOffset start offset (in bytes) of the section in the input file * @param sizeBytes size (in bytes) of the section in the input file * @param attrs attributes contained in the section */ public Section(int startOffset, int sizeBytes, List attrs) { mStartOffset = startOffset; mSizeBytes = sizeBytes; String sectionName = null; if (!attrs.isEmpty()) { Attribute firstAttr = attrs.get(0); if ("Name".equalsIgnoreCase(firstAttr.getName())) { sectionName = firstAttr.getValue(); } } mName = sectionName; mAttributes = Collections.unmodifiableList(new ArrayList<>(attrs)); } public String getName() { return mName; } /** * Returns the offset (in bytes) at which this section starts in the input. */ public int getStartOffset() { return mStartOffset; } /** * Returns the size (in bytes) of this section in the input. */ public int getSizeBytes() { return mSizeBytes; } /** * Returns this section's attributes, in the order in which they appear in the input. */ public List getAttributes() { return mAttributes; } /** * Returns the value of the specified attribute in this section or {@code null} if this * section does not contain a matching attribute. */ public String getAttributeValue(Attributes.Name name) { return getAttributeValue(name.toString()); } /** * Returns the value of the specified attribute in this section or {@code null} if this * section does not contain a matching attribute. * * @param name name of the attribute. Attribute names are case-insensitive. */ public String getAttributeValue(String name) { for (Attribute attr : mAttributes) { if (attr.getName().equalsIgnoreCase(name)) { return attr.getValue(); } } return null; } } } src/main/java/com/android/apksig/internal/jar/ManifestWriter.java0100644 0000000 0000000 00000011335 13243353143 024117 0ustar000000000 0000000 /* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.internal.jar; import java.io.IOException; import java.io.OutputStream; import java.nio.charset.StandardCharsets; import java.util.Map; import java.util.Set; import java.util.SortedMap; import java.util.TreeMap; import java.util.jar.Attributes; /** * Producer of {@code META-INF/MANIFEST.MF} file. * * @see JAR Manifest format */ public abstract class ManifestWriter { private static final byte[] CRLF = new byte[] {'\r', '\n'}; private static final int MAX_LINE_LENGTH = 70; private ManifestWriter() {} public static void writeMainSection(OutputStream out, Attributes attributes) throws IOException { // Main section must start with the Manifest-Version attribute. // See https://docs.oracle.com/javase/8/docs/technotes/guides/jar/jar.html#Signed_JAR_File. String manifestVersion = attributes.getValue(Attributes.Name.MANIFEST_VERSION); if (manifestVersion == null) { throw new IllegalArgumentException( "Mandatory " + Attributes.Name.MANIFEST_VERSION + " attribute missing"); } writeAttribute(out, Attributes.Name.MANIFEST_VERSION, manifestVersion); if (attributes.size() > 1) { SortedMap namedAttributes = getAttributesSortedByName(attributes); namedAttributes.remove(Attributes.Name.MANIFEST_VERSION.toString()); writeAttributes(out, namedAttributes); } writeSectionDelimiter(out); } public static void writeIndividualSection(OutputStream out, String name, Attributes attributes) throws IOException { writeAttribute(out, "Name", name); if (!attributes.isEmpty()) { writeAttributes(out, getAttributesSortedByName(attributes)); } writeSectionDelimiter(out); } static void writeSectionDelimiter(OutputStream out) throws IOException { out.write(CRLF); } static void writeAttribute(OutputStream out, Attributes.Name name, String value) throws IOException { writeAttribute(out, name.toString(), value); } private static void writeAttribute(OutputStream out, String name, String value) throws IOException { writeLine(out, name + ": " + value); } private static void writeLine(OutputStream out, String line) throws IOException { byte[] lineBytes = line.getBytes(StandardCharsets.UTF_8); int offset = 0; int remaining = lineBytes.length; boolean firstLine = true; while (remaining > 0) { int chunkLength; if (firstLine) { // First line chunkLength = Math.min(remaining, MAX_LINE_LENGTH); } else { // Continuation line out.write(CRLF); out.write(' '); chunkLength = Math.min(remaining, MAX_LINE_LENGTH - 1); } out.write(lineBytes, offset, chunkLength); offset += chunkLength; remaining -= chunkLength; firstLine = false; } out.write(CRLF); } static SortedMap getAttributesSortedByName(Attributes attributes) { Set> attributesEntries = attributes.entrySet(); SortedMap namedAttributes = new TreeMap(); for (Map.Entry attribute : attributesEntries) { String attrName = attribute.getKey().toString(); String attrValue = attribute.getValue().toString(); namedAttributes.put(attrName, attrValue); } return namedAttributes; } static void writeAttributes( OutputStream out, SortedMap attributesSortedByName) throws IOException { for (Map.Entry attribute : attributesSortedByName.entrySet()) { String attrName = attribute.getKey(); String attrValue = attribute.getValue(); writeAttribute(out, attrName, attrValue); } } } src/main/java/com/android/apksig/internal/jar/SignatureFileWriter.java0100644 0000000 0000000 00000004605 13243353143 025114 0ustar000000000 0000000 /* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.internal.jar; import java.io.IOException; import java.io.OutputStream; import java.util.SortedMap; import java.util.jar.Attributes; /** * Producer of JAR signature file ({@code *.SF}). * * @see JAR Manifest format */ public abstract class SignatureFileWriter { private SignatureFileWriter() {} public static void writeMainSection(OutputStream out, Attributes attributes) throws IOException { // Main section must start with the Signature-Version attribute. // See https://docs.oracle.com/javase/8/docs/technotes/guides/jar/jar.html#Signed_JAR_File. String signatureVersion = attributes.getValue(Attributes.Name.SIGNATURE_VERSION); if (signatureVersion == null) { throw new IllegalArgumentException( "Mandatory " + Attributes.Name.SIGNATURE_VERSION + " attribute missing"); } ManifestWriter.writeAttribute(out, Attributes.Name.SIGNATURE_VERSION, signatureVersion); if (attributes.size() > 1) { SortedMap namedAttributes = ManifestWriter.getAttributesSortedByName(attributes); namedAttributes.remove(Attributes.Name.SIGNATURE_VERSION.toString()); ManifestWriter.writeAttributes(out, namedAttributes); } writeSectionDelimiter(out); } public static void writeIndividualSection(OutputStream out, String name, Attributes attributes) throws IOException { ManifestWriter.writeIndividualSection(out, name, attributes); } public static void writeSectionDelimiter(OutputStream out) throws IOException { ManifestWriter.writeSectionDelimiter(out); } } src/main/java/com/android/apksig/internal/pkcs7/0040755 0000000 0000000 00000000000 13243353143 020564 5ustar000000000 0000000 src/main/java/com/android/apksig/internal/pkcs7/AlgorithmIdentifier.java0100644 0000000 0000000 00000002617 13243353143 025363 0ustar000000000 0000000 /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.internal.pkcs7; import com.android.apksig.internal.asn1.Asn1Class; import com.android.apksig.internal.asn1.Asn1Field; import com.android.apksig.internal.asn1.Asn1OpaqueObject; import com.android.apksig.internal.asn1.Asn1Type; /** * PKCS #7 {@code AlgorithmIdentifier} as specified in RFC 5652. */ @Asn1Class(type = Asn1Type.SEQUENCE) public class AlgorithmIdentifier { @Asn1Field(index = 0, type = Asn1Type.OBJECT_IDENTIFIER) public String algorithm; @Asn1Field(index = 1, type = Asn1Type.ANY, optional = true) public Asn1OpaqueObject parameters; public AlgorithmIdentifier() {} public AlgorithmIdentifier(String algorithmOid, Asn1OpaqueObject parameters) { this.algorithm = algorithmOid; this.parameters = parameters; } } src/main/java/com/android/apksig/internal/pkcs7/Attribute.java0100644 0000000 0000000 00000002275 13243353143 023375 0ustar000000000 0000000 /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.internal.pkcs7; import com.android.apksig.internal.asn1.Asn1Class; import com.android.apksig.internal.asn1.Asn1Field; import com.android.apksig.internal.asn1.Asn1OpaqueObject; import com.android.apksig.internal.asn1.Asn1Type; import java.util.List; /** * PKCS #7 {@code Attribute} as specified in RFC 5652. */ @Asn1Class(type = Asn1Type.SEQUENCE) public class Attribute { @Asn1Field(index = 0, type = Asn1Type.OBJECT_IDENTIFIER) public String attrType; @Asn1Field(index = 1, type = Asn1Type.SET_OF) public List attrValues; } src/main/java/com/android/apksig/internal/pkcs7/ContentInfo.java0100644 0000000 0000000 00000002405 13243353143 023653 0ustar000000000 0000000 /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.internal.pkcs7; import com.android.apksig.internal.asn1.Asn1Class; import com.android.apksig.internal.asn1.Asn1Field; import com.android.apksig.internal.asn1.Asn1OpaqueObject; import com.android.apksig.internal.asn1.Asn1Type; import com.android.apksig.internal.asn1.Asn1Tagging; /** * PKCS #7 {@code ContentInfo} as specified in RFC 5652. */ @Asn1Class(type = Asn1Type.SEQUENCE) public class ContentInfo { @Asn1Field(index = 1, type = Asn1Type.OBJECT_IDENTIFIER) public String contentType; @Asn1Field(index = 2, type = Asn1Type.ANY, tagging = Asn1Tagging.EXPLICIT, tagNumber = 0) public Asn1OpaqueObject content; } src/main/java/com/android/apksig/internal/pkcs7/EncapsulatedContentInfo.java0100644 0000000 0000000 00000002726 13243353143 026212 0ustar000000000 0000000 /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.internal.pkcs7; import com.android.apksig.internal.asn1.Asn1Class; import com.android.apksig.internal.asn1.Asn1Field; import com.android.apksig.internal.asn1.Asn1Type; import com.android.apksig.internal.asn1.Asn1Tagging; import java.nio.ByteBuffer; /** * PKCS #7 {@code EncapsulatedContentInfo} as specified in RFC 5652. */ @Asn1Class(type = Asn1Type.SEQUENCE) public class EncapsulatedContentInfo { @Asn1Field(index = 0, type = Asn1Type.OBJECT_IDENTIFIER) public String contentType; @Asn1Field( index = 1, type = Asn1Type.OCTET_STRING, tagging = Asn1Tagging.EXPLICIT, tagNumber = 0, optional = true) public ByteBuffer content; public EncapsulatedContentInfo() {} public EncapsulatedContentInfo(String contentTypeOid) { contentType = contentTypeOid; } } src/main/java/com/android/apksig/internal/pkcs7/IssuerAndSerialNumber.java0100644 0000000 0000000 00000002703 13243353143 025634 0ustar000000000 0000000 /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.internal.pkcs7; import com.android.apksig.internal.asn1.Asn1Class; import com.android.apksig.internal.asn1.Asn1Field; import com.android.apksig.internal.asn1.Asn1OpaqueObject; import com.android.apksig.internal.asn1.Asn1Type; import java.math.BigInteger; /** * PKCS #7 {@code IssuerAndSerialNumber} as specified in RFC 5652. */ @Asn1Class(type = Asn1Type.SEQUENCE) public class IssuerAndSerialNumber { @Asn1Field(index = 0, type = Asn1Type.ANY) public Asn1OpaqueObject issuer; @Asn1Field(index = 1, type = Asn1Type.INTEGER) public BigInteger certificateSerialNumber; public IssuerAndSerialNumber() {} public IssuerAndSerialNumber(Asn1OpaqueObject issuer, BigInteger certificateSerialNumber) { this.issuer = issuer; this.certificateSerialNumber = certificateSerialNumber; } } src/main/java/com/android/apksig/internal/pkcs7/Pkcs7Constants.java0100644 0000000 0000000 00000002071 13243353143 024310 0ustar000000000 0000000 /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.internal.pkcs7; /** * Assorted PKCS #7 constants from RFC 5652. */ public abstract class Pkcs7Constants { private Pkcs7Constants() {} public static final String OID_DATA = "1.2.840.113549.1.7.1"; public static final String OID_SIGNED_DATA = "1.2.840.113549.1.7.2"; public static final String OID_CONTENT_TYPE = "1.2.840.113549.1.9.3"; public static final String OID_MESSAGE_DIGEST = "1.2.840.113549.1.9.4"; } src/main/java/com/android/apksig/internal/pkcs7/Pkcs7DecodingException.java0100644 0000000 0000000 00000002035 13243353143 025727 0ustar000000000 0000000 /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.internal.pkcs7; /** * Indicates that an error was encountered while decoding a PKCS #7 structure. */ public class Pkcs7DecodingException extends Exception { private static final long serialVersionUID = 1L; public Pkcs7DecodingException(String message) { super(message); } public Pkcs7DecodingException(String message, Throwable cause) { super(message, cause); } } src/main/java/com/android/apksig/internal/pkcs7/SignedData.java0100644 0000000 0000000 00000003553 13243353143 023435 0ustar000000000 0000000 /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.internal.pkcs7; import com.android.apksig.internal.asn1.Asn1Class; import com.android.apksig.internal.asn1.Asn1Field; import com.android.apksig.internal.asn1.Asn1OpaqueObject; import com.android.apksig.internal.asn1.Asn1Type; import com.android.apksig.internal.asn1.Asn1Tagging; import java.nio.ByteBuffer; import java.util.List; /** * PKCS #7 {@code SignedData} as specified in RFC 5652. */ @Asn1Class(type = Asn1Type.SEQUENCE) public class SignedData { @Asn1Field(index = 0, type = Asn1Type.INTEGER) public int version; @Asn1Field(index = 1, type = Asn1Type.SET_OF) public List digestAlgorithms; @Asn1Field(index = 2, type = Asn1Type.SEQUENCE) public EncapsulatedContentInfo encapContentInfo; @Asn1Field( index = 3, type = Asn1Type.SET_OF, tagging = Asn1Tagging.IMPLICIT, tagNumber = 0, optional = true) public List certificates; @Asn1Field( index = 4, type = Asn1Type.SET_OF, tagging = Asn1Tagging.IMPLICIT, tagNumber = 1, optional = true) public List crls; @Asn1Field(index = 5, type = Asn1Type.SET_OF) public List signerInfos; } src/main/java/com/android/apksig/internal/pkcs7/SignerIdentifier.java0100644 0000000 0000000 00000002651 13243353143 024662 0ustar000000000 0000000 /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.internal.pkcs7; import com.android.apksig.internal.asn1.Asn1Class; import com.android.apksig.internal.asn1.Asn1Field; import com.android.apksig.internal.asn1.Asn1Type; import com.android.apksig.internal.asn1.Asn1Tagging; import java.nio.ByteBuffer; /** * PKCS #7 {@code SignerIdentifier} as specified in RFC 5652. */ @Asn1Class(type = Asn1Type.CHOICE) public class SignerIdentifier { @Asn1Field(type = Asn1Type.SEQUENCE) public IssuerAndSerialNumber issuerAndSerialNumber; @Asn1Field(type = Asn1Type.OCTET_STRING, tagging = Asn1Tagging.IMPLICIT, tagNumber = 0) public ByteBuffer subjectKeyIdentifier; public SignerIdentifier() {} public SignerIdentifier(IssuerAndSerialNumber issuerAndSerialNumber) { this.issuerAndSerialNumber = issuerAndSerialNumber; } } src/main/java/com/android/apksig/internal/pkcs7/SignerInfo.java0100644 0000000 0000000 00000003667 13243353143 023503 0ustar000000000 0000000 /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.internal.pkcs7; import com.android.apksig.internal.asn1.Asn1Class; import com.android.apksig.internal.asn1.Asn1Field; import com.android.apksig.internal.asn1.Asn1OpaqueObject; import com.android.apksig.internal.asn1.Asn1Type; import com.android.apksig.internal.asn1.Asn1Tagging; import java.nio.ByteBuffer; import java.util.List; /** * PKCS #7 {@code SignerInfo} as specified in RFC 5652. */ @Asn1Class(type = Asn1Type.SEQUENCE) public class SignerInfo { @Asn1Field(index = 0, type = Asn1Type.INTEGER) public int version; @Asn1Field(index = 1, type = Asn1Type.CHOICE) public SignerIdentifier sid; @Asn1Field(index = 2, type = Asn1Type.SEQUENCE) public AlgorithmIdentifier digestAlgorithm; @Asn1Field( index = 3, type = Asn1Type.SET_OF, tagging = Asn1Tagging.IMPLICIT, tagNumber = 0, optional = true) public Asn1OpaqueObject signedAttrs; @Asn1Field(index = 4, type = Asn1Type.SEQUENCE) public AlgorithmIdentifier signatureAlgorithm; @Asn1Field(index = 5, type = Asn1Type.OCTET_STRING) public ByteBuffer signature; @Asn1Field( index = 6, type = Asn1Type.SET_OF, tagging = Asn1Tagging.IMPLICIT, tagNumber = 1, optional = true) public List unsignedAttrs; } src/main/java/com/android/apksig/internal/util/0040755 0000000 0000000 00000000000 13243353143 020512 5ustar000000000 0000000 src/main/java/com/android/apksig/internal/util/AndroidSdkVersion.java0100644 0000000 0000000 00000002540 13243353143 024743 0ustar000000000 0000000 /* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.internal.util; /** * Android SDK version / API Level constants. */ public abstract class AndroidSdkVersion { /** Hidden constructor to prevent instantiation. */ private AndroidSdkVersion() {} /** Android 2.3. */ public static final int GINGERBREAD = 9; /** Android 4.3. The revenge of the beans. */ public static final int JELLY_BEAN_MR2 = 18; /** Android 4.4. KitKat, another tasty treat. */ public static final int KITKAT = 19; /** Android 5.0. A flat one with beautiful shadows. But still tasty. */ public static final int LOLLIPOP = 21; /** Android 7.0. N is for Nougat. */ public static final int N = 24; /** Android O. */ public static final int O = 26; } src/main/java/com/android/apksig/internal/util/ByteArrayDataSink.java0100644 0000000 0000000 00000021030 13243353143 024667 0ustar000000000 0000000 /* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.internal.util; import com.android.apksig.util.DataSink; import com.android.apksig.util.DataSource; import com.android.apksig.util.ReadableDataSink; import java.io.IOException; import java.nio.ByteBuffer; import java.util.Arrays; /** * Growable byte array which can be appended to via {@link DataSink} interface and read from via * {@link DataSource} interface. */ public class ByteArrayDataSink implements ReadableDataSink { private static final int MAX_READ_CHUNK_SIZE = 65536; private byte[] mArray; private int mSize; public ByteArrayDataSink() { this(65536); } public ByteArrayDataSink(int initialCapacity) { if (initialCapacity < 0) { throw new IllegalArgumentException("initial capacity: " + initialCapacity); } mArray = new byte[initialCapacity]; } @Override public void consume(byte[] buf, int offset, int length) throws IOException { if (offset < 0) { // Must perform this check because System.arraycopy below doesn't perform it when // length == 0 throw new IndexOutOfBoundsException("offset: " + offset); } if (offset > buf.length) { // Must perform this check because System.arraycopy below doesn't perform it when // length == 0 throw new IndexOutOfBoundsException( "offset: " + offset + ", buf.length: " + buf.length); } if (length == 0) { return; } ensureAvailable(length); System.arraycopy(buf, offset, mArray, mSize, length); mSize += length; } @Override public void consume(ByteBuffer buf) throws IOException { if (!buf.hasRemaining()) { return; } if (buf.hasArray()) { consume(buf.array(), buf.arrayOffset() + buf.position(), buf.remaining()); buf.position(buf.limit()); return; } ensureAvailable(buf.remaining()); byte[] tmp = new byte[Math.min(buf.remaining(), MAX_READ_CHUNK_SIZE)]; while (buf.hasRemaining()) { int chunkSize = Math.min(buf.remaining(), tmp.length); buf.get(tmp, 0, chunkSize); System.arraycopy(tmp, 0, mArray, mSize, chunkSize); mSize += chunkSize; } } private void ensureAvailable(int minAvailable) throws IOException { if (minAvailable <= 0) { return; } long minCapacity = ((long) mSize) + minAvailable; if (minCapacity <= mArray.length) { return; } if (minCapacity > Integer.MAX_VALUE) { throw new IOException( "Required capacity too large: " + minCapacity + ", max: " + Integer.MAX_VALUE); } int doubleCurrentSize = (int) Math.min(mArray.length * 2L, Integer.MAX_VALUE); int newSize = (int) Math.max(minCapacity, doubleCurrentSize); mArray = Arrays.copyOf(mArray, newSize); } @Override public long size() { return mSize; } @Override public ByteBuffer getByteBuffer(long offset, int size) { checkChunkValid(offset, size); // checkChunkValid ensures that it's OK to cast offset to int. return ByteBuffer.wrap(mArray, (int) offset, size).slice(); } @Override public void feed(long offset, long size, DataSink sink) throws IOException { checkChunkValid(offset, size); // checkChunkValid ensures that it's OK to cast offset and size to int. sink.consume(mArray, (int) offset, (int) size); } @Override public void copyTo(long offset, int size, ByteBuffer dest) throws IOException { checkChunkValid(offset, size); // checkChunkValid ensures that it's OK to cast offset to int. dest.put(mArray, (int) offset, size); } private void checkChunkValid(long offset, long size) { if (offset < 0) { throw new IndexOutOfBoundsException("offset: " + offset); } if (size < 0) { throw new IndexOutOfBoundsException("size: " + size); } if (offset > mSize) { throw new IndexOutOfBoundsException( "offset (" + offset + ") > source size (" + mSize + ")"); } long endOffset = offset + size; if (endOffset < offset) { throw new IndexOutOfBoundsException( "offset (" + offset + ") + size (" + size + ") overflow"); } if (endOffset > mSize) { throw new IndexOutOfBoundsException( "offset (" + offset + ") + size (" + size + ") > source size (" + mSize + ")"); } } @Override public DataSource slice(long offset, long size) { checkChunkValid(offset, size); // checkChunkValid ensures that it's OK to cast offset and size to int. return new SliceDataSource((int) offset, (int) size); } /** * Slice of the growable byte array. The slice's offset and size in the array are fixed. */ private class SliceDataSource implements DataSource { private final int mSliceOffset; private final int mSliceSize; private SliceDataSource(int offset, int size) { mSliceOffset = offset; mSliceSize = size; } @Override public long size() { return mSliceSize; } @Override public void feed(long offset, long size, DataSink sink) throws IOException { checkChunkValid(offset, size); // checkChunkValid combined with the way instances of this class are constructed ensures // that mSliceOffset + offset does not overflow and that it's fine to cast size to int. sink.consume(mArray, (int) (mSliceOffset + offset), (int) size); } @Override public ByteBuffer getByteBuffer(long offset, int size) throws IOException { checkChunkValid(offset, size); // checkChunkValid combined with the way instances of this class are constructed ensures // that mSliceOffset + offset does not overflow. return ByteBuffer.wrap(mArray, (int) (mSliceOffset + offset), size).slice(); } @Override public void copyTo(long offset, int size, ByteBuffer dest) throws IOException { checkChunkValid(offset, size); // checkChunkValid combined with the way instances of this class are constructed ensures // that mSliceOffset + offset does not overflow. dest.put(mArray, (int) (mSliceOffset + offset), size); } @Override public DataSource slice(long offset, long size) { checkChunkValid(offset, size); // checkChunkValid combined with the way instances of this class are constructed ensures // that mSliceOffset + offset does not overflow and that it's fine to cast size to int. return new SliceDataSource((int) (mSliceOffset + offset), (int) size); } private void checkChunkValid(long offset, long size) { if (offset < 0) { throw new IndexOutOfBoundsException("offset: " + offset); } if (size < 0) { throw new IndexOutOfBoundsException("size: " + size); } if (offset > mSliceSize) { throw new IndexOutOfBoundsException( "offset (" + offset + ") > source size (" + mSliceSize + ")"); } long endOffset = offset + size; if (endOffset < offset) { throw new IndexOutOfBoundsException( "offset (" + offset + ") + size (" + size + ") overflow"); } if (endOffset > mSliceSize) { throw new IndexOutOfBoundsException( "offset (" + offset + ") + size (" + size + ") > source size (" + mSliceSize + ")"); } } } } src/main/java/com/android/apksig/internal/util/ByteBufferDataSource.java0100644 0000000 0000000 00000010646 13243353143 025371 0ustar000000000 0000000 /* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.internal.util; import com.android.apksig.util.DataSink; import com.android.apksig.util.DataSource; import java.io.IOException; import java.nio.ByteBuffer; /** * {@link DataSource} backed by a {@link ByteBuffer}. */ public class ByteBufferDataSource implements DataSource { private final ByteBuffer mBuffer; private final int mSize; /** * Constructs a new {@code ByteBufferDigestSource} based on the data contained in the provided * buffer between the buffer's position and limit. */ public ByteBufferDataSource(ByteBuffer buffer) { this(buffer, true); } /** * Constructs a new {@code ByteBufferDigestSource} based on the data contained in the provided * buffer between the buffer's position and limit. */ private ByteBufferDataSource(ByteBuffer buffer, boolean sliceRequired) { mBuffer = (sliceRequired) ? buffer.slice() : buffer; mSize = buffer.remaining(); } @Override public long size() { return mSize; } @Override public ByteBuffer getByteBuffer(long offset, int size) { checkChunkValid(offset, size); // checkChunkValid ensures that it's OK to cast offset to int. int chunkPosition = (int) offset; int chunkLimit = chunkPosition + size; // Creating a slice of ByteBuffer modifies the state of the source ByteBuffer (position // and limit fields, to be more specific). We thus use synchronization around these // state-changing operations to make instances of this class thread-safe. synchronized (mBuffer) { // ByteBuffer.limit(int) and .position(int) check that that the position >= limit // invariant is not broken. Thus, the only way to safely change position and limit // without caring about their current values is to first set position to 0 or set the // limit to capacity. mBuffer.position(0); mBuffer.limit(chunkLimit); mBuffer.position(chunkPosition); return mBuffer.slice(); } } @Override public void copyTo(long offset, int size, ByteBuffer dest) { dest.put(getByteBuffer(offset, size)); } @Override public void feed(long offset, long size, DataSink sink) throws IOException { if ((size < 0) || (size > mSize)) { throw new IndexOutOfBoundsException("size: " + size + ", source size: " + mSize); } sink.consume(getByteBuffer(offset, (int) size)); } @Override public ByteBufferDataSource slice(long offset, long size) { if ((offset == 0) && (size == mSize)) { return this; } if ((size < 0) || (size > mSize)) { throw new IndexOutOfBoundsException("size: " + size + ", source size: " + mSize); } return new ByteBufferDataSource( getByteBuffer(offset, (int) size), false // no need to slice -- it's already a slice ); } private void checkChunkValid(long offset, long size) { if (offset < 0) { throw new IndexOutOfBoundsException("offset: " + offset); } if (size < 0) { throw new IndexOutOfBoundsException("size: " + size); } if (offset > mSize) { throw new IndexOutOfBoundsException( "offset (" + offset + ") > source size (" + mSize + ")"); } long endOffset = offset + size; if (endOffset < offset) { throw new IndexOutOfBoundsException( "offset (" + offset + ") + size (" + size + ") overflow"); } if (endOffset > mSize) { throw new IndexOutOfBoundsException( "offset (" + offset + ") + size (" + size + ") > source size (" + mSize +")"); } } } src/main/java/com/android/apksig/internal/util/ByteBufferSink.java0100644 0000000 0000000 00000003431 13243353143 024235 0ustar000000000 0000000 /* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.internal.util; import com.android.apksig.util.DataSink; import java.io.IOException; import java.nio.BufferOverflowException; import java.nio.ByteBuffer; /** * Data sink which stores all received data into the associated {@link ByteBuffer}. */ public class ByteBufferSink implements DataSink { private final ByteBuffer mBuffer; public ByteBufferSink(ByteBuffer buffer) { mBuffer = buffer; } public ByteBuffer getBuffer() { return mBuffer; } @Override public void consume(byte[] buf, int offset, int length) throws IOException { try { mBuffer.put(buf, offset, length); } catch (BufferOverflowException e) { throw new IOException( "Insufficient space in output buffer for " + length + " bytes", e); } } @Override public void consume(ByteBuffer buf) throws IOException { int length = buf.remaining(); try { mBuffer.put(buf); } catch (BufferOverflowException e) { throw new IOException( "Insufficient space in output buffer for " + length + " bytes", e); } } } src/main/java/com/android/apksig/internal/util/ByteBufferUtils.java0100644 0000000 0000000 00000002101 13243353143 024422 0ustar000000000 0000000 /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.internal.util; import java.nio.ByteBuffer; public final class ByteBufferUtils { private ByteBufferUtils() {} /** * Returns the remaining data of the provided buffer as a new byte array and advances the * position of the buffer to the buffer's limit. */ public static byte[] toByteArray(ByteBuffer buf) { byte[] result = new byte[buf.remaining()]; buf.get(result); return result; } } src/main/java/com/android/apksig/internal/util/DelegatingX509Certificate.java0100644 0000000 0000000 00000013533 13243353143 026153 0ustar000000000 0000000 /* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.internal.util; import java.math.BigInteger; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.Principal; import java.security.Provider; import java.security.PublicKey; import java.security.SignatureException; import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; import java.security.cert.CertificateExpiredException; import java.security.cert.CertificateNotYetValidException; import java.security.cert.CertificateParsingException; import java.security.cert.X509Certificate; import java.util.Collection; import java.util.Date; import java.util.List; import java.util.Set; import javax.security.auth.x500.X500Principal; /** * {@link X509Certificate} which delegates all method invocations to the provided delegate * {@code X509Certificate}. */ public class DelegatingX509Certificate extends X509Certificate { private final X509Certificate mDelegate; public DelegatingX509Certificate(X509Certificate delegate) { this.mDelegate = delegate; } @Override public Set getCriticalExtensionOIDs() { return mDelegate.getCriticalExtensionOIDs(); } @Override public byte[] getExtensionValue(String oid) { return mDelegate.getExtensionValue(oid); } @Override public Set getNonCriticalExtensionOIDs() { return mDelegate.getNonCriticalExtensionOIDs(); } @Override public boolean hasUnsupportedCriticalExtension() { return mDelegate.hasUnsupportedCriticalExtension(); } @Override public void checkValidity() throws CertificateExpiredException, CertificateNotYetValidException { mDelegate.checkValidity(); } @Override public void checkValidity(Date date) throws CertificateExpiredException, CertificateNotYetValidException { mDelegate.checkValidity(date); } @Override public int getVersion() { return mDelegate.getVersion(); } @Override public BigInteger getSerialNumber() { return mDelegate.getSerialNumber(); } @Override public Principal getIssuerDN() { return mDelegate.getIssuerDN(); } @Override public Principal getSubjectDN() { return mDelegate.getSubjectDN(); } @Override public Date getNotBefore() { return mDelegate.getNotBefore(); } @Override public Date getNotAfter() { return mDelegate.getNotAfter(); } @Override public byte[] getTBSCertificate() throws CertificateEncodingException { return mDelegate.getTBSCertificate(); } @Override public byte[] getSignature() { return mDelegate.getSignature(); } @Override public String getSigAlgName() { return mDelegate.getSigAlgName(); } @Override public String getSigAlgOID() { return mDelegate.getSigAlgOID(); } @Override public byte[] getSigAlgParams() { return mDelegate.getSigAlgParams(); } @Override public boolean[] getIssuerUniqueID() { return mDelegate.getIssuerUniqueID(); } @Override public boolean[] getSubjectUniqueID() { return mDelegate.getSubjectUniqueID(); } @Override public boolean[] getKeyUsage() { return mDelegate.getKeyUsage(); } @Override public int getBasicConstraints() { return mDelegate.getBasicConstraints(); } @Override public byte[] getEncoded() throws CertificateEncodingException { return mDelegate.getEncoded(); } @Override public void verify(PublicKey key) throws CertificateException, NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException, SignatureException { mDelegate.verify(key); } @Override public void verify(PublicKey key, String sigProvider) throws CertificateException, NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException, SignatureException { mDelegate.verify(key, sigProvider); } @Override public String toString() { return mDelegate.toString(); } @Override public PublicKey getPublicKey() { return mDelegate.getPublicKey(); } @Override public X500Principal getIssuerX500Principal() { return mDelegate.getIssuerX500Principal(); } @Override public X500Principal getSubjectX500Principal() { return mDelegate.getSubjectX500Principal(); } @Override public List getExtendedKeyUsage() throws CertificateParsingException { return mDelegate.getExtendedKeyUsage(); } @Override public Collection> getSubjectAlternativeNames() throws CertificateParsingException { return mDelegate.getSubjectAlternativeNames(); } @Override public Collection> getIssuerAlternativeNames() throws CertificateParsingException { return mDelegate.getIssuerAlternativeNames(); } @Override public void verify(PublicKey key, Provider sigProvider) throws CertificateException, NoSuchAlgorithmException, InvalidKeyException, SignatureException { mDelegate.verify(key, sigProvider); } } src/main/java/com/android/apksig/internal/util/GuaranteedEncodedFormX509Certificate.java0100644 0000000 0000000 00000002532 13243353143 030272 0ustar000000000 0000000 /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.internal.util; import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; /** * {@link X509Certificate} whose {@link #getEncoded()} returns the data provided at construction * time. */ public class GuaranteedEncodedFormX509Certificate extends DelegatingX509Certificate { private final byte[] mEncodedForm; public GuaranteedEncodedFormX509Certificate(X509Certificate wrapped, byte[] encodedForm) { super(wrapped); this.mEncodedForm = (encodedForm != null) ? encodedForm.clone() : null; } @Override public byte[] getEncoded() throws CertificateEncodingException { return (mEncodedForm != null) ? mEncodedForm.clone() : null; } } src/main/java/com/android/apksig/internal/util/InclusiveIntRange.java0100644 0000000 0000000 00000005135 13243353143 024747 0ustar000000000 0000000 /* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.internal.util; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** * Inclusive interval of integers. */ public class InclusiveIntRange { private final int min; private final int max; private InclusiveIntRange(int min, int max) { this.min = min; this.max = max; } public int getMin() { return min; } public int getMax() { return max; } public static InclusiveIntRange fromTo(int min, int max) { return new InclusiveIntRange(min, max); } public static InclusiveIntRange from(int min) { return new InclusiveIntRange(min, Integer.MAX_VALUE); } public List getValuesNotIn( List sortedNonOverlappingRanges) { if (sortedNonOverlappingRanges.isEmpty()) { return Collections.singletonList(this); } int testValue = min; List result = null; for (InclusiveIntRange range : sortedNonOverlappingRanges) { int rangeMax = range.max; if (testValue > rangeMax) { continue; } int rangeMin = range.min; if (testValue < range.min) { if (result == null) { result = new ArrayList<>(); } result.add(fromTo(testValue, rangeMin - 1)); } if (rangeMax >= max) { return (result != null) ? result : Collections.emptyList(); } testValue = rangeMax + 1; } if (testValue <= max) { if (result == null) { result = new ArrayList<>(1); } result.add(fromTo(testValue, max)); } return (result != null) ? result : Collections.emptyList(); } @Override public String toString() { return "[" + min + ", " + ((max < Integer.MAX_VALUE) ? (max + "]") : "\u221e)"); } } src/main/java/com/android/apksig/internal/util/MessageDigestSink.java0100644 0000000 0000000 00000003321 13243353143 024722 0ustar000000000 0000000 /* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.internal.util; import com.android.apksig.util.DataSink; import java.nio.ByteBuffer; import java.security.MessageDigest; /** * Data sink which feeds all received data into the associated {@link MessageDigest} instances. Each * {@code MessageDigest} instance receives the same data. */ public class MessageDigestSink implements DataSink { private final MessageDigest[] mMessageDigests; public MessageDigestSink(MessageDigest[] digests) { mMessageDigests = digests; } @Override public void consume(byte[] buf, int offset, int length) { for (MessageDigest md : mMessageDigests) { md.update(buf, offset, length); } } @Override public void consume(ByteBuffer buf) { int originalPosition = buf.position(); for (MessageDigest md : mMessageDigests) { // Reset the position back to the original because the previous iteration's // MessageDigest.update set the buffer's position to the buffer's limit. buf.position(originalPosition); md.update(buf); } } } src/main/java/com/android/apksig/internal/util/OutputStreamDataSink.java0100644 0000000 0000000 00000004506 13243353143 025452 0ustar000000000 0000000 /* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.internal.util; import com.android.apksig.util.DataSink; import java.io.IOException; import java.io.OutputStream; import java.nio.ByteBuffer; /** * {@link DataSink} which outputs received data into the associated {@link OutputStream}. */ public class OutputStreamDataSink implements DataSink { private static final int MAX_READ_CHUNK_SIZE = 65536; private final OutputStream mOut; /** * Constructs a new {@code OutputStreamDataSink} which outputs received data into the provided * {@link OutputStream}. */ public OutputStreamDataSink(OutputStream out) { if (out == null) { throw new NullPointerException("out == null"); } mOut = out; } /** * Returns {@link OutputStream} into which this data sink outputs received data. */ public OutputStream getOutputStream() { return mOut; } @Override public void consume(byte[] buf, int offset, int length) throws IOException { mOut.write(buf, offset, length); } @Override public void consume(ByteBuffer buf) throws IOException { if (!buf.hasRemaining()) { return; } if (buf.hasArray()) { mOut.write( buf.array(), buf.arrayOffset() + buf.position(), buf.remaining()); buf.position(buf.limit()); } else { byte[] tmp = new byte[Math.min(buf.remaining(), MAX_READ_CHUNK_SIZE)]; while (buf.hasRemaining()) { int chunkSize = Math.min(buf.remaining(), tmp.length); buf.get(tmp, 0, chunkSize); mOut.write(tmp, 0, chunkSize); } } } } src/main/java/com/android/apksig/internal/util/Pair.java0100644 0000000 0000000 00000004156 13243353143 022253 0ustar000000000 0000000 /* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.internal.util; /** * Pair of two elements. */ public final class Pair { private final A mFirst; private final B mSecond; private Pair(A first, B second) { mFirst = first; mSecond = second; } public static Pair of(A first, B second) { return new Pair(first, second); } public A getFirst() { return mFirst; } public B getSecond() { return mSecond; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((mFirst == null) ? 0 : mFirst.hashCode()); result = prime * result + ((mSecond == null) ? 0 : mSecond.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } @SuppressWarnings("rawtypes") Pair other = (Pair) obj; if (mFirst == null) { if (other.mFirst != null) { return false; } } else if (!mFirst.equals(other.mFirst)) { return false; } if (mSecond == null) { if (other.mSecond != null) { return false; } } else if (!mSecond.equals(other.mSecond)) { return false; } return true; } } src/main/java/com/android/apksig/internal/util/RandomAccessFileDataSink.java0100644 0000000 0000000 00000006367 13243353143 026147 0ustar000000000 0000000 /* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.internal.util; import com.android.apksig.util.DataSink; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; /** * {@link DataSink} which outputs received data into the associated file, sequentially. */ public class RandomAccessFileDataSink implements DataSink { private final RandomAccessFile mFile; private final FileChannel mFileChannel; private long mPosition; /** * Constructs a new {@code RandomAccessFileDataSink} which stores output starting from the * beginning of the provided file. */ public RandomAccessFileDataSink(RandomAccessFile file) { this(file, 0); } /** * Constructs a new {@code RandomAccessFileDataSink} which stores output starting from the * specified position of the provided file. */ public RandomAccessFileDataSink(RandomAccessFile file, long startPosition) { if (file == null) { throw new NullPointerException("file == null"); } if (startPosition < 0) { throw new IllegalArgumentException("startPosition: " + startPosition); } mFile = file; mFileChannel = file.getChannel(); mPosition = startPosition; } /** * Returns the underlying {@link RandomAccessFile}. */ public RandomAccessFile getFile() { return mFile; } @Override public void consume(byte[] buf, int offset, int length) throws IOException { if (offset < 0) { // Must perform this check here because RandomAccessFile.write doesn't throw when offset // is negative but length is 0 throw new IndexOutOfBoundsException("offset: " + offset); } if (offset > buf.length) { // Must perform this check here because RandomAccessFile.write doesn't throw when offset // is too large but length is 0 throw new IndexOutOfBoundsException( "offset: " + offset + ", buf.length: " + buf.length); } if (length == 0) { return; } synchronized (mFile) { mFile.seek(mPosition); mFile.write(buf, offset, length); mPosition += length; } } @Override public void consume(ByteBuffer buf) throws IOException { int length = buf.remaining(); if (length == 0) { return; } synchronized (mFile) { mFile.seek(mPosition); while (buf.hasRemaining()) { mFileChannel.write(buf); } mPosition += length; } } } src/main/java/com/android/apksig/internal/util/RandomAccessFileDataSource.java0100644 0000000 0000000 00000013717 13243353143 026500 0ustar000000000 0000000 /* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.internal.util; import com.android.apksig.util.DataSink; import com.android.apksig.util.DataSource; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.BufferOverflowException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; /** * {@link DataSource} backed by a {@link RandomAccessFile}. */ public class RandomAccessFileDataSource implements DataSource { private static final int MAX_READ_CHUNK_SIZE = 65536; private final RandomAccessFile mFile; private final long mOffset; private final long mSize; /** * Constructs a new {@code RandomAccessFileDataSource} based on the data contained in the * specified the whole file. Changes to the contents of the file, including the size of the * file, will be visible in this data source. */ public RandomAccessFileDataSource(RandomAccessFile file) { mFile = file; mOffset = 0; mSize = -1; } /** * Constructs a new {@code RandomAccessFileDataSource} based on the data contained in the * specified region of the provided file. Changes to the contents of the file will be visible in * this data source. * * @throws IndexOutOfBoundsException if {@code offset} or {@code size} is negative. */ public RandomAccessFileDataSource(RandomAccessFile file, long offset, long size) { if (offset < 0) { throw new IndexOutOfBoundsException("offset: " + size); } if (size < 0) { throw new IndexOutOfBoundsException("size: " + size); } mFile = file; mOffset = offset; mSize = size; } @Override public long size() { if (mSize == -1) { try { return mFile.length(); } catch (IOException e) { return 0; } } else { return mSize; } } @Override public RandomAccessFileDataSource slice(long offset, long size) { long sourceSize = size(); checkChunkValid(offset, size, sourceSize); if ((offset == 0) && (size == sourceSize)) { return this; } return new RandomAccessFileDataSource(mFile, mOffset + offset, size); } @Override public void feed(long offset, long size, DataSink sink) throws IOException { long sourceSize = size(); checkChunkValid(offset, size, sourceSize); if (size == 0) { return; } long chunkOffsetInFile = mOffset + offset; long remaining = size; byte[] buf = new byte[(int) Math.min(remaining, MAX_READ_CHUNK_SIZE)]; while (remaining > 0) { int chunkSize = (int) Math.min(remaining, buf.length); synchronized (mFile) { mFile.seek(chunkOffsetInFile); mFile.readFully(buf, 0, chunkSize); } sink.consume(buf, 0, chunkSize); chunkOffsetInFile += chunkSize; remaining -= chunkSize; } } @Override public void copyTo(long offset, int size, ByteBuffer dest) throws IOException { long sourceSize = size(); checkChunkValid(offset, size, sourceSize); if (size == 0) { return; } if (size > dest.remaining()) { throw new BufferOverflowException(); } long offsetInFile = mOffset + offset; int remaining = size; int prevLimit = dest.limit(); try { // FileChannel.read(ByteBuffer) reads up to dest.remaining(). Thus, we need to adjust // the buffer's limit to avoid reading more than size bytes. dest.limit(dest.position() + size); FileChannel fileChannel = mFile.getChannel(); while (remaining > 0) { int chunkSize; synchronized (mFile) { fileChannel.position(offsetInFile); chunkSize = fileChannel.read(dest); } offsetInFile += chunkSize; remaining -= chunkSize; } } finally { dest.limit(prevLimit); } } @Override public ByteBuffer getByteBuffer(long offset, int size) throws IOException { if (size < 0) { throw new IndexOutOfBoundsException("size: " + size); } ByteBuffer result = ByteBuffer.allocate(size); copyTo(offset, size, result); result.flip(); return result; } private static void checkChunkValid(long offset, long size, long sourceSize) { if (offset < 0) { throw new IndexOutOfBoundsException("offset: " + offset); } if (size < 0) { throw new IndexOutOfBoundsException("size: " + size); } if (offset > sourceSize) { throw new IndexOutOfBoundsException( "offset (" + offset + ") > source size (" + sourceSize + ")"); } long endOffset = offset + size; if (endOffset < offset) { throw new IndexOutOfBoundsException( "offset (" + offset + ") + size (" + size + ") overflow"); } if (endOffset > sourceSize) { throw new IndexOutOfBoundsException( "offset (" + offset + ") + size (" + size + ") > source size (" + sourceSize +")"); } } } src/main/java/com/android/apksig/internal/zip/0040755 0000000 0000000 00000000000 13243353143 020337 5ustar000000000 0000000 src/main/java/com/android/apksig/internal/zip/CentralDirectoryRecord.java0100644 0000000 0000000 00000025665 13243353143 025631 0ustar000000000 0000000 /* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.internal.zip; import com.android.apksig.zip.ZipFormatException; import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.charset.StandardCharsets; import java.util.Comparator; /** * ZIP Central Directory (CD) Record. */ public class CentralDirectoryRecord { /** * Comparator which compares records by the offset of the corresponding Local File Header in the * archive. */ public static final Comparator BY_LOCAL_FILE_HEADER_OFFSET_COMPARATOR = new ByLocalFileHeaderOffsetComparator(); private static final int RECORD_SIGNATURE = 0x02014b50; private static final int HEADER_SIZE_BYTES = 46; private static final int GP_FLAGS_OFFSET = 8; private static final int LOCAL_FILE_HEADER_OFFSET_OFFSET = 42; private static final int NAME_OFFSET = HEADER_SIZE_BYTES; private final ByteBuffer mData; private final short mGpFlags; private final short mCompressionMethod; private final int mLastModificationTime; private final int mLastModificationDate; private final long mCrc32; private final long mCompressedSize; private final long mUncompressedSize; private final long mLocalFileHeaderOffset; private final String mName; private final int mNameSizeBytes; private CentralDirectoryRecord( ByteBuffer data, short gpFlags, short compressionMethod, int lastModificationTime, int lastModificationDate, long crc32, long compressedSize, long uncompressedSize, long localFileHeaderOffset, String name, int nameSizeBytes) { mData = data; mGpFlags = gpFlags; mCompressionMethod = compressionMethod; mLastModificationDate = lastModificationDate; mLastModificationTime = lastModificationTime; mCrc32 = crc32; mCompressedSize = compressedSize; mUncompressedSize = uncompressedSize; mLocalFileHeaderOffset = localFileHeaderOffset; mName = name; mNameSizeBytes = nameSizeBytes; } public int getSize() { return mData.remaining(); } public String getName() { return mName; } public int getNameSizeBytes() { return mNameSizeBytes; } public short getGpFlags() { return mGpFlags; } public short getCompressionMethod() { return mCompressionMethod; } public int getLastModificationTime() { return mLastModificationTime; } public int getLastModificationDate() { return mLastModificationDate; } public long getCrc32() { return mCrc32; } public long getCompressedSize() { return mCompressedSize; } public long getUncompressedSize() { return mUncompressedSize; } public long getLocalFileHeaderOffset() { return mLocalFileHeaderOffset; } /** * Returns the Central Directory Record starting at the current position of the provided buffer * and advances the buffer's position immediately past the end of the record. */ public static CentralDirectoryRecord getRecord(ByteBuffer buf) throws ZipFormatException { ZipUtils.assertByteOrderLittleEndian(buf); if (buf.remaining() < HEADER_SIZE_BYTES) { throw new ZipFormatException( "Input too short. Need at least: " + HEADER_SIZE_BYTES + " bytes, available: " + buf.remaining() + " bytes", new BufferUnderflowException()); } int originalPosition = buf.position(); int recordSignature = buf.getInt(); if (recordSignature != RECORD_SIGNATURE) { throw new ZipFormatException( "Not a Central Directory record. Signature: 0x" + Long.toHexString(recordSignature & 0xffffffffL)); } buf.position(originalPosition + GP_FLAGS_OFFSET); short gpFlags = buf.getShort(); short compressionMethod = buf.getShort(); int lastModificationTime = ZipUtils.getUnsignedInt16(buf); int lastModificationDate = ZipUtils.getUnsignedInt16(buf); long crc32 = ZipUtils.getUnsignedInt32(buf); long compressedSize = ZipUtils.getUnsignedInt32(buf); long uncompressedSize = ZipUtils.getUnsignedInt32(buf); int nameSize = ZipUtils.getUnsignedInt16(buf); int extraSize = ZipUtils.getUnsignedInt16(buf); int commentSize = ZipUtils.getUnsignedInt16(buf); buf.position(originalPosition + LOCAL_FILE_HEADER_OFFSET_OFFSET); long localFileHeaderOffset = ZipUtils.getUnsignedInt32(buf); buf.position(originalPosition); int recordSize = HEADER_SIZE_BYTES + nameSize + extraSize + commentSize; if (recordSize > buf.remaining()) { throw new ZipFormatException( "Input too short. Need: " + recordSize + " bytes, available: " + buf.remaining() + " bytes", new BufferUnderflowException()); } String name = getName(buf, originalPosition + NAME_OFFSET, nameSize); buf.position(originalPosition); int originalLimit = buf.limit(); int recordEndInBuf = originalPosition + recordSize; ByteBuffer recordBuf; try { buf.limit(recordEndInBuf); recordBuf = buf.slice(); } finally { buf.limit(originalLimit); } // Consume this record buf.position(recordEndInBuf); return new CentralDirectoryRecord( recordBuf, gpFlags, compressionMethod, lastModificationTime, lastModificationDate, crc32, compressedSize, uncompressedSize, localFileHeaderOffset, name, nameSize); } public void copyTo(ByteBuffer output) { output.put(mData.slice()); } public CentralDirectoryRecord createWithModifiedLocalFileHeaderOffset( long localFileHeaderOffset) { ByteBuffer result = ByteBuffer.allocate(mData.remaining()); result.put(mData.slice()); result.flip(); result.order(ByteOrder.LITTLE_ENDIAN); ZipUtils.setUnsignedInt32(result, LOCAL_FILE_HEADER_OFFSET_OFFSET, localFileHeaderOffset); return new CentralDirectoryRecord( result, mGpFlags, mCompressionMethod, mLastModificationTime, mLastModificationDate, mCrc32, mCompressedSize, mUncompressedSize, localFileHeaderOffset, mName, mNameSizeBytes); } public static CentralDirectoryRecord createWithDeflateCompressedData( String name, int lastModifiedTime, int lastModifiedDate, long crc32, long compressedSize, long uncompressedSize, long localFileHeaderOffset) { byte[] nameBytes = name.getBytes(StandardCharsets.UTF_8); short gpFlags = ZipUtils.GP_FLAG_EFS; // UTF-8 character encoding used for entry name short compressionMethod = ZipUtils.COMPRESSION_METHOD_DEFLATED; int recordSize = HEADER_SIZE_BYTES + nameBytes.length; ByteBuffer result = ByteBuffer.allocate(recordSize); result.order(ByteOrder.LITTLE_ENDIAN); result.putInt(RECORD_SIGNATURE); ZipUtils.putUnsignedInt16(result, 0x14); // Version made by ZipUtils.putUnsignedInt16(result, 0x14); // Minimum version needed to extract result.putShort(gpFlags); result.putShort(compressionMethod); ZipUtils.putUnsignedInt16(result, lastModifiedTime); ZipUtils.putUnsignedInt16(result, lastModifiedDate); ZipUtils.putUnsignedInt32(result, crc32); ZipUtils.putUnsignedInt32(result, compressedSize); ZipUtils.putUnsignedInt32(result, uncompressedSize); ZipUtils.putUnsignedInt16(result, nameBytes.length); ZipUtils.putUnsignedInt16(result, 0); // Extra field length ZipUtils.putUnsignedInt16(result, 0); // File comment length ZipUtils.putUnsignedInt16(result, 0); // Disk number ZipUtils.putUnsignedInt16(result, 0); // Internal file attributes ZipUtils.putUnsignedInt32(result, 0); // External file attributes ZipUtils.putUnsignedInt32(result, localFileHeaderOffset); result.put(nameBytes); if (result.hasRemaining()) { throw new RuntimeException("pos: " + result.position() + ", limit: " + result.limit()); } result.flip(); return new CentralDirectoryRecord( result, gpFlags, compressionMethod, lastModifiedTime, lastModifiedDate, crc32, compressedSize, uncompressedSize, localFileHeaderOffset, name, nameBytes.length); } static String getName(ByteBuffer record, int position, int nameLengthBytes) { byte[] nameBytes; int nameBytesOffset; if (record.hasArray()) { nameBytes = record.array(); nameBytesOffset = record.arrayOffset() + position; } else { nameBytes = new byte[nameLengthBytes]; nameBytesOffset = 0; int originalPosition = record.position(); try { record.position(position); record.get(nameBytes); } finally { record.position(originalPosition); } } return new String(nameBytes, nameBytesOffset, nameLengthBytes, StandardCharsets.UTF_8); } private static class ByLocalFileHeaderOffsetComparator implements Comparator { @Override public int compare(CentralDirectoryRecord r1, CentralDirectoryRecord r2) { long offset1 = r1.getLocalFileHeaderOffset(); long offset2 = r2.getLocalFileHeaderOffset(); if (offset1 > offset2) { return 1; } else if (offset1 < offset2) { return -1; } else { return 0; } } } } src/main/java/com/android/apksig/internal/zip/EocdRecord.java0100644 0000000 0000000 00000003530 13243353143 023211 0ustar000000000 0000000 /* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.internal.zip; import java.nio.ByteBuffer; import java.nio.ByteOrder; /** * ZIP End of Central Directory record. */ public class EocdRecord { private static final int CD_RECORD_COUNT_ON_DISK_OFFSET = 8; private static final int CD_RECORD_COUNT_TOTAL_OFFSET = 10; private static final int CD_SIZE_OFFSET = 12; private static final int CD_OFFSET_OFFSET = 16; public static ByteBuffer createWithModifiedCentralDirectoryInfo( ByteBuffer original, int centralDirectoryRecordCount, long centralDirectorySizeBytes, long centralDirectoryOffset) { ByteBuffer result = ByteBuffer.allocate(original.remaining()); result.order(ByteOrder.LITTLE_ENDIAN); result.put(original.slice()); result.flip(); ZipUtils.setUnsignedInt16( result, CD_RECORD_COUNT_ON_DISK_OFFSET, centralDirectoryRecordCount); ZipUtils.setUnsignedInt16( result, CD_RECORD_COUNT_TOTAL_OFFSET, centralDirectoryRecordCount); ZipUtils.setUnsignedInt32(result, CD_SIZE_OFFSET, centralDirectorySizeBytes); ZipUtils.setUnsignedInt32(result, CD_OFFSET_OFFSET, centralDirectoryOffset); return result; } } src/main/java/com/android/apksig/internal/zip/LocalFileRecord.java0100644 0000000 0000000 00000055666 13243353143 024212 0ustar000000000 0000000 /* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.internal.zip; import com.android.apksig.internal.util.ByteBufferSink; import com.android.apksig.util.DataSink; import com.android.apksig.util.DataSource; import com.android.apksig.zip.ZipFormatException; import java.io.Closeable; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.charset.StandardCharsets; import java.util.zip.DataFormatException; import java.util.zip.Inflater; /** * ZIP Local File record. * *

The record consists of the Local File Header, file data, and (if present) Data Descriptor. */ public class LocalFileRecord { private static final int RECORD_SIGNATURE = 0x04034b50; private static final int HEADER_SIZE_BYTES = 30; private static final int GP_FLAGS_OFFSET = 6; private static final int CRC32_OFFSET = 14; private static final int COMPRESSED_SIZE_OFFSET = 18; private static final int UNCOMPRESSED_SIZE_OFFSET = 22; private static final int NAME_LENGTH_OFFSET = 26; private static final int EXTRA_LENGTH_OFFSET = 28; private static final int NAME_OFFSET = HEADER_SIZE_BYTES; private static final int DATA_DESCRIPTOR_SIZE_BYTES_WITHOUT_SIGNATURE = 12; private static final int DATA_DESCRIPTOR_SIGNATURE = 0x08074b50; private final String mName; private final int mNameSizeBytes; private final ByteBuffer mExtra; private final long mStartOffsetInArchive; private final long mSize; private final int mDataStartOffset; private final long mDataSize; private final boolean mDataCompressed; private final long mUncompressedDataSize; private LocalFileRecord( String name, int nameSizeBytes, ByteBuffer extra, long startOffsetInArchive, long size, int dataStartOffset, long dataSize, boolean dataCompressed, long uncompressedDataSize) { mName = name; mNameSizeBytes = nameSizeBytes; mExtra = extra; mStartOffsetInArchive = startOffsetInArchive; mSize = size; mDataStartOffset = dataStartOffset; mDataSize = dataSize; mDataCompressed = dataCompressed; mUncompressedDataSize = uncompressedDataSize; } public String getName() { return mName; } public ByteBuffer getExtra() { return (mExtra.capacity() > 0) ? mExtra.slice() : mExtra; } public int getExtraFieldStartOffsetInsideRecord() { return HEADER_SIZE_BYTES + mNameSizeBytes; } public long getStartOffsetInArchive() { return mStartOffsetInArchive; } public int getDataStartOffsetInRecord() { return mDataStartOffset; } /** * Returns the size (in bytes) of this record. */ public long getSize() { return mSize; } /** * Returns {@code true} if this record's file data is stored in compressed form. */ public boolean isDataCompressed() { return mDataCompressed; } /** * Returns the Local File record starting at the current position of the provided buffer * and advances the buffer's position immediately past the end of the record. The record * consists of the Local File Header, data, and (if present) Data Descriptor. */ public static LocalFileRecord getRecord( DataSource apk, CentralDirectoryRecord cdRecord, long cdStartOffset) throws ZipFormatException, IOException { return getRecord( apk, cdRecord, cdStartOffset, true, // obtain extra field contents true // include Data Descriptor (if present) ); } /** * Returns the Local File record starting at the current position of the provided buffer * and advances the buffer's position immediately past the end of the record. The record * consists of the Local File Header, data, and (if present) Data Descriptor. */ private static LocalFileRecord getRecord( DataSource apk, CentralDirectoryRecord cdRecord, long cdStartOffset, boolean extraFieldContentsNeeded, boolean dataDescriptorIncluded) throws ZipFormatException, IOException { // IMPLEMENTATION NOTE: This method attempts to mimic the behavior of Android platform // exhibited when reading an APK for the purposes of verifying its signatures. String entryName = cdRecord.getName(); int cdRecordEntryNameSizeBytes = cdRecord.getNameSizeBytes(); int headerSizeWithName = HEADER_SIZE_BYTES + cdRecordEntryNameSizeBytes; long headerStartOffset = cdRecord.getLocalFileHeaderOffset(); long headerEndOffset = headerStartOffset + headerSizeWithName; if (headerEndOffset > cdStartOffset) { throw new ZipFormatException( "Local File Header of " + entryName + " extends beyond start of Central" + " Directory. LFH end: " + headerEndOffset + ", CD start: " + cdStartOffset); } ByteBuffer header; try { header = apk.getByteBuffer(headerStartOffset, headerSizeWithName); } catch (IOException e) { throw new IOException("Failed to read Local File Header of " + entryName, e); } header.order(ByteOrder.LITTLE_ENDIAN); int recordSignature = header.getInt(); if (recordSignature != RECORD_SIGNATURE) { throw new ZipFormatException( "Not a Local File Header record for entry " + entryName + ". Signature: 0x" + Long.toHexString(recordSignature & 0xffffffffL)); } short gpFlags = header.getShort(GP_FLAGS_OFFSET); boolean dataDescriptorUsed = (gpFlags & ZipUtils.GP_FLAG_DATA_DESCRIPTOR_USED) != 0; boolean cdDataDescriptorUsed = (cdRecord.getGpFlags() & ZipUtils.GP_FLAG_DATA_DESCRIPTOR_USED) != 0; if (dataDescriptorUsed != cdDataDescriptorUsed) { throw new ZipFormatException( "Data Descriptor presence mismatch between Local File Header and Central" + " Directory for entry " + entryName + ". LFH: " + dataDescriptorUsed + ", CD: " + cdDataDescriptorUsed); } long uncompressedDataCrc32FromCdRecord = cdRecord.getCrc32(); long compressedDataSizeFromCdRecord = cdRecord.getCompressedSize(); long uncompressedDataSizeFromCdRecord = cdRecord.getUncompressedSize(); if (!dataDescriptorUsed) { long crc32 = ZipUtils.getUnsignedInt32(header, CRC32_OFFSET); if (crc32 != uncompressedDataCrc32FromCdRecord) { throw new ZipFormatException( "CRC-32 mismatch between Local File Header and Central Directory for entry " + entryName + ". LFH: " + crc32 + ", CD: " + uncompressedDataCrc32FromCdRecord); } long compressedSize = ZipUtils.getUnsignedInt32(header, COMPRESSED_SIZE_OFFSET); if (compressedSize != compressedDataSizeFromCdRecord) { throw new ZipFormatException( "Compressed size mismatch between Local File Header and Central Directory" + " for entry " + entryName + ". LFH: " + compressedSize + ", CD: " + compressedDataSizeFromCdRecord); } long uncompressedSize = ZipUtils.getUnsignedInt32(header, UNCOMPRESSED_SIZE_OFFSET); if (uncompressedSize != uncompressedDataSizeFromCdRecord) { throw new ZipFormatException( "Uncompressed size mismatch between Local File Header and Central Directory" + " for entry " + entryName + ". LFH: " + uncompressedSize + ", CD: " + uncompressedDataSizeFromCdRecord); } } int nameLength = ZipUtils.getUnsignedInt16(header, NAME_LENGTH_OFFSET); if (nameLength > cdRecordEntryNameSizeBytes) { throw new ZipFormatException( "Name mismatch between Local File Header and Central Directory for entry" + entryName + ". LFH: " + nameLength + " bytes, CD: " + cdRecordEntryNameSizeBytes + " bytes"); } String name = CentralDirectoryRecord.getName(header, NAME_OFFSET, nameLength); if (!entryName.equals(name)) { throw new ZipFormatException( "Name mismatch between Local File Header and Central Directory. LFH: \"" + name + "\", CD: \"" + entryName + "\""); } int extraLength = ZipUtils.getUnsignedInt16(header, EXTRA_LENGTH_OFFSET); long dataStartOffset = headerStartOffset + HEADER_SIZE_BYTES + nameLength + extraLength; long dataSize; boolean compressed = (cdRecord.getCompressionMethod() != ZipUtils.COMPRESSION_METHOD_STORED); if (compressed) { dataSize = compressedDataSizeFromCdRecord; } else { dataSize = uncompressedDataSizeFromCdRecord; } long dataEndOffset = dataStartOffset + dataSize; if (dataEndOffset > cdStartOffset) { throw new ZipFormatException( "Local File Header data of " + entryName + " overlaps with Central Directory" + ". LFH data start: " + dataStartOffset + ", LFH data end: " + dataEndOffset + ", CD start: " + cdStartOffset); } ByteBuffer extra = EMPTY_BYTE_BUFFER; if ((extraFieldContentsNeeded) && (extraLength > 0)) { extra = apk.getByteBuffer( headerStartOffset + HEADER_SIZE_BYTES + nameLength, extraLength); } long recordEndOffset = dataEndOffset; // Include the Data Descriptor (if requested and present) into the record. if ((dataDescriptorIncluded) && ((gpFlags & ZipUtils.GP_FLAG_DATA_DESCRIPTOR_USED) != 0)) { // The record's data is supposed to be followed by the Data Descriptor. Unfortunately, // the descriptor's size is not known in advance because the spec lets the signature // field (the first four bytes) be omitted. Thus, there's no 100% reliable way to tell // how long the Data Descriptor record is. Most parsers (including Android) check // whether the first four bytes look like Data Descriptor record signature and, if so, // assume that it is indeed the record's signature. However, this is the wrong // conclusion if the record's CRC-32 (next field after the signature) has the same value // as the signature. In any case, we're doing what Android is doing. long dataDescriptorEndOffset = dataEndOffset + DATA_DESCRIPTOR_SIZE_BYTES_WITHOUT_SIGNATURE; if (dataDescriptorEndOffset > cdStartOffset) { throw new ZipFormatException( "Data Descriptor of " + entryName + " overlaps with Central Directory" + ". Data Descriptor end: " + dataEndOffset + ", CD start: " + cdStartOffset); } ByteBuffer dataDescriptorPotentialSig = apk.getByteBuffer(dataEndOffset, 4); dataDescriptorPotentialSig.order(ByteOrder.LITTLE_ENDIAN); if (dataDescriptorPotentialSig.getInt() == DATA_DESCRIPTOR_SIGNATURE) { dataDescriptorEndOffset += 4; if (dataDescriptorEndOffset > cdStartOffset) { throw new ZipFormatException( "Data Descriptor of " + entryName + " overlaps with Central Directory" + ". Data Descriptor end: " + dataEndOffset + ", CD start: " + cdStartOffset); } } recordEndOffset = dataDescriptorEndOffset; } long recordSize = recordEndOffset - headerStartOffset; int dataStartOffsetInRecord = HEADER_SIZE_BYTES + nameLength + extraLength; return new LocalFileRecord( entryName, cdRecordEntryNameSizeBytes, extra, headerStartOffset, recordSize, dataStartOffsetInRecord, dataSize, compressed, uncompressedDataSizeFromCdRecord); } /** * Outputs this record and returns returns the number of bytes output. */ public long outputRecord(DataSource sourceApk, DataSink output) throws IOException { long size = getSize(); sourceApk.feed(getStartOffsetInArchive(), size, output); return size; } /** * Outputs this record, replacing its extra field with the provided one, and returns returns the * number of bytes output. */ public long outputRecordWithModifiedExtra( DataSource sourceApk, ByteBuffer extra, DataSink output) throws IOException { long recordStartOffsetInSource = getStartOffsetInArchive(); int extraStartOffsetInRecord = getExtraFieldStartOffsetInsideRecord(); int extraSizeBytes = extra.remaining(); int headerSize = extraStartOffsetInRecord + extraSizeBytes; ByteBuffer header = ByteBuffer.allocate(headerSize); header.order(ByteOrder.LITTLE_ENDIAN); sourceApk.copyTo(recordStartOffsetInSource, extraStartOffsetInRecord, header); header.put(extra.slice()); header.flip(); ZipUtils.setUnsignedInt16(header, EXTRA_LENGTH_OFFSET, extraSizeBytes); long outputByteCount = header.remaining(); output.consume(header); long remainingRecordSize = getSize() - mDataStartOffset; sourceApk.feed(recordStartOffsetInSource + mDataStartOffset, remainingRecordSize, output); outputByteCount += remainingRecordSize; return outputByteCount; } /** * Outputs the specified Local File Header record with its data and returns the number of bytes * output. */ public static long outputRecordWithDeflateCompressedData( String name, int lastModifiedTime, int lastModifiedDate, byte[] compressedData, long crc32, long uncompressedSize, DataSink output) throws IOException { byte[] nameBytes = name.getBytes(StandardCharsets.UTF_8); int recordSize = HEADER_SIZE_BYTES + nameBytes.length; ByteBuffer result = ByteBuffer.allocate(recordSize); result.order(ByteOrder.LITTLE_ENDIAN); result.putInt(RECORD_SIGNATURE); ZipUtils.putUnsignedInt16(result, 0x14); // Minimum version needed to extract result.putShort(ZipUtils.GP_FLAG_EFS); // General purpose flag: UTF-8 encoded name result.putShort(ZipUtils.COMPRESSION_METHOD_DEFLATED); ZipUtils.putUnsignedInt16(result, lastModifiedTime); ZipUtils.putUnsignedInt16(result, lastModifiedDate); ZipUtils.putUnsignedInt32(result, crc32); ZipUtils.putUnsignedInt32(result, compressedData.length); ZipUtils.putUnsignedInt32(result, uncompressedSize); ZipUtils.putUnsignedInt16(result, nameBytes.length); ZipUtils.putUnsignedInt16(result, 0); // Extra field length result.put(nameBytes); if (result.hasRemaining()) { throw new RuntimeException("pos: " + result.position() + ", limit: " + result.limit()); } result.flip(); long outputByteCount = result.remaining(); output.consume(result); outputByteCount += compressedData.length; output.consume(compressedData, 0, compressedData.length); return outputByteCount; } private static final ByteBuffer EMPTY_BYTE_BUFFER = ByteBuffer.allocate(0); /** * Sends uncompressed data of this record into the the provided data sink. */ public void outputUncompressedData( DataSource lfhSection, DataSink sink) throws IOException, ZipFormatException { long dataStartOffsetInArchive = mStartOffsetInArchive + mDataStartOffset; try { if (mDataCompressed) { try (InflateSinkAdapter inflateAdapter = new InflateSinkAdapter(sink)) { lfhSection.feed(dataStartOffsetInArchive, mDataSize, inflateAdapter); long actualUncompressedSize = inflateAdapter.getOutputByteCount(); if (actualUncompressedSize != mUncompressedDataSize) { throw new ZipFormatException( "Unexpected size of uncompressed data of " + mName + ". Expected: " + mUncompressedDataSize + " bytes" + ", actual: " + actualUncompressedSize + " bytes"); } } catch (IOException e) { if (e.getCause() instanceof DataFormatException) { throw new ZipFormatException("Data of entry " + mName + " malformed", e); } throw e; } } else { lfhSection.feed(dataStartOffsetInArchive, mDataSize, sink); // No need to check whether output size is as expected because DataSource.feed is // guaranteed to output exactly the number of bytes requested. } } catch (IOException e) { throw new IOException( "Failed to read data of " + ((mDataCompressed) ? "compressed" : "uncompressed") + " entry " + mName, e); } // Interestingly, Android doesn't check that uncompressed data's CRC-32 is as expected. We // thus don't check either. } /** * Sends uncompressed data pointed to by the provided ZIP Central Directory (CD) record into the * provided data sink. */ public static void outputUncompressedData( DataSource source, CentralDirectoryRecord cdRecord, long cdStartOffsetInArchive, DataSink sink) throws ZipFormatException, IOException { // IMPLEMENTATION NOTE: This method attempts to mimic the behavior of Android platform // exhibited when reading an APK for the purposes of verifying its signatures. // When verifying an APK, Android doesn't care reading the extra field or the Data // Descriptor. LocalFileRecord lfhRecord = getRecord( source, cdRecord, cdStartOffsetInArchive, false, // don't care about the extra field false // don't read the Data Descriptor ); lfhRecord.outputUncompressedData(source, sink); } /** * Returns the uncompressed data pointed to by the provided ZIP Central Directory (CD) record. */ public static byte[] getUncompressedData( DataSource source, CentralDirectoryRecord cdRecord, long cdStartOffsetInArchive) throws ZipFormatException, IOException { if (cdRecord.getUncompressedSize() > Integer.MAX_VALUE) { throw new IOException( cdRecord.getName() + " too large: " + cdRecord.getUncompressedSize()); } byte[] result = new byte[(int) cdRecord.getUncompressedSize()]; ByteBuffer resultBuf = ByteBuffer.wrap(result); ByteBufferSink resultSink = new ByteBufferSink(resultBuf); outputUncompressedData( source, cdRecord, cdStartOffsetInArchive, resultSink); return result; } /** * {@link DataSink} which inflates received data and outputs the deflated data into the provided * delegate sink. */ private static class InflateSinkAdapter implements DataSink, Closeable { private final DataSink mDelegate; private Inflater mInflater = new Inflater(true); private byte[] mOutputBuffer; private byte[] mInputBuffer; private long mOutputByteCount; private boolean mClosed; private InflateSinkAdapter(DataSink delegate) { mDelegate = delegate; } @Override public void consume(byte[] buf, int offset, int length) throws IOException { checkNotClosed(); mInflater.setInput(buf, offset, length); if (mOutputBuffer == null) { mOutputBuffer = new byte[65536]; } while (!mInflater.finished()) { int outputChunkSize; try { outputChunkSize = mInflater.inflate(mOutputBuffer); } catch (DataFormatException e) { throw new IOException("Failed to inflate data", e); } if (outputChunkSize == 0) { return; } mDelegate.consume(mOutputBuffer, 0, outputChunkSize); mOutputByteCount += outputChunkSize; } } @Override public void consume(ByteBuffer buf) throws IOException { checkNotClosed(); if (buf.hasArray()) { consume(buf.array(), buf.arrayOffset() + buf.position(), buf.remaining()); buf.position(buf.limit()); } else { if (mInputBuffer == null) { mInputBuffer = new byte[65536]; } while (buf.hasRemaining()) { int chunkSize = Math.min(buf.remaining(), mInputBuffer.length); buf.get(mInputBuffer, 0, chunkSize); consume(mInputBuffer, 0, chunkSize); } } } public long getOutputByteCount() { return mOutputByteCount; } @Override public void close() throws IOException { mClosed = true; mInputBuffer = null; mOutputBuffer = null; if (mInflater != null) { mInflater.end(); mInflater = null; } } private void checkNotClosed() { if (mClosed) { throw new IllegalStateException("Closed"); } } } } src/main/java/com/android/apksig/internal/zip/ZipUtils.java0100644 0000000 0000000 00000034113 13243353143 022764 0ustar000000000 0000000 /* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.internal.zip; import com.android.apksig.internal.util.Pair; import com.android.apksig.util.DataSource; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.zip.CRC32; import java.util.zip.Deflater; /** * Assorted ZIP format helpers. * *

NOTE: Most helper methods operating on {@code ByteBuffer} instances expect that the byte * order of these buffers is little-endian. */ public abstract class ZipUtils { private ZipUtils() {} public static final short COMPRESSION_METHOD_STORED = 0; public static final short COMPRESSION_METHOD_DEFLATED = 8; public static final short GP_FLAG_DATA_DESCRIPTOR_USED = 0x08; public static final short GP_FLAG_EFS = 0x0800; private static final int ZIP_EOCD_REC_MIN_SIZE = 22; private static final int ZIP_EOCD_REC_SIG = 0x06054b50; private static final int ZIP_EOCD_CENTRAL_DIR_TOTAL_RECORD_COUNT_OFFSET = 10; private static final int ZIP_EOCD_CENTRAL_DIR_SIZE_FIELD_OFFSET = 12; private static final int ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_OFFSET = 16; private static final int ZIP_EOCD_COMMENT_LENGTH_FIELD_OFFSET = 20; private static final int UINT16_MAX_VALUE = 0xffff; /** * Sets the offset of the start of the ZIP Central Directory in the archive. * *

NOTE: Byte order of {@code zipEndOfCentralDirectory} must be little-endian. */ public static void setZipEocdCentralDirectoryOffset( ByteBuffer zipEndOfCentralDirectory, long offset) { assertByteOrderLittleEndian(zipEndOfCentralDirectory); setUnsignedInt32( zipEndOfCentralDirectory, zipEndOfCentralDirectory.position() + ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_OFFSET, offset); } /** * Returns the offset of the start of the ZIP Central Directory in the archive. * *

NOTE: Byte order of {@code zipEndOfCentralDirectory} must be little-endian. */ public static long getZipEocdCentralDirectoryOffset(ByteBuffer zipEndOfCentralDirectory) { assertByteOrderLittleEndian(zipEndOfCentralDirectory); return getUnsignedInt32( zipEndOfCentralDirectory, zipEndOfCentralDirectory.position() + ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_OFFSET); } /** * Returns the size (in bytes) of the ZIP Central Directory. * *

NOTE: Byte order of {@code zipEndOfCentralDirectory} must be little-endian. */ public static long getZipEocdCentralDirectorySizeBytes(ByteBuffer zipEndOfCentralDirectory) { assertByteOrderLittleEndian(zipEndOfCentralDirectory); return getUnsignedInt32( zipEndOfCentralDirectory, zipEndOfCentralDirectory.position() + ZIP_EOCD_CENTRAL_DIR_SIZE_FIELD_OFFSET); } /** * Returns the total number of records in ZIP Central Directory. * *

NOTE: Byte order of {@code zipEndOfCentralDirectory} must be little-endian. */ public static int getZipEocdCentralDirectoryTotalRecordCount( ByteBuffer zipEndOfCentralDirectory) { assertByteOrderLittleEndian(zipEndOfCentralDirectory); return getUnsignedInt16( zipEndOfCentralDirectory, zipEndOfCentralDirectory.position() + ZIP_EOCD_CENTRAL_DIR_TOTAL_RECORD_COUNT_OFFSET); } /** * Returns the ZIP End of Central Directory record of the provided ZIP file. * * @return contents of the ZIP End of Central Directory record and the record's offset in the * file or {@code null} if the file does not contain the record. * * @throws IOException if an I/O error occurs while reading the file. */ public static Pair findZipEndOfCentralDirectoryRecord(DataSource zip) throws IOException { // ZIP End of Central Directory (EOCD) record is located at the very end of the ZIP archive. // The record can be identified by its 4-byte signature/magic which is located at the very // beginning of the record. A complication is that the record is variable-length because of // the comment field. // The algorithm for locating the ZIP EOCD record is as follows. We search backwards from // end of the buffer for the EOCD record signature. Whenever we find a signature, we check // the candidate record's comment length is such that the remainder of the record takes up // exactly the remaining bytes in the buffer. The search is bounded because the maximum // size of the comment field is 65535 bytes because the field is an unsigned 16-bit number. long fileSize = zip.size(); if (fileSize < ZIP_EOCD_REC_MIN_SIZE) { return null; } // Optimization: 99.99% of APKs have a zero-length comment field in the EoCD record and thus // the EoCD record offset is known in advance. Try that offset first to avoid unnecessarily // reading more data. Pair result = findZipEndOfCentralDirectoryRecord(zip, 0); if (result != null) { return result; } // EoCD does not start where we expected it to. Perhaps it contains a non-empty comment // field. Expand the search. The maximum size of the comment field in EoCD is 65535 because // the comment length field is an unsigned 16-bit number. return findZipEndOfCentralDirectoryRecord(zip, UINT16_MAX_VALUE); } /** * Returns the ZIP End of Central Directory record of the provided ZIP file. * * @param maxCommentSize maximum accepted size (in bytes) of EoCD comment field. The permitted * value is from 0 to 65535 inclusive. The smaller the value, the faster this method * locates the record, provided its comment field is no longer than this value. * * @return contents of the ZIP End of Central Directory record and the record's offset in the * file or {@code null} if the file does not contain the record. * * @throws IOException if an I/O error occurs while reading the file. */ private static Pair findZipEndOfCentralDirectoryRecord( DataSource zip, int maxCommentSize) throws IOException { // ZIP End of Central Directory (EOCD) record is located at the very end of the ZIP archive. // The record can be identified by its 4-byte signature/magic which is located at the very // beginning of the record. A complication is that the record is variable-length because of // the comment field. // The algorithm for locating the ZIP EOCD record is as follows. We search backwards from // end of the buffer for the EOCD record signature. Whenever we find a signature, we check // the candidate record's comment length is such that the remainder of the record takes up // exactly the remaining bytes in the buffer. The search is bounded because the maximum // size of the comment field is 65535 bytes because the field is an unsigned 16-bit number. if ((maxCommentSize < 0) || (maxCommentSize > UINT16_MAX_VALUE)) { throw new IllegalArgumentException("maxCommentSize: " + maxCommentSize); } long fileSize = zip.size(); if (fileSize < ZIP_EOCD_REC_MIN_SIZE) { // No space for EoCD record in the file. return null; } // Lower maxCommentSize if the file is too small. maxCommentSize = (int) Math.min(maxCommentSize, fileSize - ZIP_EOCD_REC_MIN_SIZE); int maxEocdSize = ZIP_EOCD_REC_MIN_SIZE + maxCommentSize; long bufOffsetInFile = fileSize - maxEocdSize; ByteBuffer buf = zip.getByteBuffer(bufOffsetInFile, maxEocdSize); buf.order(ByteOrder.LITTLE_ENDIAN); int eocdOffsetInBuf = findZipEndOfCentralDirectoryRecord(buf); if (eocdOffsetInBuf == -1) { // No EoCD record found in the buffer return null; } // EoCD found buf.position(eocdOffsetInBuf); ByteBuffer eocd = buf.slice(); eocd.order(ByteOrder.LITTLE_ENDIAN); return Pair.of(eocd, bufOffsetInFile + eocdOffsetInBuf); } /** * Returns the position at which ZIP End of Central Directory record starts in the provided * buffer or {@code -1} if the record is not present. * *

NOTE: Byte order of {@code zipContents} must be little-endian. */ private static int findZipEndOfCentralDirectoryRecord(ByteBuffer zipContents) { assertByteOrderLittleEndian(zipContents); // ZIP End of Central Directory (EOCD) record is located at the very end of the ZIP archive. // The record can be identified by its 4-byte signature/magic which is located at the very // beginning of the record. A complication is that the record is variable-length because of // the comment field. // The algorithm for locating the ZIP EOCD record is as follows. We search backwards from // end of the buffer for the EOCD record signature. Whenever we find a signature, we check // the candidate record's comment length is such that the remainder of the record takes up // exactly the remaining bytes in the buffer. The search is bounded because the maximum // size of the comment field is 65535 bytes because the field is an unsigned 16-bit number. int archiveSize = zipContents.capacity(); if (archiveSize < ZIP_EOCD_REC_MIN_SIZE) { return -1; } int maxCommentLength = Math.min(archiveSize - ZIP_EOCD_REC_MIN_SIZE, UINT16_MAX_VALUE); int eocdWithEmptyCommentStartPosition = archiveSize - ZIP_EOCD_REC_MIN_SIZE; for (int expectedCommentLength = 0; expectedCommentLength <= maxCommentLength; expectedCommentLength++) { int eocdStartPos = eocdWithEmptyCommentStartPosition - expectedCommentLength; if (zipContents.getInt(eocdStartPos) == ZIP_EOCD_REC_SIG) { int actualCommentLength = getUnsignedInt16( zipContents, eocdStartPos + ZIP_EOCD_COMMENT_LENGTH_FIELD_OFFSET); if (actualCommentLength == expectedCommentLength) { return eocdStartPos; } } } return -1; } static void assertByteOrderLittleEndian(ByteBuffer buffer) { if (buffer.order() != ByteOrder.LITTLE_ENDIAN) { throw new IllegalArgumentException("ByteBuffer byte order must be little endian"); } } public static int getUnsignedInt16(ByteBuffer buffer, int offset) { return buffer.getShort(offset) & 0xffff; } public static int getUnsignedInt16(ByteBuffer buffer) { return buffer.getShort() & 0xffff; } static void setUnsignedInt16(ByteBuffer buffer, int offset, int value) { if ((value < 0) || (value > 0xffff)) { throw new IllegalArgumentException("uint16 value of out range: " + value); } buffer.putShort(offset, (short) value); } static void setUnsignedInt32(ByteBuffer buffer, int offset, long value) { if ((value < 0) || (value > 0xffffffffL)) { throw new IllegalArgumentException("uint32 value of out range: " + value); } buffer.putInt(offset, (int) value); } public static void putUnsignedInt16(ByteBuffer buffer, int value) { if ((value < 0) || (value > 0xffff)) { throw new IllegalArgumentException("uint16 value of out range: " + value); } buffer.putShort((short) value); } static long getUnsignedInt32(ByteBuffer buffer, int offset) { return buffer.getInt(offset) & 0xffffffffL; } static long getUnsignedInt32(ByteBuffer buffer) { return buffer.getInt() & 0xffffffffL; } static void putUnsignedInt32(ByteBuffer buffer, long value) { if ((value < 0) || (value > 0xffffffffL)) { throw new IllegalArgumentException("uint32 value of out range: " + value); } buffer.putInt((int) value); } public static DeflateResult deflate(ByteBuffer input) { byte[] inputBuf; int inputOffset; int inputLength = input.remaining(); if (input.hasArray()) { inputBuf = input.array(); inputOffset = input.arrayOffset() + input.position(); input.position(input.limit()); } else { inputBuf = new byte[inputLength]; inputOffset = 0; input.get(inputBuf); } CRC32 crc32 = new CRC32(); crc32.update(inputBuf, inputOffset, inputLength); long crc32Value = crc32.getValue(); ByteArrayOutputStream out = new ByteArrayOutputStream(); Deflater deflater = new Deflater(9, true); deflater.setInput(inputBuf, inputOffset, inputLength); deflater.finish(); byte[] buf = new byte[65536]; while (!deflater.finished()) { int chunkSize = deflater.deflate(buf); out.write(buf, 0, chunkSize); } return new DeflateResult(inputLength, crc32Value, out.toByteArray()); } public static class DeflateResult { public final int inputSizeBytes; public final long inputCrc32; public final byte[] output; public DeflateResult(int inputSizeBytes, long inputCrc32, byte[] output) { this.inputSizeBytes = inputSizeBytes; this.inputCrc32 = inputCrc32; this.output = output; } } }src/main/java/com/android/apksig/util/0040755 0000000 0000000 00000000000 13243353143 016676 5ustar000000000 0000000 src/main/java/com/android/apksig/util/DataSink.java0100644 0000000 0000000 00000003054 13243353143 021236 0ustar000000000 0000000 /* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.util; import java.io.IOException; import java.nio.ByteBuffer; /** * Consumer of input data which may be provided in one go or in chunks. */ public interface DataSink { /** * Consumes the provided chunk of data. * *

This data sink guarantees to not hold references to the provided buffer after this method * terminates. * * @throws IndexOutOfBoundsException if {@code offset} or {@code length} are negative, or if * {@code offset + length} is greater than {@code buf.length}. */ void consume(byte[] buf, int offset, int length) throws IOException; /** * Consumes all remaining data in the provided buffer and advances the buffer's position * to the buffer's limit. * *

This data sink guarantees to not hold references to the provided buffer after this method * terminates. */ void consume(ByteBuffer buf) throws IOException; } src/main/java/com/android/apksig/util/DataSinks.java0100644 0000000 0000000 00000004166 13243353143 021426 0ustar000000000 0000000 /* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.util; import com.android.apksig.internal.util.ByteArrayDataSink; import com.android.apksig.internal.util.OutputStreamDataSink; import com.android.apksig.internal.util.RandomAccessFileDataSink; import java.io.OutputStream; import java.io.RandomAccessFile; /** * Utility methods for working with {@link DataSink} abstraction. */ public abstract class DataSinks { private DataSinks() {} /** * Returns a {@link DataSink} which outputs received data into the provided * {@link OutputStream}. */ public static DataSink asDataSink(OutputStream out) { return new OutputStreamDataSink(out); } /** * Returns a {@link DataSink} which outputs received data into the provided file, sequentially, * starting at the beginning of the file. */ public static DataSink asDataSink(RandomAccessFile file) { return new RandomAccessFileDataSink(file); } /** * Returns a new in-memory {@link DataSink} which exposes all data consumed so far via the * {@link DataSource} interface. */ public static ReadableDataSink newInMemoryDataSink() { return new ByteArrayDataSink(); } /** * Returns a new in-memory {@link DataSink} which exposes all data consumed so far via the * {@link DataSource} interface. * * @param initialCapacity initial capacity in bytes */ public static ReadableDataSink newInMemoryDataSink(int initialCapacity) { return new ByteArrayDataSink(initialCapacity); } } src/main/java/com/android/apksig/util/DataSource.java0100644 0000000 0000000 00000011773 13243353143 021601 0ustar000000000 0000000 /* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.util; import java.io.IOException; import java.nio.ByteBuffer; /** * Abstract representation of a source of data. * *

This abstraction serves three purposes: *

    *
  • Transparent handling of different types of sources, such as {@code byte[]}, * {@link java.nio.ByteBuffer}, {@link java.io.RandomAccessFile}, memory-mapped file.
  • *
  • Support sources larger than 2 GB. If all sources were smaller than 2 GB, {@code ByteBuffer} * may have worked as the unifying abstraction.
  • *
  • Support sources which do not fit into logical memory as a contiguous region.
  • *
* *

There are following ways to obtain a chunk of data from the data source: *

    *
  • Stream the chunk's data into a {@link DataSink} using * {@link #feed(long, long, DataSink) feed}. This is best suited for scenarios where there is no * need to have the chunk's data accessible at the same time, for example, when computing the * digest of the chunk. If you need to keep the chunk's data around after {@code feed} * completes, you must create a copy during {@code feed}. However, in that case the following * methods of obtaining the chunk's data may be more appropriate.
  • *
  • Obtain a {@link ByteBuffer} containing the chunk's data using * {@link #getByteBuffer(long, int) getByteBuffer}. Depending on the data source, the chunk's * data may or may not be copied by this operation. This is best suited for scenarios where * you need to access the chunk's data in arbitrary order, but don't need to modify the data and * thus don't require a copy of the data.
  • *
  • Copy the chunk's data to a {@link ByteBuffer} using * {@link #copyTo(long, int, ByteBuffer) copyTo}. This is best suited for scenarios where * you require a copy of the chunk's data, such as to when you need to modify the data. *
  • *
*/ public interface DataSource { /** * Returns the amount of data (in bytes) contained in this data source. */ long size(); /** * Feeds the specified chunk from this data source into the provided sink. * * @param offset index (in bytes) at which the chunk starts inside data source * @param size size (in bytes) of the chunk * * @throws IndexOutOfBoundsException if {@code offset} or {@code size} is negative, or if * {@code offset + size} is greater than {@link #size()}. */ void feed(long offset, long size, DataSink sink) throws IOException; /** * Returns a buffer holding the contents of the specified chunk of data from this data source. * Changes to the data source are not guaranteed to be reflected in the returned buffer. * Similarly, changes in the buffer are not guaranteed to be reflected in the data source. * *

The returned buffer's position is {@code 0}, and the buffer's limit and capacity is * {@code size}. * * @param offset index (in bytes) at which the chunk starts inside data source * @param size size (in bytes) of the chunk * * @throws IndexOutOfBoundsException if {@code offset} or {@code size} is negative, or if * {@code offset + size} is greater than {@link #size()}. */ ByteBuffer getByteBuffer(long offset, int size) throws IOException; /** * Copies the specified chunk from this data source into the provided destination buffer, * advancing the destination buffer's position by {@code size}. * * @param offset index (in bytes) at which the chunk starts inside data source * @param size size (in bytes) of the chunk * * @throws IndexOutOfBoundsException if {@code offset} or {@code size} is negative, or if * {@code offset + size} is greater than {@link #size()}. */ void copyTo(long offset, int size, ByteBuffer dest) throws IOException; /** * Returns a data source representing the specified region of data of this data source. Changes * to data represented by this data source will also be visible in the returned data source. * * @param offset index (in bytes) at which the region starts inside data source * @param size size (in bytes) of the region * * @throws IndexOutOfBoundsException if {@code offset} or {@code size} is negative, or if * {@code offset + size} is greater than {@link #size()}. */ DataSource slice(long offset, long size); } src/main/java/com/android/apksig/util/DataSources.java0100644 0000000 0000000 00000004377 13243353143 021766 0ustar000000000 0000000 /* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.util; import com.android.apksig.internal.util.ByteBufferDataSource; import com.android.apksig.internal.util.RandomAccessFileDataSource; import java.io.RandomAccessFile; import java.nio.ByteBuffer; /** * Utility methods for working with {@link DataSource} abstraction. */ public abstract class DataSources { private DataSources() {} /** * Returns a {@link DataSource} backed by the provided {@link ByteBuffer}. The data source * represents the data contained between the position and limit of the buffer. Changes to the * buffer's contents will be visible in the data source. */ public static DataSource asDataSource(ByteBuffer buffer) { if (buffer == null) { throw new NullPointerException(); } return new ByteBufferDataSource(buffer); } /** * Returns a {@link DataSource} backed by the provided {@link RandomAccessFile}. Changes to the * file, including changes to size of file, will be visible in the data source. */ public static DataSource asDataSource(RandomAccessFile file) { if (file == null) { throw new NullPointerException(); } return new RandomAccessFileDataSource(file); } /** * Returns a {@link DataSource} backed by the provided region of the {@link RandomAccessFile}. * Changes to the file will be visible in the data source. */ public static DataSource asDataSource(RandomAccessFile file, long offset, long size) { if (file == null) { throw new NullPointerException(); } return new RandomAccessFileDataSource(file, offset, size); } } src/main/java/com/android/apksig/util/ReadableDataSink.java0100644 0000000 0000000 00000001572 13243353143 022661 0ustar000000000 0000000 /* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.util; /** * {@link DataSink} which exposes all data consumed so far as a {@link DataSource}. This abstraction * offers append-only write access and random read access. */ public interface ReadableDataSink extends DataSink, DataSource { } src/main/java/com/android/apksig/zip/0040755 0000000 0000000 00000000000 13243353143 016523 5ustar000000000 0000000 src/main/java/com/android/apksig/zip/ZipFormatException.java0100644 0000000 0000000 00000001753 13243353143 023163 0ustar000000000 0000000 /* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.zip; /** * Indicates that a ZIP archive is not well-formed. */ public class ZipFormatException extends Exception { private static final long serialVersionUID = 1L; public ZipFormatException(String message) { super(message); } public ZipFormatException(String message, Throwable cause) { super(message, cause); } } src/test/0040755 0000000 0000000 00000000000 13243353143 011357 5ustar000000000 0000000 src/test/java/0040755 0000000 0000000 00000000000 13243353143 012300 5ustar000000000 0000000 src/test/java/com/0040755 0000000 0000000 00000000000 13243353143 013056 5ustar000000000 0000000 src/test/java/com/android/0040755 0000000 0000000 00000000000 13243353143 014476 5ustar000000000 0000000 src/test/java/com/android/apksig/0040755 0000000 0000000 00000000000 13243353143 015754 5ustar000000000 0000000 src/test/java/com/android/apksig/AllTests.java0100644 0000000 0000000 00000001717 13243353143 020355 0ustar000000000 0000000 /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig; import org.junit.runner.RunWith; import org.junit.runners.Suite; @RunWith(Suite.class) @Suite.SuiteClasses({ ApkSignerTest.class, ApkVerifierTest.class, com.android.apksig.apk.AllTests.class, com.android.apksig.internal.AllTests.class, com.android.apksig.util.AllTests.class, }) public class AllTests {} src/test/java/com/android/apksig/ApkSignerTest.java0100644 0000000 0000000 00000051100 13243353143 021334 0ustar000000000 0000000 /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig; import static org.junit.Assert.fail; import com.android.apksig.ApkVerifier.Issue; import com.android.apksig.apk.ApkFormatException; import com.android.apksig.internal.util.Resources; import com.android.apksig.util.DataSinks; import com.android.apksig.util.DataSource; import com.android.apksig.util.DataSources; import com.android.apksig.util.ReadableDataSink; import java.io.File; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.ByteChannel; import java.nio.file.Files; import java.nio.file.StandardOpenOption; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.cert.X509Certificate; import java.util.Collections; import java.util.List; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @RunWith(JUnit4.class) public class ApkSignerTest { /** * Whether to preserve, as files, outputs of failed tests. This is useful for investigating test * failures. */ private static final boolean KEEP_FAILING_OUTPUT_AS_FILES = false; public static void main(String[] params) throws Exception { File outDir = (params.length > 0) ? new File(params[0]) : new File("."); generateGoldenFiles(outDir); } private static void generateGoldenFiles(File outDir) throws Exception { System.out.println( "Generating golden files " + ApkSignerTest.class.getSimpleName() + " into " + outDir); if (!outDir.mkdirs()) { throw new IOException("Failed to create directory: " + outDir); } List rsa2048SignerConfig = Collections.singletonList(getDefaultSignerConfigFromResources("rsa-2048")); signGolden( "golden-unaligned-in.apk", new File(outDir, "golden-unaligned-out.apk"), new ApkSigner.Builder(rsa2048SignerConfig)); signGolden( "golden-legacy-aligned-in.apk", new File(outDir, "golden-legacy-aligned-out.apk"), new ApkSigner.Builder(rsa2048SignerConfig)); signGolden( "golden-aligned-in.apk", new File(outDir, "golden-aligned-out.apk"), new ApkSigner.Builder(rsa2048SignerConfig)); signGolden( "golden-unaligned-in.apk", new File(outDir, "golden-unaligned-v1-out.apk"), new ApkSigner.Builder(rsa2048SignerConfig) .setV1SigningEnabled(true) .setV2SigningEnabled(false)); signGolden( "golden-legacy-aligned-in.apk", new File(outDir, "golden-legacy-aligned-v1-out.apk"), new ApkSigner.Builder(rsa2048SignerConfig) .setV1SigningEnabled(true) .setV2SigningEnabled(false)); signGolden( "golden-aligned-in.apk", new File(outDir, "golden-aligned-v1-out.apk"), new ApkSigner.Builder(rsa2048SignerConfig) .setV1SigningEnabled(true) .setV2SigningEnabled(false)); signGolden( "golden-unaligned-in.apk", new File(outDir, "golden-unaligned-v2-out.apk"), new ApkSigner.Builder(rsa2048SignerConfig) .setV1SigningEnabled(false) .setV2SigningEnabled(true)); signGolden( "golden-legacy-aligned-in.apk", new File(outDir, "golden-legacy-aligned-v2-out.apk"), new ApkSigner.Builder(rsa2048SignerConfig) .setV1SigningEnabled(false) .setV2SigningEnabled(true)); signGolden( "golden-aligned-in.apk", new File(outDir, "golden-aligned-v2-out.apk"), new ApkSigner.Builder(rsa2048SignerConfig) .setV1SigningEnabled(false) .setV2SigningEnabled(true)); signGolden( "golden-unaligned-in.apk", new File(outDir, "golden-unaligned-v1v2-out.apk"), new ApkSigner.Builder(rsa2048SignerConfig) .setV1SigningEnabled(true) .setV2SigningEnabled(true)); signGolden( "golden-legacy-aligned-in.apk", new File(outDir, "golden-legacy-aligned-v1v2-out.apk"), new ApkSigner.Builder(rsa2048SignerConfig) .setV1SigningEnabled(true) .setV2SigningEnabled(true)); signGolden( "golden-aligned-in.apk", new File(outDir, "golden-aligned-v1v2-out.apk"), new ApkSigner.Builder(rsa2048SignerConfig) .setV1SigningEnabled(true) .setV2SigningEnabled(true)); signGolden( "original.apk", new File(outDir, "golden-rsa-out.apk"), new ApkSigner.Builder(rsa2048SignerConfig)); signGolden( "original.apk", new File(outDir, "golden-rsa-minSdkVersion-1-out.apk"), new ApkSigner.Builder(rsa2048SignerConfig).setMinSdkVersion(1)); signGolden( "original.apk", new File(outDir, "golden-rsa-minSdkVersion-18-out.apk"), new ApkSigner.Builder(rsa2048SignerConfig).setMinSdkVersion(18)); signGolden( "original.apk", new File(outDir, "golden-rsa-minSdkVersion-24-out.apk"), new ApkSigner.Builder(rsa2048SignerConfig).setMinSdkVersion(24)); } private static void signGolden( String inResourceName, File outFile, ApkSigner.Builder apkSignerBuilder) throws Exception { DataSource in = DataSources.asDataSource( ByteBuffer.wrap(Resources.toByteArray(ApkSigner.class, inResourceName))); apkSignerBuilder .setInputApk(in) .setOutputApk(outFile) .build() .sign(); } @Test public void testAlignmentPreserved_Golden() throws Exception { // Regression tests for preserving (mis)alignment of ZIP Local File Header data // NOTE: Expected output files can be re-generated by running the "main" method. List rsa2048SignerConfig = Collections.singletonList(getDefaultSignerConfigFromResources("rsa-2048")); // Uncompressed entries in this input file are not aligned -- the file was created using // the jar utility. temp4.txt entry was then manually added into the archive. This entry's // ZIP Local File Header "extra" field declares that the entry's data must be aligned to // 4 kB boundary, but the data isn't actually aligned in the file. assertGolden( "golden-unaligned-in.apk", "golden-unaligned-out.apk", new ApkSigner.Builder(rsa2048SignerConfig)); assertGolden( "golden-unaligned-in.apk", "golden-unaligned-v1-out.apk", new ApkSigner.Builder(rsa2048SignerConfig) .setV1SigningEnabled(true) .setV2SigningEnabled(false)); assertGolden( "golden-unaligned-in.apk", "golden-unaligned-v2-out.apk", new ApkSigner.Builder(rsa2048SignerConfig) .setV1SigningEnabled(false) .setV2SigningEnabled(true)); assertGolden( "golden-unaligned-in.apk", "golden-unaligned-v1v2-out.apk", new ApkSigner.Builder(rsa2048SignerConfig) .setV1SigningEnabled(true) .setV2SigningEnabled(true)); // Uncompressed entries in this input file are aligned by zero-padding the "extra" field, as // performed by zipalign at the time of writing. This padding technique produces ZIP // archives whose "extra" field are not compliant with APPNOTE.TXT. Hence, this technique // was deprecated. assertGolden( "golden-legacy-aligned-in.apk", "golden-legacy-aligned-out.apk", new ApkSigner.Builder(rsa2048SignerConfig)); assertGolden( "golden-legacy-aligned-in.apk", "golden-legacy-aligned-v1-out.apk", new ApkSigner.Builder(rsa2048SignerConfig) .setV1SigningEnabled(true) .setV2SigningEnabled(false)); assertGolden( "golden-legacy-aligned-in.apk", "golden-legacy-aligned-v2-out.apk", new ApkSigner.Builder(rsa2048SignerConfig) .setV1SigningEnabled(false) .setV2SigningEnabled(true)); assertGolden( "golden-legacy-aligned-in.apk", "golden-legacy-aligned-v1v2-out.apk", new ApkSigner.Builder(rsa2048SignerConfig) .setV1SigningEnabled(true) .setV2SigningEnabled(true)); // Uncompressed entries in this input file are aligned by padding the "extra" field, as // generated by signapk and apksigner. This padding technique produces "extra" fields which // are compliant with APPNOTE.TXT. assertGolden( "golden-aligned-in.apk", "golden-aligned-out.apk", new ApkSigner.Builder(rsa2048SignerConfig)); assertGolden( "golden-aligned-in.apk", "golden-aligned-v1-out.apk", new ApkSigner.Builder(rsa2048SignerConfig) .setV1SigningEnabled(true) .setV2SigningEnabled(false)); assertGolden( "golden-aligned-in.apk", "golden-aligned-v2-out.apk", new ApkSigner.Builder(rsa2048SignerConfig) .setV1SigningEnabled(false) .setV2SigningEnabled(true)); assertGolden( "golden-aligned-in.apk", "golden-aligned-v1v2-out.apk", new ApkSigner.Builder(rsa2048SignerConfig) .setV1SigningEnabled(true) .setV2SigningEnabled(true)); } @Test public void testMinSdkVersion_Golden() throws Exception { // Regression tests for minSdkVersion-based signature/digest algorithm selection // NOTE: Expected output files can be re-generated by running the "main" method. List rsaSignerConfig = Collections.singletonList(getDefaultSignerConfigFromResources("rsa-2048")); assertGolden("original.apk", "golden-rsa-out.apk", new ApkSigner.Builder(rsaSignerConfig)); assertGolden( "original.apk", "golden-rsa-minSdkVersion-1-out.apk", new ApkSigner.Builder(rsaSignerConfig).setMinSdkVersion(1)); assertGolden( "original.apk", "golden-rsa-minSdkVersion-18-out.apk", new ApkSigner.Builder(rsaSignerConfig).setMinSdkVersion(18)); assertGolden( "original.apk", "golden-rsa-minSdkVersion-24-out.apk", new ApkSigner.Builder(rsaSignerConfig).setMinSdkVersion(24)); // TODO: Add tests for DSA and ECDSA. This is non-trivial because the default // implementations of these signature algorithms are non-deterministic which means output // files always differ from golden files. } @Test public void testRsaSignedVerifies() throws Exception { List signers = Collections.singletonList(getDefaultSignerConfigFromResources("rsa-2048")); String in = "original.apk"; // Sign so that the APK is guaranteed to verify on API Level 1+ DataSource out = sign(in, new ApkSigner.Builder(signers).setMinSdkVersion(1)); assertVerified(verifyForMinSdkVersion(out, 1)); // Sign so that the APK is guaranteed to verify on API Level 18+ out = sign(in, new ApkSigner.Builder(signers).setMinSdkVersion(18)); assertVerified(verifyForMinSdkVersion(out, 18)); // Does not verify on API Level 17 because RSA with SHA-256 not supported assertVerificationFailure( verifyForMinSdkVersion(out, 17), Issue.JAR_SIG_UNSUPPORTED_SIG_ALG); } @Test public void testDsaSignedVerifies() throws Exception { List signers = Collections.singletonList(getDefaultSignerConfigFromResources("dsa-1024")); String in = "original.apk"; // Sign so that the APK is guaranteed to verify on API Level 1+ DataSource out = sign(in, new ApkSigner.Builder(signers).setMinSdkVersion(1)); assertVerified(verifyForMinSdkVersion(out, 1)); // Sign so that the APK is guaranteed to verify on API Level 21+ out = sign(in, new ApkSigner.Builder(signers).setMinSdkVersion(21)); assertVerified(verifyForMinSdkVersion(out, 21)); // Does not verify on API Level 20 because DSA with SHA-256 not supported assertVerificationFailure( verifyForMinSdkVersion(out, 20), Issue.JAR_SIG_UNSUPPORTED_SIG_ALG); } @Test public void testEcSignedVerifies() throws Exception { List signers = Collections.singletonList(getDefaultSignerConfigFromResources("ec-p256")); String in = "original.apk"; // NOTE: EC APK signatures are not supported prior to API Level 18 // Sign so that the APK is guaranteed to verify on API Level 18+ DataSource out = sign(in, new ApkSigner.Builder(signers).setMinSdkVersion(18)); assertVerified(verifyForMinSdkVersion(out, 18)); // Does not verify on API Level 17 because EC not supported assertVerificationFailure( verifyForMinSdkVersion(out, 17), Issue.JAR_SIG_UNSUPPORTED_SIG_ALG); } @Test public void testV1SigningRejectsInvalidZipEntryNames() throws Exception { // ZIP/JAR entry name cannot contain CR, LF, or NUL characters when the APK is being // JAR-signed. List signers = Collections.singletonList(getDefaultSignerConfigFromResources("rsa-2048")); try { sign("v1-only-with-cr-in-entry-name.apk", new ApkSigner.Builder(signers).setV1SigningEnabled(true)); fail(); } catch (ApkFormatException expected) {} try { sign("v1-only-with-lf-in-entry-name.apk", new ApkSigner.Builder(signers).setV1SigningEnabled(true)); fail(); } catch (ApkFormatException expected) {} try { sign("v1-only-with-nul-in-entry-name.apk", new ApkSigner.Builder(signers).setV1SigningEnabled(true)); fail(); } catch (ApkFormatException expected) {} } @Test public void testWeirdZipCompressionMethod() throws Exception { // Any ZIP compression method other than STORED is treated as DEFLATED by Android. // This APK declares compression method 21 (neither STORED nor DEFLATED) for CERT.RSA entry, // but the entry is actually Deflate-compressed. List signers = Collections.singletonList(getDefaultSignerConfigFromResources("rsa-2048")); sign("weird-compression-method.apk", new ApkSigner.Builder(signers)); } @Test public void testZipCompressionMethodMismatchBetweenLfhAndCd() throws Exception { // Android Package Manager ignores compressionMethod field in Local File Header and always // uses the compressionMethod from Central Directory instead. // In this APK, compression method of CERT.RSA is declared as STORED in Local File Header // and as DEFLATED in Central Directory. The entry is actually Deflate-compressed. List signers = Collections.singletonList(getDefaultSignerConfigFromResources("rsa-2048")); sign("mismatched-compression-method.apk", new ApkSigner.Builder(signers)); } /** * Asserts that signing the specified golden input file using the provided signing * configuration produces output identical to the specified golden output file. */ private void assertGolden( String inResourceName, String expectedOutResourceName, ApkSigner.Builder apkSignerBuilder) throws Exception { // Sign the provided golden input DataSource out = sign(inResourceName, apkSignerBuilder); // Assert that the output is identical to the provided golden output if (out.size() > Integer.MAX_VALUE) { throw new RuntimeException("Output too large: " + out.size() + " bytes"); } ByteBuffer actualOutBuf = out.getByteBuffer(0, (int) out.size()); ByteBuffer expectedOutBuf = ByteBuffer.wrap(Resources.toByteArray(getClass(), expectedOutResourceName)); int actualStartPos = actualOutBuf.position(); boolean identical = false; if (actualOutBuf.remaining() == expectedOutBuf.remaining()) { while (actualOutBuf.hasRemaining()) { if (actualOutBuf.get() != expectedOutBuf.get()) { break; } } identical = !actualOutBuf.hasRemaining(); } if (identical) { return; } actualOutBuf.position(actualStartPos); if (KEEP_FAILING_OUTPUT_AS_FILES) { File tmp = File.createTempFile(getClass().getSimpleName(), ".apk"); try (ByteChannel outChannel = Files.newByteChannel( tmp.toPath(), StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) { while (actualOutBuf.hasRemaining()) { outChannel.write(actualOutBuf); } } fail(tmp + " differs from " + expectedOutResourceName); } else { fail("Output differs from " + expectedOutResourceName); } } private DataSource sign( String inResourceName, ApkSigner.Builder apkSignerBuilder) throws Exception { DataSource in = DataSources.asDataSource( ByteBuffer.wrap(Resources.toByteArray(getClass(), inResourceName))); ReadableDataSink out = DataSinks.newInMemoryDataSink(); apkSignerBuilder .setInputApk(in) .setOutputApk(out) .build() .sign(); return out; } private static ApkVerifier.Result verifyForMinSdkVersion(DataSource apk, int minSdkVersion) throws IOException, ApkFormatException, NoSuchAlgorithmException { return verify(apk, minSdkVersion); } private static ApkVerifier.Result verify(DataSource apk, Integer minSdkVersionOverride) throws IOException, ApkFormatException, NoSuchAlgorithmException { ApkVerifier.Builder builder = new ApkVerifier.Builder(apk); if (minSdkVersionOverride != null) { builder.setMinCheckedPlatformVersion(minSdkVersionOverride); } return builder.build().verify(); } private static void assertVerified(ApkVerifier.Result result) { ApkVerifierTest.assertVerified(result); } private static void assertVerificationFailure(ApkVerifier.Result result, Issue expectedIssue) { ApkVerifierTest.assertVerificationFailure(result, expectedIssue); } private static ApkSigner.SignerConfig getDefaultSignerConfigFromResources( String keyNameInResources) throws Exception { PrivateKey privateKey = Resources.toPrivateKey(ApkSignerTest.class, keyNameInResources + ".pk8"); List certs = Resources.toCertificateChain(ApkSignerTest.class, keyNameInResources + ".x509.pem"); return new ApkSigner.SignerConfig.Builder(keyNameInResources, privateKey, certs).build(); } } src/test/java/com/android/apksig/ApkVerifierTest.java0100644 0000000 0000000 00000141774 13243353143 021701 0ustar000000000 0000000 /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import static org.junit.Assume.assumeNoException; import com.android.apksig.ApkVerifier.Issue; import com.android.apksig.ApkVerifier.IssueWithParams; import com.android.apksig.apk.ApkFormatException; import com.android.apksig.internal.util.AndroidSdkVersion; import com.android.apksig.internal.util.HexEncoding; import com.android.apksig.internal.util.Resources; import com.android.apksig.util.DataSources; import java.io.IOException; import java.nio.ByteBuffer; import java.security.InvalidKeyException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.PublicKey; import java.security.Security; import java.security.Signature; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.util.List; import java.util.Locale; import org.junit.Assume; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @RunWith(JUnit4.class) public class ApkVerifierTest { private static final String[] DSA_KEY_NAMES = {"1024", "2048", "3072"}; private static final String[] DSA_KEY_NAMES_1024_AND_SMALLER = {"1024"}; private static final String[] DSA_KEY_NAMES_2048_AND_LARGER = {"2048", "3072"}; private static final String[] EC_KEY_NAMES = {"p256", "p384", "p521"}; private static final String[] RSA_KEY_NAMES = {"1024", "2048", "3072", "4096", "8192", "16384"}; private static final String[] RSA_KEY_NAMES_2048_AND_LARGER = {"2048", "3072", "4096", "8192", "16384"}; @Test public void testOriginalAccepted() throws Exception { // APK signed with v1 and v2 schemes. Obtained by building // cts/hostsidetests/appsecurity/test-apps/tinyapp. // This APK is used as a basis for many of the other tests here. Hence, we check that this // APK verifies. assertVerified(verify("original.apk")); } @Test public void testV1OneSignerMD5withRSAAccepted() throws Exception { // APK signed with v1 scheme only, one signer assertVerifiedForEach( "v1-only-with-rsa-pkcs1-md5-1.2.840.113549.1.1.1-%s.apk", RSA_KEY_NAMES); assertVerifiedForEach( "v1-only-with-rsa-pkcs1-md5-1.2.840.113549.1.1.4-%s.apk", RSA_KEY_NAMES); } @Test public void testV1OneSignerSHA1withRSAAccepted() throws Exception { // APK signed with v1 scheme only, one signer assertVerifiedForEach( "v1-only-with-rsa-pkcs1-sha1-1.2.840.113549.1.1.1-%s.apk", RSA_KEY_NAMES); assertVerifiedForEach( "v1-only-with-rsa-pkcs1-sha1-1.2.840.113549.1.1.5-%s.apk", RSA_KEY_NAMES); } @Test public void testV1OneSignerSHA224withRSAAccepted() throws Exception { // APK signed with v1 scheme only, one signer assertVerifiedForEach( "v1-only-with-rsa-pkcs1-sha224-1.2.840.113549.1.1.1-%s.apk", RSA_KEY_NAMES); assertVerifiedForEach( "v1-only-with-rsa-pkcs1-sha224-1.2.840.113549.1.1.14-%s.apk", RSA_KEY_NAMES); } @Test public void testV1OneSignerSHA256withRSAAccepted() throws Exception { // APK signed with v1 scheme only, one signer assertVerifiedForEach( "v1-only-with-rsa-pkcs1-sha256-1.2.840.113549.1.1.1-%s.apk", RSA_KEY_NAMES); assertVerifiedForEach( "v1-only-with-rsa-pkcs1-sha256-1.2.840.113549.1.1.11-%s.apk", RSA_KEY_NAMES); } @Test public void testV1OneSignerSHA384withRSAAccepted() throws Exception { // APK signed with v1 scheme only, one signer assertVerifiedForEach( "v1-only-with-rsa-pkcs1-sha384-1.2.840.113549.1.1.1-%s.apk", RSA_KEY_NAMES); assertVerifiedForEach( "v1-only-with-rsa-pkcs1-sha384-1.2.840.113549.1.1.12-%s.apk", RSA_KEY_NAMES); } @Test public void testV1OneSignerSHA512withRSAVerifies() throws Exception { // APK signed with v1 scheme only, one signer assertVerifiedForEach( "v1-only-with-rsa-pkcs1-sha512-1.2.840.113549.1.1.1-%s.apk", RSA_KEY_NAMES); assertVerifiedForEach( "v1-only-with-rsa-pkcs1-sha512-1.2.840.113549.1.1.13-%s.apk", RSA_KEY_NAMES); } @Test public void testV1OneSignerSHA1withECDSAAccepted() throws Exception { // APK signed with v1 scheme only, one signer assertVerifiedForEach( "v1-only-with-ecdsa-sha1-1.2.840.10045.2.1-%s.apk", EC_KEY_NAMES); assertVerifiedForEach( "v1-only-with-ecdsa-sha1-1.2.840.10045.4.1-%s.apk", EC_KEY_NAMES); } @Test public void testV1OneSignerSHA224withECDSAAccepted() throws Exception { // APK signed with v1 scheme only, one signer assertVerifiedForEach( "v1-only-with-ecdsa-sha224-1.2.840.10045.2.1-%s.apk", EC_KEY_NAMES); assertVerifiedForEach( "v1-only-with-ecdsa-sha224-1.2.840.10045.4.3.1-%s.apk", EC_KEY_NAMES); } @Test public void testV1OneSignerSHA256withECDSAAccepted() throws Exception { // APK signed with v1 scheme only, one signer assertVerifiedForEach( "v1-only-with-ecdsa-sha256-1.2.840.10045.2.1-%s.apk", EC_KEY_NAMES); assertVerifiedForEach( "v1-only-with-ecdsa-sha256-1.2.840.10045.4.3.2-%s.apk", EC_KEY_NAMES); } @Test public void testV1OneSignerSHA384withECDSAAccepted() throws Exception { // APK signed with v1 scheme only, one signer assertVerifiedForEach( "v1-only-with-ecdsa-sha384-1.2.840.10045.2.1-%s.apk", EC_KEY_NAMES); assertVerifiedForEach( "v1-only-with-ecdsa-sha384-1.2.840.10045.4.3.3-%s.apk", EC_KEY_NAMES); } @Test public void testV1OneSignerSHA512withECDSAAccepted() throws Exception { // APK signed with v1 scheme only, one signer assertVerifiedForEach( "v1-only-with-ecdsa-sha512-1.2.840.10045.2.1-%s.apk", EC_KEY_NAMES); assertVerifiedForEach( "v1-only-with-ecdsa-sha512-1.2.840.10045.4.3.4-%s.apk", EC_KEY_NAMES); } @Test public void testV1OneSignerSHA1withDSAAccepted() throws Exception { // APK signed with v1 scheme only, one signer // NOTE: This test is split into two because JCA Providers shipping with OpenJDK refuse to // verify DSA signatures with keys too long for the SHA-1 digest. assertVerifiedForEach( "v1-only-with-dsa-sha1-1.2.840.10040.4.1-%s.apk", DSA_KEY_NAMES_1024_AND_SMALLER); assertVerifiedForEach( "v1-only-with-dsa-sha1-1.2.840.10040.4.3-%s.apk", DSA_KEY_NAMES_1024_AND_SMALLER); } @Test public void testV1OneSignerSHA1withDSAAcceptedWithKeysTooLongForDigest() throws Exception { // APK signed with v1 scheme only, one signer // OpenJDK's default implementation of Signature.SHA1withDSA refuses to verify signatures // created with keys too long for the digest used. Android Package Manager does not reject // such signatures. We thus skip this test if Signature.SHA1withDSA exhibits this issue. PublicKey publicKey = Resources.toCertificate(getClass(), "dsa-2048.x509.pem").getPublicKey(); Signature s = Signature.getInstance("SHA1withDSA"); try { s.initVerify(publicKey); } catch (InvalidKeyException e) { assumeNoException(e); } assertVerifiedForEach( "v1-only-with-dsa-sha1-1.2.840.10040.4.1-%s.apk", DSA_KEY_NAMES_2048_AND_LARGER); assertVerifiedForEach( "v1-only-with-dsa-sha1-1.2.840.10040.4.3-%s.apk", DSA_KEY_NAMES_2048_AND_LARGER); } @Test public void testV1OneSignerSHA224withDSAAccepted() throws Exception { // APK signed with v1 scheme only, one signer // NOTE: This test is split into two because JCA Providers shipping with OpenJDK refuse to // verify DSA signatures with keys too long for the SHA-224 digest. assertVerifiedForEach( "v1-only-with-dsa-sha224-1.2.840.10040.4.1-%s.apk", DSA_KEY_NAMES_1024_AND_SMALLER); assertVerifiedForEach( "v1-only-with-dsa-sha224-2.16.840.1.101.3.4.3.1-%s.apk", DSA_KEY_NAMES_1024_AND_SMALLER); } @Test public void testV1OneSignerSHA224withDSAAcceptedWithKeysTooLongForDigest() throws Exception { // APK signed with v1 scheme only, one signer // OpenJDK's default implementation of Signature.SHA224withDSA refuses to verify signatures // created with keys too long for the digest used. Android Package Manager does not reject // such signatures. We thus skip this test if Signature.SHA224withDSA exhibits this issue. PublicKey publicKey = Resources.toCertificate(getClass(), "dsa-2048.x509.pem").getPublicKey(); Signature s = Signature.getInstance("SHA224withDSA"); try { s.initVerify(publicKey); } catch (InvalidKeyException e) { assumeNoException(e); } assertVerifiedForEach( "v1-only-with-dsa-sha224-1.2.840.10040.4.1-%s.apk", DSA_KEY_NAMES_2048_AND_LARGER); assertVerifiedForEach( "v1-only-with-dsa-sha224-2.16.840.1.101.3.4.3.1-%s.apk", DSA_KEY_NAMES_2048_AND_LARGER); } @Test public void testV1OneSignerSHA256withDSAAccepted() throws Exception { // APK signed with v1 scheme only, one signer assertVerifiedForEach( "v1-only-with-dsa-sha256-1.2.840.10040.4.1-%s.apk", DSA_KEY_NAMES); assertVerifiedForEach( "v1-only-with-dsa-sha256-2.16.840.1.101.3.4.3.2-%s.apk", DSA_KEY_NAMES); } @Test public void testV2StrippedRejected() throws Exception { // APK signed with v1 and v2 schemes, but v2 signature was stripped from the file (by using // zipalign). // This should fail because the v1 signature indicates that the APK was supposed to be // signed with v2 scheme as well, making the platform's anti-stripping protections reject // the APK. assertVerificationFailure( "v2-stripped.apk", Issue.JAR_SIG_MISSING_APK_SIG_REFERENCED); // Similar to above, but the X-Android-APK-Signed anti-stripping header in v1 signature // lists unknown signature schemes in addition to APK Signature Scheme v2. Unknown schemes // should be ignored. assertVerificationFailure( "v2-stripped-with-ignorable-signing-schemes.apk", Issue.JAR_SIG_MISSING_APK_SIG_REFERENCED); } @Test public void testV2OneSignerOneSignatureAccepted() throws Exception { // APK signed with v2 scheme only, one signer, one signature assertVerifiedForEachForMinSdkVersion( "v2-only-with-dsa-sha256-%s.apk", DSA_KEY_NAMES, AndroidSdkVersion.N); assertVerifiedForEachForMinSdkVersion( "v2-only-with-ecdsa-sha256-%s.apk", EC_KEY_NAMES, AndroidSdkVersion.N); assertVerifiedForEachForMinSdkVersion( "v2-only-with-rsa-pkcs1-sha256-%s.apk", RSA_KEY_NAMES, AndroidSdkVersion.N); // RSA-PSS signatures tested in a separate test below // DSA with SHA-512 is not supported by Android platform and thus APK Signature Scheme v2 // does not support that either // assertInstallSucceedsForEach("v2-only-with-dsa-sha512-%s.apk", DSA_KEY_NAMES); assertVerifiedForEachForMinSdkVersion( "v2-only-with-ecdsa-sha512-%s.apk", EC_KEY_NAMES, AndroidSdkVersion.N); assertVerifiedForEachForMinSdkVersion( "v2-only-with-rsa-pkcs1-sha512-%s.apk", RSA_KEY_NAMES, AndroidSdkVersion.N); } @Test public void testV2OneSignerOneRsaPssSignatureAccepted() throws Exception { assumeThatRsaPssAvailable(); // APK signed with v2 scheme only, one signer, one signature assertVerifiedForEachForMinSdkVersion( "v2-only-with-rsa-pss-sha256-%s.apk", RSA_KEY_NAMES, AndroidSdkVersion.N); assertVerifiedForEachForMinSdkVersion( "v2-only-with-rsa-pss-sha512-%s.apk", RSA_KEY_NAMES_2048_AND_LARGER, // 1024-bit key is too short for PSS with SHA-512 AndroidSdkVersion.N); } @Test public void testV2SignatureDoesNotMatchSignedDataRejected() throws Exception { // APK signed with v2 scheme only, but the signature over signed-data does not verify // Bitflip in certificate field inside signed-data. Based on // v2-only-with-dsa-sha256-1024.apk. assertVerificationFailure( "v2-only-with-dsa-sha256-1024-sig-does-not-verify.apk", Issue.V2_SIG_DID_NOT_VERIFY); // Signature claims to be RSA PKCS#1 v1.5 with SHA-256, but is actually using SHA-512. // Based on v2-only-with-rsa-pkcs1-sha256-2048.apk. assertVerificationFailure( "v2-only-with-rsa-pkcs1-sha256-2048-sig-does-not-verify.apk", Issue.V2_SIG_VERIFY_EXCEPTION); // Bitflip in the ECDSA signature. Based on v2-only-with-ecdsa-sha256-p256.apk. assertVerificationFailure( "v2-only-with-ecdsa-sha256-p256-sig-does-not-verify.apk", Issue.V2_SIG_DID_NOT_VERIFY); } @Test public void testV2RsaPssSignatureDoesNotMatchSignedDataRejected() throws Exception { assumeThatRsaPssAvailable(); // APK signed with v2 scheme only, but the signature over signed-data does not verify. // Signature claims to be RSA PSS with SHA-256 and 32 bytes of salt, but is actually using 0 // bytes of salt. Based on v2-only-with-rsa-pkcs1-sha256-2048.apk. Obtained by modifying APK // signer to use the wrong amount of salt. assertVerificationFailure( "v2-only-with-rsa-pss-sha256-2048-sig-does-not-verify.apk", Issue.V2_SIG_DID_NOT_VERIFY); } @Test public void testV2ContentDigestMismatchRejected() throws Exception { // APK signed with v2 scheme only, but the digest of contents does not match the digest // stored in signed-data ApkVerifier.Issue error = Issue.V2_SIG_APK_DIGEST_DID_NOT_VERIFY; // Based on v2-only-with-rsa-pkcs1-sha512-4096.apk. Obtained by modifying APK signer to // flip the leftmost bit in content digest before signing signed-data. assertVerificationFailure( "v2-only-with-rsa-pkcs1-sha512-4096-digest-mismatch.apk", error); // Based on v2-only-with-ecdsa-sha256-p256.apk. Obtained by modifying APK signer to flip the // leftmost bit in content digest before signing signed-data. assertVerificationFailure( "v2-only-with-ecdsa-sha256-p256-digest-mismatch.apk", error); } @Test public void testNoApkSignatureSchemeBlockRejected() throws Exception { // APK signed with v2 scheme only, but the rules for verifying APK Signature Scheme v2 // signatures say that this APK must not be verified using APK Signature Scheme v2. // Obtained from v2-only-with-rsa-pkcs1-sha512-4096.apk by flipping a bit in the magic // field in the footer of APK Signing Block. This makes the APK Signing Block disappear. assertVerificationFailure( "v2-only-wrong-apk-sig-block-magic.apk", Issue.JAR_SIG_NO_MANIFEST); // Obtained by modifying APK signer to insert "GARBAGE" between ZIP Central Directory and // End of Central Directory. The APK is otherwise fine and is signed with APK Signature // Scheme v2. Based on v2-only-with-rsa-pkcs1-sha256.apk. assertVerificationFailure( "v2-only-garbage-between-cd-and-eocd.apk", Issue.JAR_SIG_NO_MANIFEST); // Obtained by modifying the size in APK Signature Block header. Based on // v2-only-with-ecdsa-sha512-p521.apk. assertVerificationFailure( "v2-only-apk-sig-block-size-mismatch.apk", Issue.JAR_SIG_NO_MANIFEST); // Obtained by modifying the ID under which APK Signature Scheme v2 Block is stored in // APK Signing Block and by modifying the APK signer to not insert anti-stripping // protections into JAR Signature. The APK should appear as having no APK Signature Scheme // v2 Block and should thus successfully verify using JAR Signature Scheme. assertVerified(verify("v1-with-apk-sig-block-but-without-apk-sig-scheme-v2-block.apk")); } @Test(expected = ApkFormatException.class) public void testTruncatedZipCentralDirectoryRejected() throws Exception { // Obtained by modifying APK signer to truncate the ZIP Central Directory by one byte. The // APK is otherwise fine and is signed with APK Signature Scheme v2. Based on // v2-only-with-rsa-pkcs1-sha256.apk verify("v2-only-truncated-cd.apk"); } @Test public void testV2UnknownPairIgnoredInApkSigningBlock() throws Exception { // Obtained by modifying APK signer to emit an unknown ID-value pair into APK Signing Block // before the ID-value pair containing the APK Signature Scheme v2 Block. The unknown // ID-value should be ignored. assertVerified( verifyForMinSdkVersion( "v2-only-unknown-pair-in-apk-sig-block.apk", AndroidSdkVersion.N)); } @Test public void testV2UnknownSignatureAlgorithmsIgnored() throws Exception { // APK is signed with a known signature algorithm and with a couple of unknown ones. // Obtained by modifying APK signer to use "unknown" signature algorithms in addition to // known ones. assertVerified( verifyForMinSdkVersion( "v2-only-with-ignorable-unsupported-sig-algs.apk", AndroidSdkVersion.N)); } @Test public void testV2MismatchBetweenSignaturesAndDigestsBlockRejected() throws Exception { // APK is signed with a single signature algorithm, but the digests block claims that it is // signed with two different signature algorithms. Obtained by modifying APK Signer to // emit an additional digest record with signature algorithm 0x12345678. assertVerificationFailure( "v2-only-signatures-and-digests-block-mismatch.apk", Issue.V2_SIG_SIG_ALG_MISMATCH_BETWEEN_SIGNATURES_AND_DIGESTS_RECORDS); } @Test public void testV2MismatchBetweenPublicKeyAndCertificateRejected() throws Exception { // APK is signed with v2 only. The public key field does not match the public key in the // leaf certificate. Obtained by modifying APK signer to write out a modified leaf // certificate where the RSA modulus has a bitflip. assertVerificationFailure( "v2-only-cert-and-public-key-mismatch.apk", Issue.V2_SIG_PUBLIC_KEY_MISMATCH_BETWEEN_CERTIFICATE_AND_SIGNATURES_RECORD); } @Test public void testV2SignerBlockWithNoCertificatesRejected() throws Exception { // APK is signed with v2 only. There are no certificates listed in the signer block. // Obtained by modifying APK signer to output no certificates. assertVerificationFailure( "v2-only-no-certs-in-sig.apk", Issue.V2_SIG_NO_CERTIFICATES); } @Test public void testTwoSignersAccepted() throws Exception { // APK signed by two different signers assertVerified(verify("two-signers.apk")); assertVerified(verify("v1-only-two-signers.apk")); assertVerified(verifyForMinSdkVersion("v2-only-two-signers.apk", AndroidSdkVersion.N)); } @Test public void testV2TwoSignersRejectedWhenOneBroken() throws Exception { // Bitflip in the ECDSA signature of second signer. Based on two-signers.apk. // This asserts that breakage in any signer leads to rejection of the APK. assertVerificationFailure( "two-signers-second-signer-v2-broken.apk", Issue.V2_SIG_DID_NOT_VERIFY); } @Test public void testV2TwoSignersRejectedWhenOneWithoutSignatures() throws Exception { // APK v2-signed by two different signers. However, there are no signatures for the second // signer. assertVerificationFailure( "v2-only-two-signers-second-signer-no-sig.apk", Issue.V2_SIG_NO_SIGNATURES); } @Test public void testV2TwoSignersRejectedWhenOneWithoutSupportedSignatures() throws Exception { // APK v2-signed by two different signers. However, there are no supported signatures for // the second signer. assertVerificationFailure( "v2-only-two-signers-second-signer-no-supported-sig.apk", Issue.V2_SIG_NO_SUPPORTED_SIGNATURES); } @Test public void testCorrectCertUsedFromPkcs7SignedDataCertsSet() throws Exception { // Obtained by prepending the rsa-1024 certificate to the PKCS#7 SignedData certificates set // of v1-only-with-rsa-pkcs1-sha1-1.2.840.113549.1.1.1-2048.apk META-INF/CERT.RSA. The certs // (in the order of appearance in the file) are thus: rsa-1024, rsa-2048. The package's // signing cert is rsa-2048. ApkVerifier.Result result = verify("v1-only-pkcs7-cert-bag-first-cert-not-used.apk"); assertVerified(result); List signingCerts = result.getSignerCertificates(); assertEquals(1, signingCerts.size()); assertEquals( "fb5dbd3c669af9fc236c6991e6387b7f11ff0590997f22d0f5c74ff40e04fca8", HexEncoding.encode(sha256(signingCerts.get(0).getEncoded()))); } @Test public void testV1SchemeSignatureCertNotReencoded() throws Exception { // Regression test for b/30148997 and b/18228011. When PackageManager does not preserve the // original encoded form of signing certificates, bad things happen, such as rejection of // completely valid updates to apps. The issue in b/30148997 and b/18228011 was that // PackageManager started re-encoding signing certs into DER. This normally produces exactly // the original form because X.509 certificates are supposed to be DER-encoded. However, a // small fraction of Android apps uses X.509 certificates which are not DER-encoded. For // such apps, re-encoding into DER changes the serialized form of the certificate, creating // a mismatch with the serialized form stored in the PackageManager database, leading to the // rejection of updates for the app. // // v1-only-with-rsa-1024-cert-not-der.apk cert's signature is not DER-encoded. It is // BER-encoded, with length encoded as two bytes instead of just one. // v1-only-with-rsa-1024-cert-not-der.apk META-INF/CERT.RSA was obtained from // v1-only-with-rsa-1024.apk META-INF/CERT.RSA by manually modifying the ASN.1 structure. ApkVerifier.Result result = verify("v1-only-with-rsa-1024-cert-not-der.apk"); // On JDK 8u131 and newer, when the default (SUN) X.509 CertificateFactory implementation is // used, PKCS #7 signature verification fails because the certificate is not DER-encoded. // This contrived block of code disables this test in this scenario. if (!result.isVerified()) { List signers = result.getV1SchemeSigners(); if (signers.size() > 0) { ApkVerifier.Result.V1SchemeSignerInfo signer = signers.get(0); for (IssueWithParams issue : signer.getErrors()) { if (issue.getIssue() == Issue.JAR_SIG_PARSE_EXCEPTION) { CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); if ("SUN".equals(certFactory.getProvider().getName())) { Throwable exception = (Throwable) issue.getParams()[1]; Throwable e = exception; while (e != null) { String msg = e.getMessage(); e = e.getCause(); if ((msg != null) && (msg.contains("Redundant length bytes found"))) { Assume.assumeNoException(exception); } } } break; } } } } assertVerified(result); List signingCerts = result.getSignerCertificates(); assertEquals(1, signingCerts.size()); assertEquals( "c5d4535a7e1c8111687a8374b2198da6f5ff8d811a7a25aa99ef060669342fa9", HexEncoding.encode(sha256(signingCerts.get(0).getEncoded()))); } @Test public void testV1SchemeSignatureCertNotReencoded2() throws Exception { // Regression test for b/30148997 and b/18228011. When PackageManager does not preserve the // original encoded form of signing certificates, bad things happen, such as rejection of // completely valid updates to apps. The issue in b/30148997 and b/18228011 was that // PackageManager started re-encoding signing certs into DER. This normally produces exactly // the original form because X.509 certificates are supposed to be DER-encoded. However, a // small fraction of Android apps uses X.509 certificates which are not DER-encoded. For // such apps, re-encoding into DER changes the serialized form of the certificate, creating // a mismatch with the serialized form stored in the PackageManager database, leading to the // rejection of updates for the app. // // v1-only-with-rsa-1024-cert-not-der2.apk cert's signature is not DER-encoded. It is // BER-encoded, with the BIT STRING value containing an extraneous leading 0x00 byte. // v1-only-with-rsa-1024-cert-not-der2.apk META-INF/CERT.RSA was obtained from // v1-only-with-rsa-1024.apk META-INF/CERT.RSA by manually modifying the ASN.1 structure. ApkVerifier.Result result = verify("v1-only-with-rsa-1024-cert-not-der2.apk"); assertVerified(result); List signingCerts = result.getSignerCertificates(); assertEquals(1, signingCerts.size()); assertEquals( "da3da398de674541313deed77218ce94798531ea5131bb9b1bb4063ba4548cfb", HexEncoding.encode(sha256(signingCerts.get(0).getEncoded()))); } @Test public void testMaxSizedZipEocdCommentAccepted() throws Exception { // Obtained by modifying apksigner to produce a max-sized (0xffff bytes long) ZIP End of // Central Directory comment, and signing the original.apk using the modified apksigner. assertVerified(verify("v1-only-max-sized-eocd-comment.apk")); assertVerified( verifyForMinSdkVersion("v2-only-max-sized-eocd-comment.apk", AndroidSdkVersion.N)); } @Test public void testEmptyApk() throws Exception { // Unsigned empty ZIP archive try { verifyForMinSdkVersion("empty-unsigned.apk", 1); fail("ApkFormatException should've been thrown"); } catch (ApkFormatException expected) {} // JAR-signed empty ZIP archive try { verifyForMinSdkVersion("v1-only-empty.apk", 18); fail("ApkFormatException should've been thrown"); } catch (ApkFormatException expected) {} // APK Signature Scheme v2 signed empty ZIP archive try { verifyForMinSdkVersion("v2-only-empty.apk", AndroidSdkVersion.N); fail("ApkFormatException should've been thrown"); } catch (ApkFormatException expected) {} } @Test public void testTargetSandboxVersion2AndHigher() throws Exception { // This APK (and its variants below) use minSdkVersion 18, meaning it needs to be signed // with v1 and v2 schemes // This APK is signed with v1 and v2 schemes and thus should verify assertVerified(verify("targetSandboxVersion-2.apk")); // v1 signature is needed only if minSdkVersion is lower than 24 assertVerificationFailure( verify("v2-only-targetSandboxVersion-2.apk"), Issue.JAR_SIG_NO_MANIFEST); assertVerified(verifyForMinSdkVersion("v2-only-targetSandboxVersion-2.apk", 24)); // v2 signature is required assertVerificationFailure( verify("v1-only-targetSandboxVersion-2.apk"), Issue.NO_SIG_FOR_TARGET_SANDBOX_VERSION); assertVerificationFailure( verify("unsigned-targetSandboxVersion-2.apk"), Issue.NO_SIG_FOR_TARGET_SANDBOX_VERSION); // minSdkVersion 28, meaning v1 signature not needed assertVerified(verify("v2-only-targetSandboxVersion-3.apk")); } @Test public void testV1MultipleDigestAlgsInManifestAndSignatureFile() throws Exception { // MANIFEST.MF contains SHA-1 and SHA-256 digests for each entry, .SF contains only SHA-1 // digests. This file was obtained by: // jarsigner -sigalg SHA256withRSA -digestalg SHA-256 ... ... // jarsigner -sigalg SHA1withRSA -digestalg SHA1 ... ... assertVerified(verify("v1-sha1-sha256-manifest-and-sha1-sf.apk")); // MANIFEST.MF and .SF contain SHA-1 and SHA-256 digests for each entry. This file was // obtained by modifying apksigner to output multiple digests. assertVerified(verify("v1-sha1-sha256-manifest-and-sf.apk")); // One of the digests is wrong in either MANIFEST.MF or .SF. These files were obtained by // modifying apksigner to output multiple digests and to flip a bit to create a wrong // digest. // SHA-1 digests in MANIFEST.MF are wrong, but SHA-256 digests are OK. // The APK will fail to verify on API Level 17 and lower, but will verify on API Level 18 // and higher. assertVerificationFailure( verify("v1-sha1-sha256-manifest-and-sf-with-sha1-wrong-in-manifest.apk"), Issue.JAR_SIG_ZIP_ENTRY_DIGEST_DID_NOT_VERIFY); assertVerificationFailure( verifyForMaxSdkVersion( "v1-sha1-sha256-manifest-and-sf-with-sha1-wrong-in-manifest.apk", 17), Issue.JAR_SIG_ZIP_ENTRY_DIGEST_DID_NOT_VERIFY); assertVerified( verifyForMinSdkVersion( "v1-sha1-sha256-manifest-and-sf-with-sha1-wrong-in-manifest.apk", 18)); // SHA-1 digests in .SF are wrong, but SHA-256 digests are OK. // The APK will fail to verify on API Level 17 and lower, but will verify on API Level 18 // and higher. assertVerificationFailure( verify("v1-sha1-sha256-manifest-and-sf-with-sha1-wrong-in-sf.apk"), Issue.JAR_SIG_MANIFEST_SECTION_DIGEST_DID_NOT_VERIFY); assertVerificationFailure( verifyForMaxSdkVersion( "v1-sha1-sha256-manifest-and-sf-with-sha1-wrong-in-sf.apk", 17), Issue.JAR_SIG_MANIFEST_SECTION_DIGEST_DID_NOT_VERIFY); assertVerified( verifyForMinSdkVersion( "v1-sha1-sha256-manifest-and-sf-with-sha1-wrong-in-sf.apk", 18)); // SHA-256 digests in MANIFEST.MF are wrong, but SHA-1 digests are OK. // The APK will fail to verify on API Level 18 and higher, but will verify on API Level 17 // and lower. assertVerificationFailure( verify("v1-sha1-sha256-manifest-and-sf-with-sha256-wrong-in-manifest.apk"), Issue.JAR_SIG_ZIP_ENTRY_DIGEST_DID_NOT_VERIFY); assertVerificationFailure( verifyForMinSdkVersion( "v1-sha1-sha256-manifest-and-sf-with-sha256-wrong-in-manifest.apk", 18), Issue.JAR_SIG_ZIP_ENTRY_DIGEST_DID_NOT_VERIFY); assertVerified( verifyForMaxSdkVersion( "v1-sha1-sha256-manifest-and-sf-with-sha256-wrong-in-manifest.apk", 17)); // SHA-256 digests in .SF are wrong, but SHA-1 digests are OK. // The APK will fail to verify on API Level 18 and higher, but will verify on API Level 17 // and lower. assertVerificationFailure( verify("v1-sha1-sha256-manifest-and-sf-with-sha256-wrong-in-sf.apk"), Issue.JAR_SIG_MANIFEST_SECTION_DIGEST_DID_NOT_VERIFY); assertVerificationFailure( verifyForMinSdkVersion( "v1-sha1-sha256-manifest-and-sf-with-sha256-wrong-in-sf.apk", 18), Issue.JAR_SIG_MANIFEST_SECTION_DIGEST_DID_NOT_VERIFY); assertVerified( verifyForMaxSdkVersion( "v1-sha1-sha256-manifest-and-sf-with-sha256-wrong-in-sf.apk", 17)); } @Test public void testV1WithUnsupportedCharacterInZipEntryName() throws Exception { // Android Package Manager does not support ZIP entry names containing CR or LF assertVerificationFailure( verify("v1-only-with-cr-in-entry-name.apk"), Issue.JAR_SIG_UNNNAMED_MANIFEST_SECTION); assertVerificationFailure( verify("v1-only-with-lf-in-entry-name.apk"), Issue.JAR_SIG_UNNNAMED_MANIFEST_SECTION); } @Test public void testWeirdZipCompressionMethod() throws Exception { // Any ZIP compression method other than STORED is treated as DEFLATED by Android. // This APK declares compression method 21 (neither STORED nor DEFLATED) for CERT.RSA entry, // but the entry is actually Deflate-compressed. assertVerified(verify("weird-compression-method.apk")); } @Test public void testZipCompressionMethodMismatchBetweenLfhAndCd() throws Exception { // Android Package Manager ignores compressionMethod field in Local File Header and always // uses the compressionMethod from Central Directory instead. // In this APK, compression method of CERT.RSA is declared as STORED in Local File Header // and as DEFLATED in Central Directory. The entry is actually Deflate-compressed. assertVerified(verify("mismatched-compression-method.apk")); } @Test public void testV1SignedAttrs() throws Exception { String apk = "v1-only-with-signed-attrs.apk"; assertVerificationFailure( verifyForMinSdkVersion(apk, AndroidSdkVersion.JELLY_BEAN_MR2), Issue.JAR_SIG_VERIFY_EXCEPTION); assertVerified(verifyForMinSdkVersion(apk, AndroidSdkVersion.KITKAT)); apk = "v1-only-with-signed-attrs-signerInfo1-good-signerInfo2-good.apk"; assertVerificationFailure( verifyForMinSdkVersion(apk, AndroidSdkVersion.JELLY_BEAN_MR2), Issue.JAR_SIG_VERIFY_EXCEPTION); assertVerified(verifyForMinSdkVersion(apk, AndroidSdkVersion.KITKAT)); } @Test public void testV1SignedAttrsNotInDerOrder() throws Exception { // Android does not re-order SignedAttributes despite it being a SET OF. Pre-N, Android // treated them as SEQUENCE OF, meaning no re-ordering is necessary. From N onwards, it // treats them as SET OF, but does not re-encode into SET OF during verification if all // attributes parsed fine. assertVerified(verify("v1-only-with-signed-attrs-wrong-order.apk")); assertVerified( verify("v1-only-with-signed-attrs-signerInfo1-wrong-order-signerInfo2-good.apk")); } @Test public void testV1SignedAttrsMissingContentType() throws Exception { // SignedAttributes must contain ContentType. Pre-N, Android ignores this requirement. // Android N onwards rejects such APKs. String apk = "v1-only-with-signed-attrs-missing-content-type.apk"; assertVerified(verifyForMaxSdkVersion(apk, AndroidSdkVersion.N - 1)); assertVerificationFailure(verify(apk), Issue.JAR_SIG_VERIFY_EXCEPTION); // Assert that this issue fails verification of the entire signature block, rather than // skipping the broken SignerInfo. The second signer info SignerInfo verifies fine, but // verification does not get there. apk = "v1-only-with-signed-attrs-signerInfo1-missing-content-type-signerInfo2-good.apk"; assertVerified(verifyForMaxSdkVersion(apk, AndroidSdkVersion.N - 1)); assertVerificationFailure(verify(apk), Issue.JAR_SIG_VERIFY_EXCEPTION); } @Test public void testV1SignedAttrsWrongContentType() throws Exception { // ContentType of SignedAttributes must equal SignedData.encapContentInfo.eContentType. // Pre-N, Android ignores this requirement. // From N onwards, Android rejects such SignerInfos. String apk = "v1-only-with-signed-attrs-wrong-content-type.apk"; assertVerified(verifyForMaxSdkVersion(apk, AndroidSdkVersion.N - 1)); assertVerificationFailure(verify(apk), Issue.JAR_SIG_DID_NOT_VERIFY); // First SignerInfo does not verify on Android N and newer, but verification moves on to the // second SignerInfo, which verifies. apk = "v1-only-with-signed-attrs-signerInfo1-wrong-content-type-signerInfo2-good.apk"; assertVerified(verifyForMaxSdkVersion(apk, AndroidSdkVersion.N - 1)); assertVerified(verifyForMinSdkVersion(apk, AndroidSdkVersion.N)); // Although the APK's signature verifies on pre-N and N+, we reject such APKs because the // APK's verification results in different verified SignerInfos (and thus potentially // different signing certs) between pre-N and N+. assertVerificationFailure(verify(apk), Issue.JAR_SIG_DID_NOT_VERIFY); } @Test public void testV1SignedAttrsMissingDigest() throws Exception { // Content digest must be present in SignedAttributes String apk = "v1-only-with-signed-attrs-missing-digest.apk"; assertVerificationFailure( verifyForMaxSdkVersion(apk, AndroidSdkVersion.N - 1), Issue.JAR_SIG_VERIFY_EXCEPTION); assertVerificationFailure( verifyForMinSdkVersion(apk, AndroidSdkVersion.N), Issue.JAR_SIG_VERIFY_EXCEPTION); // Assert that this issue fails verification of the entire signature block, rather than // skipping the broken SignerInfo. The second signer info SignerInfo verifies fine, but // verification does not get there. apk = "v1-only-with-signed-attrs-signerInfo1-missing-digest-signerInfo2-good.apk"; assertVerificationFailure( verifyForMaxSdkVersion(apk, AndroidSdkVersion.N - 1), Issue.JAR_SIG_VERIFY_EXCEPTION); assertVerificationFailure( verifyForMinSdkVersion(apk, AndroidSdkVersion.N), Issue.JAR_SIG_VERIFY_EXCEPTION); } @Test public void testV1SignedAttrsMultipleGoodDigests() throws Exception { // Only one content digest must be present in SignedAttributes String apk = "v1-only-with-signed-attrs-multiple-good-digests.apk"; assertVerificationFailure( verifyForMaxSdkVersion(apk, AndroidSdkVersion.N - 1), Issue.JAR_SIG_PARSE_EXCEPTION); assertVerificationFailure( verifyForMinSdkVersion(apk, AndroidSdkVersion.N), Issue.JAR_SIG_PARSE_EXCEPTION); // Assert that this issue fails verification of the entire signature block, rather than // skipping the broken SignerInfo. The second signer info SignerInfo verifies fine, but // verification does not get there. apk = "v1-only-with-signed-attrs-signerInfo1-multiple-good-digests-signerInfo2-good.apk"; assertVerificationFailure( verifyForMaxSdkVersion(apk, AndroidSdkVersion.N - 1), Issue.JAR_SIG_PARSE_EXCEPTION); assertVerificationFailure( verifyForMinSdkVersion(apk, AndroidSdkVersion.N), Issue.JAR_SIG_PARSE_EXCEPTION); } @Test public void testV1SignedAttrsWrongDigest() throws Exception { // Content digest in SignedAttributes does not match the contents String apk = "v1-only-with-signed-attrs-wrong-digest.apk"; assertVerificationFailure( verifyForMaxSdkVersion(apk, AndroidSdkVersion.N - 1), Issue.JAR_SIG_DID_NOT_VERIFY); assertVerificationFailure( verifyForMinSdkVersion(apk, AndroidSdkVersion.N), Issue.JAR_SIG_DID_NOT_VERIFY); // First SignerInfo does not verify, but Android N and newer moves on to the second // SignerInfo, which verifies. apk = "v1-only-with-signed-attrs-signerInfo1-wrong-digest-signerInfo2-good.apk"; assertVerificationFailure( verifyForMaxSdkVersion(apk, AndroidSdkVersion.N - 1), Issue.JAR_SIG_DID_NOT_VERIFY); assertVerified(verifyForMinSdkVersion(apk, AndroidSdkVersion.N)); } @Test public void testV1SignedAttrsWrongSignature() throws Exception { // Signature over SignedAttributes does not verify String apk = "v1-only-with-signed-attrs-wrong-signature.apk"; assertVerificationFailure( verifyForMaxSdkVersion(apk, AndroidSdkVersion.N - 1), Issue.JAR_SIG_DID_NOT_VERIFY); assertVerificationFailure( verifyForMinSdkVersion(apk, AndroidSdkVersion.N), Issue.JAR_SIG_DID_NOT_VERIFY); // First SignerInfo does not verify, but Android N and newer moves on to the second // SignerInfo, which verifies. apk = "v1-only-with-signed-attrs-signerInfo1-wrong-signature-signerInfo2-good.apk"; assertVerificationFailure( verifyForMaxSdkVersion(apk, AndroidSdkVersion.N - 1), Issue.JAR_SIG_DID_NOT_VERIFY); assertVerified(verifyForMinSdkVersion(apk, AndroidSdkVersion.N)); } private ApkVerifier.Result verify(String apkFilenameInResources) throws IOException, ApkFormatException, NoSuchAlgorithmException { return verify(apkFilenameInResources, null, null); } private ApkVerifier.Result verifyForMinSdkVersion( String apkFilenameInResources, int minSdkVersion) throws IOException, ApkFormatException, NoSuchAlgorithmException { return verify(apkFilenameInResources, minSdkVersion, null); } private ApkVerifier.Result verifyForMaxSdkVersion( String apkFilenameInResources, int maxSdkVersion) throws IOException, ApkFormatException, NoSuchAlgorithmException { return verify(apkFilenameInResources, null, maxSdkVersion); } private ApkVerifier.Result verify( String apkFilenameInResources, Integer minSdkVersionOverride, Integer maxSdkVersionOverride) throws IOException, ApkFormatException, NoSuchAlgorithmException { byte[] apkBytes = Resources.toByteArray(getClass(), apkFilenameInResources); ApkVerifier.Builder builder = new ApkVerifier.Builder(DataSources.asDataSource(ByteBuffer.wrap(apkBytes))); if (minSdkVersionOverride != null) { builder.setMinCheckedPlatformVersion(minSdkVersionOverride); } if (maxSdkVersionOverride != null) { builder.setMaxCheckedPlatformVersion(maxSdkVersionOverride); } return builder.build().verify(); } static void assertVerified(ApkVerifier.Result result) { assertVerified(result, "APK"); } static void assertVerified(ApkVerifier.Result result, String apkId) { if (result.isVerified()) { return; } StringBuilder msg = new StringBuilder(); for (IssueWithParams issue : result.getErrors()) { if (msg.length() > 0) { msg.append('\n'); } msg.append(issue); } for (ApkVerifier.Result.V1SchemeSignerInfo signer : result.getV1SchemeSigners()) { String signerName = signer.getName(); for (IssueWithParams issue : signer.getErrors()) { if (msg.length() > 0) { msg.append('\n'); } msg.append("JAR signer ").append(signerName).append(": ") .append(issue.getIssue()).append(": ").append(issue); } } for (ApkVerifier.Result.V2SchemeSignerInfo signer : result.getV2SchemeSigners()) { String signerName = "signer #" + (signer.getIndex() + 1); for (IssueWithParams issue : signer.getErrors()) { if (msg.length() > 0) { msg.append('\n'); } msg.append("APK Signature Scheme v2 signer ") .append(signerName).append(": ") .append(issue.getIssue()).append(": ").append(issue); } } fail(apkId + " did not verify: " + msg); } private void assertVerified( String apkFilenameInResources, Integer minSdkVersionOverride, Integer maxSdkVersionOverride) throws Exception { assertVerified( verify(apkFilenameInResources, minSdkVersionOverride, maxSdkVersionOverride), apkFilenameInResources); } static void assertVerificationFailure(ApkVerifier.Result result, Issue expectedIssue) { if (result.isVerified()) { fail("APK verification succeeded instead of failing with " + expectedIssue); return; } StringBuilder msg = new StringBuilder(); for (IssueWithParams issue : result.getErrors()) { if (expectedIssue.equals(issue.getIssue())) { return; } if (msg.length() > 0) { msg.append('\n'); } msg.append(issue); } for (ApkVerifier.Result.V1SchemeSignerInfo signer : result.getV1SchemeSigners()) { String signerName = signer.getName(); for (ApkVerifier.IssueWithParams issue : signer.getErrors()) { if (expectedIssue.equals(issue.getIssue())) { return; } if (msg.length() > 0) { msg.append('\n'); } msg.append("JAR signer ").append(signerName).append(": ") .append(issue.getIssue()).append(" ").append(issue); } } for (ApkVerifier.Result.V2SchemeSignerInfo signer : result.getV2SchemeSigners()) { String signerName = "signer #" + (signer.getIndex() + 1); for (IssueWithParams issue : signer.getErrors()) { if (expectedIssue.equals(issue.getIssue())) { return; } if (msg.length() > 0) { msg.append('\n'); } msg.append("APK Signature Scheme v2 signer ") .append(signerName).append(": ").append(issue); } } fail("APK failed verification for the wrong reason" + ". Expected: " + expectedIssue + ", actual: " + msg); } private void assertVerificationFailure( String apkFilenameInResources, ApkVerifier.Issue expectedIssue) throws Exception { assertVerificationFailure(verify(apkFilenameInResources), expectedIssue); } private void assertVerifiedForEach( String apkFilenamePatternInResources, String[] args) throws Exception { assertVerifiedForEach(apkFilenamePatternInResources, args, null, null); } private void assertVerifiedForEach( String apkFilenamePatternInResources, String[] args, Integer minSdkVersionOverride, Integer maxSdkVersionOverride) throws Exception { for (String arg : args) { String apkFilenameInResources = String.format(Locale.US, apkFilenamePatternInResources, arg); assertVerified(apkFilenameInResources, minSdkVersionOverride, maxSdkVersionOverride); } } private void assertVerifiedForEachForMinSdkVersion( String apkFilenameInResources, String[] args, int minSdkVersion) throws Exception { assertVerifiedForEach(apkFilenameInResources, args, minSdkVersion, null); } private static byte[] sha256(byte[] msg) throws Exception { try { return MessageDigest.getInstance("SHA-256").digest(msg); } catch (NoSuchAlgorithmException e) { throw new RuntimeException("Failed to create SHA-256 MessageDigest", e); } } private static void assumeThatRsaPssAvailable() throws Exception { Assume.assumeTrue(Security.getProviders("Signature.SHA256withRSA/PSS") != null); } } src/test/java/com/android/apksig/apk/0040755 0000000 0000000 00000000000 13243353143 016527 5ustar000000000 0000000 src/test/java/com/android/apksig/apk/AllTests.java0100644 0000000 0000000 00000001460 13243353143 021123 0ustar000000000 0000000 /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.apk; import org.junit.runner.RunWith; import org.junit.runners.Suite; @RunWith(Suite.class) @Suite.SuiteClasses({ ApkUtilsTest.class, }) public class AllTests {} src/test/java/com/android/apksig/apk/ApkUtilsTest.java0100644 0000000 0000000 00000003567 13243353143 021776 0ustar000000000 0000000 /* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.apk; import static org.junit.Assert.assertEquals; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @RunWith(JUnit4.class) public class ApkUtilsTest { @Test public void testGetMinSdkVersionForValidCodename() throws Exception { assertEquals(1, ApkUtils.getMinSdkVersionForCodename("AAAA")); assertEquals(2, ApkUtils.getMinSdkVersionForCodename("CUPCAKE")); assertEquals(7, ApkUtils.getMinSdkVersionForCodename("FROYO")); assertEquals(23, ApkUtils.getMinSdkVersionForCodename("N")); assertEquals(23, ApkUtils.getMinSdkVersionForCodename("NMR1")); assertEquals(25, ApkUtils.getMinSdkVersionForCodename("OMG")); // Speculative: Q should be 27 or higher (not yet known at the time of writing) assertEquals(27, ApkUtils.getMinSdkVersionForCodename("QQQ")); } @Test(expected = CodenameMinSdkVersionException.class) public void testGetMinSdkVersionForEmptyCodename() throws Exception { ApkUtils.getMinSdkVersionForCodename(""); } @Test(expected = CodenameMinSdkVersionException.class) public void testGetMinSdkVersionForUnexpectedCodename() throws Exception { ApkUtils.getMinSdkVersionForCodename("1ABC"); } } src/test/java/com/android/apksig/internal/0040755 0000000 0000000 00000000000 13243353143 017570 5ustar000000000 0000000 src/test/java/com/android/apksig/internal/AllTests.java0100644 0000000 0000000 00000001607 13243353143 022167 0ustar000000000 0000000 /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.internal; import org.junit.runner.RunWith; import org.junit.runners.Suite; @RunWith(Suite.class) @Suite.SuiteClasses({ com.android.apksig.internal.asn1.AllTests.class, com.android.apksig.internal.util.AllTests.class, }) public class AllTests {} src/test/java/com/android/apksig/internal/asn1/0040755 0000000 0000000 00000000000 13243353143 020432 5ustar000000000 0000000 src/test/java/com/android/apksig/internal/asn1/AllTests.java0100644 0000000 0000000 00000001730 13243353143 023026 0ustar000000000 0000000 /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.internal.asn1; import org.junit.runner.RunWith; import org.junit.runners.Suite; @RunWith(Suite.class) @Suite.SuiteClasses({ com.android.apksig.internal.asn1.Asn1BerParserTest.class, com.android.apksig.internal.asn1.Asn1DerEncoderTest.class, com.android.apksig.internal.asn1.ber.AllTests.class, }) public class AllTests {} src/test/java/com/android/apksig/internal/asn1/Asn1BerParserTest.java0100644 0000000 0000000 00000033631 13243353143 024550 0ustar000000000 0000000 /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.internal.asn1; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.fail; import com.android.apksig.internal.util.HexEncoding; import java.math.BigInteger; import java.nio.ByteBuffer; import java.util.List; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @RunWith(JUnit4.class) public class Asn1BerParserTest { @Test(expected = NullPointerException.class) public void testNullInput() throws Exception { parse((ByteBuffer) null, EmptySequence.class); } @Test(expected = Asn1DecodingException.class) public void testEmptyInput() throws Exception { parse("", EmptySequence.class); } @Test public void testEmptySequence() throws Exception { // Empty SEQUENCE (0x3000) followed by garbage (0x12345678) ByteBuffer input = ByteBuffer.wrap(HexEncoding.decode("300012345678")); EmptySequence container = parse(input, EmptySequence.class); assertNotNull(container); // Check that input position has been advanced appropriately assertEquals(2, input.position()); } @Test public void testOctetString() throws Exception { assertEquals( "123456", HexEncoding.encode(parse("30050403123456", SequenceWithOctetString.class).buf)); assertEquals( "", HexEncoding.encode(parse("30020400", SequenceWithOctetString.class).buf)); } @Test public void testInteger() throws Exception { // Various Java types decoded from INTEGER // Empty SEQUENCE (0x3000) followed by garbage (0x12345678) SequenceWithIntegers container = parse("301e" + "0201ff" // -1 + "0207ff123456789abc" // -7f123456789abc + "0200" // 0 + "020280ff" // -255 + "020a00000000000000001234", // 0x1234 SequenceWithIntegers.class); assertEquals(-1, container.n1); } @Test public void testOid() throws Exception { // Empty OID try { parse("30020600", SequenceWithOid.class); fail(); } catch (Asn1DecodingException expected) {} assertEquals("2.100.3", parse("30050603813403", SequenceWithOid.class).oid); assertEquals( "2.16.840.1.101.3.4.2.1", parse("300b0609608648016503040201", SequenceWithOid.class).oid); } @Test public void testSequenceOf() throws Exception { assertEquals(2, parse("3006300430003000", SequenceWithSequenceOf.class).values.size()); } @Test public void testSetOf() throws Exception { assertEquals(2, parse("3006310430003000", SequenceWithSetOf.class).values.size()); } @Test public void testImplicitOptionalField() throws Exception { // Optional field f2 missing in the input SequenceWithImplicitOptionalField seq = parse("300602010d02012a", SequenceWithImplicitOptionalField.class); assertEquals(13, seq.f1.intValue()); assertNull(seq.f2); assertEquals(42, seq.f3.intValue()); // Optional field f2 present in the input seq = parse("300a02010da102ffff02012a", SequenceWithImplicitOptionalField.class); assertEquals(13, seq.f1.intValue()); assertEquals(-1, seq.f2.intValue()); assertEquals(42, seq.f3.intValue()); } @Test public void testExplicitOptionalField() throws Exception { // Optional field f2 missing in the input SequenceWithExplicitOptionalField seq = parse("300602010d02012a", SequenceWithExplicitOptionalField.class); assertEquals(13, seq.f1.intValue()); assertNull(seq.f2); assertEquals(42, seq.f3.intValue()); // Optional field f2 present in the input seq = parse("300c02010da1040202ffff02012a", SequenceWithExplicitOptionalField.class); assertEquals(13, seq.f1.intValue()); assertEquals(-1, seq.f2.intValue()); assertEquals(42, seq.f3.intValue()); } @Test public void testChoiceWithDifferentTypedOptions() throws Exception { // The CHOICE can be either an INTEGER or an OBJECT IDENTIFIER // INTEGER ChoiceWithTwoOptions c = parse("0208ffffffffffffffff", ChoiceWithTwoOptions.class); assertNull(c.oid); assertEquals(-1, c.num.intValue()); // OBJECT IDENTIFIER c = parse("060100", ChoiceWithTwoOptions.class); assertEquals("0.0", c.oid); assertNull(c.num); // Empty input try { parse("", ChoiceWithTwoOptions.class); fail(); } catch (Asn1DecodingException expected) {} // Neither of the options match try { // Empty SEQUENCE parse("3000", ChoiceWithTwoOptions.class); fail(); } catch (Asn1DecodingException expected) {} } @Test public void testChoiceWithSameTypedOptions() throws Exception { // The CHOICE can be either a SEQUENCE, an IMPLICIT SEQUENCE, or an EXPLICIT SEQUENCE // SEQUENCE ChoiceWithThreeSequenceOptions c = parse("3000", ChoiceWithThreeSequenceOptions.class); assertNotNull(c.s1); assertNull(c.s2); assertNull(c.s3); // IMPLICIT [0] SEQUENCE c = parse("a000", ChoiceWithThreeSequenceOptions.class); assertNull(c.s1); assertNotNull(c.s2); assertNull(c.s3); // EXPLICIT [0] SEQUENCE c = parse("a1023000", ChoiceWithThreeSequenceOptions.class); assertNull(c.s1); assertNull(c.s2); assertNotNull(c.s3); // INTEGER -- None of the options match try { parse("02010a", ChoiceWithThreeSequenceOptions.class); fail(); } catch (Asn1DecodingException expected) {} } @Test(expected = Asn1DecodingException.class) public void testChoiceWithClashingOptions() throws Exception { // The CHOICE is between INTEGER and INTEGER which clash parse("0200", ChoiceWithClashingOptions.class); } @Test public void testPrimitiveIndefiniteLengthEncodingWithGarbage() throws Exception { // Indefinite length INTEGER containing what may look like a malformed definite length // INTEGER, followed by an INTEGER. This tests that contents of indefinite length encoded // primitive (i.e., not constructed) data values must not be parsed to locate the 0x00 0x00 // terminator. ByteBuffer input = ByteBuffer.wrap(HexEncoding.decode("0280020401000002010c")); ChoiceWithTwoOptions c = parse(input, ChoiceWithTwoOptions.class); // Check what's remaining in the input buffer assertEquals("02010c", HexEncoding.encode(input)); // Check what was consumed assertEquals(0x020401, c.num.intValue()); // Indefinite length INTEGER containing what may look like a malformed indefinite length // INTEGER, followed by an INTEGER input = ByteBuffer.wrap(HexEncoding.decode("0280028001000002010c")); c = parse(input, ChoiceWithTwoOptions.class); // Check what's remaining in the input buffer assertEquals("02010c", HexEncoding.encode(input)); // Check what was consumed assertEquals(0x028001, c.num.intValue()); } @Test public void testConstructedIndefiniteLengthEncodingWithoutNestedIndefiniteLengthDataValues() throws Exception { // Indefinite length SEQUENCE containing an INTEGER whose encoding contains 0x00 0x00 which // can be misinterpreted as indefinite length encoding terminator of the SEQUENCE, followed // by an INTEGER ByteBuffer input = ByteBuffer.wrap(HexEncoding.decode("308002020000000002010c")); SequenceWithAsn1Opaque c = parse(input, SequenceWithAsn1Opaque.class); // Check what's remaining in the input buffer assertEquals("02010c", HexEncoding.encode(input)); // Check what was read assertEquals("02020000", HexEncoding.encode(c.obj.getEncoded())); } @Test public void testConstructedIndefiniteLengthEncodingWithNestedIndefiniteLengthDataValues() throws Exception { // Indefinite length SEQUENCE containing two INTEGER fields using indefinite // length encoding, followed by an INTEGER. This tests that the 0x00 0x00 terminators used // by the encoding of the two INTEGERs are not confused for the 0x00 0x00 terminator of the // SEQUENCE. ByteBuffer input = ByteBuffer.wrap(HexEncoding.decode("308002800300000280030000020103000002010c")); SequenceWithAsn1Opaque c = parse(input, SequenceWithAsn1Opaque.class); // Check what's remaining in the input buffer assertEquals("02010c", HexEncoding.encode(input)); // Check what was consumed assertEquals("0280030000", HexEncoding.encode(c.obj.getEncoded())); } @Test(expected = Asn1DecodingException.class) public void testConstructedIndefiniteLengthEncodingWithGarbage() throws Exception { // Indefinite length SEQUENCE containing an indefinite length encoded SEQUENCE containing // garbage which doesn't parse as BER, followed by an INTEGER. This tests that contents of // the SEQUENCEs must be parsed to establish where their 0x00 0x00 terminators are located. ByteBuffer input = ByteBuffer.wrap(HexEncoding.decode("3080308002040000000002010c")); parse(input, SequenceWithAsn1Opaque.class); } private static T parse(String hexEncodedInput, Class containerClass) throws Asn1DecodingException { ByteBuffer input = (hexEncodedInput == null) ? null : ByteBuffer.wrap(HexEncoding.decode(hexEncodedInput)); return parse(input, containerClass); } private static T parse(ByteBuffer input, Class containerClass) throws Asn1DecodingException { return Asn1BerParser.parse(input, containerClass); } @Asn1Class(type = Asn1Type.SEQUENCE) public static class EmptySequence {} @Asn1Class(type = Asn1Type.SEQUENCE) public static class SequenceWithIntegers { @Asn1Field(index = 1, type = Asn1Type.INTEGER) public int n1; @Asn1Field(index = 2, type = Asn1Type.INTEGER) public long n2; @Asn1Field(index = 3, type = Asn1Type.INTEGER) public Integer n3; @Asn1Field(index = 4, type = Asn1Type.INTEGER) public Long n4; @Asn1Field(index = 5, type = Asn1Type.INTEGER) public BigInteger n5; } @Asn1Class(type = Asn1Type.SEQUENCE) public static class SequenceWithOid { @Asn1Field(index = 0, type = Asn1Type.OBJECT_IDENTIFIER) public String oid; } @Asn1Class(type = Asn1Type.SEQUENCE) public static class SequenceWithImplicitOptionalField { @Asn1Field(index = 1, type = Asn1Type.INTEGER) public Integer f1; @Asn1Field(index = 2, type = Asn1Type.INTEGER, optional = true, tagging = Asn1Tagging.IMPLICIT, tagNumber = 1) public Integer f2; @Asn1Field(index = 3, type = Asn1Type.INTEGER) public Integer f3; } @Asn1Class(type = Asn1Type.SEQUENCE) public static class SequenceWithExplicitOptionalField { @Asn1Field(index = 1, type = Asn1Type.INTEGER) public Integer f1; @Asn1Field(index = 2, type = Asn1Type.INTEGER, optional = true, tagging = Asn1Tagging.EXPLICIT, tagNumber = 1) public Integer f2; @Asn1Field(index = 3, type = Asn1Type.INTEGER) public Integer f3; } @Asn1Class(type = Asn1Type.CHOICE) public static class ChoiceWithTwoOptions { @Asn1Field(type = Asn1Type.OBJECT_IDENTIFIER) public String oid; @Asn1Field(type = Asn1Type.INTEGER) public Integer num; } @Asn1Class(type = Asn1Type.CHOICE) public static class ChoiceWithThreeSequenceOptions { @Asn1Field(type = Asn1Type.SEQUENCE) public EmptySequence s1; @Asn1Field(type = Asn1Type.SEQUENCE, tagging = Asn1Tagging.IMPLICIT, tagNumber = 0) public EmptySequence s2; @Asn1Field(type = Asn1Type.SEQUENCE, tagging = Asn1Tagging.EXPLICIT, tagNumber = 1) public EmptySequence s3; } @Asn1Class(type = Asn1Type.CHOICE) public static class ChoiceWithClashingOptions { @Asn1Field(type = Asn1Type.INTEGER) public int n1; @Asn1Field(type = Asn1Type.INTEGER) public Integer n2; } @Asn1Class(type = Asn1Type.SEQUENCE) public static class SequenceWithOctetString { @Asn1Field(index = 0, type = Asn1Type.OCTET_STRING) public ByteBuffer buf; } @Asn1Class(type = Asn1Type.SEQUENCE) public static class SequenceWithSequenceOf { @Asn1Field(index = 0, type = Asn1Type.SEQUENCE_OF) public List values; } @Asn1Class(type = Asn1Type.SEQUENCE) public static class SequenceWithSetOf { @Asn1Field(index = 0, type = Asn1Type.SET_OF) public List values; } @Asn1Class(type = Asn1Type.SEQUENCE) public static class SequenceWithAsn1Opaque { @Asn1Field(type = Asn1Type.ANY) public Asn1OpaqueObject obj; } } src/test/java/com/android/apksig/internal/asn1/Asn1DerEncoderTest.java0100644 0000000 0000000 00000020671 13243353143 024675 0ustar000000000 0000000 /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.internal.asn1; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import com.android.apksig.internal.util.HexEncoding; import java.math.BigInteger; import java.nio.ByteBuffer; import java.util.Arrays; import java.util.List; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @RunWith(JUnit4.class) public class Asn1DerEncoderTest { @Test public void testInteger() throws Exception { assertEquals("3003020100", encodeToHex(new SequenceWithInteger(0))); assertEquals("300302010c", encodeToHex(new SequenceWithInteger(12))); assertEquals("300302017f", encodeToHex(new SequenceWithInteger(0x7f))); assertEquals("3004020200ff", encodeToHex(new SequenceWithInteger(0xff))); assertEquals("30030201ff", encodeToHex(new SequenceWithInteger(-1))); assertEquals("3003020180", encodeToHex(new SequenceWithInteger(-128))); assertEquals("3005020300ffee", encodeToHex(new SequenceWithInteger(0xffee))); assertEquals("300602047fffffff", encodeToHex(new SequenceWithInteger(Integer.MAX_VALUE))); assertEquals("3006020480000000", encodeToHex(new SequenceWithInteger(Integer.MIN_VALUE))); } @Test public void testOctetString() throws Exception { assertEquals( "30050403010203", encodeToHex( new SequenceWithByteBufferOctetString( ByteBuffer.wrap(new byte[] {1, 2, 3})))); assertEquals( "30030401ff", encodeToHex( new SequenceWithByteBufferOctetString( ByteBuffer.wrap(new byte[] {(byte) 0xff})))); assertEquals( "30020400", encodeToHex( new SequenceWithByteBufferOctetString(ByteBuffer.wrap(new byte[0])))); } @Test public void testOid() throws Exception { assertEquals("3003060100", encodeToHex(new SequenceWithOid("0.0"))); assertEquals( "300b06092b0601040182371514", encodeToHex(new SequenceWithOid("1.3.6.1.4.1.311.21.20"))); assertEquals( "300b06092a864886f70d010701", encodeToHex(new SequenceWithOid("1.2.840.113549.1.7.1"))); assertEquals( "300b0609608648016503040201", encodeToHex(new SequenceWithOid("2.16.840.1.101.3.4.2.1"))); } @Test public void testChoice() throws Exception { assertEquals("0201ff", encodeToHex(Choice.of(-1))); assertEquals("80092b0601040182371514", encodeToHex(Choice.of("1.3.6.1.4.1.311.21.20"))); } @Test(expected = Asn1EncodingException.class) public void testChoiceWithNoFieldsSet() throws Exception { // CHOICE is required to have exactly one field set encode(new Choice(null, null)); } @Test(expected = Asn1EncodingException.class) public void testChoiceWithMultipleFieldsSet() throws Exception { // CHOICE is required to have exactly one field set encode(new Choice(123, "1.3.6.1.4.1.311.21.20")); } @Test public void testSetOf() throws Exception { assertEquals("3009310702010a020200ff", encodeToHex(SetOfIntegers.of(0x0a, 0xff))); // Reordering the elements of the set should not make a difference to the resulting encoding assertEquals("3009310702010a020200ff", encodeToHex(SetOfIntegers.of(0xff, 0x0a))); assertEquals( "300e310c02010a020200ff0203112233", encodeToHex(SetOfIntegers.of(0xff, 0x0a, 0x112233))); } @Test public void testSequence() throws Exception { assertEquals( "30080201000601000400", encodeToHex(new Sequence(BigInteger.ZERO, "0.0", new byte[0]))); // Optional OBJECT IDENTIFIER not set assertEquals( "30050201000400", encodeToHex(new Sequence(BigInteger.ZERO, null, new byte[0]))); // Required INTEGER not set try { assertEquals( "30050201000400", encodeToHex(new Sequence(null, "0.0", new byte[0]))); fail(); } catch (Asn1EncodingException expected) {} } @Test public void testAsn1Class() throws Exception { assertEquals( "30053003060100", encodeToHex(new SequenceWithAsn1Class(new SequenceWithOid("0.0")))); } @Test public void testOpaque() throws Exception { assertEquals( "3003060100", encodeToHex(new SequenceWithOpaque( new Asn1OpaqueObject(new byte[] {0x06, 0x01, 0x00})))); } private static byte[] encode(Object obj) throws Asn1EncodingException { return Asn1DerEncoder.encode(obj); } private static String encodeToHex(Object obj) throws Asn1EncodingException { return HexEncoding.encode(encode(obj)); } @Asn1Class(type = Asn1Type.SEQUENCE) public static class SequenceWithInteger { @Asn1Field(index = 1, type = Asn1Type.INTEGER) public int num; public SequenceWithInteger(int num) { this.num = num; } } @Asn1Class(type = Asn1Type.SEQUENCE) public static class SequenceWithOid { @Asn1Field(index = 1, type = Asn1Type.OBJECT_IDENTIFIER) public String oid; public SequenceWithOid(String oid) { this.oid = oid; } } @Asn1Class(type = Asn1Type.SEQUENCE) public static class SequenceWithByteBufferOctetString { @Asn1Field(index = 1, type = Asn1Type.OCTET_STRING) public ByteBuffer data; public SequenceWithByteBufferOctetString(ByteBuffer data) { this.data = data; } } @Asn1Class(type = Asn1Type.CHOICE) public static class Choice { @Asn1Field(type = Asn1Type.INTEGER) public Integer num; @Asn1Field(type = Asn1Type.OBJECT_IDENTIFIER, tagging = Asn1Tagging.IMPLICIT, tagNumber = 0) public String oid; public Choice(Integer num, String oid) { this.num = num; this.oid = oid; } public static Choice of(int num) { return new Choice(num, null); } public static Choice of(String oid) { return new Choice(null, oid); } } @Asn1Class(type = Asn1Type.SEQUENCE) public static class SetOfIntegers { @Asn1Field(type = Asn1Type.SET_OF, elementType = Asn1Type.INTEGER) public List values; public static SetOfIntegers of(Integer... values) { SetOfIntegers result = new SetOfIntegers(); result.values = Arrays.asList(values); return result; } } @Asn1Class(type = Asn1Type.SEQUENCE) public static class Sequence { @Asn1Field(type = Asn1Type.INTEGER, index = 0) public BigInteger num; @Asn1Field(type = Asn1Type.OBJECT_IDENTIFIER, index = 1, optional = true) public String oid; @Asn1Field(type = Asn1Type.OCTET_STRING, index = 2) public byte[] octets; public Sequence(BigInteger num, String oid, byte[] octets) { this.num = num; this.oid = oid; this.octets = octets; } } @Asn1Class(type = Asn1Type.SEQUENCE) public static class SequenceWithAsn1Class { @Asn1Field(type = Asn1Type.SEQUENCE) public SequenceWithOid seqWithOid; public SequenceWithAsn1Class(SequenceWithOid seqWithOid) { this.seqWithOid = seqWithOid; } } @Asn1Class(type = Asn1Type.SEQUENCE) public static class SequenceWithOpaque { @Asn1Field(type = Asn1Type.ANY) public Asn1OpaqueObject obj; public SequenceWithOpaque(Asn1OpaqueObject obj) { this.obj = obj; } } } src/test/java/com/android/apksig/internal/asn1/ber/0040755 0000000 0000000 00000000000 13243353143 021202 5ustar000000000 0000000 src/test/java/com/android/apksig/internal/asn1/ber/AllTests.java0100644 0000000 0000000 00000001633 13243353143 023600 0ustar000000000 0000000 /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.internal.asn1.ber; import org.junit.runner.RunWith; import org.junit.runners.Suite; @RunWith(Suite.class) @Suite.SuiteClasses({ BerDataValueTest.class, ByteBufferBerDataValueReaderTest.class, InputStreamBerDataValueReaderTest.class, }) public class AllTests {} src/test/java/com/android/apksig/internal/asn1/ber/BerDataValueReaderTestBase.java0100644 0000000 0000000 00000030645 13243353143 027127 0ustar000000000 0000000 /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.internal.asn1.ber; import static com.android.apksig.internal.test.MoreAsserts.assertByteBufferEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import com.android.apksig.internal.util.HexEncoding; import org.junit.Test; /** * Base class for unit tests of ASN.1 BER (see {@code X.690}) data value reader implementations. * *

Subclasses need to provide only an implementation of {@link #createReader(byte[])} and * subclass-specific tests. */ public abstract class BerDataValueReaderTestBase { /** * Returns a new reader initialized with the provided input. */ protected abstract BerDataValueReader createReader(byte[] input); @Test public void testEmptyInput() throws Exception { assertNull(readDataValue("")); } @Test public void testEndOfInput() throws Exception { BerDataValueReader reader = createReader("3000"); // SEQUENCE with empty contents assertNotNull(reader.readDataValue()); // End of input has been reached assertNull(reader.readDataValue()); // Null should also be returned on consecutive invocations assertNull(reader.readDataValue()); } @Test public void testSingleByteTagId() throws Exception { BerDataValue dataValue = readDataValue("1000"); assertEquals(BerEncoding.TAG_CLASS_UNIVERSAL, dataValue.getTagClass()); assertFalse(dataValue.isConstructed()); assertEquals(0x10, dataValue.getTagNumber()); dataValue = readDataValue("3900"); assertEquals(BerEncoding.TAG_CLASS_UNIVERSAL, dataValue.getTagClass()); assertTrue(dataValue.isConstructed()); assertEquals(0x19, dataValue.getTagNumber()); dataValue = readDataValue("6700"); assertEquals(BerEncoding.TAG_CLASS_APPLICATION, dataValue.getTagClass()); assertTrue(dataValue.isConstructed()); assertEquals(7, dataValue.getTagNumber()); dataValue = readDataValue("8600"); assertEquals(BerEncoding.TAG_CLASS_CONTEXT_SPECIFIC, dataValue.getTagClass()); assertFalse(dataValue.isConstructed()); assertEquals(6, dataValue.getTagNumber()); dataValue = readDataValue("fe00"); assertEquals(BerEncoding.TAG_CLASS_PRIVATE, dataValue.getTagClass()); assertTrue(dataValue.isConstructed()); assertEquals(0x1e, dataValue.getTagNumber()); } @Test public void testHighTagNumber() throws Exception { assertEquals(7, readDataValue("3f0700").getTagNumber()); assertEquals(7, readDataValue("3f800700").getTagNumber()); assertEquals(7, readDataValue("3f80800700").getTagNumber()); assertEquals(7, readDataValue("3f8080800700").getTagNumber()); assertEquals(7, readDataValue("3f808080808080808080808080808080800700").getTagNumber()); assertEquals(375, readDataValue("3f827700").getTagNumber()); assertEquals(268435455, readDataValue("3fffffff7f00").getTagNumber()); assertEquals(Integer.MAX_VALUE, readDataValue("3f87ffffff7f00").getTagNumber()); } @Test(expected = BerDataValueFormatException.class) public void testHighTagNumberTooLarge() throws Exception { readDataValue("3f888080800000"); // Integer.MAX_VALUE + 1 } // @Test(expected = BerDataValueFormatException.class) public void testTruncatedHighTagNumberLastOctetMissing() throws Exception { readDataValue("9f80"); // terminating octet must not have the highest bit set } @Test(expected = BerDataValueFormatException.class) public void testTruncatedBeforeFirstLengthOctet() throws Exception { readDataValue("30"); } @Test public void testShortFormLength() throws Exception { assertByteBufferEquals(new byte[0], readDataValue("3000").getEncodedContents()); assertByteBufferEquals( HexEncoding.decode("010203"), readDataValue("3003010203").getEncodedContents()); } @Test public void testLongFormLength() throws Exception { assertByteBufferEquals(new byte[0], readDataValue("308100").getEncodedContents()); assertByteBufferEquals( HexEncoding.decode("010203"), readDataValue("30820003010203").getEncodedContents()); assertEquals( 255, readDataValue(concat(HexEncoding.decode("3081ff"), new byte[255])) .getEncodedContents().remaining()); assertEquals( 0x110, readDataValue(concat(HexEncoding.decode("30820110"), new byte[0x110])) .getEncodedContents().remaining()); } @Test(expected = BerDataValueFormatException.class) public void testTruncatedLongFormLengthBeforeFirstLengthByte() throws Exception { readDataValue("3081"); } @Test(expected = BerDataValueFormatException.class) public void testTruncatedLongFormLengthLastLengthByteMissing() throws Exception { readDataValue("308200"); } @Test(expected = BerDataValueFormatException.class) public void testLongFormLengthTooLarge() throws Exception { readDataValue("3084ffffffff"); } @Test public void testIndefiniteFormLength() throws Exception { assertByteBufferEquals(new byte[0], readDataValue("30800000").getEncodedContents()); assertByteBufferEquals( HexEncoding.decode("020103"), readDataValue("30800201030000").getEncodedContents()); assertByteBufferEquals( HexEncoding.decode( "000102030405060708090a0b0c0d0e0f" + "000102030405060708090a0b0c0d0e0f" + "000102030405060708090a0b0c0d0e0f" + "000102030405060708090a0b0c0d0e0f" + "000102030405060708090a0b0c0d0e0f" + "000102030405060708090a0b0c0d0e0f" + "000102030405060708090a0b0c0d0e0f" + "000102030405060708090a0b0c0d0e0f" + "000102030405060708090a0b0c0d0e0f" + "000102030405060708090a0b0c0d0e0f" + "000102030405060708090a0b0c0d0e0f" + "000102030405060708090a0b0c0d0e0f" + "000102030405060708090a0b0c0d0e0f" + "000102030405060708090a0b0c0d0e0f" + "000102030405060708090a0b0c0d0e0f" + "000102030405060708090a0b0c0d0e0f" + "000102030405060708090a0b0c0d0e0f"), readDataValue( "0280" + "000102030405060708090a0b0c0d0e0f" + "000102030405060708090a0b0c0d0e0f" + "000102030405060708090a0b0c0d0e0f" + "000102030405060708090a0b0c0d0e0f" + "000102030405060708090a0b0c0d0e0f" + "000102030405060708090a0b0c0d0e0f" + "000102030405060708090a0b0c0d0e0f" + "000102030405060708090a0b0c0d0e0f" + "000102030405060708090a0b0c0d0e0f" + "000102030405060708090a0b0c0d0e0f" + "000102030405060708090a0b0c0d0e0f" + "000102030405060708090a0b0c0d0e0f" + "000102030405060708090a0b0c0d0e0f" + "000102030405060708090a0b0c0d0e0f" + "000102030405060708090a0b0c0d0e0f" + "000102030405060708090a0b0c0d0e0f" + "000102030405060708090a0b0c0d0e0f" + "0000" ).getEncodedContents()); } @Test(expected = BerDataValueFormatException.class) public void testDefiniteLengthContentsTruncatedBeforeFirstContentOctet() throws Exception { readDataValue("3001"); } @Test(expected = BerDataValueFormatException.class) public void testIndefiniteLengthContentsTruncatedBeforeFirstContentOctet() throws Exception { readDataValue("3080"); } @Test(expected = BerDataValueFormatException.class) public void testTruncatedDefiniteLengthContents() throws Exception { readDataValue("30030102"); } @Test(expected = BerDataValueFormatException.class) public void testTruncatedIndefiniteLengthContents() throws Exception { readDataValue("308001020300"); } @Test public void testEmptyDefiniteLengthContents() throws Exception { assertByteBufferEquals(new byte[0], readDataValue("3000").getEncodedContents()); } @Test public void testEmptyIndefiniteLengthContents() throws Exception { assertByteBufferEquals(new byte[0], readDataValue("30800000").getEncodedContents()); } @Test public void testPrimitiveIndefiniteLengthContentsMustNotBeParsed() throws Exception { // INTEGER (0x0203) followed by 0x010000. This could be misinterpreted as INTEGER // (0x0203000001) if the contents of the original INTEGER are parsed to find the 0x00 0x00 // indefinite length terminator. Such parsing must not take place for primitive (i.e., not // constructed) values. assertEquals( "0203", HexEncoding.encode(readDataValue("028002030000010000").getEncodedContents())); } @Test public void testConstructedIndefiniteLengthContentsContainingIndefiniteLengthEncodedValues() throws Exception { // Indefinite length SEQUENCE containing elements which themselves use indefinite length // encoding, followed by INTEGER (0x0e). assertEquals( "3080028001000000000280020000", HexEncoding.encode(readDataValue( "30803080028001000000000280020000000002010c").getEncodedContents())); } @Test(expected = BerDataValueFormatException.class) public void testConstructedIndefiniteLengthContentsContainingGarbage() throws Exception { // Indefinite length SEQUENCE containing truncated data value. Parsing is expected to fail // because the value of the sequence must be parsed (and this will fail because of garbage) // to establish where to look for the 0x00 0x00 indefinite length terminator of the // SEQUENCE. readDataValue("3080020a030000"); } @Test public void testReadAdvancesPosition() throws Exception { BerDataValueReader reader = createReader("37018f050001020304"); assertByteBufferEquals(HexEncoding.decode("37018f"), reader.readDataValue().getEncoded()); assertByteBufferEquals(HexEncoding.decode("0500"), reader.readDataValue().getEncoded()); assertByteBufferEquals(HexEncoding.decode("01020304"), reader.readDataValue().getEncoded()); assertNull(reader.readDataValue()); } private BerDataValueReader createReader(String hexEncodedInput) { return createReader(HexEncoding.decode(hexEncodedInput)); } private BerDataValue readDataValue(byte[] input) throws BerDataValueFormatException { return createReader(input).readDataValue(); } private BerDataValue readDataValue(String hexEncodedInput) throws BerDataValueFormatException { return createReader(hexEncodedInput).readDataValue(); } private static byte[] concat(byte[] arr1, byte[] arr2) { byte[] result = new byte[arr1.length + arr2.length]; System.arraycopy(arr1, 0, result, 0, arr1.length); System.arraycopy(arr2, 0, result, arr1.length, arr2.length); return result; } } src/test/java/com/android/apksig/internal/asn1/ber/BerDataValueTest.java0100644 0000000 0000000 00000011571 13243353143 025206 0ustar000000000 0000000 /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.internal.asn1.ber; import static com.android.apksig.internal.test.MoreAsserts.assertByteBufferEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import com.android.apksig.internal.util.HexEncoding; import java.nio.ByteBuffer; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @RunWith(JUnit4.class) public class BerDataValueTest { private static final BerDataValue TEST_VALUE1 = new BerDataValue( ByteBuffer.wrap(HexEncoding.decode("aa")), ByteBuffer.wrap(HexEncoding.decode("bb")), BerEncoding.TAG_CLASS_UNIVERSAL, true, BerEncoding.TAG_NUMBER_SEQUENCE); private static final BerDataValue TEST_VALUE2 = new BerDataValue( ByteBuffer.wrap(HexEncoding.decode("cc")), ByteBuffer.wrap(HexEncoding.decode("dd")), BerEncoding.TAG_CLASS_CONTEXT_SPECIFIC, false, BerEncoding.TAG_NUMBER_OCTET_STRING); @Test public void testGetTagClass() { assertEquals(BerEncoding.TAG_CLASS_UNIVERSAL, TEST_VALUE1.getTagClass()); assertEquals(BerEncoding.TAG_CLASS_CONTEXT_SPECIFIC, TEST_VALUE2.getTagClass()); } @Test public void testIsConstructed() { assertTrue(TEST_VALUE1.isConstructed()); assertFalse(TEST_VALUE2.isConstructed()); } @Test public void testGetTagNumber() { assertEquals(BerEncoding.TAG_NUMBER_SEQUENCE, TEST_VALUE1.getTagNumber()); assertEquals(BerEncoding.TAG_NUMBER_OCTET_STRING, TEST_VALUE2.getTagNumber()); } @Test public void testGetEncoded() { assertByteBufferEquals(HexEncoding.decode("aa"), TEST_VALUE1.getEncoded()); assertByteBufferEquals(HexEncoding.decode("cc"), TEST_VALUE2.getEncoded()); } @Test public void testGetEncodedReturnsSlice() { // Assert that changing the position of returned ByteBuffer does not affect ByteBuffers // returned in the future ByteBuffer encoded = TEST_VALUE1.getEncoded(); assertByteBufferEquals(HexEncoding.decode("aa"), encoded); encoded.position(encoded.limit()); assertByteBufferEquals(HexEncoding.decode("aa"), TEST_VALUE1.getEncoded()); } @Test public void testGetEncodedContents() { assertByteBufferEquals(HexEncoding.decode("bb"), TEST_VALUE1.getEncodedContents()); assertByteBufferEquals(HexEncoding.decode("dd"), TEST_VALUE2.getEncodedContents()); } @Test public void testGetEncodedContentsReturnsSlice() { // Assert that changing the position of returned ByteBuffer does not affect ByteBuffers // returned in the future ByteBuffer encoded = TEST_VALUE1.getEncodedContents(); assertByteBufferEquals(HexEncoding.decode("bb"), encoded); encoded.position(encoded.limit()); assertByteBufferEquals(HexEncoding.decode("bb"), TEST_VALUE1.getEncodedContents()); } @Test public void testDataValueReader() throws BerDataValueFormatException { BerDataValueReader reader = TEST_VALUE1.dataValueReader(); assertSame(TEST_VALUE1, reader.readDataValue()); assertNull(reader.readDataValue()); assertNull(reader.readDataValue()); } @Test public void testContentsReader() throws BerDataValueFormatException { BerDataValue dataValue = new BerDataValue( ByteBuffer.allocate(0), ByteBuffer.wrap(HexEncoding.decode("300203040500")), BerEncoding.TAG_CLASS_UNIVERSAL, true, BerEncoding.TAG_NUMBER_SEQUENCE); BerDataValueReader reader = dataValue.contentsReader(); assertEquals(ByteBufferBerDataValueReader.class, reader.getClass()); assertByteBufferEquals(HexEncoding.decode("30020304"), reader.readDataValue().getEncoded()); assertByteBufferEquals(HexEncoding.decode("0500"), reader.readDataValue().getEncoded()); assertNull(reader.readDataValue()); } } src/test/java/com/android/apksig/internal/asn1/ber/ByteBufferBerDataValueReaderTest.java0100644 0000000 0000000 00000002322 13243353143 030301 0ustar000000000 0000000 /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.internal.asn1.ber; import java.nio.ByteBuffer; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @RunWith(JUnit4.class) public class ByteBufferBerDataValueReaderTest extends BerDataValueReaderTestBase { @Override protected ByteBufferBerDataValueReader createReader(byte[] input) { return new ByteBufferBerDataValueReader(ByteBuffer.wrap(input)); } @Test(expected = NullPointerException.class) public void testConstructWithNullByteBuffer() throws Exception { new ByteBufferBerDataValueReader(null); } } src/test/java/com/android/apksig/internal/asn1/ber/InputStreamBerDataValueReaderTest.java0100644 0000000 0000000 00000002350 13243353143 030520 0ustar000000000 0000000 /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.internal.asn1.ber; import java.io.ByteArrayInputStream; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @RunWith(JUnit4.class) public class InputStreamBerDataValueReaderTest extends BerDataValueReaderTestBase { @Override protected InputStreamBerDataValueReader createReader(byte[] input) { return new InputStreamBerDataValueReader(new ByteArrayInputStream(input)); } @Test(expected = NullPointerException.class) public void testConstructWithNullByteBuffer() throws Exception { new InputStreamBerDataValueReader(null); } } src/test/java/com/android/apksig/internal/test/0040755 0000000 0000000 00000000000 13243353143 020547 5ustar000000000 0000000 src/test/java/com/android/apksig/internal/test/MoreAsserts.java0100644 0000000 0000000 00000003566 13243353143 023670 0ustar000000000 0000000 /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.internal.test; import static org.junit.Assert.assertArrayEquals; import java.nio.ByteBuffer; public abstract class MoreAsserts { private MoreAsserts() {} /** * Asserts that the contents of the provided {@code ByteBuffer} are as expected. This method * does not change the position or the limit of the provided buffer. */ public static void assertByteBufferEquals(byte[] expected, ByteBuffer actual) { assertByteBufferEquals(null, expected, actual); } /** * Asserts that the contents of the provided {@code ByteBuffer} are as expected. This method * does not change the position or the limit of the provided buffer. */ public static void assertByteBufferEquals(String message, byte[] expected, ByteBuffer actual) { byte[] actualArr; if ((actual.hasArray()) && (actual.arrayOffset() == 0) && (actual.array().length == actual.remaining())) { actualArr = actual.array(); } else { actualArr = new byte[actual.remaining()]; int actualOriginalPos = actual.position(); actual.get(actualArr); actual.position(actualOriginalPos); } assertArrayEquals(message, expected, actualArr); } } src/test/java/com/android/apksig/internal/util/0040755 0000000 0000000 00000000000 13243353143 020545 5ustar000000000 0000000 src/test/java/com/android/apksig/internal/util/AllTests.java0100644 0000000 0000000 00000001557 13243353143 023150 0ustar000000000 0000000 /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.internal.util; import org.junit.runner.RunWith; import org.junit.runners.Suite; @RunWith(Suite.class) @Suite.SuiteClasses({ ArrayBackedByteBufferSinkTest.class, DirectByteBufferSinkTest.class, }) public class AllTests {} src/test/java/com/android/apksig/internal/util/ArrayBackedByteBufferSinkTest.java0100644 0000000 0000000 00000001714 13243353143 027223 0ustar000000000 0000000 /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.internal.util; import java.nio.ByteBuffer; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @RunWith(JUnit4.class) public class ArrayBackedByteBufferSinkTest extends ByteBufferSinkTestBase { @Override protected ByteBuffer createBuffer(int size) { return ByteBuffer.allocate(size); } } src/test/java/com/android/apksig/internal/util/ByteBufferSinkTestBase.java0100644 0000000 0000000 00000003446 13243353143 025731 0ustar000000000 0000000 /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.internal.util; import com.android.apksig.util.DataSinkTestBase; import java.io.IOException; import java.nio.ByteBuffer; public abstract class ByteBufferSinkTestBase extends DataSinkTestBase { private static final int START_POS = 100; protected abstract ByteBuffer createBuffer(int size); @Override protected CloseableWithDataSink createDataSink() { ByteBuffer buf = createBuffer(1024); // Use non-zero position and limit which isn't set to capacity to catch the implementation // under test ignoring the initial position. buf.position(START_POS); buf.limit(buf.capacity() - 300); return CloseableWithDataSink.of(new ByteBufferSink(buf)); } @Override protected ByteBuffer getContents(ByteBufferSink dataSink) throws IOException { ByteBuffer buf = dataSink.getBuffer(); int oldPos = buf.position(); int oldLimit = buf.limit(); try { buf.position(START_POS); buf.limit(oldPos); return buf.slice(); } finally { buf.limit(oldLimit); buf.position(oldPos); } } } src/test/java/com/android/apksig/internal/util/ByteStreams.java0100644 0000000 0000000 00000002455 13243353143 023655 0ustar000000000 0000000 /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.internal.util; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; /** * Utilities for byte arrays and I/O streams. */ public final class ByteStreams { private ByteStreams() {} /** * Returns the data remaining in the provided input stream as a byte array */ public static byte[] toByteArray(InputStream in) throws IOException { ByteArrayOutputStream result = new ByteArrayOutputStream(); byte[] buf = new byte[16384]; int chunkSize; while ((chunkSize = in.read(buf)) != -1) { result.write(buf, 0, chunkSize); } return result.toByteArray(); } } src/test/java/com/android/apksig/internal/util/DirectByteBufferSinkTest.java0100644 0000000 0000000 00000001715 13243353143 026266 0ustar000000000 0000000 /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.internal.util; import java.nio.ByteBuffer; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @RunWith(JUnit4.class) public class DirectByteBufferSinkTest extends ByteBufferSinkTestBase { @Override protected ByteBuffer createBuffer(int size) { return ByteBuffer.allocateDirect(size); } } src/test/java/com/android/apksig/internal/util/HexEncoding.java0100644 0000000 0000000 00000006474 13243353143 023613 0ustar000000000 0000000 /* * Copyright (C) 2012 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.internal.util; import java.nio.ByteBuffer; /** * Hexadecimal encoding where each byte is represented by two hexadecimal digits. */ public class HexEncoding { /** Hidden constructor to prevent instantiation. */ private HexEncoding() {} private static final char[] HEX_DIGITS = "0123456789abcdef".toCharArray(); /** * Encodes the provided data as a hexadecimal string. */ public static String encode(byte[] data) { return encode(data, 0, data.length); } /** * Encodes the provided data as a hexadecimal string. */ public static String encode(byte[] data, int offset, int len) { StringBuilder result = new StringBuilder(len * 2); for (int i = 0; i < len; i++) { byte b = data[offset + i]; result.append(HEX_DIGITS[(b >>> 4) & 0x0f]); result.append(HEX_DIGITS[b & 0x0f]); } return result.toString(); } /** * Encodes the provided data as a hexadecimal string. */ public static String encode(ByteBuffer buf) { return encode(buf.array(), buf.arrayOffset() + buf.position(), buf.remaining()); } /** * Decodes the provided hexadecimal string into an array of bytes. */ public static byte[] decode(String encoded) { // IMPLEMENTATION NOTE: Special care is taken to permit odd number of hexadecimal digits. int resultLengthBytes = (encoded.length() + 1) / 2; byte[] result = new byte[resultLengthBytes]; int resultOffset = 0; int encodedCharOffset = 0; if ((encoded.length() % 2) != 0) { // Odd number of digits -- the first digit is the lower 4 bits of the first result byte. result[resultOffset++] = (byte) getHexadecimalDigitValue(encoded.charAt(encodedCharOffset)); encodedCharOffset++; } for (int len = encoded.length(); encodedCharOffset < len; encodedCharOffset += 2) { result[resultOffset++] = (byte) ((getHexadecimalDigitValue(encoded.charAt(encodedCharOffset)) << 4) | getHexadecimalDigitValue(encoded.charAt(encodedCharOffset + 1))); } return result; } private static int getHexadecimalDigitValue(char c) { if ((c >= 'a') && (c <= 'f')) { return (c - 'a') + 0x0a; } else if ((c >= 'A') && (c <= 'F')) { return (c - 'A') + 0x0a; } else if ((c >= '0') && (c <= '9')) { return c - '0'; } else { throw new IllegalArgumentException( "Invalid hexadecimal digit at position : '" + c + "' (0x" + Integer.toHexString(c) + ")"); } } } src/test/java/com/android/apksig/internal/util/Resources.java0100644 0000000 0000000 00000010560 13243353143 023361 0ustar000000000 0000000 /* * Copyright (C) 2012 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.internal.util; import java.io.IOException; import java.io.InputStream; import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Locale; /** * Assorted methods to obtaining test input from resources. */ public final class Resources { private Resources() {} public static byte[] toByteArray(Class cls, String resourceName) throws IOException { try (InputStream in = cls.getResourceAsStream(resourceName)) { if (in == null) { throw new IllegalArgumentException("Resource not found: " + resourceName); } return ByteStreams.toByteArray(in); } } public static X509Certificate toCertificate( Class cls, String resourceName) throws IOException, CertificateException { try (InputStream in = cls.getResourceAsStream(resourceName)) { if (in == null) { throw new IllegalArgumentException("Resource not found: " + resourceName); } return (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(in); } } public static List toCertificateChain( Class cls, String resourceName) throws IOException, CertificateException { Collection certs; try (InputStream in = cls.getResourceAsStream(resourceName)) { if (in == null) { throw new IllegalArgumentException("Resource not found: " + resourceName); } certs = CertificateFactory.getInstance("X.509").generateCertificates(in); } List result = new ArrayList<>(certs.size()); for (Certificate cert : certs) { result.add((X509Certificate) cert); } return result; } public static PrivateKey toPrivateKey(Class cls, String resourceName) throws IOException, InvalidKeySpecException, NoSuchAlgorithmException { int delimiterIndex = resourceName.indexOf('-'); if (delimiterIndex == -1) { throw new IllegalArgumentException( "Failed to autodetect key algorithm from resource name: " + resourceName); } String keyAlgorithm = resourceName.substring(0, delimiterIndex).toUpperCase(Locale.US); return toPrivateKey(cls, resourceName, keyAlgorithm); } public static PrivateKey toPrivateKey( Class cls, String resourceName, String keyAlgorithm) throws IOException, InvalidKeySpecException, NoSuchAlgorithmException { byte[] encoded = toByteArray(cls, resourceName); // Keep overly strictly linter happy by limiting what JCA KeyFactory algorithms are used // here KeyFactory keyFactory; switch (keyAlgorithm.toUpperCase(Locale.US)) { case "RSA": keyFactory = KeyFactory.getInstance("rsa"); break; case "DSA": keyFactory = KeyFactory.getInstance("dsa"); break; case "EC": keyFactory = KeyFactory.getInstance("ec"); break; default: throw new InvalidKeySpecException("Unsupported key algorithm: " + keyAlgorithm); } return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(encoded)); } } src/test/java/com/android/apksig/util/0040755 0000000 0000000 00000000000 13243353143 016731 5ustar000000000 0000000 src/test/java/com/android/apksig/util/AllTests.java0100644 0000000 0000000 00000002031 13243353143 021320 0ustar000000000 0000000 /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.util; import org.junit.runner.RunWith; import org.junit.runners.Suite; @RunWith(Suite.class) @Suite.SuiteClasses({ DataSinkFromOutputStreamTest.class, DataSinkFromRAFTest.class, DataSourceFromByteBufferTest.class, DataSourceFromRAFChunkTest.class, DataSourceFromRAFTest.class, InMemoryDataSinkDataSourceTest.class, InMemoryDataSinkTest.class, }) public class AllTests {} src/test/java/com/android/apksig/util/DataSinkFromOutputStreamTest.java0100644 0000000 0000000 00000003004 13243353143 025345 0ustar000000000 0000000 /* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.util; import com.android.apksig.internal.util.OutputStreamDataSink; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; /** * Tests for the {@link DataSink} returned by {@link DataSinks#asDataSink(java.io.OutputStream)}. */ @RunWith(JUnit4.class) public class DataSinkFromOutputStreamTest extends DataSinkTestBase { @Override protected CloseableWithDataSink createDataSink() { return CloseableWithDataSink.of( (OutputStreamDataSink) DataSinks.asDataSink(new ByteArrayOutputStream())); } @Override protected ByteBuffer getContents(OutputStreamDataSink dataSink) throws IOException { return ByteBuffer.wrap(((ByteArrayOutputStream) dataSink.getOutputStream()).toByteArray()); } } src/test/java/com/android/apksig/util/DataSinkFromRAFTest.java0100644 0000000 0000000 00000004144 13243353143 023307 0ustar000000000 0000000 /* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.util; import com.android.apksig.internal.util.RandomAccessFileDataSink; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; /** * Tests for the {@link DataSink} returned by * {@link DataSinks#asDataSink(java.io.RandomAccessFile)}. */ @RunWith(JUnit4.class) public class DataSinkFromRAFTest extends DataSinkTestBase { @Override protected CloseableWithDataSink createDataSink() throws IOException { File tmp = File.createTempFile(DataSourceFromRAFTest.class.getSimpleName(), ".bin"); RandomAccessFile f = null; try { f = new RandomAccessFile(tmp, "rw"); } finally { if (f == null) { tmp.delete(); } } return CloseableWithDataSink.of( (RandomAccessFileDataSink) DataSinks.asDataSink(f), new DataSourceFromRAFTest.TmpFileCloseable(tmp, f)); } @Override protected ByteBuffer getContents(RandomAccessFileDataSink dataSink) throws IOException { RandomAccessFile f = dataSink.getFile(); if (f.length() > Integer.MAX_VALUE) { throw new IOException("File too large: " + f.length()); } byte[] contents = new byte[(int) f.length()]; f.seek(0); f.readFully(contents); return ByteBuffer.wrap(contents); } } src/test/java/com/android/apksig/util/DataSinkTestBase.java0100644 0000000 0000000 00000013024 13243353143 022722 0ustar000000000 0000000 /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.util; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import java.io.Closeable; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import org.junit.Test; /** * Base class for testing implementations of {@link DataSink}. This class tests the contract of * {@code DataSink}. * *

To subclass, provide an implementation of {@link #createDataSink()} which returns the * implementation of {@code DataSink} you want to test. */ public abstract class DataSinkTestBase { /** * Returns a new {@link DataSink}. */ protected abstract CloseableWithDataSink createDataSink() throws IOException; /** * Returns the contents of the data sink. */ protected abstract ByteBuffer getContents(T dataSink) throws IOException; @Test public void testConsumeFromArray() throws Exception { try (CloseableWithDataSink c = createDataSink()) { T sink = c.getDataSink(); byte[] input = "abcdefg".getBytes(StandardCharsets.UTF_8); sink.consume(input, 2, 3); // "cde" sink.consume(input, 0, 1); // "a" assertContentsEquals("cdea", sink); // Zero-length chunks sink.consume(input, 0, 0); sink.consume(input, 1, 0); sink.consume(input, input.length - 2, 0); sink.consume(input, input.length - 1, 0); sink.consume(input, input.length, 0); // Invalid chunks assertConsumeArrayThrowsIOOB(sink, input, -1, 0); assertConsumeArrayThrowsIOOB(sink, input, -1, 3); assertConsumeArrayThrowsIOOB(sink, input, 0, input.length + 1); assertConsumeArrayThrowsIOOB(sink, input, input.length - 2, 4); assertConsumeArrayThrowsIOOB(sink, input, input.length + 1, 0); assertConsumeArrayThrowsIOOB(sink, input, input.length + 1, 1); assertContentsEquals("cdea", sink); } } @Test public void testConsumeFromByteBuffer() throws Exception { try (CloseableWithDataSink c = createDataSink()) { T sink = c.getDataSink(); ByteBuffer input = ByteBuffer.wrap("abcdefg".getBytes(StandardCharsets.UTF_8)); input.position(2); input.limit(5); sink.consume(input); // "cde" assertEquals(5, input.position()); assertEquals(5, input.limit()); input.position(0); input.limit(1); sink.consume(input); // "a" assertContentsEquals("cdea", sink); // Empty input sink.consume(input); assertContentsEquals("cdea", sink); // ByteBuffer which isn't backed by a byte[] input = ByteBuffer.allocateDirect(2); input.put((byte) 'X'); input.put((byte) 'Z'); input.flip(); sink.consume(input); assertContentsEquals("cdeaXZ", sink); assertEquals(2, input.position()); assertEquals(2, input.limit()); // Empty input sink.consume(input); assertContentsEquals("cdeaXZ", sink); } } /** * Returns the contents of the provided buffer as a string. The buffer's position and limit * remain unchanged. */ private static String toString(ByteBuffer buf) { return DataSourceTestBase.toString(buf); } private void assertContentsEquals(String expectedContents, T sink) throws IOException { ByteBuffer actual = getContents(sink); assertEquals(expectedContents, toString(actual)); } private static void assertConsumeArrayThrowsIOOB( DataSink sink, byte[] arr, int offset, int length) throws IOException { try { sink.consume(arr, offset, length); fail(); } catch (IndexOutOfBoundsException expected) {} } public static class CloseableWithDataSink implements Closeable { private final T mDataSink; private final Closeable mCloseable; private CloseableWithDataSink(T dataSink, Closeable closeable) { mDataSink = dataSink; mCloseable = closeable; } public static CloseableWithDataSink of(T dataSink) { return new CloseableWithDataSink<>(dataSink, null); } public static CloseableWithDataSink of( T dataSink, Closeable closeable) { return new CloseableWithDataSink<>(dataSink, closeable); } public T getDataSink() { return mDataSink; } public Closeable getCloseable() { return mCloseable; } @Override public void close() throws IOException { if (mCloseable != null) { mCloseable.close(); } } } } src/test/java/com/android/apksig/util/DataSourceFromByteBufferTest.java0100644 0000000 0000000 00000003453 13243353143 025272 0ustar000000000 0000000 /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.util; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; /** * Tests for the {@link DataSource} returned by {@link DataSources#asDataSource(ByteBuffer)}. */ @RunWith(JUnit4.class) public class DataSourceFromByteBufferTest extends DataSourceTestBase { @Test public void testChangesToBufferPosAndLimitNotVisible() throws Exception { ByteBuffer buf = ByteBuffer.wrap("abcdefgh".getBytes(StandardCharsets.UTF_8)); buf.position(1); buf.limit(4); DataSource ds = DataSources.asDataSource(buf); buf.position(2); buf.limit(buf.capacity()); assertGetByteBufferEquals("bcd", ds, 0, (int) ds.size()); assertFeedEquals("bcd", ds, 0, (int) ds.size()); assertSliceEquals("bcd", ds, 0, (int) ds.size()); assertCopyToEquals("bcd", ds, 0, (int) ds.size()); } @Override protected CloseableWithDataSource createDataSource(byte[] contents) throws IOException { return CloseableWithDataSource.of(DataSources.asDataSource(ByteBuffer.wrap(contents))); } } src/test/java/com/android/apksig/util/DataSourceFromRAFChunkTest.java0100644 0000000 0000000 00000006651 13243353143 024641 0ustar000000000 0000000 /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.util; import com.android.apksig.util.DataSourceFromRAFTest.TmpFileCloseable; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; /** * Tests for the {@link DataSource} returned by * {@link DataSources#asDataSource(RandomAccessFile, long, long)}. */ @RunWith(JUnit4.class) public class DataSourceFromRAFChunkTest extends DataSourceTestBase { @Test public void testFileSizeChangesNotVisible() throws Exception { try (CloseableWithDataSource c = createDataSource("abcdefg")) { DataSource ds = c.getDataSource(); DataSource slice = ds.slice(3, 2); File f = ((TmpFileCloseable) c.getCloseable()).getFile(); assertGetByteBufferEquals("abcdefg", ds, 0, (int) ds.size()); assertGetByteBufferEquals("de", slice, 0, (int) slice.size()); assertFeedEquals("cdefg", ds, 2, 5); assertFeedEquals("e", slice, 1, 1); assertCopyToEquals("cdefg", ds, 2, 5); assertCopyToEquals("e", slice, 1, 1); assertSliceEquals("cdefg", ds, 2, 5); assertSliceEquals("e", slice, 1, 1); try (RandomAccessFile raf = new RandomAccessFile(f, "rw")) { raf.seek(raf.length()); raf.write("hijkl".getBytes(StandardCharsets.UTF_8)); } assertGetByteBufferEquals("abcdefg", ds, 0, (int) ds.size()); assertGetByteBufferEquals("de", slice, 0, (int) slice.size()); assertGetByteBufferThrowsIOOB(ds, 0, (int) ds.size() + 3); assertFeedThrowsIOOB(ds, 0, (int) ds.size() + 3); assertSliceThrowsIOOB(ds, 0, (int) ds.size() + 3); assertCopyToThrowsIOOB(ds, 0, (int) ds.size() + 3); } } @Override protected CloseableWithDataSource createDataSource(byte[] contents) throws IOException { // "01" | contents | "9" byte[] fullContents = new byte[2 + contents.length + 1]; fullContents[0] = '0'; fullContents[1] = '1'; System.arraycopy(contents, 0, fullContents, 2, contents.length); fullContents[fullContents.length - 1] = '9'; File tmp = File.createTempFile(DataSourceFromRAFChunkTest.class.getSimpleName(), ".bin"); RandomAccessFile f = null; try { Files.write(tmp.toPath(), fullContents); f = new RandomAccessFile(tmp, "r"); } finally { if (f == null) { tmp.delete(); } } return CloseableWithDataSource.of( DataSources.asDataSource(f, 2, contents.length), new DataSourceFromRAFTest.TmpFileCloseable(tmp, f)); } } src/test/java/com/android/apksig/util/DataSourceFromRAFTest.java0100644 0000000 0000000 00000007461 13243353143 023650 0ustar000000000 0000000 /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.util; import static org.junit.Assert.assertEquals; import java.io.Closeable; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; /** * Tests for the {@link DataSource} returned by * {@link DataSources#asDataSource(java.io.RandomAccessFile)}. */ @RunWith(JUnit4.class) public class DataSourceFromRAFTest extends DataSourceTestBase { @Test public void testFileSizeChangesVisible() throws Exception { try (CloseableWithDataSource c = createDataSource("abcdefg")) { DataSource ds = c.getDataSource(); DataSource slice = ds.slice(3, 2); File f = ((TmpFileCloseable) c.getCloseable()).getFile(); assertGetByteBufferEquals("abcdefg", ds, 0, (int) ds.size()); assertGetByteBufferEquals("de", slice, 0, (int) slice.size()); assertFeedEquals("cdefg", ds, 2, 5); assertFeedEquals("e", slice, 1, 1); assertCopyToEquals("cdefg", ds, 2, 5); assertCopyToEquals("e", slice, 1, 1); assertSliceEquals("cdefg", ds, 2, 5); assertSliceEquals("e", slice, 1, 1); try (RandomAccessFile raf = new RandomAccessFile(f, "rw")) { raf.seek(7); raf.write("hijkl".getBytes(StandardCharsets.UTF_8)); } assertEquals(12, ds.size()); assertGetByteBufferEquals("abcdefghijkl", ds, 0, (int) ds.size()); assertGetByteBufferEquals("de", slice, 0, (int) slice.size()); assertFeedEquals("cdefg", ds, 2, 5); assertFeedEquals("fgh", ds, 5, 3); assertCopyToEquals("fgh", ds, 5, 3); assertSliceEquals("fgh", ds, 5, 3); } } @Override protected CloseableWithDataSource createDataSource(byte[] contents) throws IOException { File tmp = File.createTempFile(DataSourceFromRAFTest.class.getSimpleName(), ".bin"); RandomAccessFile f = null; try { Files.write(tmp.toPath(), contents); f = new RandomAccessFile(tmp, "r"); } finally { if (f == null) { tmp.delete(); } } return CloseableWithDataSource.of( DataSources.asDataSource(f), new TmpFileCloseable(tmp, f)); } /** * {@link Closeable} which closes the delegate {@code Closeable} and deletes the provided file. */ static class TmpFileCloseable implements Closeable { private final File mFile; private final Closeable mDelegate; TmpFileCloseable(File file, Closeable closeable) { mFile = file; mDelegate = closeable; } File getFile() { return mFile; } @Override public void close() throws IOException { try { if (mDelegate != null) { mDelegate.close(); } } finally { if (mFile != null) { mFile.delete(); } } } } } src/test/java/com/android/apksig/util/DataSourceTestBase.java0100644 0000000 0000000 00000035625 13243353143 023271 0ustar000000000 0000000 /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.util; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import java.io.Closeable; import java.io.IOException; import java.nio.BufferOverflowException; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import org.junit.Test; /** * Base class for testing implementations of {@link DataSource}. This class tests the contract of * {@code DataSource}. * *

To subclass, provide an implementation of {@link #createDataSource(byte[])} which returns * the implementation of {@code DataSource} you want to test. */ public abstract class DataSourceTestBase { /** * Returns a new {@link DataSource} containing the provided contents. */ protected abstract CloseableWithDataSource createDataSource(byte[] contents) throws IOException; protected CloseableWithDataSource createDataSource(String contents) throws IOException { return createDataSource(contents.getBytes(StandardCharsets.UTF_8)); } @Test public void testSize() throws Exception { try (CloseableWithDataSource c = createDataSource("Hello12345")) { DataSource ds = c.getDataSource(); assertEquals(10, ds.size()); } } @Test public void testSlice() throws Exception { try (CloseableWithDataSource c = createDataSource("Hello12345")) { DataSource ds = c.getDataSource(); assertSliceEquals("123", ds, 5, 3); DataSource slice = ds.slice(3, 5); assertGetByteBufferEquals("lo123", slice, 0, 5); // Zero-length slices assertSliceEquals("", ds, 0, 0); assertSliceEquals("", ds, 1, 0); assertSliceEquals("", ds, ds.size() - 2, 0); assertSliceEquals("", ds, ds.size() - 1, 0); assertSliceEquals("", ds, ds.size(), 0); assertSliceEquals("", slice, 0, 0); assertSliceEquals("", slice, 1, 0); assertSliceEquals("", slice, slice.size() - 2, 0); assertSliceEquals("", slice, slice.size() - 1, 0); assertSliceEquals("", slice, slice.size(), 0); // Invalid slices assertSliceThrowsIOOB(ds, -1, 0); assertSliceThrowsIOOB(slice, -1, 0); assertSliceThrowsIOOB(ds, -1, 2); assertSliceThrowsIOOB(slice, -1, 2); assertSliceThrowsIOOB(ds, -1, 20); assertSliceThrowsIOOB(slice, -1, 20); assertSliceThrowsIOOB(ds, 1, 20); assertSliceThrowsIOOB(slice, 1, 20); assertSliceThrowsIOOB(ds, ds.size() + 1, 0); assertSliceThrowsIOOB(slice, slice.size() + 1, 0); assertSliceThrowsIOOB(ds, ds.size(), 1); assertSliceThrowsIOOB(slice, slice.size(), 1); assertSliceThrowsIOOB(ds, ds.size() - 1, -1); assertSliceThrowsIOOB(ds, slice.size() - 1, -1); } } @Test public void testGetByteBuffer() throws Exception { try (CloseableWithDataSource c = createDataSource("test1234")) { DataSource ds = c.getDataSource(); assertGetByteBufferEquals("s", ds, 2, 1); DataSource slice = ds.slice(3, 4); // "t123" assertGetByteBufferEquals("2", slice, 2, 1); // Zero-length chunks assertEquals(0, ds.getByteBuffer(0, 0).capacity()); assertEquals(0, ds.getByteBuffer(ds.size(), 0).capacity()); assertEquals(0, ds.getByteBuffer(ds.size() - 1, 0).capacity()); assertEquals(0, ds.getByteBuffer(ds.size() - 2, 0).capacity()); assertEquals(0, slice.getByteBuffer(0, 0).capacity()); assertEquals(0, slice.getByteBuffer(slice.size(), 0).capacity()); assertEquals(0, slice.getByteBuffer(slice.size() - 1, 0).capacity()); assertEquals(0, slice.getByteBuffer(slice.size() - 2, 0).capacity()); // Invalid chunks assertGetByteBufferThrowsIOOB(ds, -1, 0); assertGetByteBufferThrowsIOOB(slice, -1, 0); assertGetByteBufferThrowsIOOB(ds, -1, 2); assertGetByteBufferThrowsIOOB(slice, -1, 2); assertGetByteBufferThrowsIOOB(ds, -1, 20); assertGetByteBufferThrowsIOOB(slice, -1, 20); assertGetByteBufferThrowsIOOB(ds, 1, 20); assertGetByteBufferThrowsIOOB(slice, 1, 20); assertGetByteBufferThrowsIOOB(ds, ds.size() + 1, 0); assertGetByteBufferThrowsIOOB(slice, slice.size() + 1, 0); assertGetByteBufferThrowsIOOB(ds, ds.size(), 1); assertGetByteBufferThrowsIOOB(slice, slice.size(), 1); assertGetByteBufferThrowsIOOB(ds, ds.size() - 1, -1); assertGetByteBufferThrowsIOOB(ds, slice.size() - 1, -1); } } @Test public void testFeed() throws Exception { try (CloseableWithDataSource c = createDataSource("test1234")) { DataSource ds = c.getDataSource(); assertFeedEquals("23", ds, 5, 2); DataSource slice = ds.slice(1, 5); // "est12" assertFeedEquals("t", slice, 2, 1); // Zero-length chunks assertFeedEquals("", ds, 0, 0); assertFeedEquals("", ds, 1, 0); assertFeedEquals("", ds, ds.size() - 2, 0); assertFeedEquals("", ds, ds.size() - 1, 0); assertFeedEquals("", ds, ds.size(), 0); assertFeedEquals("", slice, 0, 0); assertFeedEquals("", slice, 2, 0); assertFeedEquals("", slice, slice.size() - 2, 0); assertFeedEquals("", slice, slice.size() - 1, 0); assertFeedEquals("", slice, slice.size(), 0); // Invalid chunks assertFeedThrowsIOOB(ds, -1, 0); assertFeedThrowsIOOB(slice, -1, 0); assertFeedThrowsIOOB(ds, -1, 2); assertFeedThrowsIOOB(slice, -1, 2); assertFeedThrowsIOOB(ds, -1, 10); assertFeedThrowsIOOB(slice, -1, 10); assertFeedThrowsIOOB(ds, 1, 10); assertFeedThrowsIOOB(slice, 1, 10); assertFeedThrowsIOOB(ds, ds.size() + 1, 0); assertFeedThrowsIOOB(slice, slice.size() + 1, 0); assertFeedThrowsIOOB(ds, ds.size(), 1); assertFeedThrowsIOOB(slice, slice.size(), 1); assertFeedThrowsIOOB(ds, ds.size() - 1, -1); assertFeedThrowsIOOB(ds, slice.size() - 1, -1); } } @Test public void testCopyTo() throws Exception { try (CloseableWithDataSource c = createDataSource("abcdefghijklmnop")) { DataSource ds = c.getDataSource(); assertCopyToEquals("fgh", ds, 5, 3); DataSource slice = ds.slice(2, 7); // "cdefghi" assertCopyToEquals("efgh", slice, 2, 4); // Zero-length chunks assertCopyToEquals("", ds, 0, 0); assertCopyToEquals("", ds, 1, 0); assertCopyToEquals("", ds, ds.size() - 2, 0); assertCopyToEquals("", ds, ds.size() - 1, 0); assertCopyToEquals("", ds, ds.size(), 0); assertCopyToEquals("", slice, 0, 0); assertCopyToEquals("", slice, 2, 0); assertCopyToEquals("", slice, slice.size() - 2, 0); assertCopyToEquals("", slice, slice.size() - 1, 0); assertCopyToEquals("", slice, slice.size(), 0); // Invalid chunks assertCopyToThrowsIOOB(ds, -1, 0); assertCopyToThrowsIOOB(slice, -1, 0); assertCopyToThrowsIOOB(ds, -1, 2); assertCopyToThrowsIOOB(slice, -1, 2); assertCopyToThrowsIOOB(ds, -1, 20); assertCopyToThrowsIOOB(slice, -1, 20); assertCopyToThrowsIOOB(ds, 1, 20); assertCopyToThrowsIOOB(slice, 1, 20); assertCopyToThrowsIOOB(ds, ds.size() + 1, 0); assertCopyToThrowsIOOB(slice, slice.size() + 1, 0); assertCopyToThrowsIOOB(ds, ds.size(), 1); assertCopyToThrowsIOOB(slice, slice.size(), 1); assertCopyToThrowsIOOB(ds, ds.size() - 1, -1); assertCopyToThrowsIOOB(ds, slice.size() - 1, -1); // Destination buffer too small ByteBuffer buf = ByteBuffer.allocate(5); buf.position(2); buf.limit(3); assertCopyToThrowsBufferOverflow(ds, 0, 2, buf); buf.position(2); buf.limit(3); assertCopyToThrowsBufferOverflow(slice, 1, 2, buf); // Destination buffer larger than chunk copied using copyTo buf = ByteBuffer.allocate(10); buf.position(2); assertCopyToEquals("bcd", ds, 1, 3, buf); buf = ByteBuffer.allocate(10); buf.position(2); assertCopyToEquals("fg", slice, 3, 2, buf); } } protected static void assertSliceEquals( String expectedContents, DataSource ds, long offset, int size) throws IOException { DataSource slice = ds.slice(offset, size); assertEquals(size, slice.size()); assertGetByteBufferEquals(expectedContents, slice, 0, size); } protected static void assertSliceThrowsIOOB(DataSource ds, long offset, int size) { try { ds.slice(offset, size); fail(); } catch (IndexOutOfBoundsException expected) {} } protected static void assertGetByteBufferEquals( String expectedContents, DataSource ds, long offset, int size) throws IOException { ByteBuffer buf = ds.getByteBuffer(offset, size); assertEquals(0, buf.position()); assertEquals(size, buf.limit()); assertEquals(size, buf.capacity()); assertEquals(expectedContents, toString(buf)); } protected static void assertGetByteBufferThrowsIOOB(DataSource ds, long offset, int size) throws IOException { try { ds.getByteBuffer(offset, size); fail(); } catch (IndexOutOfBoundsException expected) {} } protected static void assertFeedEquals( String expectedFedContents, DataSource ds, long offset, int size) throws IOException { ReadableDataSink out = DataSinks.newInMemoryDataSink(size); ds.feed(offset, size, out); assertEquals(size, out.size()); assertEquals(expectedFedContents, toString(out.getByteBuffer(0, size))); } protected static void assertFeedThrowsIOOB(DataSource ds, long offset, long size) throws IOException { try { ds.feed(offset, size, NullDataSink.INSTANCE); fail(); } catch (IndexOutOfBoundsException expected) {} } protected static void assertCopyToEquals( String expectedContents, DataSource ds, long offset, int size) throws IOException { // Create a ByteBuffer backed by a section of a byte array. The ByteBuffer is on purpose not // starting at offset 0 to catch issues to do with not checking ByteBuffer.arrayOffset(). byte[] arr = new byte[size + 10]; ByteBuffer buf = ByteBuffer.wrap(arr, 1, size + 5); // Use non-zero position to catch issues with not checking buf.position() buf.position(2); // Buffer contains sufficient space for the requested copyTo operation assertEquals(size + 4, buf.remaining()); assertCopyToEquals(expectedContents, ds, offset, size, buf); } private static void assertCopyToEquals( String expectedContents, DataSource ds, long offset, int size, ByteBuffer buf) throws IOException { int oldPosition = buf.position(); int oldLimit = buf.limit(); ds.copyTo(offset, size, buf); // Position should've advanced by size whereas limit should've remained unchanged assertEquals(oldPosition + size, buf.position()); assertEquals(oldLimit, buf.limit()); buf.limit(buf.position()); buf.position(oldPosition); assertEquals(expectedContents, toString(buf)); } protected static void assertCopyToThrowsIOOB(DataSource ds, long offset, int size) throws IOException { ByteBuffer buf = ByteBuffer.allocate((size < 0) ? 0 : size); try { ds.copyTo(offset, size, buf); fail(); } catch (IndexOutOfBoundsException expected) {} } private static void assertCopyToThrowsBufferOverflow( DataSource ds, long offset, int size, ByteBuffer buf) throws IOException { try { ds.copyTo(offset, size, buf); fail(); } catch (BufferOverflowException expected) {} } /** * Returns the contents of the provided buffer as a string. The buffer's position and limit * remain unchanged. */ static String toString(ByteBuffer buf) { byte[] arr; int offset; int size = buf.remaining(); if (buf.hasArray()) { arr = buf.array(); offset = buf.arrayOffset() + buf.position(); } else { arr = new byte[buf.remaining()]; offset = 0; int oldPos = buf.position(); buf.get(arr); buf.position(oldPos); } return new String(arr, offset, size, StandardCharsets.UTF_8); } public static class CloseableWithDataSource implements Closeable { private final DataSource mDataSource; private final Closeable mCloseable; private CloseableWithDataSource(DataSource dataSource, Closeable closeable) { mDataSource = dataSource; mCloseable = closeable; } public static CloseableWithDataSource of(DataSource dataSource) { return new CloseableWithDataSource(dataSource, null); } public static CloseableWithDataSource of(DataSource dataSource, Closeable closeable) { return new CloseableWithDataSource(dataSource, closeable); } public DataSource getDataSource() { return mDataSource; } public Closeable getCloseable() { return mCloseable; } @Override public void close() throws IOException { if (mCloseable != null) { mCloseable.close(); } } } private static final class NullDataSink implements DataSink { private static final NullDataSink INSTANCE = new NullDataSink(); @Override public void consume(byte[] buf, int offset, int length) {} @Override public void consume(ByteBuffer buf) {} } } src/test/java/com/android/apksig/util/InMemoryDataSinkDataSourceTest.java0100644 0000000 0000000 00000002310 13243353143 025556 0ustar000000000 0000000 /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.util; import java.io.IOException; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; /** * Tests for the {@link DataSource} returned by {@link DataSinks#newInMemoryDataSink()}. */ @RunWith(JUnit4.class) public class InMemoryDataSinkDataSourceTest extends DataSourceTestBase { @Override protected CloseableWithDataSource createDataSource(byte[] contents) throws IOException { ReadableDataSink sink = DataSinks.newInMemoryDataSink(); sink.consume(contents, 0, contents.length); return CloseableWithDataSource.of(sink); } } src/test/java/com/android/apksig/util/InMemoryDataSinkTest.java0100644 0000000 0000000 00000002652 13243353143 023614 0ustar000000000 0000000 /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apksig.util; import java.io.IOException; import java.nio.ByteBuffer; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; /** * Tests for the {@link DataSink} returned by {@link DataSinks#newInMemoryDataSink()}. */ @RunWith(JUnit4.class) public class InMemoryDataSinkTest extends DataSinkTestBase { @Override protected CloseableWithDataSink createDataSink() { return CloseableWithDataSink.of(DataSinks.newInMemoryDataSink()); } @Override protected ByteBuffer getContents(ReadableDataSink dataSink) throws IOException { if (dataSink.size() > Integer.MAX_VALUE) { throw new IOException("Too much data: " + dataSink.size()); } return dataSink.getByteBuffer(0, (int) dataSink.size()); } } src/test/resources/0040755 0000000 0000000 00000000000 13243353143 013371 5ustar000000000 0000000 src/test/resources/com/0040755 0000000 0000000 00000000000 13243353143 014147 5ustar000000000 0000000 src/test/resources/com/android/0040755 0000000 0000000 00000000000 13243353143 015567 5ustar000000000 0000000 src/test/resources/com/android/apksig/0040755 0000000 0000000 00000000000 13243353143 017045 5ustar000000000 0000000 src/test/resources/com/android/apksig/dsa-1024.pk80100644 0000000 0000000 00000000516 13243353143 020623 0ustar000000000 0000000 0J0+*H80.XHeYY?`·7`ߞV>U vGjᡵ/ȩX0m܀|;oQ}05זjNwb(#c `~AfpqrknRPmNs. XF-~,9r%q ) A]M^^ ɕEUQ:UץXX=;4ڐ8ݐB@Znѷm2@,0A 7``src/test/resources/com/android/apksig/dsa-1024.x509.pem0100644 0000000 0000000 00000001741 13243353143 021407 0ustar000000000 0000000 -----BEGIN CERTIFICATE----- MIICsTCCAnGgAwIBAgIJAP6EmkoBF8UoMAkGByqGSM44BAMwEzERMA8GA1UEAwwI ZHNhLTEwMjQwHhcNMTYwMzMxMTUyNzEwWhcNNDMwODE3MTUyNzEwWjATMREwDwYD VQQDDAhkc2EtMTAyNDCCAbYwggErBgcqhkjOOAQBMIIBHgKBgQCTDpv8gS5Y+Ehl oln6WT/MYBnywrc3tWDfnlY+9MpVDdB2+kcB7WrhobW1L+6ayKmlkrTaAFjiMPDf bdyA6hy3fDu1teLCb89R0uodfZa3MDXXlmqvBk4Fdw8fYijWI/q175e4Y5sNYO9+ QZg8bBIZnxxCdbKASJ6NAHc50ts3vwIVAIebRw3HnYOZbo6rPoBmcBOxcZTLAoGA ch+0D7JrbqmR1w5S3VBtTnONLiBYnaz1Ri3Pfiw5FHKfJcfFcQopIOLJwfdBmY4b FLGV5u7DXeJNp16Nvl4MrsmVjkWs9MZVAp5RqzrN9JhVi4ShpdelyFjdWOXHPbc7 NNqQpTjdkK23r/tCE6XkvkCiWm7Rt22LMpZA4ePALIoDgYQAAoGAc8SkppDzSUPH SpKrhrldRyh5m4wSH14ZE96mlSze9tRoSDo8hsA9/vGLgoN7F+3jYSvj8m42tmNt jZJWk7vPkJHC/9qoEGbVBY+aTNYwVJyKDJ07vZB9bLxpjD/yyQlsn7/vZTOS657c W2S817RgGGyGcCNRoKNig6i0k9fzE8ajUDBOMB0GA1UdDgQWBBSPwzoIjftVH2ke EJXtLq+bB50lzDAfBgNVHSMEGDAWgBSPwzoIjftVH2keEJXtLq+bB50lzDAMBgNV HRMEBTADAQH/MAkGByqGSM44BAMDLwAwLAIUH1GQcpqx8/9p9QfhCRMvcxrECM4C FH8ZULK91BMaHodbRMUtdxB9kIbL -----END CERTIFICATE----- src/test/resources/com/android/apksig/dsa-2048.pk80100644 0000000 0000000 00000001150 13243353143 020625 0ustar000000000 0000000 0d09*H80,oRG"٦Y92+lS[2Z1MZn=Zq !_2h"2~At~zYc{6->rx%z}.B_4 @5HŻP~ן7N\;ϛۄ> Qh%e$Ög) Y*B$ np^,rvSFP>1)?Q;|"-=?+(Ojb뚘l\#U pFi !+)nLyg.Yaj}9,7{ZIȾG1N$J2[f!̵H8P[x^qpq{} )"vGEà)o3JD7̨j䣍>?b|nع^Cɤx5yez ^< x4o86;bI1/|[ڀ, P<0ϳUcExf%Ȥ*:Nh 0MH0@" pDE1j-vf8D'钛src/test/resources/com/android/apksig/dsa-2048.x509.pem0100644 0000000 0000000 00000003042 13243353143 021412 0ustar000000000 0000000 -----BEGIN CERTIFICATE----- MIIEWzCCBAKgAwIBAgIJAK4uYtTAat4+MAkGByqGSM44BAMwEzERMA8GA1UEAwwI ZHNhLTIwNDgwHhcNMTYwMzMxMTgzMDAxWhcNNDMwODE3MTgzMDAxWjATMREwDwYD VQQDDAhkc2EtMjA0ODCCA0cwggI5BgcqhkjOOAQBMIICLAKCAQEAv2/yUkci8fnZ ppepy1kcOTIrbPJTW46Z2jKOWjHCTd1aqRFunz3gllpx/YDgjboNIYZf2jKhk2gi MgF+8OT0hEHQGXSofnqRoVljexA2nC3tPhVyjN14pQ7ZJXp/raHh1uCT5K7Lfafc 8y5Cs180CdfisspANfCp10jFu1D2hKOLfggUA9efN05c0f2yOx/Pm9uEiz6ftLUJ UWgcJenx1WUk4Nb1qMOWgg9nKQyDiBxZKoVCH5/XJO0K225w6F4scol2hMdTRlA+ 9TGb5tvHKT+Y+lE7fBbcBSKzLT3pwD/T08ErpsIoHqRPamLGHeuamGzvXLHwI1UN cJNGg2nnCwIhAJz9K+Qpgg7sbqRMeRvIf2cuWRWbi4XnYR+IAGqgfYUZAoIBADks N3taHUnIvkcxTuEUiCSb+d1KMlvbZiH5zLVIyzi5kZNQW8L/gR23z8T6qngXXnFw tcTRcb0TDqR7iYPxsX3/x+jBjiApIgebqpnPdpiEAUfORZX7w6CpCNsp9Kqxb98z 4PtKE9z/20Q3zKhqlwDm8uSjjT7E5xw/YpV8bg7YuQ5eQ8mkonipNXllf3qmDV48 see/+QwIeBiL7jTjgeBv5ziL8BY2AYmfOwGKwmK9ERxJpIoxgS+5fFuA2oAQ8CwJ UDwwv5Wbz7NVkvljHK1Fh5u26ZHdeLT4qp4F0GamJcikq4MqFco6ToCvp8NoDNMZ MOno2k1IvsIwrIbrQJgDggEGAAKCAQEAuxbFbx2H4n4BqfbkC9tjn/Mg3zr4LZgG 7v3FWpzpkbAcEcFCVIqTmJiRlsuf4ml/t9hflOvarfD6TesSc7gyGCJ/2QiqJcI+ Vif5AKqZskQFlZ5BUMIMjPFMy1WtTVpEotmdbIOaQif4wQrz6SNFUOAXPBKRTY33 HOLMoo8FRiZ1+uMu9PlUWYqMhSJg+rm2AQPt06D+JToXREaNkYjN0K97T2MTcUNh OWiliH/zFuF8N2s6IlNaCv1Yc4FoYEIRoS07dUxcjrV9KRd0TyU0q++rRPluytJP yAoyTIrfwa2SM5JR9RtdBZsPdR9Ckpy4ZKSJqDzTbCIU70zsGgHA4aNQME4wHQYD VR0OBBYEFL3koBjVHAySf8Xr196yqDT6VhwSMB8GA1UdIwQYMBaAFL3koBjVHAyS f8Xr196yqDT6VhwSMAwGA1UdEwQFMAMBAf8wCQYHKoZIzjgEAwNIADBFAiEAjowh laeXA/CUrHt6iH4u6edWGeZzyFGlpWsxssKTMBECIG2tZs/xnZVAtXioiIcH1CXT LN5AAzZ8wlNUKSvTc12j -----END CERTIFICATE----- src/test/resources/com/android/apksig/dsa-3072.pk80100644 0000000 0000000 00000001550 13243353143 020627 0ustar000000000 0000000 0d09*H80,(2< u^OO6?qX1t"Q@N|%, p۳$}0.E1.=u,D੝  s-amTKmܣ jY>nDfi%'KR;;\YViaP;n$t.}BFsJL=v6^ǼВX5Nj$P3zGn S PȺ{ǯwW6@f&ߵ/rm04Z#l"$hmW & 1f@as/0 n%p ݱ)'߭6a:4/+3KwY.ШǍd@]8{q3PE@G5.΁YR wd%bR˚2x)O)6=%§?#C;7 < bޠ{C@ψ)[!Czv_elt]Gu7wdƇz7AZ0Kõ/9›*ʽ):] ֨ǽEf4.!H l@nPoP= N}x;wGnoH00P" _2:j t#_t?yKiusrc/test/resources/com/android/apksig/dsa-3072.x509.pem0100644 0000000 0000000 00000004056 13243353143 021416 0ustar000000000 0000000 -----BEGIN CERTIFICATE----- MIIF3DCCBYKgAwIBAgIJAKmpH5wXPDbGMAkGByqGSM44BAMwEzERMA8GA1UEAwwI ZHNhLTMwNzIwHhcNMTYwMzMxMTgzMjU2WhcNNDMwODE3MTgzMjU2WjATMREwDwYD VQQDDAhkc2EtMzA3MjCCBMcwggM5BgcqhkjOOAQBMIIDLAKCAYEAscwotTL9jjwZ CpGRqXVezU9P0zY/wHFYMQB09JjgoCJRm0D9FZzmTnySJa4suwlwoduzgv+pkIkk nn0w+hL6LsblRTEuFj11o/fLLAZE4KmdIAPkCcH74HOnws4BwazJLWFtVEv+bdyj CmrI2urYWT5uRJwWZmkG4iUnoL/5FUv2pvpSOzu6EO+qFcFcWf3/VrQDaaP3jRRh iVCThxvX5xOrO24krrOFlHQufY0A6fHYQqII6EYac6tKTI+ltz121DYH914SHMe8 0JJYpDVOatjTJADyg1AzHAKsyfTKHHpHbqkgU/CRtALEC1DUyLqQhHvBGpodmqPH r9Mbd7pXudw2h6O8uapAsmaFt/YdrSbd37UvhLJyyW0fodTjBTA0WiNs0CLTJL9o 422Nt1cUDQS1JpsghAzmMWaavq7+QIRhpnMHL47p1jCZvcwMboMlcJwOCjyyZS0F fmaD+pEi56Lx+9XDblESM78crb8/lbvuohyrwIJciQmL0hQoKIg1AiEApYT1sROj BMB4FIQaS86VKdJ4h9OR13AraES3ba9WV1cCggGAI9LskZ7wcUna6nZyGT4f3bH1 KSeD3602Yd86NKuB8QGr/QT5L+wFwCuvvI4V8P2lM4wb+q1LrR688f53WS4c0Kin /MeN4YtkQBzV4l04Bxrce//EcfQzUPDAr41Fwu2tA0BHE67trxGjNS7SzoFZUiDo d/aOZCVilFIay5oF2X8yeNcpTynsxTbGxjxhhgG2tS0fvxhbGF6rl4D0Ddskx6L/ xjstzu0ADE8FNpDehuKqxzOisNLIJgc1INPQ06AmaUF1Pge74T2AJYPYwqfzArG3 pj+l2iND0PM7lDeQCgivPAqhYrveoIqJe0NAEu/PiCnOW50hQ4F6dl9lzn9sGXTP 5KxdR5OyG+Ki+nWTN8h3yOy9ZK7Gh8gUeqE3QVow4+1LhcO19C+X+NM588KbKsq9 KRP5H4v238g6oF2wgbmPrYvqiwoa1qjHvUX7ZjQuIfrN9+lICflsHKtAtchuUIiO b1AWPeaJwAynTn2EeDsVd0e8bm+h+g7ZSDAwUOKuA4IBhgACggGBAJfW6T1BxtZo EacqR2TwTi9B3O93Zcj0OzDOn9UMahepcfaF1B3wq9UdJ4PPIC1U27CaEXEJNxgJ cIURfFPDXA6pLVEqrGgRX2u0FR1pmHVIxjBpvPJvO954+Hawp+ClU6PKLmxuVZpA VeJBx7C8/DU/58J//iuG9B6/mwzPuEoPmnGYMsfVCcEX8yQ2PZXyNGp+KnUygyql mMxpE8UXtH6mHYontjiw4Afxqwop3v/bG8eT5FHhpXoQDSN/oeGghDaq9DyIjCqr GXZDBvEufLYRgOvnpV6T0oMa9U2W2Trz3HYD6eX09FOhGnMWl9euYrSVRihAKBh1 72ystvPy2R4wdAA5EQf7EsDdgt/QOMf/AvkaKHMP7DNK5BZxwPQ0KwRkLB+0vaDI Mnpu29L6TiixMxI1ihnZbR/U0v8H+/SlSjZaPNRQeuoV9d8t81miUqVSMLcpqNui ZljSCtoGfktPAbPyFwGenBkGK5oo0I381KTECMynC0R2P9CQUHegeaNQME4wHQYD VR0OBBYEFPRqMyQLPdq0guTx3YKiVxtWN+bPMB8GA1UdIwQYMBaAFPRqMyQLPdq0 guTx3YKiVxtWN+bPMAwGA1UdEwQFMAMBAf8wCQYHKoZIzjgEAwNJADBGAiEAjcit 8dFR02elWKoeRAounP9TE2aqDqd5cJXqXn0ssMYCIQCIINjXEYRovfVjDotKelRg 5k0lmzMmx6Xfz8EgZDLovw== -----END CERTIFICATE----- src/test/resources/com/android/apksig/ec-p256.pk80100644 0000000 0000000 00000000212 13243353143 020542 0ustar000000000 0000000 00*H=*H=m0k |g*.M ]1=XM BX!ƾDB_="I1+x_e6NΐҴᔵ ܎T:v2%ǂtDsrc/test/resources/com/android/apksig/ec-p256.x509.pem0100644 0000000 0000000 00000001052 13243353143 021330 0ustar000000000 0000000 -----BEGIN CERTIFICATE----- MIIBbDCCARGgAwIBAgIJAMoPtk37ZudyMAoGCCqGSM49BAMCMBIxEDAOBgNVBAMM B2VjLXAyNTYwHhcNMTYwMzMxMTQ1ODA2WhcNNDMwODE3MTQ1ODA2WjASMRAwDgYD VQQDDAdlYy1wMjU2MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEpl8RPSLLSROQ gwesMe4roOkTi3hfrGU20U6izpDStL/hlLUM3I4Wn1SnOpke8Pp2MpglvgeMx4J0 BwPaRLTX66NQME4wHQYDVR0OBBYEFNQTNWi5WzAVizIgceqMQ/9bBczIMB8GA1Ud IwQYMBaAFNQTNWi5WzAVizIgceqMQ/9bBczIMAwGA1UdEwQFMAMBAf8wCgYIKoZI zj0EAwIDSQAwRgIhAPUEoIZsrvAp9BcULFy3E1THn/zR1kBhjfyk8Z4W23jWAiEA +O6kgpeZwGytCMbT0tLsBeBXQVTnR+oP27gELLZVqt0= -----END CERTIFICATE----- src/test/resources/com/android/apksig/ec-p384.pk80100644 0000000 0000000 00000000271 13243353143 020551 0ustar000000000 0000000 00*H=+"00eOk_TRZK!Osyw(`cNf74ppNظtdbcu?q4ģ= 8A(bo#(%=5C~>U.cXKۜ' Ʒ8?\ src/test/resources/com/android/apksig/ec-p384.x509.pem0100644 0000000 0000000 00000001173 13243353143 021336 0ustar000000000 0000000 -----BEGIN CERTIFICATE----- MIIBqTCCAS6gAwIBAgIJAMRWS+CLIsxqMAoGCCqGSM49BAMDMBIxEDAOBgNVBAMM B2VjLXAzODQwHhcNMTYwMzMxMTUzMDU3WhcNNDMwODE3MTUzMDU3WjASMRAwDgYD VQQDDAdlYy1wMzg0MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE18hjdbk/HXGQNIuF xKPMAZO3PQnROO6izB/mHM1BKPpih2/51iMTFKn6KCU9NZt/Q4Z+PpZVLuawEWP/ uoWwWIj+60vk25z47/Sr0icelSDGt9T9ujiNP6aTA5hc9gypo1AwTjAdBgNVHQ4E FgQU981MoejFjh0rbaGXODywOYvB32kwHwYDVR0jBBgwFoAU981MoejFjh0rbaGX ODywOYvB32kwDAYDVR0TBAUwAwEB/zAKBggqhkjOPQQDAwNpADBmAjEA/58rXa+F mB6JwB89/IAucpNlktjSPrH2tD63BSROvpUpXNy+p+OlJu4sCvY7HnwEAjEA0VWw QqUBFLQHFJx1JjMYYfT78V8ylY+Ns1lxrdvs29NNg45MA9uw/ZVMMHgTFNph -----END CERTIFICATE----- src/test/resources/com/android/apksig/ec-p521.pk80100644 0000000 0000000 00000000361 13243353143 020542 0ustar000000000 0000000 00*H=+#0BوWt$J*xX,UD>/y8mMmڡ/56ѱ@0Utay( ȰTʯr,yN ~ua7*F 6 0YV&,S֕'a(P6ӵ)kشӘ J~|En6/eBusHPzsrc/test/resources/com/android/apksig/ec-p521.x509.pem0100644 0000000 0000000 00000001341 13243353143 021324 0ustar000000000 0000000 -----BEGIN CERTIFICATE----- MIIB8zCCAVSgAwIBAgIJAOxXdFsvm3YiMAoGCCqGSM49BAMEMBIxEDAOBgNVBAMM B2VjLXA1MjEwHhcNMTYwMzMxMTUzMTIyWhcNNDMwODE3MTUzMTIyWjASMRAwDgYD VQQDDAdlYy1wNTIxMIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQAYX95sSjPEQqg yLD04tNUyq9y/w8seblOpfqa/Amx6H4GFdrjGXX0YTfXKr9GhAyIyQSnNrIg0zDl WQUbBPRW4CYBLFOg1pUn1NBhKFD4NtO1KWvYtNOYDegFjRCPB0p+fEXDbq8QFDYv lh+NZUJ16+ih8XNIf1C29xuLEqN6oKOnAvajUDBOMB0GA1UdDgQWBBT/Ra3kz60g Q7tYk3byZckcLabt8TAfBgNVHSMEGDAWgBT/Ra3kz60gQ7tYk3byZckcLabt8TAM BgNVHRMEBTADAQH/MAoGCCqGSM49BAMEA4GMADCBiAJCAP39hYLsWk2H84oEw+HJ qGGjexhqeD3vSO1mWhopripE/81oy3yV10puYoJe11xDSfcDj2VfNCHazuXO3kSx GA/1AkIBLUJxp/WYbYzhBGKr6lcxczKI/wuMfkZ6vL+0EMJVA/2uEoeqvnl7Bsdk icyaOBNEADijuVdaPPIWzKClt9OaVxE= -----END CERTIFICATE----- src/test/resources/com/android/apksig/empty-unsigned.apk0100644 0000000 0000000 00000000026 13243353143 022505 0ustar000000000 0000000 PKsrc/test/resources/com/android/apksig/golden-aligned-in.apk0100644 0000000 0000000 00000012754 13243353143 023025 0ustar000000000 0000000 PK)[J META-INF/PKPK)[JLMMETA-INF/MANIFEST.MFMLK-. K-*ϳR03r.JM,IMu Xěf敤%(h%&*8%irrPKLMPK!:^avAndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK !:Շ classes.dex5dex 035jIr9ԄJo:u?pxV40p  $ ,t Gs(;CFJWag{        $po nppp ILandroid/app/Activity;.Landroid/appsecurity/cts/tinyapp/MainActivity;(Landroid/appsecurity/cts/tinyapp/R$attr;*Landroid/appsecurity/cts/tinyapp/R$string;#Landroid/appsecurity/cts/tinyapp/R;Landroid/os/Bundle;"Ldalvik/annotation/EnclosingClass;Ldalvik/annotation/InnerClass;!Ldalvik/annotation/MemberClasses;Ljava/lang/Object;MainActivity.javaR.javaVVL accessFlagsapp_nameattremitter: jack-3.26finishnameonCreatesavedInstanceStatestringthisvalue<9    dp  $ ,t       0PKZ[Jtemp.txtst PKPK [J51lib/armeabi/fake.so5Hello PK !:!Oresources.arsc5 (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK[J> temp2.txtsr\PK>PK)[J META-INF/PK)[JLM=META-INF/MANIFEST.MFPK!:^avAndroidManifest.xmlPK !:Շ classes.dexPKZ[J temp.txtPK [J51 lib/armeabi/fake.soPK !:!Oresources.arscPK[J> temp2.txtPKsrc/test/resources/com/android/apksig/golden-aligned-out.apk0100644 0000000 0000000 00000021727 13243353143 023226 0ustar000000000 0000000 PK!:^avAndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK !:Շ classes.dex5dex 035jIr9ԄJo:u?pxV40p  $ ,t Gs(;CFJWag{        $po nppp ILandroid/app/Activity;.Landroid/appsecurity/cts/tinyapp/MainActivity;(Landroid/appsecurity/cts/tinyapp/R$attr;*Landroid/appsecurity/cts/tinyapp/R$string;#Landroid/appsecurity/cts/tinyapp/R;Landroid/os/Bundle;"Ldalvik/annotation/EnclosingClass;Ldalvik/annotation/InnerClass;!Ldalvik/annotation/MemberClasses;Ljava/lang/Object;MainActivity.javaR.javaVVL accessFlagsapp_nameattremitter: jack-3.26finishnameonCreatesavedInstanceStatestringthisvalue<9    dp  $ ,t       0PKZ[Jtemp.txtst PKPK [J51lib/armeabi/fake.so5٧Hello PK !:!Oresources.arsc5 (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK[J> temp2.txtsr\PK>PK[JIMETA-INF/RSA-2048.SFm]s0{g\nABEXK.&QuvS.sΜ'!S+ )Puvˮ1)oA5. zjH=f\%c(p^cs QMo47 BP۝^_[n'r`4vݺwD_>Ku+J1u Μ x=zQn/1:Giqr!WҖ7=kcu{T CƂikʦf?i,θ튌QF,/UZ2 <{l3~62kU67H>*uٯ.>uMm}n4sONe)~{ybě Iqۮٟ֮+~WY77~3 9Lx0^y͵xJ^}Zx/dO(0{[Ҿr3Nkߍ,MUZGϰp]j3:}_jn^)uHkNz%F}[sٻPK[JcMMETA-INF/MANIFEST.MFmM@߁cʚv,R 0#0 n^*ȸ@+AӉM!`H_."!Š-lB{BN'I:L\RJ,];DwaIԼdid fiP΅' q{w9,( *lSD[Z5l}WmZٓe00 50l0  *H  010U rsa-20480 160331145749Z 430817145749Z010U rsa-20480"0  *H 0 5^0#YY2aD%],E sdM,T75P"¥TtImyjDn%X'J5" qrNsߧgBF_3.."/:GMH)x0(hC$nqJ;ߥ_'{7Wzq`haJh\xJb0N vt\%3Xw'ɦ"[z塌8б(!!,[P0N0U-s89j!Y,o0U#0-s89j!Y,o0 U00  *H  dKnBȓ@dyYNFsK'6 , $u*\I&}{y?0}xG)L` 2@* temp2.txtPK[JIMETA-INF/RSA-2048.SFPK[J%META-INF/RSA-2048.RSAPK[JcM0META-INF/MANIFEST.MFPK +!src/test/resources/com/android/apksig/golden-aligned-v1-out.apk0100644 0000000 0000000 00000017033 13243353143 023545 0ustar000000000 0000000 PK!:^avAndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK !:Շ classes.dex5dex 035jIr9ԄJo:u?pxV40p  $ ,t Gs(;CFJWag{        $po nppp ILandroid/app/Activity;.Landroid/appsecurity/cts/tinyapp/MainActivity;(Landroid/appsecurity/cts/tinyapp/R$attr;*Landroid/appsecurity/cts/tinyapp/R$string;#Landroid/appsecurity/cts/tinyapp/R;Landroid/os/Bundle;"Ldalvik/annotation/EnclosingClass;Ldalvik/annotation/InnerClass;!Ldalvik/annotation/MemberClasses;Ljava/lang/Object;MainActivity.javaR.javaVVL accessFlagsapp_nameattremitter: jack-3.26finishnameonCreatesavedInstanceStatestringthisvalue<9    dp  $ ,t       0PKZ[Jtemp.txtst PKPK [J51lib/armeabi/fake.so5٧Hello PK !:!Oresources.arsc5 (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK[J> temp2.txtsr\PK>PK[J|META-INF/RSA-2048.SFmK@l\pGyޚFhnOfw'1*ʯU!> j)x0~n`(eB~/&:Deߋx'y%x]w ܩn& ;2ch<2ʯ;ZDr|QY XY`g5"o'?*t@P#Lj|vWyխזv^O,mg2h+2, I~- 7ۋ[C/!Ѭ\ܭ?N96SӶ] yAK7?z@gVUܨWv.C 4׌gZ&GJDIfWJi+rpÏu Oܬ֊(]s!BHݹ6Gp;PK[JB%META-INF/RSA-2048.RSA3hbiajhδԠѐ߀3̓1qA_&M03121q2eU 'l(hQTkd`ba 'khf`llhhbjnb%kbl`ahbĨl8QM @q.&FF & 5`Y契FodpQ] ʽ8w:˧1pT<@IҐ%pTf5_v@yy{m`:iOawv,^v#w:ns7[Ǫu;_ƽS\4`WI *yxY~pizyxՏO 2Y'2^ʈ^ /d틐7?.+Y;9Fs.=(oW?lR_Y {6YtM}H' FoDox}qq,0dXXDęt-f[ R#o Vf0k@Հ?Z2{mMOė{O_W'&;0_OR"Oލuf<txUKt?aVHo{q鋏ֶop_ICNfJLhTcxJWE2y,ZnL41NSCslv)>e ]U;=smƒ%I[:TJdt OZ1X:QmG…{gU_vzWc,'%vK-OXM}wS -1U ٣Ÿ {# 50^^'}o!5-~gh8(^us}$冎PK[JcMMETA-INF/MANIFEST.MFmM@߁cʚv,R 0#0 n^*ȸ@+AӉM!`H_."!Š-lB{BN'I:L\RJ,];DwaIԼdid fiP΅'PK!:^avAndroidManifest.xmlPK !:Շ classes.dexPKZ[Jtemp.txtPK [J51$ lib/armeabi/fake.soPK !:!Oresources.arscPK[J> temp2.txtPK[J|META-INF/RSA-2048.SFPK[JB%META-INF/RSA-2048.RSAPK[JcMMETA-INF/MANIFEST.MFPK +src/test/resources/com/android/apksig/golden-aligned-v1v2-out.apk0100644 0000000 0000000 00000021727 13243353143 024022 0ustar000000000 0000000 PK!:^avAndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK !:Շ classes.dex5dex 035jIr9ԄJo:u?pxV40p  $ ,t Gs(;CFJWag{        $po nppp ILandroid/app/Activity;.Landroid/appsecurity/cts/tinyapp/MainActivity;(Landroid/appsecurity/cts/tinyapp/R$attr;*Landroid/appsecurity/cts/tinyapp/R$string;#Landroid/appsecurity/cts/tinyapp/R;Landroid/os/Bundle;"Ldalvik/annotation/EnclosingClass;Ldalvik/annotation/InnerClass;!Ldalvik/annotation/MemberClasses;Ljava/lang/Object;MainActivity.javaR.javaVVL accessFlagsapp_nameattremitter: jack-3.26finishnameonCreatesavedInstanceStatestringthisvalue<9    dp  $ ,t       0PKZ[Jtemp.txtst PKPK [J51lib/armeabi/fake.so5٧Hello PK !:!Oresources.arsc5 (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK[J> temp2.txtsr\PK>PK[JIMETA-INF/RSA-2048.SFm]s0{g\nABEXK.&QuvS.sΜ'!S+ )Puvˮ1)oA5. zjH=f\%c(p^cs QMo47 BP۝^_[n'r`4vݺwD_>Ku+J1u Μ x=zQn/1:Giqr!WҖ7=kcu{T CƂikʦf?i,θ튌QF,/UZ2 <{l3~62kU67H>*uٯ.>uMm}n4sONe)~{ybě Iqۮٟ֮+~WY77~3 9Lx0^y͵xJ^}Zx/dO(0{[Ҿr3Nkߍ,MUZGϰp]j3:}_jn^)uHkNz%F}[sٻPK[JcMMETA-INF/MANIFEST.MFmM@߁cʚv,R 0#0 n^*ȸ@+AӉM!`H_."!Š-lB{BN'I:L\RJ,];DwaIԼdid fiP΅' q{w9,( *lSD[Z5l}WmZٓe00 50l0  *H  010U rsa-20480 160331145749Z 430817145749Z010U rsa-20480"0  *H 0 5^0#YY2aD%],E sdM,T75P"¥TtImyjDn%X'J5" qrNsߧgBF_3.."/:GMH)x0(hC$nqJ;ߥ_'{7Wzq`haJh\xJb0N vt\%3Xw'ɦ"[z塌8б(!!,[P0N0U-s89j!Y,o0U#0-s89j!Y,o0 U00  *H  dKnBȓ@dyYNFsK'6 , $u*\I&}{y?0}xG)L` 2@* temp2.txtPK[JIMETA-INF/RSA-2048.SFPK[J%META-INF/RSA-2048.RSAPK[JcM0META-INF/MANIFEST.MFPK +!src/test/resources/com/android/apksig/golden-aligned-v2-out.apk0100644 0000000 0000000 00000015432 13243353143 023547 0ustar000000000 0000000 PK!:^avAndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK !:Շ classes.dex5dex 035jIr9ԄJo:u?pxV40p  $ ,t Gs(;CFJWag{        $po nppp ILandroid/app/Activity;.Landroid/appsecurity/cts/tinyapp/MainActivity;(Landroid/appsecurity/cts/tinyapp/R$attr;*Landroid/appsecurity/cts/tinyapp/R$string;#Landroid/appsecurity/cts/tinyapp/R;Landroid/os/Bundle;"Ldalvik/annotation/EnclosingClass;Ldalvik/annotation/InnerClass;!Ldalvik/annotation/MemberClasses;Ljava/lang/Object;MainActivity.javaR.javaVVL accessFlagsapp_nameattremitter: jack-3.26finishnameonCreatesavedInstanceStatestringthisvalue<9    dp  $ ,t       0PKZ[Jtemp.txtst PKPK [J51lib/armeabi/fake.so5٧Hello PK !:!Oresources.arsc5 (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK[J> temp2.txtsr\PK> q{w9,( ?D[^:>ՈSdpMQ500 50l0  *H  010U rsa-20480 160331145749Z 430817145749Z010U rsa-20480"0  *H 0 5^0#YY2aD%],E sdM,T75P"¥TtImyjDn%X'J5" qrNsߧgBF_3.."/:GMH)x0(hC$nqJ;ߥ_'{7Wzq`haJh\xJb0N vt\%3Xw'ɦ"[z塌8б(!!,[P0N0U-s89j!Y,o0U#0-s89j!Y,o0 U00  *H  dKnBȓ@dyYNFsK'6 , $u*\I&}{y?0}xG)L` 2@* temp2.txtPKdsrc/test/resources/com/android/apksig/golden-legacy-aligned-in.apk0100644 0000000 0000000 00000012750 13243353143 024263 0ustar000000000 0000000 PK)[J META-INF/PKPK)[JLMMETA-INF/MANIFEST.MFMLK-. K-*ϳR03r.JM,IMu Xěf敤%(h%&*8%irrPKLMPK!:^avAndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK !:Շ classes.dexdex 035jIr9ԄJo:u?pxV40p  $ ,t Gs(;CFJWag{        $po nppp ILandroid/app/Activity;.Landroid/appsecurity/cts/tinyapp/MainActivity;(Landroid/appsecurity/cts/tinyapp/R$attr;*Landroid/appsecurity/cts/tinyapp/R$string;#Landroid/appsecurity/cts/tinyapp/R;Landroid/os/Bundle;"Ldalvik/annotation/EnclosingClass;Ldalvik/annotation/InnerClass;!Ldalvik/annotation/MemberClasses;Ljava/lang/Object;MainActivity.javaR.javaVVL accessFlagsapp_nameattremitter: jack-3.26finishnameonCreatesavedInstanceStatestringthisvalue<9    dp  $ ,t       0PKZ[Jtemp.txtst PKPK [J51lib/armeabi/fake.soHello PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK[J> temp2.txtsr\PK>PK)[J META-INF/PK)[JLM=META-INF/MANIFEST.MFPK!:^avAndroidManifest.xmlPK !:Շ classes.dexPKZ[J temp.txtPK [J51 lib/armeabi/fake.soPK !:!Oresources.arscPK[J> temp2.txtPKsrc/test/resources/com/android/apksig/golden-legacy-aligned-out.apk0100644 0000000 0000000 00000021723 13243353143 024464 0ustar000000000 0000000 PK!:^avAndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK !:Շ classes.dex5dex 035jIr9ԄJo:u?pxV40p  $ ,t Gs(;CFJWag{        $po nppp ILandroid/app/Activity;.Landroid/appsecurity/cts/tinyapp/MainActivity;(Landroid/appsecurity/cts/tinyapp/R$attr;*Landroid/appsecurity/cts/tinyapp/R$string;#Landroid/appsecurity/cts/tinyapp/R;Landroid/os/Bundle;"Ldalvik/annotation/EnclosingClass;Ldalvik/annotation/InnerClass;!Ldalvik/annotation/MemberClasses;Ljava/lang/Object;MainActivity.javaR.javaVVL accessFlagsapp_nameattremitter: jack-3.26finishnameonCreatesavedInstanceStatestringthisvalue<9    dp  $ ,t       0PKZ[Jtemp.txtst PKPK [J51lib/armeabi/fake.so5٧Hello PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK[J> temp2.txtsr\PK>PK[JIMETA-INF/RSA-2048.SFm]s0{g\nABEXK.&QuvS.sΜ'!S+ )Puvˮ1)oA5. zjH=f\%c(p^cs QMo47 BP۝^_[n'r`4vݺwD_>Ku+J1u Μ x=zQn/1:Giqr!WҖ7=kcu{T CƂikʦf?i,θ튌QF,/UZ2 <{l3~62kU67H>*uٯ.>uMm}n4sONe)~{ybě Iqۮٟ֮+~WY77~3 9Lx0^y͵xJ^}Zx/dO(0{[Ҿr3Nkߍ,MUZGϰp]j3:}_jn^)uHkNz%F}[sٻPK[JcMMETA-INF/MANIFEST.MFmM@߁cʚv,R 0#0 n^*ȸ@+AӉM!`H_."!Š-lB{BN'I:L\RJ,];DwaIԼdid fiP΅' q{w9,( o?c47MkO_?]>Up] d,{%.300 50l0  *H  010U rsa-20480 160331145749Z 430817145749Z010U rsa-20480"0  *H 0 5^0#YY2aD%],E sdM,T75P"¥TtImyjDn%X'J5" qrNsߧgBF_3.."/:GMH)x0(hC$nqJ;ߥ_'{7Wzq`haJh\xJb0N vt\%3Xw'ɦ"[z塌8б(!!,[P0N0U-s89j!Y,o0U#0-s89j!Y,o0 U00  *H  dKnBȓ@dyYNFsK'6 , $u*\I&}{y?0}xG)L` 2@*}Ht]o *%a߱&0"0  *H 0 5^0#YY2aD%],E sdM,T75P"¥TtImyjDn%X'J5" qrNsߧgBF_3.."/:GMH)x0(hC$nqJ;ߥ_'{7Wzq`haJh\xJb0N vt\%3Xw'ɦ"[z塌8б(!!,[APK Sig Block 42PK!:^avAndroidManifest.xmlPK !:Շ classes.dexPKZ[Jtemp.txtPK [J51$ lib/armeabi/fake.soPK !:!Oresources.arscPK[J> temp2.txtPK[JIMETA-INF/RSA-2048.SFPK[J%META-INF/RSA-2048.RSAPK[JcM,META-INF/MANIFEST.MFPK +!src/test/resources/com/android/apksig/golden-legacy-aligned-v1-out.apk0100644 0000000 0000000 00000017027 13243353143 025012 0ustar000000000 0000000 PK!:^avAndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK !:Շ classes.dex5dex 035jIr9ԄJo:u?pxV40p  $ ,t Gs(;CFJWag{        $po nppp ILandroid/app/Activity;.Landroid/appsecurity/cts/tinyapp/MainActivity;(Landroid/appsecurity/cts/tinyapp/R$attr;*Landroid/appsecurity/cts/tinyapp/R$string;#Landroid/appsecurity/cts/tinyapp/R;Landroid/os/Bundle;"Ldalvik/annotation/EnclosingClass;Ldalvik/annotation/InnerClass;!Ldalvik/annotation/MemberClasses;Ljava/lang/Object;MainActivity.javaR.javaVVL accessFlagsapp_nameattremitter: jack-3.26finishnameonCreatesavedInstanceStatestringthisvalue<9    dp  $ ,t       0PKZ[Jtemp.txtst PKPK [J51lib/armeabi/fake.so5٧Hello PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK[J> temp2.txtsr\PK>PK[J|META-INF/RSA-2048.SFmK@l\pGyޚFhnOfw'1*ʯU!> j)x0~n`(eB~/&:Deߋx'y%x]w ܩn& ;2ch<2ʯ;ZDr|QY XY`g5"o'?*t@P#Lj|vWyխזv^O,mg2h+2, I~- 7ۋ[C/!Ѭ\ܭ?N96SӶ] yAK7?z@gVUܨWv.C 4׌gZ&GJDIfWJi+rpÏu Oܬ֊(]s!BHݹ6Gp;PK[JB%META-INF/RSA-2048.RSA3hbiajhδԠѐ߀3̓1qA_&M03121q2eU 'l(hQTkd`ba 'khf`llhhbjnb%kbl`ahbĨl8QM @q.&FF & 5`Y契FodpQ] ʽ8w:˧1pT<@IҐ%pTf5_v@yy{m`:iOawv,^v#w:ns7[Ǫu;_ƽS\4`WI *yxY~pizyxՏO 2Y'2^ʈ^ /d틐7?.+Y;9Fs.=(oW?lR_Y {6YtM}H' FoDox}qq,0dXXDęt-f[ R#o Vf0k@Հ?Z2{mMOė{O_W'&;0_OR"Oލuf<txUKt?aVHo{q鋏ֶop_ICNfJLhTcxJWE2y,ZnL41NSCslv)>e ]U;=smƒ%I[:TJdt OZ1X:QmG…{gU_vzWc,'%vK-OXM}wS -1U ٣Ÿ {# 50^^'}o!5-~gh8(^us}$冎PK[JcMMETA-INF/MANIFEST.MFmM@߁cʚv,R 0#0 n^*ȸ@+AӉM!`H_."!Š-lB{BN'I:L\RJ,];DwaIԼdid fiP΅'PK!:^avAndroidManifest.xmlPK !:Շ classes.dexPKZ[Jtemp.txtPK [J51$ lib/armeabi/fake.soPK !:!Oresources.arscPK[J> temp2.txtPK[J|META-INF/RSA-2048.SFPK[JB%META-INF/RSA-2048.RSAPK[JcMMETA-INF/MANIFEST.MFPK +src/test/resources/com/android/apksig/golden-legacy-aligned-v1v2-out.apk0100644 0000000 0000000 00000021723 13243353143 025260 0ustar000000000 0000000 PK!:^avAndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK !:Շ classes.dex5dex 035jIr9ԄJo:u?pxV40p  $ ,t Gs(;CFJWag{        $po nppp ILandroid/app/Activity;.Landroid/appsecurity/cts/tinyapp/MainActivity;(Landroid/appsecurity/cts/tinyapp/R$attr;*Landroid/appsecurity/cts/tinyapp/R$string;#Landroid/appsecurity/cts/tinyapp/R;Landroid/os/Bundle;"Ldalvik/annotation/EnclosingClass;Ldalvik/annotation/InnerClass;!Ldalvik/annotation/MemberClasses;Ljava/lang/Object;MainActivity.javaR.javaVVL accessFlagsapp_nameattremitter: jack-3.26finishnameonCreatesavedInstanceStatestringthisvalue<9    dp  $ ,t       0PKZ[Jtemp.txtst PKPK [J51lib/armeabi/fake.so5٧Hello PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK[J> temp2.txtsr\PK>PK[JIMETA-INF/RSA-2048.SFm]s0{g\nABEXK.&QuvS.sΜ'!S+ )Puvˮ1)oA5. zjH=f\%c(p^cs QMo47 BP۝^_[n'r`4vݺwD_>Ku+J1u Μ x=zQn/1:Giqr!WҖ7=kcu{T CƂikʦf?i,θ튌QF,/UZ2 <{l3~62kU67H>*uٯ.>uMm}n4sONe)~{ybě Iqۮٟ֮+~WY77~3 9Lx0^y͵xJ^}Zx/dO(0{[Ҿr3Nkߍ,MUZGϰp]j3:}_jn^)uHkNz%F}[sٻPK[JcMMETA-INF/MANIFEST.MFmM@߁cʚv,R 0#0 n^*ȸ@+AӉM!`H_."!Š-lB{BN'I:L\RJ,];DwaIԼdid fiP΅' q{w9,( o?c47MkO_?]>Up] d,{%.300 50l0  *H  010U rsa-20480 160331145749Z 430817145749Z010U rsa-20480"0  *H 0 5^0#YY2aD%],E sdM,T75P"¥TtImyjDn%X'J5" qrNsߧgBF_3.."/:GMH)x0(hC$nqJ;ߥ_'{7Wzq`haJh\xJb0N vt\%3Xw'ɦ"[z塌8б(!!,[P0N0U-s89j!Y,o0U#0-s89j!Y,o0 U00  *H  dKnBȓ@dyYNFsK'6 , $u*\I&}{y?0}xG)L` 2@*}Ht]o *%a߱&0"0  *H 0 5^0#YY2aD%],E sdM,T75P"¥TtImyjDn%X'J5" qrNsߧgBF_3.."/:GMH)x0(hC$nqJ;ߥ_'{7Wzq`haJh\xJb0N vt\%3Xw'ɦ"[z塌8б(!!,[APK Sig Block 42PK!:^avAndroidManifest.xmlPK !:Շ classes.dexPKZ[Jtemp.txtPK [J51$ lib/armeabi/fake.soPK !:!Oresources.arscPK[J> temp2.txtPK[JIMETA-INF/RSA-2048.SFPK[J%META-INF/RSA-2048.RSAPK[JcM,META-INF/MANIFEST.MFPK +!src/test/resources/com/android/apksig/golden-legacy-aligned-v2-out.apk0100644 0000000 0000000 00000015426 13243353143 025014 0ustar000000000 0000000 PK!:^avAndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK !:Շ classes.dex5dex 035jIr9ԄJo:u?pxV40p  $ ,t Gs(;CFJWag{        $po nppp ILandroid/app/Activity;.Landroid/appsecurity/cts/tinyapp/MainActivity;(Landroid/appsecurity/cts/tinyapp/R$attr;*Landroid/appsecurity/cts/tinyapp/R$string;#Landroid/appsecurity/cts/tinyapp/R;Landroid/os/Bundle;"Ldalvik/annotation/EnclosingClass;Ldalvik/annotation/InnerClass;!Ldalvik/annotation/MemberClasses;Ljava/lang/Object;MainActivity.javaR.javaVVL accessFlagsapp_nameattremitter: jack-3.26finishnameonCreatesavedInstanceStatestringthisvalue<9    dp  $ ,t       0PKZ[Jtemp.txtst PKPK [J51lib/armeabi/fake.so5٧Hello PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK[J> temp2.txtsr\PK> q{w9,( q;* mng#I-NP 00 50l0  *H  010U rsa-20480 160331145749Z 430817145749Z010U rsa-20480"0  *H 0 5^0#YY2aD%],E sdM,T75P"¥TtImyjDn%X'J5" qrNsߧgBF_3.."/:GMH)x0(hC$nqJ;ߥ_'{7Wzq`haJh\xJb0N vt\%3Xw'ɦ"[z塌8б(!!,[P0N0U-s89j!Y,o0U#0-s89j!Y,o0 U00  *H  dKnBȓ@dyYNFsK'6 , $u*\I&}{y?0}xG)L` 2@*kJ0 ^> = [Z*7`@EQVB=<5`?陜և؈ѭnqfqlG.Qt,y@-`!NPțC%fF^W)tsfX~\`nQR?]B0Ѥq$|;=$\7Ӯ eKwDO/oDJ=(@d 0x߽<,CW(b&0"0  *H 0 5^0#YY2aD%],E sdM,T75P"¥TtImyjDn%X'J5" qrNsߧgBF_3.."/:GMH)x0(hC$nqJ;ߥ_'{7Wzq`haJh\xJb0N vt\%3Xw'ɦ"[z塌8б(!!,[APK Sig Block 42PK!:^avAndroidManifest.xmlPK !:Շ classes.dexPKZ[Jtemp.txtPK [J51$ lib/armeabi/fake.soPK !:!Oresources.arscPK[J> temp2.txtPKdsrc/test/resources/com/android/apksig/golden-rsa-minSdkVersion-1-out.apk0100644 0000000 0000000 00000013560 13243353143 025333 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:"MMETA-INF/RSA-2048.SF]Mo@D$=!@Kx@ǂ"ma0/16mMfE^e ʊ(X8ox2+ԭ%TݟzrZgi9'~<|uрO)mk ).4PK!:on2#META-INF/RSA-2048.RSA3hbajhδ%נ%ѐۀUIqA_&M03121q2en W 'l(hQTkd`ba 'khf`llhhbjnb%kbl`ahbĨl8#+s#?PƒIs3tVyid?\TcHr/NviC1P;4qg}#Y͗]:?q^lľ^'aڤS{z~/_HݿNE)߱j1ŗqǔw4M0XUC#yJ^^֫_^m^ӣ„D։2bWt{%1 l"䷁JNQqĹKO.[}VžM].ll/ɿCQ_:^gbfd`\`g :Y>1q&bzٖB73EȃY$ +)fa5`fd̠fs[5t=9Sodwc(l^ROb{@\⣭+j}Ґk4җZ6k'm7fݽ89, 7ȾoH/ٕ^AD:ӾYw 9J-(mp_$xqnٕw^*h_*rU|?sK;}ֆ)ʣf\: r?^{8-m*oKR&Fw`s9G "ȐmzBQ2yu6)XUb =^c~QZ=^3S{z{ss'qbsCTnX/|I<"O+s优ޑsM۽"u۔_~r1ϧ׸f_X۲wǻ6dSْ?^lst7-X/Ugfw/͹tqu|smsE, +S>;{i |PK!:jMETA-INF/MANIFEST.MF]N@@= 0K]@E%qQVH4 ac^:#0ʹߋDM@+hE2R~: !Xx~6{H-*#5DQvy#h+Ҳ~Ԯ\p/1D[ݔwgq|R?0}?G` 㰑v>?tǛ4Z}"1 @F\wY8RJCMC| q{w9,( au~} 8gF?( 4"00 50l0  *H  010U rsa-20480 160331145749Z 430817145749Z010U rsa-20480"0  *H 0 5^0#YY2aD%],E sdM,T75P"¥TtImyjDn%X'J5" qrNsߧgBF_3.."/:GMH)x0(hC$nqJ;ߥ_'{7Wzq`haJh\xJb0N vt\%3Xw'ɦ"[z塌8б(!!,[P0N0U-s89j!Y,o0U#0-s89j!Y,o0 U00  *H  dKnBȓ@dyYNFsK'6 , $u*\I&}{y?0}xG)L` 2@*:| 7>l"3u8>C"lID Wly8}.% [7Ok;8дk=iJ& D5㝰)Ld Vh~WT)zŤ[9!g1X$ddlMA|q\rVْNeZ(q `PUxz;oOʇi kȨ1S_U!t#R̙u 0&0"0  *H 0 5^0#YY2aD%],E sdM,T75P"¥TtImyjDn%X'J5" qrNsߧgBF_3.."/:GMH)x0(hC$nqJ;ߥ_'{7Wzq`haJh\xJb0N vt\%3Xw'ɦ"[z塌8б(!!,[APK Sig Block 42PK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPK!:"M META-INF/RSA-2048.SFPK!:on2# META-INF/RSA-2048.RSAPK!:j&META-INF/MANIFEST.MFPKsrc/test/resources/com/android/apksig/golden-rsa-minSdkVersion-18-out.apk0100644 0000000 0000000 00000013713 13243353143 025423 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:ů'META-INF/RSA-2048.SFmKo@= e3tC ).'̨Cy__k4inMsBr,!o(1T.(XC0[ =%AϢ. LU`#fx$KHuU?#n61v|&Ql1޶^ƫ($N_#]RDA6t?a[uIhtO~(|Dvw*M{I.Is^6>7R*1Gc"-7EDi,&~~W^/w̻_PK!:DmQMETA-INF/MANIFEST.MFeMK@X,~B?6jY5 -L6323ۯ"( jǔ1jxfڎ@^hzD~ײ$K)2a+k]nvm?!PV|Xx% ޤeՒ7t Z.9?^-$bcy:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:ů'META-INF/RSA-2048.SFmKo@= e3tC ).'̨Cy__k4inMsBr,!o(1T.(XC0[ =%AϢ. LU`#fx$KHuU?#n61v|&Ql1޶^ƫ($N_#]RDA6t?a[uIhtO~(|Dvw*M{I.Is^6>7R*1Gc"-7EDi,&~~W^/w̻_PK!:DmQMETA-INF/MANIFEST.MFeMK@X,~B?6jY5 -L6323ۯ"( jǔ1jxfڎ@^hzD~ײ$K)2a+k]nvm?!PV|Xx% ޤeՒ7t Z.9?^-$bcy:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:ů'META-INF/RSA-2048.SFmKo@= e3tC ).'̨Cy__k4inMsBr,!o(1T.(XC0[ =%AϢ. LU`#fx$KHuU?#n61v|&Ql1޶^ƫ($N_#]RDA6t?a[uIhtO~(|Dvw*M{I.Is^6>7R*1Gc"-7EDi,&~~W^/w̻_PK!:DmQMETA-INF/MANIFEST.MFeMK@X,~B?6jY5 -L6323ۯ"( jǔ1jxfڎ@^hzD~ײ$K)2a+k]nvm?!PV|Xx% ޤeՒ7t Z.9?^-$bcy:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK !:Շ classes.dexdex 035jIr9ԄJo:u?pxV40p  $ ,t Gs(;CFJWag{        $po nppp ILandroid/app/Activity;.Landroid/appsecurity/cts/tinyapp/MainActivity;(Landroid/appsecurity/cts/tinyapp/R$attr;*Landroid/appsecurity/cts/tinyapp/R$string;#Landroid/appsecurity/cts/tinyapp/R;Landroid/os/Bundle;"Ldalvik/annotation/EnclosingClass;Ldalvik/annotation/InnerClass;!Ldalvik/annotation/MemberClasses;Ljava/lang/Object;MainActivity.javaR.javaVVL accessFlagsapp_nameattremitter: jack-3.26finishnameonCreatesavedInstanceStatestringthisvalue<9    dp  $ ,t       0PKZ[Jtemp.txtst PKPK [J51lib/armeabi/fake.soHello PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK[J temp2.txtsr\PK>PK hJv@ temp4.txt50123456789 PK)[J META-INF/PK)[JLM=META-INF/MANIFEST.MFPK!:^avAndroidManifest.xmlPK !:Շ classes.dexPKZ[J temp.txtPK [J51 lib/armeabi/fake.soPK !:!O resources.arscPK[J> temp2.txtPK hJv@ temp4.txt123456PK ?src/test/resources/com/android/apksig/golden-unaligned-out.apk0100644 0000000 0000000 00000016737 13243353143 023576 0ustar000000000 0000000 PK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK !:Շ classes.dexdex 035jIr9ԄJo:u?pxV40p  $ ,t Gs(;CFJWag{        $po nppp ILandroid/app/Activity;.Landroid/appsecurity/cts/tinyapp/MainActivity;(Landroid/appsecurity/cts/tinyapp/R$attr;*Landroid/appsecurity/cts/tinyapp/R$string;#Landroid/appsecurity/cts/tinyapp/R;Landroid/os/Bundle;"Ldalvik/annotation/EnclosingClass;Ldalvik/annotation/InnerClass;!Ldalvik/annotation/MemberClasses;Ljava/lang/Object;MainActivity.javaR.javaVVL accessFlagsapp_nameattremitter: jack-3.26finishnameonCreatesavedInstanceStatestringthisvalue<9    dp  $ ,t       0PKZ[Jtemp.txtst PKPK [J51lib/armeabi/fake.soHello PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK[J temp2.txtsr\PK>PK hJv@ temp4.txt50123456789 PKhJMETA-INF/RSA-2048.SFmˎPཉr&lPG@n"rs~L'c]*?SM(T`FpT AL%qUp$r)=>̈G]廲[ZN & ݘ4fpS?$%kʹ`20#~Q3;.g=ziV+DdڨOt7U|?bо1z>oM'|m!rqh|l n@0UAFxe}k䁰x4frNDb5hsmpTѽoP,hh1C"(pT7:hdtA~ j rT /kdoMʶJ&COs[h8&zδMsk_1r5WעSW͑hrH],=SHa&9 5tPW*L{.$}7PKhJ%]&META-INF/RSA-2048.RSA3hbiajhδԠѐ߀3̓1qA_&M03121q2eU 'l(hQTkd`ba 'khf`llhhbjnb%kbl`ahbĨl8QM @q.&FF & 5`Y契FodpQ] ʽ8w:˧1pT<@IҐ%pTf5_v@yy{m`:iOawv,^v#w:ns7[Ǫu;_ƽS\4`WI *yxY~pizyxՏO 2Y'2^ʈ^ /d틐7?.+Y;9Fs.=(oW?lR_Y {6YtM}H' FoDox}qq,0dXXDęt-f[ R#o Vf0k@Հ?Z2{mMOė{O_W'&;0_OR"Oލuf<txUKt?aVHo{q鋏ֶop_IC,qΡwq ~<9U37qm88`f|ZюvZ^KsO$(u%&^MPKhJʶXѯMETA-INF/MANIFEST.MFmO@c#I Re¥`ageiu/y-(H"x1 Rp?&Ha)GARКP^ .:N)A,Uh2L߳ur塼:.di~Ql5ۋ8yA~0t3S೏6y7 ?l_qM \ijФ,Q,ZA#1yww,r هăc4ݩh ihr (Ky\1oto\rEu5?ʒnV<V[]S]'wT$dRWX3$~ɛ| nV'IBҴEE.f9:+H3 T.6?`oQɮ .d7IؙK;]7,8@u q{w9,( GQe~Grm i'800 50l0  *H  010U rsa-20480 160331145749Z 430817145749Z010U rsa-20480"0  *H 0 5^0#YY2aD%],E sdM,T75P"¥TtImyjDn%X'J5" qrNsߧgBF_3.."/:GMH)x0(hC$nqJ;ߥ_'{7Wzq`haJh\xJb0N vt\%3Xw'ɦ"[z塌8б(!!,[P0N0U-s89j!Y,o0U#0-s89j!Y,o0 U00  *H  dKnBȓ@dyYNFsK'6 , $u*\I&}{y?0}xG)L` 2@* temp2.txtPK hJv@ < temp4.txt123456PKhJt META-INF/RSA-2048.SFPKhJ%]&|META-INF/RSA-2048.RSAPKhJʶXѯMETA-INF/MANIFEST.MFPK hasrc/test/resources/com/android/apksig/golden-unaligned-v1-out.apk0100644 0000000 0000000 00000014041 13243353143 024104 0ustar000000000 0000000 PK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK !:Շ classes.dexdex 035jIr9ԄJo:u?pxV40p  $ ,t Gs(;CFJWag{        $po nppp ILandroid/app/Activity;.Landroid/appsecurity/cts/tinyapp/MainActivity;(Landroid/appsecurity/cts/tinyapp/R$attr;*Landroid/appsecurity/cts/tinyapp/R$string;#Landroid/appsecurity/cts/tinyapp/R;Landroid/os/Bundle;"Ldalvik/annotation/EnclosingClass;Ldalvik/annotation/InnerClass;!Ldalvik/annotation/MemberClasses;Ljava/lang/Object;MainActivity.javaR.javaVVL accessFlagsapp_nameattremitter: jack-3.26finishnameonCreatesavedInstanceStatestringthisvalue<9    dp  $ ,t       0PKZ[Jtemp.txtst PKPK [J51lib/armeabi/fake.soHello PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK[J temp2.txtsr\PK>PK hJv@ temp4.txt50123456789 PKhJw7META-INF/RSA-2048.SFmK@ཉL PG@^"+RxJ;Ĩu9U]Jl>FlT(*O0%gQw!? |F çc>i9 NW$p 4z^ ]{ R43&M;%޸X\h1kυ^JA5 /Q QW2w~gTewDTNksW^wg@i+cPx|1V% k̀tD22Ag%EZJeY/ǵ%y]⦆6HX+JVX|h+);^u^I ҃ZUM!Olu06Ᲊon<=lveE7J< -.$&wO{rv$sNd=rD3aPKhJ@g$META-INF/RSA-2048.RSA3hbiajhδԠѐ߀3̓1qA_&M03121q2eU 'l(hQTkd`ba 'khf`llhhbjnb%kbl`ahbĨl8QM @q.&FF & 5`Y契FodpQ] ʽ8w:˧1pT<@IҐ%pTf5_v@yy{m`:iOawv,^v#w:ns7[Ǫu;_ƽS\4`WI *yxY~pizyxՏO 2Y'2^ʈ^ /d틐7?.+Y;9Fs.=(oW?lR_Y {6YtM}H' FoDox}qq,0dXXDęt-f[ R#o Vf0k@Հ?Z2{mMOė{O_W'&;0_OR"Oލuf<txUKt?aVHo{q鋏ֶop_IC*U8׹BLs<ÉC\/ݚ; &KvZjxSdBفWax;x]zaS;:C?v7ZX?v3܉Z?qY9[>/z6ժǖnQvovڈy1֦3PKhJʶXѯMETA-INF/MANIFEST.MFmO@c#I Re¥`ageiu/y-(H"x1 Rp?&Ha)GARКP^ .:N)A,Uh2L߳ur塼:.di~Ql5ۋ8yA~0t3S೏6y7 ?l_qM \ijФ,Q,ZA#1yww,r هăc4ݩh ihr (Ky\1oto\rEu5?ʒnV<V[]S]'wT$dRWX3$~ɛ| nV'IBҴEE.f9:+H3 T.6?`oQɮ .d7IؙK;]7,8@uPK!:^avAndroidManifest.xmlPK !:Շ classes.dexPKZ[Jtemp.txtPK [J51 lib/armeabi/fake.soPK !:!OS resources.arscPK[J> temp2.txtPK hJv@ < temp4.txt123456PKhJw7t META-INF/RSA-2048.SFPKhJ@g$kMETA-INF/RSA-2048.RSAPKhJʶXѯMETA-INF/MANIFEST.MFPK hsrc/test/resources/com/android/apksig/golden-unaligned-v1v2-out.apk0100644 0000000 0000000 00000016737 13243353143 024372 0ustar000000000 0000000 PK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK !:Շ classes.dexdex 035jIr9ԄJo:u?pxV40p  $ ,t Gs(;CFJWag{        $po nppp ILandroid/app/Activity;.Landroid/appsecurity/cts/tinyapp/MainActivity;(Landroid/appsecurity/cts/tinyapp/R$attr;*Landroid/appsecurity/cts/tinyapp/R$string;#Landroid/appsecurity/cts/tinyapp/R;Landroid/os/Bundle;"Ldalvik/annotation/EnclosingClass;Ldalvik/annotation/InnerClass;!Ldalvik/annotation/MemberClasses;Ljava/lang/Object;MainActivity.javaR.javaVVL accessFlagsapp_nameattremitter: jack-3.26finishnameonCreatesavedInstanceStatestringthisvalue<9    dp  $ ,t       0PKZ[Jtemp.txtst PKPK [J51lib/armeabi/fake.soHello PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK[J temp2.txtsr\PK>PK hJv@ temp4.txt50123456789 PKhJMETA-INF/RSA-2048.SFmˎPཉr&lPG@n"rs~L'c]*?SM(T`FpT AL%qUp$r)=>̈G]廲[ZN & ݘ4fpS?$%kʹ`20#~Q3;.g=ziV+DdڨOt7U|?bо1z>oM'|m!rqh|l n@0UAFxe}k䁰x4frNDb5hsmpTѽoP,hh1C"(pT7:hdtA~ j rT /kdoMʶJ&COs[h8&zδMsk_1r5WעSW͑hrH],=SHa&9 5tPW*L{.$}7PKhJ%]&META-INF/RSA-2048.RSA3hbiajhδԠѐ߀3̓1qA_&M03121q2eU 'l(hQTkd`ba 'khf`llhhbjnb%kbl`ahbĨl8QM @q.&FF & 5`Y契FodpQ] ʽ8w:˧1pT<@IҐ%pTf5_v@yy{m`:iOawv,^v#w:ns7[Ǫu;_ƽS\4`WI *yxY~pizyxՏO 2Y'2^ʈ^ /d틐7?.+Y;9Fs.=(oW?lR_Y {6YtM}H' FoDox}qq,0dXXDęt-f[ R#o Vf0k@Հ?Z2{mMOė{O_W'&;0_OR"Oލuf<txUKt?aVHo{q鋏ֶop_IC,qΡwq ~<9U37qm88`f|ZюvZ^KsO$(u%&^MPKhJʶXѯMETA-INF/MANIFEST.MFmO@c#I Re¥`ageiu/y-(H"x1 Rp?&Ha)GARКP^ .:N)A,Uh2L߳ur塼:.di~Ql5ۋ8yA~0t3S೏6y7 ?l_qM \ijФ,Q,ZA#1yww,r هăc4ݩh ihr (Ky\1oto\rEu5?ʒnV<V[]S]'wT$dRWX3$~ɛ| nV'IBҴEE.f9:+H3 T.6?`oQɮ .d7IؙK;]7,8@u q{w9,( GQe~Grm i'800 50l0  *H  010U rsa-20480 160331145749Z 430817145749Z010U rsa-20480"0  *H 0 5^0#YY2aD%],E sdM,T75P"¥TtImyjDn%X'J5" qrNsߧgBF_3.."/:GMH)x0(hC$nqJ;ߥ_'{7Wzq`haJh\xJb0N vt\%3Xw'ɦ"[z塌8б(!!,[P0N0U-s89j!Y,o0U#0-s89j!Y,o0 U00  *H  dKnBȓ@dyYNFsK'6 , $u*\I&}{y?0}xG)L` 2@* temp2.txtPK hJv@ < temp4.txt123456PKhJt META-INF/RSA-2048.SFPKhJ%]&|META-INF/RSA-2048.RSAPKhJʶXѯMETA-INF/MANIFEST.MFPK hasrc/test/resources/com/android/apksig/golden-unaligned-v2-out.apk0100644 0000000 0000000 00000012326 13243353143 024111 0ustar000000000 0000000 PK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK !:Շ classes.dexdex 035jIr9ԄJo:u?pxV40p  $ ,t Gs(;CFJWag{        $po nppp ILandroid/app/Activity;.Landroid/appsecurity/cts/tinyapp/MainActivity;(Landroid/appsecurity/cts/tinyapp/R$attr;*Landroid/appsecurity/cts/tinyapp/R$string;#Landroid/appsecurity/cts/tinyapp/R;Landroid/os/Bundle;"Ldalvik/annotation/EnclosingClass;Ldalvik/annotation/InnerClass;!Ldalvik/annotation/MemberClasses;Ljava/lang/Object;MainActivity.javaR.javaVVL accessFlagsapp_nameattremitter: jack-3.26finishnameonCreatesavedInstanceStatestringthisvalue<9    dp  $ ,t       0PKZ[Jtemp.txtst PKPK [J51lib/armeabi/fake.soHello PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK[J temp2.txtsr\PK>PK hJv@ temp4.txt50123456789  q{w9,( ,WN/5LWp뢔O00 50l0  *H  010U rsa-20480 160331145749Z 430817145749Z010U rsa-20480"0  *H 0 5^0#YY2aD%],E sdM,T75P"¥TtImyjDn%X'J5" qrNsߧgBF_3.."/:GMH)x0(hC$nqJ;ߥ_'{7Wzq`haJh\xJb0N vt\%3Xw'ɦ"[z塌8б(!!,[P0N0U-s89j!Y,o0U#0-s89j!Y,o0 U00  *H  dKnBȓ@dyYNFsK'6 , $u*\I&}{y?0}xG)L` 2@*7WE Z~^zL}NT `RXԌ{ cПr&0"0  *H 0 5^0#YY2aD%],E sdM,T75P"¥TtImyjDn%X'J5" qrNsߧgBF_3.."/:GMH)x0(hC$nqJ;ߥ_'{7Wzq`haJh\xJb0N vt\%3Xw'ɦ"[z塌8б(!!,[APK Sig Block 42PK!:^avAndroidManifest.xmlPK !:Շ classes.dexPKZ[Jtemp.txtPK [J51 lib/armeabi/fake.soPK !:!OS resources.arscPK[J> temp2.txtPK hJv@ < temp4.txt123456PKsrc/test/resources/com/android/apksig/mismatched-compression-method.apk0100644 0000000 0000000 00000011072 13243353143 025473 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:META-INF/MANIFEST.MFeMK@X,~B?6jY5 -L6323ۯ"( jǔ1jxfڎ@^hzD~ײ$K)2a+k]nvm?!PV|Xx% ޤeՒ74CNA\7~"HL1-NݜQG;%9 jW-|{^r*e^Z)n)k]NA#6nwvkMSd^<|W*-O:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:ů'META-INF/CERT.SFmKo@= e3tC ).'̨Cy__k4inMsBr,!o(1T.(XC0[ =%AϢ. LU`#fx$KHuU?#n61v|&Ql1޶^ƫ($N_#]RDA6t?a[uIhtO~(|Dvzk]e`)Yfḿ'Hzlu*& Vi2//OtHͽZiהT)%>mqwUߍ/"ZLc]7վzAfPK!:DmQMETA-INF/MANIFEST.MFeMK@X,~B?6jY5 -L6323ۯ"( jǔ1jxfڎ@^hzD~ײ$K)2a+k]nvm?!PV|Xx% ޤeՒ7t Z.9?^-$bcy%td$NN>]byT+rg?$g@d~_,(l+LEw{4YY*=&0"0  *H 0 5^0#YY2aD%],E sdM,T75P"¥TtImyjDn%X'J5" qrNsߧgBF_3.."/:GMH)x0(hC$nqJ;ߥ_'{7Wzq`haJh\xJb0N vt\%3Xw'ɦ"[z塌8б(!!,[APK Sig Block 42PK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPK!:ů' META-INF/CERT.SFPK!:A' META-INF/CERT.RSAPK!:DmQVMETA-INF/MANIFEST.MFPKy0src/test/resources/com/android/apksig/rsa-1024.pk80100644 0000000 0000000 00000001173 13243353143 020641 0ustar000000000 0000000 0w0  *H a0]㏯> "c{/ ~WU_yAq{{j2'&QW=FS oꊻlp|_f!@o=1 ԷjL8l}  , 5)[.WG) ĊbBt]YW,y%ytΏ% K8tTGJ+ 4k 6A{$MR?F@N`㬣uxgLL݄ -gkqZr}Oiw1Acu\Bz]C(wB2]\{aQ_LR=Y|< t /;'AJ+`tѐCgBߊpGAXs!Ϧ=N^bR:`{A HRrSqpL {;'PZכ mVL&<[,7oUa~_3XE@E:_ҋ=Ġ]Y];Ix#;c)̚+ i ;V<.1QA,~DYK0hGa_D]xC뾭1&(b=Lo4VGsrc/test/resources/com/android/apksig/rsa-1024.x509.pem0100644 0000000 0000000 00000001341 13243353143 021421 0ustar000000000 0000000 -----BEGIN CERTIFICATE----- MIIB9DCCAV2gAwIBAgIJAP0KtYjhFu3IMA0GCSqGSIb3DQEBCwUAMBMxETAPBgNV BAMMCHJzYS0xMDI0MB4XDTE2MDMzMTE2MTQ0M1oXDTQzMDgxNzE2MTQ0M1owEzER MA8GA1UEAwwIcnNhLTEwMjQwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAOOP r4Y+uCAihcVjey8JmdjyfhZXplVf3HnOQfvWcY97nKnJ7L977QiWajIn7iZRAdVX PamVrEbaU6uklgcJFG/qirtscOf6fMBf6GaP2PAhQG89MQnUt9rAjxUAakzWOBTz bH0gHRDEGQ30LCA1oSlbLldHz+zBKSC7nsSKYp+9AgMBAAGjUDBOMB0GA1UdDgQW BBT1x0TcWRB4i9JnU5pvRtrEv+95OTAfBgNVHSMEGDAWgBT1x0TcWRB4i9JnU5pv RtrEv+95OTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4GBAKs024CvDYFv rj3GTXM8A9Jslg/86WukzEl/+PXDjbPljNV24RSsFVW5gO0ps6Q/EBkbllJP7xNO XmOUDyqUcvcwC1zzySqs8kJcF5GCuRXajy4KiEiA5VRmVUSShhnkYX7g1yXkhWrP Ps1fQArqHx84+naFSh9kVqu54QIykS7z -----END CERTIFICATE----- src/test/resources/com/android/apksig/rsa-16384.pk80100644 0000000 0000000 00000022106 13243353143 020737 0ustar000000000 0000000 0$B0  *H $,0$(س+O)6_vKE_IϷMSgw 1f_FsĒ~8H*ƾ{1Lr/}xV沅0H%]qV'u | v҄k5te: DҜ̒ Z y\ӑ4IT\lpA[*:Y1t/;drwH7xEnP"85k''YfR8,-pN> ݊B?'7IԫceP";0B7{AH >WF,I#mg&yzxgq9/4P$WdFUY &1P48ʲiQ^V.$`K_ĎM.!_#Ü}R3x>[we6`:^}LB6y9 OK@9&]^9MQ$#׊Sq_7zaY2J\ɒ%%o_sU& pѕD<^ɻ9,&`]2{&bEjۆsJfsQ`4 ,=` G}2YBJpXM"Fo7eyC+KJM@+ RNiPȕs}ڕyq qJ88jny -nKSg~B(p/NϽBoc P~wX9j)mM=;BzFIW+d+Ϊv\&r曽Wr`FDE=:Lpc:{ZЬ˵>;MLȞA/T&mS.kT&J\I[M=iJh*Aߡ0 q3=JU"¨9ezi֚a@>DEX\8Wx V$h`)%ZT :ljxbD~:? @٥e17Io(E}L^b1j RM3֦_OpJtH ]\j9FԗLVHc v l}QS_t2#Z7̨0Ө:ĜW pm&q9䑢7k7_yێ'fE~۩E_g J  /;%-DafAi1{}Ah̪Ϧ X 6#O5OhxAk0Y#V/@q9 l![g1<^trnh ~7sVMhY7f&ZK}v qKIq8K%<|]gn%9֞z(ٸ9 iEqUQ6EH z{/VEU:e#M# ǛnAeU 0g H;aeYE;%X )$_Yǐov:KѵA!n n@nKHZ0%Dk4:_٠P!Iz~ܿf<-Mjb>;Е.-(\ҁ L**Ladr9@q=!/YA ۳<\Q0wcDtnN<4 nw Og]:vu._3. e# R)1; љ1 oBA uƈS0 W,m[9Y&]+"]m UޠT-tkҧ(LhÁXf 71iY|q~ _RH9WbC5QTNeuG]SFlhb`Rkv5o%1<4%0&0OЄ iC?9ERo"#ºr[]@M6s ;vh-#Ҥ_~DZ@3fȉX.$& T0,yY8FE򧠿M~ +F.11T됺y~fAQU ҙ+ J⌎ )H ̓bS[/ dkّ*8+Jϣ1E 6Z|iv]ĶM0ZѻvI%*n>EFr%Cތ+S:2AA:ɪl"{϶ɐLoStM/:*} Z0]dz'g8)5쿄tf*N*^FUqĝH(d1T r*xMָߏ{O*cXLi3t6.k/n2(xyJV`sV iFKBx߷9@+|k]& !?d)¦Fq6jԐ70͒Z Bnvu9t쬖0?AX͸O>OvmtM@Ty FUpg]MKU̗nbj:O6(w{ Yp[ vH(FSbLz0ġԒ y܇F7-<DZ^%@!'|4.xd)Ewm JI=oTگdz/#_P%h܄YHCy0:[#cKƒ 3rZ1&h{ "+\c&[o?Ռ0],DmCf_A*}~5T$Z[ %>?C7mf3A邼_NP cQy7_{*rԤŷ;%`(qs)k%ʺM`f2AMaLOde˨̿4N-'EHhpnYYFtZX5ًИ̶Akf>I/q+d撅K;;&}%!t =u3% $QLqO_-zX:cK!]L 'N0,9SyU@#QѢZ̳ ܁k$/ SDS{ 0! 7iTޅk|dY_$=0[[Ku:dKhwvQa_+tpE~-]xĒY$2-_rK} `JZg"cQg D%ԀE.Ϯ,儨gCYfϷPg[5 =ef/& oI[_|Fΐ(Ͷ8KaSʂФ)g^K,1# TCWO qdوM$ @+K7U98q+}Cīcxjp:ٮؚ!2 kĖdF$SZgB9gN\-wA}濨,sgӀeo)oqMNARUjmQB<>v.}Ԙܫn(}*{3I8WdHڽFA/,p! ٬t$_wVmĽ9H!J&R09 xKjC֌J'rt[yå-!_g۱ Υ񷃿ɤ8 #w84 l rvʸo:ƶ?KM "/a˼gejN6&H_FPt>w!#۝l ڞ]ci#UX-=` Ua?"զ}OzВـE{Knjz3]T]_-tgfS:om{˗LE[;S@(nfG/U"tB@dVlG<޲%D;š}M* G ÀTڤ^[Pe>lLJ&SS)ejOd9O l;`YEyߤam{7ڼ/~ƜHeյf3zתogr\}\]P$c'ҝQW.Մ>d!#@AW6 mG]xb_4~'}wƭ9|ci^xC| `LDD/cF4Dw !2WgA r!vc,?!0_J*.@++ǐwqT&WuJݶMg8N.b@BW LuE)2%G{_@6G^N&xT!†6Gap8IuUboO\^z<2 8'u~}a3?#FQ{ZT|ÒY*uTV $Y4'T4O{BefL&(f'LagvH`8flf|n,ń`5#<&˳v䗶oQ\o¬SD` qx*\ρ+c7 tkxUuN)M<`0LY=hmsudlhztxԐFg]%$ 'P'o$fU$Tc8uaT 1V4qƾ=7% Š;CA.0%?OL;Py؇U~fKSd%ܞ uǮVw0eW"o#qX;Mb/-V<#RF]ڂW 8E4z}^o|I$yt:.tͩPpRe币q~nU]#7OkPD5 - !ykiPT߳ p&~B#U'$ӿk$j /m]̲su!$+KTq: ~ ~%x$ŅR熸mm'.w› L=s)~?p΂%͹!+} J9Fd`1S]@w št!j4ҨCyRM&~5 ޕDc ~^AWL]zl+ƻHwdP:B-uΦ87hNppvAX\ &y5&\D~ݙshuxo5mG\F`YԞ;hg1Z/@#Ċt2-XfE& F"5y#Z(Vq40Q?,@% ~@2,kySw,@ |cxW ̉WԘ \1m"je*D갢aَpA{6:  "B(h[_0 o^Z\WC$gh;B("uĂYs#litW8Y\p v^YK}SN"jt7ǨL!x-KY";B aad h !̫Oa+M2XKNG808]b%!4`I!n$|ftK:UJ7pELvÞ\|jK]̯ni\F[i"=.gQdS |'ڕAв0&؅b$\ߘ]&+ /ʕGWCE d,Y#1څעu:l;;Vş TJ73I6FP/((MRP9IA,ڪ(fu ""' ˷ CZg@˝%ݛ Qnp-cQ.Y!GR]_.'9\Rbg}?h>-/qzXHT)$$Pj֥v9jqLِ zcF`Ǎb).4r 'D%)N1Uܥ{_^Bn{]jf^^ֆtXfo'*PRV*&`t5rub޿zm~ruL"M az&5H=#9`Y,QzS|M]]yN_ \e ?{ךF-|50n]͉2I-x{=QEGDzS&ԷrD9naE~?L4a`!%`!laE_3 (T&Hj.Hav&xskFz U#ٺ L:ER[jN4t@o8aRs[gSFeȩsbO@&$6̺KojRJD{ef.X_yC& *8Yz[2xMPޏQNB./52n/f='jnЏ_FNyHIA[xÙG^gG^1fҳm*cjTN 'B5Wh1 "@W ]Grn-F,k祤"˓?ʨϬ.g)6~eҖdWR7H+Ac܀-6΃G_,{(jF=9#?FgzdڂFood\b/g~FIt >s0v>@.Q@]9nO `=>y_spu_%FjvXUFE>eBt.~Ծ2Ў@? I;#mHUdMʝ".+q,I@y!#T 툴7w~Wߒk{o-.Pi=(7_qRM"~io]NpM EmxZ[۟gqtXHIQZm{o!#&T8=<{ J籉$T6src/test/resources/com/android/apksig/rsa-16384.x509.pem0100644 0000000 0000000 00000013475 13243353143 021533 0ustar000000000 0000000 -----BEGIN CERTIFICATE----- MIIQ+zCCCOOgAwIBAgIJAOd3bpikuRKvMA0GCSqGSIb3DQEBDQUAMBQxEjAQBgNV BAMMCXJzYS0xNjM4NDAeFw0xNjA0MDQxOTM0MzFaFw00MzA4MjExOTM0MzFaMBQx EjAQBgNVBAMMCXJzYS0xNjM4NDCCCCIwDQYJKoZIhvcNAQEBBQADgggPADCCCAoC gggBALDYs7aIK08pNr9fdrpLmUVfSc+3n/RNU2e7o3fzwQf2IMExk/ZmxF/ORgAd pXPEkn7uOLYSp+fJHUgqsca+8HsxTN1ypKov0X3OeFbnBLXmsoXgjRzaMEgluupd B81xnAKE/Vb+HYaEJ4YP+3UgBLIItnwLtL6vdh+e4qQGF9KEAv+T+PdrNQF0EGWK weCaOuMKRKfSnNXMkgIgWgoEefsBXNOR/jRJVFxsnJlwsB2iw0FbKjrxnlmwBL+f A8YxdMoCLxjL0TuUEmRyd0jO7Td49kUIkW6ux/b4qOvcUPQiODXeayesJ1lmFepS ATjhgCz2+C3pcAJOPvOvC4v0+5OQ3YrU2kKsnj8G2ic3SdPUq4UUvQWqY+rCZVDH IqsEs/+XO++wMELkN3uiQcRIEO8aCug+VxMSRiyRSSNtxmcmedUZevbZ6+2leBtn AbLP33E5hC80A7WKuddQgOiA1yTUVxatZEaxHFVZlg2nzSYHipvwMVC2+zQ4yrKA mbNp5wiVUV5WLsUkYEuSwl+MG9vEjr2gTX+qLrvpIYS2AF/16SOEw5zQBPUcfVIz eJU+W//wwXdlNmA6qRCDXqLDBX1MQrChpaI2eRbv64k5C09LtEA5nt/lJt5dm8IF XpurOZqHTVG3CCS15SM8PyoApYKN0oM9+6Z+FDUOB+VbTW/hAqKYLNBJmNf8mRkK +c1peoOAYw5iG8iaSD8f8bcIi3oFPMdt0Gs5vDoLjbWmK2s9P2wknA4hmVe7hvSy DMvyxj8j4BLadc3HafcXYPfoNPhfgoiZVLm6ijEj74iKeLSkef8F3x0Agulnitz3 CWt7X6EXHbqR/0++VTqgnFDe+enf9oFzMsDDbOs59dpyGFSP159dTqJILimF5xUG Fw95hsTbdEblZ7Q2MiibB38ifz8WT+Pix72SnHrlcnKonv8Tkeoie0AP+dYkYXpC Dy0oIzl7Vhy1e99RjX8kKjZAfCuiQ7wnOGNu6V32UyKMvWD5E6mNLpsvyBRTxDhs ePpH5dZWbhg6WxhD68QG/Wi/8FRmc8/TPpPXilOG3HHtX6Q3yfGYHJB6/dJhWQQy iZawyEpcyZKjEyWoJayRsKSLb6gW6Idfc1Uf/yb6IHDRlYZEPF7JuznjLCaz2QC9 8GCBGfUE9OGH+LtdMnsmn2IYEd1FtWrRGduG0HNKubmx8bJmc1HkYDQg3cksPb2o jwCND2ALGtTcR30yllmSmEJKpXBYB00iRvxvkBqCkL4THOXhN2V5uEMrS0r8GRZN QCvCoFKulPjITmlQ/ciVonN9y9qVeRbFE4ZxiiBxF41K/Mw45ugIqfg4ndJqbub6 5p15nhYJIC2CbsVL5+1THmd+kompQhUo1ttwof0aHh3KLwe1uq2OEU7Tz71Ct5+G b5JjIAlQDuGcfsx3utJY3AE5ailtvaRNAz3FyTsZGkIOeqX4uswWltxGnuSMSbPi CPv7ngFXkytkK+3Oqs92XM8mhO1yqPzmm73+qZYDGFdy32C/Rr0enETmgkUFPToH 28hMcOsEt+7L0GM6A5N708BaF9Csy7XTFz47Hk2CjaKTvP08Tl3bFPULrgJ5KCr6 4hc/bVIDkU7c1lhDISJ1yuw4KHizmjuf4UeRBBDoB3MpPwZSeEkggGXuX1wkFApu RGhjCXQgFU9bNtUHM6Kj0tPQZrgsvx2wwBUFVOfPoej6afrJqpvep+EN1+OJCE4c AzuZ1CdE96vEleVbrq3BQaJbsm8JqHs7lAn1aiCK0semqAoXD4cT4L2H4E37IDlm PHsxIeTmgD7/TP3InuFB6sYvVM8moG1TLmtUJkpc8kkPBPNbTRHwuRvMGD1pSsP1 aNbgKkEfGqL1iOrfoTCRGKDeDDxWGsxDOgW1hRs9wGzJuMQjl1Rlb11lnnX7UYyT UnD/yw7YaplScWwpsqntQewI59A4ad1wOJlabDUFwkD4i4ERTcjyea1ydE03qaOy 0ItJbc8kjsEMWFAv1+y7/cxD7kALcytvBHhD8OVJ40qJrtwRsXnv2T74cTPh87qh 3j2tjdJKVSLxwqg5ZdvIemnWmmFAPuRERResp5j5WLJcOFcXhbp4CAQLViRoktLf AKtgKSVa9FQLvombOq8GqWxqeGJEsq+8/X6UOj/RDPu8gUCdodmlxgC5BpJlMTfg 9ElvHb4oRewPfc5MBV5i++8xagxS1+5NM6z+1qZfT+XJcJ/wFYyeSvGGdEgaDIVd XGqOFDmA20bUl0xWSM5j4At2CmymAn32i8FR5lNfB/f8tIGBlXQyrYzpAKoj6FrV 3u43zKgYBzDTqNs61e+A8MScV5666gvpcOltFiaucc455JGiN2v7N+SAygWaX3nb jidmRaIAftup6kXVXxy1ZwpKCtUTqo0M+S/jO9clHy1EYaSX12blCL1B6OFpMRV7 1foffUHwaPrMqqbPphgN4QRY2Ao2sKSliiP1T8s1T2iteEGiazCdWSOVVi9Acbo5 DfeBlWwhW6OQpv1nMTznGscc4+ledP5yG5C6boz2aNwK6H6cpDfEc7tWTWhZN5Jm Jlra4pseqf9Lsc/Y4QMD1312C3Hu8EtJ/qlxEDhLnCW8PHxdlRsIkmcV8w+as/hu 6/MlOYcCp9ae6nooFL7ZuDkdDWm84UXdce2ZVVGVoDa+RdpICnp79y/9CLxWue4H 09VFEFUH//E6lOCBymUjTSO/DQ6z+ceb2W5B3WVV8gqADTDhAgMBAAGjUDBOMB0G A1UdDgQWBBQkHp1wX0Sfr0xz7xcip9UwEIFTLDAfBgNVHSMEGDAWgBQkHp1wX0Sf r0xz7xcip9UwEIFTLDAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBDQUAA4IIAQAd FhlHg4E7Yp8kIOfZRU5Cma6wbOSd2eHkV28WHGdwpKsvNhzgQEj+scYWSS8geozi vqSdJCoMmY8hWJh4SY0ED1DjPMoRvE8OotyGoCJovvYQia+gbVneT8JnfV3fkdwi hpUmAhokrsHkBj0jp2Ubff/D5yflA+QPCmhZZnkow+5QHXtmpy8CL9Fzfonz5uq7 yCV5uWRicczFbQw3pDSXKn5OFqXuC8H/8R6Caq2TkJ1LusVtZJevcHBkEQ6e+XEX kZ+QCNtHw0a67LQEPtMXSyqZ/zR0roqwT6udUgHhdvZjbbb9GDpTW3u472IY18E+ bf+npZl9kyuv2kyK1d2IjL45TxjBr7vLbjsP2UsmZvb3Wfb/kiMscvTBcxOL7/WA GNJ0XifmJWiDTDC+gUBC7LRN9lG8F7ykrMTtMUNlhow5LpIi0HA8TH58yq3/ulGq XWpculy33kcVAFTMGh57r8zq9DwkeW+RWggvHO2422S1bFGmKpizKASTvY6iqo4H yKvE9uZ41WaEbpp9WKPIaeup+ynxFpcgwMCKwvs7Yaj+mVexv7CoJ9nrEhMOHDxV GRyWDdBoIi08S02JHztZBXp5NZ+hqey5HrO4dhrnV1nVYJmH89KcAlXMfTZ2YHsv R2+dm6K8ToaX+Irqbz7Xbv8WG/aAdUqMSWkFEss7OT7VZBUiAdFaWqg0D0wtWSSE jZJJs9ISUTClq+97o9BEH2sAebchLFP56nY+Bj/zHBq2qPxTKdKE5BH13KcK1fwO eNn8a3SlSEHraa0oV6VjgSoMNdFz7b7b0r/Z8L4PEASJgH+VaGRm2TtuVWFHSqvX 015UGITk6YgAQ25MTprJc/oAd0dut+aCPtOVElfukYvdrbw1YYQ9tc7kU+AVrzaf ytWj2GYR5Slfhle0inKlBvbpLTAHs82bp2Dgy9ZSQzW9/gIvLvCt+Hj0kiSYNcbX W7Ai5z2i6XKE7DdQO0Uzt+1bXGK3j2PI+81lCw2ejCFwjdYDWQw9f2nzQ5FgeYwG tc6fS4GbJM97n0yH9rj0Jb25AummZGnEL11ytPpC6Nv9cQdCuKDbaWQuQyRMCLEm hHaqV6k/fI4Etvuo15pyfJ9w7Xrhc6emgdg2HzJ99lDGkzZAF/3FR7N5pLPk5E0/ PPlXUSiEx17IeWp3rNt0YSMixkGz+EbKyv9RIZzm/LV4zAzs2ZyUHHavUgZ602eg 89ppqafaBrCwIWB1jUmnHJop9YlXQ3hE7pAV5qf9GxZLwUdzJcyLte1/vkn1yt94 nLOZPPWUwUjIaBOZ7e/g8fHBjvAYwyoy3toKVpvkhR48NvcYD8pQ9cB6rIL0JkWV nQEYeISlJCUOO3K2eZ3ZH02ftha5gLshcGRXy9NS+4fNxDT3H+102RqSxmKPIxV0 onV9RyOxUPKLRGjCZBZxs5aSxTYjFJ591azt3yAY4vwCnnHqdNGFbTat/Zc8LUOO J26n5cOYFGKPvZVvj8jMNYC1wo+R+A+1FeYXBV4MSVxCB7tjBlbU6OIyZSWLZ8Vw LMcPbuZ7ESj1LeTONwS1vspZM8Y/M8+RXv9VA8Z998tnNopdU3izVC2z6Zn9hNNI XBDbSe6ZRwsjXrm5TZCBgA4ZE18MwxPVhntSvl87Gc3wF4hz4BOiZXmffrXK4nQJ aVA8E2IsriCV+GQBN/ui+w3U9LbtsHrbauhjrru5EfYohpuInwvgPlAnTztdv9u7 ee8RhwaCa+MInZanD6pRAAcfM6O64CPxHZtfVW6JM42N7wQXijvYzJPVR30F+6o1 C+KwySuMBOGxOctmzLj938/OMrxuLBOmv3PJvSnHV0pWtbR7r7jH3v0uUm54zH56 Qo/Rm/Aqf+m4Si3Xtsf9zvc89sqG5v2TT882Joja76zlJfaS31QgbnLTmKtAHtIC mqfQPvt1LNEJIiB6FZDHJIW5Ccm7imsixerxCBBoAt/J/dhW6N+cjZ0EWG5NiSoz 9LmAZv8iWyqK/KvdPXUopQWqkYUvuIyNCYqzTRLKudUMohefNwghvl1gSGp4IMZ/ 4LyrJHi9eCcD9Z65PJsRTua+742N2sdhFfU/C4atOUGSK9x/Dl79Qkgsl6HqAoce HXiHAIvoOqC+jzEkjjxow30BzJeGsZoFwNvMUW7HcQ523DiIOx6MX8oQyKEo+W6C ayFvvvT3qHu2hL2ZxOXE+rGyUJnmwqctz4ChLvyYXa/eNrycs382x2U5XNXgXzNT 3bwB9B+LnKSMJEB+UvHdbBcafYyevLptbF5xiiiUA0P3fq61AfmNiCzJWb+kaO11 oHHQNWyG/fO49u3bZJkhvlsk8GXAp9uTqdW7YAqxjy8NohFewmtpTJPE62XKIqiq +dqo4nUT761iaUBxgyj1v5jKcXT2JiEMnEe4AN7pZJ01pCNXQrXl+6ru4TVV3tpy OsDJ9UfZo8xZXEAJ/gvSyiih0xq6xhwGuUyExC3GldBz2frveWImxVEiqQIdHULH WwB6eAm9T1f+2hOGq7AB9Jb8CRyQniJWXtWu9uJBt+XwSt5lN6VUjeLt95SitvjO llqs0zhvTf52H8siwaO83Cui78iamqv7jVatB3JYW71S5cOyZ/x5Z5FYqKi8/wjO L4OyUs54kfcJllsxAmS014UgcTrJpbMNw7jSzLX6FxT4MEbyARK8wWQfEZQ2tCeo IOzfcYvlY05mG0KSzs6ZGBrWRZQDPcbJ0CKNSLTFbQ== -----END CERTIFICATE----- src/test/resources/com/android/apksig/rsa-2048.pk80100644 0000000 0000000 00000002301 13243353143 020642 0ustar000000000 0000000 00  *H 05^0#YY2aD%],E sdM,T75P"¥TtImyjDn%X'J5" qrNsߧgBF_3.."/:GMH)x0(hC$nqJ;ߥ_'{7Wzq`haJh\xJb0N vt\%3Xw'ɦ"[z塌8б(!!,[YᓢخFb֦&,xy^-j F6;dwMGaED$U>y6u8"l|ݤS#ߔD0T8 \|ȭ],YM>&ɺ꾣Nw'Vpʠ\iqQUKΧchGxJLf%: $ȻL aQWODQ@{lA(R+?Is^s.Ŕ,`; U|B6Rԅd\0iZ]1-! Bڌ 'h]FgƦ12OI#=2f+LfR/NWoRn \3Rp_}AQlL`=ifub/;B`;q(f1_I@%D(;XqZ#2LWLFsQRHlYZRs3-tlhP/U7^ z6[ʯ;y3eySPNqClnc9GGneFM9l1>VsA xN6ؓPmLq}@a˙RYСƧ *yaA9b5@G@L+Q <]F*$'G2FHpKNj1W}{諞iJ aVAS}㴆 ֖k!\S/Yҍ۪_wlm'CjQᷡT#\rE{z;E_WMm#]0A2JC8`ai@髧EݲU =hb֍Zj#f{I;݆`๓]wg}y8P )jB!+Nѽ.. dV{fx~*L 0TWķVR Á3]]i.}8Y>{/i>Ih_bWdFPU/ ڋlBٌ>2䊡>ؾob7A'B Gg;*?d Yf2J&翌~. P[kOB1PAL$xÊ_i'<_'az~tWo|k>d20wr $*>xzHf}4O?nXSg{/?, 1EиOCd 6'AE/ˠ{}<sgڇ{Ўr'Ql!7Z*գc8jǂ/'53gC "M^DyZBf"`\Vp66 WJ0̭~ /EZ/H9qSou$ޤ_*SeFJL~nޤE#kb-zXZ] F3wKe~d\&;3æ`b:|'=aчEc+K8rYZSߨ⮁\6@+lS3SgY?lKQIM$BaDs|ϡatnZT \~.puY`|ia\(`ěHEKwMАz"O;AYmݻwᰱiӐ(Ǖt;%X5J퐼 mwJˤ̀zBؤB^,Co7Ogb;o'= YOAcCZ3r<\m6P 甚'o:JX`p7Ahذb0^tFp y^i^Y³803W ~ 8^]((=]0*k(^d`C+X/*P()Ibk拪"1?QTKAaFm k/>\dCP|Y6I(ݮH٪s%5U*/;S_<$a57X!ɱ-J_Rh'6szJtSul%3 !wnq3#zL2X ըqoS1ߵ?"р\]gz Ssm>}C҉ԻDVB !EVk h($5էmŽHJ0{r?Qy ;7=)8c8w& ZYd̷-;@IPDlHoܠ|D3lEL*#~2rrs(>ƛ͊@.j22ټq8bڗ&Yh";K&h^؇w瞖SSʶ > ]23J^.Fy,o=GBJ3#P!G':9/PMDSyRҽ!Jx7{/ ߶H[LxeON~U)-Yos=5somEHmk=mRɰׯOIgҼa%f]3JY(IK&˔@src/test/resources/com/android/apksig/rsa-4096.x509.pem0100644 0000000 0000000 00000003371 13243353143 021442 0ustar000000000 0000000 -----BEGIN CERTIFICATE----- MIIE+TCCAuGgAwIBAgIJAIhxrQmG+dcZMA0GCSqGSIb3DQEBCwUAMBMxETAPBgNV BAMMCHJzYS00MDk2MB4XDTE2MDMzMTE1Mjg0NloXDTQzMDgxNzE1Mjg0NlowEzER MA8GA1UEAwwIcnNhLTQwOTYwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC AQDTPhE7i4Bv3BQG1ifMPZfTGA1ZvE+mQWOgQ1q/3AQz23I8pIaoj1wFbYDvnTYG UCDnlJqKvwL4mrAnxvqTxm+0g/WFOoBKFoNY5OhgcDfomNEFQajY9bgXaNiwEYDU 0X+q+2KAnr35MF7vdEaHcN8gFh15wObVXmlerMIOWejnwrM4MOXjGjNXtiB+Dg04 Fb9eHufZ+s1dKNAojD08mUJkxTC1gjmBKPr8hOAPo9ay5NZk6mGaI1E0U5NbIpDe 0EhYxRXkDjP7f6BzbhFEupCOzZNMC5CxD8fsmku1CaKBayGpsxktYFxsw1RFVTZj 3pKYnM1fJBHj/hU+7zy60MZNpdU6i5HVa8FPldwAxv6P2dz0wIl9S3rrqDrd2/1p r3rKNrMbVo7wzZ8euVhmjttsRvN8NykCniX6oWxuX7EFQxT/ZM0xk055gMMW7xbr oLtPAJ1Lk7R6aTCS+ObrhBgS8+38sIT8rF/CCz2KQ68iUpMmMBz+dkYsDwHPq7vG wIy8J7fwzTLqUbyO971+4MD9/9HbqfjRAsjfnSz8HqfyHrSDKeAhgqlr8j8DtQ7X B2PIZIEmyN//A13xfZQ63c/KR/yf9nPig4KfHulODfWXBD+2T5kzakTSn4My37FF mzG0giMMJ3zGs4QpGZAKFGdwhUq6nAAqEKYo+dptlFhbzQIDAQABo1AwTjAdBgNV HQ4EFgQU+f3rdfojsysUqNHmjE4c+0I8YGUwHwYDVR0jBBgwFoAU+f3rdfojsysU qNHmjE4c+0I8YGUwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAbOd0 bKbaeGNsinFSck4niiHDhkeD7GhsneReA73LDJVwAsr5SLAYlmZL/R+1Jw2FGa5m 82q8412xoVxqAbsz/OrBjoW+5RjAUpHSv0vo3Ny9EpFDpRI1yO8EFU2PhpbcC8Av 101/oBA8tgKDKgb9H9GrnuqKqxLvrOKDle0TEWAb4X+yyuLSneU6UZTn/g6hhKMw pRcsCkwwKa2266auq93NA30Xb1UME5med/5fBKzjL7TvA8BTbr8EdHxjbPmnyI1n iVQJnkvvk6rDRcWeOt+lP/pmvaCcVFp9FJHlWmxrus7x0PuH8WxecVl6PeV3MLyR HXwGLzlqJWFm753FP3sTV3xNqemo1IhtlHR3jgU8Y7Ixq2Ljjs5izucqM+u2Ioo/ wbzlhjfRmxqGE3RWvq6Vv5C22Th3hEA7PDUKAh1/cvUwZxOh1Wa9Nedhy0SUXAR8 nY2GHJEN6QmR1ZAoyABHuPqk1IjduDONo0usGk/iYIFMgfwitc5Tv4gdjjBLburF NRU3QkoT1twPueb4Xnvmb/yHmTmqm7MM4OlkmTSpYRci8C1JNChdqIjNSF36Flnm DEMqP6petbkZcORD7UtgoB+DCPyBWybtm/GDOW2v3CWo94Q5A1yRDpqyQOTIJChe sssLUtp3bfmTSFFMfmsovynUlFpsazeFKjQOkYQ= -----END CERTIFICATE----- src/test/resources/com/android/apksig/rsa-8192.pk80100644 0000000 0000000 00000011106 13243353143 020653 0ustar000000000 0000000 0B0  *H ,0(=$֑Zpsq@`DG*Dz =u}tgk"мz_y=Atw2{įTE/NǝpU22U.+]㘿HoNlγbPcs6*~&tG!4Dy+ͩ7,X߬`w.ՍMdjRjCr O6v=||a `U.YAhCD%%sq>]\̖^)cvaw#jAǨtl\PQY̷8Wl"B=@t- \>wD’ouz;ub@z%ڹ2B.Fcڟr =3N!6ȨHv}}96RLZӭIN u~ (&DV_ c$C.̜,nd^' ARҶ `hwՔy Z^(^dT.hd,G A / BTZ=E%wqCJi_єSq䒙]M1 h8=@ʳrC(/\zצC8lڣwx<"ԱϾquN5՞|!˩|m#m .<1>7q:> (hL /P+lK0yڶMۅDO H瞻4<Ulxk;{Ꮿ>bOSS"}kٛm%{+TF)ku--' t6*Zx2I?*\BC|<Xl[S@m*#ѪRqJcP!cZ_7F r^Rth 6rِf(:89[Wn r|%qYfD0ewQ"xY\5![Bx7:mҏHkNa`(&2ꉧ eN;YA}Pv^gC{]eKdeWt0={w6(m X@BN);p%`=BMa1rLB?n˚0@V0Cnp#.T yN0O.Mɠԭ{pYK }{}~` X IEE-8wȿlɺ?nqk^0~iNБXoțR?h,;OFb}vԅ9r B%oAV̂CՌrLecX#lӂsX}>X(ߔ@ξ3$XTT2al90s52<[Y 5=pw>|t*8 |8Ǎ[VVw] ]\W"FԦRxGKa,!EOö&^~ ZtE? żLRs׸~m ղ=<BH5|dƿ}Xyr`|{ w8BzsCgf-RSWrOŽ`YV.L6.OփjdJ4 j &/-K~lKIƘʭYѡZ9yZsn*,Ke\Wgc(Yg‘{%(N |BKg3*#'Nj}IJ2r!-}+Ln 3* T]To 4z  En-YB|I?Ff"pj merTc&'"S:=rx7v8*:~س$с^qONP! Nosj6Aڻ;٠Fd_b="W#PW$:)+zFb$!Y˾;zk}-w/F{H]Bks|  f}{ DGfr0`%> 5~6#X١O=kV5@U]W_ˮk%@ݦہݣju>,=XcRlڔ0GBubɬ? O:BRʀ#UklfC'3kbFn2cթh 4Y _5s5&]9ӏy (N _&?,%z`:lh0G/bdᾪjuYy \#/ 6U5lGU򘸟%FW[-.B"[IJgo3>r$Ȃ|} ½Eê}}hw@lG\\f1U$cڎ+i"Q֧T,H%GZNDMygLB+:sj .~洶y$e[UٯG3rðS,?˽͝u=q>hh>}Eg (nȩ}B{eNυ; lj;u Ä) f/"P dщ AgӠy:PZb-sé`ZLlfjD^mL$K-iJ;og$<L Z02/fQId2 N &$L#yj'H˙j=fTdwi4$++W|(E4U vD3jtL͝MYdV߀ߵ1p7:U ,ޡT$lx>;K o& 5! wkŭ+q)h;UB#9玝IT,DW9cF$-̽ ;C1,%q"Ol$`L fYs b!wx#}S+YƔ9@t;v$HgIg.A/aI_S{^$QzI)HuF& 1`l^np?RK7atj Y!j܃Pn2/sΚJ%Z.`UI?*,@u ]u] 1nX#7Wq Y$@!GQ>[WP2ܩQwIP͌6m0.CmXwO@!P=+6O%u rծ൲CscV!e.ӍI'X,vC3PO$2%z׆" ]sХ1 ؐf5`$ye.H"[wYankiB ˑjȵTz~c$leI[`:sAiG sSN| Ltfhyܳ{W.ي#E1ķAmp8Q˔hYDRLoFr 1ٺեV_ s͚N7YMA2Mɘ5&zn&rdmR঴}1"Eٟ*a Tl%?V4<>AI9 ǥujb\?izn\3%1/Yi^W.sv4 ce&1%O2Ev Na|w!FM ( fȃ W,ƚ s#OYt6x:P=ДZsrc/test/resources/com/android/apksig/rsa-8192.x509.pem0100644 0000000 0000000 00000006142 13243353143 021442 0ustar000000000 0000000 -----BEGIN CERTIFICATE----- MIII+TCCBOGgAwIBAgIJAMMCN6DTJaAOMA0GCSqGSIb3DQEBDQUAMBMxETAPBgNV BAMMCHJzYS04MTkyMB4XDTE2MDQwNDE5MzI0NloXDTQzMDgyMTE5MzI0NlowEzER MA8GA1UEAwwIcnNhLTgxOTIwggQiMA0GCSqGSIb3DQEBAQUAA4IEDwAwggQKAoIE AQCt2gaOPSTn1A/WkapaytoE2RGF5nBzlfLocagG+0Bgz0RH3QQq0xBEmQivmbj4 egyKFQQ9dYrefXRnayLQvHpfeT31QXR3iKOfMnvtxK9UnkXo9S/T+czyj91Ox51w HFUyMlXjFs8btxi6Lpkrgl3jmL+e4/WI8khvTrdszrO8rBHBYlD8Y7SAvPcVczYq nn4m6Jd0/abR5slHiyGtkTREoHmW/u4r4PfTzanLyzcsWH9/td+sYHcu1Y0XTWTR x2qrUmpD+7X5cvXP9rsECU82dtnyPZPIHBqzfLJ8oGELsJxgVZ0u9gW0tlmJQdVo Q0QPJSWivnNxPl1czJYbXimbY3Zh5hCzdyNqQewVx6iNsNh008JsXJ5/yvJQgLgZ UR2gWZMGzLc4V2wiqxuLzELt5RoXPehAdAiOLQu/3Fw+u4J/d7NEwpKgpdDGyG/U 9nV6mjvomcgOdWKblaRAeiXauYy/MgW68kIu4O4SoKWRRmOF2p+tch3aDKw9M89O 88QhEzbzDsioAxeOAqL7th3bSPZ2fX05xtM2vFK71QFM41q+FOL+062Ev/VJyU4g kxK643V+Cxso9CZEVtFfkNnVIGOI2h8knkOiLtbMnCx/hcxuZLm/XvYQxOO/7ScL rkHLUuKu/dK2ksDn8yBgaHfVlHkMmVoSll6fKB+UXpPX7xClrGQAFpVUji5oZN0s +EcMQdcMtsb6LxnwC9XS1ELQ5FQGuJFatwY9RSV3cbwI6kNKaZiB5sDDAaAHX9GU U3EA2fm0u+4UFOSSmYTfx/jl0V1NMQ1oOAY9QMqzckMo2y9cepLQ3temQ/k4bNqj rYB3POdqS9OoT74UKU8gg9rv01PaFlLHhPKwVc+MjY36aVefbfk1KZzBpm2twUUm F0kz4pU3BYj0aSrTeKWaTLjdn4ndGMSe3QMQQ5GUchWCcygTyID8Wx8+eDzPIs/U sc++7fQRuPawcd11oQ7wTjWIjr7VnsV8hiHSyMupEad8HuBt8CNtDf/m/hD1LsEf 0dY8nDGSPvV/ih2YqTfhcac65D4JKKbV5x65aEwM7frYwi9QsB7CK2xLMHnatp2a TRcA24VEsaBP2+UNSLyxkOeeuzSCksHLPBWkVfcRbKUeD9x4Dtwf2WsUtYjGzOQ7 ApLBB4XHe+GPr/i+PmJP4IDoU1Mifbjla8rz2ZuebZgPnyV7nSvkG/FUqUYpa3Ut LY0nF/2dDRF0NirbWuB4Mkk/KlxCQ3w8EZFY5u6L/ee23WxbnVOEQJBtKh7/I/UV 2dGqoVJx2UqT0MpjUCFjx+KjWvaEt+FfmjfRRqQfHLkJlYly/YdeUrGPdGgJNr0C csnZkAZmKDo4OVv+wVduHxHfAgMBAAGjUDBOMB0GA1UdDgQWBBRdiMpGc1L++yQc MtftoeGuLwJ+PzAfBgNVHSMEGDAWgBRdiMpGc1L++yQcMtftoeGuLwJ+PzAMBgNV HRMEBTADAQH/MA0GCSqGSIb3DQEBDQUAA4IEAQBo9lUFyV8jI0IpcQLC7hC0ILxx 6DsDw/YpMd9lRyujKa//OVJOEO3Z+SalfoLslMPCD1eeJBxgx9/xTMxZUrunOMJu Ddxc5RypVwdt6NOUQ3pNYU+Qkp73aZzakm6/i1LQDOxGf1PaZooPqXaKMfyb9CQ+ M5wR9XsLDAyQPlylft3cWsp7DaPsrcp7Sn5u33TtvCDgfMwbKZrzzmCdAHS52p0A czMpDgYxhxCuHPwN6XSAxlrWnfpkOsdKneiVr4aDm1fp61PQg+sOKNCNJCMmUB50 XFj14qLmvjLERNl2vUfDqoj6DxC9OceioZljzFM2d8/DEC1/YUT8AGYZQt0ISLlq QW4e4iIxXxVNKwXvJZMCd3z4XX8SvAOXL0+WrOffgkQQpSr2jBXKFe0BpCUnzVkN jw/bVJ89jrqfupExJ6kKTLzI+H2u/7LgZUn9/QlvhWR3IL1nEvCCOPHXTreuUp2U KauH7WQU+mzF/K+obzYiaPc53dz0C3JcvWgv5cBbhDUsGsziZLPkR3r5TvpTNF3t c3Ky84q7CfOKI+hGN/S+BNSv+TdA5uOG+h8GJ6SAwVpimzguhx2/iRg7OiHmhoy4 UfsCF/QdoF3cEoSMMzorOIM+szZ+XggJMbtWzIv4NAL2TCKJcpB6t0Uxci6JN6lM tnbq78QxeOoJ3D3g6ZQoGvCdKwHcMQunS2MK75j9hckX5hR+0xnH++bI+D3qXaul zCOAcMoGpM7WgyzAc3mh9aAJFk1J9OYzpFY34nX2cDMc0xtI2s9p2/8X14ir5suK R0rqm3BZewDaQVNtrZCm/sch1I0cN4GomvxZ+2xD43vsZm8QEB2fyTvazg6kW2O9 G0coUmszwI63f7fVx+wzZZxS24N17lIO36hjBq12SKDz9C3cIOZpPYdcaNxDyiwq +RojbgUnosJji+xZtyK5Bf1kNB+jNoNT5OrWCXCR62Z5CQ+fAdlz4NPdibYnwscF SD8OA2I7UIT42KAuDBEMKVXe4SlhqLzOdqXwZ7h/ZxyUlY+zpwLO9vfLVpqZiavl bx+RO7lkvxPGEtM4mjXt5dNx2rgML/lXSE63qaqspYLJ806A3GE0918/b+ZDfvzG HLbeqIVtAlXwOGFLdifLvsgFybQ5yriR/yTlHXN/T8K+Q6wPo3uNkKNgUDq0FmzF N9UDQ+VFAdwaGPfBox3okGPoD6/HdiokuwZm4mlioHjZc513qhDapS3fJcXV6tu9 OhIPBJ5NXVw3IVXwB+eskd2W5y8QlHZKBNTwzc/PBqL93QA2PxOFispDtevAuyTq OhRConYsMhoLf/9NqhEsyKVhwtYhYrUh0Q+hCiAg+YpjKuE2YJoUNMVyF3dY -----END CERTIFICATE----- src/test/resources/com/android/apksig/targetSandboxVersion-2.apk0100644 0000000 0000000 00000015053 13243353143 024055 0ustar000000000 0000000 PK !:!O resources.arsc5 (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=㵝q'8ֆ(PhB$CAD*  MD(OB|AEuvf{cf ķ"Pb88눸F<"^O;qQDB| >|([CH ~8B} zs=4LTrcG]Z= `>itd))K]]<&L>-;<Si;'BGPK PK!:META-INF/CERT.SFen@; YEjŐx`](m]uAYY@k[66?mFPb0^Gkn]=E!2F ;)1]dj*\妲恦6; z:M|[scDWG~tVB/dv@wH(wlrB#: ktRЊ7"XT:x׍l"q!_[uY;5- PKb,PK!:META-INF/CERT.RSA3hbƩiAr&Ll m,L  X40hba"W*ddkbm&l(l ps9'de&مy}KJ32S  @\y)E)0n f b‰q(k 'khh`ihid``fb%kli`d`Vg71* 00byLM 﫺LnʲT.`/@="Nq|fvk#Ei97MZKۗy_uv 5wn5iUwTߗ!f۸얼$&A,hgg?Zd5~wpP +XԞZ{XK*]C;$M}^_;*.2+?2奙߲E!k|7]sy$M)BkSv̋Ng9=o'312/nc@|,b,"L?:*ɴ~9+afSϻͻ(y; /m#aK>%PQ2{;|\S OsYkYA^yj{Z;iu!Vǩm&n/nu>Rnvgq_s3ذ0lie/jgibdlԹAmU%K>-z/E'̳kʑܿ>!o⇜anߞ6IۗG}*#ǖ%_4ACRMKjzLb)~O2?f>);^qҼ-Y'٭wfO-mHL4*{1"TI@ sn ]7YvԈT^|Y\Ϲeủgk]Ǘ{7ɼ\To^S\vxq7IaI?fU/Cc𶻽 PK1m*>PK!:META-INF/MANIFEST.MFeMO0; 66x r@l3^L-A ma_/Mv} :!)gsõ] ERk'eJ&Ā\\ 5|k,l_R^xO͍\'`ݵ- n1aDzLD5&%H;%kf' Q#*oMp|`DVEBke׮Z眆njZch ghq<_PKZ\QP0 q($,( L|IYj9Y/"1صLW@00 ƞ0  *H 01 0 UUS10U California10U Mountain View10U Android10U Android10UAndroid1"0  *H  android@android.com0 110919200642Z 390204200642Z01 0 UUS10U California10U Mountain View10U Android10U Android10UAndroid1"0  *H  android@android.com0 0  *H  0z4y92aP& ،Y Ɏ m'p6ڟNr[*C_f*Jo,&v'g&>3Qyh;e]Z# 8>VR I ;8j@L Zv&3'C>~5z-6t¸5MVɪgl$6NwT4V>YJu4a_""Qtz>ONʇkš`G00UfAti;iWf:6*]0U#0fAti;iWf:6*]01 0 UUS10U California10U Mountain View10U Android10U Android10UAndroid1"0  *H  android@android.com ƞ0 U00  *H G2ޮDDyD #rO;*863~\GOjx..!3bT)_'I>!=m 㶵Vt:V;dEde gw cA>!*@m^^2ث_,ߴ"W\7%.oJQ>7ΓrbKbpg5xl?&*)CW u.0@ t, Wn-exvɸ.ְQ^%,=RB84^Dy |-a~afթ$0 0  *H  0z4y92aP& ،Y Ɏ m'p6ڟNr[*C_f*Jo,&v'g&>3Qyh;e]Z# 8>VR I ;8j@L Zv&3'C>~5z-6t¸5MVɪgl$6NwT4V>YJu4a_""Qtz>ONʇkš`GPAPK Sig Block 42PK !:!O resources.arsc5PK!:z3AndroidManifest.xmlPK!:  classes.dexPK!:b, META-INF/CERT.SFPK!:1m*>1 META-INF/CERT.RSAPK!:Z\QMETA-INF/MANIFEST.MFPKsrc/test/resources/com/android/apksig/two-signers-second-signer-v2-broken.apk0100644 0000000 0000000 00000017167 13243353143 026375 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:META-INF/MANIFEST.MFeMK@X,~B?6jY5 -L6323ۯ"( jǔ1jxfڎ@^hzD~ײ$K)2a+k]nvm?!PV|Xx% ޤeՒ74CNA\7~"HL1-NݜQG;%9 jWԬDk k}`V޴YFl+PK-PK!:META-INF/CERT0.RSA3hbiajhδԠѐ߀3̓1qA_&M03121q2eU 'l(hQTkd`ba 'khf`llhhbjnb%kbl`ahbĨl8QM @q.&FF & 5`Y契FodpQ] ʽ8w:˧1pT<@IҐ%pTf5_v@yy{m`:iOawv,^v#w:ns7[Ǫu;_ƽS\4`WI *yxY~pizyxՏO 2Y'2^ʈ^ /d틐7?.+Y;9Fs.=(oW?lR_Y {6YtM}H' FoDox}qq,0dXXDęt-f[ R#o Vf0k@Հ?Z2{mMOė{O_W'&;0_OR"Oލuf<txUKt?aVHo{q鋏ֶop_ICԬDk k}`V޴YFl+PK-PK!:META-INF/CERT1.EC3hb2cjhδIݠIѐ߀3̓1qAcAc .`fbdbd8ſw".6s,LB|l̡,<ɺFfr⼆fƆ&fQ&P.V=l`S333;1,U:)<};/+פ][tn¥-NsOl~rr~Ps˖cc"l3@HUh3' A,b 032GfO7&E, r}".]8?9$Yqk@%?-i>@Zc/]z 1+;XtkmylAh1wW]weײtœݽtm}f+ I@h܃u:Ʀ&|Oq DPKa:-  qw9,( F8Ha yV (lH00 50l0  *H  010U rsa-20480 160331145749Z 430817145749Z010U rsa-20480"0  *H 0 5^0#YY2aD%],E sdM,T75P"¥TtImyjDn%X'J5" qrNsߧgBF_3.."/:GMH)x0(hC$nqJ;ߥ_'{7Wzq`haJh\xJb0N vt\%3Xw'ɦ"[z塌8б(!!,[P0N0U-s89j!Y,o0U#0-s89j!Y,o0 U00  *H  dKnBȓ@dyYNFsK'6 , $u*\I&}{y?0}xG)L` 2@*:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:META-INF/MANIFEST.MFeMK@X,~B?6jY5 -L6323ۯ"( jǔ1jxfڎ@^hzD~ײ$K)2a+k]nvm?!PV|Xx% ޤeՒ74CNA\7~"HL1-NݜQG;%9 jWԬDk k}`V޴YFl+PK-PK!:META-INF/CERT0.RSA3hbiajhδԠѐ߀3̓1qA_&M03121q2eU 'l(hQTkd`ba 'khf`llhhbjnb%kbl`ahbĨl8QM @q.&FF & 5`Y契FodpQ] ʽ8w:˧1pT<@IҐ%pTf5_v@yy{m`:iOawv,^v#w:ns7[Ǫu;_ƽS\4`WI *yxY~pizyxՏO 2Y'2^ʈ^ /d틐7?.+Y;9Fs.=(oW?lR_Y {6YtM}H' FoDox}qq,0dXXDęt-f[ R#o Vf0k@Հ?Z2{mMOė{O_W'&;0_OR"Oލuf<txUKt?aVHo{q鋏ֶop_ICԬDk k}`V޴YFl+PK-PK!:META-INF/CERT1.EC3hb2cjhδIݠIѐ߀3̓1qAcAc .`fbdbd8ſw".6s,LB|l̡,<ɺFfr⼆fƆ&fQ&P.V=l`S333;1,U:)<};/+פ][tn¥-NsOl~rr~Ps˖cc"l3@HUh3' A,b 032GfO7&E, r}".]8?9$Yqk@%?-i>@Zc/]z 1+;XtkmylAh1wW]weײtœݽtm}f+ I@h܃u:Ʀ&|Oq DPKa:-  qw9,( F8Ha yV (lH00 50l0  *H  010U rsa-20480 160331145749Z 430817145749Z010U rsa-20480"0  *H 0 5^0#YY2aD%],E sdM,T75P"¥TtImyjDn%X'J5" qrNsߧgBF_3.."/:GMH)x0(hC$nqJ;ߥ_'{7Wzq`haJh\xJb0N vt\%3Xw'ɦ"[z塌8б(!!,[P0N0U-s89j!Y,o0U#0-s89j!Y,o0 U00  *H  dKnBȓ@dyYNFsK'6 , $u*\I&}{y?0}xG)L` 2@*|([CH ~8B} zs=4LTrcG]Z= `>itd))K]]<&L>-;<Si;'BGPK !:!O resources.arsc5PK!:z3AndroidManifest.xmlPK!:  uclasses.dexPK src/test/resources/com/android/apksig/v1-only-empty.apk0100644 0000000 0000000 00000003060 13243353143 022177 0ustar000000000 0000000 PK!:y|META-INF/RSA-2048.SF LK,)-J K-*ϳR03r.JM,IMu (h8ghr{8d&eV aEA鎹nQaQnUi.I~&nU!e\\PK!:KȨ(META-INF/RSA-2048.RSA3hbiajhδԠѐ߀3̓1qA_&M03121q2eU 'l(hQTkd`ba 'khf`llhhbjnb%kbl`ahbĨl8QM @q.&FF & 5`Y契FodpQ] ʽ8w:˧1pT<@IҐ%pTf5_v@yy{m`:iOawv,^v#w:ns7[Ǫu;_ƽS\4`WI *yxY~pizyxՏO 2Y'2^ʈ^ /d틐7?.+Y;9Fs.=(oW?lR_Y {6YtM}H' FoDox}qq,0dXXDęt-f[ R#o Vf0k@Հ?Z2{mMOė{O_W'&;0_OR"Oލuf<txUKt?aVHo{q鋏ֶop_IC:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:o9%META-INF/RSA-2048.SFmKo@; c"F$=P+ UwCvzi67#a5ŌP%a 0R^(OvXEг,햶O #G̅nR*|_Ӥߘ#&hEu"_eI֠ 0öoXxtN!~""ttp 0쑆9!_Iڇl̜Ƽ?u븯y0$üjqw}FЛ7 PPǔ8wu05͐a+PK!:zL'META-INF/RSA-2048.RSA3hbiajhδԠѐ߀3̓1qA_&M03121q2eU 'l(hQTkd`ba 'khf`llhhbjnb%kbl`ahbĨl8QM @q.&FF & 5`Y契FodpQ] ʽ8w:˧1pT<@IҐ%pTf5_v@yy{m`:iOawv,^v#w:ns7[Ǫu;_ƽS\4`WI *yxY~pizyxՏO 2Y'2^ʈ^ /d틐7?.+Y;9Fs.=(oW?lR_Y {6YtM}H' FoDox}qq,0dXXDęt-f[ R#o Vf0k@Հ?Z2{mMOė{O_W'&;0_OR"Oލuf<txUKt?aVHo{q鋏ֶop_ICdZs3Ojݛh~JK^_,<8q_>=ζsw]/8HGC|¹9R⶧WHpPQ87ю}MZbc6oƌ+~?NyreSv66P+xA~׏r4]c/\(0S'xէ.25^*>V6J-ZųOPK!:DmQMETA-INF/MANIFEST.MFeMK@X,~B?6jY5 -L6323ۯ"( jǔ1jxfڎ@^hzD~ײ$K)2a+k]nvm?!PV|Xx% ޤeՒ7t Z.9?^-$bcy:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexTMhA~l4i5?DvUMQXIi*ԣL7ki:AEEE衈Aăovf4T&߾7}fc`Wsdpn|[I.CՓ``~h G"tk|<(㨬|{wĞYxx9,b1C\@PDqqxxL^ľ0ܶ8DQ{!Z VumJ˹ /5TA_J{Hىa%c |!^6Cj߆XIp=n.Mdf#D'R׿| |BJZ$3"sf4HKTy/E}O`&Z7McA7vOqɛ\6OYܩ9|! }v϶4L{OP+Y8LWVYؾNr=tKvi̚1S<ìa,݄c̮(Mܴ"HIi% m,7fOR}jZeٞ7Zx@D e9ەLZ~0oϋlbSnCʣ5;c̲sa3dǃp6Z$]Z:bAxKhՓkT{Njuo_J w?WĿ]_|k?DUpB0HE рz %qu`###z~R|^32pFEOe*PKՇ PK!:META-INF/MANIFEST.MF]N@@= 0K]@E%qQVH4 ac^:#0ʹߋDM@+hE2R~: !Xx~6{H-*#5DQvy#h+Ҳ~Ԯ\p/1D[ݔwgq|R?0}? 4>]vR&SPPG#D ƌ@a#_ w}~7i˵PCPK>%BPK!:META-INF/CERT.SF]Mo@; am bJiBme_q|dڅ__bڴ6dIhQc8+ &Kk+-W< qo=ci֥>̃~f,9 p DYw=a5%(O,K,Ȫ(o>--lpE&<=Krx0ur*f< ]vR<ͨ߻= s>>bg7SĦ:<Lj|X֬vg^[*kO [!DML?}9wEw# x7157D;]Sz713Ź.>Q|FkqMJ<'+x뎤I-XTl։S|zLYpGs _%942gN8eU?>=*LHdx+#f{EW/B~?dgG]EJ}g.d5";4 U|qliOIXb,͌m:(iiԌS~nkz"{ƟNg?g?1فz~nw՟3ë^ [|OBՕ~K_|}sBMOx9<_RfM ֬7'aBe__#RcϫW2Hg_8N!s[ O20mټ3Kc?]Q%WsG71pn|V0q"%u]yԌZ_9AksM7}UTusumiZjLw@kjEH'jeibdc3ukOMu^0;Eiך^i'`I5̶:?=RkT68rR1ĕR22SjxS9g^ eRK*IC: IABgoK.<PKxPK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPK!:>%B META-INF/MANIFEST.MFPK!:yU< META-INF/CERT.SFPKrHx META-INF/CERT.RSAPKysrc/test/resources/com/android/apksig/v1-only-targetSandboxVersion-2.apk0100644 0000000 0000000 00000011073 13243353143 025356 0ustar000000000 0000000 PK !:!O resources.arsc5 (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=㵝q'8ֆ(PhB$CAD*  MD(OB|AEuvf{cf ķ"Pb88눸F<"^O;qQDB| >|([CH ~8B} zs=4LTrcG]Z= `>itd))K]]<&L>-;<Si;'BGPK PK!:XhMETA-INF/RSA-2048.SFeOo0; ߁"sbHv6!CT[iJ *~f3o/{/f 5Ɗ xh*SAQCB{TDpFU%'<2 QŒӱ56l`:6ea,&/*!*LRo8چG= 4 ]-ܚ_xpH%: 3[/+g(sqͥw.IO'HTn]̓}p?ot{0eu;]vW,L } ^xPK!:Z\QMETA-INF/MANIFEST.MFeMO0; 66x r@l3^L-A ma_/Mv} :!)gsõ] ERk'eJ&Ā\\ 5|k,l_R^xO͍\'`ݵ- n1aDzLD5&%H;%kf' Q#*oMp|`DVEBke׮Z眆njZch ghq<_PK !:!O resources.arsc5PK!:z3AndroidManifest.xmlPK!:  classes.dexPK!:Xh META-INF/RSA-2048.SFPK!:~* META-INF/RSA-2048.RSAPK!:Z\QkMETA-INF/MANIFEST.MFPKsrc/test/resources/com/android/apksig/v1-only-two-signers.apk0100644 0000000 0000000 00000013043 13243353143 023324 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:META-INF/MANIFEST.MFeMK@X,~B?6jY5 -L6323ۯ"( jǔ1jxfڎ@^hzD~ײ$K)2a+k]nvm?!PV|Xx% ޤeՒ74CNA\7~"HL1-NݜQG;%9 jW-|{^r*e^Z)n)k]NA#6nwvkMSd^<|W*-O@Zc/]z 1+;XtkmylAh1wW]f³Nԍg~f{]7,3)H [Nv GҋQq_W|PKӄ:PK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPK!: Q META-INF/MANIFEST.MFPK!:.Y META-INF/CERT0.SFPK!:/&G META-INF/CERT0.RSAPK!:.YMETA-INF/CERT1.SFPK!:ӄ: META-INF/CERT1.ECPKsrc/test/resources/com/android/apksig/v1-only-with-cr-in-entry-name.apk0100644 0000000 0000000 00000011325 13243353143 025102 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexTMhA~l4i5?DvUMQXIi*ԣL7ki:AEEE衈Aăovf4T&߾7}fc`Wsdpn|[I.CՓ``~h G"tk|<(㨬|{wĞYxx9,b1C\@PDqqxxL^ľ0ܶ8DQ{!Z VumJ˹ /5TA_J{Hىa%c |!^6Cj߆XIp=n.Mdf#D'R׿| |BJZ$3"sf4HKTy/E}O`&Z7McA7vOqɛ\6OYܩ9|! }v϶4L{OP+Y8LWVYؾNr=tKvi̚1S<ìa,݄c̮(Mܴ"HIi% m,7fOR}jZeٞ7Zx@D e9ەLZ~0oϋlbSnCʣ5;c̲sa3dǃp6Z$]Z:bAxKhՓkT{Njuo_J w?WĿ]_|k?DUpB0HE рz %qu`###z~R|^32pFEOe*PKՇ PK&sJ test.txt HPK51PK&sJ|"AMETA-INF/CERT.SFmKo@ eXEIQ4[t7tP@fn;tCQ1DqZRk++&9fŏR_L0*&.ir=t,ˀ׈yřDی>~l3RKjŒtC4 6@Ȧ - 㜶Qs\i< rNx 8}HFuaXѴ ҝ˘{v-#q2g$Y* JQ]J4э*>A !BҾ:WN=YE&7PK&sJ ag(QMETA-INF/CERT.RSA3hbejhδΠՊѐ߀3̓1qAsAsASf&F&&A -P5@-|l̡,lyyy0 v . n/A@fa9q^C3Kc#C(q^#C3(NibTB`1713Ź(bujkva}GJZGBNsJzEޙLPlHМ 6ԱLvPO:iÚK"{Oi;$Ԧͫd'޲d*pouyEry[D7_H7Ȝ ''e*?.ȦЯM,z8xJEkg>͙R4a 6}}W2wm^= i'|;3j31320.V47,Vn&oƚpYyF{ ;V+,~M({<ڬϭauCPFO6ln}ЋdX5.y9ُY _3niL׏~sy["^s˧H'{զaK7^=k v9y5-ܖ}kw~ {^0D~yS!5nN gwo~Җv;!g?»jwƭz_~_z?a6u{ywꅗK3Xu ðq0!PB:&jDF.PS9K# =*< t+-$tKEW-W/*~p](EyZxsmse2+ۊLEdŜo6utދv+META-INF/MANIFEST.MFen@= DAѤ .JU*l tؙO_mퟜ/9!3H"x)iX loT!#!Šb J'BEA"pK% de8jc&.cӜM]_M`qXZhllc)ɠsEc{3+t4|djVyU쎤WPV>ߟ'Ί&QhQcܩv\|/: <'q2\X56~PK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPK&sJ51 test.txt PK&sJ|"A META-INF/CERT.SFPK&sJ ag(Q[ META-INF/CERT.RSAPK&sJs>+META-INF/MANIFEST.MFPKsrc/test/resources/com/android/apksig/v1-only-with-dsa-sha1-1.2.840.10040.4.1-1024.apk0100644 0000000 0000000 00000010340 13243353143 025552 0ustar000000000 0000000 PK!:>%BMETA-INF/MANIFEST.MF]N@@= 0K]@E%qQVH4 ac^:#0ʹߋDM@+hE2R~: !Xx~6{H-*#5DQvy#h+Ҳ~Ԯ\p/1D[ݔwgq|R?0}? 4>]vR&SPPG#D ƌ@a#_ w}~7i˵PCPKlP.IMETA-INF/CERT.SFmn@F&, ԖPA-iFr̠׶iښ9Y8âd qgReZ8*=f8A3})h,Ϥ0@gx-0wWOSyP+q6}i "YGȣhD3, @vΊaqo-` 3#,JOviHg&5SrPpaEext(r 1﹊/`4YEko)s~nuIO˦[\?wnߛ~PK}PKlP.IMETA-INF/CERT.DSA3hbbjhδ۠ٝѐۀUIqAV&@\_,/F@=@,X  ٘CYy8Ru L y M yM , ͡\욚6hF Ga2?z?IH͛qM|M z-/6$>۝y} 7N9P$Svʉ)D]M|H|G 4v75W}SٶIaw0[+Үf2g 9{2{?̙}D칾%A"@9p[&_,|lq,0:eXXD[qϔVolg A,b 032GJ` :L"fm?+CNab#D%6",%rT\vBi|\`6PPS1Rʇ:V='y-X?R% L"fNmfbqGPK/s^PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexTMhA~l4i5?DvUMQXIi*ԣL7ki:AEEE衈Aăovf4T&߾7}fc`Wsdpn|[I.CՓ``~h G"tk|<(㨬|{wĞYxx9,b1C\@PDqqxxL^ľ0ܶ8DQ{!Z VumJ˹ /5TA_J{Hىa%c |!^6Cj߆XIp=n.Mdf#D'R׿| |BJZ$3"sf4HKTy/E}O`&Z7McA7vOqɛ\6OYܩ9|! }v϶4L{OP+Y8LWVYؾNr=tKvi̚1S<ìa,݄c̮(Mܴ"HIi% m,7fOR}jZeٞ7Zx@D e9ەLZ~0oϋlbSnCʣ5;c̲sa3dǃp6Z$]Z:bAxKhՓkT{Njuo_J w?WĿ]_|k?DUpB0HE рz %qu`###z~R|^32pFEOe*PKՇ PK!:>%BMETA-INF/MANIFEST.MFPKlP.I}META-INF/CERT.SFPKlP.I/s^cMETA-INF/CERT.DSAPK !:!Oresources.arscPK!:^avT AndroidManifest.xmlPK!:Շ  classes.dexPKyQsrc/test/resources/com/android/apksig/v1-only-with-dsa-sha1-1.2.840.10040.4.1-2048.apk0100644 0000000 0000000 00000011275 13243353143 025571 0ustar000000000 0000000 PK!:>%BMETA-INF/MANIFEST.MF]N@@= 0K]@E%qQVH4 ac^:#0ʹߋDM@+hE2R~: !Xx~6{H-*#5DQvy#h+Ҳ~Ԯ\p/1D[ݔwgq|R?0}? 4>]vR&SPPG#D ƌ@a#_ w}~7i˵PCPKP.IMETA-INF/CERT.SFmn@F&, ԖPA-iFr̠׶iښ9Y8âd qgReZ8*=f8A3})h,Ϥ0@gx-0wWOSyP+q6}i "YGȣhD3, @vΊaqo-` 3#,JOviHg&5SrPpaEext(r 1﹊/`4YEko)s~nuIO˦[\?wnߛ~PK}PKP.IMETA-INF/CERT.DSA3hbcjhδߠѐۀUIqAKAK43-`fbdbdXt@=;vs,†l̡,<)ʼnF&r⼆fƆQ&P.vMMML0&FFܕ>lӑ2F9f22<{7j`|Ӣ 6<ūh %#ƺO8^,YQW5qadrݷvE=w+T_;]g=&m:`au,a>/Mgn鶛e+g`ˏWSU\&tMHV'Ur+xSYr<-gkh]#vUi/^vHCnV1׳f伏A9`[ssn&E9h6[S)}>]/RtvwDpb1`%m/67~K.gVdMgx^#e쓦|rɢUxl6>B-?1vηf:WPsIaΚ[ t8l O}~s褟2k]go{9nŖ^H[zbf-SV~ ,i-_} ִvN0Ŏ˶?c\ [НѨ9/'n<5yƌNYF׷~ZxRMU2Ʌu<ǀC<=}NrYtsnN,'>Tv x n#4ѷ̣3YJ=3$U)mo//j%;…ΉK;?=1϶R Qܘ$P׺'okx]~杺ȧ'~e_*4iΎ%+l.(y#x?Y`ccdUIG__iɯ0!y2Xv< ,̌r<+0I(N]>Ô5Uuz/I>+>tiC rצ8w֊WT/s`69}8vacAc+2PZ"8H`e`q3paRp+Ms'D'9_z n:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexTMhA~l4i5?DvUMQXIi*ԣL7ki:AEEE衈Aăovf4T&߾7}fc`Wsdpn|[I.CՓ``~h G"tk|<(㨬|{wĞYxx9,b1C\@PDqqxxL^ľ0ܶ8DQ{!Z VumJ˹ /5TA_J{Hىa%c |!^6Cj߆XIp=n.Mdf#D'R׿| |BJZ$3"sf4HKTy/E}O`&Z7McA7vOqɛ\6OYܩ9|! }v϶4L{OP+Y8LWVYؾNr=tKvi̚1S<ìa,݄c̮(Mܴ"HIi% m,7fOR}jZeٞ7Zx@D e9ەLZ~0oϋlbSnCʣ5;c̲sa3dǃp6Z$]Z:bAxKhՓkT{Njuo_J w?WĿ]_|k?DUpB0HE рz %qu`###z~R|^32pFEOe*PKՇ PK!:>%BMETA-INF/MANIFEST.MFPKP.I}META-INF/CERT.SFPKP.I@"cMETA-INF/CERT.DSAPK !:!Oresources.arscPK!:^av1 AndroidManifest.xmlPK!:Շ  classes.dexPKy.src/test/resources/com/android/apksig/v1-only-with-dsa-sha1-1.2.840.10040.4.1-3072.apk0100644 0000000 0000000 00000012110 13243353143 025554 0ustar000000000 0000000 PK!:>%BMETA-INF/MANIFEST.MF]N@@= 0K]@E%qQVH4 ac^:#0ʹߋDM@+hE2R~: !Xx~6{H-*#5DQvy#h+Ҳ~Ԯ\p/1D[ݔwgq|R?0}? 4>]vR&SPPG#D ƌ@a#_ w}~7i˵PCPK(Q.IMETA-INF/CERT.SFmn@F&, ԖPA-iFr̠׶iښ9Y8âd qgReZ8*=f8A3})h,Ϥ0@gx-0wWOSyP+q6}i "YGȣhD3, @vΊaqo-` 3#,JOviHg&5SrPpaEext(r 1﹊/`4YEko)s~nuIO˦[\?wnߛ~PK}PK(Q.IMETA-INF/CERT.DSAm{4y=f#GgA!`0(t"9H9X"؄ABbH L$$@#T%D'a4XXB4P3F\'mt~~E\obK[!"kO1eCd+qTtm"/Y@*%_&48Z"*~C'Qc'VQZu[] яfEjv- .sˁ%1qfh0ZR Esc Ɵi~lc<Öm֬m͹\Eig~@nwn1$nQDoJqּpδ@_넕W|q$y=&N;VZGxWS%Fh 'Șu" /L]v";3:ʗ Me.!]KW N^.dEQ}ەiy-Ғ:y.Rtd1[:li\0A8R?/TK*w= |Qb}/b-f5Qֱ{gѸ롶-Vg"uyF!{<զTu訞L;Y*U ԉ(ga80!^֮#oN%@ϽJ1+=z^m.Ձ"L ^ I{xTǼ* w zœ/+q"i('\u\;νȐ+uTsFЋj\2";.F0̹מck6]vǡn C  &}ƹI ϕJSEsv.!&inP[x+įqxCSVȪ;Kiv r]9}A Lc7͑<&5b$K&6,z֓T.hFdu ׃|ة4G9}rU껡 %2?lhJхAg-r$?Џa,t$wb󺕘eOGEA0w`7o8|F^I@JTi)ֿd ]f`78Z(e#[PKTjPK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexTMhA~l4i5?DvUMQXIi*ԣL7ki:AEEE衈Aăovf4T&߾7}fc`Wsdpn|[I.CՓ``~h G"tk|<(㨬|{wĞYxx9,b1C\@PDqqxxL^ľ0ܶ8DQ{!Z VumJ˹ /5TA_J{Hىa%c |!^6Cj߆XIp=n.Mdf#D'R׿| |BJZ$3"sf4HKTy/E}O`&Z7McA7vOqɛ\6OYܩ9|! }v϶4L{OP+Y8LWVYؾNr=tKvi̚1S<ìa,݄c̮(Mܴ"HIi% m,7fOR}jZeٞ7Zx@D e9ەLZ~0oϋlbSnCʣ5;c̲sa3dǃp6Z$]Z:bAxKhՓkT{Njuo_J w?WĿ]_|k?DUpB0HE рz %qu`###z~R|^32pFEOe*PKՇ PK!:>%BMETA-INF/MANIFEST.MFPK(Q.I}META-INF/CERT.SFPK(Q.ITjcMETA-INF/CERT.DSAPK !:!O resources.arscPK!:^av AndroidManifest.xmlPK!:Շ  sclasses.dexPKysrc/test/resources/com/android/apksig/v1-only-with-dsa-sha1-1.2.840.10040.4.3-1024.apk0100644 0000000 0000000 00000010271 13243353143 025557 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:META-INF/MANIFEST.MF]N@@= 0K]@E%qQVH4 ac^:#0ʹߋDM@+hE2R~: !Xx~6{H-*#5DQvy#h+Ҳ~Ԯ\p/1D[ݔwgq|R?0}? 4>]vR&SPPG#D ƌ@a#_ w}~7i˵PCPK>%BPK!:META-INF/CERT.SF]Mo@; am bJiBme_q|dڅ__bڴ6dIhQc8+ &Kk+-W< qo=ci֥>̃~f,9 p DYw=a5%(O,K,Ȫ(o>--lpE&<=Krx0ur*f< ]vR<ͨ߻= s>>bg7IH͛qM|M z-/6$>۝y} 7N9P$Svʉ)D]M|H|G 4v75W}SٶIaw0[+Үf2g 9{2{?̙}D칾%A"@9p[&_,|lq,0:eXXD[qϔVolg A,b 032GJ` :L"fm?+CNab#D%6",%rT\vBi\l`6PPS1<#cYgVxDcҜu&ܪ?uv`JVl붟EpzPKF\PK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPK!:>%B META-INF/MANIFEST.MFPK!:yU< META-INF/CERT.SFPK!:F\ META-INF/CERT.DSAPKy*src/test/resources/com/android/apksig/v1-only-with-dsa-sha1-1.2.840.10040.4.3-2048.apk0100644 0000000 0000000 00000011225 13243353143 025566 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:META-INF/MANIFEST.MF]N@@= 0K]@E%qQVH4 ac^:#0ʹߋDM@+hE2R~: !Xx~6{H-*#5DQvy#h+Ҳ~Ԯ\p/1D[ݔwgq|R?0}? 4>]vR&SPPG#D ƌ@a#_ w}~7i˵PCPK>%BPK!:META-INF/CERT.SF]Mo@; am bJiBme_q|dڅ__bڴ6dIhQc8+ &Kk+-W< qo=ci֥>̃~f,9 p DYw=a5%(O,K,Ȫ(o>--lpE&<=Krx0ur*f< ]vR<ͨ߻= s>>bg7lӑ2F9f22<{7j`|Ӣ 6<ūh %#ƺO8^,YQW5qadrݷvE=w+T_;]g=&m:`au,a>/Mgn鶛e+g`ˏWSU\&tMHV'Ur+xSYr<-gkh]#vUi/^vHCnV1׳f伏A9`[ssn&E9h6[S)}>]/RtvwDpb1`%m/67~K.gVdMgx^#e쓦|rɢUxl6>B-?1vηf:WPsIaΚ[ t8l O}~s褟2k]go{9nŖ^H[zbf-SV~ ,i-_} ִvN0Ŏ˶?c\ [НѨ9/'n<5yƌNYF׷~ZxRMU2Ʌu<ǀC<=}NrYtsnN,'>Tv x n#4ѷ̣3YJ=3$U)mo//j%;…ΉK;?=1϶R Qܘ$P׺'okx]~杺ȧ'~e_*4iΎ%+l.(y#x?Y`ccdUIG__iɯ0!y2Xv< ,̌r<+0I(N]>Ô5Uuz/I>+>tiC rצ8w֊WT/s`69}8vacAc 2PZ"8R wK 9lg ӷiN1.s6ܟtěZB=Vw8}Y'uqӋۯ_npZ#fprAPKl!PK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPK!:>%B META-INF/MANIFEST.MFPK!:yU< META-INF/CERT.SFPK!:l! META-INF/CERT.DSAPKysrc/test/resources/com/android/apksig/v1-only-with-dsa-sha1-1.2.840.10040.4.3-3072.apk0100644 0000000 0000000 00000012036 13243353143 025565 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:META-INF/MANIFEST.MF]N@@= 0K]@E%qQVH4 ac^:#0ʹߋDM@+hE2R~: !Xx~6{H-*#5DQvy#h+Ҳ~Ԯ\p/1D[ݔwgq|R?0}? 4>]vR&SPPG#D ƌ@a#_ w}~7i˵PCPK>%BPK!:META-INF/CERT.SF]Mo@; am bJiBme_q|dڅ__bڴ6dIhQc8+ &Kk+-W< qo=ci֥>̃~f,9 p DYw=a5%(O,K,Ȫ(o>--lpE&<=Krx0ur*f< ]vR<ͨ߻= s>>bg73 픙uD=+h ր5[곤Ȳs, ){Hx3=F&HQz7y,/նu٫Zr1m:zH ni7@?Lp9C1f&*ET 8h"{FL6H%HgOƱLރ=Ü5]il5۷BRV@2٠?h0 [e92DNzPѫNX/ ^2lE=|>Ц6 {&|dݶ|:*j{He*7vx}mܸ\;e W.3{SydG ʣh__lqmB. 1CqL~;*pؔ[6%lƯa%RnFv@~˃+qN6Y'mYY1@M>WJܾ@M;"L9tƘToHth0g/U"7|_Q~zJ(7ZFG* eV}QWG_=x10]ĜYzvLH݌Z08q\_uρGj eVd sv "K²W?p}r?Ep|A2Y7jĿ^c j jOňF-j-_B"1HwikmjIOPK&%B META-INF/MANIFEST.MFPK!:yU< META-INF/CERT.SFPK!:&IH͛qM|M z-/6$>۝y} 7N9P$Svʉ)D]M|H|G 4v75W}SٶIaw0[+Үf2g 9{2{?̙}D칾%A"@9p[&_,|lq,0:eXXD[qϔVolg A,b 032GJ` :L"fm?+CNab#D%6",%rT\vBibB`6PʐS1FꇥPK$ffPK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexTMhA~l4i5?DvUMQXIi*ԣL7ki:AEEE衈Aăovf4T&߾7}fc`Wsdpn|[I.CՓ``~h G"tk|<(㨬|{wĞYxx9,b1C\@PDqqxxL^ľ0ܶ8DQ{!Z VumJ˹ /5TA_J{Hىa%c |!^6Cj߆XIp=n.Mdf#D'R׿| |BJZ$3"sf4HKTy/E}O`&Z7McA7vOqɛ\6OYܩ9|! }v϶4L{OP+Y8LWVYؾNr=tKvi̚1S<ìa,݄c̮(Mܴ"HIi% m,7fOR}jZeٞ7Zx@D e9ەLZ~0oϋlbSnCʣ5;c̲sa3dǃp6Z$]Z:bAxKhՓkT{Njuo_J w?WĿ]_|k?DUpB0HE рz %qu`###z~R|^32pFEOe*PKՇ PK!: QMETA-INF/MANIFEST.MFPKcQ.IP/%e/META-INF/CERT.SFPKcQ.I$ffMETA-INF/CERT.DSAPK !:!Oresources.arscPK!:^av AndroidManifest.xmlPK!:Շ  classes.dexPKysrc/test/resources/com/android/apksig/v1-only-with-dsa-sha224-1.2.840.10040.4.1-2048.apk0100644 0000000 0000000 00000011457 13243353143 025742 0ustar000000000 0000000 PK!: QMETA-INF/MANIFEST.MFeMK@X,~B?6jY5 -L6323ۯ"( jǔ1jxfڎ@^hzD~ײ$K)2a+k]nvm?!PV|Xx% ޤeՒ74CNA\7~"HL1-NݜQG;%9 jWlӑ2F9f22<{7j`|Ӣ 6<ūh %#ƺO8^,YQW5qadrݷvE=w+T_;]g=&m:`au,a>/Mgn鶛e+g`ˏWSU\&tMHV'Ur+xSYr<-gkh]#vUi/^vHCnV1׳f伏A9`[ssn&E9h6[S)}>]/RtvwDpb1`%m/67~K.gVdMgx^#e쓦|rɢUxl6>B-?1vηf:WPsIaΚ[ t8l O}~s褟2k]go{9nŖ^H[zbf-SV~ ,i-_} ִvN0Ŏ˶?c\ [НѨ9/'n<5yƌNYF׷~ZxRMU2Ʌu<ǀC<=}NrYtsnN,'>Tv x n#4ѷ̣3YJ=3$U)mo//j%;…ΉK;?=1϶R Qܘ$P׺'okx]~杺ȧ'~e_*4iΎ%+l.(y#x?Y`ccdUIG__iɯ0!y2Xv< ,̌r<+0I(N]>Ô5Uuz/I>+>tiC rצ8w֊WT/s`69}8vacAc$2PZ" 8%`e`q9VgA;7ܭ>lKצgl׹ͤŴ=ȮUۦ_f}3C65 {PKqP^+PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexTMhA~l4i5?DvUMQXIi*ԣL7ki:AEEE衈Aăovf4T&߾7}fc`Wsdpn|[I.CՓ``~h G"tk|<(㨬|{wĞYxx9,b1C\@PDqqxxL^ľ0ܶ8DQ{!Z VumJ˹ /5TA_J{Hىa%c |!^6Cj߆XIp=n.Mdf#D'R׿| |BJZ$3"sf4HKTy/E}O`&Z7McA7vOqɛ\6OYܩ9|! }v϶4L{OP+Y8LWVYؾNr=tKvi̚1S<ìa,݄c̮(Mܴ"HIi% m,7fOR}jZeٞ7Zx@D e9ەLZ~0oϋlbSnCʣ5;c̲sa3dǃp6Z$]Z:bAxKhՓkT{Njuo_J w?WĿ]_|k?DUpB0HE рz %qu`###z~R|^32pFEOe*PKՇ PK!: QMETA-INF/MANIFEST.MFPKQ.IP/%e/META-INF/CERT.SFPKQ.IqP^+META-INF/CERT.DSAPK !:!Oresources.arscPK!:^av AndroidManifest.xmlPK!:Շ  Zclasses.dexPKysrc/test/resources/com/android/apksig/v1-only-with-dsa-sha224-1.2.840.10040.4.1-3072.apk0100644 0000000 0000000 00000012273 13243353143 025735 0ustar000000000 0000000 PK!: QMETA-INF/MANIFEST.MFeMK@X,~B?6jY5 -L6323ۯ"( jǔ1jxfڎ@^hzD~ײ$K)2a+k]nvm?!PV|Xx% ޤeՒ74CNA\7~"HL1-NݜQG;%9 jW[zl}!Es- GLog=WnJ}my[q$`Cv^U ΦV:Ќj2&, |{)\ R\>t+b@lZLt!U%Oth8FCyj^>LX-$;B>n[IѲWmbɊqQeMad6/UTn"ɤP"RʛyLn]v}.-6ZX{@Q!tyEdrGQ9"5<>a,׫aj gW(񼸠?<̵%? N{ecౝ6ܴ0wf5o7mS.H=['t{n MGbGHH=o7՚7-Aԕ{b|/i1|0:tw 77YkqJ, HN͜Xu" '3M]v!tJ5N,_Էt YrbuD?ʼ}f7Emwi៶NK?v+%RtdQ:,i\0A8R?/TK*s ^x$xRgٓ6S51NѺsfsm{Cm[θ"u&!w4ݮPu蠞J;U&UtD 0W8aB潬\F&J"4+bV2z}zӻq11(;-Tt߃#J>fUac6_sᭋF+"i)&\ul;SޛgJy% wc/AְX9ǤFLbaZ'цER/|=I`AH\p<ȁԤY1˥/h15 =$B!= b|O垕˻;S߳7=%LMA2wjO.uMuʆ0-@#@`B++^G P+P*S T*>h6E cam_+x$?>q DXr }Vɟ*⣦ @a_%zU4HF+`kX,˔@pnnףiw pӁ#r7v7/ PKy8nPK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexTMhA~l4i5?DvUMQXIi*ԣL7ki:AEEE衈Aăovf4T&߾7}fc`Wsdpn|[I.CՓ``~h G"tk|<(㨬|{wĞYxx9,b1C\@PDqqxxL^ľ0ܶ8DQ{!Z VumJ˹ /5TA_J{Hىa%c |!^6Cj߆XIp=n.Mdf#D'R׿| |BJZ$3"sf4HKTy/E}O`&Z7McA7vOqɛ\6OYܩ9|! }v϶4L{OP+Y8LWVYؾNr=tKvi̚1S<ìa,݄c̮(Mܴ"HIi% m,7fOR}jZeٞ7Zx@D e9ەLZ~0oϋlbSnCʣ5;c̲sa3dǃp6Z$]Z:bAxKhՓkT{Njuo_J w?WĿ]_|k?DUpB0HE рz %qu`###z~R|^32pFEOe*PKՇ PK!: QMETA-INF/MANIFEST.MFPKQ.IP/%e/META-INF/CERT.SFPKQ.Iy8nMETA-INF/CERT.DSAPK !:!O resources.arscPK!:^av/ AndroidManifest.xmlPK!:Շ  classes.dexPKy,src/test/resources/com/android/apksig/v1-only-with-dsa-sha224-2.16.840.1.101.3.4.3.1-1024.apk0100644 0000000 0000000 00000010433 13243353143 026310 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:META-INF/MANIFEST.MFeMK@X,~B?6jY5 -L6323ۯ"( jǔ1jxfڎ@^hzD~ײ$K)2a+k]nvm?!PV|Xx% ޤeՒ74CNA\7~"HL1-NݜQG;%9 jWIH͛qM|M z-/6$>۝y} 7N9P$Svʉ)D]M|H|G 4v75W}SٶIaw0[+Үf2g 9{2{?̙}D칾%A"@9p[&_,|lq,0:eXXD[qϔVolg A,b 032GJ` :L"fm?+CNab#D%6",%rT\vBibB`6PʐS1frY@j쏪xs 5,bY$p3[V.fx"?b4PKQfPK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPK!: Q META-INF/MANIFEST.MFPK!:.Y META-INF/CERT.SFPK!:QfF META-INF/CERT.DSAPKysrc/test/resources/com/android/apksig/v1-only-with-dsa-sha224-2.16.840.1.101.3.4.3.1-2048.apk0100644 0000000 0000000 00000011365 13243353143 026324 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:META-INF/MANIFEST.MFeMK@X,~B?6jY5 -L6323ۯ"( jǔ1jxfڎ@^hzD~ײ$K)2a+k]nvm?!PV|Xx% ޤeՒ74CNA\7~"HL1-NݜQG;%9 jWlӑ2F9f22<{7j`|Ӣ 6<ūh %#ƺO8^,YQW5qadrݷvE=w+T_;]g=&m:`au,a>/Mgn鶛e+g`ˏWSU\&tMHV'Ur+xSYr<-gkh]#vUi/^vHCnV1׳f伏A9`[ssn&E9h6[S)}>]/RtvwDpb1`%m/67~K.gVdMgx^#e쓦|rɢUxl6>B-?1vηf:WPsIaΚ[ t8l O}~s褟2k]go{9nŖ^H[zbf-SV~ ,i-_} ִvN0Ŏ˶?c\ [НѨ9/'n<5yƌNYF׷~ZxRMU2Ʌu<ǀC<=}NrYtsnN,'>Tv x n#4ѷ̣3YJ=3$U)mo//j%;…ΉK;?=1϶R Qܘ$P׺'okx]~杺ȧ'~e_*4iΎ%+l.(y#x?Y`ccdUIG__iɯ0!y2Xv< ,̌r<+0I(N]>Ô5Uuz/I>+>tiC rצ8w֊WT/s`69}8vacAc'$2PZ" 8%f¤_IvuX, Ô?'3U_Ƥ`6-߄ӵ\okC PKOL*PK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPK!: Q META-INF/MANIFEST.MFPK!:.Y META-INF/CERT.SFPK!:OL*F META-INF/CERT.DSAPKyfsrc/test/resources/com/android/apksig/v1-only-with-dsa-sha224-2.16.840.1.101.3.4.3.1-3072.apk0100644 0000000 0000000 00000012203 13243353143 026312 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:META-INF/MANIFEST.MFeMK@X,~B?6jY5 -L6323ۯ"( jǔ1jxfڎ@^hzD~ײ$K)2a+k]nvm?!PV|Xx% ޤeՒ74CNA\7~"HL1-NݜQG;%9 jW`t/_(u WjP5H>\= h]73{9búhqTIG@ exY4K +o{+2e77~Bn[qwq5,]^p gyk Oܡ_Ry6# !YG )W LsD/)Άc݋u0zbkO&煚1r6y|;P )>>ט뺜 nJ~l+̎_Ɯ'z3J_@k3Yy1t-N%V2U3ɣS!WAt= dB0Lp0˷]qtA,=PԮ[7X L[k,ơtбOΪT[YԆc?Y $SFP'MlOωQӮƶm{7[f ;ʔzZWb+@wiPV"Z$ئ[J^D|Se|B1'*{K٧l|E9eݴ&E./?\(Iǭ}ë㉰d \VP"w.ʈ!xrR|ɜs)8BkF+Sfg 2jngmScfCw$ڨ_@mx^Ny͵I ?l5ㄝ`FW}+r]e)/wۧ{^%ӓ_qho*p-,ly9 ;0 6Z~%.ck\q~yj 0rEzX آ+V]$ ?IH͛qM|M z-/6$>۝y} 7N9P$Svʉ)D]M|H|G 4v75W}SٶIaw0[+Үf2g 9{2{?̙}D칾%A"@9p[&_,|lq,0:eXXD[qϔVolg A,b 032GJ` :L"fm?+CNab#D%6",%rT\vBibB`6PʐS1Fꇥ:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexTMhA~l4i5?DvUMQXIi*ԣL7ki:AEEE衈Aăovf4T&߾7}fc`Wsdpn|[I.CՓ``~h G"tk|<(㨬|{wĞYxx9,b1C\@PDqqxxL^ľ0ܶ8DQ{!Z VumJ˹ /5TA_J{Hىa%c |!^6Cj߆XIp=n.Mdf#D'R׿| |BJZ$3"sf4HKTy/E}O`&Z7McA7vOqɛ\6OYܩ9|! }v϶4L{OP+Y8LWVYؾNr=tKvi̚1S<ìa,݄c̮(Mܴ"HIi% m,7fOR}jZeٞ7Zx@D e9ەLZ~0oϋlbSnCʣ5;c̲sa3dǃp6Z$]Z:bAxKhՓkT{Njuo_J w?WĿ]_|k?DUpB0HE рz %qu`###z~R|^32pFEOe*PKՇ PK!: QMETA-INF/MANIFEST.MFPKQ.IP/%e/META-INF/CERT.SFPKQ.IiafMETA-INF/CERT.DSAPK !:!Oresources.arscPK!:^av AndroidManifest.xmlPK!:Շ  ~ classes.dexPKysrc/test/resources/com/android/apksig/v1-only-with-dsa-sha256-1.2.840.10040.4.1-2048.apk0100644 0000000 0000000 00000011460 13243353143 025741 0ustar000000000 0000000 PK!: QMETA-INF/MANIFEST.MFeMK@X,~B?6jY5 -L6323ۯ"( jǔ1jxfڎ@^hzD~ײ$K)2a+k]nvm?!PV|Xx% ޤeՒ74CNA\7~"HL1-NݜQG;%9 jWlӑ2F9f22<{7j`|Ӣ 6<ūh %#ƺO8^,YQW5qadrݷvE=w+T_;]g=&m:`au,a>/Mgn鶛e+g`ˏWSU\&tMHV'Ur+xSYr<-gkh]#vUi/^vHCnV1׳f伏A9`[ssn&E9h6[S)}>]/RtvwDpb1`%m/67~K.gVdMgx^#e쓦|rɢUxl6>B-?1vηf:WPsIaΚ[ t8l O}~s褟2k]go{9nŖ^H[zbf-SV~ ,i-_} ִvN0Ŏ˶?c\ [НѨ9/'n<5yƌNYF׷~ZxRMU2Ʌu<ǀC<=}NrYtsnN,'>Tv x n#4ѷ̣3YJ=3$U)mo//j%;…ΉK;?=1϶R Qܘ$P׺'okx]~杺ȧ'~e_*4iΎ%+l.(y#x?Y`ccdUIG__iɯ0!y2Xv< ,̌r<+0I(N]>Ô5Uuz/I>+>tiC rצ8w֊WT/s`69}8vacAc'$2PZ" 8%`e`q3paRp]a:I)x%0}1٫`B z,/dmVW=YyfO+k]DWPKcIz*PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexTMhA~l4i5?DvUMQXIi*ԣL7ki:AEEE衈Aăovf4T&߾7}fc`Wsdpn|[I.CՓ``~h G"tk|<(㨬|{wĞYxx9,b1C\@PDqqxxL^ľ0ܶ8DQ{!Z VumJ˹ /5TA_J{Hىa%c |!^6Cj߆XIp=n.Mdf#D'R׿| |BJZ$3"sf4HKTy/E}O`&Z7McA7vOqɛ\6OYܩ9|! }v϶4L{OP+Y8LWVYؾNr=tKvi̚1S<ìa,݄c̮(Mܴ"HIi% m,7fOR}jZeٞ7Zx@D e9ەLZ~0oϋlbSnCʣ5;c̲sa3dǃp6Z$]Z:bAxKhՓkT{Njuo_J w?WĿ]_|k?DUpB0HE рz %qu`###z~R|^32pFEOe*PKՇ PK!: QMETA-INF/MANIFEST.MFPKR.IP/%e/META-INF/CERT.SFPKR.IcIz*META-INF/CERT.DSAPK !:!Oresources.arscPK!:^av AndroidManifest.xmlPK!:Շ  [classes.dexPKysrc/test/resources/com/android/apksig/v1-only-with-dsa-sha256-1.2.840.10040.4.1-3072.apk0100644 0000000 0000000 00000012273 13243353143 025742 0ustar000000000 0000000 PK!: QMETA-INF/MANIFEST.MFeMK@X,~B?6jY5 -L6323ۯ"( jǔ1jxfڎ@^hzD~ײ$K)2a+k]nvm?!PV|Xx% ޤeՒ74CNA\7~"HL1-NݜQG;%9 jW)s eN(Uo 7zV0NB-|]վr숢@?lZ^+~'ň6f>f P|LNaQ`T򈱀-;Zu( (/2'dvHhË{,Hׄ/SM;+V 7\FzVc0'`]>;ˬ;$~u1ZtTq[liB0IZ˿$TO)w '_{e"q?11~zhUPe{LA[ROۊZ+eۨ 6kO#~jpC;%lDWP8]bKR%qڜDoYϗ#Y,3usϔ'?5)! cP6RZ.vY~ @_Ti-LzPd!+j\ V\Eq+Sq[ٮ,XE։wai PK#[DnPK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexTMhA~l4i5?DvUMQXIi*ԣL7ki:AEEE衈Aăovf4T&߾7}fc`Wsdpn|[I.CՓ``~h G"tk|<(㨬|{wĞYxx9,b1C\@PDqqxxL^ľ0ܶ8DQ{!Z VumJ˹ /5TA_J{Hىa%c |!^6Cj߆XIp=n.Mdf#D'R׿| |BJZ$3"sf4HKTy/E}O`&Z7McA7vOqɛ\6OYܩ9|! }v϶4L{OP+Y8LWVYؾNr=tKvi̚1S<ìa,݄c̮(Mܴ"HIi% m,7fOR}jZeٞ7Zx@D e9ەLZ~0oϋlbSnCʣ5;c̲sa3dǃp6Z$]Z:bAxKhՓkT{Njuo_J w?WĿ]_|k?DUpB0HE рz %qu`###z~R|^32pFEOe*PKՇ PK!: QMETA-INF/MANIFEST.MFPK R.IP/%e/META-INF/CERT.SFPK R.I#[DnMETA-INF/CERT.DSAPK !:!O resources.arscPK!:^av/ AndroidManifest.xmlPK!:Շ  classes.dexPKy,src/test/resources/com/android/apksig/v1-only-with-dsa-sha256-2.16.840.1.101.3.4.3.2-1024.apk0100644 0000000 0000000 00000010433 13243353143 026316 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:META-INF/MANIFEST.MFeMK@X,~B?6jY5 -L6323ۯ"( jǔ1jxfڎ@^hzD~ײ$K)2a+k]nvm?!PV|Xx% ޤeՒ74CNA\7~"HL1-NݜQG;%9 jWIH͛qM|M z-/6$>۝y} 7N9P$Svʉ)D]M|H|G 4v75W}SٶIaw0[+Үf2g 9{2{?̙}D칾%A"@9p[&_,|lq,0:eXXD[qϔVolg A,b 032GJ` :L"fm?+CNab#D%6",%rT\vBibB`6PʐS1frX@ڬ|M:ˏ<$Rm cdJW-|PK ofPK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPK!: Q META-INF/MANIFEST.MFPK!:.Y META-INF/CERT.SFPK!: ofF META-INF/CERT.DSAPKysrc/test/resources/com/android/apksig/v1-only-with-dsa-sha256-2.16.840.1.101.3.4.3.2-2048.apk0100644 0000000 0000000 00000011365 13243353143 026332 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:META-INF/MANIFEST.MFeMK@X,~B?6jY5 -L6323ۯ"( jǔ1jxfڎ@^hzD~ײ$K)2a+k]nvm?!PV|Xx% ޤeՒ74CNA\7~"HL1-NݜQG;%9 jWlӑ2F9f22<{7j`|Ӣ 6<ūh %#ƺO8^,YQW5qadrݷvE=w+T_;]g=&m:`au,a>/Mgn鶛e+g`ˏWSU\&tMHV'Ur+xSYr<-gkh]#vUi/^vHCnV1׳f伏A9`[ssn&E9h6[S)}>]/RtvwDpb1`%m/67~K.gVdMgx^#e쓦|rɢUxl6>B-?1vηf:WPsIaΚ[ t8l O}~s褟2k]go{9nŖ^H[zbf-SV~ ,i-_} ִvN0Ŏ˶?c\ [НѨ9/'n<5yƌNYF׷~ZxRMU2Ʌu<ǀC<=}NrYtsnN,'>Tv x n#4ѷ̣3YJ=3$U)mo//j%;…ΉK;?=1϶R Qܘ$P׺'okx]~杺ȧ'~e_*4iΎ%+l.(y#x?Y`ccdUIG__iɯ0!y2Xv< ,̌r<+0I(N]>Ô5Uuz/I>+>tiC rצ8w֊WT/s`69}8vacAc$2PZ" 8%vOOkSWw:koJ(r &Ԑy..}x䟎 [H>8wDPKr+PK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPK!: Q META-INF/MANIFEST.MFPK!:.Y META-INF/CERT.SFPK!:r+F META-INF/CERT.DSAPKyfsrc/test/resources/com/android/apksig/v1-only-with-dsa-sha256-2.16.840.1.101.3.4.3.2-3072.apk0100644 0000000 0000000 00000012204 13243353143 026321 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:META-INF/MANIFEST.MFeMK@X,~B?6jY5 -L6323ۯ"( jǔ1jxfڎ@^hzD~ײ$K)2a+k]nvm?!PV|Xx% ޤeՒ74CNA\7~"HL1-NݜQG;%9 jW[L6@P:j7@! 1z UWJ P(d nH\1a =]a aA-V;nFҺ'r(tNY1TEѫ Em'*f6SUWR`~"ErLwOn-')zQEÍ̜rAbΫ$))%QSё7B(GMD d^GY}\ >J $.D"wL{ ?5\RjLK8*z Zg_)8v08{gg~vfRѹنS>e32git;Q|YQ|{]iwվ*GDMu Tso h[#ю[O6}Q8]ʠYzi>]5[@i2yywO 'j)smLjzA]z0~I0ߟ3H;:\IB9s1BԬ n|U>"W vu}{j3}Dg4wsxwMҤJ}~ZIT9ս|1.:!ǚWA[f-;.e\5i,) zHR 6Zt˿j-0J=X&]/L]S_ʐlmV4+Ô wVWMs䶶S1"QA6VZY$ek͖:d0&e[V-m+F 2r[󣆻 3va`^0A5ɉ>!CO 2W 76VCEo>PJ*ҁoxM>LWG1>Ɯ]~wLh :08UI\ {גERA/y'MФjUNt&򅈋]$06at}YqO,(b𺇜}.}ბWB`ހ@lx SLzoOH_u3Os y~bBwVG-ևfPKpPK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPK!: Q META-INF/MANIFEST.MFPK!:.Y META-INF/CERT.SFPK!:pF META-INF/CERT.DSAPKysrc/test/resources/com/android/apksig/v1-only-with-dsa-sha384-2.16.840.1.101.3.4.3.3-1024.apk0100644 0000000 0000000 00000010630 13243353143 026320 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:META-INF/MANIFEST.MFen@= Cj+Z4Sfu;>}1iퟜyDjM1XA~T)*W "˚#Ϊɹ&v,2>(ь3+HzBWbٽIH͛qM|M z-/6$>۝y} 7N9P$Svʉ)D]M|H|G 4v75W}SٶIaw0[+Үf2g 9{2{?̙}D칾%A"@9p[&_,|lq,0:eXXD[qϔVolg A,b 032GJ` :L"fm?+CNab#D%6",%rT\vBibB`6PʐS1frY@ +1u*iZyc 1dωߣ6kZSPK+ϷfPK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPK!:Z"Hs4 META-INF/MANIFEST.MFPK!:ile! META-INF/CERT.SFPK!:+Ϸf META-INF/CERT.DSAPKy src/test/resources/com/android/apksig/v1-only-with-dsa-sha384-2.16.840.1.101.3.4.3.3-2048.apk0100644 0000000 0000000 00000011564 13243353143 026336 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:META-INF/MANIFEST.MFen@= Cj+Z4Sfu;>}1iퟜyDjM1XA~T)*W "˚#Ϊɹ&v,2>(ь3+HzBWbٽlӑ2F9f22<{7j`|Ӣ 6<ūh %#ƺO8^,YQW5qadrݷvE=w+T_;]g=&m:`au,a>/Mgn鶛e+g`ˏWSU\&tMHV'Ur+xSYr<-gkh]#vUi/^vHCnV1׳f伏A9`[ssn&E9h6[S)}>]/RtvwDpb1`%m/67~K.gVdMgx^#e쓦|rɢUxl6>B-?1vηf:WPsIaΚ[ t8l O}~s褟2k]go{9nŖ^H[zbf-SV~ ,i-_} ִvN0Ŏ˶?c\ [НѨ9/'n<5yƌNYF׷~ZxRMU2Ʌu<ǀC<=}NrYtsnN,'>Tv x n#4ѷ̣3YJ=3$U)mo//j%;…ΉK;?=1϶R Qܘ$P׺'okx]~杺ȧ'~e_*4iΎ%+l.(y#x?Y`ccdUIG__iɯ0!y2Xv< ,̌r<+0I(N]>Ô5Uuz/I>+>tiC rצ8w֊WT/s`69}8vacAc'$2PZ" 8%f¤`St~PK{x\ 9/=*.=Fp[Wj1:ƺ7PK9*PK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPK!:Z"Hs4 META-INF/MANIFEST.MFPK!:ile! META-INF/CERT.SFPK!:9* META-INF/CERT.DSAPKysrc/test/resources/com/android/apksig/v1-only-with-dsa-sha384-2.16.840.1.101.3.4.3.3-3072.apk0100644 0000000 0000000 00000012401 13243353143 026323 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:META-INF/MANIFEST.MFen@= Cj+Z4Sfu;>}1iퟜyDjM1XA~T)*W "˚#Ϊɹ&v,2>(ь3+HzBWbٽDG og;d84{c2Y op m8'CԍELYXq:uV)Bl]}4|Sy[ckGSK.o\>C8.r6Y%~Gi$n΁q2sJ2ޗ?0JUB!R$wXgrLE9zF}Mm :Ʊe)~" }Ӵ?RPB<`9狞̷r '9Qzd% G aOz׌/ZrmD. OcgfU8,r=KA^"-Lo/%R&ve#SUd+C'K)x񵷖FL3]fDX Y⓷͓uN(;16&UH FLJ䆜O>>@MY_CVcR("# |sI|a[gޖީET: UfӅiH[=FӋO0C.3}黴Lz?Ђ(B?T'ɖ-,MTjEhH,8o(R!y! +cS취 Y}ݸ(Ǘl6}Q {-WBajZ)zD*%'<<*=A;}_7.9!;3e, 8X'PKoPK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPK!:Z"Hs4 META-INF/MANIFEST.MFPK!:ile! META-INF/CERT.SFPK!:o META-INF/CERT.DSAPKyrsrc/test/resources/com/android/apksig/v1-only-with-dsa-sha512-2.16.840.1.101.3.4.3.4-1024.apk0100644 0000000 0000000 00000011030 13243353143 026305 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:META-INF/MANIFEST.MFeAo@; n*D+eeb]IH͛qM|M z-/6$>۝y} 7N9P$Svʉ)D]M|H|G 4v75W}SٶIaw0[+Үf2g 9{2{?̙}D칾%A"@9p[&_,|lq,0:eXXD[qϔVolg A,b 032GJ` :L"fm?+CNab#D%6",%rT\vBibB`6PʐS1frYX@ =lq-<ً20kzzۖ|w~ML? PK:fPK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPK!:l META-INF/MANIFEST.MFPK!:1j̬DY META-INF/CERT.SFPK!::fC META-INF/CERT.DSAPKysrc/test/resources/com/android/apksig/v1-only-with-dsa-sha512-2.16.840.1.101.3.4.3.4-2048.apk0100644 0000000 0000000 00000011763 13243353143 026331 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:META-INF/MANIFEST.MFeAo@; n*D+eeb]lӑ2F9f22<{7j`|Ӣ 6<ūh %#ƺO8^,YQW5qadrݷvE=w+T_;]g=&m:`au,a>/Mgn鶛e+g`ˏWSU\&tMHV'Ur+xSYr<-gkh]#vUi/^vHCnV1׳f伏A9`[ssn&E9h6[S)}>]/RtvwDpb1`%m/67~K.gVdMgx^#e쓦|rɢUxl6>B-?1vηf:WPsIaΚ[ t8l O}~s褟2k]go{9nŖ^H[zbf-SV~ ,i-_} ִvN0Ŏ˶?c\ [НѨ9/'n<5yƌNYF׷~ZxRMU2Ʌu<ǀC<=}NrYtsnN,'>Tv x n#4ѷ̣3YJ=3$U)mo//j%;…ΉK;?=1϶R Qܘ$P׺'okx]~杺ȧ'~e_*4iΎ%+l.(y#x?Y`ccdUIG__iɯ0!y2Xv< ,̌r<+0I(N]>Ô5Uuz/I>+>tiC rצ8w֊WT/s`69}8vacAc'$2PZ" 8%f¤T'ֳw,͝76wN޲TfdTҜyN+YlI.PKh*PK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPK!:l META-INF/MANIFEST.MFPK!:1j̬DY META-INF/CERT.SFPK!:h*C META-INF/CERT.DSAPKydsrc/test/resources/com/android/apksig/v1-only-with-dsa-sha512-2.16.840.1.101.3.4.3.4-3072.apk0100644 0000000 0000000 00000012601 13243353143 026317 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:META-INF/MANIFEST.MFeAo@; n*D+eeb]{ycL\} E@$=$Yy`uGKl; ,M`ɫҞq-t|0}*D!;k[u65CGdܺ)9lwOQ&7n40y ;z yEpV|-@'懙QLM$澧.Y&[v/CfJl3QC ^gMVI@nLjv (o\3˰n}7 j]\97]S cpwgQ$WX(MεՈHS+Oo}24)n,߻Ufn:.9U(w6yOvp#žKڌf=HHo2g9jߋgdV ư0&k,hr]2' [s.U8=P֫_',{2zGFJqG}L\&BVՑިC*IQKg_T'cGKG'%uu]d0n/S}ལ4*t-\/Y%R3%QZ5^K٩n(Ē^F %ESo;M/WiU kVBEf\O!E ޷<Q'9X|Ȓn!yyi8 ًCohvb9ITueŽ9{J7Cao1ilHQײtj0k/S7|o ~Fʸr07r#˰#K>~Kz;(R˾=-WH1_o .B[ mwidM-bq 9N&Mn_-C?$.08e%BMETA-INF/MANIFEST.MF]N@@= 0K]@E%qQVH4 ac^:#0ʹߋDM@+hE2R~: !Xx~6{H-*#5DQvy#h+Ҳ~Ԯ\p/1D[ݔwgq|R?0}? 4>]vR&SPPG#D ƌ@a#_ w}~7i˵PCPKp-IMETA-INF/CERT.SFmn@F&, ԖPA-iFr̠׶iښ9Y8âd qgReZ8*=f8A3})h,Ϥ0@gx-0wWOSyP+q6}i "YGȣhD3, @vΊaqo-` 3#,JOviHg&5SrPpaEext(r 1﹊/`4YEko)s~nuIO˦[\?wnߛ~PK}PKp-IMETA-INF/CERT.EC3hbgjhδIIѐۀUIqAcAc .`fbdbd8ſw".6s,LB|l̡,<ɺFfr⼆fƆ&fQ&P.V=l`S333;1,U:)<};/+פ][tn¥-NsOl~rr~Ps˖cc"l3@HUh3' A,b 032GfO7&E, r}".]8?9$Yqk@%?-i>@Zc/]z 1+;Xtka ylA( 6VwW&y&u[6u_+}{ʖ-GN+:9}| 3i:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexTMhA~l4i5?DvUMQXIi*ԣL7ki:AEEE衈Aăovf4T&߾7}fc`Wsdpn|[I.CՓ``~h G"tk|<(㨬|{wĞYxx9,b1C\@PDqqxxL^ľ0ܶ8DQ{!Z VumJ˹ /5TA_J{Hىa%c |!^6Cj߆XIp=n.Mdf#D'R׿| |BJZ$3"sf4HKTy/E}O`&Z7McA7vOqɛ\6OYܩ9|! }v϶4L{OP+Y8LWVYؾNr=tKvi̚1S<ìa,݄c̮(Mܴ"HIi% m,7fOR}jZeٞ7Zx@D e9ەLZ~0oϋlbSnCʣ5;c̲sa3dǃp6Z$]Z:bAxKhՓkT{Njuo_J w?WĿ]_|k?DUpB0HE рz %qu`###z~R|^32pFEOe*PKՇ PK!:>%BMETA-INF/MANIFEST.MFPKp-I}META-INF/CERT.SFPKp-IM*3cMETA-INF/CERT.ECPK !:!Ojresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  classes.dexPKxsrc/test/resources/com/android/apksig/v1-only-with-ecdsa-sha1-1.2.840.10045.2.1-p384.apk0100644 0000000 0000000 00000010013 13243353143 026172 0ustar000000000 0000000 PK!:>%BMETA-INF/MANIFEST.MF]N@@= 0K]@E%qQVH4 ac^:#0ʹߋDM@+hE2R~: !Xx~6{H-*#5DQvy#h+Ҳ~Ԯ\p/1D[ݔwgq|R?0}? 4>]vR&SPPG#D ƌ@a#_ w}~7i˵PCPK;q-IMETA-INF/CERT.SFmn@F&, ԖPA-iFr̠׶iښ9Y8âd qgReZ8*=f8A3})h,Ϥ0@gx-0wWOSyP+q6}i "YGȣhD3, @vΊaqo-` 3#,JOviHg&5SrPpaEext(r 1﹊/`4YEko)s~nuIO˦[\?wnߛ~PK}PK;q-IMETA-INF/CERT.EC3hbfjhδƠѐۀUIqAZ&ƕ@Hn3Y\l@lY  ؘCYySu -L y M LͣyM , ͡\z 21 ĜrDrN{ &ݭGaݖŻEgɜuԞ󚲰_즅= W놈ܞ՗*~.^eg|Y8@Y>1g}8'p3 A,b 032GL4&Cc׷ΐ< oAhr6~bUoT͘;?^N뛵\ P NKE)U3Hc͑ko}ٷχS} *En%6.7h\Syl}(Q sV4&C8z|2|m>ޑecS#sRr^7fN&Z7m^./>z~񮳦o5^+S2U.PK2/PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexTMhA~l4i5?DvUMQXIi*ԣL7ki:AEEE衈Aăovf4T&߾7}fc`Wsdpn|[I.CՓ``~h G"tk|<(㨬|{wĞYxx9,b1C\@PDqqxxL^ľ0ܶ8DQ{!Z VumJ˹ /5TA_J{Hىa%c |!^6Cj߆XIp=n.Mdf#D'R׿| |BJZ$3"sf4HKTy/E}O`&Z7McA7vOqɛ\6OYܩ9|! }v϶4L{OP+Y8LWVYؾNr=tKvi̚1S<ìa,݄c̮(Mܴ"HIi% m,7fOR}jZeٞ7Zx@D e9ەLZ~0oϋlbSnCʣ5;c̲sa3dǃp6Z$]Z:bAxKhՓkT{Njuo_J w?WĿ]_|k?DUpB0HE рz %qu`###z~R|^32pFEOe*PKՇ PK!:>%BMETA-INF/MANIFEST.MFPK;q-I}META-INF/CERT.SFPK;q-I2/cMETA-INF/CERT.ECPK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  7 classes.dexPKx}src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha1-1.2.840.10045.2.1-p521.apk0100644 0000000 0000000 00000010200 13243353143 026161 0ustar000000000 0000000 PK!:>%BMETA-INF/MANIFEST.MF]N@@= 0K]@E%qQVH4 ac^:#0ʹߋDM@+hE2R~: !Xx~6{H-*#5DQvy#h+Ҳ~Ԯ\p/1D[ݔwgq|R?0}? 4>]vR&SPPG#D ƌ@a#_ w}~7i˵PCPKIq-IMETA-INF/CERT.SFmn@F&, ԖPA-iFr̠׶iښ9Y8âd qgReZ8*=f8A3})h,Ϥ0@gx-0wWOSyP+q6}i "YGȣhD3, @vΊaqo-` 3#,JOviHg&5SrPpaEext(r 1﹊/`4YEko)s~nuIO˦[\?wnߛ~PK}PKIq-IMETA-INF/CERT.EC3hbƩiAk&Ll|LR E 41~LLL oKg)pqeaf120cceaaOM-052454306644564251604rim 6Fe6ʍST[k΍/Do=,h~]k[ OIf.:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexTMhA~l4i5?DvUMQXIi*ԣL7ki:AEEE衈Aăovf4T&߾7}fc`Wsdpn|[I.CՓ``~h G"tk|<(㨬|{wĞYxx9,b1C\@PDqqxxL^ľ0ܶ8DQ{!Z VumJ˹ /5TA_J{Hىa%c |!^6Cj߆XIp=n.Mdf#D'R׿| |BJZ$3"sf4HKTy/E}O`&Z7McA7vOqɛ\6OYܩ9|! }v϶4L{OP+Y8LWVYؾNr=tKvi̚1S<ìa,݄c̮(Mܴ"HIi% m,7fOR}jZeٞ7Zx@D e9ەLZ~0oϋlbSnCʣ5;c̲sa3dǃp6Z$]Z:bAxKhՓkT{Njuo_J w?WĿ]_|k?DUpB0HE рz %qu`###z~R|^32pFEOe*PKՇ PK!:>%BMETA-INF/MANIFEST.MFPKIq-I}META-INF/CERT.SFPKIq-IcMETA-INF/CERT.ECPK !:!OEresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  classes.dexPKxsrc/test/resources/com/android/apksig/v1-only-with-ecdsa-sha1-1.2.840.10045.4.1-p256.apk0100644 0000000 0000000 00000007601 13243353143 026203 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:META-INF/MANIFEST.MF]N@@= 0K]@E%qQVH4 ac^:#0ʹߋDM@+hE2R~: !Xx~6{H-*#5DQvy#h+Ҳ~Ԯ\p/1D[ݔwgq|R?0}? 4>]vR&SPPG#D ƌ@a#_ w}~7i˵PCPK>%BPK!:META-INF/CERT.SF]Mo@; am bJiBme_q|dڅ__bڴ6dIhQc8+ &Kk+-W< qo=ci֥>̃~f,9 p DYw=a5%(O,K,Ȫ(o>--lpE&<=Krx0ur*f< ]vR<ͨ߻= s>>bg7@Zc/]z 1+;Xtkf ylAH 6FwW5{Xc\v/[ҵCM_;bK*v\j Y%IIA~UZ~0,lز%B META-INF/MANIFEST.MFPK!:yU< META-INF/CERT.SFPK!:TI1 META-INF/CERT.ECPKx src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha1-1.2.840.10045.4.1-p384.apk0100644 0000000 0000000 00000007750 13243353143 026212 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:META-INF/MANIFEST.MF]N@@= 0K]@E%qQVH4 ac^:#0ʹߋDM@+hE2R~: !Xx~6{H-*#5DQvy#h+Ҳ~Ԯ\p/1D[ݔwgq|R?0}? 4>]vR&SPPG#D ƌ@a#_ w}~7i˵PCPK>%BPK!:META-INF/CERT.SF]Mo@; am bJiBme_q|dڅ__bڴ6dIhQc8+ &Kk+-W< qo=ci֥>̃~f,9 p DYw=a5%(O,K,Ȫ(o>--lpE&<=Krx0ur*f< ]vR<ͨ߻= s>>bg71g}8'p3 A,b 032GL4&Cc׷ΐ< oAhr6~bUoT͘;?^N뛵\ P NKE)U3Hc͑ko}ٷχS} *En%6.3h\ Syl}HQ sFtTŖ yn(p={ˌD%B META-INF/MANIFEST.MFPK!:yU< META-INF/CERT.SFPK!:/1 META-INF/CERT.ECPKxZsrc/test/resources/com/android/apksig/v1-only-with-ecdsa-sha1-1.2.840.10045.4.1-p521.apk0100644 0000000 0000000 00000010133 13243353143 026170 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:META-INF/MANIFEST.MF]N@@= 0K]@E%qQVH4 ac^:#0ʹߋDM@+hE2R~: !Xx~6{H-*#5DQvy#h+Ҳ~Ԯ\p/1D[ݔwgq|R?0}? 4>]vR&SPPG#D ƌ@a#_ w}~7i˵PCPK>%BPK!:META-INF/CERT.SF]Mo@; am bJiBme_q|dڅ__bڴ6dIhQc8+ &Kk+-W< qo=ci֥>̃~f,9 p DYw=a5%(O,K,Ȫ(o>--lpE&<=Krx0ur*f< ]vR<ͨ߻= s>>bg7OۏQ s^܋Q|SB ֝\®&'?L۲hV^bPKa71PK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPK!:>%B META-INF/MANIFEST.MFPK!:yU< META-INF/CERT.SFPK!:a71 META-INF/CERT.ECPKxsrc/test/resources/com/android/apksig/v1-only-with-ecdsa-sha224-1.2.840.10045.2.1-p256.apk0100644 0000000 0000000 00000010026 13243353143 026343 0ustar000000000 0000000 PK!: QMETA-INF/MANIFEST.MFeMK@X,~B?6jY5 -L6323ۯ"( jǔ1jxfڎ@^hzD~ײ$K)2a+k]nvm?!PV|Xx% ޤeՒ74CNA\7~"HL1-NݜQG;%9 jW@Zc/]z 1+;XtkkylA1 :V{Y6|ߵ-jg=K>_- wAO:a?PúzZSX٥7N>Ƿa܅<PKJ7So<PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexTMhA~l4i5?DvUMQXIi*ԣL7ki:AEEE衈Aăovf4T&߾7}fc`Wsdpn|[I.CՓ``~h G"tk|<(㨬|{wĞYxx9,b1C\@PDqqxxL^ľ0ܶ8DQ{!Z VumJ˹ /5TA_J{Hىa%c |!^6Cj߆XIp=n.Mdf#D'R׿| |BJZ$3"sf4HKTy/E}O`&Z7McA7vOqɛ\6OYܩ9|! }v϶4L{OP+Y8LWVYؾNr=tKvi̚1S<ìa,݄c̮(Mܴ"HIi% m,7fOR}jZeٞ7Zx@D e9ەLZ~0oϋlbSnCʣ5;c̲sa3dǃp6Z$]Z:bAxKhՓkT{Njuo_J w?WĿ]_|k?DUpB0HE рz %qu`###z~R|^32pFEOe*PKՇ PK!: QMETA-INF/MANIFEST.MFPKr-IP/%e/META-INF/CERT.SFPKr-IJ7So<META-INF/CERT.ECPK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  B classes.dexPKxsrc/test/resources/com/android/apksig/v1-only-with-ecdsa-sha224-1.2.840.10045.2.1-p384.apk0100644 0000000 0000000 00000010200 13243353143 026337 0ustar000000000 0000000 PK!: QMETA-INF/MANIFEST.MFeMK@X,~B?6jY5 -L6323ۯ"( jǔ1jxfڎ@^hzD~ײ$K)2a+k]nvm?!PV|Xx% ޤeՒ74CNA\7~"HL1-NݜQG;%9 jW _]8fe ie  x@ YX #s&A!ڱ[guӠW49uҍKvmΪof̝}/U{Zb"[E攪K$~1hjµܾ|{ߩ>" W4ƫh;+KA ϋ]7]R>5 Wʔ^-2&Wv˛7Nvz`| g/j,إ|tWWMX|mצZF7PK/5PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexTMhA~l4i5?DvUMQXIi*ԣL7ki:AEEE衈Aăovf4T&߾7}fc`Wsdpn|[I.CՓ``~h G"tk|<(㨬|{wĞYxx9,b1C\@PDqqxxL^ľ0ܶ8DQ{!Z VumJ˹ /5TA_J{Hىa%c |!^6Cj߆XIp=n.Mdf#D'R׿| |BJZ$3"sf4HKTy/E}O`&Z7McA7vOqɛ\6OYܩ9|! }v϶4L{OP+Y8LWVYؾNr=tKvi̚1S<ìa,݄c̮(Mܴ"HIi% m,7fOR}jZeٞ7Zx@D e9ەLZ~0oϋlbSnCʣ5;c̲sa3dǃp6Z$]Z:bAxKhՓkT{Njuo_J w?WĿ]_|k?DUpB0HE рz %qu`###z~R|^32pFEOe*PKՇ PK!: QMETA-INF/MANIFEST.MFPK s-IP/%e/META-INF/CERT.SFPK s-I/5META-INF/CERT.ECPK !:!OEresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  classes.dexPKxsrc/test/resources/com/android/apksig/v1-only-with-ecdsa-sha224-1.2.840.10045.2.1-p521.apk0100644 0000000 0000000 00000010364 13243353143 026343 0ustar000000000 0000000 PK!: QMETA-INF/MANIFEST.MFeMK@X,~B?6jY5 -L6323ۯ"( jǔ1jxfڎ@^hzD~ײ$K)2a+k]nvm?!PV|Xx% ޤeՒ74CNA\7~"HL1-NݜQG;%9 jW:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexTMhA~l4i5?DvUMQXIi*ԣL7ki:AEEE衈Aăovf4T&߾7}fc`Wsdpn|[I.CՓ``~h G"tk|<(㨬|{wĞYxx9,b1C\@PDqqxxL^ľ0ܶ8DQ{!Z VumJ˹ /5TA_J{Hىa%c |!^6Cj߆XIp=n.Mdf#D'R׿| |BJZ$3"sf4HKTy/E}O`&Z7McA7vOqɛ\6OYܩ9|! }v϶4L{OP+Y8LWVYؾNr=tKvi̚1S<ìa,݄c̮(Mܴ"HIi% m,7fOR}jZeٞ7Zx@D e9ەLZ~0oϋlbSnCʣ5;c̲sa3dǃp6Z$]Z:bAxKhՓkT{Njuo_J w?WĿ]_|k?DUpB0HE рz %qu`###z~R|^32pFEOe*PKՇ PK!: QMETA-INF/MANIFEST.MFPKWs-IP/%e/META-INF/CERT.SFPKWs-I sթMETA-INF/CERT.ECPK !:!Oresources.arscPK!:^avi AndroidManifest.xmlPK!:Շ  classes.dexPKxfsrc/test/resources/com/android/apksig/v1-only-with-ecdsa-sha224-1.2.840.10045.4.3.1-p256.apk0100644 0000000 0000000 00000007736 13243353143 026524 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:META-INF/MANIFEST.MFeMK@X,~B?6jY5 -L6323ۯ"( jǔ1jxfڎ@^hzD~ײ$K)2a+k]nvm?!PV|Xx% ޤeՒ74CNA\7~"HL1-NݜQG;%9 jW@Zc/]z 1+;XtkmylAh1p#+Юe}˜r&Aܸd{ϴ~~*ʘ+ˤ[gK+N^p3ыPKN|W:PK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPK!: Q META-INF/MANIFEST.MFPK!:.Y META-INF/CERT.SFPK!:N|W:F META-INF/CERT.ECPKxPsrc/test/resources/com/android/apksig/v1-only-with-ecdsa-sha224-1.2.840.10045.4.3.1-p384.apk0100644 0000000 0000000 00000010104 13243353143 026505 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:META-INF/MANIFEST.MFeMK@X,~B?6jY5 -L6323ۯ"( jǔ1jxfڎ@^hzD~ײ$K)2a+k]nvm?!PV|Xx% ޤeՒ74CNA\7~"HL1-NݜQG;%9 jW _]8fe ie  x@ YX #s&A!ڱ[guӠW49uҍKvmΪof̝}/U{Zb"[E攪K$~1hjµܾ|{ߩ>" W4ƫhG% "!caNS:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:META-INF/MANIFEST.MFeMK@X,~B?6jY5 -L6323ۯ"( jǔ1jxfڎ@^hzD~ײ$K)2a+k]nvm?!PV|Xx% ޤeՒ74CNA\7~"HL1-NݜQG;%9 jW9VywO'eth Vf0k@Հ?r2704v091&ʷs'W$.Ȫ}6-JJsk^Kj?5DֹlĨTܞ,I_usڳPWf;yf Q6,XpAg)@[L!'#KcAc;[љ ǺwO{]*F拃 [%}Yѐ2_' +e|;ifߞ@Zc/]z 1+;XtkkylA1 :V{2 Q/5l;S31mxAVT*ue=BSIo^+d m&PK<PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexTMhA~l4i5?DvUMQXIi*ԣL7ki:AEEE衈Aăovf4T&߾7}fc`Wsdpn|[I.CՓ``~h G"tk|<(㨬|{wĞYxx9,b1C\@PDqqxxL^ľ0ܶ8DQ{!Z VumJ˹ /5TA_J{Hىa%c |!^6Cj߆XIp=n.Mdf#D'R׿| |BJZ$3"sf4HKTy/E}O`&Z7McA7vOqɛ\6OYܩ9|! }v϶4L{OP+Y8LWVYؾNr=tKvi̚1S<ìa,݄c̮(Mܴ"HIi% m,7fOR}jZeٞ7Zx@D e9ەLZ~0oϋlbSnCʣ5;c̲sa3dǃp6Z$]Z:bAxKhՓkT{Njuo_J w?WĿ]_|k?DUpB0HE рz %qu`###z~R|^32pFEOe*PKՇ PK!: QMETA-INF/MANIFEST.MFPKt-IP/%e/META-INF/CERT.SFPKt-I<META-INF/CERT.ECPK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  A classes.dexPKxsrc/test/resources/com/android/apksig/v1-only-with-ecdsa-sha256-1.2.840.10045.2.1-p384.apk0100644 0000000 0000000 00000010177 13243353143 026361 0ustar000000000 0000000 PK!: QMETA-INF/MANIFEST.MFeMK@X,~B?6jY5 -L6323ۯ"( jǔ1jxfڎ@^hzD~ײ$K)2a+k]nvm?!PV|Xx% ޤeՒ74CNA\7~"HL1-NݜQG;%9 jW _]8fe ie  x@ YX #s&A!ڱ[guӠW49uҍKvmΪof̝}/U{Zb"[E攪K$~1hjµܾ|{ߩ>" 4ƫh;+KA*[W~OfXUMĖ|*:{tS^r7ۡ@΋(>.qo; bz'nN:®E/{% PK4PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexTMhA~l4i5?DvUMQXIi*ԣL7ki:AEEE衈Aăovf4T&߾7}fc`Wsdpn|[I.CՓ``~h G"tk|<(㨬|{wĞYxx9,b1C\@PDqqxxL^ľ0ܶ8DQ{!Z VumJ˹ /5TA_J{Hىa%c |!^6Cj߆XIp=n.Mdf#D'R׿| |BJZ$3"sf4HKTy/E}O`&Z7McA7vOqɛ\6OYܩ9|! }v϶4L{OP+Y8LWVYؾNr=tKvi̚1S<ìa,݄c̮(Mܴ"HIi% m,7fOR}jZeٞ7Zx@D e9ەLZ~0oϋlbSnCʣ5;c̲sa3dǃp6Z$]Z:bAxKhՓkT{Njuo_J w?WĿ]_|k?DUpB0HE рz %qu`###z~R|^32pFEOe*PKՇ PK!: QMETA-INF/MANIFEST.MFPKt-IP/%e/META-INF/CERT.SFPKt-I4META-INF/CERT.ECPK !:!ODresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  classes.dexPKxsrc/test/resources/com/android/apksig/v1-only-with-ecdsa-sha256-1.2.840.10045.2.1-p521.apk0100644 0000000 0000000 00000010361 13243353143 026345 0ustar000000000 0000000 PK!: QMETA-INF/MANIFEST.MFeMK@X,~B?6jY5 -L6323ۯ"( jǔ1jxfڎ@^hzD~ײ$K)2a+k]nvm?!PV|Xx% ޤeՒ74CNA\7~"HL1-NݜQG;%9 jW9VywO'eth Vf0k@Հ?r2704v091&ʷs'W$.Ȫ}6-JJsk^Kj?5DֹlĨTܞ,I_usڳPWf;yf Q6,XpAƋ)@[L!'@Vnw/>6QIDPK2约PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexTMhA~l4i5?DvUMQXIi*ԣL7ki:AEEE衈Aăovf4T&߾7}fc`Wsdpn|[I.CՓ``~h G"tk|<(㨬|{wĞYxx9,b1C\@PDqqxxL^ľ0ܶ8DQ{!Z VumJ˹ /5TA_J{Hىa%c |!^6Cj߆XIp=n.Mdf#D'R׿| |BJZ$3"sf4HKTy/E}O`&Z7McA7vOqɛ\6OYܩ9|! }v϶4L{OP+Y8LWVYؾNr=tKvi̚1S<ìa,݄c̮(Mܴ"HIi% m,7fOR}jZeٞ7Zx@D e9ەLZ~0oϋlbSnCʣ5;c̲sa3dǃp6Z$]Z:bAxKhՓkT{Njuo_J w?WĿ]_|k?DUpB0HE рz %qu`###z~R|^32pFEOe*PKՇ PK!: QMETA-INF/MANIFEST.MFPKt-IP/%e/META-INF/CERT.SFPKt-I2约META-INF/CERT.ECPK !:!Oresources.arscPK!:^avf AndroidManifest.xmlPK!:Շ   classes.dexPKxcsrc/test/resources/com/android/apksig/v1-only-with-ecdsa-sha256-1.2.840.10045.4.3.2-p256.apk0100644 0000000 0000000 00000007733 13243353143 026527 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:META-INF/MANIFEST.MFeMK@X,~B?6jY5 -L6323ۯ"( jǔ1jxfڎ@^hzD~ײ$K)2a+k]nvm?!PV|Xx% ޤeՒ74CNA\7~"HL1-NݜQG;%9 jW@Zc/]z 1+;Xtkc ylAh1s^XKiAk6ɪ,zz[VyFʿ -.Z{FbOWjŒ7lҮPK5;PK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPK!: Q META-INF/MANIFEST.MFPK!:.Y META-INF/CERT.SFPK!:5;F META-INF/CERT.ECPKxMsrc/test/resources/com/android/apksig/v1-only-with-ecdsa-sha256-1.2.840.10045.4.3.2-p384.apk0100644 0000000 0000000 00000010106 13243353143 026515 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:META-INF/MANIFEST.MFeMK@X,~B?6jY5 -L6323ۯ"( jǔ1jxfڎ@^hzD~ײ$K)2a+k]nvm?!PV|Xx% ޤeՒ74CNA\7~"HL1-NݜQG;%9 jW _]8fe ie  x@ YX #s&A!ڱ[guӠW49uҍKvmΪof̝}/U{Zb"[E攪K$~1hjµܾ|{ߩ>" W4.ƫhG% @ɓ M7*Ϸfwcv] ya袠ŭN+f>%uLWT]M7奾hԜ^;잋W{7%aM_ƳgvN]E?PKIk4PK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPK!: Q META-INF/MANIFEST.MFPK!:.Y META-INF/CERT.SFPK!:Ik4F META-INF/CERT.ECPKxsrc/test/resources/com/android/apksig/v1-only-with-ecdsa-sha256-1.2.840.10045.4.3.2-p521.apk0100644 0000000 0000000 00000010270 13243353143 026510 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:META-INF/MANIFEST.MFeMK@X,~B?6jY5 -L6323ۯ"( jǔ1jxfڎ@^hzD~ײ$K)2a+k]nvm?!PV|Xx% ޤeՒ74CNA\7~"HL1-NݜQG;%9 jW9VywO'eth Vf0k@Հ?r2704v091&ʷs'W$.Ȫ}6-JJsk^Kj?5DֹlĨTܞ,I_usڳPWf;yf Q6,XpAg)@[L!'KcAc;c ֜;hkzx3=+*l|sLU>a"P3z KDםQܬa0TWUj[+y@&M_+vFw;NșRKdUi> PKPK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPK!: Q META-INF/MANIFEST.MFPK!:.Y META-INF/CERT.SFPK!:F META-INF/CERT.ECPKx*src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha384-1.2.840.10045.2.1-p256.apk0100644 0000000 0000000 00000010245 13243353143 026355 0ustar000000000 0000000 PK!:Z"Hs4META-INF/MANIFEST.MFen@= Cj+Z4Sfu;>}1iퟜyDjM1XA~T)*W "˚#Ϊɹ&v,2>(ь3+HzBWbٽǎNwD^IwܚevT;7y.(LE p$mo%<7FW\yn%wIt+{ʰS$vuoo,}Bz*O)ΦYMȞSbޕ@J쾾f{uэ]? R.Ǘ7KPK?ӤcPKu-IMETA-INF/CERT.EC3hb`jhδIӠIѐ߀3̓1qAcAc .`fbdbd8ſw".6s,LB|l̡,<ɺFfr⼆fƆ&fQ&P.V=l`S333;1,U:)<};/+פ][tn¥-NsOl~rr~Ps˖cc"l3@HUh3' A,b 032GfO7&E, r}".]8?9$Yqk@%?-i>@Zc/]z 1+;XtkkylA1 :V{Vo^zί> =o]|"{\xE'?spf7:bw˗v}PK <PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexTMhA~l4i5?DvUMQXIi*ԣL7ki:AEEE衈Aăovf4T&߾7}fc`Wsdpn|[I.CՓ``~h G"tk|<(㨬|{wĞYxx9,b1C\@PDqqxxL^ľ0ܶ8DQ{!Z VumJ˹ /5TA_J{Hىa%c |!^6Cj߆XIp=n.Mdf#D'R׿| |BJZ$3"sf4HKTy/E}O`&Z7McA7vOqɛ\6OYܩ9|! }v϶4L{OP+Y8LWVYؾNr=tKvi̚1S<ìa,݄c̮(Mܴ"HIi% m,7fOR}jZeٞ7Zx@D e9ەLZ~0oϋlbSnCʣ5;c̲sa3dǃp6Z$]Z:bAxKhՓkT{Njuo_J w?WĿ]_|k?DUpB0HE рz %qu`###z~R|^32pFEOe*PKՇ PK!:Z"Hs4META-INF/MANIFEST.MFPKu-I?ӤcfMETA-INF/CERT.SFPKu-I <`META-INF/CERT.ECPK !:!Ojresources.arscPK!:^av AndroidManifest.xmlPK!:Շ  classes.dexPKxsrc/test/resources/com/android/apksig/v1-only-with-ecdsa-sha384-1.2.840.10045.2.1-p384.apk0100644 0000000 0000000 00000010415 13243353143 026356 0ustar000000000 0000000 PK!:Z"Hs4META-INF/MANIFEST.MFen@= Cj+Z4Sfu;>}1iퟜyDjM1XA~T)*W "˚#Ϊɹ&v,2>(ь3+HzBWbٽǎNwD^IwܚevT;7y.(LE p$mo%<7FW\yn%wIt+{ʰS$vuoo,}Bz*O)ΦYMȞSbޕ@J쾾f{uэ]? R.Ǘ7KPK?ӤcPKMu-IMETA-INF/CERT.EC3hbƩiASAS#!/gBc*3 +7B!&ƵM+Xo3#'Ñ0Jg 8 ٲ03 10&[ȉGXCXMebdcndaPbNb`~"tl#0Nnyݢ3d:jJjyMYXd/ U[muvBmLuCDǿOnKrSmwE3b\`g t,> _]8fe ie  x@ YX #s&A!ڱ[guӠW49uҍKvmΪof̝}/U{Zb"[E攪K$~1hjµܾ|{ߩ>" W4ƫh;+KA 2QR_~2|,cxi傫BLjlyzMǮLߌ5 ߸ħ?[jNb&=E5 ]f13Vh޻<PK?/4PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexTMhA~l4i5?DvUMQXIi*ԣL7ki:AEEE衈Aăovf4T&߾7}fc`Wsdpn|[I.CՓ``~h G"tk|<(㨬|{wĞYxx9,b1C\@PDqqxxL^ľ0ܶ8DQ{!Z VumJ˹ /5TA_J{Hىa%c |!^6Cj߆XIp=n.Mdf#D'R׿| |BJZ$3"sf4HKTy/E}O`&Z7McA7vOqɛ\6OYܩ9|! }v϶4L{OP+Y8LWVYؾNr=tKvi̚1S<ìa,݄c̮(Mܴ"HIi% m,7fOR}jZeٞ7Zx@D e9ەLZ~0oϋlbSnCʣ5;c̲sa3dǃp6Z$]Z:bAxKhՓkT{Njuo_J w?WĿ]_|k?DUpB0HE рz %qu`###z~R|^32pFEOe*PKՇ PK!:Z"Hs4META-INF/MANIFEST.MFPKMu-I?ӤcfMETA-INF/CERT.SFPKMu-I?/4`META-INF/CERT.ECPK !:!Oresources.arscPK!:^av AndroidManifest.xmlPK!:Շ  9 classes.dexPKxsrc/test/resources/com/android/apksig/v1-only-with-ecdsa-sha384-1.2.840.10045.2.1-p521.apk0100644 0000000 0000000 00000010576 13243353143 026357 0ustar000000000 0000000 PK!:Z"Hs4META-INF/MANIFEST.MFen@= Cj+Z4Sfu;>}1iퟜyDjM1XA~T)*W "˚#Ϊɹ&v,2>(ь3+HzBWbٽǎNwD^IwܚevT;7y.(LE p$mo%<7FW\yn%wIt+{ʰS$vuoo,}Bz*O)ΦYMȞSbޕ@J쾾f{uэ]? R.Ǘ7KPK?ӤcPKuu-IMETA-INF/CERT.EC3hbffjhδA&FFC~^6΄6Tf&&VnBM ?qf&F&&N7%˔ 8 ٲ0 10&ȉEXCX46`cƪ X_Q ׂ<rj}~ʝ~KùEǒ_ͯkwk8ɲle,_1/6UʅDfjfry ^~vyD:~cG}B,^mq,Ų|,b,"]>9VywO'eth Vf0k@Հ?r2704v091&ʷs'W$.Ȫ}6-JJsk^Kj?5DֹlĨTܞ,I_usڳPWf;yf Q6,XpAƋ)@[L!'@Vnw3ȏqz0lIQ$[-E5~k0Z7WI'lӻxs˾$+EosI4zbI-3Λuj+AY^3Ăj+93%yLigqPK4PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexTMhA~l4i5?DvUMQXIi*ԣL7ki:AEEE衈Aăovf4T&߾7}fc`Wsdpn|[I.CՓ``~h G"tk|<(㨬|{wĞYxx9,b1C\@PDqqxxL^ľ0ܶ8DQ{!Z VumJ˹ /5TA_J{Hىa%c |!^6Cj߆XIp=n.Mdf#D'R׿| |BJZ$3"sf4HKTy/E}O`&Z7McA7vOqɛ\6OYܩ9|! }v϶4L{OP+Y8LWVYؾNr=tKvi̚1S<ìa,݄c̮(Mܴ"HIi% m,7fOR}jZeٞ7Zx@D e9ەLZ~0oϋlbSnCʣ5;c̲sa3dǃp6Z$]Z:bAxKhՓkT{Njuo_J w?WĿ]_|k?DUpB0HE рz %qu`###z~R|^32pFEOe*PKՇ PK!:Z"Hs4META-INF/MANIFEST.MFPKuu-I?ӤcfMETA-INF/CERT.SFPKuu-I4`META-INF/CERT.ECPK !:!OCresources.arscPK!:^av AndroidManifest.xmlPK!:Շ  classes.dexPKxsrc/test/resources/com/android/apksig/v1-only-with-ecdsa-sha384-1.2.840.10045.4.3.3-p256.apk0100644 0000000 0000000 00000010133 13243353143 026516 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:META-INF/MANIFEST.MFen@= Cj+Z4Sfu;>}1iퟜyDjM1XA~T)*W "˚#Ϊɹ&v,2>(ь3+HzBWbٽ@Zc/]z 1+;XtkeylAh1p3 ߠ3OptPvQj: ٘lbl{7g/]??3.>=PK:k9PK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPK!:Z"Hs4 META-INF/MANIFEST.MFPK!:ile! META-INF/CERT.SFPK!::k9 META-INF/CERT.ECPKxsrc/test/resources/com/android/apksig/v1-only-with-ecdsa-sha384-1.2.840.10045.4.3.3-p384.apk0100644 0000000 0000000 00000010302 13243353143 026516 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:META-INF/MANIFEST.MFen@= Cj+Z4Sfu;>}1iퟜyDjM1XA~T)*W "˚#Ϊɹ&v,2>(ь3+HzBWbٽ _]8fe ie  x@ YX #s&A!ڱ[guӠW49uҍKvmΪof̝}/U{Zb"[E攪K$~1hjµܾ|{ߩ>" W4ƫhGr/KA*Vg{vܙX=ìLhX8caTۋ :r <$Lͫ޿Owo*=vmc*I>zˣ?S1PK>2PK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPK!:Z"Hs4 META-INF/MANIFEST.MFPK!:ile! META-INF/CERT.SFPK!:>2 META-INF/CERT.ECPKx4src/test/resources/com/android/apksig/v1-only-with-ecdsa-sha384-1.2.840.10045.4.3.3-p521.apk0100644 0000000 0000000 00000010467 13243353143 026523 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:META-INF/MANIFEST.MFen@= Cj+Z4Sfu;>}1iퟜyDjM1XA~T)*W "˚#Ϊɹ&v,2>(ь3+HzBWbٽ›mr}lr'Ο0y)²6n: k!9OFj>t(|h=Zܑ;;ޥw,sVOPKPK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPK!:Z"Hs4 META-INF/MANIFEST.MFPK!:ile! META-INF/CERT.SFPK!: META-INF/CERT.ECPKxsrc/test/resources/com/android/apksig/v1-only-with-ecdsa-sha512-1.2.840.10045.2.1-p256.apk0100644 0000000 0000000 00000010464 13243353143 026351 0ustar000000000 0000000 PK!:lMETA-INF/MANIFEST.MFeAo@; n*D+eeb]Ӿ*br17 nc|3(P i^-DX6vΙ 3O`ȻܾN[YY\í7ۦ#)lgʑ,lcB;pshxoUBDFn! ˉ,Y. 3mBIڔw4ni8:ۀ^a1Jtffnz}AWZ`oO ?7PKn&PKu-IMETA-INF/CERT.EC3hb2cjhδIݠIѐ߀3̓1qAcAc .`fbdbd8ſw".6s,LB|l̡,<ɺFfr⼆fƆ&fQ&P.V=l`S333;1,U:)<};/+פ][tn¥-NsOl~rr~Ps˖cc"l3@HUh3' A,b 032GfO7&E, r}".]8?9$Yqk@%?-i>@Zc/]z 1+;XtkmylA1 :V7&/HX˾u&]}uG<$vdS&^ml2[$prf[t6 PKVoj:PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexTMhA~l4i5?DvUMQXIi*ԣL7ki:AEEE衈Aăovf4T&߾7}fc`Wsdpn|[I.CՓ``~h G"tk|<(㨬|{wĞYxx9,b1C\@PDqqxxL^ľ0ܶ8DQ{!Z VumJ˹ /5TA_J{Hىa%c |!^6Cj߆XIp=n.Mdf#D'R׿| |BJZ$3"sf4HKTy/E}O`&Z7McA7vOqɛ\6OYܩ9|! }v϶4L{OP+Y8LWVYؾNr=tKvi̚1S<ìa,݄c̮(Mܴ"HIi% m,7fOR}jZeٞ7Zx@D e9ەLZ~0oϋlbSnCʣ5;c̲sa3dǃp6Z$]Z:bAxKhՓkT{Njuo_J w?WĿ]_|k?DUpB0HE рz %qu`###z~R|^32pFEOe*PKՇ PK!:lMETA-INF/MANIFEST.MFPKu-In&META-INF/CERT.SFPKu-IVoj:META-INF/CERT.ECPK !:!Oresources.arscPK!:^av AndroidManifest.xmlPK!:Շ  ` classes.dexPKxsrc/test/resources/com/android/apksig/v1-only-with-ecdsa-sha512-1.2.840.10045.2.1-p384.apk0100644 0000000 0000000 00000010636 13243353143 026354 0ustar000000000 0000000 PK!:lMETA-INF/MANIFEST.MFeAo@; n*D+eeb]Ӿ*br17 nc|3(P i^-DX6vΙ 3O`ȻܾN[YY\í7ۦ#)lgʑ,lcB;pshxoUBDFn! ˉ,Y. 3mBIڔw4ni8:ۀ^a1Jtffnz}AWZ`oO ?7PKn&PKu-IMETA-INF/CERT.EC3hbƩiASAS##!/gBc*3 3+7B!&ƵM+Xo3#'Ñ0Jg 8 ٲ03 10&[ȉGXCXMebdcndaPbNb`~"tl#0Nnyݢ3d:jJjyMYXd/ U[muvBmLuCDǿOnKrSmwE3b\`g t,> _]8fe ie  x@ YX #s&A!ڱ[guӠW49uҍKvmΪof̝}/U{Zb"[E攪K$~1hjµܾ|{ߩ>" 4ƫh;+KA*;z ؎%}tּ77/ee4;q 7$zv{V2teW1_K<=mN;Vlaљkqv1O&MPKUS6PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexTMhA~l4i5?DvUMQXIi*ԣL7ki:AEEE衈Aăovf4T&߾7}fc`Wsdpn|[I.CՓ``~h G"tk|<(㨬|{wĞYxx9,b1C\@PDqqxxL^ľ0ܶ8DQ{!Z VumJ˹ /5TA_J{Hىa%c |!^6Cj߆XIp=n.Mdf#D'R׿| |BJZ$3"sf4HKTy/E}O`&Z7McA7vOqɛ\6OYܩ9|! }v϶4L{OP+Y8LWVYؾNr=tKvi̚1S<ìa,݄c̮(Mܴ"HIi% m,7fOR}jZeٞ7Zx@D e9ەLZ~0oϋlbSnCʣ5;c̲sa3dǃp6Z$]Z:bAxKhՓkT{Njuo_J w?WĿ]_|k?DUpB0HE рz %qu`###z~R|^32pFEOe*PKՇ PK!:lMETA-INF/MANIFEST.MFPKu-In&META-INF/CERT.SFPKu-IUS6META-INF/CERT.ECPK !:!Ocresources.arscPK!:^av AndroidManifest.xmlPK!:Շ  classes.dexPKxsrc/test/resources/com/android/apksig/v1-only-with-ecdsa-sha512-1.2.840.10045.2.1-p521.apk0100644 0000000 0000000 00000011022 13243353143 026333 0ustar000000000 0000000 PK!:lMETA-INF/MANIFEST.MFeAo@; n*D+eeb]Ӿ*br17 nc|3(P i^-DX6vΙ 3O`ȻܾN[YY\í7ۦ#)lgʑ,lcB;pshxoUBDFn! ˉ,Y. 3mBIڔw4ni8:ۀ^a1Jtffnz}AWZ`oO ?7PKn&PKu-IMETA-INF/CERT.EC3hbfbjhδA{&FFC~^6΄6Tf&fVnBM C03121q2 /֟]dTxΖ@P9=5Y@N(JŪq;X&F6VFe6ʍST[k΍/Do=,h~]k[ OIf. 5wPK 1PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexTMhA~l4i5?DvUMQXIi*ԣL7ki:AEEE衈Aăovf4T&߾7}fc`Wsdpn|[I.CՓ``~h G"tk|<(㨬|{wĞYxx9,b1C\@PDqqxxL^ľ0ܶ8DQ{!Z VumJ˹ /5TA_J{Hىa%c |!^6Cj߆XIp=n.Mdf#D'R׿| |BJZ$3"sf4HKTy/E}O`&Z7McA7vOqɛ\6OYܩ9|! }v϶4L{OP+Y8LWVYؾNr=tKvi̚1S<ìa,݄c̮(Mܴ"HIi% m,7fOR}jZeٞ7Zx@D e9ەLZ~0oϋlbSnCʣ5;c̲sa3dǃp6Z$]Z:bAxKhՓkT{Njuo_J w?WĿ]_|k?DUpB0HE рz %qu`###z~R|^32pFEOe*PKՇ PK!:lMETA-INF/MANIFEST.MFPKu-In&META-INF/CERT.SFPKu-I 1META-INF/CERT.ECPK !:!Oresources.arscPK!:^av AndroidManifest.xmlPK!:Շ  > classes.dexPKxsrc/test/resources/com/android/apksig/v1-only-with-ecdsa-sha512-1.2.840.10045.4.3.4-p256.apk0100644 0000000 0000000 00000010332 13243353143 026511 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:META-INF/MANIFEST.MFeAo@; n*D+eeb]@Zc/]z 1+;XtkeylAh1p B!CK-.wv˧/Ddص挽"&ߤU\ʦsWS-nPKk9PK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPK!:l META-INF/MANIFEST.MFPK!:1j̬DY META-INF/CERT.SFPK!:k9C META-INF/CERT.ECPKxLsrc/test/resources/com/android/apksig/v1-only-with-ecdsa-sha512-1.2.840.10045.4.3.4-p384.apk0100644 0000000 0000000 00000010500 13243353143 026510 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:META-INF/MANIFEST.MFeAo@; n*D+eeb] _]8fe ie  x@ YX #s&A!ڱ[guӠW49uҍKvmΪof̝}/U{Zb"[E攪K$~1hjµܾ|{ߩ>" 4ƫhG%l^UiE=>\\X6k_=\ڗ/{IdF>v:rPK NϬv_]xIb Z*a<-cfǮPK1PK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPK!:l META-INF/MANIFEST.MFPK!:1j̬DY META-INF/CERT.SFPK!:1C META-INF/CERT.ECPKxsrc/test/resources/com/android/apksig/v1-only-with-ecdsa-sha512-1.2.840.10045.4.3.4-p521.apk0100644 0000000 0000000 00000010664 13243353143 026514 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:META-INF/MANIFEST.MFeAo@; n*D+eeb]:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexTMhA~l4i5?DvUMQXIi*ԣL7ki:AEEE衈Aăovf4T&߾7}fc`Wsdpn|[I.CՓ``~h G"tk|<(㨬|{wĞYxx9,b1C\@PDqqxxL^ľ0ܶ8DQ{!Z VumJ˹ /5TA_J{Hىa%c |!^6Cj߆XIp=n.Mdf#D'R׿| |BJZ$3"sf4HKTy/E}O`&Z7McA7vOqɛ\6OYܩ9|! }v϶4L{OP+Y8LWVYؾNr=tKvi̚1S<ìa,݄c̮(Mܴ"HIi% m,7fOR}jZeٞ7Zx@D e9ەLZ~0oϋlbSnCʣ5;c̲sa3dǃp6Z$]Z:bAxKhՓkT{Njuo_J w?WĿ]_|k?DUpB0HE рz %qu`###z~R|^32pFEOe*PKՇ PK&sJ test.txt HPK51PK&sJj~+BMETA-INF/CERT.SFmKo@= eET$](1<|t7TA__kn;dC^Q,0e VDaH18=^<_E!pMYkMvqysCjRx Q|0IŒhHeyNƭ5Q#6iCҮYvY_GXMwŒX ^yr,*M*~\x=g@蹻s}sӓt8w PK&sJ*QMETA-INF/CERT.RSA3hbejhδΠՊѐ߀3̓1qAsAsASf&F&&A -P5@-|l̡,lyyy0 v . n/A@fa9q^C3Kc#C(q^#C3(NibTB`1713Ź(bujkva}GJZGBNsJzEޙLPlHМ 6ԱLvPO:iÚK"{Oi;$Ԧͫd'޲d*pouyEry[D7_H7Ȝ ''e*?.ȦЯM,z8xJEkg>͙R4a 6}}W2wm^= i'|;3j31320.V47,Vn&oƚpYyF{ ;V+,~M({<ڬϭauCPFO6ln}ЋdX5.y9ُY _3niL׏~sy["^s˧H'{զaK7^=k v9y5-ܖ}kw~ {^0D~yS!5nN gwo~Җv;!g?»jwƭz_~_z?a6u{ywꅗK3Xu ðq0!PB:&jDF.PS9K#w{RSgʊ9ɒ{۷?{zWjGWӮn=ñWݼwd\6qYi:js_}yG ;ؼ <~CɮU7~̾Rh:K uLEV}lK[Ǽ[QHׂJ1Ow7fk|y?>#qtaƯMߴe 3C-Vhu_D>op[b7|4('6x [ PK&sJvT*+META-INF/MANIFEST.MFen@= ADAѤ .JU*l tؙO_mퟜ/9SH"x%(K:߂1|h0Fz(((ل{QA WKJPmDՔ\vPum+ ]fmv6Ifu]Sg0(MQ Q:?]41%Ise{#khd4lئJEڕ쎤WPV</ߟ'Ό:P`*lm^ߩv\x/&]QS'X%ޮηa~PK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPK&sJ51 test.txt PK&sJj~+B META-INF/CERT.SFPK&sJ*Q\ META-INF/CERT.RSAPK&sJvT*+META-INF/MANIFEST.MFPKsrc/test/resources/com/android/apksig/v1-only-with-nul-in-entry-name.apk0100644 0000000 0000000 00000011333 13243353143 025273 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexTMhA~l4i5?DvUMQXIi*ԣL7ki:AEEE衈Aăovf4T&߾7}fc`Wsdpn|[I.CՓ``~h G"tk|<(㨬|{wĞYxx9,b1C\@PDqqxxL^ľ0ܶ8DQ{!Z VumJ˹ /5TA_J{Hىa%c |!^6Cj߆XIp=n.Mdf#D'R׿| |BJZ$3"sf4HKTy/E}O`&Z7McA7vOqɛ\6OYܩ9|! }v϶4L{OP+Y8LWVYؾNr=tKvi̚1S<ìa,݄c̮(Mܴ"HIi% m,7fOR}jZeٞ7Zx@D e9ەLZ~0oϋlbSnCʣ5;c̲sa3dǃp6Z$]Z:bAxKhՓkT{Njuo_J w?WĿ]_|k?DUpB0HE рz %qu`###z~R|^32pFEOe*PKՇ PK&sJ test.txtHPK51PK&sJ}+DMETA-INF/CERT.SFmn@ЮIm t!JZMa"__k^V((H"Pu0pK<O-a5I@&(Kr E7tN܆Es3++X1 Q OžKKh .PN;z~xQ:kbQ6u?:JcubXߓ^5c1 ƥߔY[![oFP3thxMj3g>Fg^ₜ䬴QXoT8=xPҹq(t+;ZJ]X,LkgSjPK&sJ(@*QMETA-INF/CERT.RSA3hbejhδΠՊѐ߀3̓1qAsAsASf&F&&A -P5@-|l̡,lyyy0 v . n/A@fa9q^C3Kc#C(q^#C3(NibTB`1713Ź(bujkva}GJZGBNsJzEޙLPlHМ 6ԱLvPO:iÚK"{Oi;$Ԧͫd'޲d*pouyEry[D7_H7Ȝ ''e*?.ȦЯM,z8xJEkg>͙R4a 6}}W2wm^= i'|;3j31320.V47,Vn&oƚpYyF{ ;V+,~M({<ڬϭauCPFO6ln}ЋdX5.y9ُY _3niL׏~sy["^s˧H'{զaK7^=k v9y5-ܖ}kw~ {^0D~yS!5nN gwo~Җv;!g?»jwƭz_~_z?a6u{ywꅗK3Xu ðq0!PB:&jDF.PS9K#lkQ>׳<+'[S}3D-f/̯cKY-$|c9Lwj2yNՖR <zh^bMzl3X_m8* d`G_?܃rw.)<5޶ַ~~PK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPK&sJ51 test.txtPK&sJ}+D META-INF/CERT.SFPK&sJ(@*Q^ META-INF/CERT.RSAPK&sJBZ,META-INF/MANIFEST.MFPKsrc/test/resources/com/android/apksig/v1-only-with-rsa-1024-cert-not-der.apk0100644 0000000 0000000 00000010126 13243353143 025465 0ustar000000000 0000000 PK !:!O resources.arsc5 (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexTMhA~l4i5?DvUMQXIi*ԣL7ki:AEEE衈Aăovf4T&߾7}fc`Wsdpn|[I.CՓ``~h G"tk|<(㨬|{wĞYxx9,b1C\@PDqqxxL^ľ0ܶ8DQ{!Z VumJ˹ /5TA_J{Hىa%c |!^6Cj߆XIp=n.Mdf#D'R׿| |BJZ$3"sf4HKTy/E}O`&Z7McA7vOqɛ\6OYܩ9|! }v϶4L{OP+Y8LWVYؾNr=tKvi̚1S<ìa,݄c̮(Mܴ"HIi% m,7fOR}jZeٞ7Zx@D e9ەLZ~0oϋlbSnCʣ5;c̲sa3dǃp6Z$]Z:bAxKhՓkT{Njuo_J w?WĿ]_|k?DUpB0HE рz %qu`###z~R|^32pFEOe*PKՇ PK!:META-INF/CERT.SF]Ko@= am ^!X,r (KLgea MQEd@jeZPVfćث(x&f)Zޒ"KaHe赙c,ODAlLihЭa *ThD;'>΁+ڧ]siUFU"9c= xٰxzܥѱc7U܈_PK#>PKjHMETA-INF/CERT.RSA3hbƩiA3&GLl|LR E 41~LLL v<{{€OPЀ98Q@N8JŮq>ٌ ̍ L ׷PPj=\9Ƨ:ew*9V_=g7rL2Rx5v5nW/)kwN_5_okye Y>,D>* `P3Z/ Jqq,ٲ|,b,"_܉辔<+֑+- A,b 032G L&F&6毳=[l|)gKxzwӞeEֈlxy ~qS}7|Rk'M;Eoqux4< I u&$u'Y;pU%zC&z 4xrZ@J?Q0uF>-a[ f kf(9Uv9U.s3ڮ~/WiE\{ |U18vS&'Wy40͑uD֖} 5Ǯ3|e"jZŌIo.nPKPK!:META-INF/MANIFEST.MF]N@@= 0K]@E%qQVH4 ac^:#0ʹߋDM@+hE2R~: !Xx~6{H-*#5DQvy#h+Ҳ~Ԯ\p/1D[ݔwgq|R?0}?G` 㰑v>?tǛ4Z}"1 @F\wY8RJCMC|PKjPK !:!O resources.arsc5PK!:^avAndroidManifest.xmlPK!:Շ  oclasses.dexPK!:#> META-INF/CERT.SFPKjH META-INF/CERT.RSAPK!:j META-INF/MANIFEST.MFPKsrc/test/resources/com/android/apksig/v1-only-with-rsa-1024-cert-not-der2.apk0100644 0000000 0000000 00000010126 13243353143 025547 0ustar000000000 0000000 PK !:!O resources.arsc5 (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexTMhA~l4i5?DvUMQXIi*ԣL7ki:AEEE衈Aăovf4T&߾7}fc`Wsdpn|[I.CՓ``~h G"tk|<(㨬|{wĞYxx9,b1C\@PDqqxxL^ľ0ܶ8DQ{!Z VumJ˹ /5TA_J{Hىa%c |!^6Cj߆XIp=n.Mdf#D'R׿| |BJZ$3"sf4HKTy/E}O`&Z7McA7vOqɛ\6OYܩ9|! }v϶4L{OP+Y8LWVYؾNr=tKvi̚1S<ìa,݄c̮(Mܴ"HIi% m,7fOR}jZeٞ7Zx@D e9ەLZ~0oϋlbSnCʣ5;c̲sa3dǃp6Z$]Z:bAxKhՓkT{Njuo_J w?WĿ]_|k?DUpB0HE рz %qu`###z~R|^32pFEOe*PKՇ PK!:META-INF/CERT.SF]Ko@= am ^!X,r (KLgea MQEd@jeZPVfćث(x&f)Zޒ"KaHe赙c,ODAlLihЭa *ThD;'>΁+ڧ]siUFU"9c= xٰxzܥѱc7U܈_PK#>PKdJMETA-INF/CERT.RSA3hbƩiA3&GLl|LR E 41~LLL v<{{€OPЀ98Q@N8JŮq>ٌ ̍ L ׷PPj=\9Ƨ:ew*9V_=g7rL2Rx5v5nW/)kwN_5_okye Y>,D>* `P3Z/ Jqq,ٲ|,b,"_܉辔<+֑+- A,b 032G L&&6毳=[l|)gKxzwӞeEֈlxy ~qS}7|Rk'M;Eoqux4< I u&$u'Y;pU%zC&z 4xrZ@J?Q0uF>-a[ f kf(9Uv9U.s3ڮ~/WiE\{ |U18vS&'Wy40͑uD֖} 5Ǯ3|e"jZŌIo.nPKKPK!:META-INF/MANIFEST.MF]N@@= 0K]@E%qQVH4 ac^:#0ʹߋDM@+hE2R~: !Xx~6{H-*#5DQvy#h+Ҳ~Ԯ\p/1D[ݔwgq|R?0}?G` 㰑v>?tǛ4Z}"1 @F\wY8RJCMC|PKjPK !:!O resources.arsc5PK!:^avAndroidManifest.xmlPK!:Շ  oclasses.dexPK!:#> META-INF/CERT.SFPKdJK META-INF/CERT.RSAPK!:j META-INF/MANIFEST.MFPKsrc/test/resources/com/android/apksig/v1-only-with-rsa-1024.apk0100644 0000000 0000000 00000010122 13243353143 023160 0ustar000000000 0000000 PK !:!O resources.arsc5 (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:META-INF/CERT.SF]Ko@= am ^!X,r (KLgea MQEd@jeZPVfćث(x&f)Zޒ"KaHe赙c,ODAlLihЭa *ThD;'>΁+ڧ]siUFU"9c= xٰxzܥѱc7U܈_PK#>PK!:META-INF/CERT.RSA3hbƩiAS&Ll|LR E 41~LLL v<{{€OPЀ98Q@N8JŮq>ٌ ̍ L ׷PPj=\9Ƨ:ew*9V_=g7rL2Rx5v5nW/)kwN_5_okye Y>,D>* `P3Z/ Jqq,ٲ|,b,"_܉辔<+֑+- A,b 032G LfOWnXۘo i^f/9YO{=Y#%ӂ %OךR݀;I5b'6կ4$-eRĺUf;J^WY|J&}6lCMC|PKjPK !:!O resources.arsc5PK!:^avAndroidManifest.xmlPK!:Շ  oclasses.dexPK!:#> META-INF/CERT.SFPK!:; META-INF/CERT.RSAPK!:j META-INF/MANIFEST.MFPKsrc/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-md5-1.2.840.113549.1.1.1-1024.apk0100644 0000000 0000000 00000010100 13243353143 026670 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:META-INF/MANIFEST.MF]N@@= 0K]@E%qQVH4 ac^:#0ʹߋDM@+hE2R~: !Xx~6{H-*#5DQvy#h+Ҳ~Ԯ\p/1D[ݔwgq|R?0}? 4>]vR&SPPG#D ƌ@a#_ w}~7i˵PCPK>%BPK!:META-INF/CERT.SF]Mo@; am bJiBme_q|dڅ__bڴ6dIhQc8+ &Kk+-W< qo=ci֥>̃~f,9 p DYw=a5%(O,K,Ȫ(o>--lpE&<=Krx0ur*f< ]vR<ͨ߻= s>>bg71]D Tt_Jv ie  x@ YX &3ЧMn7m_g{̷؆R4?/p=W Vs{IiA⒧kM)n֚ON1vhx2MIb݃OZ۝wz%/oK>%l·LF>60h< L! X 9) cil0\2㚅<n e4yF^Ӿ/G2KV߃{5?' ̔*z)cbKi 4&uԩ9g#rO[Iol jpJk!7W&S/ VL_X PKkHЇPK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPK!:>%B META-INF/MANIFEST.MFPK!:yU< META-INF/CERT.SFPK!:kHЇ META-INF/CERT.RSAPKysrc/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-md5-1.2.840.113549.1.1.1-16384.apk0100644 0000000 0000000 00000023363 13243353143 027006 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:META-INF/MANIFEST.MF]N@@= 0K]@E%qQVH4 ac^:#0ʹߋDM@+hE2R~: !Xx~6{H-*#5DQvy#h+Ҳ~Ԯ\p/1D[ݔwgq|R?0}? 4>]vR&SPPG#D ƌ@a#_ w}~7i˵PCPK>%BPK!:META-INF/CERT.SF]Mo@; am bJiBme_q|dڅ__bڴ6dIhQc8+ &Kk+-W< qo=ci֥>̃~f,9 p DYw=a5%(O,K,Ȫ(o>--lpE&<=Krx0ur*f< ]vR<ͨ߻= s>>bg7;@xý&o+Gxl>oq2Hc{/U6vvoH _4"sy9I&D=?*L ɂS\F*~ m810hC8݁ůnӒX)@^eP\QF}"rJ *\ɱ(һ 1-߭>+%4lzEp"6Ƕ/.3#Jc bBͽRoyo nr8ghb0WN7'_L.R zyLт`v{L17 7_}7d @ Rn(.]BWS&)Tϴaۏd[- fhާ`E( O$\Lo f*f-d=N9 ²$Qj#: u '49I83afuF4bL[05pOl~`EI~l~`VLLّW.Sx<޼ p-tI\hCBdbl5o}o,(!t!Dj;#ݩypI% ilz]MV~@6Lɝ]|#~e\QAAg'%]Wa#߃ȯ/mfӖc"=JƮ(_- @1 Jc%5) qnDGQf^:p'wcBLM+ݗaNBK_͎sk^J_m4yi~uW$!$`X4H͒V*sGQ{qK& OkaSVz!%Rd/ҬK}: X# NPz<8W>;<p)iQTJ,QQs4w&_n-tKAU 1r|](64&BJ&_1I.Wش~~.襱YMW0EyVvD$4.M \ILemT7|bJ1-`אxfyX\J5<*dC,ᢉ~}I_ oSH| "M͹1;{+ToB5#Eh¬mwʆ^B$Ù' 6n =HwՃFu}RPF7Ԋt[G 4b(-g![.ڽPqh0 S['m&%?ؠk#UvI1[-V)j2mf1$hCTlRF3XX m7i ٚo+ϤN %pUGzz T4)0$as.vNʜK{|e.wbq]?xl_wo&+]W٬;,XHs%e=7 uS70h #W:;Zkz Nx /^)FhGNغQta=VA2|r\>WTn?^xI_t@1@(tM]i-t4r`]\Ɓ˷ oc*#ōs BŚE`^A SǴ^ TY`/t)o-VVy"hFl_ȹ+H΢˲>[hہ0`:Hi|s,VmO;W 7¼p5*I.=$=6IgvW*0թjo jdKU W:sq9RYorchI98Վ;5PTpBг&Dе-,[G#GvU?ܥ~(N W8*FZ(&d@CC((@D |ww*^tEN;2=BAHD" ?%0Bu {+{znDv՜[ )r3bd'g*&wEq԰Et=w81E.jvA&)tL@2=d1z y|yq֏e\2 >8ekr(~EX1bу80&? q $@̚ mPlYk]X,aeAil*{H;'\EWl 7}^q$ަT =aҧT4Jkϸdl"ަͺ;4L;[7 'ɨ'OnDR gmkYM,DhU"h݁mkN~ɨ7WD_hz{cE,*f}PBT"s֞CTY5Zߏ򴝪͇P`<;z>3c) z!M|ND~A)^ oBDH|њ0ghXa4)(}GJˮx#,qMARf>bv] ۡE*ogme6(&4$vEUB^zyT$b8AU}ΘGyփp <_]|b{Z[%?mqߘE},C;怠4M̷&4e9^>ޤ2d /5A=#NEdyZό-[Fl1 i 0BeR⑛ rf Unہ&ߞ^=Pۊ02ETUJ&Z+s< `a?ԇ%_JT%D4X"FLG?֬y[ K5=ȍ5h^}'1NW)|5슮W*a a9oncd|ߝ'^ɉ'Bzo7}EoF./IX7Vts>Sߓ`ׄ0fH|#D<[WU [C;iݭ&4yϼWR- 鿪{goMє:OOPXsIC,8_EC&?E`ԝ4:ܟLx@dh,ihۆ1nȝ} coZ3n3,3b A5mh:~XP0YS<ϖG7moWM "7_ 1!$‡7x:[d붻5l#K^ZZb9$NL(iJoqՊXng{x1bɷ!l[I7?Wolz ʯLKEg)^ @nWΘZydҠRRJ!v]ŏndR$.H_*mi'ݧC[3 kTPOtA/FsrtkQ<5^R+*iOӾ)F\@o˟i<ʚ0'  rx]}Ըߘ+hN7=ےKNOg-ɦ# %(|r +C@BZ{()PGb,ZTնbg|Xcā͕t'AU2:QQ !*O>h@ɕ^d:Bp= Uz01e ߟG ~mR>>= 3 ڙoSZ#Qf,Ȫwf:dCb'6t0%AK(ns7s=ݽ pQ\ أ0sDh^QSS[O:N9=EeIl;Z:P\~hmcg[H0*B9LxWȠO?M#qT0"˒Ɉ Be.gdžD&JԴDlG-k؋qR?o$ޠ֣!Qg-nIPQ}Yj+ܺgŏ+ZQU)Tq-3o$zA~j,T/!_u?KīK>.I(FйU=o/ϩ-SugM]nPweqIR?k:֖NuwpJwX9|m w1MS!KDP$4U]P "P  %ӗ5䅴ADJ-SW~at[Ā 3h@[!nCS7_si  0 H9Pu^Հ]A&YReM~ RMf=V=}>d_\vYt}3n̟s6x?~ IT)g?%uk+nbMtFD:Qgwn!*(%Ӥ = l38*Iߩ޳wh\pm S0f|5.`#g#nargR;~%&(3*M xH UΗҪ2llc2fROv)52KLKO}7_ƘаF dnzi PN=\n8-*j0Cwr4> [pPN`noRE|&m_K2{0 XS򡳱i8 QyOӐtTO42 USk$,mx'c XKaݝ&!hN!a pdפD*M^`jh( (3c!){+#]Vz^n`X'u7e!Tg}͋ <١8-A#rܳ%n J'_j(;+%x U=P;89$8ntL@z'R%USIa6ڟn:ABIe;S41\%B META-INF/MANIFEST.MFPK!:yU< META-INF/CERT.SFPK!:B: META-INF/CERT.RSAPKyd%src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-md5-1.2.840.113549.1.1.1-2048.apk0100644 0000000 0000000 00000010731 13243353143 026711 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:META-INF/MANIFEST.MF]N@@= 0K]@E%qQVH4 ac^:#0ʹߋDM@+hE2R~: !Xx~6{H-*#5DQvy#h+Ҳ~Ԯ\p/1D[ݔwgq|R?0}? 4>]vR&SPPG#D ƌ@a#_ w}~7i˵PCPK>%BPK!:META-INF/CERT.SF]Mo@; am bJiBme_q|dڅ__bڴ6dIhQc8+ &Kk+-W< qo=ci֥>̃~f,9 p DYw=a5%(O,K,Ȫ(o>--lpE&<=Krx0ur*f< ]vR<ͨ߻= s>>bg7ʬ.w8/OU6b_?uL0mR)=w=_N/_w$_MbnƢbXNt˸cʂ;V*ɡ<[%p//W6QaBF"D ^1+ }e%k'Ǩ?{8ܥ'-R>+Uta&I6ߡMc 31320.03,8n@lKY"ud A,b 032GPfPP3~Or:d)߷WJD2ɻ1s{UΌ {փ'l1= VW~mo .} 5}>iH5|K-56Z]d_7 eޗ}ՎJ?^ɠ{"iQ;oF6/kËtmvQ3^k|o|zm6|}VSyٷia0ݹ3Ԋ#P3jfibdXJUM/?gtc7wJIK7ײ?J~i_%.?|'Me]Meadh`qo$<(`Y;X2j[䌭hvAٷgvɾ;M"9[X!j2aOS7.Xu:~v$c\o*u녎;K%cr"]]J*-kj#./,?g95jPK=^ PK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPK!:>%B META-INF/MANIFEST.MFPK!:yU< META-INF/CERT.SFPK!:=^  META-INF/CERT.RSAPKyJsrc/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-md5-1.2.840.113549.1.1.1-3072.apk0100644 0000000 0000000 00000011550 13243353143 026707 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:META-INF/MANIFEST.MF]N@@= 0K]@E%qQVH4 ac^:#0ʹߋDM@+hE2R~: !Xx~6{H-*#5DQvy#h+Ҳ~Ԯ\p/1D[ݔwgq|R?0}? 4>]vR&SPPG#D ƌ@a#_ w}~7i˵PCPK>%BPK!:META-INF/CERT.SF]Mo@; am bJiBme_q|dڅ__bڴ6dIhQc8+ &Kk+-W< qo=ci֥>̃~f,9 p DYw=a5%(O,K,Ȫ(o>--lpE&<=Krx0ur*f< ]vR<ͨ߻= s>>bg7H"D]n2$"(8! OC@<6<ӧ2:Kj_ f.aS:]:Ia:;2j?iش^m`@ m5l |G =]6y˚pcTvwwU˄?Ekg,tBv_zo՟,9DjXN;5Mk/.' pܥ)ɪN ?h"Jwp9o_JXaٺmM#^F]1tYRYYl)]%\$%B META-INF/MANIFEST.MFPK!:yU< META-INF/CERT.SFPK!:W. META-INF/CERT.RSAPKysrc/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-md5-1.2.840.113549.1.1.1-4096.apk0100644 0000000 0000000 00000012355 13243353143 026722 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:META-INF/MANIFEST.MF]N@@= 0K]@E%qQVH4 ac^:#0ʹߋDM@+hE2R~: !Xx~6{H-*#5DQvy#h+Ҳ~Ԯ\p/1D[ݔwgq|R?0}? 4>]vR&SPPG#D ƌ@a#_ w}~7i˵PCPK>%BPK!:META-INF/CERT.SF]Mo@; am bJiBme_q|dڅ__bڴ6dIhQc8+ &Kk+-W< qo=ci֥>̃~f,9 p DYw=a5%(O,K,Ȫ(o>--lpE&<=Krx0ur*f< ]vR<ͨ߻= s>>bg7,cȭh ]aԛC wR~cV^Exk|оΖ隍 _M t}_*S([ڰ*.W[s1e4)d~׮6O{Ϥ*NUR#:+hq'uiS2z;c݃W 70mvfOWP|.bCo@|3 =BþUO+9}3stCY"/еkT*L"WI#ÅI=fʲ=9̈́EQ ^/G@ǘ>'xZ.0NiVT9ŒBi5ȧs7yh`IPX*%#^4srr˻J6wa=ݠ#Cc2GMjdݶ Fْ=z7olx KΤ( $XMr\LlK~Z"Ra:4oT9ZeM哀hm5DjT^p~dJG.q0!׃lBii0}[ {Ckf!>^1䖢,(p]I!/ȁGiavRk/7^%,ɼ4u}'{^4Xq\*Ǎozꞗ~(@!cqi|,y"W&パe-JKg3EUFH bUvo{/_oG۪ Nq'ɱ8ˮ-A勭iЬVvLrB?_)IA.$ͮSAϭ* 6˹K% R`Arh-SʳD @1.NgS[Pfk ]LW-wPq[ً1 uGh^hVSӤPKu4PK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPK!:>%B META-INF/MANIFEST.MFPK!:yU< META-INF/CERT.SFPK!:u4 META-INF/CERT.RSAPKy^src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-md5-1.2.840.113549.1.1.1-8192.apk0100644 0000000 0000000 00000015362 13243353143 026724 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:META-INF/MANIFEST.MF]N@@= 0K]@E%qQVH4 ac^:#0ʹߋDM@+hE2R~: !Xx~6{H-*#5DQvy#h+Ҳ~Ԯ\p/1D[ݔwgq|R?0}? 4>]vR&SPPG#D ƌ@a#_ w}~7i˵PCPK>%BPK!:META-INF/CERT.SF]Mo@; am bJiBme_q|dڅ__bڴ6dIhQc8+ &Kk+-W< qo=ci֥>̃~f,9 p DYw=a5%(O,K,Ȫ(o>--lpE&<=Krx0ur*f< ]vR<ͨ߻= s>>bg75BD|z4FOÈfS^v Ei4; Eˢe12ҲXKv ZAC8PQ@ T8NP\g\n9@M3FRymzjADVmM  R}!Xsc]uo<57rJɆ9͖ͤF.|Y/l]ڍu7lpy\q}X>V47Dx+__UV?eBC*INij8zT<<;kA[*9ѽɾc#G"F}ISAHCj(ekGx[fʛb:e縎ȷxjP0wz6ɮM A6kڄ {ՙ_2>Gs7+X-@&MaKCrS~I}$)AVȌncnR' 3{=DlI@ɲe7*mWߐ9Ӈ%:.aۤ ^hR0WPPxh䐛CcREjAO)+ۼw&RC3,mD6'K*n$H:9t#)MOc_oɖ `+B~ʹ5:|Bi䟳%zPMo5%#b``m9)UyjHY}|yB* ԨeNI4%Fl1?r6s=Cewr"̮'&O,J/Ǔb>撩ɩ8/澰HUKoG[7v{o<>r AQ|_̻n (ỒHK٘X2J_U!"&VxGSRox:\s2 ةf",t&6R:/30"E }Ut \H9hUk4jj_~8=!O(0G+ׯeڨ>Os`XyiˢVAoQ~<;]'1PKr9e&xL_}>6`og߻XdѰ`)O.Fr7Bb. vu2 r?daZ 6k]f~u``gMJqk{f2]5%92bNvUew$U@ hj jۜF3|*s{Nw#)KEPwYزz :ej~!e^2ӂűn.[fn$|Xv^ v)=>&NפaCIN*ˑKIM;i_?LJU;gVN⢇n4 ;ퟭ[47Ҝ:"_莱{6n j+Y E$U^_Q&|ir8DbN{L(JlJe29}E {49<: .<%Sa/3Gh֭7 ~Wb( =3(g+wPp0^t{'Eۓ;oi忉AQt._+#3oM8Ncnai_oiF@W 0BmޙA|qҫOxvT=v˒/armOS ?>f>w[=Ku|cYr`$b;xԪ83M.HFQ*GCmrٞ}{ȬtܛB{ݜ$T Y4D:(ce=Ey0*'dCcJjM\/wcVlb{(ɦe L D 4>  ~v3H,oq%D5WDI>[?*<9(9!C Ƈ}hdvu0!l>+V\6!{Rw^պK6bNgLQ^q8B=Gc'*,l#aYNDZQ{0*n+{0WPA|^ܿ& I}F\yI3#+0P2?|oGXm8 Oڻe]/?%. =PKJP9 PK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPK!:>%B META-INF/MANIFEST.MFPK!:yU< META-INF/CERT.SFPK!:JP9  META-INF/CERT.RSAPKycsrc/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-md5-1.2.840.113549.1.1.4-1024.apk0100644 0000000 0000000 00000010026 13243353143 026702 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:@META-INF/CERT.SF]Ko@= amĄąR/,vTd%M9y/K R7 d@ɀ@yg U^feeF|iG)d8z$oq]^ih0TD&9ϔ6< 61LUnv9M4ݽs B}5WVeoqTѯ'gˆŃ@>N/؟PK!:CोMETA-INF/CERT.RSA3hbƩiAk&L|1]D Tt_Jv ie  x@ YX &3ЧMn7m_g{̷؆R4?/p=W Vs{IiA⒧kM)n֚ON1vhx2MIb݃OZ۝wz%/oK>%l·LF>60h< L! H$ &䤀ؐ$wѥ^6~W3/tM8z߿1 >w?%3hzZ4,:^v۬ >SRꢥL UُSZ$"kqP!!.!PK!:jMETA-INF/MANIFEST.MF]N@@= 0K]@E%qQVH4 ac^:#0ʹߋDM@+hE2R~: !Xx~6{H-*#5DQvy#h+Ҳ~Ԯ\p/1D[ݔwgq|R?0}?G` 㰑v>?tǛ4Z}"1 @F\wY8RJCMC|PK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPK!:@ META-INF/CERT.SFPK!:Cो META-INF/CERT.RSAPK!:j META-INF/MANIFEST.MFPKysrc/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-md5-1.2.840.113549.1.1.4-16384.apk0100644 0000000 0000000 00000023312 13243353143 027003 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:@META-INF/CERT.SF]Ko@= amĄąR/,vTd%M9y/K R7 d@ɀ@yg U^feeF|iG)d8z$oq]^ih0TD&9ϔ6< 61LUnv9M4ݽs B}5WVeoqTѯ'gˆŃ@>N/؟PK!:I6.?META-INF/CERT.RSAuUTEih܂6 5]X %wB}gٙݳYU;L&v @ &X $#"Ar߽A=AQspR Zq! 0gDžCB"`C9y  Lv( ɮrQh=!{4`\͟o*^m iRrS9Jzozo' >62v]+O´{udKY#C LW0'O*`JzCWeVh6-Ξ2w#Mx6s~<J 04pLs!g֭v {³$Z}/eZ.u9aK9pe3J=_?ztc8:M=cĐ8[fk[;XC:9\uy[Jnm;GO1͒fBrH lﰁe](crMPgBϮ[I^(o86+#-6YZt *ǜ'kthxAiW!m_c([ۉ_HxŦB}jN8q!B#lԸg2,eTfX`$!" ҹ૱#LrH;6 }"\I z}!/}ΪT rش|kkeڎ PgI,\4 :ɯ .hHv7y,[$䟉 DrիmU_ǖ ⅹFPyPJyf QX~*jPXN/'`ㅄ| 2GynS9'9 Uk3ُ+>JB?GIı| HgDo<=et-O'˦ CNJ2:_K#鸭tKwRdJK! #/+Ʀ}z@cs a ĎAee*xoF-:B4'tMxd"H09@3c0>9>t3/vnڪQsHYZ&v82@/ɻ]^<:g_$;١4WίJOT|V;{o/ٛbGlS5 ap.ZIljlol~o0Q|;Z']&Gl %qiMѴt6|RJTwycTevՅ7MρO>2GiwOi~t8*I˸ę(;3&q+-^S}(|ʹycM2zV^沯ryw8c( D$̮ÕӻD F*uIUw|NxX8׊`γq-L(u?hdtnpYXew% *[2C#./pYWCmdڞ1sCPPGe!\x 2ddg'p1{9J*rԼ1Y5<<^Y4۶%iAʂ.J-;#؅xYT&4hMAaM%L,K&Ƈy@Wb.O2D}hFukv9g][F'^oi+Mv&‚مk;^LA?dRk+{+:ſ|[1m;6V]OӴԒs]RKh>wl/(ڭgsRﴐ.6F-ih12V-bB6p|U2݁ HSdKT+h!4X < 7OAWAg '$$`_ROqZq&$v)(gmʾʱQDmud0b?䢁تۂ8*Xy+a46]«suT Wh’IU;HBŖo ;h6VތgVll3ïڇ[-\:bp0}V&,85 %GVvp5NhlTCsFX7ip+D=d OdlV|gpQyNr.  Hq65ǚ?MXcfGQDF 7BjQO(~ml?,`3tkx?X~qEo] VD*)6=,帜lRrߪ&BEA/?e wvDtC>U,{ KQg=71N3ȼՑ|slKjjM<߄H$_H ze#OnFai7I6xi1DLkKh;;"Q՛kz,,L%䝵HDNb36ubc$a,#_VoMkPZlu*gl#PGhK D Sn*Ylgs8w`c_l=v#C@I%]O!l=_֝I`cר.Tћ ݘ54PVZ2'knY,M֨U).(o>WXx}/;s KU) uiϲ݁yU{+-cXƷ aUvsH(Yߋ)96h/39ρ?2Oe[5#QZ1I'nC:ڍ -Ck/ԝ`}cg2.[Y^&oG^Uߖ+vn5RE)fӋXό~tR gn zgx._d,D,ekFWmPw6:NMv`^ytҐ)zX%XJы,Kʏ#䰯p]A~xJXm:bӐ+ 7(w?J|B:vkTY|xN5^vd@, 795V;הQ`ꗫ 6G#$Uo,#6a/W-i] ˞vX6<>{[ācQ-OJ3[Q/7ݩ2-pkY:⍻OegZkKn=#72賣?HɐZCXϽє“P;Ut^kߟQinĚw/,y 5N'U:+zQw0?EkW"}T~XR2N}]ޢa!ʕev 赠I60PF+['LNJBm F ++]Oo} FV{XA Yz'Sa(27NUw7x,dkl_i,ťYD+ mG!|H>wKb\HOS)ђomUMp%uzns^`VM~iZj&Pf7 zbAa03ָXMfX8s$V e qe$$3j_h3w?{u?qZm(܇ed[Qc+!wΰ~J ~ OkRL0= yq PMi&3z*'xI| 55.e+wx$ /ѡF\\S "ǾI\%@hG@\{\yp$G?`-$4>Fmk^7̊nqWsVxY )OHQae9nw27"+CX_cNGd^c#8z54~vNDs-Oh*KN=)04O'=3{q[lsd <ނyy{3 Erb)o0r'^N$) z|9cmv{n"04b{ԍ[I[ZpP9XO+3A-uJ,oVeT ,l5#1Px̲%!sC\Vg]$j54d"|Ax(m2{)k>sN`YAJGc'ƇA0K?غ$(@p:hKG\a^$IJ {!HV{_ay}>3(H >XZ /?~l{eQ;iF~,ʔh)ͳ#c߮1u~RuFwt:Zyd1i^DDiB)^쎻ub\ [{|_>h46ɖtEQ+4eX}W1_ Mh9 詆׫5'RA_IJXc0fbVLtq7Y] 7GS(q(z*h(D#o3[dU{(~dspApT0EG/k|(!D& g\H)Y^1e,)Ȏ'rz4Ia23e^tnsH*>M#>HCyDb}أ]=IPK!:jMETA-INF/MANIFEST.MF]N@@= 0K]@E%qQVH4 ac^:#0ʹߋDM@+hE2R~: !Xx~6{H-*#5DQvy#h+Ҳ~Ԯ\p/1D[ݔwgq|R?0}?G` 㰑v>?tǛ4Z}"1 @F\wY8RJCMC|PK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPK!:@ META-INF/CERT.SFPK!:I6.? META-INF/CERT.RSAPK!:j3$META-INF/MANIFEST.MFPKy;%src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-md5-1.2.840.113549.1.1.4-2048.apk0100644 0000000 0000000 00000010664 13243353143 026721 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:@META-INF/CERT.SF]Ko@= amĄąR/,vTd%M9y/K R7 d@ɀ@yg U^feeF|iG)d8z$oq]^ih0TD&9ϔ6< 61LUnv9M4ݽs B}5WVeoqTѯ'gˆŃ@>N/؟PK!:Z)META-INF/CERT.RSA3hbibjhδؠ%ѐπ"`PǸAO&Ƈ 8L r2~7+d64gcea(*N52005430664415751604rkjbTB6(p$Sۿl:42y . $t\y]Xg!(Zij>ʬ.w8/OU6b_?uL0mR)=w=_N/_w$_MbnƢbXNt˸cʂ;V*ɡ<[%p//W6QaBF"D ^1+ }e%k'Ǩ?{8ܥ'-R>+Uta&I6ߡMc 31320.03,8n@lKY"ud A,b 032GPfPP3~Or:d)߷WJD2ɻ1s{UΌ {փ'l1= VW~mo .} 5}>iH5|K-56Z]d_7 eޗ}ՎJ?^ɠ{"iQ;oF6/kËtmvQ3^k|o|zm6|}VSyٷia0ݹ3RjV2o͞ ^]x!=`29?r?!1+@aϻWUotU@ֆcSsvGęwz;-ߞ2ΈDW_9>{/9Lʹ^~ _7:7^|W"Bg0[~v4?$'~H1Q~<:qgkm|fWij&Z9L- ?uivRTTj%OPK!:jMETA-INF/MANIFEST.MF]N@@= 0K]@E%qQVH4 ac^:#0ʹߋDM@+hE2R~: !Xx~6{H-*#5DQvy#h+Ҳ~Ԯ\p/1D[ݔwgq|R?0}?G` 㰑v>?tǛ4Z}"1 @F\wY8RJCMC|PK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPK!:@ META-INF/CERT.SFPK!:Z) META-INF/CERT.RSAPK!:jMETA-INF/MANIFEST.MFPKy%src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-md5-1.2.840.113549.1.1.4-3072.apk0100644 0000000 0000000 00000011500 13243353143 026705 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:@META-INF/CERT.SF]Ko@= amĄąR/,vTd%M9y/K R7 d@ɀ@yg U^feeF|iG)d8z$oq]^ih0TD&9ϔ6< 61LUnv9M4ݽs B}5WVeoqTѯ'gˆŃ@>N/؟PK!:^_m̵META-INF/CERT.RSAmTy8 \A2ko"b$+G &G(E ȱ (rĸR,lYճv}}A*V19THE,CF/ȡ¶A*O u΁A(2-ry:AȏYa}~ʪ1PBx'DxTx@I=J"!YT܀j Oo.{ږRݪݨ-#jBG3UrUghG];Kz-o-nEV|Oب_{u7r]\*F:9sqڋĠm/R 6z ֗0iPZ\zn(l%'ToG15?6{C s1ݦ8h s|&.1mINZތAv%OSZ0zN[2Ezjd\hM}S89.Ep}p0.~F b%~՜zΏr}4zAH,}Ҽ5y 0PZ?,SmưZn9)by|Aa3M@̮u>0]^ԍ{bE]%(';{(WW"5%7ڮ)M-?g[mTt%?MtǓۣGw0NrXX#ڈg!^QS+8a_1i(vD&ZG ݐKG_ d>O1o󙌪pU _Lnk-b[!y \hy-&M;+]}̰x;kvcq/9_[(GQ !{="d TZvvLԕC&zg &RR"u̞ₔEDYaA'Yw!;Z35k1w7 >dỳyE^ Sxapb #Ct] I[K.e5-y~XN~T]r(ϬX_PK!:jMETA-INF/MANIFEST.MF]N@@= 0K]@E%qQVH4 ac^:#0ʹߋDM@+hE2R~: !Xx~6{H-*#5DQvy#h+Ҳ~Ԯ\p/1D[ݔwgq|R?0}?G` 㰑v>?tǛ4Z}"1 @F\wY8RJCMC|PK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPK!:@ META-INF/CERT.SFPK!:^_m̵ META-INF/CERT.RSAPK!:jMETA-INF/MANIFEST.MFPKysrc/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-md5-1.2.840.113549.1.1.4-4096.apk0100644 0000000 0000000 00000012307 13243353143 026722 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:@META-INF/CERT.SF]Ko@= amĄąR/,vTd%M9y/K R7 d@ɀ@yg U^feeF|iG)d8z$oq]^ih0TD&9ϔ6< 61LUnv9M4ݽs B}5WVeoqTѯ'gˆŃ@>N/؟PK!:r <META-INF/CERT.RSAmy8ԋg~ 30 eMX""K&JRbI,Ti Le'%dɡQ"ٗܡGϽϹ?T1*RBaXBCU,P(VJP R/ HRc Jf*A)$CGxPE#x<gH஀ǃF8j" 0* oҹ_zߵY7fw;nuGppFMDP <:\,?l[B*i &4IP{|āsOqE?gqW@ &1tPU^Rx7< 69TalVAՓT&> yZﴮY->$NSj)ٹq">Oa؇%Sʥ^M'VI<n/جAn>nB|}i 5ZrM8p5=S`|Ytb /[ՏOc d> .GXɮplK΋#L"20TOolEQUыwGg[g*(͘ M Ct~Z Ju=`PQTި2.]Y T%[,Wހi ]B~V<I݇v,Q3y{3Ö9s㶣nm+vOWUi6},}M72^)6c'N peXV2MƝɆ==qLru8k:XFP2L=%i1ԉ5#0WAwaPqL^i',V!MˊZ; /S)58#c|+7" E֯Lsͅ~\1wrw?ɤ0h(cO2dCTAWRPjdaW|fqcB?SlX70gla`#O,]R(n`ctq &zc2o0X) VwV+ĶFӕdˑa/aHù>I _Zc? HՎ/.ӌ2*PCSi jsz< xvByqyvs,ы&.91O3*QD7y0O-F[5HO%E_ ԪJa>EQ lTVؿh+ pӶ=EgĜS@zflc}uv#* nb5 )֭toEx:F_w8UT-Y;J[qzcK^Y*%Y:6x|; 4rq&a 1?\l3qיH}[LBM3]hw+Ët qznk[TLk0_~F.{uI$W0sS]\=#ͤ^4UK3,Wfٕgg@sf fTކt_ދR,l׹@v|$rw2PN҄Ĺ"/]:Kᔇݕ Wq:k(28 rq5F6=!ew_ۖPl's5 F;k;T2^g PK!:jMETA-INF/MANIFEST.MF]N@@= 0K]@E%qQVH4 ac^:#0ʹߋDM@+hE2R~: !Xx~6{H-*#5DQvy#h+Ҳ~Ԯ\p/1D[ݔwgq|R?0}?G` 㰑v>?tǛ4Z}"1 @F\wY8RJCMC|PK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPK!:@ META-INF/CERT.SFPK!:r < META-INF/CERT.RSAPK!:j0META-INF/MANIFEST.MFPKy8src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-md5-1.2.840.113549.1.1.4-8192.apk0100644 0000000 0000000 00000015312 13243353143 026722 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:@META-INF/CERT.SF]Ko@= amĄąR/,vTd%M9y/K R7 d@ɀ@yg U^feeF|iG)d8z$oq]^ih0TD&9ϔ6< 61LUnv9M4ݽs B}5WVeoqTѯ'gˆŃ@>N/؟PK!:q? META-INF/CERT.RSAmg8 dz b{%DlQTm5"Q{-NTkV[{oZ4IPBWzy\a(i$ T .z8N> C}P1%h %AGH;nj`3P `(uP2BEN@p$BEAgC*w"r 0~0 D!60^a?L{e?;C$ 0\ܦHh +D#4oȻZ!I%Q[}5 nNG͛VV+lc\W[ds0+9+I{&>j۪]~bێYQѢ/B_O T#uq/ GFmcbeMUZYt8v 1E429k|xs} +{DQ`5婣 "R0I"BՆ dŗ=}Wǡ fx,ÜgI9ڨl#XxT{k]czu m眃f+&&V'W:>0?y;HꚟzIl1;`6A=tvΠ}ZA+=ѧa>ћ>i`(*Ct1vRaӪ>17P$ +kvwS1s,cJr~k:mۏ}`-㯧J^ '*,h34~(qn҄+>fGNxҠAAC4JwqUe;6~| 0,1y~O= 4lqS[~fh(oYr п  8A=5GVCw%A=۰:u5pb}K T4`S _5h}G7]nCN҉4v1M(8zI|l1ԋ$0 ~k*d 9oY;X)-9 Cdh'yDcñtT(6`է8C +kУnik}&V,OMwpHt;BWjcL~SᏗq eh0?K㑃ΰ)Xq]`3Źguc& { C!HOnS\ş2B%,I} +h~Vx:/7r՟/]٭yJ$5ANm L+J1C&s.:ѿ%v]nt R թ 7_ ǘtT1D=M+q6ScWq^=.YOC3$[)ݗ\q TfAjc]R`$`CV>|Mf86FY|>@]cNH֩*ʢ]&-Isi\(=X3-Z/0Pt4-rcA"0ܡ?As@0kX\r-ѨK֌H氡)aI>a)q-a#1\othp3ȚHEg_[3m:xE>1@r<+SukIfyD %Y5t['$&Jr8tâus78I nqI wYmۑ%ݥ)3fSܨuiqʹe)CϰZ[+VƊ_i7К;K ˽{]c.p-e}7~E^j]hJ[ZjɍeqQvĭ댫coZb{?" ugX 9@7J'gLppDlX8<Ѷ/:ۋjW~} Uibk]-xüٍJs!ҭ;b0W͓f&44ҍ#bSv3P?pJ8>kĜ"^o~6 *[,o>i  8#OCA].END_~FqW[-Sڃdiq`/m?ԓjH/%6*T8ΐ$ڎdՕa#ib@ʾCZ}bCTIT*m{ySB"99za?TگviLn8}"x3ec{#Yy:y$g,dTv|;%BT /A6\8u;NbMSZá^iX}>PK!:jMETA-INF/MANIFEST.MF]N@@= 0K]@E%qQVH4 ac^:#0ʹߋDM@+hE2R~: !Xx~6{H-*#5DQvy#h+Ҳ~Ԯ\p/1D[ݔwgq|R?0}?G` 㰑v>?tǛ4Z}"1 @F\wY8RJCMC|PK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPK!:@ META-INF/CERT.SFPK!:q?  META-INF/CERT.RSAPK!:j3META-INF/MANIFEST.MFPKy;src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha1-1.2.840.113549.1.1.1-1024.apk0100644 0000000 0000000 00000010101 13243353143 027040 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:META-INF/MANIFEST.MF]N@@= 0K]@E%qQVH4 ac^:#0ʹߋDM@+hE2R~: !Xx~6{H-*#5DQvy#h+Ҳ~Ԯ\p/1D[ݔwgq|R?0}? 4>]vR&SPPG#D ƌ@a#_ w}~7i˵PCPK>%BPK!:META-INF/CERT.SF]Mo@; am bJiBme_q|dڅ__bڴ6dIhQc8+ &Kk+-W< qo=ci֥>̃~f,9 p DYw=a5%(O,K,Ȫ(o>--lpE&<=Krx0ur*f< ]vR<ͨ߻= s>>bg7ٌ ̍ L ׷PPj=\9Ƨ:ew*9V_=g7rL2Rx5v5nW/)kwN_5_okye Y>,D>* `P3Z/ Jqq,ٲ|,b,"_܉辔<+֑+- A,b 032G LfOWnXۘo i^f/9YO{=Y#%ӂ %OךR݀;I5b'6կ4$-eRĺUf;J^WY|J&}6l10{˔ ?g 9Q]`IW[uhO^o:&:|g#r.[r?3k[s.{5+)N0}ڤ-{>ݙ?w֖Kmo[wrwSSOd[nPK*ќPK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPK!:>%B META-INF/MANIFEST.MFPK!:yU< META-INF/CERT.SFPK!:*ќ META-INF/CERT.RSAPKysrc/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha1-1.2.840.113549.1.1.1-16384.apk0100644 0000000 0000000 00000023362 13243353143 027154 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:META-INF/MANIFEST.MF]N@@= 0K]@E%qQVH4 ac^:#0ʹߋDM@+hE2R~: !Xx~6{H-*#5DQvy#h+Ҳ~Ԯ\p/1D[ݔwgq|R?0}? 4>]vR&SPPG#D ƌ@a#_ w}~7i˵PCPK>%BPK!:META-INF/CERT.SF]Mo@; am bJiBme_q|dڅ__bڴ6dIhQc8+ &Kk+-W< qo=ci֥>̃~f,9 p DYw=a5%(O,K,Ȫ(o>--lpE&<=Krx0ur*f< ]vR<ͨ߻= s>>bg7ʡ%Ռޗ^Ӽ m83U1kCo9+5=> RlN+fLg#Nʞs_?#xmZwSńQWT=~#MZ #VM6ϓP]ϢfD75@fևR Wmd2t ߉AfhcHB2!76T6L&`CKa¿/kc索#F=r0^Q#:%H&ٻʡ=~)I;zYHm2 Rh­-GZ!;@.PWmNBWκʦpL/L9*8bo~$n`SL#7Gsoеu7}ԊOxYrtai(?:A;t\2vC W_&P^0:N2\<|9fsG\)RX_,~ɳ}eC:aNYt9P&d:3iUi_SA(>!enҒZcp-JJ98MX;cWdޗv[.k%wfܠiUdss.',35_-4= ./|)"LBWd+N[oaa|kX=5pW!T/l LBVC/r ;1<\IksPR:2yz밆q[,0ݩ`$bhC]R?yvR'%, Z=nv+h`f6!*lǮmu,=BE;g.39<=Kᴭ [z5^lƿ9nRz0B!@DH';P <,d jUIA 0vͿHd@ A^WAWԠ"!W*,˩KgԹ-oP{WrSnI*ԏ(qY>fBp5Nwiע A=xk ]646@Q@ pM.}훠=]\ #;3M@RN wWǝc 6VSîXż$%g0Ԥ5uyڀm[kזg2as+~qҒT؅! I7eF;zv~o¸P9s}pUHOIڭViWq)mTЬj 3' b) کkQ{1'i۪MWFfDxE"X>t.-;Ȱhw#%Im__Ȼ,޺28z䔟g@*jiD S7Q-XfE_,9r]Qn$onC} &ElAeF9-HIy`Ѭ,3+c?q+J']R'Oojf0V,ާ=s=xHCClE)(W.ΛE ȸg{? Wpk݇ϧ%ǭWwk"/X(Zc2h*A=†6~)ܚ-|{Q}Ng,ԁG^'"r<&Vbby$D4 qIGi.P F߶]3t8qy5=H>{J:ڼ3FUK|HDjy,[Pq]5l;Vs$_yXѿqwWMBsĨUw{W-frn_6J}Պcy J]oR)e6GY~~oӐMhncMZae + ~n$(M_@Q.=@(3x-ӨvyLk?F⽣9YHDO R'0`C9e(?htl y=ҕ9CC&ɭto0Sw)OXJҨ(?wm=Gqo8v{&xF$xf_G&ctGYxJgY jIa8X Q=".Hc;>莚V56G#ӺUCv[樜-qM& Nsh/ϥZ4po8*RQI @ѻ5 ^V*ן)A 5;R{% ;wiUj/~456Xvvc~5I`}aYY+4gx44 Цi1Sx{mn"@oCTF{gnF:ez:\,(Aiؕ +]H gst=um<, g3-iIȑ[s֬:BsD CE ^QϘ2z܍&lT?@Y/ g<V+卦J:<_>ѦW\BE̠#Q퀦/<;_ &̗[/TqG&mb2 oi1'$\.Z, ;É"?!-g(q'[[êVν \XTG뙓Ƹ{܍aE])޽!*thŽg^Mb!C8߸߸xύtmaF 7xhVNпK*/qPj>~ˀDzxK=S3lV)<%nODڸ T3fS?a=_GɏkV:im3C:H~hUrZȧ!g|ЕDs cVZ2\cX2\UΖK fOh1)?6s T{#Դ1x@5^i(i+p/LÌE˨̋K5ĶÇ[*m?jF >/0>P$Jd0IҖWh/Y>^=(;P]_ާޓl茝;4AFTc%?=WF 5BYٍ̐Nb?aUǵ}ep04}0$+gq(Gx P2ezSqcC gP ^3-ֻh_cm /ذ.DfS>1-Ƅ#),N%`6ˣ_YNUN9ID鋇=ْeo?pQܓ  [!_S^$bQͨSRA`A- ][q i[./Q`M9A#(F2CL \ʳn.l!oBv\d'( ұb|#u'vY&3$ԇ3Gpȍ$\~ZV3ԗfaѽZ6#ʕ2]?8-zd"Toa-IqkmY#&Y1C>ߖ4U4J`߂kCD6^jSWdU s":Tyԫ1w³6'ŔD%o}iN-B4U9--f^Wt",rx~+>=5ۈ] narZn"^'t˕;"U b^ de繙;%~VrvV>.U4Z+l1ssZT-#7S߄5X_>Ī S, wn7`*b4yEY6N??f/R qL 1R=ٶk&Ö`=n 1"14Aϋ%,yѡ@unb?&!0{zqbp#d+5r^ K5j\3@Ft1^vpPK;۫9PK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPK!:>%B META-INF/MANIFEST.MFPK!:yU< META-INF/CERT.SFPK!:;۫9 META-INF/CERT.RSAPKyc%src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha1-1.2.840.113549.1.1.1-2048.apk0100644 0000000 0000000 00000010731 13243353143 027060 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:META-INF/MANIFEST.MF]N@@= 0K]@E%qQVH4 ac^:#0ʹߋDM@+hE2R~: !Xx~6{H-*#5DQvy#h+Ҳ~Ԯ\p/1D[ݔwgq|R?0}? 4>]vR&SPPG#D ƌ@a#_ w}~7i˵PCPK>%BPK!:META-INF/CERT.SF]Mo@; am bJiBme_q|dڅ__bڴ6dIhQc8+ &Kk+-W< qo=ci֥>̃~f,9 p DYw=a5%(O,K,Ȫ(o>--lpE&<=Krx0ur*f< ]vR<ͨ߻= s>>bg71q&bzٖB73EȃY$ +)fa5`fd̠fs[5t=9Sodwc(l^ROb{@\⣭+j}Ґk4җZ6k'm7fݽ89, 7ȾoH/ٕ^AD:ӾYw 9J-(mp_$xqnٕw^*h_*rU|?sK;}ֆ)ʣf\: r?^{8-m*oKR&Fw`s9G "`pg֐ J S?;`vӮ51~O2@k.{˙mu~z.֨m>q"zQIc+ete՞yrϼ~AT+6uxvw:T9*ylv[.~<K8_BUvg璌N*rMzzga0O;wbW*ݮ*tmŅ.kt-/owj_b<|: \y&PKhn9 PK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPK!:>%B META-INF/MANIFEST.MFPK!:yU< META-INF/CERT.SFPK!:hn9  META-INF/CERT.RSAPKyJsrc/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha1-1.2.840.113549.1.1.1-3072.apk0100644 0000000 0000000 00000011552 13243353143 027060 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:META-INF/MANIFEST.MF]N@@= 0K]@E%qQVH4 ac^:#0ʹߋDM@+hE2R~: !Xx~6{H-*#5DQvy#h+Ҳ~Ԯ\p/1D[ݔwgq|R?0}? 4>]vR&SPPG#D ƌ@a#_ w}~7i˵PCPK>%BPK!:META-INF/CERT.SF]Mo@; am bJiBme_q|dڅ__bڴ6dIhQc8+ &Kk+-W< qo=ci֥>̃~f,9 p DYw=a5%(O,K,Ȫ(o>--lpE&<=Krx0ur*f< ]vR<ͨ߻= s>>bg7h>RP H0 *B@> RaA*;P~)_x7(D@ AA^$xCFH..?.HT r~.?:Ch[!ڕ+rO`.gԠ~s[SOn4jeO-NVR)eLѴTvt3*9\W: 8wj)uȕUL>;;9CA{.YQꍁ!Kb@;s%ۦ R}h'DƋA8Hs1oqV{p)da&b F[6$i@b2o(uNzkq~UaYůc*Z|Po9Iwž赕譞++;^fr([=5$;0?᪐1Rq@!7fuX ':*6Vd{J_<ۭ/mF)'c-$3uu6Jr#²#kO ,dKƔ3$2+µz L=`ƬW!Ac=) qsl˻3T|# OV/!Ekg7k/Td.[)VH t\o:# OMGVIߴ@>|hMrH(q^ k\j\]c%B META-INF/MANIFEST.MFPK!:yU< META-INF/CERT.SFPK!:5D  META-INF/CERT.RSAPKysrc/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha1-1.2.840.113549.1.1.1-4096.apk0100644 0000000 0000000 00000012360 13243353143 027065 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:META-INF/MANIFEST.MF]N@@= 0K]@E%qQVH4 ac^:#0ʹߋDM@+hE2R~: !Xx~6{H-*#5DQvy#h+Ҳ~Ԯ\p/1D[ݔwgq|R?0}? 4>]vR&SPPG#D ƌ@a#_ w}~7i˵PCPK>%BPK!:META-INF/CERT.SF]Mo@; am bJiBme_q|dڅ__bڴ6dIhQc8+ &Kk+-W< qo=ci֥>̃~f,9 p DYw=a5%(O,K,Ȫ(o>--lpE&<=Krx0ur*f< ]vR<ͨ߻= s>>bg7S:r*C:!F-3/yQFK^nZNOD}%͌j@?65iژ w82b s"l\z%X<Pn6:RgԸ(=XXsrcPkS/F sR .B򗙅T%TQ}e&VHA}i㪻=tx?Kzӹ90Re+D{VkF Lۄy/%Fي?LijoV‰\\ic6&J71UO9[RTdnMpI>BeAgPu+:h\nu}̚Vn *_X k@7@.  4[( dFvŰ Na~ԕHoծN uf]4嚠&KNCDiJGXO; tCưWw])͂ qu*aʢ,wxrCi3W$ /.ܬ#}MBX[x<6!zEœ."ĵ Dk%Σ'_ӗ(q`K5. aa7gS0vlS"_?|hGXjfM33dc\2KPPM^Oɖ  g^‹ [ P6b۲v}0cp̡z˄ocH[G*t67>`/\ :|n&BkZ߈z60נOQCX  G$7*GN|9D)'ua]SMa^,t^/SXrC?p\jcR(Co4[!mY7w_i$'Cv?ހMC >:Gl\#{I^hkBfr0.-/&lq 9e)whǣW*gQ3:9 -N^yHOgs{bZlM*|:UX1QƔuˤV$,x6=Wn W[/'E'q ڛ{i`ƌo˺5-դZmT:%2V$[/cz!OZn=qHS3\ 'j͡3*~d/hLì+6$=b(Aʶ6'?R6ϠщP/]0ʐX^ͤ6J^Fl">E>ϙKzICd})([MLPBnPKכ7PK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPK!:>%B META-INF/MANIFEST.MFPK!:yU< META-INF/CERT.SFPK!:כ7 META-INF/CERT.RSAPKyasrc/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha1-1.2.840.113549.1.1.1-8192.apk0100644 0000000 0000000 00000015364 13243353143 027075 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:META-INF/MANIFEST.MF]N@@= 0K]@E%qQVH4 ac^:#0ʹߋDM@+hE2R~: !Xx~6{H-*#5DQvy#h+Ҳ~Ԯ\p/1D[ݔwgq|R?0}? 4>]vR&SPPG#D ƌ@a#_ w}~7i˵PCPK>%BPK!:META-INF/CERT.SF]Mo@; am bJiBme_q|dڅ__bڴ6dIhQc8+ &Kk+-W< qo=ci֥>̃~f,9 p DYw=a5%(O,K,Ȫ(o>--lpE&<=Krx0ur*f< ]vR<ͨ߻= s>>bg7u,{=<~h<" x|x D#x0aP$#@3Gh<!Ё  D ш@ğ  }1*rh^vV@+`T6y?`?Btx0gs !HBM<Erkɧc$XK/ODz5c?ݺ#8@cW-^~5Ү,oŤ )[?Wُ2x欹KwMj0SlؖFR7geCEe RUm=쮫mѴ{e/DEU ^e%U|Ñ׼&Pk>[v:eq=p8<Ն}_}OswI}}(PcaUhmM THOG#j' ``젪~e>15r0hgA+C5Tꊍ%|}{I+ T_}\`"nAI"*,zXe{(g*mp@~$zC|fT;3y=kˊ;}~wMk;L I MY!v2 BPQ2|#DݰXB07ËuFomfm(ՙi(wĥwY ^u|=&O"/r>ѝ}jSQMΦKklgy E3%6^/-'~`a%mhI膨3%\?=Ziy>KDhul(g0iL4gCqHS]{{ ; *8nc<3 Ou0,v=̏ sˍ̔ɂB|gl ΀!h: qYBt}@m*%5O4qM}<s9b(~5S*Kۑ4igm^V1a]hukddAIn~j>_fAqEb~kg03 `DjiOan:KDUH9.}O(0'7IKuyg1ƒ"I}0ײJz(k0_D5rj0iptxy9jRSf,^d͕гwy]mBˍHE)ξYyC 5?۱XJ;MԹLJ|PVte'<1gT^b!M0G>WTЬĆ0+hF1A5021JF+?1W`b .EuSYB_Ԯ}bbPh9U>Ҭ?<7c}4o~:@Ѷ*sLVzT\]'[1K#5a5äUS.ҀK ~[ )N!ohnU:ȑ_6b~ev/287Mm#A.i19E#gĝ 7nՆN~- mZZjL]:3d4ŵ.{`uɤ6 pRsкK ] dt9@ ]Sp"f֤[ s4SbP]<ךdzﲄ,'HCf= 2PHx*3elg$`qj$aբ,*9<{4 j^xEE'7@J!ĸI6"A`s EbƢx?G[͢_x0 0wR-.H'UNs꯿7KDfV%p5#ifC~7q߅3mlk_W NU YVTHOOyfRS2@v,ޒ>݅s {X=dtx ۑ=)m:#hnRCwuqQ5ܐ:JX-^)l eӄH|yj5&ܩbtMb2TyZ2%TdN.F4s1LE78T/vOWT(nv&\X=VCEY˲ϝ\#]ooF }K6Gd>8w:{Ғ7q-)Ii7ll$u OSf^l b!IMQqr jY蹾2yuXsV30>TW %M:ּZD؂ֆDH}e5ӣ=3{W9gE`l*|S r;q3FથmJOzDe'^ѾS4mږ' ynzxZI 3:@VU僧CoIOfFmNXEi/޽wcYk[ S1@y_\_tL|jR,4Ӫ!F\閗te-j\ +YWJ],R\^ 8""v̐JoU ^rZioz% 0ooy 27FrݱClsN& ɝue:xYV],Uq!ǀZ+I/=4/C)w( )sf.],YZ8$EQ|IZ ~]r՗x+V~2̤lȭ۳܆U2*h$<a!3id7z+8PK/m; PK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPK!:>%B META-INF/MANIFEST.MFPK!:yU< META-INF/CERT.SFPK!:/m;  META-INF/CERT.RSAPKyesrc/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha1-1.2.840.113549.1.1.5-1024.apk0100644 0000000 0000000 00000010026 13243353143 027052 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:@META-INF/CERT.SF]Ko@= amĄąR/,vTd%M9y/K R7 d@ɀ@yg U^feeF|iG)d8z$oq]^ih0TD&9ϔ6< 61LUnv9M4ݽs B}5WVeoqTѯ'gˆŃ@>N/؟PK!:AL$META-INF/CERT.RSA3hbƩiAS&Ll|LR E 41~LLL v<{{€OPЀ98Q@N8JŮq>ٌ ̍ L ׷PPj=\9Ƨ:ew*9V_=g7rL2Rx5v5nW/)kwN_5_okye Y>,D>* `P3Z/ Jqq,ٲ|,b,"_܉辔<+֑+- A,b 032G LfOWnXۘo i^f/9YO{=Y#%ӂ %OךR݀;I5b'6կ4$-eRĺUf;J^WY|J&}6l?tǛ4Z}"1 @F\wY8RJCMC|PK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPK!:@ META-INF/CERT.SFPK!:AL$ META-INF/CERT.RSAPK!:j META-INF/MANIFEST.MFPKysrc/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha1-1.2.840.113549.1.1.5-16384.apk0100644 0000000 0000000 00000023314 13243353143 027155 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:@META-INF/CERT.SF]Ko@= amĄąR/,vTd%M9y/K R7 d@ɀ@yg U^feeF|iG)d8z$oq]^ih0TD&9ϔ6< 61LUnv9M4ݽs B}5WVeoqTѯ'gˆŃ@>N/؟PK!:WAMETA-INF/CERT.RSAuUPFgaA{pww w''H!wުm>v %BBa" B]Ď  Ȋ"A@ B_OPv?3JA1b+F@Gqs7cq@)18aP(77>!7' { "! 7F0zM:gJO}ͿVҰ,pՇ|KǙzk=h2) /uL #鍥S>[iH2vxEẃ 5:3,rU{sIK(#x݈UGV4BP>"A9s72lNC6 >ľǧޤ6ƩuWp:52 dZz?c/8U[x3!ijoәO7#]L.P zyH 09 șߐs{^} Vb (J|ΏT7 2e#gW dE*}е JCg=b"(9o2ئ33Q~[`dɉR^~" ;M;t ŷ:u錸nD3$JfǰϽIhTf{G 'yTws+:9 &fϤ#UGK Gup6ĝۀ͒fBr[HX>260.l Q]7W#?1_tfXL~d-~jAt,01".F~ ѭM\nelq#\c\ ' ǽ\<*P6)ѕL] _H(8[:jp禺%G8XPbnN|] 3;u(!>*DL"[z1A"'ߴyzꤲZNVMyN1Um!7MpxRh|Qj׋2 Z-ȇqPI!Yy嗮$5 ?wSPl2ٝYI[*Đ.ekAbG.*nc%?Uxh8ZV Q&C(uW=kP['^dR' ƞ*fv+w;rCJZ؏zg/XUe$oShCsI㭃&y,E;;;_,$E7QGd5tam IAĖ9 ~ִr6|RKUwymXy*~ٟ[uwE QSWY8+4EЇʱp2!y& ؕV]i9_w޼(\{W?Es_!`'1,Fk"@r Y `RL &\0"|_Jo ' 2ϧ_VȬw]q& q)怑mm")ɥcALcn t`DbG8魩'D1Hjvі[3"`Y0 Ys8f:G}-]';|f T&(=C{IM}s%fރ+ar~dYv@,2G%ǣ6^:WukB$avnN'Z^Z3#BW9Z|`}{wBv{~L4a̸Go 8iVU"*2x)i'6fEX1Cް,>@s¯nZ9YAvڶlVƒNN(J@gW>1_|-IȬoNCo}\L2a-bti[vJєBz>'Mm^$:z󦙑W9 snvQr#g+mBq!yK|~"]x||5hi_+5ÑIkųlJ93ⳃG] NE :ilE68:8(zQe&P0gIs+*g?>¿̥9K]5 2_/mlVt{En]n1K]/Eͽu ]ЂA=C>U!i_-F~XaQBCdw%l2>$n/_1횾tXfi7a(q,VlgVNUS)n}Xrw9t// UJ|m)iQ~Qc 6eÌfH^flU:g{d1W:MՒ]eR}=g|9YMY^ )>YwؖӠuLO}r#} D,\kK;)awDXc7|^3HK1Z}r)[ ڃ;],uԟ" Qz, '{y6$pmJO rNE s=y,./3ONsN nOLj#D4F7@@yeEk݅m tþ3nJ6~B ix7i7fb/˟eʢ+%c㚠G r-l@S5gJ==an=]?lǹeȎGQeT2]9ljD u؆䦗cU);6h/ɳM`OUh[qq뉶I.8nYh7󰎂vK_][uDDLKaBXK!j.mʺ&m.ZbU>-6BC募4Le3Ɓ%>[`='> Wfn~1`y^}TX-.%IXTޛj(hλۛ&]8Gy<>~i F-F@%Dg2=Ruvk |lhT/g .|aTi [ opkv)csJ/W I:]c>!HֶFzKlO= \y#^nO-3{ ./Vw6$r`;)[TT=,Ulyטۉ1fz2Pȶ-syKhu$Zv6y֖.OEc*#"CjW dJv_|<1gFS "'6zt]^?%hnݽPX+Ռ:Tz?eY8M_q kHVVSv A@ͳMnEE+d#Q};%݀dټUp cmv*ϒd5vBٚT"er2'wC4rߝ8C!rPRO]RE?d.HB%A%y U+,\) ٹ oϔvk >_~ Cۇ9ytW삹NDGHK5*3#:8>fDG4%FhZlOS:&BRŧ*uҝ():~l"R4.bAW1g6]|z(|&c}dI3 [RIWRdg+=9(")VVt 煓Ș|B gHxeO' p#XpP-5&:9FC$k;rF^kSJIbqMMz4}EҥdJ4#M.EY:sywMn?CKx%mFAj(*) LUI#e; ޑhNjPpC,>w?Чz}£L#@n $Q!Ơ'VRPP^EW`,oh\E&zֳ=;0{+ U.=FSM){m|4j;ncڕ#f7>gF qr(dN?w2^NCVye^>]=؂\F,_~e T7.߇\!* Bt-1;Wd9gmxk M1sjO@kݧiB%ZeQg#Q.QW ު$.m,K)%h:媥4`K_Kנ| ̉{߄̗NXjCfRua(EA?f#Ӗya1FAĄy D(G+'lze5_ R úaC'P44gF"+L&S=YG~BtWW8ͦ i#RIP*2l5$:cA&k R!0,%U>}۱ug>u ogZ*p%ՈÙ$ٶ&d#^dJ_v)1fXS)[/U^!oB9-{Ι0g6/yU ĎdgF(g0#A#$?/?&V[X(`nQ!"1\; F h|pr0N(w"u%W'(և&c 9GjЗ 8s(*,-=/K̚ߙIY_*&2aIQ|R' edL[ KlCRX̺R6"6F?wx!۟cwCx֘vzHѺt'{'a)Mew9_0GCD p3HǶjpE~\u]i_:P=8lj|)y4 L +tX0%~ E<}ƌ'cO(<=l#Ziq"t?%,G4oBcP5wX{\*9r2-cFF8hQ;7@Ə&+z#L\i5'BÒ&'`jkX-ua]qwePK!:jMETA-INF/MANIFEST.MF]N@@= 0K]@E%qQVH4 ac^:#0ʹߋDM@+hE2R~: !Xx~6{H-*#5DQvy#h+Ҳ~Ԯ\p/1D[ݔwgq|R?0}?G` 㰑v>?tǛ4Z}"1 @F\wY8RJCMC|PK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPK!:@ META-INF/CERT.SFPK!:WA META-INF/CERT.RSAPK!:j5$META-INF/MANIFEST.MFPKy=%src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha1-1.2.840.113549.1.1.5-2048.apk0100644 0000000 0000000 00000010664 13243353143 027071 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:@META-INF/CERT.SF]Ko@= amĄąR/,vTd%M9y/K R7 d@ɀ@yg U^feeF|iG)d8z$oq]^ih0TD&9ϔ6< 61LUnv9M4ݽs B}5WVeoqTѯ'gˆŃ@>N/؟PK!:䂅)META-INF/CERT.RSA3hbajhδ%נ%ѐۀUIqA_&M03121q2en W 'l(hQTkd`ba 'khf`llhhbjnb%kbl`ahbĨl8#+s#?PƒIs3tVyid?\TcHr/NviC1P;4qg}#Y͗]:?q^lľ^'aڤS{z~/_HݿNE)߱j1ŗqǔw4M0XUC#yJ^^֫_^m^ӣ„D։2bWt{%1 l"䷁JNQqĹKO.[}VžM].ll/ɿCQ_:^gbfd`\`g :Y>1q&bzٖB73EȃY$ +)fa5`fd̠fs[5t=9Sodwc(l^ROb{@\⣭+j}Ґk4җZ6k'm7fݽ89, 7ȾoH/ٕ^AD:ӾYw 9J-(mp_$xqnٕw^*h_*rU|?sK;}ֆ)ʣf\: r?^{8-m*oKR&Fw`s* O9)!%++K#Cy+g_iM7ec[?x->h#k Sc _L\Gٮó7nM2mƬ'~[‘?#={Qy@ZKҙG]8:7Q#LݶROI퉺?Z\|3sRcvhnb+]=sy+pq:.?Rw\:}I\jwf.8ΦrPK!:jMETA-INF/MANIFEST.MF]N@@= 0K]@E%qQVH4 ac^:#0ʹߋDM@+hE2R~: !Xx~6{H-*#5DQvy#h+Ҳ~Ԯ\p/1D[ݔwgq|R?0}?G` 㰑v>?tǛ4Z}"1 @F\wY8RJCMC|PK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPK!:@ META-INF/CERT.SFPK!:䂅) META-INF/CERT.RSAPK!:jMETA-INF/MANIFEST.MFPKy%src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha1-1.2.840.113549.1.1.5-3072.apk0100644 0000000 0000000 00000011502 13243353143 027057 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:@META-INF/CERT.SF]Ko@= amĄąR/,vTd%M9y/K R7 d@ɀ@yg U^feeF|iG)d8z$oq]^ih0TD&9ϔ6< 61LUnv9M4ݽs B}5WVeoqTѯ'gˆŃ@>N/؟PK!:rMETA-INF/CERT.RSAmy4 fƒq;#7,vKBb4%䤤4AN-J[c_FLftpZ2!ո]sO׹??< )IvHH` ѐ % q4TD ߂hO *xAwC@2"p".)v.&HRxc@FM}RFoo%P8i@&l ҀkA)BR$$|zΈ2;F^Sũ[ZlG}BMVM9glwW{֌ ^(Woc/DqScDaV [ 'b{HaM)~ UӚx8pQv ӶttwiSo~V1ϓ OTIQϬPꐴF`nY=YXu> \[`Ȓڝ}ȁ|j=ab!x*ՈEjyӮ"!1go+tUƉl_}\:Z &Bftk±sme=g4wV-M\PӀ)ހz ^=n=X/u"70 %!-V!,oyzz3:qjWjHXS H~P${(7VaZ^:̉<ݘϚ^2ycZ;wn`(/iQI$CncKt$6и|q4ɗER|֊N7$oȊ| 鎈#$C67-5@{KL< F_ 7vhb y 5)b Lw8>[!<*۫{R@'Pla٧ ,5p!Ǟ@:"r]g[6uD9RPUt!=..$STHkJ_X >6Vzs1qYlJYp6l jP-ՃT]M6.^/V.hv}'Co0H+} cW#232Kk.6AIn#snO8۾N He[Qݺ3v-HPm)3#dpr.Js W=ϡ^r)n:luC؍[{0=A(I*킽`Nep*Pݔ\9*8"+M* S` KGqD- 'Cme-Wh bQV=-)^G/U!k;[Jyj\n5цQecУ yD{?0DoeJ'[ 3E @8@tu(3G}cY;[qp)SE<붬xE]AϟeF2<1~(rPK!:jMETA-INF/MANIFEST.MF]N@@= 0K]@E%qQVH4 ac^:#0ʹߋDM@+hE2R~: !Xx~6{H-*#5DQvy#h+Ҳ~Ԯ\p/1D[ݔwgq|R?0}?G` 㰑v>?tǛ4Z}"1 @F\wY8RJCMC|PK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPK!:@ META-INF/CERT.SFPK!:r META-INF/CERT.RSAPK!:jMETA-INF/MANIFEST.MFPKysrc/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha1-1.2.840.113549.1.1.5-4096.apk0100644 0000000 0000000 00000012311 13243353143 027065 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:@META-INF/CERT.SF]Ko@= amĄąR/,vTd%M9y/K R7 d@ɀ@yg U^feeF|iG)d8z$oq]^ih0TD&9ϔ6< 61LUnv9M4ݽs B}5WVeoqTѯ'gˆŃ@>N/؟PK!:I3>META-INF/CERT.RSAmwT7bBSD(UP"0D+*+$(h *H TF(!  {h#R͇|{se(cTeAL ؉Z_  vJ<vF m:rxS@531U3!fx^C7t yaC-_^h4 |ph&?"&"9b`>_hԲnc̥XF&׻*z+N @]jԀEX/9\31 "mKʙ:}~Ny6AOzynq9yPKˠ&^Y;=Uyg#buY5}wG/i' WU;[\J{-2{Úr!-}I~w4ͷxv%RiY;tRf;mԈ5ZPzXM6{:.9cJU' <-vCW>h\n4cG#[S de` hZ)S1O}c_%moXs,=o_.zvA/ҙtnp#.boŵ&ڤr:*~X &sxB7AWP{=:X\eaeŭ;U]c],X~okj0e8Ak 4{Ԩeo.RxF?f3>S#dXS;&7 h[pj\8ɥhަ̆6> ,O5RjH+j3I>MvbxZҴo<<{%[nݛk],I{wpSېsҲA3/q6 tzOh_<9v,r95ϼF-fp2(Dnya?oQg C2 ӌ!}r2dotca^ݿ-v"/:0j#ўob a8_)&d<_iݒ[)mF` YB p:wίE6_UOYOoGc}C$evah&ז)?58*rJ5<+% fEҡ~KCΛ[1߾,]MΊGYOzV!~vN/نĸaʲٯA7O F5.l~H3ɵxw'[\?/)KlG: *6FJ;Ve]SGjGLVQCP˟ )i%Nsً&p << 8%ȳ9? vQf0Tg^Dz>0]4uEVyM&s2ĞEA8Єҕ4+ԶA!Qb*a֜%:{LiIBUhAD=bEVAZKB,'"[;|U2D ^*,YݨV[96p]oW{}%ܖ\ahplpDd&n7 ) U~TPK!:jMETA-INF/MANIFEST.MF]N@@= 0K]@E%qQVH4 ac^:#0ʹߋDM@+hE2R~: !Xx~6{H-*#5DQvy#h+Ҳ~Ԯ\p/1D[ݔwgq|R?0}?G` 㰑v>?tǛ4Z}"1 @F\wY8RJCMC|PK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPK!:@ META-INF/CERT.SFPK!:I3> META-INF/CERT.RSAPK!:j2META-INF/MANIFEST.MFPKy:src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha1-1.2.840.113549.1.1.5-8192.apk0100644 0000000 0000000 00000015317 13243353143 027077 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:@META-INF/CERT.SF]Ko@= amĄąR/,vTd%M9y/K R7 d@ɀ@yg U^feeF|iG)d8z$oq]^ih0TD&9ϔ6< 61LUnv9M4ݽs B}5WVeoqTѯ'gˆŃ@>N/؟PK!:07D META-INF/CERT.RSAmgP HBB&%J JfH$1* H"4Ei"MJC)C=;~;Cb b`3 "<@@ D  HY#ҁ L%2!a{(f$BgpQA#8`(%"R dST@ȣ]%#A՗dj?%'ԕ]c!]u&U [J[L%t)IU?+5bF l'Q-;=OX>.E0rKl:1*G!eH/Ѥ{.-΅Uq7:}7_k[CF3BY,O$3#]3-Sd](Pqh _Gt[Blk!WDCϮJB{mF&w[QH.UL,Wo%40l%/_UnTҔ 1B\XޖxNu5AjLRC6/~%*IAJfRYT R"hX(?AlrqMz' Ô\S(k֟GpP"/tR5V_qHրЗ;D&TA <SԾ}_0Y»"IH2#C/BOW7GTx˹[_КR ddiRl@ږzhڹO{h\۝ ((EZ|_cMNSkۻ G#BVsN%EnuX7UŠݽY}]ZX? d_Z]#AheE-Sܔm-*:3OtI7jCZx_s7}޺SEY{3^Zh'Q:^[r2+XJ:qx" 7 A.42٤L6,Q.!2#_ ktB/GkȊn~<#ԤYȩ}LN9ZԜc6'([d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:META-INF/MANIFEST.MFeMK@X,~B?6jY5 -L6323ۯ"( jǔ1jxfڎ@^hzD~ײ$K)2a+k]nvm?!PV|Xx% ޤeՒ74CNA\7~"HL1-NݜQG;%9 jWٌ ̍ L ׷PPj=\9Ƨ:ew*9V_=g7rL2Rx5v5nW/)kwN_5_okye Y>,D>* `P3Z/ Jqq,ٲ|,b,"_܉辔<+֑+- A,b 032G LfOWnXۘo i^f/9YO{=Y#%ӂ %OךR݀;I5b'6կ4$-eRĺUf;J^WY|J&}6l:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:META-INF/MANIFEST.MFeMK@X,~B?6jY5 -L6323ۯ"( jǔ1jxfڎ@^hzD~ײ$K)2a+k]nvm?!PV|Xx% ޤeՒ74CNA\7~"HL1-NݜQG;%9 jWqrsC(08y!nN.n.N" n.{=T02Ϳ #cc Fjh`Q{mݦ&c0#ZE˦U/-U/g­,y@fܒߣuC]0NurY)oPA}V Di-JEGnUMSH4|Ksxb[En Je.L PA>OWnm#4Z~ CflZPw<>+kI~z+\=|{_z~MϳXɨoMp dg;r^ՠE]?%|_"Y-ȧ0"J}"t^ K cʲ): 0,.AImӛn!MKꁇKt?,et1J&"3.8՛~#'iҙ)F|U (go?i39 ϜݐirAD .z?Y a1GS+!&Ҿ60?uF!u9aK9H(_aAb1GLL4zCb[+^vwDfwiڭmB7Mr#ȵ;iy[ܛZ`nm~wvRj<DX>a62hvw;3}Ki`Xt}%ؘh`dMM:f^,RGƷZ4ѝfxe1#ZGE]g!XdŶBcz^4a)R+l̴o2G}ɵ#FxqQ.:o TIa& pfA7v0s0κL,j،bkkeƞ7(Hgi,\ 59񩒊EThd]#ibQR|&&TgլNQSI_I! sr#Q|?8D.Ϛw>x<&Db vT}EaLiq5|t䏜0x *uLOms: UEGI$1}&dvz ,,gSF !ge`=MnNL`AeЏ?3 y"Q! aUUj&|)jS~&hCPζ\&T*ꏩ>'!W~u k7ymG򌍒mݯȪdBi?* z% ]PJaB(7TsťDXx[[*OT0N꽃@7嗑m!i\'-vmE X+CV b%IMf$.K2!%J*82@mbl6>M]}ٙ)~uW4G> >U*|e3Q Ntz?s,LHG`qA.vV/޻qm_mT)\ X-ݓ!,I}PoįGEmU7F!XXh5xOE[.kEۺVp!b9RF01:G\UXl?sAx)Ojld _ pUg7c16W@1((}U\9R0dtUpjٱ̂)Y "^y Ǯ=%”%}Nm;N#ąDyt1c*omaQ$8mT"O!&qHmXņS70;6[5CBv][}xk 7_K~hn.^ܰ2;?'"\a7;\ K}[1:et4rydVQ}aw/du.ƬhUh103%֭tb@8@:DB@ "QdJ({є,@X!!@'BoQiY5  Q^QLuZs!$q-%"T*̢cFO Ua ta毅vbuP3ތ[ KbU!ЖXf܇BշaN.rz:8ni4wtB+NaW%,vIl_=Km23(.;\li#HtH9۳:V"K}k2zEJXjV##5BXJ ַwo׽xQ79Oz/ʐ qF[ʁ3N)hxWaecUomFmF?6'H(`5t>.qdg-M̩ LoߐF*n !~ FNʞ]PD= },}Mӫ-e\>$!&KƘaVRJ)ÓWvJM[O,-j.3msP K?YEŮHKͅܬ.)V,͛hC"जUG=>98O^1DijOk7gl5;0H"#6RdFO+zO3dXK[$=O]HǙXI+4hjטEx}t+83hĦ iXrm5}g+nؽQp I%wΒ]N!Ļ\L7PSR3m_nՇxʼn^| TĄ4BMF|?Uz~h{Ӡ0ݰM񁚺ĸLtmx# l Gi/!Fث hMZ[Ub㙢-4L'/"<|rvWx1!DžrDǠD6.!SwC:*蠝PFV Ճ󘘕7bhaU,k&/)yp rh8\ԡ`nW,'7a~?DJX]:BꄆEHӃsK])93m`౮^=m:09ոf#!wJha˔KkpMYM'yHDhɔ/u_x`.=uq {j=[L2)YM([h7C1sSN8)-X);2wtӝ:y@)qdSH'ٴTgGgc]gFn`BHmn)om U9dm(icǜaP HN.9l ET{] \'?}Ϧ5YݑaWsv|?lP9M9N[бƒ>^[Ss&CiiǗz)hJ!'вq)Mpen"kIXJuojM*?5&YIot FV#!E]_ F 64ۉƊ 2̘-\mwݙU,#M l( ;T1hi#F`ٷ ~砳 OW%'IRM>V0ŰF)׍7+MZ9vЅ{y=_&BrY~KFMmlK\R,BTZM춌Gz*ơ`sn:$M=v XJ#cY|0)I`j@|km`M,Qמ3O_xݷmh=:gZ;)+۫Ɵ5 ^%(2>oK<3N>ƣsf<־fQ+f0EsZ(}`Uua"+;`}w&U-ؽz>#gltawܭdz ;ϲC!Jvr)'a$ٹ)cKM(o" 9qP{Sa=5wwWK,F3O/5p6.@'ioū3ylȒCbQ\5 ,m˺C2AFM^1w e'Fj,P;N/fq(~.)pF$ID5Q\+A28G,͒ܦN3j124B"1q1?=ol,5)gM&PKC=PK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPK!: Q META-INF/MANIFEST.MFPK!:.Y META-INF/CERT.SFPK!:C=F META-INF/CERT.RSAPKy%src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha224-1.2.840.113549.1.1.1-2048.apk0100644 0000000 0000000 00000011070 13243353143 027224 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:META-INF/MANIFEST.MFeMK@X,~B?6jY5 -L6323ۯ"( jǔ1jxfڎ@^hzD~ײ$K)2a+k]nvm?!PV|Xx% ޤeՒ74CNA\7~"HL1-NݜQG;%9 jW1q&bzٖB73EȃY$ +)fa5`fd̠fs[5t=9Sodwc(l^ROb{@\⣭+j}Ґk4җZ6k'm7fݽ89, 7ȾoH/ٕ^AD:ӾYw 9J-(mp_$xqnٕw^*h_*rU|?sK;}ֆ)ʣf\: r?^{8-m*oKR&Fo`s-G $4Pv޾̪O Ê2ϸ`KϾm;ӧ#^??9z޲+.-4mor_~I.PrʾP:[d醗/}͵]E%U. {=//]T OY_ʡGEU>޶Z@cmgw:n*LڷOyo㛧uZǕ"Fm:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:META-INF/MANIFEST.MFeMK@X,~B?6jY5 -L6323ۯ"( jǔ1jxfڎ@^hzD~ײ$K)2a+k]nvm?!PV|Xx% ޤeՒ74CNA\7~"HL1-NݜQG;%9 jW>T\s.c(YC@՜Fo џ,-4m^-.(8seTX 1PS*vbM*6 E|Ƕq#}ᥑ(~)qxZ2.zF:;E r{Ӧh\?A>ZQx ؕz^©* pX%$֛ wM"|$0"Dmm4w"3-2zn?cGtzo[1VٯK̏ˈ^"Vz$@%cSN(ڱ,jR R.=$o So> ^Zf"W4\-].tHd\ol>ÄBB!١J,HSS\ClYXx^Q'[W GjM~K(Qҟ=[vqՄRQ1,䋀@j*4&f{c wmI&V Pmfo&S3ّX*]y{")2B$Jڿ' WӢ̏A§5CTyNU_˧ 'GQWٿPKrPK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPK!: Q META-INF/MANIFEST.MFPK!:.Y META-INF/CERT.SFPK!:rF META-INF/CERT.RSAPKy9src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha224-1.2.840.113549.1.1.1-4096.apk0100644 0000000 0000000 00000012515 13243353143 027236 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:META-INF/MANIFEST.MFeMK@X,~B?6jY5 -L6323ۯ"( jǔ1jxfڎ@^hzD~ײ$K)2a+k]nvm?!PV|Xx% ޤeՒ74CNA\7~"HL1-NݜQG;%9 jW}^ ͫA"EBy' $P(ZDrVЫ08炀4|$Y HPKfJ} Bs(0( sG"n tA9 $Z`h-}m]7 6G~(s9 #9@wO er9$ҵ;? LD=%{p'r_TXҁZnvfwh#q;0Z2 [z9J3)\0 !f4F ?Nlc R'qWNκ~~z>.hSmr-kνAg*_#gF1BygcX(_Js3j8G%ϛ3DBܲrܸ>sm!Qk<HPBu7ԥ(|’[ Bj`;A!⋠({XY8\Vj8!Z6Nfw (8BUhط#v^-`}0eU+svOI,UF۬g] s gEZrǼdC[:vl0-Y٦fwM 5D =ʂJbJwcB}yK}"R1X"gڼZZ]9ɀK:#PӍN:<2{N̊W]S%vZ[hƼ>s`&+5|4ؖ!}Oz~q,JmrPJ052:ȯ];ۿYmVL5~6O̕&"/wTg)I 5Tp@΢O,\@,[OR&[0U3$$bS#=E6 ĆtXFvbB9w *r>'/hy0< S gRaskXMx䮲I4a*}%<2rWwfeZ]FWE/YC[ &CXTe'=`763f[[9JEWWOǮԐ߯ '"9AyhLا%ZVȳi$g3Jp1n,.z_馩LTYSiŦ>e{ŪCm ްS}ޔXBqUֹmYLH-o ~wg3X­ n{I!too +kUêFkhgѭJYI VaAO4ur/cC3܁حc'0'BIKt{EBٚ05Rjݩe{{wCITGa%KyjPN~l탎s"gl$T*(坫Sj %v/F5f*e}J t/]]=2g!xw:-dݭr6!rlEtY!/u\$G1Wi.r|q)VׅKR/PKV"9PK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPK!: Q META-INF/MANIFEST.MFPK!:.Y META-INF/CERT.SFPK!:V"9F META-INF/CERT.RSAPKysrc/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha224-1.2.840.113549.1.1.1-8192.apk0100644 0000000 0000000 00000015520 13243353143 027236 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:META-INF/MANIFEST.MFeMK@X,~B?6jY5 -L6323ۯ"( jǔ1jxfڎ@^hzD~ײ$K)2a+k]nvm?!PV|Xx% ޤeՒ74CNA\7~"HL1-NݜQG;%9 jWMƯ7~^$,j0 8H H(fvM5ܠA tc:@@z djHRs?9N$; ѳ2aCܔ4Z^PG#TQu5*?p ~`ǁtg8 '4 ϩv$&ShA!7+ϹSA$a>S}~~4k7H/,[\p[(Јb՘W.,塚YT>|<ϐ`[|΢M@&ϯjٻ-<^'q⥗? Q7$D=:0GT񣆢]B›ZL^sϑ[5V0`wv[A̗Qᓛz^Kpcy>`c* F;; z`"$nrUfJs*JܴLl"L&6i%_[[>t_s ݊Kxe؝K(8]^ٹh)r˽NUg~rs,EPv[1GYTS[iĺv[HED_`KGI1l \,BLZq]u@"٦D'}ޤ} .sSbf;g}& @b V))e!(yLyL.{F#:\ȩHuUكAK12#FDZcX5dz3ځ؁lMYmM-Ǔ.Qi =Pf@a69m(䷤Z `>B'A_]5޻6 %$^ۂu{v7O,`kռnmkKD{ ȔӢP-2)mv9;h)970Q5f*<yT-V{ʾʚv*N9 @VcC3DAϮx2.DM {!tag_~XThp6LI iKP'RZ{vC(]$߃h)%?Oe2ie 2'm.FXڥi!\~ '{HrX9H3^5s903ű5a 'Wsg͓Я2%Ef:{,sL>(@|`}`qhk+y+CbHc4Zq6z( y^|G3OG[zo 0y#f*ޅzxw_{I<tUNר<7=OV`BT\88eOM[q)'J$ }OR|UC`17ݦ=1&D4!PNy'jh3V cuOF34nWǬ tȟٞ9z Zߔ4ַv%)t.܄4q *H0Ȕv{]ooa8T-C%Y/3 -L̐2} &l:_ ʬr6f*>ox5'x =zUZA~zbhNg'fٵER0UĢ7e8妾w]?? 2hv1\cG?kSEᐄZhYLfvk^1z^Wr2/\^|sǝ9˕zV(PPRmђ ,/ 4ih\zLCsd bx*u4\QU%Լ]ܭ{\ ~!?r-RF!q)!h+gDmR0 . [ O9ɼR - g9 /rEk&?q8/9sXkf*Er_"w2,S̃W'Y+D>[4 ac#P:a.1 Y%ʁgeu4'Znт)xM˓x.j>£(IU.C'c1BpM= m'Ǽ`)ne 3slC2Ou E_ȮtjR6%vi )\T`l-2%ʨ^:պ\&cP+lT(9tSEıd皍{UnJt2P #+hKqeY3|KFZ[*Ĝ<@s@ |"Mחۃ.(m[krpzᴡ:Ph4{";DNV'R(_0E/SX:+On9gL`R dL[{Ɣg)8A^z2Ǽr ^|1&/t~usCY rfj*^RSӭ1|hiثvm`ܔrخEa'vbY ]B{? ítR+Py&:φ+$ sgUSeEsQwSd~'E(X^ˈ^>8<ɢ%*bo {zbHh z^v%6Yg4P_ N [t˗txrRY\o⥔d+qLZ'֑uXz1aS*vdߛS5/O^V?}]qx,㬷U}q}W8sq"}SƐrvvK b%a[J5Ԁў*aN2.V3,!)ҹ2!< n_K\WnPK< PK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPK!: Q META-INF/MANIFEST.MFPK!:.Y META-INF/CERT.SFPK!:< F META-INF/CERT.RSAPKysrc/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha224-1.2.840.113549.1.1.14-1024.apk0100644 0000000 0000000 00000010166 13243353143 027306 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:}SMETA-INF/CERT.SFmn@= 06DHIP+Q"MLٜ==PԌ(aF&K#Pح4?41+(Eس,3EKτ Ň9=Sdv~®#m͛kc|aF9d^eI60#a&KoXhpXO>uq@kp.# ~:#R9*&_뇨 |񦰽ȩ® *;8>fG!GiC^,^P[VTIYܴZP0;PK!:UבMETA-INF/CERT.RSA3hbƩiA[&Ll m,L,  41~LLL v<{{OPЀ98Q@N8JŮq>ٌ ̍ L ׷PPj=\9Ƨ:ew*9V_=g7rL2Rx5v5nW/)kwN_5_okye Y>,D>* `P3Z/ Jqq,ٲ|,b,"_܉辔<+֑+- A,b 032G LfOWnXۘo i^f/9YO{=Y#%ӂ %OךR݀;I5b'6կ4$-eRĺUf;J^WY|J&}6l:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:}SMETA-INF/CERT.SFmn@= 06DHIP+Q"MLٜ==PԌ(aF&K#Pح4?41+(Eس,3EKτ Ň9=Sdv~®#m͛kc|aF9d^eI60#a&KoXhpXO>uq@kp.# ~:#R9*&_뇨 |񦰽ȩ® *;8>fG!GiC^,^P[VTIYܴZP0;PK!:! EMETA-INF/CERT.RSAuUP Fg`p]] hp݂,_Uu>_?BH#Xﰀ(!R/  <\hB!0Cp_BpBPwhϮm {Íυ BD$6$BPi= !ɦ"cݮ `8ݒwmQNǝ4Px& 5Q(w_q{WrOAA8.lL8OA _0krwB^S^6}["es Ԇ '|_>Ecl{E{#V\:PO08'Ĭh*g3Aj} S5-Ph;~q쎠&q]s󔒸sE:WyIPqgU؛.J~*PkE_A 2# }|9$E:a;FR_Y az,ɇn[: ZOTc-WOk #Jt%FxC5MTrrY|w4cЁ-/$MﳍHW$麂B(a u&)} ~$Ӭ*UTz>$+$$nSi~l}#Um Ōg()߯AVؖoG3$ZPc|F\f;E0`lN4P̉?Òt[+~8ݛDBuڣ."!M>Hc;4'?bWn]lMx'ONRZ;CBdbU/}&,@@I"ݝl}WZ?)Ez _if:a?=rߦf\ =BN&!+c6mlAEܻ,7ڃBΐGE=W#AK[yh5YUy˜'[ݨXrC9CuR]q[9Q.+#lZX.;c2<tqaǘgI8K>V`멹gG>.U1 & 9CZ#*ܳ["YHb7O 3)[Q5ѴBO4!pp458NyR^(g '#uHL. 9Byqսb^6?%ݒlE0}PؑťXQ|hLL۷}ۚ\^bS.P?͚lrx1̣ o7D}M.lكH"(*+^C <(jH\aJ狘[>̬;Ē.ZIjkehh?b4fm'pP,Е*08wE8d-ӗlQm;p-n_y_ĕc@)nƺ Ns1*J,};$!=665n~],8١jo lhEY\?kq|~+)$-j5Nf5pBb/Va ,3fZ@t_;Ns(W"[6tt=X؍#Yˮmtaqm# " .5.62L6V󂘶l 7X3(0@H\@RTTke=5i:罜=}7"r;*N-)DԾ;98Ʈ8^:vZx>}776`Cd3i6<{8][ׁC}=t{[s ?2NYπ냓Q6kK `1_:kQ9FecMJbruz5v8)/uU~@wHbX=N*G ri~!6k$Ypy-+ծD/Gv vL,+3 t7}1A$s^L _U75;;ϸelc%θ|ym׬4q4n7nِ'LQN܈%rZÜ5KY3A)](UȂmkǢQӊ'+t'QPrS|lr1]RoX3bʪQTH>y([ ̡s~n9Yj'nKDoAb'D% i|8*qɊs\%U~E3TNvo dkrgm=۵2glWSf`VLYϳչn\PTP`RҼIq$@UE-sY޵ Db> _<)f"T6EX+7Po,\)ڣ~#|kbQK/˚{M2}QxGoA -gFƖ- VO?l0rci 0e}baSyY~y*6RkANG0tk;zzPJ /L/yg۞ex)R/C>ō{{b̡#>W,h7ˍjd< Sɽk}W*Vh?)gF@d ^){2Lo7!ٍP>Q{U_%+ ^^ θmou ㇴ $[ԣ'gJ5-Zb^) ՙ2YeW'yz?1DoCtor6Ivr'^a.5/b2Leo,F'` I80b$SLEci+dG;<6 1vC#8~x̷{S X$t.݄@6Q:`xǠ3mkSM &%0 X6%qIEPhBs#dNvo |*|y|Vxd %.jҳsⴋ RB[ԾEwEew"5 TɏS7 ڮl}ٜte'(bW҃5նlrLJ'f]*q,5L݉^Rԧ#W.ɛ:ԮURse3Ogh} G:hNPq6^~mf>F޴پF?Wb.AL lCnTo[,$D61WpPnuVC LmbYxjqq+#5M_ N"() #nx1dzx}&Rꤦ{M(R2lw5ĉƭܯ9 :?Xߠ=/*xaR!Й(0b#leƫ . кZw&&lwq6G3"|" z>g_[gPn#K޲ՔsNB<~'SqʇO5R̷=`/8# f|+`:N_Tyͯ!;v+\4i`,cn`}M%tc8msǒqHBz ARB^UFKgeRiFD'n.~a_o ھ5‹̪8F]Z v>vI;_Q86hMAоq#X7-QEJZ&ao;RʩoclJ'OMAI?")4 pp* \4UW_)%HP*y܁ostXp/>&(~kt!x?9\vxٸl2mP(@xfx:YqnKso=vr۽%+ 1ԯhQS t$x(x(S`-^˯9WL߿-u@zIc~YiX@/(FNQ h!\KR!SM@&wsU<`Z> V$4N1F<(XgU@S~r7Us=\}}FAS z7]yx[)v?Lx7Z1]-K,lD~O,A=O7bYzPqt&8t>nl6۝+ߢ_pۜNd{>5隉[lAL䱆*7]pa)F U%gŷX _ ˴~J-o щ ?I;Ӄ^-HpxIׯY@^jG:!iZ<3F@MdzhxGϳo}.?:^*N¹I&;lkG6vA2),dUʣ&tE$Jᜁ>o.2ԍLCOo1#CB8RyOcp#Fe/BԵwFbJ RNsQ.HdlKڙb&r`A3nA`X`;Vĺ# }7l.1AUTT}ߘx2N̷8F>;#9h_;o$ wvЈRə65c#t;fkgf'7f:M!uy긹DcDwկAYCSѶO<ՁR,6l*HgV$2d0bb"! B^բCtm<ݔE)sNg *AujX,dSc^^$Y sfcjq爒7ń^E+*:Y!E6;J[̦W*ܘu<~6qi GhXyDL?WX6 @Ki:w "q'?m-2$g.ѶhրD򏶤/d{i}<_(xFfX=*Zm4*W6f /Et<:FPx@V NRR`k,.'Mը/IxVsł۠BR5G->J%$-,/g-Tk&_5A#q/0{1X_"ivV{MD,m噹HA"E4',s/>f=/ڏ!LO> Ocbܡ8D 5o%ɥ1Rŷ6Vtd`H]|N:i3Bъ6!Pzh|ƒMRvw@:?㶙B/E㍄Y&-WMo(Vɩ{~%]t:'[*f?0T0}u[yIF vu,Y]9qfr[dwГQ oп :{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:}SMETA-INF/CERT.SFmn@= 06DHIP+Q"MLٜ==PԌ(aF&K#Pح4?41+(Eس,3EKτ Ň9=Sdv~®#m͛kc|aF9d^eI60#a&KoXhpXO>uq@kp.# ~:#R9*&_뇨 |񦰽ȩ® *;8>fG!GiC^,^P[VTIYܴZP0;PK!:pr,META-INF/CERT.RSA3hbiajhδԠѐ߀3̓1qA_&M03121q2eU 'l(hQTkd`ba 'khf`llhhbjnb%kbl`ahbĨl8#+s#?PƒIs3tVyid?\TcHr/NviC1P;4qg}#Y͗]:?q^lľ^'aڤS{z~/_HݿNE)߱j1ŗqǔw4M0XUC#yJ^^֫_^m^ӣ„D։2bWt{%1 l"䷁JNQqĹKO.[}VžM].ll/ɿCQ_:^gbfd`\`g :Y>1q&bzٖB73EȃY$ +)fa5`fd̠fs[5t=9Sodwc(l^ROb{@\⣭+j}Ґk4җZ6k'm7fݽ89, 7ȾoH/ٕ^AD:ӾYw 9J-(mp_$xqnٕw^*h_*rU|?sK;}ֆ)ʣf\: r?^{8-m*oKR&Fo`s* Oh99'!'+K#u+<=-J9&h֋Km5k4Yz`f:gM-o슫-wj%*2r߾/3 )^I?f^lγ0[LְY<)kܲk]2'+XsDNYݬw*gȬ=PzO7WNGpގ@C/&qpiҞ;VYӣ1xd/_emq9+aϯ/ol@e9gV{rqW$u|b۸^yVXPK!:DmQMETA-INF/MANIFEST.MFeMK@X,~B?6jY5 -L6323ۯ"( jǔ1jxfڎ@^hzD~ײ$K)2a+k]nvm?!PV|Xx% ޤeՒ7t Z.9?^-$bcy:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:}SMETA-INF/CERT.SFmn@= 06DHIP+Q"MLٜ==PԌ(aF&K#Pح4?41+(Eس,3EKτ Ň9=Sdv~®#m͛kc|aF9d^eI60#a&KoXhpXO>uq@kp.# ~:#R9*&_뇨 |񦰽ȩ® *;8>fG!GiC^,^P[VTIYܴZP0;PK!:U̅META-INF/CERT.RSAm{4ywfS5Xф1vRC[}X rP.tX:-+$%܍kJdS(ɝqb-tvN>cP>BqGAf F@0|qD!Qh$?d6C0%DH$b;KeOvuC0@A+.j$H !H%jjI i ""ٿ@#@HA0x/NNX%'_pGz--7VUMM LFuэr%b双c/g:g,oc}vl/u\ez&ldO֔Kp/m܋ULǪ@t֢ri%Kw󳇣_Z8IPRjBrӭu5'%<>s3[(fA =j!^lf<hFR> /yZhVBox J#D[5sDfMt?T`V0c+N`QmC2dz3d(f=+XzQhipMG5ƪz^+T̈́ ,)ѭM9IzlU'Q )$ s9۵w%{Z8f *.{cY]}+D=@@=W^=/7&Rt5.eLm)֢;$X1L&AvIPN= obWz@6%Ixs8[E"zvsFʑzf%|5s"t37_&<7]qeGF?A^ 3sx^mlKe(KdAJ߁gkG811~mP:<,$7gu(ReLwշ㼆 ҷ8|&zvsm Hn_ZEK7Iy@@J8.fDܕId5 8K،SevAĵ =kQGhBޟ,ҐBiu|d="\1yXQBhļaHenAcZ!G*7!'fW,K2&-[}djp'[=+x0GeZWR{P [?+s WBղ!^aϗo^TaǤcixyu8E2Y࠼<Հ%+';DX/o_87SC37Ndpf(Y@;ȩ7DF]ы~ L~h㡫LڶD.9z >"rTA#@9_PK!:DmQMETA-INF/MANIFEST.MFeMK@X,~B?6jY5 -L6323ۯ"( jǔ1jxfڎ@^hzD~ײ$K)2a+k]nvm?!PV|Xx% ޤeՒ7t Z.9?^-$bcy:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:}SMETA-INF/CERT.SFmn@= 06DHIP+Q"MLٜ==PԌ(aF&K#Pح4?41+(Eس,3EKτ Ň9=Sdv~®#m͛kc|aF9d^eI60#a&KoXhpXO>uq@kp.# ~:#R9*&_뇨 |񦰽ȩ® *;8>fG!GiC^,^P[VTIYܴZP0;PK!:D (@qD80 UعQ` )aO̷?rP (rTu@q>5-TSTr@:j=9^eph (d݄+|m6e).49n;<Õ-(aPVn);?|ԷҀNK0;/{j/?fPnYg c{^ v&و0K\c:q&8~>QFf. q O/؈MDfJ&'|V=4g~|vGSn fӳ,y2^[4r=N Gv'u,HtړFh FɸTH.jAet{kO9fUYU*Y|gaAgG 첗 %`YnKPiZYMvG< p9^=waLGzcO~s"[ldUKQG.wDH .֡ob1a1 Q`I:niOߍ^ԯb*VR# ժ҇TkM򩨮pqpՎZf{(&7  k.y4vH|+\LD?Rc)Sȴ6Ct"xWSTCe;Ő<*̊OFe [y +k4H9+~. e.!\IF<Yh;˸@My,.I*$8Υ#Sr?J6O}gq#ݪ)">zyl~v[=yOkBkLSSӼȎU@xʿ/<x+ Zg45<^a1e,xm2U7ri\@kʰX7Jq3`V;֏+ Q+؅'Ti8x2Ʊ$+Un}٨Cv xZxu 6rp" .)e'm[: dV/;qrL] FJrcի#+t$EˬjlYC~Gey/ wWeKb^71u !>gSDy!su,pS`v7쀥}%8HINDlH;e8'Ʊ'4^c~+=p\Zi;LwRM(M\ 3Qwl3CǙw#S? PK!:DmQMETA-INF/MANIFEST.MFeMK@X,~B?6jY5 -L6323ۯ"( jǔ1jxfڎ@^hzD~ײ$K)2a+k]nvm?!PV|Xx% ޤeՒ7t Z.9?^-$bcy:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:}SMETA-INF/CERT.SFmn@= 06DHIP+Q"MLٜ==PԌ(aF&K#Pح4?41+(Eس,3EKτ Ň9=Sdv~®#m͛kc|aF9d^eI60#a&KoXhpXO>uq@kp.# ~:#R9*&_뇨 |񦰽ȩ® *;8>fG!GiC^,^P[VTIYܴZP0;PK!:OD META-INF/CERT.RSAmWP ӈDB (=HP"HQ:*EAHQP騄&ň[@D:(ޙ;;xχ!!0B>xTBBbH_EbP} L A` Cb>Đ %`^TJ% rXv  6~rX5E (cjJ8kFUH>2$gD5th{'փt=㟻[ c?? 3QȀ["cavZˊ_]+@=J6}GOϗ!XUEm9^hө1}叓.:z/8c@?]WkCfnjhHEztj`T#qSSw~{m\mK+!yDK}9Y~*/վa;wSS!XW2SӶ(zp$UlpSae^U49)[6Rtp^oVIX_{aruw^nmpTd^t ZJۯќmJ0OG$a/(2Rk7m.Zwr-u  gs\ b9g1nAvJ K? F{;vc{ص.R{~t]SVʶ=1x"'o͑wXR(folkiDi}#ӛ,-]dZ7C.3/ Xrn<@;llZʏ,0"P-?䤂mXGCUOzYMLejN.+±oT3:)%n|njꑛeYžG^5=(-0%ISC* [O.3sP$I6B!"vﰷN>d4>9Q&"qצ7ۮ|Blnt*yK0G+DCѧ^z^˘Pz02,cM3*7gDNN <^$ /izNS:ŬD,{ nyGe_(('*sLF'B I#4336RW=mfq:^҂EwQ'M}iLN2뽸yB$;7Ma-*f[}e&c`pCn$^7C_ZP9gQ) w& ?j{LvR FvU =)쬋LTo5Scuᙊ(לWH"1~{wa+ӶǪވ)e*^|Õ}BzwWh3k2*(R!n8 m[5=e=(Z3~){6`]x.bO`{Ğ hJRPZ1}GKܺ~aK=c"`{}wѩZԡICR0i 2S|; ,zQePEp$(v|*G+锆:pώ@ɍ)J2 0lpʁ2hP$/#O>Iʰ)hkllJks$&Y8ey|< (iFڍâ1>wK`F; r =%~*>6cSP>Vy3G(*U_ zMx@>Zn/iԝ_\Cab :$Ka"c;j|*åg*G*6oDMox (Oz|[(SչZpv}Ko+ZTWcf~7T{7U.I u3 4 d[ۉFfٛ7r{Q/xO f8"(AbmP7ǒ?9 BnVd:RdtVmyb:s:oE-'Qh4w3>yKwؚ:ni"/Ko'Ў/ȇ'q\oxcIC)aa+1? !A,ײݕ u^mr93 ٙߡB~m}(5Pn,33׮M⽖$-@:(ߟWnb"gB 8ԧD`'X9̆wSVm_5 z>?Ƣ|6Yh-%k<2\;#6JCxガ^{'iERJ]RS-uT L%{IsYe[[1ұ陁 \8+Hko"ҥoԅ"TV<n2bˬ8F r22U3 wo^ȳ$K3& vMm#&䆀B[W[KNx[ɣ'\Гc C6T3Q۪%Qwvy)nʒ#kucp~\UD_!W5an0H+P)];ul)>F]KLJphdtw֍2в!Gϝ}\<ن}4_yޑG̹V~u,-c Eɖ2P+gėg#}CExW zJg3=~BO^E ӷvhO9̳&7caW'i)yO>v~iVht(!KWr.:@l]A ؜ûדxv3 6uBmhȆ4""SCbӴ'BњZpBfB:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:META-INF/MANIFEST.MFeMK@X,~B?6jY5 -L6323ۯ"( jǔ1jxfڎ@^hzD~ײ$K)2a+k]nvm?!PV|Xx% ޤeՒ74CNA\7~"HL1-NݜQG;%9 jW@71724v2562<_fCAhr>—ߩ]mra=oc:c6̗ryg{7?ZPdhΆ HJO /<_kJw'|rشSVWGӐPImO\W}Қul+yy_e^)aw>d2ٰA1`1PMhI9 Kc$5|=yOC`ú;Oa) 9+m3#*CSV> Owg1ڤ>~3skmqdc5eMYay w':H>2(kdnPK PK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPK!: Q META-INF/MANIFEST.MFPK!:.Y META-INF/CERT.SFPK!: F META-INF/CERT.RSAPKysrc/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha256-1.2.840.113549.1.1.1-16384.apk0100644 0000000 0000000 00000023523 13243353143 027327 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:META-INF/MANIFEST.MFeMK@X,~B?6jY5 -L6323ۯ"( jǔ1jxfڎ@^hzD~ײ$K)2a+k]nvm?!PV|Xx% ޤeՒ74CNA\7~"HL1-NݜQG;%9 jW(?]Wt`fз!8X]UDojcoSl,JV!2/T}~ӍTiyo6BSj|M/ù$A;ǓaIu]$ٹ1jG]+z2m4̲Iz+NgnFCpL14tm0@9:6| gtG*jzњL'̤p)@I[I!/ib@>qv?Iv@Vz+ڼO+,ƄP?&\ ePe0eF>cO&1㤽_X%hu+8IxiAK=ADEqۀ1{Xvokrbv`I{TEzl~o Bޑڣ-XxxcTn՝H+q9XPCdo̾m;l 1B_#9~yI'4؏KPs?IlVFZf- ('B.~Kcc&M\#~E<[6A~G'%g"AC"A+-yє͈usb /~/swiu FcEttIuű.GڔOTM:~7 t_߁nw":[nH7%^PhonP3c.OD6C'YX x!P⪇L# I|>k6)fE?Ą߿>ثzLGPAsr}QXC5)l+$ \P4w$OwKG$[Ra>v>u.V HbX9 |e`n^ !w)B>gq,SߵBESf[4ExG_Bt͋ӗmG;OWvOR"O4޺; UB\}TvqTGjâ//‡$#dM<~pۭ zt(%fST*O.V+u 2N%'`g5>8 D"-1Yi Ǵڑp;VO-^Cgw_/Jh^ ~Uj|^qذɱ 8T8ar,! "}~%l'D_cZa`KB49!lXE>pEZE2{^Ξ !kq;/ٶaByv*ݞ\(Ff"^|<[8n*VzD]'IS6x+i&"oY̚4uafo8pFoSƎ>}sp9Jner+Ng T!,@{P%Wc* + &; q(2er{~Sx-Z^5 .-D^J2RkWb7zTj;'WmdZ>˻`9O:0%qɳ&ezzS:"N-fUX)xkzMY- -Vdf';o[jvT/ehdt'V&V |mFO+^S,d^7SaLX, sƎUXQ9RHϗ'[Y;Ec7ɡYf\6,̟]%7K%"!Et'o7!$ hE@50(VbՇ'7sT]ͼt@mf|<E!#i)v.|n m9SF& Y󞵹ޕlPtpP͊BQqJ֘GYփPT<݋؍.p=mZŢ6oM#tbKǬ(efKR<ٮj|n<ˆu4"g|jB\FϓyqԮK@} $38|ŐlR$햮~ؽIh E`ފM ԭjy._rzQV]\m8W2$[ܸ,&>b2c9-ͰD:ufJW2Z dyۂUU:j3x2wѺR>/*zbσzƷ|r3ɍg_n__%\ 0\EL!{O=vï:^:5@^ro]To!lvҝx侧<\!IH%, WޮߊЭ1-yM^b<N5/¬R eo,S]Ga{̢P|~nGq:!n9ʉ9+G3qݶۼ2b,ALӨ7޾QS}t0څqUz.WG #j$׃L^$Xҿ FRD" $rB,k0u7,u(!8qoΡ.=-ݙO jednv4X -nrintn)cs'GƯ@̀(1bsLj6U&u!GZJbZ}";f!IR8Myje[]oƸ'#G a*VWybG{s]؝JLH/8ya2vAK_ !u DrrC?nUݿUu۴?d_htLw%Z6Jw]q0t µFnPdé+E/w݂1$CLi;zǶ-RW#D{d۬3W8T/^p H G m}c]OyJEA\\ “Ah qُJ?̛QX\JXC ;št7|?"9q_6|YeQ*~*]9[>J{#eǿ'PJuCzixTߡ\?5SB%y7-èU~ ACf庨}lnأeDzb\}BS47a)0<K'!u ޢbV4}(!_$u򞫕= zF/PߦR%MHbKƾz3/~(CTv֚FOϐg#6L{uq$;́AeR˸ mSuഥ4jT%?or$R1wZ^Pyd" E;C1~$38N\@zhRn^o2Zф}T`"^|; *Bb[ٞM"O}+/hҚ1+u [ETr&zIɓDwBQb~'fJSo#3U*,@ ΃_wE#c:MƋ|j1P.f+Nev#8a~NDͷBQxQ Jpdi%iŽ .4,Em>@.fu`Jǵ_;yU| N(cQ5ݖK)gף8t>|mK۪#j>ua(n=ŝ X)"@EzÒ]vkEy5WWطptʫ&@,ehXze\G%Y^򲃞%y(, ӆVLng[b)f %:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:META-INF/MANIFEST.MFeMK@X,~B?6jY5 -L6323ۯ"( jǔ1jxfڎ@^hzD~ײ$K)2a+k]nvm?!PV|Xx% ޤeՒ74CNA\7~"HL1-NݜQG;%9 jW-|{^r*e^Z)n)k]NA#6nwvkMSd^<|W*-O:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:META-INF/MANIFEST.MFeMK@X,~B?6jY5 -L6323ۯ"( jǔ1jxfڎ@^hzD~ײ$K)2a+k]nvm?!PV|Xx% ޤeՒ74CNA\7~"HL1-NݜQG;%9 jW q|1@ZLCl4 xqPL@+Pi]Ҡ0( CfRd8@>O / 0QPo-;x % <{%CO&рِ~C hܹA1yBj $Rus K >f6ǺkYv\X*;U?SbTkߞ<\ev}.99mjPJmӈS4>H[h/HvC_P!@$ݹ֙Iţ #Obr6#?-Jc0B^Z(a0JJ4iiBtUy]i&[pMK[W;VɜώAJK_Ѻʔ'۬P|x ^COmkLYN/fvCKd‘6Ƶ"NPEiT_\kp\Pd$d%+% =sʄX\tמ?Tڲ_sQ ڜe?kY9,f' Ǣ"c9aD8]b(,etp?Pӽ^Q =HەdhzFIA*K"4AsZA>5X33%D:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:META-INF/MANIFEST.MFeMK@X,~B?6jY5 -L6323ۯ"( jǔ1jxfڎ@^hzD~ײ$K)2a+k]nvm?!PV|Xx% ޤeՒ74CNA\7~"HL1-NݜQG;%9 jW Y*(A!B,t$ P(VD {,ap*E'2- d` I {$L %PX P\ 䭥 VXں2m (}Q02 d yw\͸Fw"\Δ2L][8p܇p4w8n̜`=F}#=8Oy0)Z}*saF"}_lO\A3F:zH}5yѳenU>8={áHT'fvx֫bd}ү d 61کy%;nz-ۤXƥ` Tvhc/9iyZu}?ݾ>,1-ukn]ҏI|y&iN|g%]#D/?~غXݥ['.:oה|}z9n%k%%?[56o9,%2"VҪl5G1TvA9xz.u6K= \@fGK7͌<kiWR`cregKt$DG18M_EcsĠ̈TCZldjOnA1e4M/w? *%aX?z☡ _QEfY7f[tgazZ'Ӎ?>*&T?3oWdvmID6)uϫ3!ubcz(.iQȶؐv30.d> 2]$x1RTs)A՜ekT;S{bSYͳHd/zZݿ%hh#T, r.)C??w?dzS)!.WY?ӎy|Fnḫ..u8yFfV JFZZc6!"&˗++Xye;x2/`D7} oOG Mӯ'j%rߡ6-2şEFX?ڢ^)3]TBʺǛ72MN)~.g=csJz)S)c' i7֟mT{|}MQkP0g* JVdiu2&6: ̓*[h|/ʭg2.8bʥhG=NaQ6NgLk_LP޺MW}ԥz{!_@BbK~P_]w[Vt 㞢 .DipI2be\.qsY\[0hYu\YIc8 1c_2&QUdLCʘQ\A`h"a顔-0P XPKvth<PK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPK!: Q META-INF/MANIFEST.MFPK!:.Y META-INF/CERT.SFPK!:vth<F META-INF/CERT.RSAPKysrc/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha256-1.2.840.113549.1.1.1-8192.apk0100644 0000000 0000000 00000015522 13243353143 027245 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:META-INF/MANIFEST.MFeMK@X,~B?6jY5 -L6323ۯ"( jǔ1jxfڎ@^hzD~ײ$K)2a+k]nvm?!PV|Xx% ޤeՒ74CNA\7~"HL1-NݜQG;%9 jW-fg.-~aW_/'WˡA/.~J,R4[鈁O$V?9t=hӘN=VS|GK&^k].jU~yO,%iV|J(K~?Ӯ |p,-nsz;ڛKV5SgԛTWdA@pjN?Cs^k`aRcR eӎ6|'$]%TX&WɎ32SEuJ}D тm*}ƀ s0:Nѽ(q >8PN|r8_2K2nC$ר\O) `:hV(s]^'_>m16%b6#.mi,r7Oy漶 }x'j>2C}?<;\Eu҉Fw#͓tIxDp1x%Aˇf9lF*kCFީG,UWb!SԝM"V-,B NxeC Cpn= Ja?Aŧmyʘn3|yg%s):5A3~|Atw\q׼\4Yg?ȷSiTnʊJw/:x2A @:l`A=?S!.i xs "`S"@q?pvĐ%Ū/M`SK°];B\_F6o!3vK\wx3uL2 =Icz$hy`,oOH]6q?>Mݮ$J=:]R?$ ϻ,Xo?&4&)쵁T+h./ĞRuS?nPQ_Mju d0E'*K(FȇݣA6[ 'iH4<^ܨ.pmdfFmGm`I Vy2K*tHm;dX0 (Wx6{˲r9 Hӗ_* ۄP,J/_"rlN=uB6|`0.d;R'ͧ~3̖PUwf=Ԟٹ J_)rIܰh,rhv5 ;mMSLU(!j>Ʊ;L-Z{Ι^6lAV حT[VI_&DN dEJ^ydc\apMKD sUtn|*FE^/:ɶ쉗˂'Iy Vҥ4/off=gM;&d5pV <l'bQc5jG3FU72R/Cs?~tXzHc)i83%q LS}]MGvXd#P, aNc+ u:)ʞ!:trO[~ĪDFޭinhkcԎ_Fuw|+n\ȔuOp11q.sCi8ef 'Gؐ9W&c0@ #1gNuWiHc:Th0Fjq πLzd ؿc~S[^^9yPaDoI,%j.: ^l<)f}6VoB=PRm^zi#lܖU\QOރ_lͺ鍌k *7*nrmO PK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPK!: Q META-INF/MANIFEST.MFPK!:.Y META-INF/CERT.SFPK!:ϕ> F META-INF/CERT.RSAPKysrc/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha256-1.2.840.113549.1.1.11-1024.apk0100644 0000000 0000000 00000010160 13243353143 027302 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:}SMETA-INF/CERT.SFmn@= 06DHIP+Q"MLٜ==PԌ(aF&K#Pح4?41+(Eس,3EKτ Ň9=Sdv~®#m͛kc|aF9d^eI60#a&KoXhpXO>uq@kp.# ~:#R9*&_뇨 |񦰽ȩ® *;8>fG!GiC^,^P[VTIYܴZP0;PK!:HMETA-INF/CERT.RSA3hbƩiA[&Ll m,L  41~LLL v<{{OPЀ98Q@N8JŮq>@71724v2562<_fCAhr>—ߩ]mra=oc:c6̗ryg{7?ZPdhΆ HJO /<_kJw'|rشSVWGӐPImO\W}Қul+yy_e^)aw>d2ٰA1`1P@&ax4%$Ɔ[?1.]|>~Yȩz޸ BDT4?MdJń]&LOm{t:+~_{|כJB?ug=)4;$%OwA.S{$:1'Nߕt5ӗ&B,sPK!:DmQMETA-INF/MANIFEST.MFeMK@X,~B?6jY5 -L6323ۯ"( jǔ1jxfڎ@^hzD~ײ$K)2a+k]nvm?!PV|Xx% ޤeՒ7t Z.9?^-$bcy:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:}SMETA-INF/CERT.SFmn@= 06DHIP+Q"MLٜ==PԌ(aF&K#Pح4?41+(Eس,3EKτ Ň9=Sdv~®#m͛kc|aF9d^eI60#a&KoXhpXO>uq@kp.# ~:#R9*&_뇨 |񦰽ȩ® *;8>fG!GiC^,^P[VTIYܴZP0;PK!:*uFMETA-INF/CERT.RSAuUP\Fqwwwwwkpww A; xTMLݺ쳾]q%DDaP  BI Po @DP80@@A(8Q~}t,W >.^[LIp rpsapsBi{?g"c3G"VۢY};抳oU? R`3rp^Am&(XG{TԠ&Oh-Ty%D a{'^7MnOT򅜥d *`GQ9@Q4$i*tgˇrO Z{~T9d\`gNL5LZ`vlj;PM-9V̏u{"˨?lBfh oX>^OXk-HkQ)&KG`) 9MLrypBOw]'4ampw'taC9s;@-nHG> J/fEC*R E|~gтh-y;ۦكfՂYߐ[DYJC>b"23W^$[aZg)S]CGssBo-cswwE6O]>pOdq`ᢻ!Plޣ8Qt& hl?B9 ޭ{$䛪)c EH1-wh93gqBc=9yoxIX8[v4?ӳI|b+fr=1iX!#b6%y "te>1ߒgKqd#bKKzk]".Ǧ|tL)Rw$UZ_{mڥٔ^HU% R4^ӊG"h'm.HL`4g 'X 6&2V(|KHE s} :f &0V~F# C0WH7T9R,UaED>S ڊpIr2hsaT21VŹ &uP_e"-:)'lNK !;Oeظ_r9k\r].3W?pϿ"SV E75qL ސ0c\ hi#ݿy;;6Zھ F/Vl-]_ u2y$;=O"dm$0mT.ڡ*.Uţ?r^aPb{#yED(ye4]U6[F2XEk()SUH9s 8%<:1CҜiq"#ybדkk~8(ثKOސ?ٖh}?Mqvu~%JYorC'42d;׳H`rީ?3~Z C9TW3Wv]?[iqI[Z(u7d҆Nq,{UrFj0xk&OOn._uID[fSD & x 0<_AAH"%D2ך wm@ߍν|WBo("'~oI.=+:4 )2ӭYQ@k Î/p1V 4IڤHc  ҕې b:?B7cР-r{Ev3uX[zLd^ ]S:֦|UqΨww{ J!?$$H }iP¬\^/-W\K)l*1K[Qq]SXkʌ{C[2^]0poUf7QwI9MDd9wOoU&&߶ tI)OoEܒ97[5X3A)= EU Hð#ȒmkVAY35auPƾ 3\_NynS77B2XRؒ5-`ȨKÐ>,>YyW\Z}P<'C]W QEe!nu] yPˬ_jŠ;u޻зvՋ fom&A^zeL8$ NUE-k 뷣P4\حna3ڻ|Kū8,-10Rrݩe-iޞ@wWdL_E'a5eX~KnG o.F:/u&_9$~6 _%~<)v4ly:x@j/p~'M[_))\)ϫ,d} Lc4OuDٯ@U\Q_9llc͆:Q7MV4ﵜTrz-DEuuĆ xr,h}iWW2By/Ͻw;f]¸=rz x:^A8=1֤]M-lZ.VJπ)旻FFEn?}{4y2'PQO5Gsz~^R5 0{>܅5CC:ɵp(2'-q|7u01 ]D Q{Xw1,ף;~Pk!8lٛ.h_6Y`xG3PIg>k.L(k/?urACϒ:\ت+.lڷw97Js"\5;>D#:$\ŋ0knݙyʎW52P>N2/8}]cmu^6q>~YsKѡv\̬\fω=ϣR|@q9FƶL \UYo;mhYc^D}K9QbUq';ЋizM-!ԣw!=&lwhl1^3 OHcD.@tFDcBƴ@0e$x!k[}E@[XtpIz4X1\%Ӵ~;3ߘҮ9V7*6$^"Hz˙40.#9^>>aWbS1z;߰_oT.uY6&rb :9 LVT=W]Z:Hx|a˜1ҠȷmXK>W]n{ OɯOHE[d.\$%GP MZ{dѤRPHCPzET7n_Pȿ-%^)mߧ=̤, (d+ʪ~׫E40:j{K2~CҤ5IGI7ZS{M6c /L>?ppRZ*xZْI%@IeSEG'hZF\BU @L_uUw_B Ec8o!(.Is{͓u'OlFx͖|%Bߋ|4}/&/wY}^;¾+âQc({ef)))"QX+-G4D+𹯕,BrlF' LO Db统\$i[O%>oHV"0Wo`8-HYlmj'% w3#eiK Dn']kdSG}o2fL5FO.L{D\X¦!5襲^ g8%LnkRF}b?/%'E&-A⠑@iwAf> w{6FzJȖe'P}m #VDYnDPÂ]6`ݙ"٫q+,#Dz2ʜNN{# -D1u($4Tǹx`V Ll4I}/,Emʋ2cu mjVo[e)J.eF#4B3mhڦWa+Wp5!h~sb p@=W(`8}ѝGJ7 -(Q"[|8M:Dk}\m';"rOO2;kFƆa1d'_utpʺIOgƏr߄d?12bx'O;&×.xS~gک`cgR GɈW$S禟ے;ZPχ4b}cO-La,6>A+sѮ& f*VpoM2{Қo*-%1+N4DBEDdBA@ ,m "zQ) A~>N˯b]ǯRͬdL>ZpѶ(PS P֢DTRCo=q5Sc;Yy248Ejq3C_#/TX ̷tb:Q)F.V^msuZ5Os#aI@.$\~3SU$٧#83adB;IEs̀\*MECE2Hj:8Im`\r*$]`Qf'hRÏy]s).䷄Y6~Ҿ1\W(%>$ Ѕ6n=-K\e=ssN{$tU{zx 2viW4>"mbf: j:/<& w1,)ɽ-qG{<~i7!,PL1r$C6R~3NF_=/K\\mu줺p0sr#|f]WkZ#١8U ^ʐ,^7:o~ձ'lj>(31> ٺ) |=yxQ@ $̒W*)'xƦp?ȁ)ew&!I[svNA66;zTl}n/KBLNr$o5i"iLrÝur#*`+"r]S-{dl"7w^)Hlo+>nY X2~FM[mS9CMrOU _PM󝫮$p Օ,b/E~A eM[{AԗM%l+[5X:5afs',iֲb_TLP:.8,p6j9{J+vu|qNN:.шD~ܯF1-sc;XgX Lr "Ȱ/Kj_ ns~9Zdqp8={SXF42 Ţjd|28< \3bkEB qbv]8=ZѫW;ÞlE:gՉ6KwlN۱lPK!:DmQMETA-INF/MANIFEST.MFeMK@X,~B?6jY5 -L6323ۯ"( jǔ1jxfڎ@^hzD~ײ$K)2a+k]nvm?!PV|Xx% ޤeՒ7t Z.9?^-$bcy:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:}SMETA-INF/CERT.SFmn@= 06DHIP+Q"MLٜ==PԌ(aF&K#Pح4?41+(Eس,3EKτ Ň9=Sdv~®#m͛kc|aF9d^eI60#a&KoXhpXO>uq@kp.# ~:#R9*&_뇨 |񦰽ȩ® *;8>fG!GiC^,^P[VTIYܴZP0;PK!:a(META-INF/CERT.RSA3hbiajhδԠѐ߀3̓1qA_&M03121q2eU 'l(hQTkd`ba 'khf`llhhbjnb%kbl`ahbĨl8QM @q.&FF & 5`Y契FodpQ] ʽ8w:˧1pT<@IҐ%pTf5_v@yy{m`:iOawv,^v#w:ns7[Ǫu;_ƽS\4`WI *yxY~pizyxՏO 2Y'2^ʈ^ /d틐7?.+Y;9Fs.=(oW?lR_Y {6YtM}H' FoDox}qq,0dXXDęt-f[ R#o Vf0k@Հ?Z2{mMOė{O_W'&;0_OR"Oލuf<txUKt?aVHo{q鋏ֶop_ICX[G{Oz >rxyts|,~9k3'lssE4kFzԪ=h]YqջfNZp$Ǩ4F# Q/ }2s#{|Ol6^*Q*y˖lp3Y]zb-^~E}plsl=/J}"71* U͂Noo^86Z%nrPK!:DmQMETA-INF/MANIFEST.MFeMK@X,~B?6jY5 -L6323ۯ"( jǔ1jxfڎ@^hzD~ײ$K)2a+k]nvm?!PV|Xx% ޤeՒ7t Z.9?^-$bcy:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:}SMETA-INF/CERT.SFmn@= 06DHIP+Q"MLٜ==PԌ(aF&K#Pح4?41+(Eس,3EKτ Ň9=Sdv~®#m͛kc|aF9d^eI60#a&KoXhpXO>uq@kp.# ~:#R9*&_뇨 |񦰽ȩ® *;8>fG!GiC^,^P[VTIYܴZP0;PK!:GjΞMETA-INF/CERT.RSAmTy4 ز%cjuƒT[ -3b,QCN"-<x:F&[ɫ,Ҩ9z~=~"E%tҀ<Y!E8eAiQ J5@E  @uB|!W~ b^@@r'eE$$BJb>4 *+JA7sS6$&?ߓ G! '! Mt]y]$V6Qӹ47">tx`󫍩Oeo-%ԅ -ld@Vןkj=-m]nx[5x׭'tb|ȫYբRX|`Nhdm6} A]ɀ5ΞC#Cvգof8wxI L A:AXu!wS[ϽV\oPFmĮl0:NsWuID‰[D@9O+hݛƪ-ka :_Zoڧ_a26!IUѹV9[7N[Y-@i]WV m$[u/s_O$D%xc؞_+ XW wB1b)JJ],iUc`'sa^䦇x̖.nNbgLq Uɼ+Vmkfh=34 #,͔)o$4*AsD-~ZfwKgs{_NCx} v ;C!:#,3^YxɅC&l 8?7D8ٔI7Xd,eXQh$yoߌip[X)B8$܏}gWNU`D+76c?Jx[Ւ ,o~MPyII|L*D'O}3PK!:DmQMETA-INF/MANIFEST.MFeMK@X,~B?6jY5 -L6323ۯ"( jǔ1jxfڎ@^hzD~ײ$K)2a+k]nvm?!PV|Xx% ޤeՒ7t Z.9?^-$bcy:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:}SMETA-INF/CERT.SFmn@= 06DHIP+Q"MLٜ==PԌ(aF&K#Pح4?41+(Eس,3EKτ Ň9=Sdv~®#m͛kc|aF9d^eI60#a&KoXhpXO>uq@kp.# ~:#R9*&_뇨 |񦰽ȩ® *;8>fG!GiC^,^P[VTIYܴZP0;PK!:5L<META-INF/CERT.RSAmy4 Ɠ7Xk-_cXGl(A-b-$RUEТ-0ua}jG~s{s=D^2ObM "o4HP(XC( A"  >Hj> wBo!TЇD"+&x$򚯶6处P. cg!%  .D^~x@BFΈ~s;)lR1ɽi!GfIH̏LO=/ lUzH)I6$epC<^n6J]}Y-4:X{9 ף,oO+Hо8Wv //uc$xW;/AtL.ֿ'w3"%yi~v떘Md\ 5 dՈ{arb9KFǓbj8'E$άcؕߦw8dOBz2_M~+Vf1{sp5Y[q ߀nȋqv+e! _-yy.cZRiGp(u*(|>?  ^A/=qw('B+|Ա,^UΏSwJpf}m٩ .t[_kiZX ۻg άJ)VOݫ,c#(hiZo 7{aȓ%_R}AB6i%:4`r1mzuMgC 'bNXrcf "Ͷc$+*6'WyRWBŮ1tFY#}ˈhƊ%b^:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:}SMETA-INF/CERT.SFmn@= 06DHIP+Q"MLٜ==PԌ(aF&K#Pح4?41+(Eس,3EKτ Ň9=Sdv~®#m͛kc|aF9d^eI60#a&KoXhpXO>uq@kp.# ~:#R9*&_뇨 |񦰽ȩ® *;8>fG!GiC^,^P[VTIYܴZP0;PK!: ]G META-INF/CERT.RSAmy4gƘ1C{ k#Bd_#"K=4ƞ5nl 4n4a/)n9{>{>4Ӌۇρhh  H wޣA Mw@", >D,߹@$30;]VF!x9HEB"/hUG(!!4XEAh0#ARQkcjx9v?o%HqX[ $µsrox94ʊ7,tdidBn+99%aƋerK9EK{ 7zշ0w8ngW,]prt_7Y }~%5OR58$ma1XfnwX'7V2 5ݴa""mv\w%]VB5V vzE 6ndƃ BhT|͕LɁZK>kGGkwfb+FzQs5DHOh?.3łȸK*kP;]w*i8RAG <3zǏUz-qm)i߻oR`$Qq~pV@:*~Zjc4_=ڐim-x<+1זbq&+j\ٖ2nd]FIƆmcG-!MHo ݆{ΓOsY-V\\ʨ Ai iڅe||uI*E_]{h@4ic0!"Z{1X ?MI[{^(x)D.I 3l"'/viZY~lh }wC nen%0m_IHm/ꎈ7X*wk[ zz ߓZTߋJɩRZ}N/r|͐q:)kSJj(3jŚ̬@ZަЙWʭ=+xa!L|zj̎s ium&V<vr`"R\YVݐ|9E(9c;"wu6['k d;t/I3=sig#..eLCr>fDo[A~*wN;@4@aSD .vːpJ:k]e}kfdY^@nO6ay#ʂt? V<V/` g`dLW$C~#6[#\y;CfR!_^ rCDNR]U{ ׳b$>",$j`gX&ץ=Ԫ: ,?/G9i_Nk~ͻ(t`3% q`BByZ^!Iy6 ,r,F~,:ow''b][l4n5/̒xJ>|(趯Beku]tsEZvhLP{IZ;TX]Aʣ;ley/ =m2;q`gY0OUc## `_zXO&Qʰ!hW2d~5=KsP 8dxerWIl?*k$RjjuA(gR>4#?dKntswUb GȥGv9Lx:s;@Ҵ¥<KVzRw|Sk)Gp8OqiK+ƨޟ gb̙*] =YUwT۬ր!7XiKۍʹ' |e1>?ÊDrbXg/5&g5SVQۂSePPYi@C[yWe5_a!*]\!ek*;ѳN jEp7|Ym+; Wh8^2lӪ9gVZr^(YOwYI !ݝ!pR9VUNIjZM,<+Ha` ́JC [y\^!]ܨNn3$Z3M8:@ȤWZ-:͖%dNZ_ F8t,$T''_{h8aV&񺰙.cN#LT[oӌz،/dF ;Z߽d"6qVBoޯ'D3(Lꦱg;ĸ>Μџ :Z uNk^uc6>=)7] )sSWݷ;=S58_ ۭK|-:ȓZo.u@^kdKvmf\t|D2jZPWoyWr.Tc΄DiH6&6yjAH6`fo@^N˾J=`5dE8!cqyr>肊4붅Aq[_bXD^'2Kc .وBS&V״R1Kn ȇ~Y0L{9O.6>AEB?3R76,AXl8CmD(5uS);L~!<~1Ɂ"H}:ԅ'va$lvdX''請6x^ CM(oeuy|m}^*&ѝ9)P4ϭG̐&&hwAwyQBHә\|/+^PF#}39ݻE9F~Ĝ_~83&V>x@*[{mWf)kג㚭2/ zS늭jp>*O/ fG3ilr;d$P;ɧPK!:DmQMETA-INF/MANIFEST.MFeMK@X,~B?6jY5 -L6323ۯ"( jǔ1jxfڎ@^hzD~ײ$K)2a+k]nvm?!PV|Xx% ޤeՒ7t Z.9?^-$bcy:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:META-INF/MANIFEST.MFen@= Cj+Z4Sfu;>}1iퟜyDjM1XA~T)*W "˚#Ϊɹ&v,2>(ь3+HzBWbٽٌ ̍ L ׷PPj=\9Ƨ:ew*9V_=g7rL2Rx5v5nW/)kwN_5_okye Y>,D>* `P3Z/ Jqq,ٲ|,b,"_܉辔<+֑+- A,b 032G LfOWnXۘo i^f/9YO{=Y#%ӂ %OךR݀;I5b'6կ4$-eRĺUf;J^WY|J&}6l1{˹.6ODʲi\;PK#coPK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPK!:Z"Hs4 META-INF/MANIFEST.MFPK!:ile! META-INF/CERT.SFPK!:#co META-INF/CERT.RSAPKysrc/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha384-1.2.840.113549.1.1.1-16384.apk0100644 0000000 0000000 00000023721 13243353143 027331 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:META-INF/MANIFEST.MFen@= Cj+Z4Sfu;>}1iퟜyDjM1XA~T)*W "˚#Ϊɹ&v,2>(ь3+HzBWbٽcq Q}=!s4`\Hv VPwJTKޭmG^dM/O2f|=e@RP3^("[P{Q׌$DtPG­~b 2<9ًXƛ:wx6-Qcݻ)&W:0O 0K˯k┝VK 6f5! xsψYV<Q]aektB?lOBہc7xuo+2?r%V++>YkU޾^r Aq.Ў$ s$*;2,–Oϋ!䶀Mp(?BStےfQüQ: =4_LYWӆiVYVpfV C>$\qplsI򾱚6<4cZ)}%oH',KLEI_DaiGNvGqI'l, z?C[x+^-̃#3tڣy/#!E>opǶk~Ĕoz"ٚǛ ޟNw+$ KyPwaH>mA,Y_Ji w%e'Ia?.2|dӧhԊdrl ӡx/{5B3 yR_w!1&=@zҵ񘚓H^ _ ^S|}hJQ6M*g,cX[ZFtaGD6.'f6!԰ ueDzݎx0cʳ$]or!] PnkڳY*Kö@'_SM3JJQ.v3DV{,[7>O, ){_vuÈH*|%Շh?M&S~:8B)ʙup9x:%B vT9<ס H*!dY\u+x%BnjUi wMD0}p U >8*JJ&[>A>w/Va9`n~>Y1m2z^r< }Ӣ}UA<ꈪ)b|B?ƛ%DԂɝbꦫw>,lO;Ēkt^cgB,LѼmD $6D&܄E' 1hZ8;7mCYQpi"ktsi7 EA4cYr u߸Wj\Z'z@=;\RFL#l#Cw(:-}2ĥ;ŔQI6X |D^nqM zyYH!O6!+F.iuu׳Vl8o mk}ᴐP'> \?>Uj1JS \KgR@վҮrTno`C*nVYLzu~1Ğ,l\N sϕ,\^ d䶟;1U'ς{c>6Ps&[4e8G]%FjNEߗlQy&*JJH0@EwvUi#w}>]>(Չָ[ɫ-!0_wo.05"}5;#$+;YTo*uSW*J8B*ٌb_LSQl o3\No#KsO|Y[)1;zZnE4_{WN y=k1kz5Leuk?h8@&:7?%H@f.WqILWMoz 7Q0 4D !Ay 42յN{9{DnE\ -)ſuCDJ\4;E9 l 8 nt-<ήUlgoFOPݻ$N_jMȼfgdIgS5usp1fe t,NT|vMP-G}{.V#81ʪBF [gutFe6`ɹDDدҒdW8?fE5za=2[XcE{Yvo{Kp $4c>` Q9kTiuw8瑵~D7&e]ݤeCNwʯfV5 yUV [z 0+Zks,i2@_'Fήϊܐ4'_qIC > sU~㔍gv)O'LJuMlKH|n"< F4P2A?kc;1rrǹ7)l3J4{CE@t93^CMpV.2Ծbc/iw"‡Z4xk[rTӤ>XϘ絾G;pa,Yy[=>H(9iZp|[הYz.Q|qq@h ;:qͩIN.aeFw._+ǭʜKïbd)S}FXҦ.\z,|fij */a6юhGk;Gq9@C(6۶@7:cuq/bĊ085 (q mfVg% Œp&~}2N=JX:}+٫"*JH\̑H~wC]?`{x]& -a0P(UHˣK#k}_shWS]fA_)T`tӚ PgvWа–9z':&(A*@z݉?6 ͒ {Px5);O 8޴W2h&/tX%paBqUN*Wv/,!,w=c`O-FB+ƱU@LsŶƊ; #)Կc,sdCHmCO>LZt2p. +ᕵ۴Totf հ+ kbݸhdڇsBjz ;ء#qU]/>eeo(\!ړ{4cxƥ0hqfD1?^+jP#s&8f Ld ٢| 4]b3O0*w0"<MuH)WbQ@Cc}{ʑv)Poi׫R x:ީN!.a .jH`s66g]W;82n'1 sueIagjfCWY0 !xhr)m?͙\ `i@r]^Ln0qSݛt??u߈1Vk['iTYC+?J(iX%p,XTh-=;و#^N~¸F]]jK_v ߆c[g( !)xRe7MjOTYՓį GI"~ I_2li(W8,OdO%s3RB`^1Ym$)l;Y 1ݭJ6X]l">h02&M[ǥuF"($d㚿$i>;ht/-9 p.XRj(\Z, O~+Aͪ)Z." ?;w?6T1D@J|4|y.i"D^a|mҌ`":cj]mʸ"brވxRrؽ+r ЇD8i/aBCC\?:8?ԼBnЯefNlg5իmm$e(g$JRW,QH0T:ł!dۑ9< NmJ˜ea!h˞7]DJv*ݏw>w+]]_4um_ ] l0 )҂ǜ`9v~T>,;+^}q+]_'DUebƅ\qQ] 8Q'I>ie/1vMZ"tcFvvS i8-C- zǃAJ(‚%GEEe]ӸML hbnQjEUߐzhB gmL~WQ1yTG#7w3? `k&0w/HNi]H 6ڑ+mj4D/Q8*9wpn.sN:y01P m)'}ސ*~ԣ0$:Ɂi,e<1U>U1#"S:_¬m-1q?Y) Utrj&V9EԟC?DΠ]c!8r<Jc)u7(akKXG7}_s#D])Ҁ&/ƛqB&Z:^& mcvJ+Č+U#O׏ }t X0\hi}v(vlΨn+P>SP1Q粌)raj,ZfWxTN刜& qrR:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:META-INF/MANIFEST.MFen@= Cj+Z4Sfu;>}1iퟜyDjM1XA~T)*W "˚#Ϊɹ&v,2>(ь3+HzBWbٽ1q&bzٖB73EȃY$ +)fa5`fd̠fs[5t=9Sodwc(l^ROb{@\⣭+j}Ґk4җZ6k'm7fݽ89, 7ȾoH/ٕ^AD:ӾYw 9J-(mp_$xqnٕw^*h_*rU|?sK;}ֆ)ʣf\: r?^{8-m*oKR&Fo`s-G $40%YI>d(jl4|j#vI7<Y=ak3S OwUO횗gO&AO'{[D~vkߝ>?܃AɩRmN3 )nsr%)~?Dzm*}&qÔ!z*87GPʬ(ykW?]5q |(pkM*IR޶D켨{PK]$PK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPK!:Z"Hs4 META-INF/MANIFEST.MFPK!:ile! META-INF/CERT.SFPK!:]$ META-INF/CERT.RSAPKy'src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha384-1.2.840.113549.1.1.1-3072.apk0100644 0000000 0000000 00000012111 13243353143 027226 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:META-INF/MANIFEST.MFen@= Cj+Z4Sfu;>}1iퟜyDjM1XA~T)*W "˚#Ϊɹ&v,2>(ь3+HzBWbٽ)By  PDm$H* bK 龁ЮN D\0a(_ hK@ 6ExH`@"Pr#9~:$A*1adktʵ枓?,5jjF_#ⱪsmaIe /x˴z*o-YOwzCs4Eti4l]WGu#pߘB~&)[F x)KQkk󉖙Wڋ #ޒ.i9ͱ'–pc#g?SD)^*dX._X>㖻}fD˷V*B>׋|Mj !l޳Qۡuo/xSf\/;D :8ʍ:yzْ$=s(sb[kRmҍ}d -1vgr2PJN'@鐢pIxmUnTêzPao %bp;?<lJ.Ć,ؚg~/dݣ O*2r{2Ÿ\3bF=PVz4&>?2qԂ.aɜA'gT;^%|iWZHx##l*2IL#1d'sCNwQ,YKUd?ߤv K7KXQoCXo-S1M>v=ى`ug"1:[÷?P7L[awn| 0J(mQl'Se g)wviOv[٨KY7@B2VtIU.7e"-n1ƣ=.sY^B05 OC"|$0$$ F,5aOi*w,:(6&T`1̄= SFmC/EɺXXLtkm'tKіF_3s%b:&Մl"LDC'WyJL^?n۝`,{ STK܍oխK^^RM \г{2~&zl`»Z;7}! \_B*ѺB5d-/9Tvhy =PʲXT֯lt6Gql#%ظۭ֒htQW~0Nן;EǦOЈ2#-0Sőr^;*8]$py1%c_PK3&CPK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPK!:Z"Hs4 META-INF/MANIFEST.MFPK!:ile! META-INF/CERT.SFPK!:3&C META-INF/CERT.RSAPKysrc/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha384-1.2.840.113549.1.1.1-4096.apk0100644 0000000 0000000 00000012713 13243353143 027245 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:META-INF/MANIFEST.MFen@= Cj+Z4Sfu;>}1iퟜyDjM1XA~T)*W "˚#Ϊɹ&v,2>(ь3+HzBWbٽ( hj_6FK9sw\ +[bJr WH P4?vB=apP  ĀP$UrSv?BG9A  qSQ5@)Q$Z`huU-5 QBkv{9#9@!=?B~}0$o,0phc1BJIqyEz!=+ ɪRd::"dE@ŇJ>n7X%@)ۻAmw'E$Ϗ}/^js3'1ZmxQV3jͼ֓cQ,vnYVj64GL:g0{1洔>"YzGKkU\]p9}.gfֱE#:?SG|_Xf0!̿p+%:>O#z4jM˓jJ'oEj*r|K5СGfqJd]dB"|_vWNl./IOeigo˟u'(ѧlՒ5z}pptg蚤eK=S8uT>{G|!y3*]gm ;s4'd~o2|GV6Fבdy";kx/7@v-ZGՑ䃋vWXB%{੯߲p1Pm+ aP Z5B(x;!):B!+ ,˛VퟪW@Ƌ?ں2\t ^~|Nݚ dPZ R3>0cUA>LX/Egy@ʆ'}(рk.suv6xh;aq=īܹuZ+\"}W+#Cu΁ܿ9wwН [oɹy;i^T2uAߘIѿYiI2G>I }$-aT+:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:META-INF/MANIFEST.MFen@= Cj+Z4Sfu;>}1iퟜyDjM1XA~T)*W "˚#Ϊɹ&v,2>(ь3+HzBWbٽ0'z6UY"q K?yk RG~{]ff_E6/nj0`1j6u1Oo.g.=z6%4q}직6ٔLĒ蓆{}>y2?YK x1I$_9i?H AG{-m=d}3*J+yTl{KE!w8cRĽ{~/8뜣EDH9+m'r3ȯw"z[겪m7 O@eSlcl>X\@alp<( J,''OW C/}Q'令ؓFO2)9UÚ#&/W3;7\aITN9,`Y"gꆘZ{oMVí(hz8Fn( q( NVݟ_ky]:ה=PWN94{/|uIo`ΣAȡvdQ{ªQCIX97i+ W/`x nu0NämJ5 ni#R 4=';rPzG+'jΥ/&xМX^QU6rUwF$P,dj([אv* hoѭ&X^R.\No+\Ju_t"Is^nG Dde8$AEfq*lQh <4-:pF_H_̥S(~jV.SMxaxw䬲KRo-1L %?F&Ӭ?7+*s_Oי'V.S쇳I)<b*EiqRN/WzaV?짥}Pe[9<7>h͎,l'L|1T!C2Q@ SlXה@nh|-E5Gj/ 9,?`'qFU\G_P`or-GU0ʜ+1 (]5+ ZFuZ+׉*o!mBvktWyoPWU[Ŏ(T;OM;/WAbQ?1kɓjFYޛ}؝mn$;Jmh]U7uΫԅ;@nXQSRv_l>"81}ŦmEpjIq`3}>8)tN>rdw'd ?!0l%uc R~*,Md.a`ѽD<,tXӅXė@7c'] }/rmOAy | z% +MJKeFCionqZeu,WK2E{qc;Oxt5ZU䮖,2_fŗQL@lW͝#JX'VܨZ"%QcpN{ډlQ4Y-"&x]c!g䯽90䦪Kj%)ձݽ}/+,ֵ-,[] DS_mnf7wk*+xI4Q%ҹ?v M r")Hֹ*1߉z|.9,ڰG%c=SJ6V G0}o1aM:kWk*,PPAQQGFCŐDD:(y܄vc2J_J%B'UV?Blkka}>\`G֧Cˈ;/0HMꃳu v@U%L3_硤S+PK=35B PK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPK!:Z"Hs4 META-INF/MANIFEST.MFPK!:ile! META-INF/CERT.SFPK!:=35B  META-INF/CERT.RSAPKyEsrc/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha384-1.2.840.113549.1.1.12-1024.apk0100644 0000000 0000000 00000010363 13243353143 027312 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:L.eMETA-INF/CERT.SFen@້ .`zVP`Eϊ+,*&m\f H\k$f p`(jP*L#A'iMq*D3+T?KTm,A!HnDQPsK5|Sd=yBϘ9b&$;M[**LGmYg;yIK989m9W'ŤέwcLڜT6hqR~ŤClT$Sߪ<z?0i|Ln[%|ԣ\*ėpjJ5bZ'܊j^{LZw&[)Y~+IPX#/xyC&[n9x OPK!:lMETA-INF/CERT.RSA3hbƩiA[&Ll m,LL  41~LLL v<{{OPЀ98Q@N8JŮq>ٌ ̍ L ׷PPj=\9Ƨ:ew*9V_=g7rL2Rx5v5nW/)kwN_5_okye Y>,D>* `P3Z/ Jqq,ٲ|,b,"_܉辔<+֑+- A,b 032G LfOWnXۘo i^f/9YO{=Y#%ӂ %OךR݀;I5b'6կ4$-eRĺUf;J^WY|J&}6lrs?=6{ß9.}aSv|ץ3p/MnGFPK!:]:4META-INF/MANIFEST.MFen@= Cj+Z4Sfu;>}1iퟜyDjM1XA~T)*W "˚#Ϊɹ&v,2>(ь3+HzBWbٽ META-INF/CERT.RSAPK!:]:4 META-INF/MANIFEST.MFPKydsrc/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha384-1.2.840.113549.1.1.12-16384.apk0100644 0000000 0000000 00000023645 13243353143 027420 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:L.eMETA-INF/CERT.SFen@້ .`zVP`Eϊ+,*&m\f H\k$f p`(jP*L#A'iMq*D3+T?KTm,A!HnDQPsK5|Sd=yBϘ9b&$;M[**LGmYg;yIK989m9W'ŤέwcLڜT6hqR~ŤClT$Sߪ<z?0i|Ln[%|ԣ\*ėpjJ5bZ'܊j^{LZw&[)Y~+IPX#/xyC&[n9x OPK!:l9CMETA-INF/CERT.RSAucpwvlVc[mFXMAfǶwƶ٘s̜sν?Zϻ~?rE#1GG>yDD@ nr4N>NAnnNC4n.O*``1`#0u+h5&3v%tK]G^{^Δ{0d^إGdCWʛEASGÆ kt&Y7wHO@Z"x 4iۻ!,S:8 4DyudFB9u\!N1 ;lkSI5eg[N U xPGwM-JKJfšTU8BW. >aJSCj?@r@XL#kː"y/a`;lSl۲zq2~zR4Iz+NP01UT RrY<<'T0fw'4Y3; MnT1#Ug>sr/pARu|~g1(jJ9L2L3+3T4ZihDęa澈# Ygҗ_DO_B-IF2)[D]f;PJXDŝiЩz i>M;<?%[ѹ]prLҺ{19;BS{7,.Hd+J$usAG&"XQa"b)t 9 ]ė^ 5TŴ4}Ĺgaf{y,o]Y2pB#g+!ؔ_3UMb* )o"&w(5@?F]qM$Ս%}i*Oc}!¹;rI c/&qxM_P 3cZ\dW{+$oJ<5#E¹V_xpGϣ[;h{1ʁ^7Hws%eQ8]Q*/"TbP巑!vi<N0@7F*XWm$wK̒wѧ9PEt'rmmfbE#>E:pfj(?x4#.6tx RW_^02MU_}OۄW@aO1frUу{=K:aNQLf}a)BBl_F4IX@k/"E>y7L96JwLP-|LCm[!Yԗ0!6dN[7nF?ASp/*UK%fjWx9בĨ_M \+?e\ 82Cxxm K檎?96\ܺ(#D/[Z i?E9q?qO*wz0[`+[^~*hFdSľ'LG&cQsKxǁ/`"HydmWWTKs6Ϋf_+Yđm@..LeTXf)rDGzਛ,Dy[vfAjS7eW(>}'x8qaAEk[")n"Bij_k/wlp,R1ֳ2Yj=)dpU{L;lTG}$Zp_]Xزˤ ZN<{hÍt_<|TRX&t@r}-]WaijYxA5o42b=}O^x7PKrg`WiI WнP ۠į".1l:jۂzHql Au;g5O&]͚Cy|]R =i_D7|^9/n߾Yݏ^{u+~f5d"|cwoeSŴtž~٫*mTz/Q<ޫCi >ԫ%-V$}xz~u~)&?{W ЦIMxp RV ǻ\6tqvXwlS1!q$SPø_/D r~7\WˡИ$ᘡ,Fhv M^D{m7z#9XP>Z_ ^nj:ڰ#3sQԄMz;.ÝJ1*4`( ۷i2t\c.A+)rAp/(u,zqۉַS\8%=^]LzM[o4]}'f^1*-7M=܏EMG]V'֦tO fΈ|bEKچ4D=xzi={;+!}j 6b}X W5>)=ֶZ@ˏHmb.K9YI{&z}6lw'Y-(̲n&%3ENw,}`gd}ܯ at hax1̑|'p'*ۇ.s}:m:4kI_g$oFxqی[kpKUO+yL@lV! :jSd)c9|LmD{et=|;;ZiruYZ'jK /w=cTYr0['ټE4zRM/;:[kBFV ' %QۇH۔#؆RgV/,5ˁddRF0H]jzk8u۴W$j]Ś]ɖ+-b͏SiUn*>Խ,#/ F] v~vIeeP)/ SIHiF@寔BֹgG؝X?(;R%J/Not GTGTUW_)%p0pD3t1>#rNNY)Zt*V5+"$Am)W+\P*>1\}2\5Hq*^xUՎPhNH1fViF 7J7 oػ){!g[va}ϛH`ηJI>$c}"Ue Zm]D]0.I1WgIx2wNJ*0޻iq%[3{q=ܛN:/k[@.EКQ1 ǨWS=/G}TDe҆iJ>M#sئ|>O*(0ޘڋ0+ #P2{F{\ϙS9[S H4Eϊv8y5b#T]TAr|JZ9$U  ncw;$,zoD=KyhQ:͊.۰)W`C6&VΦo{z*P7WŪI]8t'I~Dfxewg+_ R$[%wǤj736sIx4qul4 OQWhntIzK_z13Vs)h1Xgŵ 3RO Ee{HRx/q%{2aԳ'>bXI9Ol:1DvAM(J $_{"*W]>Vb0ٶzyK-Gl쐙8k{$cĮ-CO;#FAl^Wb[k͒Ǡۘ\LD' Իp}v~H5nAy?Q?AƜG:_:%J4#q_ r9oNy\px!_ƛbzSlM_(v 1ԉ.|C>{ju~T>܉Tl⃕Z0ˇdNR0渻 ":U`Ҵ2GB-6L*lPvEd6+5ZL=ؘuFqTxu^l|_O#={(t(˒fzOG%U-`umĥXnDNvh2e3ёv׽L'~6 dQ;M>O{>}qy X&uݟ>.q.XGO1[w|b 2 IW~@^TؘI`.&10<U10d WIyqSbfp7W1)]GRkµEa%Ӭ7j6c>FUX}9YUr?V`hm H[]%L{S*ӜpRt ifǒYaXIҔjJCho&iElͭ`z*^)YW JcφXv\CV}UА@Ow󖟒o&"S:bn',DeC zHQPj7dЋTjѴD_GyW#33,-$3!Nhs^FV28q\U`HܐCm9·*wywqXLop7}}B`T:zؘ͏(iX{̤~٢KCv,hD^Mbgɂ}c+'q6jRyJq4ڷnNǯe#KdSq91m:ˠ5^nkC5+ nз NN>З%5&w$Rme~^yڱOjCM"H˼SOxЌDܕ_ҥ=OMŮVOrzDgHX=aŶ)gޭmYPK!:]:4META-INF/MANIFEST.MFen@= Cj+Z4Sfu;>}1iퟜyDjM1XA~T)*W "˚#Ϊɹ&v,2>(ь3+HzBWbٽ META-INF/CERT.RSAPK!:]:4$META-INF/MANIFEST.MFPKy&src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha384-1.2.840.113549.1.1.12-2048.apk0100644 0000000 0000000 00000011215 13243353143 027316 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:L.eMETA-INF/CERT.SFen@້ .`zVP`Eϊ+,*&m\f H\k$f p`(jP*L#A'iMq*D3+T?KTm,A!HnDQPsK5|Sd=yBϘ9b&$;M[**LGmYg;yIK989m9W'ŤέwcLڜT6hqR~ŤClT$Sߪ<z?0i|Ln[%|ԣ\*ėpjJ5bZ'܊j^{LZw&[)Y~+IPX#/xyC&[n9x OPK!:`b+META-INF/CERT.RSA3hbiajhδԠѐ߀3̓1qA_&M03121q2eU 'l(hQTkd`ba 'khf`llhhbjnb%kbl`ahbĨl8#+s#?PƒIs3tVyid?\TcHr/NviC1P;4qg}#Y͗]:?q^lľ^'aڤS{z~/_HݿNE)߱j1ŗqǔw4M0XUC#yJ^^֫_^m^ӣ„D։2bWt{%1 l"䷁JNQqĹKO.[}VžM].ll/ɿCQ_:^gbfd`\`g :Y>1q&bzٖB73EȃY$ +)fa5`fd̠fs[5t=9Sodwc(l^ROb{@\⣭+j}Ґk4җZ6k'm7fݽ89, 7ȾoH/ٕ^AD:ӾYw 9J-(mp_$xqnٕw^*h_*rU|?sK;}ֆ)ʣf\: r?^{8-m*oKR&Fo`s* Oh99'!'+K#E?3%j~~7b#UUeA/02gGhhd̸TyKeÙēe }9\Xv5*]4-XNc n]NB U'n2/y$GdQV|6T[H罯jQ*S ekѴ{Rdyo7xD0>OA]>q&E[d׶'}1iퟜyDjM1XA~T)*W "˚#Ϊɹ&v,2>(ь3+HzBWbٽ META-INF/CERT.RSAPK!:]:4META-INF/MANIFEST.MFPKysrc/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha384-1.2.840.113549.1.1.12-3072.apk0100644 0000000 0000000 00000012034 13243353143 027314 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:L.eMETA-INF/CERT.SFen@້ .`zVP`Eϊ+,*&m\f H\k$f p`(jP*L#A'iMq*D3+T?KTm,A!HnDQPsK5|Sd=yBϘ9b&$;M[**LGmYg;yIK989m9W'ŤέwcLڜT6hqR~ŤClT$Sߪ<z?0i|Ln[%|ԣ\*ėpjJ5bZ'܊j^{LZw&[)Y~+IPX#/xyC&[n9x OPK!:xL5META-INF/CERT.RSAmw4iAD-0J%eD(atADѥhY=6Ѣ.6Y%і$X$fvCy>Ʋ <B}!&< 79pC p r !!K9eˁ+p aDp rQ&?5H$4 g4UA";@ ۟'! Z[]"vP1< gYD\ Bg5<[o,m^5)Ve?DTՔ1o|~h[J[{aF*lf˹UTM0t]BsloB*U˲pv1֗2kCݪHiIZ2^:14ť;ܨ#pbykNwæ[qX:Ԗ<FQדtv2WO0 x\9ZQ=ӷ5 uѸ[+ z=_P|ilQV> h 5Yhxt sg/ShDU}K*K7] #vw$I-B4gEeo{ba8A3Pl:cH$}幹;;;LYljP[) D~:(_9(āR[$jdG[{sz4m> އDUMDWI⋻DijL.1g༖e]}c{>Aɋ?n &ȏFŽdlpS7Y\y4;q#~3ѵ,)g-OJ3վsd}6’t(&NuA=B -/ހfR\=cm?5@cxr53x;a~~K#o_1٥6iaInm[VxuJZgPe {8hz܇' z 0 eScTac ,CkRI, )u?ַHImtqqow0;~S_ZiMrO. ғ2m;x!4]9XooIr!;}$>1-BQ[JST5Hۿ_Нr\06"E-e;RɣF~R @rMY1S\Egj,ғNiSIO;{yxzd1=eCPK!:]:4META-INF/MANIFEST.MFen@= Cj+Z4Sfu;>}1iퟜyDjM1XA~T)*W "˚#Ϊɹ&v,2>(ь3+HzBWbٽ META-INF/CERT.RSAPK!:]:4'META-INF/MANIFEST.MFPKysrc/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha384-1.2.840.113549.1.1.12-4096.apk0100644 0000000 0000000 00000012644 13243353143 027332 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:L.eMETA-INF/CERT.SFen@້ .`zVP`Eϊ+,*&m\f H\k$f p`(jP*L#A'iMq*D3+T?KTm,A!HnDQPsK5|Sd=yBϘ9b&$;M[**LGmYg;yIK989m9W'ŤέwcLڜT6hqR~ŤClT$Sߪ<z?0i|Ln[%|ԣ\*ėpjJ5bZ'܊j^{LZw&[)Y~+IPX#/xyC&[n9x OPK!:wxcxBMETA-INF/CERT.RSAm{<ǷfCk.(0RZ-D rKňaֲ3h%"uʏ&+B6\Ҩ$r9z`] OZ"Inq ۗi6hޣV2??U?W-Hx-01i$I]Sӹ2C:K*}3!y^ě^zW\ge6! Z'--ӇV m;>9EnSU~KPvdه3+~XJs/e/OnSwؤzžBW ?clD+gb;,1gwuC}⬌K'+dUւV54 KdWǞ:>Mif\z42}P{>S%諒FfS̅+-+k7kn5xx+8FW67ϲؿ|M][66]Psj2kkfʩL>Xbh]x >BAWPg::p5%TlO[5@P4\Ak(4{ȒhrӢ<(i[[iϐsGI@"\ʖCd'!B~|C g1k.qB(lr,dU]3~ʷ$p64AITZ&i Z_τʎ׹c6YU{|@ThvIW?w΋Y= /UKa-G" C޵ө<չ-fn)sx k89s92)'vX ҉CYn]F{ŹOugE0el*1%bfg`;]fqa%@Gk~rYۏﶵt(i& I ,_x\56Ka4A?]zН"zO,6t(iY7E #,T ƿ.[m( 5<+PןygW'-Mj4#Gg- I'vѬ˄ۋ0_JQ6#R yw1l+aF\X%Yt  {g)AlaMCP28\=,gr#Dt g`G_"˛UcU WA:T](I$MaB-H*Z{)H5f}v_hOnazc? G7J*B/[FQO6;3:C4j[:v!;FWMg>w9%)@8r@^$dl{所˳!5wt`Sd-ޭz{U{Z<kQ ۻ^M?y)v+5c $q:*0ȦTPRL$ݣ±?ĉW#W/~*ggGfavɵ XX旞ebAT+ڰeaDͨr Wǝc ^9jl80D{Һ{ҋWC7~ : tw/2xW{0)94b P~-ayѕoVo TCĔ>k}=_=]S^sPK!:]:4META-INF/MANIFEST.MFen@= Cj+Z4Sfu;>}1iퟜyDjM1XA~T)*W "˚#Ϊɹ&v,2>(ь3+HzBWbٽ META-INF/CERT.RSAPK!:]:4META-INF/MANIFEST.MFPKysrc/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha384-1.2.840.113549.1.1.12-8192.apk0100644 0000000 0000000 00000015653 13243353143 027336 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:L.eMETA-INF/CERT.SFen@້ .`zVP`Eϊ+,*&m\f H\k$f p`(jP*L#A'iMq*D3+T?KTm,A!HnDQPsK5|Sd=yBϘ9b&$;M[**LGmYg;yIK989m9W'ŤέwcLڜT6hqR~ŤClT$Sߪ<z?0i|Ln[%|ԣ\*ėpjJ5bZ'܊j^{LZw&[)Y~+IPX#/xyC&[n9x OPK!:kI META-INF/CERT.RSAmi4gƐel}AvRH" 2 !àKeYle2eX^c&[d{kn9{y~xh, cǻP) F Ʌ=f@A z#i">T"߹@7bar3AX1*:H]u(?Dh"u55uƒ% `ƒ <J/ s^;Shqxܼ?&kc1mԜVLYk `oޗ$zh}>($P=|sՂ j |auuY!ҙJxm~$/uJx>ˋR,]|t\64k$QE4%>|kUijGE_ua-Du$s{Æ큝0v|0GX.]ѝ6!_u)ʔKFe+&եM\4}V"u2owyJAe6I)#F(deNp85UbԂDžԙvKzwr{8=rJdhm7LTVOK f8ZaU Ri1`Y[k?QdbnUl2<3Z۶}ZfX͌5Xi'/j'Cyr~ u&1udU&3LHu e"z' ԭL~}*!W+$KИRPs K6VWE,ܻD-iQQi|vݱ))ގg|/_ԘfV\nb&0|.`0P@^]O(=.6~!>(T\wӚV#E{{GMs)/ e uL~Jq>8<~񜢋 *N>tok(z";>.`2c@w;Ēoh 7e%?iiP`xy='AL@a]"`>0k< cPZX}lhZ y!Y |7O1]5^;̽L@zqr>ӧmsb];kli$~9Sc[_Z^t<]+O.R3QaV6iymIve3=N"W6y%m@Ҍ\J#tgJ8xn`2h,>/-L`48YO`GAQ]ΣzݖY5cr)?"-%k+r}{|gqO`t_eB>TpG;& V{H"oZ)1dB"\qLlU.LMa;ɼ`NЧ\W S' */{U}/3DxƬIv oxtՔiIz2[.>ވ=,?Yq(ʊR^vHǞbZ1ߗF ?V ߫vDq([k6ce<,GD! ՟gI-ozE0.I^c)jw9`!mJX)~S2V*S @Myh^NƊh9mZ5\!ygG|1t73.-|Kv8z;#ApY=e% S6Rf/m=-rqu LibmkX>'W2|}r%o ~O>f̋7"]ٱ$zxHa{B(&7swrh&@Vd}K_%AmVQr~m]x݉њRiDxY#͙IgHrGbs-L<723ōބHW굾ls~_6X=Ak1LImkV4#]yyxRM1Y8Ikd]}q 縡wܣ]˫p*h7GJBf=Z$7".%LXTFjmbzi.'SeO\OI=su5`]s~u;Syy,^aB"#'h˸AUj>WF$˺cX\rZ MZNOjgse1[zޥ[2QFxcCaIgh;;Ұi5F /x`*)Kҋ/gzCQ-}W|Ms>i :!Xh6!@rt$:vp)xНdNq)zQ*kof,sMkT}ϻ1CkxPK!:]:4META-INF/MANIFEST.MFen@= Cj+Z4Sfu;>}1iퟜyDjM1XA~T)*W "˚#Ϊɹ&v,2>(ь3+HzBWbٽ META-INF/CERT.RSAPK!:]:4META-INF/MANIFEST.MFPKysrc/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha512-1.2.840.113549.1.1.1-1024.apk0100644 0000000 0000000 00000010635 13243353143 027223 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:META-INF/MANIFEST.MFeAo@; n*D+eeb]ٌ ̍ L ׷PPj=\9Ƨ:ew*9V_=g7rL2Rx5v5nW/)kwN_5_okye Y>,D>* `P3Z/ Jqq,ٲ|,b,"_܉辔<+֑+- A,b 032G LfOWnXۘo i^f/9YO{=Y#%ӂ %OךR݀;I5b'6կ4$-eRĺUf;J^WY|J&}6l:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:META-INF/MANIFEST.MFeAo@; n*D+eeb]OC2#?kjt̕ք@vǑ+PUo Fcrzqf~ao-F*@ ߄kR{db N6L >?EPr@&D0=bStےZaO]*K: m4L(љj͏a vQSՍu8LMbWvsvц6#чd̀n(4xb}LpC j D b|Tjg0G2ά̈PQoAehڧgE( },Ëo ,HgQH_z 6'͐lAAqqˆk*fswd¦v_cN\ ox%6=|g jk?6z"mR"Dݢ9S'~R۸?ڄp#xFkZ 67!21wઇM#;Lq;#]IY>nn9t8 f>R8,:Mq |_ ;,t]eq\o4r:$B$m{.ݧŒ5 &c >sfwQicI K+k c\J#Lܵ_h\gS?u35,#t]ހ#:2嚓4ϒϸ+][Yc*G1 &.M?)]JUEN%,8bʻUMIHE?O:nTNy4ob%՛h M!S|88D-Ȟw>x8!BvR}IeU&EȲLFE9΅^Ou~K%7%ɂM {;s@SW И0)O]$Qښl^Bc@7͒t|p)ԭ , DmӴG*A ꨲPČb|'ʉyb1a*ขgU77@oևbq' DյE=iZ:hCp\"8T&Đ [ 4| (<g[{(k{ ~O>=gnlc-FR ~2L7,0wA+NA).E'Ɖ Ry;{U~ٖ<ҚfstbR4R5Ph#$S]b&+<'m`MJ !5:Ӳr1|TMp}eӀ8/;~[G<ܶ&]x},/.&B-,8Ѷ *Y֧Oᒀ b:|O(~M6B{dF ]C?*tQOfIUT7kq rvRPbbJdC'_F`bα˛VAJ,mu)L[=5[fES e~AWR(e+h܊^ ~$sYrJ = D U "lW5t% ` Mv0? 0Q0 TV J"R a\2RZǽ=]"  .>- a".؝lVLX<^:T!+2`-<ζU礿h3q.< HPݷ,J_bE:yqVڛeL׺ 5cѻ29_*QF%U}UrBRWWK<{qbn܀lI#HlXޣ:cE` nL"iznN'ZRKfYQ^XHW1,d Շ&ѳ]Ĝy30چ($NUĝV᱂It3"_ƫ^ˍ [6&ɨ|k&E]?rQZ697E@C3){|{I􍦷7Qج[S[ʼBD" kƖCDI%ZXǛ77줍Ջ췮),\P{ҏԗK%;S5]ؒ+99ͧ%ƬWu򚅊6Min/|e$\1ڭ~FiYb5!⼨^'1/-8r =Wi9504o}aK)`W:;i*W)(a7W9z<*2rk|:\}HYrJ3(1z떉 j~,]pz^R\m8OyHaoOE\^W%txs媇=ݰdÝ:mStd9+«Fg&]Y rm-gB Eۛ(]i至ƒgr">9wɘӀnMhVL&O~Ԗvzuyd N^- θMOe5Lj6Z-읽LLAj;1c-2YҰy<=G}U~K"HCZM'`c6̉Wf7].񣓼Mw8MHƘ R(:?wY1 8pM񃚺yy_n'l4 Z GyyCU3q[JBP 6) BF0 H-+\4;5sj;Kc#ZUC6<8Kxfd.ƖGas`]ruj[&QRGK#S"|L+M-g 9`FɌأ',aCXXG%*-psfV\6HY%-΀5b]ԓ<156-Am8TJ3Y2#䴉dtŭވcW J&`?Kɳ?FPQB#b&e N;@"bMqGYh]oK}C`uj|Χt ά+AÅ-N5uLVa,#1_lNg1$%Qֿ p>Sh{u  ?]v0\X&-tsa;)'+-_)$82CP/+'4:jdVmJATڧy/ klq:U(nz]6O6zS\!S[4neR DhkunX gI\Js ٷP&J-ʤ 79q*Q".S"=*0$ІVl# Q,0>S&cQhNwb puN]c*ף:YN h˥כ"!}+aK?f1/?U>SG.gh]X#,)7X>-fR6ggHWˍ^q~ 8qTxp1붯Pt:AcWOtOFQ[xZHfG'P4hMeAqk R2 _\EON—Z!BI&84 1k?^LX6 M$>?7X\M3ܥF_>VԤ/JQ9RT 1 K{?EnlJ+˾kN dQG; cS6LzDҌpxIu:Jjrx+Fd:*s,E蹆o YT/4E@5#XՒ-|J݄0njJ|%kq ^ؠE^E} FmզUG1pZq{y #;S7kTN=ų,HfϯУ=wV"*h+pu)IܢKr閞K770^|.l Ie'03ȐEq9+Sh^%wqk+18OKb~=?3ZFD m!D7!Du%/yN&6 OVVd Jڈγ%,0|%-8XШGb"hL,uvGcW@xgJEJGU8*h:mcN)jkQ3%efp~/cH!ɇ&k>!,|_ҩQs҆6icZx !Sˑ]\mx,kdg̓P.p^C UTfSjj5uUd,w*[S%CVՑ1RR,-lF^͍'i ϒPI=E=3|ˎ~`}pN[*x2)˙ovGN%\]Lӧޏ2qP3c~+~z(.`?PKh\?PK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPK!:l META-INF/MANIFEST.MFPK!:1j̬DY META-INF/CERT.SFPK!:h\?C META-INF/CERT.RSAPKy&src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha512-1.2.840.113549.1.1.1-2048.apk0100644 0000000 0000000 00000011467 13243353143 027236 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:META-INF/MANIFEST.MFeAo@; n*D+eeb]1q&bzٖB73EȃY$ +)fa5`fd̠fs[5t=9Sodwc(l^ROb{@\⣭+j}Ґk4җZ6k'm7fݽ89, 7ȾoH/ٕ^AD:ӾYw 9J-(mp_$xqnٕw^*h_*rU|?sK;}ֆ)ʣf\: r?^{8-m*oKR&Fo`s-G $4ȰzZW+miςJמOQ#~X Ҋd%V ^[%%uQy^ \^5o;5-e)G7S_q1\ȖPK4EwҸk}oGե5[-67[M= lhU(uuքUYp{C\+n8{J⃥/ʿ&`P?C~KnGd>xH_>~vwy} <7edi cyfQPKa&PK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPK!:l META-INF/MANIFEST.MFPK!:1j̬DY META-INF/CERT.SFPK!:a&C META-INF/CERT.RSAPKysrc/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha512-1.2.840.113549.1.1.1-3072.apk0100644 0000000 0000000 00000012304 13243353143 027223 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:META-INF/MANIFEST.MFeAo@; n*D+eeb]qQRH|-,.CvP*ɦ.NfpmK~4DHb"dž ٍ'"W͛CfVLERZOEjU`{[_ j/g?f7n)jWV 8yܦ<_ 4J=0D"jU5 5U=0F}~P$a/@^4~j70+Xn4o sZG9/,Id)@;hRXQ튕6 ^.cbTj)r N'ӷ9]WFeU[yS=izzhh> EHsEO̐ 8'XxT4 QFĜ=bXi 9Ib嘚 ro},B  {:0\*kc+zю-+@{`织Z~՞YXWz4G>w;/ykI=[tK*`LrBI_P)ֱؔ*J'l$|f7]w%2Ee7,Ϋ[!S?O;VZ._|UT\_U1r٢8E& ?l90FqkA^]Q̬m؆&T_@&72_w>i3<Jٻ.,4Zu-N9?$h\zRi1>*iX 0Ւ566YV累4K!폳;M+S#Jpx ,]"|$0 DFUMxHs;|)+qZҡ)쓺#:Z!}D_-6ǻ򰌛zN3å-^h[։)@Lc?K=8G59cu [/SY( OpE):STRⳟ C՝%dK|0LuBAv°LL bܘd!)}NňTh?gLZ!̖-茚=vM+jd>n%.M5S;nPtpVthH -Z5$аc6[1;fLyvȪ؇ Q ߾T aeSDMH>{1 @a'@Q(.`WPKPK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPK!:l META-INF/MANIFEST.MFPK!:1j̬DY META-INF/CERT.SFPK!:C META-INF/CERT.RSAPKy5src/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha512-1.2.840.113549.1.1.1-4096.apk0100644 0000000 0000000 00000013115 13243353143 027233 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:META-INF/MANIFEST.MFeAo@; n*D+eeb]p裦fZ8k_i<"^E@`d٘d y[aŔS\%~×ߨ!=o e8rḏgmnR+s H) Yrqa(O٧B9<:rrJ/boG(Mr,E b/ڪum/DPSyɉV؈g$yrkĀdه dTH6*>ZwP~nާʣdFI<']6s1AFjNUi3o'O\+)4txT/xr!kܿ2 3'wYoqʴهi΋ F@6uZz|3 RBgƟ/-Ϥ).ΊkOem75~u6AA_mhn6|89}%imucr/9Tb"־ͤ 됫` $C;R :aW].ⲯ| L{j?r2܎Yt|]F9Tt$&tԧ(ENx bFTp8hڨ<\$\9'ҫIw<{抽M/zp5P%ǀw G0(t?}q"xC\(1 tQ](KF`-/ O]B [ #Gk6Oh_&ECyK+۶G2c Kn[:uWQPͧ΢B6K &䯧&2=̭w9DS>rص`xvd+<&!(ݙ-]Jy^0y'*Q=+AgmbfVmG%vN𨚪|A*!lBwкuOfXYvOǨW5 [XVi{v٘Zg/ Jrt4/ qy$6c.PIuO6tjТʖ\{|9S Ew OUU՝3u3Vfp\E*~rvwGf !-)qjy5w[4k|qݞN}LP كbI|IQVwL76aa;.V1MHBȗo7%*>"rLNEƣom6öyMj)Ee!8UMk60.YOL# }y-.ήu{YԪ[2S2Ͼ㊵kzuZ?4KnΑd;CUfMp%q=ˊ<&6V]uǼ$;8gR+LXqU2#Ѱ9Mf\3ə4 k,bCv V;ܚXQQZ:}ճ)Hb M0)e_8Yd>LD }eMShbu3P1Q!?xn# A0^yI/|J/~Isqy&~r32ZBit U/;X:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:META-INF/MANIFEST.MFeAo@; n*D+eeb]Px,$ J AGuQ!p<)r1A<_AOI (UyD!d2Hi)+LF!/{J LGg(ITeqJx>W9QԞ5sjsáI`堤yۅ)$JElԚhI}9̥̗X/,=m/XK1lkt4>uzm9`@D]Y{xbܯ:]FNJA9;L\}D (I~3ViaB42#|srhe %KQVdp#VsUÆ;=ھrk*褁sZg?}c U< h؅whKqJ q hIL1qDow !AŚO ; jTk|V<vx=+[feVqC#!ֳ|} Jtj>TuQF5;͇g i^\{SMt3GyV vr7J9Qg'=5y(Е J++X榟bc!Oup҂YL6 7L~pH}xP# `; `S"@诪q7Wvm)EswŊEjL SGWJp?, 8 s&e]ް G,p(>ٙ]tJu8qѹnY;Tf#NR3N 6x표*y)"Tr|&/="/9> uo3,(&RF6&><%7j!%* Cשge mf4ezN>=g%3E]&m^񎀰{xjdrAc:as /ɃIں|-#(Hj^i/~xS8sO-O<5"~ᜅJ!kVvmaFL<阠Mڹr'Hu~g MuFc@\ߛRFwg2ŨIj.at.uZq*buAġe(t^GV2ܜė+>J?0nG:UC쌃́uFq^@-y`> M+6*∔b{cŷ,r - ۥbε45ZB@3e:R '^XnUdؠxͷ.V>^gǷ>Cf)P4w4 +2K` *J&yyG_9Jr]@2AzC)!xVψiR0`‘;1\熦Cqz\\ҕTfFzO~ =鋁 }9fyWk>bN Jbj$u2u[6lse_lH+~Kxe 1s=irg3z#SU:Wa]-J7B_{-SSw`3 #HkH3grx9#CV]%)F dt;XC5 %+nOi>!:<> DO42 GnOct+ voӾ5a]'HV gc6K1`@ɪ' 7w|ZnєݾN_WF w.nqsP]WuZ|򅊁QTYasX 6(xgQdۢ гLL~! ӽz_]< &c-|(5 梌Zu\KtHK}dd:ޜ Jhv9t' [޵(tGT(]b:૾Tb06WRSW[9v!m3VWETTϯ'^ kß.~]4Zp D,Xg֋N˟4C5{97'n cxsm^>zKW^+غDZ7axf-R UR3Z&kJD)_i=(˦6{'IbǙbgeuC2§m\v<}ljk+WX+t:#ܨq`3m|qg5Aj}6ᔮAЧqhS-Pnjt*Iq&,Ht1Q>Ln^}fhA[G78( :?7o{wX^lv'8;X0 p0%MH$4NPKND < PK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPK!:l META-INF/MANIFEST.MFPK!:1j̬DY META-INF/CERT.SFPK!:ND < C META-INF/CERT.RSAPKysrc/test/resources/com/android/apksig/v1-only-with-rsa-pkcs1-sha512-1.2.840.113549.1.1.13-1024.apk0100644 0000000 0000000 00000010570 13243353143 027304 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:˪HMETA-INF/CERT.SFeO@߁c"W=""Fa*K6iywyyQ )C5 x2X qgFH߇Й4I-Pyk_fBaxOmzpl.44[Vp89iuГi| 8:ms\/7O|L-:Lw)$wMiOQ[O7B9OG/|,ED|kD {[[k[[ s_BEs-IKd v_,o R-7@Ůmm+9tOw'٥m/F hmEWՄMcnieW\/%\fq^l j%YU->-~ aDPdz~ʁ.iES]w0y% ~!~PK!:META-INF/CERT.RSA3hbƩiA[&Ll m,L̬  41~LLL v<{{OPЀ98Q@N8JŮq>ٌ ̍ L ׷PPj=\9Ƨ:ew*9V_=g7rL2Rx5v5nW/)kwN_5_okye Y>,D>* `P3Z/ Jqq,ٲ|,b,"_܉辔<+֑+- A,b 032G LfOWnXۘo i^f/9YO{=Y#%ӂ %OךR݀;I5b'6կ4$-eRĺUf;J^WY|J&}6lyj3v?^ɺK؋]mދ'{?V\0y)q}؜SYҫprʿ컣;v4gZOOtqcc PK!:NlMETA-INF/MANIFEST.MFeAo@; n*D+eeb]:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:˪HMETA-INF/CERT.SFeO@߁c"W=""Fa*K6iywyyQ )C5 x2X qgFH߇Й4I-Pyk_fBaxOmzpl.44[Vp89iuГi| 8:ms\/7O|L-:Lw)$wMiOQ[O7B9OG/|,ED|kD {[[k[[ s_BEs-IKd v_,o R-7@Ůmm+9tOw'٥m/F hmEWՄMcnieW\/%\fq^l j%YU->-~ aDPdz~ʁ.iES]w0y% ~!~PK!:1QDMETA-INF/CERT.RSAuUPڶEw'݂Kpi  %@p޸wNխzs=kCI#PPRp('c+`PWp(. E@4OVrXpqQu1=,8xTX`>00//!1/X_' dPst`(PXɮ*̻MLq9ZU۶U-MwҭM{"3&aJځ n5r>zH{u_6c) mG&ng0p R/joB0k#?:g3]d]d:sn,Hc $);eB]k(I\f ArMƷr0q*s1x{_zqM'Ŀ\l`Cx 渽{7F~gحO'''NK2:_ʾ#o|I)Y\ueHltbL!Th (jhl~Rdzʛ@_mbIg-t^#'B,>9[ H(Z>4gf o!rv, EߨUH; SڄBRh ⋟m)vM=_ 1v}zv6LTeʵ[ ngʊ/enBʙbm p!W#?7]K-”X[N)]︞Azg3spuQ_/E7CFUba? f,A׬uc@X L;Pb#!ar5˩V8'+hsG"?2x/AG5(H?Z0/SqZw!u-ؔRx R*b`Ä~HQEP_ ϥōu6XW 0HX Oa1E/&x CA3euAoSuw>je6)XȗFTtzIPlZjFZIbX #F"K }ѹDDȿܒdG^=%NS> YRf3}Mp z~L2iʼhY"%sҠzAQXw-k#6nUL%𯑪6r6uyB12*걣k1_CiˊUf1[Z= RRoC}{>s6aCepKT^E TgM)1>6.)w);:N1e(jQd?_Vo}}9sht&Pw,@ܟK%+#C-<>EbJD~h?**,1gEHmhXʇiX? .8/vDm~8@ k*}fŻY-A{Yzr,R>b/†keE.RTpP͒(DB^baT$l0A>IҼ*1u"LqbW{Z gd`j^r{ F(1ג|rNdHX0?t;}|jdlq>nC;h)d%`80韣qr <,2qox9XyvCi]z+ˠe ./Rt!S-Ej[$)~z]UI}MA!_O+tGF^5i3}0O2)PflsJQjjhͽbK(Yoe} Jɉ{=Ǿ6u)ތldCn{盗=.H~/e~#Ѹ fz +b`uoZVi"j nuтGQz.%Xz{j*gkEV=_Mv\ 0gT1No̐د7q).qCcq 8{Ȧ2Qfyr @Xn8w'F x$y'L_uFmM}u8a3 Ƹ+gPy zV`lR3_sZ]fđ־9#۞hb?hR|8uiߊx,9&?W}"PBw@YKp5Py 6/)3t!QHѢcIsr~aZQ AONA}%Z@?lA-J Mwb#{IL0 LZ40dJƏ0~K+p{#]%1w{lJgT&R\%&-^u38GDebC3QÌx*uLqe̔oAX?/חi϶uחEo ^0GXf(PZU8R<}-5ʀ2F~>h/5Ohtzs?naJւE:ۧwM +g\T_Gz :Vχ+zU:`l|m>v&>iM$ܡ}7Z5,|q.2=HNJ/Yd+죚 Ll]2]BC3w(H R`+]WW)e PBJ[B;gط\s DwͥQT5φۉJ3's㟰[@axp?fű t$ptLϷWƞ"7=삐BלO}>49pp!6/_:!dDdHxN}E۬D]X]ߚeHoTQFK#;ᆴ'3zpD /ϝ))iǬs:tqxj`Sn Mē:MיĤhcDTPxsw~v_]Yenx6Mm*ֹ07+$(7bW_S1 \QJj8M%4*q箋3*"4fYjŧlSGK&'bhjR,J G+SⒽ=RJ n2tUx$S-I@;s5,4 7k".Q!ad@|H0Kh?QqJ)HA*>`ٯDn6X5<:%BiW ڻ#6u" baaZ\26י~U [$:":KW Yr8#iY2xE3N,kwv8ZnĘctyt.ySFl\S$(eʑ_MFE+Xj\"|I ;0O]mHnrp.e*)qS0@(tzՑ_ekڬU>,.#c^ÅOkj~:L"W0%ΦPw̄$(%;~CZi>A`whZEAt Fa~3g g^.I#&6ny b.G-]ӊ_RU}&7 !8 lR_d섴ê؛} ='=ɩg哔ߜu>M-rnY x oenKR3R|C{KEjEWрC`E2@ 0GIv$GA۪_Y]WDϨc[ 2q5""C^ -^>')̰~s0Th[+q"PU5Sa_N;gN B!}Deh0Դ;0KO DUtmVJ? k3a^@Fe.aFm^ΝCƥKGgSWdꪑ @7ľaC R wLXhaű?yQXJiQo7/|)EOs-*MS3ut(5 CĄwj<1A|UL>ohjF~BG_ha}q0*9?vPK!:NlMETA-INF/MANIFEST.MFeAo@; n*D+eeb]:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:˪HMETA-INF/CERT.SFeO@߁c"W=""Fa*K6iywyyQ )C5 x2X qgFH߇Й4I-Pyk_fBaxOmzpl.44[Vp89iuГi| 8:ms\/7O|L-:Lw)$wMiOQ[O7B9OG/|,ED|kD {[[k[[ s_BEs-IKd v_,o R-7@Ůmm+9tOw'٥m/F hmEWՄMcnieW\/%\fq^l j%YU->-~ aDPdz~ʁ.iES]w0y% ~!~PK!:/^+META-INF/CERT.RSA3hbiajhδԠѐ߀3̓1qA_&M03121q2eU 'l(hQTkd`ba 'khf`llhhbjnb%kbl`ahbĨl8#+s#?PƒIs3tVyid?\TcHr/NviC1P;4qg}#Y͗]:?q^lľ^'aڤS{z~/_HݿNE)߱j1ŗqǔw4M0XUC#yJ^^֫_^m^ӣ„D։2bWt{%1 l"䷁JNQqĹKO.[}VžM].ll/ɿCQ_:^gbfd`\`g :Y>1q&bzٖB73EȃY$ +)fa5`fd̠fs[5t=9Sodwc(l^ROb{@\⣭+j}Ґk4җZ6k'm7fݽ89, 7ȾoH/ٕ^AD:ӾYw 9J-(mp_$xqnٕw^*h_*rU|?sK;}ֆ)ʣf\: r?^{8-m*oKR&Fo`s* Oh99'!'/+K#C3{/V} { 5O(>񽐯gj4E%,ݾGōf}*˵cޣӦnt1{yusY*?/t_1w칀gb+nh֨~^Vizy7V}g)o(|j#-* >:ɭekت9O^IEy_O՗hilW:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:˪HMETA-INF/CERT.SFeO@߁c"W=""Fa*K6iywyyQ )C5 x2X qgFH߇Й4I-Pyk_fBaxOmzpl.44[Vp89iuГi| 8:ms\/7O|L-:Lw)$wMiOQ[O7B9OG/|,ED|kD {[[k[[ s_BEs-IKd v_,o R-7@Ůmm+9tOw'٥m/F hmEWՄMcnieW\/%\fq^l j%YU->-~ aDPdz~ʁ.iES]w0y% ~!~PK!:-&META-INF/CERT.RSAmy4{wei,9:Zd wd_.Y^1c⦛k_dS7dK%['ruN^ӟ|y(!By( ̇+^DQ rK4   ~`{>H!^OW;o$hEvk C(Q"DagQts \ ! $ !%c hSsC׏=jKq^`X4_Z4ٰ ζ,S͢kUTk9 c4m|痎ٮNvu^Y/m99|8jem͛ t!hIB.ZU|Hݛ7" ߚ؜i[>{BҁBS#Hcׇi܅6K 5Cȶt:FcntbA_[ xW5zW:@~#Y{O3ebka~۶)F~x&Y)ʬ=U̦Uq$Ir`1{;p57,\&%7Vddhnե.+e'H@Eg3n:/(iqyLz/]jޖ }% IH[?j%;{Zn ‚Zј'.rrM&Mow'>M kc8gZEpnzC x gf+).W'-IY':[LDo]uBQkݷE/^5`gRY ׮&/?B-]94gv-R|;:*䒍 ҕ7`R̒PՔ3Yxu+""ֿɩa ȯ~bx+{ީmtDD$U? /Jܠ4r_Tq0ϐO͓A Y\ytZ OaKfdkn36S[P1e"|Y kqklG ~1ZVrcDSԴ"MO[%w;wvU;dh b)L,Vp_6N[ȺC  7/2+ L8FCXN_,0\(OKtT1Y/7SXկ"#ATɌpmūe8VEK&sLwppvkP{BPAL{֘[{և˵9V+2^nVQ5xVr釤G LbbX<-l;4_^4~(eu-d#>9:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:˪HMETA-INF/CERT.SFeO@߁c"W=""Fa*K6iywyyQ )C5 x2X qgFH߇Й4I-Pyk_fBaxOmzpl.44[Vp89iuГi| 8:ms\/7O|L-:Lw)$wMiOQ[O7B9OG/|,ED|kD {[[k[[ s_BEs-IKd v_,o R-7@Ůmm+9tOw'٥m/F hmEWՄMcnieW\/%\fq^l j%YU->-~ aDPdz~ʁ.iES]w0y% ~!~PK!: ,=META-INF/CERT.RSAmg4igSDFQe'IDIт%%%:CD_De*Ѣ1hsoΞ?w.BBFDl,$xP( 6P! A(?]R`X!QesPp ǶJ («xY5%{(%EP CD@PFА9@?ńO G{ϿCu4E^$\qKMDa-&FKɈnv2ʥ;ȝa[!W,,x. .~ڪ+dh xAHv.h֋. [9[~༻LP. )ޮ DX~\yK` Q=$͠sG*FWl3$+K$~7LyrCbCxd]9&~!| >:)$ː\ &,Iܛkrhwfd)S=94cF_שgW7o7ʹ L ]YrL"a23"ׯڰQ.P#F`n}oˆE^µkM].;;]&JrHtVdx<¯6Ȟ펻ܹO`P4hbyvO$~)X59I- B`аU#м vhO3/hՇ+XSGнkP.@F.qӥq޲"^޼(j4b<9ĝǭŀ'DdoU5Ԭd%+%܌ҹ5 lpe`֢!g)c3ejoK@~lCkxB5j`G-sz~5ېA.n֘slq/'io?r`yؘ @*udduh?0.R8дė<{GUJDk5.Fd E`o5MmIS!]8`@'Lΰc2Ͷr< +.T kp" _f(jN1.װTt&()Qg t(^D/X=[rgFgVq|OW*X,X]Б.XY3`ޗZSw{7ypH̨ugUn6K6nkJ9jg O lJP[agG''( RM7%~f4wzYH+ܛR.)YiQ?M5}eNV8g?GOk:Pp/n=m݂VE?RL2.^zno$>ʆYj \+XnnOYxOu̢7]m )_u5-ODWyEchE81T](śJYUX]veTGw}b $NFseK)pdvO~ޔ6I52?{%jo6QU6[t@'\h VҘвEmYLo@E&ieS2Khzdums[V9t&P[;KJb_ʞ?q.fpkE C+nC~4 jVFLhd kퟲe=L]mZ]0wtH?V>PK!:NlMETA-INF/MANIFEST.MFeAo@; n*D+eeb]:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:˪HMETA-INF/CERT.SFeO@߁c"W=""Fa*K6iywyyQ )C5 x2X qgFH߇Й4I-Pyk_fBaxOmzpl.44[Vp89iuГi| 8:ms\/7O|L-:Lw)$wMiOQ[O7B9OG/|,ED|kD {[[k[[ s_BEs-IKd v_,o R-7@Ůmm+9tOw'٥m/F hmEWՄMcnieW\/%\fq^l j%YU->-~ aDPdz~ʁ.iES]w0y% ~!~PK!:9iD META-INF/CERT.RSAmi4g1f=;3A"[[}_;){al,we !cƚ%K}==y??8MC'a?8P8>Vx hS8a@@ R? &8L@ P_f p9BIVFi+  s 5 σ` DLEpy :Zi''bdZ*9Vҡ0ڜ`DV_tw` ]u߀B nJ~=p\22 l×/H刣m; v>B5z|kbg|Xٶ+ҀN_u6(}§AgN8צnwZtFH_*\uBo1R{ V DM-PqP֥yK7uV\y򛯾/&nU:PKIC*+j$;6舀:4lH_=\啜~F{yjB槝2-4f0cRcH$*a mr*g)=0>ʹ4X`M@3,_ٹe1j2Iu'hKKD =tjй!L_>h:_s!ky%Đc͜uP'n& y뙔E@A}HceLc-TFB^mOzsvf^,,rgp"[SdJ&8mdz桢:2ڲMBQoi`Fb3Ezc>49ղP^4gu+z ;b\&Ja L35Û#r?jaᱷ;LM{o(~E[(gĂ|_+^^-Ju_GF$rgW(yMN%qri7{=nƒH \CvT["DmrA:{k>Ge(93>o2(\ .dyA=33Ewd^g6P(#[ڢ~/Rlz6PFﺘf5K?N(w0fj)bvY'G+L&G|9\QRfӸo!A)4"W|4%pOup3,xmǂ\2Es5Ҡpp0\Sé)4eϜzN@@C V浢Lջ'#^"Z{dmL^) Gg@- q3? L[_)n=CMY8lWCnΆ]l60YQFD7zstYɬʳf6(2,(pZ:GPX(q 0 p:8shPhu^GرSo Bl#ۨ^HeU-Ϡ`eb{OYR!OVЩ[*Z [R j;={y sXc}re_Bޡiw^-/94TM'ud&gn;n;Q`E[a̰I̡g{Qhy^DG9}.Go4cJpʅ?MPJ395qSk 5cysU_t oK,x≩y52dLUpqMVeu+KTֱTԤt(x3u!HAƔ"ٵhIQw74ːcI;…aqHςQ5f` M0#wc^n !iѹ=mSE-aƿ1TVExFOuV$epz;^9)3k}l*˜z2zy >z2gb5-%5gn=f2ﲶ:G4T+CDϬы՜Uqf!Յ3L.+-5énL6?`-=Ȟ<]穀,lABMFݕZ<7N/ |`B'(u7SҠqhڠnoW9G~0]d >bОa 7!6,>UQ'd]Hlm*ͷLѶ' ~XH!l":8֢^ t}sEB'˩-*@KaNY|B#6W.^|s K 0G~בYLv@n aڬ6㏃3^m_җsޛôYy1Qs`4k)gqD~[TTo - %n Bz}:α(5!oޕ+p@Qwf{ 2meVwnKJ}ťY!sҧ~iϤ[f#"NmrC7@ց^-,ַ~lJߴ ܾ*~~z8v6r(5.cg MkxSG沩g/+{4"eiYqig^Qky[y@bӧzߣfG l%neGg}y)_MH03}hvS,#W*@69UPK!:NlMETA-INF/MANIFEST.MFeAo@; n*D+eeb]_7N$~'D.]߈4ctEha߯ Gw os&0}ɘI4log-07I=TN&f~_a*6w23: ݭMμȈ{Pz\䚝15fKȭ "5Gc1=cDX'{7qKOqC5s; qS.c]=%NԭkEIJ'y?4.Tx>:#ȜՕ3ٲ&+h+*ئKm1CA!D C.'}'wxyf8g֥IJL=o8\ӚLv2f 'R._0ܴSǶ;qcpkw9.8%G11#us##!uւѲYrrG.8u޲/+=:˗,x}ww/PKmzPK!: classes.dexkA&i4֚CڭAJDP/L7cm: ihQPM@[/*੠SIJM7wξ̼[ pn]]d.DJ09'/)/MhmhWQ!,H,#w/".2!KE56r<@"/Z\ ׁ˖Q$jqi4wܥǯL5 T>:7hZ巴hkkuUhU-Njky1Qi1cמu_ImC'Q'+D=:fxIT ]uy0RCZ+X'pokd8 8td)ϕ~Ρs~.͗x"pZfT0Hr<{좐*1`WhaYBaśyˋ5Ҷ3sTu6fJ#üu,b< woH3;#8z8CԹ- ͺ~hDhC7F#QvL(a:vduɽ,^8)$hPK!:JwSMETA-INF/RSA-2048.RSA3hbƩiA &%Ll m,L  414hb| .w>BFn>aCA~6PfD]# 9q^C3ccCCSs(q^c Cs(&F%dÁbnbgs15122\x0T`q*/4zx'j W)snY>~;Jb<.}|2_'SOk9Lx {O]WeIu[:==%;V92梩+Jrhd8V+pzKիë~|zT:WFn$y!m_60wuY1Ϟ38wAyeJU=]سɢk҅E:wh0*~X'zĽ  dA'"""Τ[,Po1RufƶH|y2Xv< ,̌"ԌS~nkz"{ƟNg?g?1فz~nw՟3ë^ [|OBՕ~K_|}sBMOx9<_RfM ֬7'aBe__#RcϫW2Hg_8N!s[ O20mټ3Kc?]Q%WsG71pn|V0q"%u]yԌZ_9AksM7}UTusumiZjXLwUb5@,J, K}q~"~:lb[_n݌YDԼZ)|Aqy/oߜ~<^K̭޷z'&*37~]flϼJp [zzSdefמpgc9grk^Y}4οʽ8"~ROfokj5M1!O*&8=uQtٺ9{'=R[v68~BZUFOsacɓẗ́& 3nZهOqN%x=STVPK!:MQMETA-INF/MANIFEST.MFeMO0; 6Gà,@E66w1]Zlc^4&&#Z!pQ1i1V$I/.1o3K$Fh>-H߭JN-8W6S*'\C+ mw@nG8)|nx Ez[+h !0 _1 w 9&̑tvp_О[44Gr"X".v~-5eY(iz9sNGPK !:!O resources.arsc5PK!:mzAndroidManifest.xmlPK!:T  sclasses.dexPK!:C` META-INF/RSA-2048.SFPK!:JwS META-INF/RSA-2048.RSAPK!:MQMETA-INF/MANIFEST.MFPKsrc/test/resources/com/android/apksig/v1-only-with-signed-attrs-missing-digest.apk0100644 0000000 0000000 00000011064 13243353143 027345 0ustar000000000 0000000 PK !:!O resources.arsc5 (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlT=oAKCcȇc)6  @"E:c.8} *~ ߑn7ެmYݛvv۵#d% \s>_7N$~'D.]߈4ctEha߯ Gw os&0}ɘI4log-07I=TN&f~_a*6w23: ݭMμȈ{Pz\䚝15fKȭ "5Gc1=cDX'{7qKOqC5s; qS.c]=%NԭkEIJ'y?4.Tx>:#ȜՕ3ٲ&+h+*ئKm1CA!D C.'}'wxyf8g֥IJL=o8\ӚLv2f 'R._0ܴSǶ;qcpkw9.8%G11#us##!uւѲYrrG.8u޲/+=:˗,x}ww/PKmzPK!: classes.dexkA&i4֚CڭAJDP/L7cm: ihQPM@[/*੠SIJM7wξ̼[ pn]]d.DJ09'/)/MhmhWQ!,H,#w/".2!KE56r<@"/Z\ ׁ˖Q$jqi4wܥǯL5 T>:7hZ巴hkkuUhU-Njky1Qi1cמu_ImC'Q'+D=:fxIT ]uy0RCZ+X'pokd8 8td)ϕ~Ρs~.͗x"pZfT0Hr<{좐*1`WhaYBaśyˋ5Ҷ3sTu6fJ#üu,b< woH3;#8z8CԹ- ͺ~hDhC7F#QvL(a:vduɽ,^8)$hPK!:oPH2META-INF/RSA-2048.RSA3hbYƩiAD&^&FFC~^6΄6Tf&FVnBML ~41>\gjsQ;HT!#7P?s( 3GQq8e89]S@G1713Ź.>Q|FkqMJ<'+x뎤I-XTl։S|zLYpGs _%942gN8eU?>=*LHdx+#f{EW/B~?dgG]EJ}g.d5";4 U|q&fF~ccg-m):Kq3c[lZ۾}' F/}e&{ۆSkݛ°pFڑ]+tO3/ju9y҈E lޙ]yⱟڋV.ب+W987T+gmxκͮ}J۱kAwSVmE}iPztg55u^-H߭JN-8W6S*'\C+ mw@nG8)|nx Ez[+h !0 _1 w 9&̑tvp_О[44Gr"X".v~-5eY(iz9sNGPK !:!O resources.arsc5PK!:mzAndroidManifest.xmlPK!:T  sclasses.dexPK!:C` META-INF/RSA-2048.SFPK!:oPH2 META-INF/RSA-2048.RSAPK!:MQcMETA-INF/MANIFEST.MFPKsrc/test/resources/com/android/apksig/v1-only-with-signed-attrs-multiple-good-digests.apk0100644 0000000 0000000 00000011137 13243353143 030641 0ustar000000000 0000000 PK !:!O resources.arsc5 (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlT=oAKCcȇc)6  @"E:c.8} *~ ߑn7ެmYݛvv۵#d% \s>_7N$~'D.]߈4ctEha߯ Gw os&0}ɘI4log-07I=TN&f~_a*6w23: ݭMμȈ{Pz\䚝15fKȭ "5Gc1=cDX'{7qKOqC5s; qS.c]=%NԭkEIJ'y?4.Tx>:#ȜՕ3ٲ&+h+*ئKm1CA!D C.'}'wxyf8g֥IJL=o8\ӚLv2f 'R._0ܴSǶ;qcpkw9.8%G11#us##!uւѲYrrG.8u޲/+=:˗,x}ww/PKmzPK!: classes.dexkA&i4֚CڭAJDP/L7cm: ihQPM@[/*੠SIJM7wξ̼[ pn]]d.DJ09'/)/MhmhWQ!,H,#w/".2!KE56r<@"/Z\ ׁ˖Q$jqi4wܥǯL5 T>:7hZ巴hkkuUhU-Njky1Qi1cמu_ImC'Q'+D=:fxIT ]uy0RCZ+X'pokd8 8td)ϕ~Ρs~.͗x"pZfT0Hr<{좐*1`WhaYBaśyˋ5Ҷ3sTu6fJ#üu,b< woH3;#8z8CԹ- ͺ~hDhC7F#QvL(a:vduɽ,^8)$hPK!:鱒]META-INF/RSA-2048.RSA3hbebjhδA{&FFC~^6΄6Tf&FVnBML ~41>\gjsQ;HT!#7P?s( 3GQq8e89]S@G1713Ź.>Q|FkqMJ<'+x뎤I-XTl։S|zLYpGs _%942gN8eU?>=*LHdx+#f{EW/B~?dgG]EJ}g.d5";4 U|q&fF~ccg-m):Kq3c[lZ۾}' F/}e&{ۆSkݛ°pFڑ]+tO3/ju9y҈E lޙ]yⱟڋV.ب+W987T+gmxκͮBPEaɢ?:ۯ_pϢQ 6\]ly[˭I׀Xtv965OvnƬJŇ$9Fpwr`Ij[ib);.V :qsg ~*{>d帡ǜ'z;.[7F"\x ]qϋX5oK~B9v%&(-T&[bC~8R=TOsgy2J% ,en>\ϺdF:&4 Gs27,&4mePK!:MQMETA-INF/MANIFEST.MFeMO0; 6Gà,@E66w1]Zlc^4&&#Z!pQ1i1V$I/.1o3K$Fh>-H߭JN-8W6S*'\C+ mw@nG8)|nx Ez[+h !0 _1 w 9&̑tvp_О[44Gr"X".v~-5eY(iz9sNGPK !:!O resources.arsc5PK!:mzAndroidManifest.xmlPK!:T  sclasses.dexPK!:C` META-INF/RSA-2048.SFPK!:鱒] META-INF/RSA-2048.RSAPK!:MQMETA-INF/MANIFEST.MFPK./PaxHeaders.X/src_test_resources_com_android_apksig_v1-only-with-signed-attrs-signerInfo1-good-sig0100644 0000000 0000000 00000000157 13243353143 032677 xustar000000000 0000000 111 path=src/test/resources/com/android/apksig/v1-only-with-signed-attrs-signerInfo1-good-signerInfo2-good.apk src/test/resources/com/android/apksig/v1-only-with-signed-attrs-signerInfo1-good-signerInfo2-good.ap0100644 0000000 0000000 00000011565 13243353143 032475 0ustar000000000 0000000 PK !:!O resources.arsc5 (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlT=oAKCcȇc)6  @"E:c.8} *~ ߑn7ެmYݛvv۵#d% \s>_7N$~'D.]߈4ctEha߯ Gw os&0}ɘI4log-07I=TN&f~_a*6w23: ݭMμȈ{Pz\䚝15fKȭ "5Gc1=cDX'{7qKOqC5s; qS.c]=%NԭkEIJ'y?4.Tx>:#ȜՕ3ٲ&+h+*ئKm1CA!D C.'}'wxyf8g֥IJL=o8\ӚLv2f 'R._0ܴSǶ;qcpkw9.8%G11#us##!uւѲYrrG.8u޲/+=:˗,x}ww/PKmzPK!: classes.dexkA&i4֚CڭAJDP/L7cm: ihQPM@[/*੠SIJM7wξ̼[ pn]]d.DJ09'/)/MhmhWQ!,H,#w/".2!KE56r<@"/Z\ ׁ˖Q$jqi4wܥǯL5 T>:7hZ巴hkkuUhU-Njky1Qi1cמu_ImC'Q'+D=:fxIT ]uy0RCZ+X'pokd8 8td)ϕ~Ρs~.͗x"pZfT0Hr<{좐*1`WhaYBaśyˋ5Ҷ3sTu6fJ#üu,b< woH3;#8z8CԹ- ͺ~hDhC7F#QvL(a:vduɽ,^8)$hPK!:yHs META-INF/RSA-2048.RSA3hbajhδנѐ߀3̓1qA_&M03121q2eU 'l(hQTkd`ba 'khf`llhhbjnb%kbl`ahbĨl8QM @q.&FF & 5`Y契FodpQ] ʽ8w:˧1pT<@IҐ%pTf5_v@yy{m`:iOawv,^v#w:ns7[Ǫu;_ƽS\4`WI *yxY~pizyxՏO 2Y'2^ʈ^ /d틐7?.+Y;9Fs.=(oW?lR_Y {6YtM}H' FoDox}qq,0dXXDęt-f[ R#o Vf0k@Հ?Z2{mMOė{O_W'&;0_OR"Oލuf<txUKt?aVHo{q鋏ֶop_ICڟ3Yhޣ3 oe|a/(Ɖ@f};8|Xj. Bs *cLܴrYyL/>>¸y&|ӊjUkt1DmՃI{|>ǙP|Vi}}PK!:MQMETA-INF/MANIFEST.MFeMO0; 6Gà,@E66w1]Zlc^4&&#Z!pQ1i1V$I/.1o3K$Fh>-H߭JN-8W6S*'\C+ mw@nG8)|nx Ez[+h !0 _1 w 9&̑tvp_О[44Gr"X".v~-5eY(iz9sNGPK !:!O resources.arsc5PK!:mzAndroidManifest.xmlPK!:T  sclasses.dexPK!:C` META-INF/RSA-2048.SFPK!:yHs  META-INF/RSA-2048.RSAPK!:MQMETA-INF/MANIFEST.MFPK./PaxHeaders.X/src_test_resources_com_android_apksig_v1-only-with-signed-attrs-signerInfo1-missing-0100644 0000000 0000000 00000000177 13243353143 032717 xustar000000000 0000000 127 path=src/test/resources/com/android/apksig/v1-only-with-signed-attrs-signerInfo1-missing-content-type-signerInfo2-good.apk src/test/resources/com/android/apksig/v1-only-with-signed-attrs-signerInfo1-missing-content-type-sig0100644 0000000 0000000 00000011555 13243353143 032714 0ustar000000000 0000000 PK !:!O resources.arsc5 (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlT=oAKCcȇc)6  @"E:c.8} *~ ߑn7ެmYݛvv۵#d% \s>_7N$~'D.]߈4ctEha߯ Gw os&0}ɘI4log-07I=TN&f~_a*6w23: ݭMμȈ{Pz\䚝15fKȭ "5Gc1=cDX'{7qKOqC5s; qS.c]=%NԭkEIJ'y?4.Tx>:#ȜՕ3ٲ&+h+*ئKm1CA!D C.'}'wxyf8g֥IJL=o8\ӚLv2f 'R._0ܴSǶ;qcpkw9.8%G11#us##!uւѲYrrG.8u޲/+=:˗,x}ww/PKmzPK!: classes.dexkA&i4֚CڭAJDP/L7cm: ihQPM@[/*੠SIJM7wξ̼[ pn]]d.DJ09'/)/MhmhWQ!,H,#w/".2!KE56r<@"/Z\ ׁ˖Q$jqi4wܥǯL5 T>:7hZ巴hkkuUhU-Njky1Qi1cמu_ImC'Q'+D=:fxIT ]uy0RCZ+X'pokd8 8td)ϕ~Ρs~.͗x"pZfT0Hr<{좐*1`WhaYBaśyˋ5Ҷ3sTu6fJ#üu,b< woH3;#8z8CԹ- ͺ~hDhC7F#QvL(a:vduɽ,^8)$hPK!:TkMETA-INF/RSA-2048.RSA3hbcbjhδA{&FFC~^6΄6Tf&FVnBML ~41>\gjsQ;HT!#7P?s( 3GQq8e89]S@G1713Ź.>Q|FkqMJ<'+x뎤I-XTl։S|zLYpGs _%942gN8eU?>=*LHdx+#f{EW/B~?dgG]EJ}g.d5";4 U|q&fF~ccg-m):Kq3c[lZ۾}' F/}e&{ۆSkݛ°pFڑ]+tO3/ju9y҈E lޙ]yⱟڋV.ب+W987T+gmxκͮ-H߭JN-8W6S*'\C+ mw@nG8)|nx Ez[+h !0 _1 w 9&̑tvp_О[44Gr"X".v~-5eY(iz9sNGPK !:!O resources.arsc5PK!:mzAndroidManifest.xmlPK!:T  sclasses.dexPK!:C` META-INF/RSA-2048.SFPK!:Tk META-INF/RSA-2048.RSAPK!:MQMETA-INF/MANIFEST.MFPK./PaxHeaders.X/src_test_resources_com_android_apksig_v1-only-with-signed-attrs-signerInfo1-missing-0100644 0000000 0000000 00000000171 13243353143 032711 xustar000000000 0000000 121 path=src/test/resources/com/android/apksig/v1-only-with-signed-attrs-signerInfo1-missing-digest-signerInfo2-good.apk src/test/resources/com/android/apksig/v1-only-with-signed-attrs-signerInfo1-missing-digest-signerInf0100644 0000000 0000000 00000011513 13243353143 032676 0ustar000000000 0000000 PK !:!O resources.arsc5 (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlT=oAKCcȇc)6  @"E:c.8} *~ ߑn7ެmYݛvv۵#d% \s>_7N$~'D.]߈4ctEha߯ Gw os&0}ɘI4log-07I=TN&f~_a*6w23: ݭMμȈ{Pz\䚝15fKȭ "5Gc1=cDX'{7qKOqC5s; qS.c]=%NԭkEIJ'y?4.Tx>:#ȜՕ3ٲ&+h+*ئKm1CA!D C.'}'wxyf8g֥IJL=o8\ӚLv2f 'R._0ܴSǶ;qcpkw9.8%G11#us##!uւѲYrrG.8u޲/+=:˗,x}ww/PKmzPK!: classes.dexkA&i4֚CڭAJDP/L7cm: ihQPM@[/*੠SIJM7wξ̼[ pn]]d.DJ09'/)/MhmhWQ!,H,#w/".2!KE56r<@"/Z\ ׁ˖Q$jqi4wܥǯL5 T>:7hZ巴hkkuUhU-Njky1Qi1cמu_ImC'Q'+D=:fxIT ]uy0RCZ+X'pokd8 8td)ϕ~Ρs~.͗x"pZfT0Hr<{좐*1`WhaYBaśyˋ5Ҷ3sTu6fJ#üu,b< woH3;#8z8CԹ- ͺ~hDhC7F#QvL(a:vduɽ,^8)$hPK!:WIMETA-INF/RSA-2048.RSA3hb}ƩiA&Ll m,L  414hb| .w>BFn>aCA~6PfD]# 9q^C3ccCCSs(q^c Cs(&F%dÁbnbgs15122\x0T`q*/4zx'j W)snY>~;Jb<.}|2_'SOk9Lx {O]WeIu[:==%;V92梩+Jrhd8V+pzKիë~|zT:WFn$y!m_60wuY1Ϟ38wAyeJU=]سɢk҅E:wh0*~X'zĽ  dA'"""Τ[,Po1RufƶH|y2Xv< ,̌"ԌS~nkz"{ƟNg?g?1فz~nw՟3ë^ [|OBՕ~K_|}sBMOx9<_RfM ֬7'aBe__#RcϫW2Hg_8N!s[ O20mټ3Kc?]Q%WsG71pn|V0q"%u]yԌZ_9AksM7}UTusumiZjĴ 9@kjEH9imȵ/d4{nTЭHʫVwySX.zR*RRwtů4^-H߭JN-8W6S*'\C+ mw@nG8)|nx Ez[+h !0 _1 w 9&̑tvp_О[44Gr"X".v~-5eY(iz9sNGPK !:!O resources.arsc5PK!:mzAndroidManifest.xmlPK!:T  sclasses.dexPK!:C` META-INF/RSA-2048.SFPK!:WI META-INF/RSA-2048.RSAPK!:MQzMETA-INF/MANIFEST.MFPK./PaxHeaders.X/src_test_resources_com_android_apksig_v1-only-with-signed-attrs-signerInfo1-multiple0100644 0000000 0000000 00000000200 13243353143 033007 xustar000000000 0000000 128 path=src/test/resources/com/android/apksig/v1-only-with-signed-attrs-signerInfo1-multiple-good-digests-signerInfo2-good.apk src/test/resources/com/android/apksig/v1-only-with-signed-attrs-signerInfo1-multiple-good-digests-si0100644 0000000 0000000 00000011566 13243353143 032670 0ustar000000000 0000000 PK !:!O resources.arsc5 (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlT=oAKCcȇc)6  @"E:c.8} *~ ߑn7ެmYݛvv۵#d% \s>_7N$~'D.]߈4ctEha߯ Gw os&0}ɘI4log-07I=TN&f~_a*6w23: ݭMμȈ{Pz\䚝15fKȭ "5Gc1=cDX'{7qKOqC5s; qS.c]=%NԭkEIJ'y?4.Tx>:#ȜՕ3ٲ&+h+*ئKm1CA!D C.'}'wxyf8g֥IJL=o8\ӚLv2f 'R._0ܴSǶ;qcpkw9.8%G11#us##!uւѲYrrG.8u޲/+=:˗,x}ww/PKmzPK!: classes.dexkA&i4֚CڭAJDP/L7cm: ihQPM@[/*੠SIJM7wξ̼[ pn]]d.DJ09'/)/MhmhWQ!,H,#w/".2!KE56r<@"/Z\ ׁ˖Q$jqi4wܥǯL5 T>:7hZ巴hkkuUhU-Njky1Qi1cמu_ImC'Q'+D=:fxIT ]uy0RCZ+X'pokd8 8td)ϕ~Ρs~.͗x"pZfT0Hr<{좐*1`WhaYBaśyˋ5Ҷ3sTu6fJ#üu,b< woH3;#8z8CԹ- ͺ~hDhC7F#QvL(a:vduɽ,^8)$hPK!:^mtQMETA-INF/RSA-2048.RSA3hbejhδΠ͊ѐ߀3̓1qA_&M03121q2eU 'l(hQTkd`ba 'khf`llhhbjnb%kbl`ahbĨl8QM @q.&FF & 5`Y契FodpQ] ʽ8w:˧1pT<@IҐ%pTf5_v@yy{m`:iOawv,^v#w:ns7[Ǫu;_ƽS\4`WI *yxY~pizyxՏO 2Y'2^ʈ^ /d틐7?.+Y;9Fs.=(oW?lR_Y {6YtM}H' FoDox}qq,0dXXDęt-f[ R#o Vf0k@Հ?Z2{mMOė{O_W'&;0_OR"Oލuf<txUKt?aVHo{q鋏ֶop_ICoVz\\]/o}ēZW?3qV*ހ7u͝.5-z0i8_2Vu=2oPK!:MQMETA-INF/MANIFEST.MFeMO0; 6Gà,@E66w1]Zlc^4&&#Z!pQ1i1V$I/.1o3K$Fh>-H߭JN-8W6S*'\C+ mw@nG8)|nx Ez[+h !0 _1 w 9&̑tvp_О[44Gr"X".v~-5eY(iz9sNGPK !:!O resources.arsc5PK!:mzAndroidManifest.xmlPK!:T  sclasses.dexPK!:C` META-INF/RSA-2048.SFPK!:^mtQ META-INF/RSA-2048.RSAPK!:MQMETA-INF/MANIFEST.MFPK./PaxHeaders.X/src_test_resources_com_android_apksig_v1-only-with-signed-attrs-signerInfo1-wrong-co0100644 0000000 0000000 00000000175 13243353143 032722 xustar000000000 0000000 125 path=src/test/resources/com/android/apksig/v1-only-with-signed-attrs-signerInfo1-wrong-content-type-signerInfo2-good.apk src/test/resources/com/android/apksig/v1-only-with-signed-attrs-signerInfo1-wrong-content-type-signe0100644 0000000 0000000 00000011564 13243353143 032722 0ustar000000000 0000000 PK !:!O resources.arsc5 (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlT=oAKCcȇc)6  @"E:c.8} *~ ߑn7ެmYݛvv۵#d% \s>_7N$~'D.]߈4ctEha߯ Gw os&0}ɘI4log-07I=TN&f~_a*6w23: ݭMμȈ{Pz\䚝15fKȭ "5Gc1=cDX'{7qKOqC5s; qS.c]=%NԭkEIJ'y?4.Tx>:#ȜՕ3ٲ&+h+*ئKm1CA!D C.'}'wxyf8g֥IJL=o8\ӚLv2f 'R._0ܴSǶ;qcpkw9.8%G11#us##!uւѲYrrG.8u޲/+=:˗,x}ww/PKmzPK!: classes.dexkA&i4֚CڭAJDP/L7cm: ihQPM@[/*੠SIJM7wξ̼[ pn]]d.DJ09'/)/MhmhWQ!,H,#w/".2!KE56r<@"/Z\ ׁ˖Q$jqi4wܥǯL5 T>:7hZ巴hkkuUhU-Njky1Qi1cמu_ImC'Q'+D=:fxIT ]uy0RCZ+X'pokd8 8td)ϕ~Ρs~.͗x"pZfT0Hr<{좐*1`WhaYBaśyˋ5Ҷ3sTu6fJ#üu,b< woH3;#8z8CԹ- ͺ~hDhC7F#QvL(a:vduɽ,^8)$hPK!:P)r META-INF/RSA-2048.RSA{4i}1fduHc0B׆Fe!4.fZ o2%B$EfY:K.]V1nd&s:k||`08so(#U&(Dy0B 8T5|dКY}+}5XECؘ%le &)60JXYӬ6'୨-鸼 ~|S@P3+/O5%!O('B"yEUl莼ohȚeZ=9֎epre#LZ0cTgZ5Fʬ_4zJbVKm v!_ج@[Vs i0ܵQ}h$mGza i(u攺g-&{>=S̺eur_NM=[RyJa`^mp'B (=aUhMH]ɵݤ. 7x$h`il&I h -E ̄o"Rܨd9}6wB6qL= x@Xa6s$ZxsQ8ƿ`B1٭0aU.16z`eÍLYc0KB'Oj8ioux I:k#s}\P{"k*&t49vBE)(yEp\L]Vkۮ n1ҎA/ub:hST;0dahY8eR=b;]Dq;mGwA&-H߭JN-8W6S*'\C+ mw@nG8)|nx Ez[+h !0 _1 w 9&̑tvp_О[44Gr"X".v~-5eY(iz9sNGPK !:!O resources.arsc5PK!:mzAndroidManifest.xmlPK!:T  sclasses.dexPK!:C` META-INF/RSA-2048.SFPK!:P)r  META-INF/RSA-2048.RSAPK!:MQMETA-INF/MANIFEST.MFPK./PaxHeaders.X/src_test_resources_com_android_apksig_v1-only-with-signed-attrs-signerInfo1-wrong-di0100644 0000000 0000000 00000000167 13243353143 032716 xustar000000000 0000000 119 path=src/test/resources/com/android/apksig/v1-only-with-signed-attrs-signerInfo1-wrong-digest-signerInfo2-good.apk src/test/resources/com/android/apksig/v1-only-with-signed-attrs-signerInfo1-wrong-digest-signerInfo20100644 0000000 0000000 00000011566 13243353143 032632 0ustar000000000 0000000 PK !:!O resources.arsc5 (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlT=oAKCcȇc)6  @"E:c.8} *~ ߑn7ެmYݛvv۵#d% \s>_7N$~'D.]߈4ctEha߯ Gw os&0}ɘI4log-07I=TN&f~_a*6w23: ݭMμȈ{Pz\䚝15fKȭ "5Gc1=cDX'{7qKOqC5s; qS.c]=%NԭkEIJ'y?4.Tx>:#ȜՕ3ٲ&+h+*ئKm1CA!D C.'}'wxyf8g֥IJL=o8\ӚLv2f 'R._0ܴSǶ;qcpkw9.8%G11#us##!uւѲYrrG.8u޲/+=:˗,x}ww/PKmzPK!: classes.dexkA&i4֚CڭAJDP/L7cm: ihQPM@[/*੠SIJM7wξ̼[ pn]]d.DJ09'/)/MhmhWQ!,H,#w/".2!KE56r<@"/Z\ ׁ˖Q$jqi4wܥǯL5 T>:7hZ巴hkkuUhU-Njky1Qi1cמu_ImC'Q'+D=:fxIT ]uy0RCZ+X'pokd8 8td)ϕ~Ρs~.͗x"pZfT0Hr<{좐*1`WhaYBaśyˋ5Ҷ3sTu6fJ#üu,b< woH3;#8z8CԹ- ͺ~hDhC7F#QvL(a:vduɽ,^8)$hPK!:t META-INF/RSA-2048.RSA{Tg*x JmfvҼ\LYBs*-]TlbD ]AKgTjvUNNǿ~=yd*oPFnəA* 2 d*#@QȨ JP`BA&T2g0("!v`;(TW|-PCSCF[ۀED֎dx%&&Od~ ~iŖ`SiuHDa^{6UE)vޭ;3FS=_zEvYtbyEu[ ;`y#w7}+7Ϧ5#6QKid::i?cEVbdJXKz1σ~2;rLro1O,=k)3+O79*o̽sRu`Akdʛ* W3Tu0PTLRGT  &ƫcS8Z[*І+0XPjA5{p/P{|p `T\{,7QXk}َ&Qnjߟsj̙ ۋB)ƒoϑ~i8pd'cVѕ+^!ܸ~Ag$UEn4pogG!4)R:GU"/3HvGs>*c9#RueJo87ؗP=Yn3Kl[*z1Gj+EſDFhwJUu>fV3w*&!nTٝ?5͒W)1. co!E微5_H:#wᮥ&r;lP[/;V5^zhƏ* HyH^#(_+fdl8w,?h=~nrIX9rYX4΋"2tŋOzY#JQ;s ciwuB+a(3-H߭JN-8W6S*'\C+ mw@nG8)|nx Ez[+h !0 _1 w 9&̑tvp_О[44Gr"X".v~-5eY(iz9sNGPK !:!O resources.arsc5PK!:mzAndroidManifest.xmlPK!:T  sclasses.dexPK!:C` META-INF/RSA-2048.SFPK!:t  META-INF/RSA-2048.RSAPK!:MQMETA-INF/MANIFEST.MFPK./PaxHeaders.X/src_test_resources_com_android_apksig_v1-only-with-signed-attrs-signerInfo1-wrong-or0100644 0000000 0000000 00000000166 13243353143 032741 xustar000000000 0000000 118 path=src/test/resources/com/android/apksig/v1-only-with-signed-attrs-signerInfo1-wrong-order-signerInfo2-good.apk src/test/resources/com/android/apksig/v1-only-with-signed-attrs-signerInfo1-wrong-order-signerInfo2-0100644 0000000 0000000 00000011563 13243353143 032540 0ustar000000000 0000000 PK !:!O resources.arsc5 (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlT=oAKCcȇc)6  @"E:c.8} *~ ߑn7ެmYݛvv۵#d% \s>_7N$~'D.]߈4ctEha߯ Gw os&0}ɘI4log-07I=TN&f~_a*6w23: ݭMμȈ{Pz\䚝15fKȭ "5Gc1=cDX'{7qKOqC5s; qS.c]=%NԭkEIJ'y?4.Tx>:#ȜՕ3ٲ&+h+*ئKm1CA!D C.'}'wxyf8g֥IJL=o8\ӚLv2f 'R._0ܴSǶ;qcpkw9.8%G11#us##!uւѲYrrG.8u޲/+=:˗,x}ww/PKmzPK!: classes.dexkA&i4֚CڭAJDP/L7cm: ihQPM@[/*੠SIJM7wξ̼[ pn]]d.DJ09'/)/MhmhWQ!,H,#w/".2!KE56r<@"/Z\ ׁ˖Q$jqi4wܥǯL5 T>:7hZ巴hkkuUhU-Njky1Qi1cמu_ImC'Q'+D=:fxIT ]uy0RCZ+X'pokd8 8td)ϕ~Ρs~.͗x"pZfT0Hr<{좐*1`WhaYBaśyˋ5Ҷ3sTu6fJ#üu,b< woH3;#8z8CԹ- ͺ~hDhC7F#QvL(a:vduɽ,^8)$hPK!:h#Yq META-INF/RSA-2048.RSAyPi*(xhRjPQkv2 tҼ+kְ_%Pf뙑WXxdҖeeiX:vlL5}wyM6ٓX@^cAH UGg$PZ߄@9! 8a` `8*}=PGD`P)T4#`) Fvv4БBz\t`sM8'uK>b@" 2܈u)eWD>`ȏ><`O!;*[8N+M_e I֬5g_0o8-c)UCբΨq9f^MZ3|nk>VDo,^1 UY`+1ã#}FtpSFc^GKO ޣk]% IMد;;y{N ތT?~`y$GGFm.DZ 7cd2Z\Y#@P(b!j`r*4B8#$PL+4NG<32 7#P)֯Ei2vFpΈz-V8}wS?%dW2nVz G±Բ1ucyf"8{ r0뉣ypё%ujcY+T}/l9CZggiiY^jle YQx$5,7ck硋0tØUS#D5bh̵ö́xfq{ {o8NSE%J ]<)E"#\_h$\Z!<ұN+Yc@U;}睞}}3 (ߓHe iC;:j&;Z)oQ6!Tv.?bL?;J4-i{UW#fEԅ$GGv}ORNv=7z?e<*{X!eT!pw [mZ8Q0m}ݷldhXi(,mzBS:%"nFC"*B:]և—TR:a{G!O`ԸzY$YSA[5F>i=](HDnת #n}; *I92F6U*wRV]v.f$$HI0`yfV7u=Y`u. .?msA9M6v[I[J%!m3㍤Ӊpa;1Cg.U2-a7}Cm.Oc8l@R/PK!:MQMETA-INF/MANIFEST.MFeMO0; 6Gà,@E66w1]Zlc^4&&#Z!pQ1i1V$I/.1o3K$Fh>-H߭JN-8W6S*'\C+ mw@nG8)|nx Ez[+h !0 _1 w 9&̑tvp_О[44Gr"X".v~-5eY(iz9sNGPK !:!O resources.arsc5PK!:mzAndroidManifest.xmlPK!:T  sclasses.dexPK!:C` META-INF/RSA-2048.SFPK!:h#Yq  META-INF/RSA-2048.RSAPK!:MQMETA-INF/MANIFEST.MFPK./PaxHeaders.X/src_test_resources_com_android_apksig_v1-only-with-signed-attrs-signerInfo1-wrong-si0100644 0000000 0000000 00000000172 13243353143 032731 xustar000000000 0000000 122 path=src/test/resources/com/android/apksig/v1-only-with-signed-attrs-signerInfo1-wrong-signature-signerInfo2-good.apk src/test/resources/com/android/apksig/v1-only-with-signed-attrs-signerInfo1-wrong-signature-signerIn0100644 0000000 0000000 00000011561 13243353143 032740 0ustar000000000 0000000 PK !:!O resources.arsc5 (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlT=oAKCcȇc)6  @"E:c.8} *~ ߑn7ެmYݛvv۵#d% \s>_7N$~'D.]߈4ctEha߯ Gw os&0}ɘI4log-07I=TN&f~_a*6w23: ݭMμȈ{Pz\䚝15fKȭ "5Gc1=cDX'{7qKOqC5s; qS.c]=%NԭkEIJ'y?4.Tx>:#ȜՕ3ٲ&+h+*ئKm1CA!D C.'}'wxyf8g֥IJL=o8\ӚLv2f 'R._0ܴSǶ;qcpkw9.8%G11#us##!uւѲYrrG.8u޲/+=:˗,x}ww/PKmzPK!: classes.dexkA&i4֚CڭAJDP/L7cm: ihQPM@[/*੠SIJM7wξ̼[ pn]]d.DJ09'/)/MhmhWQ!,H,#w/".2!KE56r<@"/Z\ ׁ˖Q$jqi4wܥǯL5 T>:7hZ巴hkkuUhU-Njky1Qi1cמu_ImC'Q'+D=:fxIT ]uy0RCZ+X'pokd8 8td)ϕ~Ρs~.͗x"pZfT0Hr<{좐*1`WhaYBaśyˋ5Ҷ3sTu6fJ#üu,b< woH3;#8z8CԹ- ͺ~hDhC7F#QvL(a:vduɽ,^8)$hPK!:+R%o META-INF/RSA-2048.RSASk4 1f0F2"F}cq܆PȽc5 5JçKK ӺmhքBN[Ln;T|yyFoBcMҝpBk5'LP4y*7Sˑ%ڲ~T i~V|,h BBmx+9yvYWq҃iPӣKVO>]m&(@,Wz&5JM\}jx'c jn;. fyK2_Z~|i&^V(n6J6g hxDg$(?PK!:MQMETA-INF/MANIFEST.MFeMO0; 6Gà,@E66w1]Zlc^4&&#Z!pQ1i1V$I/.1o3K$Fh>-H߭JN-8W6S*'\C+ mw@nG8)|nx Ez[+h !0 _1 w 9&̑tvp_О[44Gr"X".v~-5eY(iz9sNGPK !:!O resources.arsc5PK!:mzAndroidManifest.xmlPK!:T  sclasses.dexPK!:C` META-INF/RSA-2048.SFPK!:+R%o  META-INF/RSA-2048.RSAPK!:MQMETA-INF/MANIFEST.MFPKsrc/test/resources/com/android/apksig/v1-only-with-signed-attrs-wrong-content-type.apk0100644 0000000 0000000 00000011140 13243353143 030175 0ustar000000000 0000000 PK !:!O resources.arsc5 (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlT=oAKCcȇc)6  @"E:c.8} *~ ߑn7ެmYݛvv۵#d% \s>_7N$~'D.]߈4ctEha߯ Gw os&0}ɘI4log-07I=TN&f~_a*6w23: ݭMμȈ{Pz\䚝15fKȭ "5Gc1=cDX'{7qKOqC5s; qS.c]=%NԭkEIJ'y?4.Tx>:#ȜՕ3ٲ&+h+*ئKm1CA!D C.'}'wxyf8g֥IJL=o8\ӚLv2f 'R._0ܴSǶ;qcpkw9.8%G11#us##!uւѲYrrG.8u޲/+=:˗,x}ww/PKmzPK!: classes.dexkA&i4֚CڭAJDP/L7cm: ihQPM@[/*੠SIJM7wξ̼[ pn]]d.DJ09'/)/MhmhWQ!,H,#w/".2!KE56r<@"/Z\ ׁ˖Q$jqi4wܥǯL5 T>:7hZ巴hkkuUhU-Njky1Qi1cמu_ImC'Q'+D=:fxIT ]uy0RCZ+X'pokd8 8td)ϕ~Ρs~.͗x"pZfT0Hr<{좐*1`WhaYBaśyˋ5Ҷ3sTu6fJ#üu,b< woH3;#8z8CԹ- ͺ~hDhC7F#QvL(a:vduɽ,^8)$hPK!:G,^META-INF/RSA-2048.RSA3hbƩiA!&}Ll m,L  414hb| .w>BFn>aCA~6PfD]# 9q^C3ccCCSs(q^c Cs(&F%dÁbnbgs15122\x0T`q*/4zx'j W)snY>~;Jb<.}|2_'SOk9Lx {O]WeIu[:==%;V92梩+Jrhd8V+pzKիë~|zT:WFn$y!m_60wuY1Ϟ38wAyeJU=]سɢk҅E:wh0*~X'zĽ  dA'"""Τ[,Po1RufƶH|y2Xv< ,̌"ԌS~nkz"{ƟNg?g?1فz~nw՟3ë^ [|OBՕ~K_|}sBMOx9<_RfM ֬7'aBe__#RcϫW2Hg_8N!s[ O20mټ3Kc?]Q%WsG71pn|V0q"%u]yԌZ_9AksM7}UTusumiZj8sԊ#ro xr2"e,&} ’Et?_ᦟE?4lַ[7e&F$3n?vzKZ~z̕B.^2tʚ35k?8ĪJ=^=oNR5޶T7+"ˮ&&H+Jr<>+kZ||KKT7M&yI/߂'Jޭ7͝DKu}S*3z^'[ot -hV1XܾI{ɼ*IڱgSL()y6vX囓7PK!:MQMETA-INF/MANIFEST.MFeMO0; 6Gà,@E66w1]Zlc^4&&#Z!pQ1i1V$I/.1o3K$Fh>-H߭JN-8W6S*'\C+ mw@nG8)|nx Ez[+h !0 _1 w 9&̑tvp_О[44Gr"X".v~-5eY(iz9sNGPK !:!O resources.arsc5PK!:mzAndroidManifest.xmlPK!:T  sclasses.dexPK!:C` META-INF/RSA-2048.SFPK!:G,^ META-INF/RSA-2048.RSAPK!:MQMETA-INF/MANIFEST.MFPKsrc/test/resources/com/android/apksig/v1-only-with-signed-attrs-wrong-digest.apk0100644 0000000 0000000 00000011136 13243353143 027030 0ustar000000000 0000000 PK !:!O resources.arsc5 (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlT=oAKCcȇc)6  @"E:c.8} *~ ߑn7ެmYݛvv۵#d% \s>_7N$~'D.]߈4ctEha߯ Gw os&0}ɘI4log-07I=TN&f~_a*6w23: ݭMμȈ{Pz\䚝15fKȭ "5Gc1=cDX'{7qKOqC5s; qS.c]=%NԭkEIJ'y?4.Tx>:#ȜՕ3ٲ&+h+*ئKm1CA!D C.'}'wxyf8g֥IJL=o8\ӚLv2f 'R._0ܴSǶ;qcpkw9.8%G11#us##!uւѲYrrG.8u޲/+=:˗,x}ww/PKmzPK!: classes.dexkA&i4֚CڭAJDP/L7cm: ihQPM@[/*੠SIJM7wξ̼[ pn]]d.DJ09'/)/MhmhWQ!,H,#w/".2!KE56r<@"/Z\ ׁ˖Q$jqi4wܥǯL5 T>:7hZ巴hkkuUhU-Njky1Qi1cמu_ImC'Q'+D=:fxIT ]uy0RCZ+X'pokd8 8td)ϕ~Ρs~.͗x"pZfT0Hr<{좐*1`WhaYBaśyˋ5Ҷ3sTu6fJ#üu,b< woH3;#8z8CԹ- ͺ~hDhC7F#QvL(a:vduɽ,^8)$hPK!:%10\META-INF/RSA-2048.RSA3hbƩiA!&}Ll m,L  414hb| .w>BFn>aCA~6PfD]# 9q^C3ccCCSs(q^c Cs(&F%dÁbnbgs15122\x0T`q*/4zx'j W)snY>~;Jb<.}|2_'SOk9Lx {O]WeIu[:==%;V92梩+Jrhd8V+pzKիë~|zT:WFn$y!m_60wuY1Ϟ38wAyeJU=]سɢk҅E:wh0*~X'zĽ  dA'"""Τ[,Po1RufƶH|y2Xv< ,̌"ԌS~nkz"{ƟNg?g?1فz~nw՟3ë^ [|OBՕ~K_|}sBMOx9<_RfM ֬7'aBe__#RcϫW2Hg_8N!s[ O20mټ3Kc?]Q%WsG71pn|V0q"%u]yԌZ_9AksM7}UTusumiZj8sԊ#ro xr2"g,} ’Et?_ᦟE?4lַ[7e&FU_]wz䥭t-@xvG5/ٱ@Fvs91nvfxQ';TX2Q #_lϗ|K"~pm*v:^Q'!.aOmPt3c{Le:(g5yGJ*V`U7|]R/*uܓ.W%\,]6!c¯oguHN]O -v%>WJSPK!:MQMETA-INF/MANIFEST.MFeMO0; 6Gà,@E66w1]Zlc^4&&#Z!pQ1i1V$I/.1o3K$Fh>-H߭JN-8W6S*'\C+ mw@nG8)|nx Ez[+h !0 _1 w 9&̑tvp_О[44Gr"X".v~-5eY(iz9sNGPK !:!O resources.arsc5PK!:mzAndroidManifest.xmlPK!:T  sclasses.dexPK!:C` META-INF/RSA-2048.SFPK!:%10\ META-INF/RSA-2048.RSAPK!:MQMETA-INF/MANIFEST.MFPKsrc/test/resources/com/android/apksig/v1-only-with-signed-attrs-wrong-order.apk0100644 0000000 0000000 00000011137 13243353143 026665 0ustar000000000 0000000 PK !:!O resources.arsc5 (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlT=oAKCcȇc)6  @"E:c.8} *~ ߑn7ެmYݛvv۵#d% \s>_7N$~'D.]߈4ctEha߯ Gw os&0}ɘI4log-07I=TN&f~_a*6w23: ݭMμȈ{Pz\䚝15fKȭ "5Gc1=cDX'{7qKOqC5s; qS.c]=%NԭkEIJ'y?4.Tx>:#ȜՕ3ٲ&+h+*ئKm1CA!D C.'}'wxyf8g֥IJL=o8\ӚLv2f 'R._0ܴSǶ;qcpkw9.8%G11#us##!uւѲYrrG.8u޲/+=:˗,x}ww/PKmzPK!: classes.dexkA&i4֚CڭAJDP/L7cm: ihQPM@[/*੠SIJM7wξ̼[ pn]]d.DJ09'/)/MhmhWQ!,H,#w/".2!KE56r<@"/Z\ ׁ˖Q$jqi4wܥǯL5 T>:7hZ巴hkkuUhU-Njky1Qi1cמu_ImC'Q'+D=:fxIT ]uy0RCZ+X'pokd8 8td)ϕ~Ρs~.͗x"pZfT0Hr<{좐*1`WhaYBaśyˋ5Ҷ3sTu6fJ#üu,b< woH3;#8z8CԹ- ͺ~hDhC7F#QvL(a:vduɽ,^8)$hPK!:I 5]META-INF/RSA-2048.RSA3hbƩiA!&}Ll m,L  414hb| .w>BFn>aCA~6PfD]# 9q^C3ccCCSs(q^c Cs(&F%dÁbnbgs15122\x0T`q*/4zx'j W)snY>~;Jb<.}|2_'SOk9Lx {O]WeIu[:==%;V92梩+Jrhd8V+pzKիë~|zT:WFn$y!m_60wuY1Ϟ38wAyeJU=]سɢk҅E:wh0*~X'zĽ  dA'"""Τ[,Po1RufƶH|y2Xv< ,̌"ԌS~nkz"{ƟNg?g?1فz~nw՟3ë^ [|OBՕ~K_|}sBMOx9<_RfM ֬7'aBe__#RcϫW2Hg_8N!s[ O20mټ3Kc?]Q%WsG71pn|V0q"%u]yԌZ_9AksM7}UTusumiZj8sԊ#ro}xr*(,YG!cu~nY3@-o}uB!rND,M kr\>{uCšw'Խ?ȡkT~2KGe܈s?'elR9dSW*{AS]9E+9w~ElE!iK KX.Yd&'3҈?>{ruE\{֨{#L;y瞍;ҝ*=z̸ffS8q\*Ϧ>;'[6K F o]-H߭JN-8W6S*'\C+ mw@nG8)|nx Ez[+h !0 _1 w 9&̑tvp_О[44Gr"X".v~-5eY(iz9sNGPK !:!O resources.arsc5PK!:mzAndroidManifest.xmlPK!:T  sclasses.dexPK!:C` META-INF/RSA-2048.SFPK!:I 5] META-INF/RSA-2048.RSAPK!:MQMETA-INF/MANIFEST.MFPKsrc/test/resources/com/android/apksig/v1-only-with-signed-attrs-wrong-signature.apk0100644 0000000 0000000 00000011140 13243353143 027545 0ustar000000000 0000000 PK !:!O resources.arsc5 (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlT=oAKCcȇc)6  @"E:c.8} *~ ߑn7ެmYݛvv۵#d% \s>_7N$~'D.]߈4ctEha߯ Gw os&0}ɘI4log-07I=TN&f~_a*6w23: ݭMμȈ{Pz\䚝15fKȭ "5Gc1=cDX'{7qKOqC5s; qS.c]=%NԭkEIJ'y?4.Tx>:#ȜՕ3ٲ&+h+*ئKm1CA!D C.'}'wxyf8g֥IJL=o8\ӚLv2f 'R._0ܴSǶ;qcpkw9.8%G11#us##!uւѲYrrG.8u޲/+=:˗,x}ww/PKmzPK!: classes.dexkA&i4֚CڭAJDP/L7cm: ihQPM@[/*੠SIJM7wξ̼[ pn]]d.DJ09'/)/MhmhWQ!,H,#w/".2!KE56r<@"/Z\ ׁ˖Q$jqi4wܥǯL5 T>:7hZ巴hkkuUhU-Njky1Qi1cמu_ImC'Q'+D=:fxIT ]uy0RCZ+X'pokd8 8td)ϕ~Ρs~.͗x"pZfT0Hr<{좐*1`WhaYBaśyˋ5Ҷ3sTu6fJ#üu,b< woH3;#8z8CԹ- ͺ~hDhC7F#QvL(a:vduɽ,^8)$hPK!:^META-INF/RSA-2048.RSA3hbƩiA!&}Ll m,L  414hb| .w>BFn>aCA~6PfD]# 9q^C3ccCCSs(q^c Cs(&F%dÁbnbgs15122\x0T`q*/4zx'j W)snY>~;Jb<.}|2_'SOk9Lx {O]WeIu[:==%;V92梩+Jrhd8V+pzKիë~|zT:WFn$y!m_60wuY1Ϟ38wAyeJU=]سɢk҅E:wh0*~X'zĽ  dA'"""Τ[,Po1RufƶH|y2Xv< ,̌"ԌS~nkz"{ƟNg?g?1فz~nw՟3ë^ [|OBՕ~K_|}sBMOx9<_RfM ֬7'aBe__#RcϫW2Hg_8N!s[ O20mټ3Kc?]Q%WsG71pn|V0q"%u]yԌZ_9AksM7}UTusumiZj8sԊ#ro xr2"g,} ’Et?_ᦟE?4lַ[7e&F)R-H߭JN-8W6S*'\C+ mw@nG8)|nx Ez[+h !0 _1 w 9&̑tvp_О[44Gr"X".v~-5eY(iz9sNGPK !:!O resources.arsc5PK!:mzAndroidManifest.xmlPK!:T  sclasses.dexPK!:C` META-INF/RSA-2048.SFPK!:^ META-INF/RSA-2048.RSAPK!:MQMETA-INF/MANIFEST.MFPKsrc/test/resources/com/android/apksig/v1-only-with-signed-attrs.apk0100644 0000000 0000000 00000011137 13243353143 024422 0ustar000000000 0000000 PK !:!O resources.arsc5 (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlT=oAKCcȇc)6  @"E:c.8} *~ ߑn7ެmYݛvv۵#d% \s>_7N$~'D.]߈4ctEha߯ Gw os&0}ɘI4log-07I=TN&f~_a*6w23: ݭMμȈ{Pz\䚝15fKȭ "5Gc1=cDX'{7qKOqC5s; qS.c]=%NԭkEIJ'y?4.Tx>:#ȜՕ3ٲ&+h+*ئKm1CA!D C.'}'wxyf8g֥IJL=o8\ӚLv2f 'R._0ܴSǶ;qcpkw9.8%G11#us##!uւѲYrrG.8u޲/+=:˗,x}ww/PKmzPK!: classes.dexkA&i4֚CڭAJDP/L7cm: ihQPM@[/*੠SIJM7wξ̼[ pn]]d.DJ09'/)/MhmhWQ!,H,#w/".2!KE56r<@"/Z\ ׁ˖Q$jqi4wܥǯL5 T>:7hZ巴hkkuUhU-Njky1Qi1cמu_ImC'Q'+D=:fxIT ]uy0RCZ+X'pokd8 8td)ϕ~Ρs~.͗x"pZfT0Hr<{좐*1`WhaYBaśyˋ5Ҷ3sTu6fJ#üu,b< woH3;#8z8CԹ- ͺ~hDhC7F#QvL(a:vduɽ,^8)$hPK!:&}]META-INF/RSA-2048.RSA3hbƩiA!&}Ll m,L  414hb| .w>BFn>aCA~6PfD]# 9q^C3ccCCSs(q^c Cs(&F%dÁbnbgs15122\x0T`q*/4zx'j W)snY>~;Jb<.}|2_'SOk9Lx {O]WeIu[:==%;V92梩+Jrhd8V+pzKիë~|zT:WFn$y!m_60wuY1Ϟ38wAyeJU=]سɢk҅E:wh0*~X'zĽ  dA'"""Τ[,Po1RufƶH|y2Xv< ,̌"ԌS~nkz"{ƟNg?g?1فz~nw՟3ë^ [|OBՕ~K_|}sBMOx9<_RfM ֬7'aBe__#RcϫW2Hg_8N!s[ O20mټ3Kc?]Q%WsG71pn|V0q"%u]yԌZ_9AksM7}UTusumiZj8sԊ#ro xr2"g,} ’Et?_ᦟE?4lַ[7e&F3u3C/6J`gXt.\S{݅-Xz;\bcl%%³3W%\giǼ-&·y"9T{lg[Yc6`紜0\6/޲c_YH;"4G꧖l]~W2aû'ܿ}aI{TU!d\Jk:+>Ԅgb+_oy&zMy{˅Wr]}RDoGdZYw]w PK!:MQMETA-INF/MANIFEST.MFeMO0; 6Gà,@E66w1]Zlc^4&&#Z!pQ1i1V$I/.1o3K$Fh>-H߭JN-8W6S*'\C+ mw@nG8)|nx Ez[+h !0 _1 w 9&̑tvp_О[44Gr"X".v~-5eY(iz9sNGPK !:!O resources.arsc5PK!:mzAndroidManifest.xmlPK!:T  sclasses.dexPK!:C` META-INF/RSA-2048.SFPK!:&}] META-INF/RSA-2048.RSAPK!:MQMETA-INF/MANIFEST.MFPK./PaxHeaders.X/src_test_resources_com_android_apksig_v1-sha1-sha256-manifest-and-sf-with-sha1-wrong0100644 0000000 0000000 00000000156 13243353143 032223 xustar000000000 0000000 110 path=src/test/resources/com/android/apksig/v1-sha1-sha256-manifest-and-sf-with-sha1-wrong-in-manifest.apk src/test/resources/com/android/apksig/v1-sha1-sha256-manifest-and-sf-with-sha1-wrong-in-manifest.apk0100644 0000000 0000000 00000011405 13243353143 032045 0ustar000000000 0000000 PK !:;k薸 resources.arsc5 ($hTiny App for CTS [b U b` one two three]  . Tiny,   . App,   . for,   . CTS,   android.appsecurity.cts.tinyapp `@$ attrstring4 app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlnA&I@B(b"  @"  X"x@7dm :;s=gƎQN T֧HZTy <]g>_7(m|_wP L\S[յ6y?<Ў^Y$k/}‹sxϨa_Wc·\Rïڧ+ߎWZ1ó- f{Ȥ?7ü gz1=֨ߡ1*v!ܠiEoh VcȜx@uWN|->`c1{.Ƿ.y-jvhOY&ܤ+N;n4~EWǖ۫czu^"3A5erz#r7ims^e?GRspi:I\|iw6.[!z%Wq)_2sJ󙹧r>PKO~PK!: classes.dexkA&i4֚CڭAJDP/L7cm: ihQPM@[/*੠SIJM7wξ̼[ pn]]d.DJ09'/)/MhmhWQ!,H,#w/".2!KE56r<@"/Z\ ׁ˖Q$jqi4wܥǯL5 T>:7hZ巴hkkuUhU-Njky1Qi1cמu_ImC'Q'+D=:fxIT ]uy0RCZ+X'pokd8 8td)ϕ~Ρs~.͗x"pZfT0Hr<{좐*1`WhaYBaśyˋ5Ҷ3sTu6fJ#üu,b< woH3;#8z8CԹ- ͺ~hDhC7F#QvL(}Gw779ro S䤩8#~>|-AH(p-^Hbj~IQt~3/I&kd(>4¦BvU#=w472jd%^aM̿شϳG/+%U'bh-w^CxsB8j[a! sG1" cM{p tj\cAP#}{qz 6n N1+Y n,D $Ebʛ;Y1Ky+|W[Yn.uc]HW:'|PK!:fK&META-INF/CERT.RSA3hbajhδ%נ%ѐۀUIqA_&M03121q2en W 'l(hQTkd`ba 'khf`llhhbjnb%kbl`ahbĨl8#+s#?PƒIs3tVyid?\TcHr/NviC1P;4qg}#Y͗]:?q^lľ^'aڤS{z~/_HݿNE)߱j1ŗqǔw4M0XUC#yJ^^֫_^m^ӣ„D։2bWt{%1 l"䷁JNQqĹKO.[}VžM].ll/ɿCQ_:^gbfd`\`g :Y>1q&bzٖB73EȃY$ +)fa5`fd̠fs[5t=9Sodwc(l^ROb{@\⣭+j}Ґk4җZ6k'm7fݽ89, 7ȾoH/ٕ^AD:ӾYw 9J-(mp_$xqnٕw^*h_*rU|?sK;}ֆ)ʣf\: r?^{8-m*oKR&Fw`s* O9)_&F Z&qSgO+ril^/py0(g׺Vb6eUU6mQ:o*H پ&2&KRߎw1`s]tYMA<&E_,V%n g ŜzdwJ=q}u޿g$Lʱ3Z1sy,tw+U^vuܛ,LO|vQi+ff!{9q9?ܘmffJkY.:~䞼moPK!:o1KMETA-INF/MANIFEST.MFen@E&6 (bM\ R^":A60c(Я5MM=ǁEeL˔SF~OV8a0בĦEi3eQ  +{.Q4q6`4fM\Z86i6̧P GF\-Jir3֙sA&zfV %.`9~[AG_zjyYTd+u9 #ikg oꇴD+&u_(˃7%z ;9\zlp{QQ_T=H 0>PK !:;k薸 resources.arsc5PK!:O~AndroidManifest.xmlPK!:T  classes.dexPK!:Aq|5 META-INF/CERT.SFPK!:fK& META-INF/CERT.RSAPK!:o1KMETA-INF/MANIFEST.MFPKnsrc/test/resources/com/android/apksig/v1-sha1-sha256-manifest-and-sf-with-sha1-wrong-in-sf.apk0100644 0000000 0000000 00000011405 13243353143 030647 0ustar000000000 0000000 PK !:;k薸 resources.arsc5 ($hTiny App for CTS [b U b` one two three]  . Tiny,   . App,   . for,   . CTS,   android.appsecurity.cts.tinyapp `@$ attrstring4 app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlnA&I@B(b"  @"  X"x@7dm :;s=gƎQN T֧HZTy <]g>_7(m|_wP L\S[յ6y?<Ў^Y$k/}‹sxϨa_Wc·\Rïڧ+ߎWZ1ó- f{Ȥ?7ü gz1=֨ߡ1*v!ܠiEoh VcȜx@uWN|->`c1{.Ƿ.y-jvhOY&ܤ+N;n4~EWǖ۫czu^"3A5erz#r7ims^e?GRspi:I\|iw6.[!z%Wq)_2sJ󙹧r>PKO~PK!: classes.dexkA&i4֚CڭAJDP/L7cm: ihQPM@[/*੠SIJM7wξ̼[ pn]]d.DJ09'/)/MhmhWQ!,H,#w/".2!KE56r<@"/Z\ ׁ˖Q$jqi4wܥǯL5 T>:7hZ巴hkkuUhU-Njky1Qi1cמu_ImC'Q'+D=:fxIT ]uy0RCZ+X'pokd8 8td)ϕ~Ρs~.͗x"pZfT0Hr<{좐*1`WhaYBaśyˋ5Ҷ3sTu6fJ#üu,b< woH3;#8z8CԹ- ͺ~hDhC7F#QvL("^ZxCd Gvvv范&=BBQ5Fޔk揎#R-tV)ΐV&(㥫sb I 7ApQQz::waT>;OG^x0Y{߳j_̿ذɳGݞ^=dz!8 0 ڌ ݹg@:k/_0 (ty=s}u-[q#дr"ٺNmדqȷr M.&u/H V-O[9]'<\pIҗMTu4V\=w3:sd[[?/PK!:ۣ&META-INF/CERT.RSA3hbajhδ%נ%ѐۀUIqA_&M03121q2en W 'l(hQTkd`ba 'khf`llhhbjnb%kbl`ahbĨl8#+s#?PƒIs3tVyid?\TcHr/NviC1P;4qg}#Y͗]:?q^lľ^'aڤS{z~/_HݿNE)߱j1ŗqǔw4M0XUC#yJ^^֫_^m^ӣ„D։2bWt{%1 l"䷁JNQqĹKO.[}VžM].ll/ɿCQ_:^gbfd`\`g :Y>1q&bzٖB73EȃY$ +)fa5`fd̠fs[5t=9Sodwc(l^ROb{@\⣭+j}Ґk4җZ6k'm7fݽ89, 7ȾoH/ٕ^AD:ӾYw 9J-(mp_$xqnٕw^*h_*rU|?sK;}ֆ)ʣf\: r?^{8-m*oKR&Fw`s* O9)_&FS䞩_^z%"iK/L*䫣]]/m2oUEtՙK=Izƞ<ɟDT*VnBcA$U6ygy>LDnwDm4Wؖ[mf |!7AwU,|Lv#oS>Oص\auB"Yi ~p}WGoO2,W69zٟs.XY7kg`}n?<[{fniPK!: KMETA-INF/MANIFEST.MFen@E$6 ȣ@ 40* @4mS790-SRYzN1pMa BaV'B(c1.Պ4q6zLq2fẍ\Y9;6m@6Ć4REU4#+.ɍP- )u.gSA6=M~+P|{Ѱ4fk9苲*YUO(@KR[ YD8@/?vKRStCZ"&u_7-zt;5fѣz.:EFhM PK !:;k薸 resources.arsc5PK!:O~AndroidManifest.xmlPK!:T  classes.dexPK!:y1R`|5 META-INF/CERT.SFPK!:ۣ& META-INF/CERT.RSAPK!: KMETA-INF/MANIFEST.MFPKn./PaxHeaders.X/src_test_resources_com_android_apksig_v1-sha1-sha256-manifest-and-sf-with-sha256-wro0100644 0000000 0000000 00000000160 13243353143 032045 xustar000000000 0000000 112 path=src/test/resources/com/android/apksig/v1-sha1-sha256-manifest-and-sf-with-sha256-wrong-in-manifest.apk src/test/resources/com/android/apksig/v1-sha1-sha256-manifest-and-sf-with-sha256-wrong-in-manifest.a0100644 0000000 0000000 00000011405 13243353143 031666 0ustar000000000 0000000 PK !:;k薸 resources.arsc5 ($hTiny App for CTS [b U b` one two three]  . Tiny,   . App,   . for,   . CTS,   android.appsecurity.cts.tinyapp `@$ attrstring4 app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlnA&I@B(b"  @"  X"x@7dm :;s=gƎQN T֧HZTy <]g>_7(m|_wP L\S[յ6y?<Ў^Y$k/}‹sxϨa_Wc·\Rïڧ+ߎWZ1ó- f{Ȥ?7ü gz1=֨ߡ1*v!ܠiEoh VcȜx@uWN|->`c1{.Ƿ.y-jvhOY&ܤ+N;n4~EWǖ۫czu^"3A5erz#r7ims^e?GRspi:I\|iw6.[!z%Wq)_2sJ󙹧r>PKO~PK!: classes.dexkA&i4֚CڭAJDP/L7cm: ihQPM@[/*੠SIJM7wξ̼[ pn]]d.DJ09'/)/MhmhWQ!,H,#w/".2!KE56r<@"/Z\ ׁ˖Q$jqi4wܥǯL5 T>:7hZ巴hkkuUhU-Njky1Qi1cמu_ImC'Q'+D=:fxIT ]uy0RCZ+X'pokd8 8td)ϕ~Ρs~.͗x"pZfT0Hr<{좐*1`WhaYBaśyˋ5Ҷ3sTu6fJ#üu,b< woH3;#8z8CԹ- ͺ~hDhC7F#QvL(hPFDvXPT[-ǘGw779|!*"H('_Ek2Qk< m"gҖsCeyQXK˜I'?"_ ^3X5gqI ܢA-e2㑗|&~tL*`+[:NEv帠"gm8һ`/4Zl~$s[V7لuJ) QW˖rɣk(z5Ex] 9S~UoYq}M+JM1Jxc :wSB3@ AZDJ5vи}\,0/t<\G@{~DYzjeΚ PK!:s8&META-INF/CERT.RSA3hbajhδ%נ%ѐۀUIqA_&M03121q2en W 'l(hQTkd`ba 'khf`llhhbjnb%kbl`ahbĨl8#+s#?PƒIs3tVyid?\TcHr/NviC1P;4qg}#Y͗]:?q^lľ^'aڤS{z~/_HݿNE)߱j1ŗqǔw4M0XUC#yJ^^֫_^m^ӣ„D։2bWt{%1 l"䷁JNQqĹKO.[}VžM].ll/ɿCQ_:^gbfd`\`g :Y>1q&bzٖB73EȃY$ +)fa5`fd̠fs[5t=9Sodwc(l^ROb{@\⣭+j}Ґk4җZ6k'm7fݽ89, 7ȾoH/ٕ^AD:ӾYw 9J-(mp_$xqnٕw^*h_*rU|?sK;}ֆ)ʣf\: r?^{8-m*oKR&Fw`s* O9)_&FsOz gէ`)9ȥ[4/eIf]4ٻ~ܸ-Wf G?9F.>[a}c69$sX0mj9[|]?4jdۊV)WT8g {XpUm陼x'el -a7uLLowu`#O<}qZ_vۤOh8:g87T;PK!:/JMETA-INF/MANIFEST.MFen@E&6 (bM\ E^":A60"c(Я5MM=DžEeLˌVQ +rkP沢´9Srj^Օ{dzLσgb'K&XoSmAD]hܭ`'0]-s0V!ՎңP5-OPK !:;k薸 resources.arsc5PK!:O~AndroidManifest.xmlPK!:T  classes.dexPK!:'-,<}5 META-INF/CERT.SFPK!:s8& META-INF/CERT.RSAPK!:/JMETA-INF/MANIFEST.MFPKnsrc/test/resources/com/android/apksig/v1-sha1-sha256-manifest-and-sf-with-sha256-wrong-in-sf.apk0100644 0000000 0000000 00000011402 13243353143 031020 0ustar000000000 0000000 PK !:;k薸 resources.arsc5 ($hTiny App for CTS [b U b` one two three]  . Tiny,   . App,   . for,   . CTS,   android.appsecurity.cts.tinyapp `@$ attrstring4 app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlnA&I@B(b"  @"  X"x@7dm :;s=gƎQN T֧HZTy <]g>_7(m|_wP L\S[յ6y?<Ў^Y$k/}‹sxϨa_Wc·\Rïڧ+ߎWZ1ó- f{Ȥ?7ü gz1=֨ߡ1*v!ܠiEoh VcȜx@uWN|->`c1{.Ƿ.y-jvhOY&ܤ+N;n4~EWǖ۫czu^"3A5erz#r7ims^e?GRspi:I\|iw6.[!z%Wq)_2sJ󙹧r>PKO~PK!: classes.dexkA&i4֚CڭAJDP/L7cm: ihQPM@[/*੠SIJM7wξ̼[ pn]]d.DJ09'/)/MhmhWQ!,H,#w/".2!KE56r<@"/Z\ ׁ˖Q$jqi4wܥǯL5 T>:7hZ巴hkkuUhU-Njky1Qi1cמu_ImC'Q'+D=:fxIT ]uy0RCZ+X'pokd8 8td)ϕ~Ρs~.͗x"pZfT0Hr<{좐*1`WhaYBaśyˋ5Ҷ3sTu6fJ#üu,b< woH3;#8z8CԹ- ͺ~hDhC7F#QvL(1q&bzٖB73EȃY$ +)fa5`fd̠fs[5t=9Sodwc(l^ROb{@\⣭+j}Ґk4җZ6k'm7fݽ89, 7ȾoH/ٕ^AD:ӾYw 9J-(mp_$xqnٕw^*h_*rU|?sK;}ֆ)ʣf\: r?^{8-m*oKR&Fw`s* O9)_&F[a#=^U;yZީ\\آh91]v%S28ج p2[7uaimK-S}xbV^ǰ2<\GCgSӲד-}eNJ5o`-fcl~Zpӯ[6r|=`cLnքnye>^h|7hTEq>|wE"{K)D'-nX/ /4{}xh36닽*M5?wLgn[PK!: KMETA-INF/MANIFEST.MFen@E$6 ȣ@ 40* @4mS790-SRYzN1pMa BaV'B(c1.Պ4q6zLq2fẍ\Y9;6m@6Ć4REU4#+.ɍP- )u.gSA6=M~+P|{Ѱ4fk9苲*YUO(@KR[ YD8@/?vKRStCZ"&u_7-zt;5fѣz.:EFhM PK !:;k薸 resources.arsc5PK!:O~AndroidManifest.xmlPK!:T  classes.dexPK!:d@|5 META-INF/CERT.SFPK!:W# META-INF/CERT.RSAPK!: KMETA-INF/MANIFEST.MFPKksrc/test/resources/com/android/apksig/v1-sha1-sha256-manifest-and-sf.apk0100644 0000000 0000000 00000011404 13243353143 024677 0ustar000000000 0000000 PK !:;k薸 resources.arsc5 ($hTiny App for CTS [b U b` one two three]  . Tiny,   . App,   . for,   . CTS,   android.appsecurity.cts.tinyapp `@$ attrstring4 app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlnA&I@B(b"  @"  X"x@7dm :;s=gƎQN T֧HZTy <]g>_7(m|_wP L\S[յ6y?<Ў^Y$k/}‹sxϨa_Wc·\Rïڧ+ߎWZ1ó- f{Ȥ?7ü gz1=֨ߡ1*v!ܠiEoh VcȜx@uWN|->`c1{.Ƿ.y-jvhOY&ܤ+N;n4~EWǖ۫czu^"3A5erz#r7ims^e?GRspi:I\|iw6.[!z%Wq)_2sJ󙹧r>PKO~PK!: classes.dexkA&i4֚CڭAJDP/L7cm: ihQPM@[/*੠SIJM7wξ̼[ pn]]d.DJ09'/)/MhmhWQ!,H,#w/".2!KE56r<@"/Z\ ׁ˖Q$jqi4wܥǯL5 T>:7hZ巴hkkuUhU-Njky1Qi1cמu_ImC'Q'+D=:fxIT ]uy0RCZ+X'pokd8 8td)ϕ~Ρs~.͗x"pZfT0Hr<{좐*1`WhaYBaśyˋ5Ҷ3sTu6fJ#üu,b< woH3;#8z8CԹ- ͺ~hDhC7F#QvL(2UFo&܋f'L>,C"Qb?GccOw/n /EB:a{|Lqy([*p+дj"ٺLq\TqC{]H\l_Ҳ!}?$4zHuǍSTϸ{ϥx**Xxn{mA{ PK!:Y'O%META-INF/CERT.RSA3hbajhδ%נ%ѐۀUIqA_&M03121q2en W 'l(hQTkd`ba 'khf`llhhbjnb%kbl`ahbĨl8#+s#?PƒIs3tVyid?\TcHr/NviC1P;4qg}#Y͗]:?q^lľ^'aڤS{z~/_HݿNE)߱j1ŗqǔw4M0XUC#yJ^^֫_^m^ӣ„D։2bWt{%1 l"䷁JNQqĹKO.[}VžM].ll/ɿCQ_:^gbfd`\`g :Y>1q&bzٖB73EȃY$ +)fa5`fd̠fs[5t=9Sodwc(l^ROb{@\⣭+j}Ґk4җZ6k'm7fݽ89, 7ȾoH/ٕ^AD:ӾYw 9J-(mp_$xqnٕw^*h_*rU|?sK;}ֆ)ʣf\: r?^{8-m*oKR&Fw`s* O9)_&F897 )]|T>;_mIWq [Jx_7(m|_wP L\S[յ6y?<Ў^Y$k/}‹sxϨa_Wc·\Rïڧ+ߎWZ1ó- f{Ȥ?7ü gz1=֨ߡ1*v!ܠiEoh VcȜx@uWN|->`c1{.Ƿ.y-jvhOY&ܤ+N;n4~EWǖ۫czu^"3A5erz#r7ims^e?GRspi:I\|iw6.[!z%Wq)_2sJ󙹧r>PKO~PK!: classes.dexkA&i4֚CڭAJDP/L7cm: ihQPM@[/*੠SIJM7wξ̼[ pn]]d.DJ09'/)/MhmhWQ!,H,#w/".2!KE56r<@"/Z\ ׁ˖Q$jqi4wܥǯL5 T>:7hZ巴hkkuUhU-Njky1Qi1cמu_ImC'Q'+D=:fxIT ]uy0RCZ+X'pokd8 8td)ϕ~Ρs~.͗x"pZfT0Hr<{좐*1`WhaYBaśyˋ5Ҷ3sTu6fJ#üu,b< woH3;#8z8CԹ- ͺ~hDhC7F#QvL(2UFo&܋f'L>,C"Qb?GccOw/n /EB:a{|Lqy([*p+дj"ٺLq\TqC{]H\l_Ҳ!}?$4zHuǍSTϸ{ϥx**Xxn{mA{ PK!:Y'O%META-INF/CERT.RSA3hbajhδ%נ%ѐۀUIqA_&M03121q2en W 'l(hQTkd`ba 'khf`llhhbjnb%kbl`ahbĨl8#+s#?PƒIs3tVyid?\TcHr/NviC1P;4qg}#Y͗]:?q^lľ^'aڤS{z~/_HݿNE)߱j1ŗqǔw4M0XUC#yJ^^֫_^m^ӣ„D։2bWt{%1 l"䷁JNQqĹKO.[}VžM].ll/ɿCQ_:^gbfd`\`g :Y>1q&bzٖB73EȃY$ +)fa5`fd̠fs[5t=9Sodwc(l^ROb{@\⣭+j}Ґk4җZ6k'm7fݽ89, 7ȾoH/ٕ^AD:ӾYw 9J-(mp_$xqnٕw^*h_*rU|?sK;}ֆ)ʣf\: r?^{8-m*oKR&Fw`s* O9)_&F897 )]|T>;_mIWq [Jx:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:META-INF/MANIFEST.MFeMK@X,~B?6jY5 -L6323ۯ"( jǔ1jxfڎ@^hzD~ײ$K)2a+k]nvm?!PV|Xx% ޤeՒ74CNA\7~"HL1-NݜQG;%9 jW-|{^r*e^Z)n)k]NA#6nwvkMSd^<|W*-O:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ  qSLH@V8t6U ڇpag栓dA\^۰1 T Q5Wxr⻶y@00T Wt[/v"0 *H=010U ec-p5210 160331153122Z 430817153122Z010U ec-p52100*H=+#ay( ȰTʯr,yN ~ua7*F 6 0YV&,S֕'a(P6ӵ)kشӘ J~|En6/eBusHPzP0N0UEϭ CXve-0U#0Eϭ CXve-0 U00 *H=0BZMɨa{jx=HfZ)*Dh|Jnb^\CIe_4!DB-BqmbW1s2 ~FzUy{d̚8D8WZ<̠ӚW0B @YVzǶ>LǀRvf+"&HsHG]yUxX^肋A7B>W/U}X o$[.i"})aכЀzayT; 'p2,gVKpn泞00*H=+#ay( ȰTʯr,yN ~ua7*F 6 0YV&,S֕'a(P6ӵ)kشӘ J~|En6/eBusHPzAPK Sig Block 42PK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPKs src/test/resources/com/android/apksig/v2-only-cert-and-public-key-mismatch.apk0100644 0000000 0000000 00000010046 13243353143 026406 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ  q{w9,( zd@GҦY}$5H{IJm\00 50l0  *H  010U rsa-20480 160331145749Z 430817145749Z010U rsa-20480"0  *H 0 5^0#YY2aD%],E sdM,T75P"¥TtImyjDn%X'J5" qrNsߧgBF_3.."/:GMH)x0(hC$nqJ;ߥ_'{7Wzq`haJh\xJb0N vt\%3Xw'ɦ"[z塌8б(!!,[P0N0U-s89j!Y,o0U#0-s89j!Y,o0 U00  *H  dKnBȓ@dyYNFsK'6 , $u*\I&}{y?0}xG)L` 2@*kOojki"@\X2"G1p92]UȽ||Vqrk6k)yVll ͩkcqgɻ2G uв}U5E rB^Ӻ𐺎ʳ7&#AU[J?ԺL(sIV~@? 'N>O֭`p䉱y? Sb=QpŠ,`rFq6)l7y_ X]*mJ2:Oɖ &0"0  *H 0 5^0#YY2aD%],E sdM,T75P"¥TtImyjDn%X'J5" qrNsߧgBF_3.."/:GMH)x0(hC$nqJ;ߥ_'{7Wzq`haJh\xJb0N vt\%3Xw'ɦ"[z塌8б(!!,[APK Sig Block 42PKsrc/test/resources/com/android/apksig/v2-only-garbage-between-cd-and-eocd.apk0100644 0000000 0000000 00000010055 13243353143 026117 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ  q{w9,( 0% gަhRL|t5(J}00 50l0  *H  010U rsa-20480 160331145749Z 430817145749Z010U rsa-20480"0  *H 0 5^0#YY2aD%],E sdM,T75P"¥TtImyjDn%X'J5" qrNsߧgBF_3.."/:GMH)x0(hC$nqJ;ߥ_'{7Wzq`haJh\xJb0N vt\%3Xw'ɦ"[z塌8б(!!,[P0N0U-s89j!Y,o0U#0-s89j!Y,o0 U00  *H  dKnBȓ@dyYNFsK'6 , $u*\I&}{y?0}xG)L` 2@*:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ  q{w9,( Wg볂D֣M<00 50l0  *H  010U rsa-20480 160331145749Z 430817145749Z010U rsa-20480"0  *H 0 5^0#YY2aD%],E sdM,T75P"¥TtImyjDn%X'J5" qrNsߧgBF_3.."/:GMH)x0(hC$nqJ;ߥ_'{7Wzq`haJh\xJb0N vt\%3Xw'ɦ"[z塌8б(!!,[P0N0U-s89j!Y,o0U#0-s89j!Y,o0 U00  *H  dKnBȓ@dyYNFsK'6 , $u*\I&}{y?0}xG)L` 2@*<:8Ae4x ^Z:J'C$ &&0"0  *H 0 5^0#YY2aD%],E sdM,T75P"¥TtImyjDn%X'J5" qrNsߧgBF_3.."/:GMH)x0(hC$nqJ;ߥ_'{7Wzq`haJh\xJb0N vt\%3Xw'ɦ"[z塌8б(!!,[APK Sig Block 42PK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPKVsrc/test/resources/com/android/apksig/v2-only-missing-classes.dex.apk0100644 0000000 0000000 00000005062 13243353143 024731 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^av,  q LH@Bh2U.cXKۜ' Ʒ8?\ P0N0ULŎ+m8<9i0U#0LŎ+m8<9i0 U00 *H=i0f1+]=.re>>$N)\ܾ&, ;|1UBu&3a_2YqML۰L0xasog0e1*spBGi"؂?/\|1ڂLjqc鋢Dq{ϲ0G٣,%}*bN@|ۢ 'H_(6UEx0v0*H=+"bcu?q4ģ= 8A(bo#(%=5C~>U.cXKۜ' Ʒ8?\ ,APK Sig Block 42PK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK src/test/resources/com/android/apksig/v2-only-no-certs-in-sig.apk0100644 0000000 0000000 00000006445 13243353143 023772 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ  qzv8,( zd@GҦY}$5H{IJm\ *lM|ߤV6Y rϐAqzd$ess~|E%PuUkwi6o%_]^#J_yO2O=*Aś^GQP6U=s#5h(e"-t*&>d9BiB"qNR]on;[1-~'85 eĕnߟr-gL1S/:+R|J\rtgBM] .Gq&0"0  *H 0 5^0#YY2aD%],E sdM,T75P"¥TtImyjDn%X'J5" qrNsߧgBF_3.."/:GMH)x0(hC$nqJ;ߥ_'{7Wzq`haJh\xJb0N vt\%3Xw'ɦ"[z塌8б(!!,[APK Sig Block 42PK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPKU src/test/resources/com/android/apksig/v2-only-signatures-and-digests-block-mismatch.apk0100644 0000000 0000000 00000010122 13243353143 030316 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ  qeX( zd@GҦY}$5H{IJm\(xV4 zd@GҦY}$5H{IJm\00 50l0  *H  010U rsa-20480 160331145749Z 430817145749Z010U rsa-20480"0  *H 0 5^0#YY2aD%],E sdM,T75P"¥TtImyjDn%X'J5" qrNsߧgBF_3.."/:GMH)x0(hC$nqJ;ߥ_'{7Wzq`haJh\xJb0N vt\%3Xw'ɦ"[z塌8б(!!,[P0N0U-s89j!Y,o0U#0-s89j!Y,o0 U00  *H  dKnBȓ@dyYNFsK'6 , $u*\I&}{y?0}xG)L` 2@*=@n6MҨl!9xTH_~mI~e!jv=cqƈѳ%t{1S~uK9ETtzIET$IW6Ms>V8-44?ͶAVL5 VYS,Eb1˛a`[fK3ʜmcOB~͖\ D#+ ڗ2MzWb7Ҙrc~9ʄ֑<5KƦ3&0"0  *H 0 5^0#YY2aD%],E sdM,T75P"¥TtImyjDn%X'J5" qrNsߧgBF_3.."/:GMH)x0(hC$nqJ;ߥ_'{7Wzq`haJh\xJb0N vt\%3Xw'ɦ"[z塌8б(!!,[APK Sig Block 42PK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPKsrc/test/resources/com/android/apksig/v2-only-targetSandboxVersion-2.apk0100644 0000000 0000000 00000010112 13243353143 025350 0ustar000000000 0000000 PK !:!O resources.arsc5 (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=㵝q'8ֆ(PhB$CAD*  MD(OB|AEuvf{cf ķ"Pb88눸F<"^O;qQDB| >|([CH ~8B} zs=4LTrcG]Z= `>itd))K]]<&L>-;<Si;'BGPK  q{w9,( d7%ٵ w@έjb+`VA00 50l0  *H  010U rsa-20480 160331145749Z 430817145749Z010U rsa-20480"0  *H 0 5^0#YY2aD%],E sdM,T75P"¥TtImyjDn%X'J5" qrNsߧgBF_3.."/:GMH)x0(hC$nqJ;ߥ_'{7Wzq`haJh\xJb0N vt\%3Xw'ɦ"[z塌8б(!!,[P0N0U-s89j!Y,o0U#0-s89j!Y,o0 U00  *H  dKnBȓ@dyYNFsK'6 , $u*\I&}{y?0}xG)L` 2@*Ν#̞:@oE@PpqqxD$/w.7@|&~P"""&6 -A$&q6}.ѝ{hީ 序|Ku>;zc>}:\mnSgS@6;d|˓yL}Zv8#xB n-2m7=ܫOΝ6#-5u"--Tzt}BWgP#K-"5$m3S]{UWq]wzM3Rer2r'2.Z {Jr1cvOAMſz~4RJ^Ym["/CѠm`qU՜R5QP9! S-1aɿsxSlf=˧ 3:z]`b?m 7ib]dYc7krkpsr;mr9N̚wAlLX<Si;'BGPK   q YLH@AR_AY;NhDuW˜[SW1Rh2ggwsQm00 q 0  *H  010U rsa-40960 160331152846Z 430817152846Z010U rsa-40960"0  *H 0 >;o'= YOAcCZ3r<\m6P 甚'o:JX`p7Ahذb0^tFp y^i^Y³803W ~ 8^]((=RA&@Iut`r"Ҙ h9(z#>Ltz٤mѦ+Txi&dZuež?g SȦ/z#S1W2ԿGCfA iŨ/4a֙3q5a̺ {.?ˠ̝Kp4oP֔%=["r#>߹!D;=H㒢!;&0"0  *H 0 >;o'= YOAcCZ3r<\m6P 甚'o:JX`p7Ahذb0^tFp y^i^Y³803W ~ 8^]((=:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ  q{w9,( @ɷzGDފez-m\%00 50l0  *H  010U rsa-20480 160331145749Z 430817145749Z010U rsa-20480"0  *H 0 5^0#YY2aD%],E sdM,T75P"¥TtImyjDn%X'J5" qrNsߧgBF_3.."/:GMH)x0(hC$nqJ;ߥ_'{7Wzq`haJh\xJb0N vt\%3Xw'ɦ"[z塌8б(!!,[P0N0U-s89j!Y,o0U#0-s89j!Y,o0 U00  *H  dKnBȓ@dyYNFsK'6 , $u*\I&}{y?0}xG)L` 2@*:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ n qfw9,( zd@GҦY}$5H{IJm\00 50l0  *H  010U rsa-20480 160331145749Z 430817145749Z010U rsa-20480"0  *H 0 5^0#YY2aD%],E sdM,T75P"¥TtImyjDn%X'J5" qrNsߧgBF_3.."/:GMH)x0(hC$nqJ;ߥ_'{7Wzq`haJh\xJb0N vt\%3Xw'ɦ"[z塌8б(!!,[P0N0U-s89j!Y,o0U#0-s89j!Y,o0 U00  *H  dKnBȓ@dyYNFsK'6 , $u*\I&}{y?0}xG)L` 2@*:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ  qw9,( zd@GҦY}$5H{IJm\00 50l0  *H  010U rsa-20480 160331145749Z 430817145749Z010U rsa-20480"0  *H 0 5^0#YY2aD%],E sdM,T75P"¥TtImyjDn%X'J5" qrNsߧgBF_3.."/:GMH)x0(hC$nqJ;ߥ_'{7Wzq`haJh\xJb0N vt\%3Xw'ɦ"[z塌8б(!!,[P0N0U-s89j!Y,o0U#0-s89j!Y,o0 U00  *H  dKnBȓ@dyYNFsK'6 , $u*\I&}{y?0}xG)L` 2@*:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ . qw9,( zd@GҦY}$5H{IJm\00 50l0  *H  010U rsa-20480 160331145749Z 430817145749Z010U rsa-20480"0  *H 0 5^0#YY2aD%],E sdM,T75P"¥TtImyjDn%X'J5" qrNsߧgBF_3.."/:GMH)x0(hC$nqJ;ߥ_'{7Wzq`haJh\xJb0N vt\%3Xw'ɦ"[z塌8б(!!,[P0N0U-s89j!Y,o0U#0-s89j!Y,o0 U00  *H  dKnBȓ@dyYNFsK'6 , $u*\I&}{y?0}xG)L` 2@*:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ  xV4UNKNOWN BLOCK  q YLH@V8t6U ڇpag栓dA\^۰1 T Q5Wxr⻶y@00 q 0  *H  010U rsa-40960 160331152846Z 430817152846Z010U rsa-40960"0  *H 0 >;o'= YOAcCZ3r<\m6P 甚'o:JX`p7Ahذb0^tFp y^i^Y³803W ~ 8^]((=XIdF[M %:$K`At W{Ӗ&0"0  *H 0 >;o'= YOAcCZ3r<\m6P 甚'o:JX`p7Ahذb0^tFp y^i^Y³803W ~ 8^]((=:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ  q,( zd@GҦY}$5H{IJm\00q J(0 *H8010U dsa-10250 160331152710Z 430817152710Z010U dsa-102400+*H80.XHeYY?`·7`ߞV>U vGjᡵ/ȩX0m܀|;oQ}05זjNwb(#c `~AfpqrknRPmNs. XF-~,9r%q ) A]M^^ ɕEUQ:UץXX=;4ڐ8ݐB@Znѷm2@,sĤICJ]G(y^ަ,hH:<=񋂃{a+n6cmVϐڨfL0T ;}li? le3[d״`lp#QbƣP0N0U:Ui.%0U#0:Ui.%0 U00 *H8/0,Qri /sP[D-w}:6.0, 4Mn5: LU\5\U`b;+ź00+*H80.XHeYY?`·7`ߞV>U vGjᡵ/ȩX0m܀|;oQ}05זjNwb(#c `~AfpqrknRPmNs. XF-~,9r%q ) A]M^^ ɕEUQ:UץXX=;4ڐ8ݐB@Znѷm2@,sĤICJ]G(y^ަ,hH:<=񋂃{a+n6cmVϐڨfL0T ;}li? le3[d״`lp#QbAPK Sig Block 42PK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPKsrc/test/resources/com/android/apksig/v2-only-with-dsa-sha256-1024.apk0100644 0000000 0000000 00000007640 13243353143 024164 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ  q,( zd@GҦY}$5H{IJm\00q J(0 *H8010U dsa-10240 160331152710Z 430817152710Z010U dsa-102400+*H80.XHeYY?`·7`ߞV>U vGjᡵ/ȩX0m܀|;oQ}05זjNwb(#c `~AfpqrknRPmNs. XF-~,9r%q ) A]M^^ ɕEUQ:UץXX=;4ڐ8ݐB@Znѷm2@,sĤICJ]G(y^ަ,hH:<=񋂃{a+n6cmVϐڨfL0T ;}li? le3[d״`lp#QbƣP0N0U:Ui.%0U#0:Ui.%0 U00 *H8/0,Qri /sP[D-w}:6.0, 4Mn5: LU\5\U`b;+ź00+*H80.XHeYY?`·7`ߞV>U vGjᡵ/ȩX0m܀|;oQ}05זjNwb(#c `~AfpqrknRPmNs. XF-~,9r%q ) A]M^^ ɕEUQ:UץXX=;4ڐ8ݐB@Znѷm2@,sĤICJ]G(y^ަ,hH:<=񋂃{a+n6cmVϐڨfL0T ;}li? le3[d״`lp#QbAPK Sig Block 42PK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPKsrc/test/resources/com/android/apksig/v2-only-with-dsa-sha256-2048.apk0100644 0000000 0000000 00000011364 13243353143 024171 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ qQ qIE,( zd@GҦY}$5H{IJm\c_0[0 .bj>0 *H8010U dsa-20480 160331183001Z 430817183001Z010U dsa-20480G09*H80,oRG"٦Y92+lS[2Z1MZn=Zq !_2h"2~At~zYc{6->rx%z}.B_4 @5HŻP~ן7N\;ϛۄ> Qh%e$Ög) Y*B$ np^,rvSFP>1)?Q;|"-=?+(Ojb뚘l\#U pFi !+)nLyg.Yaj}9,7{ZIȾG1N$J2[f!̵H8P[x^qpq{} )"vGEà)o3JD7̨j䣍>?b|nع^Cɤx5yez ^< x4o86;bI1/|[ڀ, P<0ϳUcExf%Ȥ*:Nh 0MH0@o~ c :-Z鑰BT˟i_ڭMs2"%>V'DAP LUMZDٝlB' #EP<M̢F&u.TY"`Ӡ%:DFЯ{OcqCa9h|7k:"SZ Xsh`B-;uL\})tO%4DnO 2L3Q]uBdrx%z}.B_4 @5HŻP~ן7N\;ϛۄ> Qh%e$Ög) Y*B$ np^,rvSFP>1)?Q;|"-=?+(Ojb뚘l\#U pFi !+)nLyg.Yaj}9,7{ZIȾG1N$J2[f!̵H8P[x^qpq{} )"vGEà)o3JD7̨j䣍>?b|nع^Cɤx5yez ^< x4o86;bI1/|[ڀ, P<0ϳUcExf%Ȥ*:Nh 0MH0@o~ c :-Z鑰BT˟i_ڭMs2"%>V'DAP LUMZDٝlB' #EP<M̢F&u.TY"`Ӡ%:DFЯ{OcqCa9h|7k:"SZ Xsh`B-;uL\})tO%4DnO 2L3Q]uBd:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ q Q  qI E ,( zd@GҦY}$5H{IJm\00 <60 *H8010U dsa-30720 160331183256Z 430817183256Z010U dsa-3072009*H80,(2< u^OO6?qX1t"Q@N|%, p۳$}0.E1.=u,D੝  s-amTKmܣ jY>nDfi%'KR;;\YViaP;n$t.}BFsJL=v6^ǼВX5Nj$P3zGn S PȺ{ǯwW6@f&ߵ/rm04Z#l"$hmW & 1f@as/0 n%p ݱ)'߭6a:4/+3KwY.ШǍd@]8{q3PE@G5.΁YR wd%bR˚2x)O)6=%§?#C;7 < bޠ{C@ψ)[!Czv_elt]Gu7wdƇz7AZ0Kõ/9›*ʽ):] ֨ǽEf4.!H l@nPoP= N}x;wGnoH00P=Ah*GdN/Awe;0Ο jq' -T۰q 7 p|S\-Q*h_kiuH0io;xvS.lnU@UAǰ5?+ ϸJq2 $6=4j~*u2*i~'8 )ǓQz #᠄6<*vC.|^҃M:vSs׮bF(@(ul0t9݂8(s3Jq4+d,2znN(35mJ6ZnDfi%'KR;;\YViaP;n$t.}BFsJL=v6^ǼВX5Nj$P3zGn S PȺ{ǯwW6@f&ߵ/rm04Z#l"$hmW & 1f@as/0 n%p ݱ)'߭6a:4/+3KwY.ШǍd@]8{q3PE@G5.΁YR wd%bR˚2x)O)6=%§?#C;7 < bޠ{C@ψ)[!Czv_elt]Gu7wdƇz7AZ0Kõ/9›*ʽ):] ֨ǽEf4.!H l@nPoP= N}x;wGnoH00P=Ah*GdN/Awe;0Ο jq' -T۰q 7 p|S\-Q*h_kiuH0io;xvS.lnU@UAǰ5?+ ϸJq2 $6=4j~*u2*i~'8 )ǓQz #᠄6<*vC.|^҃M:vSs׮bF(@(ul0t9݂8(s3Jq4+d,2znN(35mJ6Z:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ r qjf,( d@GҦY}$5H{IJm\tp0l0 Mfr0 *H=010U ec-p2560 160331145806Z 430817145806Z010U ec-p2560Y0*H=*H=B_="I1+x_e6NΐҴᔵ ܎T:v2%ǂtDP0N0U5h[02 qC[0U#05h[02 qC[0 U00 *H=I0F!l),\Tǟ@ax!lWATG۸,USOG0E!6Q٫( 5g=o_R,# ķJq< sz09#lziY)[0Y0*H=*H=B_="I1+x_e6NΐҴᔵ ܎T:v2%ǂtDAPK Sig Block 42PK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPKE src/test/resources/com/android/apksig/v2-only-with-ecdsa-sha256-p256-sig-does-not-verify.apk0100644 0000000 0000000 00000006425 13243353143 030412 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ r qjf,( zd@GҦY}$5H{IJm\tp0l0 Mfr0 *H=010U ec-p2560 160331145806Z 430817145806Z010U ec-p2560Y0*H=*H=B_="I1+x_e6NΐҴᔵ ܎T:v2%ǂtDP0N0U5h[02 qC[0U#05h[02 qC[0 U00 *H=I0F!l),\Tǟ@ax!lWATG۸,USOG0E *a~^l̜}4/R! <2:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ r qjf,( zd@GҦY}$5H{IJm\tp0l0 Mfr0 *H=010U ec-p2560 160331145806Z 430817145806Z010U ec-p2560Y0*H=*H=B_="I1+x_e6NΐҴᔵ ܎T:v2%ǂtDP0N0U5h[02 qC[0U#05h[02 qC[0 U00 *H=I0F!l),\Tǟ@ax!lWATG۸,USOG0E +a~^l̜}4/R! <2:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ   q,( zd@GҦY}$5H{IJm\00. VK"j0 *H=010U ec-p3840 160331153057Z 430817153057Z010U ec-p3840v0*H=+"bcu?q4ģ= 8A(bo#(%=5C~>U.cXKۜ' Ʒ8?\ P0N0ULŎ+m8<9i0U#0LŎ+m8<9i0 U00 *H=i0f1+]=.re>>$N)\ܾ&, ;|1UBu&3a_2YqML۰L0xarnf0d0 nGcM^W P] gڊT0..Lu[o6T{D\TH"έVx8" G"sx0v0*H=+"bcu?q4ģ= 8A(bo#(%=5C~>U.cXKۜ' Ʒ8?\ APK Sig Block 42PK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPK src/test/resources/com/android/apksig/v2-only-with-ecdsa-sha256-p521.apk0100644 0000000 0000000 00000007043 13243353143 024572 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ  qxt3,( zd@GҦY}$5H{IJm\00T Wt[/v"0 *H=010U ec-p5210 160331153122Z 430817153122Z010U ec-p52100*H=+#ay( ȰTʯr,yN ~ua7*F 6 0YV&,S֕'a(P6ӵ)kشӘ J~|En6/eBusHPzP0N0UEϭ CXve-0U#0Eϭ CXve-0 U00 *H=0BZMɨa{jx=HfZ)*Dh|Jnb^\CIe_4!DB-BqmbW1s2 ~FzUy{d̚8D8WZ<̠ӚW0BG %ĕkD4([$y~,ёVᩋ L볒B\^XIiTBD%xR܁'&#ao6rGD%1_ .,ihAR;!6s2,00*H=+#ay( ȰTʯr,yN ~ua7*F 6 0YV&,S֕'a(P6ӵ)kشӘ J~|En6/eBusHPzAPK Sig Block 42PK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPKS src/test/resources/com/android/apksig/v2-only-with-ecdsa-sha512-p256.apk0100644 0000000 0000000 00000006465 13243353143 024601 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ  qLH@V8t6U ڇpag栓dA\^۰1 T Q5Wxr⻶y@tp0l0 Mfr0 *H=010U ec-p2560 160331145806Z 430817145806Z010U ec-p2560Y0*H=*H=B_="I1+x_e6NΐҴᔵ ܎T:v2%ǂtDP0N0U5h[02 qC[0U#05h[02 qC[0 U00 *H=I0F!l),\Tǟ@ax!lWATG۸,USOG0E!ݞk/C on2&,Ԍ9Ď ~% >$oE d}0O/fy-[0Y0*H=*H=B_="I1+x_e6NΐҴᔵ ܎T:v2%ǂtDAPK Sig Block 42PK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPKe src/test/resources/com/android/apksig/v2-only-with-ecdsa-sha512-p384.apk0100644 0000000 0000000 00000006656 13243353143 024605 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ +  q LH@V8t6U ڇpag栓dA\^۰1 T Q5Wxr⻶y@00. VK"j0 *H=010U ec-p3840 160331153057Z 430817153057Z010U ec-p3840v0*H=+"bcu?q4ģ= 8A(bo#(%=5C~>U.cXKۜ' Ʒ8?\ P0N0ULŎ+m8<9i0U#0LŎ+m8<9i0 U00 *H=i0f1+]=.re>>$N)\ܾ&, ;|1UBu&3a_2YqML۰L0xarnf0d07UE8I$id^sG3 wbz$]ϷP|Y0 E&Bf'"7ͯ]f ̂H9Y #Ux0v0*H=+"bcu?q4ģ= 8A(bo#(%=5C~>U.cXKۜ' Ʒ8?\ +APK Sig Block 42PK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPK src/test/resources/com/android/apksig/v2-only-with-ecdsa-sha512-p521.apk0100644 0000000 0000000 00000007103 13243353143 024562 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ  qSLH@V8t6U ڇpag栓dA\^۰1 T Q5Wxr⻶y@00T Wt[/v"0 *H=010U ec-p5210 160331153122Z 430817153122Z010U ec-p52100*H=+#ay( ȰTʯr,yN ~ua7*F 6 0YV&,S֕'a(P6ӵ)kشӘ J~|En6/eBusHPzP0N0UEϭ CXve-0U#0Eϭ CXve-0 U00 *H=0BZMɨa{jx=HfZ)*Dh|Jnb^\CIe_4!DB-BqmbW1s2 ~FzUy{d̚8D8WZ<̠ӚW0B @YVzǶ>LǀRvf+"&HsHG]yUxX^肋A7B>W/U}X o$[.i"})aכЀzayT; 'p2,gVKpn泞00*H=+#ay( ȰTʯr,yN ~ua7*F 6 0YV&,S֕'a(P6ӵ)kشӘ J~|En6/eBusHPzAPK Sig Block 42PK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPKs src/test/resources/com/android/apksig/v2-only-with-ignorable-unsupported-sig-algs.apk0100644 0000000 0000000 00000011226 13243353143 030052 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ  q(xV4 zd@GҦY}$5H{IJm\( zd@GҦY}$5H{IJm\(!Ce zd@GҦY}$5H{IJm\00 50l0  *H  010U rsa-20480 160331145749Z 430817145749Z010U rsa-20480"0  *H 0 5^0#YY2aD%],E sdM,T75P"¥TtImyjDn%X'J5" qrNsߧgBF_3.."/:GMH)x0(hC$nqJ;ߥ_'{7Wzq`haJh\xJb0N vt\%3Xw'ɦ"[z塌8б(!!,[P0N0U-s89j!Y,o0U#0-s89j!Y,o0 U00  *H  dKnBȓ@dyYNFsK'6 , $u*\I&}{y?0}xG)L` 2@*:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ { qso5,( <*o1Vyɕ=6Q00] 0  *H  010U rsa-10240 160331161443Z 430817161443Z010U rsa-102400  *H 0㏯> "c{/ ~WU_yAq{{j2'&QW=FS oꊻlp|_f!@o=1 ԷjL8l}  , 5)[.WG) ĊbP0N0UDYxgSoFĿy90U#0DYxgSoFĿy90 U00  *H  4ۀ o=Ms<lkIÍvU)?RON^c*r0 \*B\ڏ. HTfUDa~%j>_@ 8vJdV2.ҝ*:{^6+N솲[xpCSC%F;rBTH_M|ԏ)G|=) YJ2]RStPR3of(}K=CNA5wi "c{/ ~WU_yAq{{j2'&QW=FS oꊻlp|_f!@o=1 ԷjL8l}  , 5)[.WG) ĊbAPK Sig Block 42PK !:!O resources.arsc5PK!:^avAndroidManifest.xmlPK!:Շ  oclasses.dexPKV src/test/resources/com/android/apksig/v2-only-with-rsa-pkcs1-sha256-1024.apk0100644 0000000 0000000 00000007035 13243353143 025217 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ z qrn4,( zd@GҦY}$5H{IJm\00] 0  *H  010U rsa-10240 160331161443Z 430817161443Z010U rsa-102400  *H 0㏯> "c{/ ~WU_yAq{{j2'&QW=FS oꊻlp|_f!@o=1 ԷjL8l}  , 5)[.WG) ĊbP0N0UDYxgSoFĿy90U#0DYxgSoFĿy90 U00  *H  4ۀ o=Ms<lkIÍvU)?RON^c*r0 \*B\ڏ. HTfUDa~%j>_@ 8vJdV2.Z@c-zw &m`5bR_Z<4 nGpVBD2t^nQ'Iq#;u,VeD &_eEȑ]|kY,"$;"]AJ&*q@000  *H 0㏯> "c{/ ~WU_yAq{{j2'&QW=FS oꊻlp|_f!@o=1 ԷjL8l}  , 5)[.WG) ĊbAPK Sig Block 42PK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPKM src/test/resources/com/android/apksig/v2-only-with-rsa-pkcs1-sha256-16384.apk0100644 0000000 0000000 00000026050 13243353143 025314 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ !! q}!y!;,( zd@GҦY}$5H{IJm\00 wn0  *H  010U rsa-163840 160404193431Z 430821193431Z010U rsa-163840"0  *H 0 س+O)6_vKE_IϷMSgw 1f_FsĒ~8H*ƾ{1Lr/}xV沅0H%]qV'u | v҄k5te: DҜ̒ Z y\ӑ4IT\lpA[*:Y1t/;drwH7xEnP"85k''YfR8,-pN> ݊B?'7IԫceP";0B7{AH >WF,I#mg&yzxgq9/4P$WdFUY &1P48ʲiQ^V.$`K_ĎM.!_#Ü}R3x>[we6`:^}LB6y9 OK@9&]^9MQ$#׊Sq_7zaY2J\ɒ%%o_sU& pѕD<^ɻ9,&`]2{&bEjۆsJfsQ`4 ,=` G}2YBJpXM"Fo7eyC+KJM@+ RNiPȕs}ڕyq qJ88jny -nKSg~B(p/NϽBoc P~wX9j)mM=;BzFIW+d+Ϊv\&r曽Wr`FDE=:Lpc:{ZЬ˵>;MLȞA/T&mS.kT&J\I[M=iJh*Aߡ0 q3=JU"¨9ezi֚a@>DEX\8Wx V$h`)%ZT :ljxbD~:? @٥e17Io(E}L^b1j RM3֦_OpJtH ]\j9FԗLVHc v l}QS_t2#Z7̨0Ө:ĜW pm&q9䑢7k7_yێ'fE~۩E_g J  /;%-DafAi1{}Ah̪Ϧ X 6#O5OhxAk0Y#V/@q9 l![g1<^trnh ~7sVMhY7f&ZK}v qKIq8K%<|]gn%9֞z(ٸ9 iEqUQ6EH z{/VEU:e#M# ǛnAeU 0P0N0U$p_DLs"0S,0U#0$p_DLs"0S,0 U00  *H  G;b$ ENBlWogp/6@HI/ z⾤$* !XxIP<O܆"hmYOg}]ߑ"&$=#e}' hYfy(P{f//s~%ydbqm 74*~N jKmdppdqGF>K*4tORvcm:S[{b>m}+L݈9On;K&fY#,rst^'%hL0@BMQ1Ce9."pnuJIi;9>d"ZZ4L-Y$IQ0{Dky!,Sv>?S)҄ܧ xktHAi(Wc* 5sҿhdf;nUaGJ^TCnLNswGn>ӕWݭ5a=S6գf)_Wr-0͛`RC5/.x$5["=r7P;E3[\bce !pY =iC`yΟK${L%di/]rBqBid.C$L&vW?|ךr|pzs62}PƓ6@GyM?P'O;]ۻykQ3#_Un3;̓G}5 +9f̸2n,sɽ)WJV{.Rnx~zBћ*J-׶<ʆO6&%T nrӘ@>u, " z$ ɻk"hVߜXnM*3f"[*=u(/ Mʹ 7!]`Hjx ༫$xx'<Na? 9A+^BH,x:1$Sժ(8W,UȾ5Xvm#W(oggyϸvbʅHxllf9·#pI!O|XŁP ދ)T+ 9zBl ,p,]`&bE%$E4VYMI2`ȺX&W;7MW@/#y'>ϥ;$},$ʢnfs9ؿ'EcɁg]Ѕ8f# 1{㳇Y\-}!5-k'ԓcLq*qr}Sjl*䴮`%V 0DrM e8.MGkכ㒎ID~2!aٖզ!e-ri'5-xѪEuFhpzN}@mj{kaBF8tjWBG;n*&WYWK}`v/x.Am%3{'wJ~k k&qo%?<;].)mӺ1[F&4bvt,Cޑ7d~6 CMl G8cZ(ANQ´ `(xKXAO?dُ7aAW.;wR*JZU29{.~P(3Bi5-_x|i<ώmW~@zвolhzCoh^⣑[l *\<~Ķ;܆j"|LF_PDD.;0pM*py-wt'_Qd'=HEWTd"MUNY\OE2)%Xp}ke|%S#lUWd80i>L]RmQIJ\KeO&c?ꘌ2ǔ*@L; ԨIe(+G?:pVzo LZ `ܣcGBVY6 淽&0"0  *H 0 س+O)6_vKE_IϷMSgw 1f_FsĒ~8H*ƾ{1Lr/}xV沅0H%]qV'u | v҄k5te: DҜ̒ Z y\ӑ4IT\lpA[*:Y1t/;drwH7xEnP"85k''YfR8,-pN> ݊B?'7IԫceP";0B7{AH >WF,I#mg&yzxgq9/4P$WdFUY &1P48ʲiQ^V.$`K_ĎM.!_#Ü}R3x>[we6`:^}LB6y9 OK@9&]^9MQ$#׊Sq_7zaY2J\ɒ%%o_sU& pѕD<^ɻ9,&`]2{&bEjۆsJfsQ`4 ,=` G}2YBJpXM"Fo7eyC+KJM@+ RNiPȕs}ڕyq qJ88jny -nKSg~B(p/NϽBoc P~wX9j)mM=;BzFIW+d+Ϊv\&r曽Wr`FDE=:Lpc:{ZЬ˵>;MLȞA/T&mS.kT&J\I[M=iJh*Aߡ0 q3=JU"¨9ezi֚a@>DEX\8Wx V$h`)%ZT :ljxbD~:? @٥e17Io(E}L^b1j RM3֦_OpJtH ]\j9FԗLVHc v l}QS_t2#Z7̨0Ө:ĜW pm&q9䑢7k7_yێ'fE~۩E_g J  /;%-DafAi1{}Ah̪Ϧ X 6#O5OhxAk0Y#V/@q9 l![g1<^trnh ~7sVMhY7f&ZK}v qKIq8K%<|]gn%9֞z(ٸ9 iEqUQ6EH z{/VEU:e#M# ǛnAeU 0!APK Sig Block 42PK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPKX+src/test/resources/com/android/apksig/v2-only-with-rsa-pkcs1-sha256-2048-sig-does-not-verify.apk0100644 0000000 0000000 00000010046 13243353143 031032 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ  q{w9,( zd@GҦY}$5H{IJm\00 50l0  *H  010U rsa-20480 160331145749Z 430817145749Z010U rsa-20480"0  *H 0 5^0#YY2aD%],E sdM,T75P"¥TtImyjDn%X'J5" qrNsߧgBF_3.."/:GMH)x0(hC$nqJ;ߥ_'{7Wzq`haJh\xJb0N vt\%3Xw'ɦ"[z塌8б(!!,[P0N0U-s89j!Y,o0U#0-s89j!Y,o0 U00  *H  dKnBȓ@dyYNFsK'6 , $u*\I&}{y?0}xG)L` 2@*:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ  q{w9,( zd@GҦY}$5H{IJm\00 50l0  *H  010U rsa-20480 160331145749Z 430817145749Z010U rsa-20480"0  *H 0 5^0#YY2aD%],E sdM,T75P"¥TtImyjDn%X'J5" qrNsߧgBF_3.."/:GMH)x0(hC$nqJ;ߥ_'{7Wzq`haJh\xJb0N vt\%3Xw'ɦ"[z塌8б(!!,[P0N0U-s89j!Y,o0U#0-s89j!Y,o0 U00  *H  dKnBȓ@dyYNFsK'6 , $u*\I&}{y?0}xG)L` 2@*:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ  q{w9,( zd@GҦY}$5H{IJm\00a ga0  *H  010U rsa-30720 160331191458Z 430817191458Z010U rsa-307200  *H 0"Y3o%VRcs=|’Z,KxEK%eG~%&/'ŭ&S(URX6Wkǻ&es4eA˟pav_k漟ԉ [.%{ϟy(AHF%SN,V^U #w1 C;Ah ]CMue_MYc`4C7D$ `djO4c0\֤*'lOhs37nqej'ʦ-\M!gvDm*TٽC3Lr mEV$*x&3_fH,d#w[ Vȇ`)+l[oI~P0N0UOfql5ާS0U#0Ofql5ާS0 U00  *H  "UVkKefڻY,ZcS.Bê͐ŬpÅ_/6Nq `6u'KZz{ҍ*7B>(qDV_I]1TyώUQ|ʗ i}9HЍQo݃^n񀃯iǽM@P'mclpWX _C"֓cXQ)G@($'hL0* ƵnҾJ݂*uoj$gSyyҾ{wc޺yHec@Y{vk&kIq9g\9gp"T_,DG((Fl+-^XO.Xv®FUC'v~ 8VG8iJp,?&0'5FO8CZR8'zXЁ$VHd{zqfZp2kum[dAvH_Ntv\Gm*o:m@SG-z̤bX R$ M0urkHE}a7`Tbw_én2}ٺmY0tF:BlT..$:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ   q{ w 9,( zd@GҦY}$5H{IJm\00 q 0  *H  010U rsa-40960 160331152846Z 430817152846Z010U rsa-40960"0  *H 0 >;o'= YOAcCZ3r<\m6P 甚'o:JX`p7Ahذb0^tFp y^i^Y³803W ~ 8^]((=Ǚa#N Y)Ib0|2o|g'$}:8C#C1YIH45rd wc⨎.Hk]VSw?P?6N-)*)7Z}.T/kJ@LJhܠ\0i^^ptěeөӒ[;#$D&kX͗h) _z6#q߫!v+D\ꠁ /)$Bͳ"X$Kp[ A&0"0  *H 0 >;o'= YOAcCZ3r<\m6P 甚'o:JX`p7Ahذb0^tFp y^i^Y³803W ~ 8^]((=:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ  q{w9 ,( zd@GҦY}$5H{IJm\ 00 7%0  *H  010U rsa-81920 160404193246Z 430821193246Z010U rsa-81920"0  *H 0 =$֑Zpsq@`DG*Dz =u}tgk"мz_y=Atw2{įTE/NǝpU22U.+]㘿HoNlγbPcs6*~&tG!4Dy+ͩ7,X߬`w.ՍMdjRjCr O6v=||a `U.YAhCD%%sq>]\̖^)cvaw#jAǨtl\PQY̷8Wl"B=@t- \>wD’ouz;ub@z%ڹ2B.Fcڟr =3N!6ȨHv}}96RLZӭIN u~ (&DV_ c$C.̜,nd^' ARҶ `hwՔy Z^(^dT.hd,G A / BTZ=E%wqCJi_єSq䒙]M1 h8=@ʳrC(/\zצC8lڣwx<"ԱϾquN5՞|!˩|m#m .<1>7q:> (hL /P+lK0yڶMۅDO H瞻4<Ulxk;{Ꮿ>bOSS"}kٛm%{+TF)ku--' t6*Zx2I?*\BC|<Xl[S@m*#ѪRqJcP!cZ_7F r^Rth 6rِf(:89[WnP0N0U]FsR$2/~?0U#0]FsR$2/~?0 U00  *H  hU_##B)q q;)1eG+)9RN&~W$`LYR8n \WmӔCzMaOiڒnR FSfv1$>3{ >\~Z{ {J~nt |)`tڝs3)1 tZ֝d:J蕯WSЃ(Ѝ$#&Pt\X2DvGê9ǢcS6w-aDfBHjAn"1_M+%w|]/O߂D*%'Y T=1' L}eI odw g8NR)dlo6"h9 r\h/[5,dGzNS4]sr #F7ԯ7@'Zb8.;:!憌Q]3:+8>6~^ 1V̋4L"rzE1r.7Lv1x =(+1 Kc ~=]#pփ,sy MI3V7up3Hi׈ˊGJpY{ASm!ԍ7YlC{fo;[cG(Rk33eRۃuRߨcvH- i=\hC,*#n'cY"d46S pfy s݉'H?b;Pؠ.  )U)avggVo;d85qڸ /WHNNa4_?oC~ިmU8aKv'˾ɴ9ʸ$sO¾C{`P:l7CEcv*$fibxswڥ-%۽:M]\7!U笑ݖ/vJ6?C$:Bv,2 M,ȥa!b! c*6`4rwX w`ag F"%朂K_&ḭ{(spQ (MMc3À⒲އIܼ庩 ;%I=S3@a_pҰk7n('/^Vڝk;xuOw .hBo %.daaIG%Hmf^+ⶹ3  RJL yInk8=^mN޹1ɯ-W"?+ l|aӡ0 Wc0„0 {yjT]\̖^)cvaw#jAǨtl\PQY̷8Wl"B=@t- \>wD’ouz;ub@z%ڹ2B.Fcڟr =3N!6ȨHv}}96RLZӭIN u~ (&DV_ c$C.̜,nd^' ARҶ `hwՔy Z^(^dT.hd,G A / BTZ=E%wqCJi_єSq䒙]M1 h8=@ʳrC(/\zצC8lڣwx<"ԱϾquN5՞|!˩|m#m .<1>7q:> (hL /P+lK0yڶMۅDO H瞻4<Ulxk;{Ꮿ>bOSS"}kٛm%{+TF)ku--' t6*Zx2I?*\BC|<Xl[S@m*#ѪRqJcP!cZ_7F r^Rth 6rِf(:89[WnAPK Sig Block 42PK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPKVsrc/test/resources/com/android/apksig/v2-only-with-rsa-pkcs1-sha512-1024.apk0100644 0000000 0000000 00000007075 13243353143 025216 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ  qTLH@V8t6U ڇpag栓dA\^۰1 T Q5Wxr⻶y@00] 0  *H  010U rsa-10240 160331161443Z 430817161443Z010U rsa-102400  *H 0㏯> "c{/ ~WU_yAq{{j2'&QW=FS oꊻlp|_f!@o=1 ԷjL8l}  , 5)[.WG) ĊbP0N0UDYxgSoFĿy90U#0DYxgSoFĿy90 U00  *H  4ۀ o=Ms<lkIÍvU)?RON^c*r0 \*B\ڏ. HTfUDa~%j>_@ 8vJdV2.` ŖEaj4)p0?1/Gk1ro¾@ydp8qWJ%\qdmB2np4vɑG$@U'8 ǓX 00  *H 0㏯> "c{/ ~WU_yAq{{j2'&QW=FS oꊻlp|_f!@o=1 ԷjL8l}  , 5)[.WG) ĊbAPK Sig Block 42PK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPKm src/test/resources/com/android/apksig/v2-only-with-rsa-pkcs1-sha512-16384.apk0100644 0000000 0000000 00000026110 13243353143 025304 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ !! q!![LH@V8t6U ڇpag栓dA\^۰1 T Q5Wxr⻶y@00 wn0  *H  010U rsa-163840 160404193431Z 430821193431Z010U rsa-163840"0  *H 0 س+O)6_vKE_IϷMSgw 1f_FsĒ~8H*ƾ{1Lr/}xV沅0H%]qV'u | v҄k5te: DҜ̒ Z y\ӑ4IT\lpA[*:Y1t/;drwH7xEnP"85k''YfR8,-pN> ݊B?'7IԫceP";0B7{AH >WF,I#mg&yzxgq9/4P$WdFUY &1P48ʲiQ^V.$`K_ĎM.!_#Ü}R3x>[we6`:^}LB6y9 OK@9&]^9MQ$#׊Sq_7zaY2J\ɒ%%o_sU& pѕD<^ɻ9,&`]2{&bEjۆsJfsQ`4 ,=` G}2YBJpXM"Fo7eyC+KJM@+ RNiPȕs}ڕyq qJ88jny -nKSg~B(p/NϽBoc P~wX9j)mM=;BzFIW+d+Ϊv\&r曽Wr`FDE=:Lpc:{ZЬ˵>;MLȞA/T&mS.kT&J\I[M=iJh*Aߡ0 q3=JU"¨9ezi֚a@>DEX\8Wx V$h`)%ZT :ljxbD~:? @٥e17Io(E}L^b1j RM3֦_OpJtH ]\j9FԗLVHc v l}QS_t2#Z7̨0Ө:ĜW pm&q9䑢7k7_yێ'fE~۩E_g J  /;%-DafAi1{}Ah̪Ϧ X 6#O5OhxAk0Y#V/@q9 l![g1<^trnh ~7sVMhY7f&ZK}v qKIq8K%<|]gn%9֞z(ٸ9 iEqUQ6EH z{/VEU:e#M# ǛnAeU 0P0N0U$p_DLs"0S,0U#0$p_DLs"0S,0 U00  *H  G;b$ ENBlWogp/6@HI/ z⾤$* !XxIP<O܆"hmYOg}]ߑ"&$=#e}' hYfy(P{f//s~%ydbqm 74*~N jKmdppdqGF>K*4tORvcm:S[{b>m}+L݈9On;K&fY#,rst^'%hL0@BMQ1Ce9."pnuJIi;9>d"ZZ4L-Y$IQ0{Dky!,Sv>?S)҄ܧ xktHAi(Wc* 5sҿhdf;nUaGJ^TCnLNswGn>ӕWݭ5a=S6գf)_Wr-0͛`RC5/.x$5["=r7P;E3[\bce !pY =iC`yΟK${L%di/]rBqBid.C$L&vW?|ךr|pzs62}PƓ6@GyM?P'O;]ۻykQ3#_Un3;̓G}5 +9f̸2n,sɽ)WJV{.Rnx~zBћ*J-׶<ʆO6&%T nrӘ@>u, " z$ ɻk"hVߜXnM*3f"[*=u(/ Mʹ 7!]`Hjx ༫$xx'<Na? 9A+^BH,x:1$3}v?īu`EydP*.i|VٖF(IoAs c4?\oh+ϺT;M '{ھXG_ͱK{ MY4%B_BPt=Mk;P ;I;iW-\Oy.׶(] sW,+nL>D߬9Z;{J"cbJvGJ]:ceC8q(>Ž@"IWڦxi' N_ڤ?zj>.ח9pvi1C=XЪP+%0Me$`cX-bZ_MUQK138! R[z/ 3';,r#n`%4@Rfp>-#Q~O%S۲/}Ծ t-;tX<$#7ѽ*qHȈY;e’8K'5KH21톶cڝrz`$Lrh8X9aP ymO)Ep]ȸc\BXC{S*`;NC8,hyj=!w5FYgWn1{\1`:fm,q _{ʘ8Ik8$9?HT=^4G4W%j#(}r<iL vjse4M ?t dTebF4ޭ+ZƾCES +UzL߀qfᇍc%({N@ɸmAGEOYXbЯxo$ qY|スr?5aR`yPcURt0$\obnJPfh6FE|^!I :27p#@)]"D3@wA\fgq+k~a^)ͨqϝҪXmET~v?8i/C=ٟN$ZwUu^uy, eD!.GU*ơ,=俬 1pNDჸŔtC ꞈ]C޲]p9V=tՙ`rG`iQj"Ev2jTYĐ5PjY V"n}:`}92n 284GUu$o]C 5Jpi'[kVDY-L?1"Yxl ge; 8KH.Og c"#YK mga xıOQYfCd*A.4/.z6K'Q}E&0"0  *H 0 س+O)6_vKE_IϷMSgw 1f_FsĒ~8H*ƾ{1Lr/}xV沅0H%]qV'u | v҄k5te: DҜ̒ Z y\ӑ4IT\lpA[*:Y1t/;drwH7xEnP"85k''YfR8,-pN> ݊B?'7IԫceP";0B7{AH >WF,I#mg&yzxgq9/4P$WdFUY &1P48ʲiQ^V.$`K_ĎM.!_#Ü}R3x>[we6`:^}LB6y9 OK@9&]^9MQ$#׊Sq_7zaY2J\ɒ%%o_sU& pѕD<^ɻ9,&`]2{&bEjۆsJfsQ`4 ,=` G}2YBJpXM"Fo7eyC+KJM@+ RNiPȕs}ڕyq qJ88jny -nKSg~B(p/NϽBoc P~wX9j)mM=;BzFIW+d+Ϊv\&r曽Wr`FDE=:Lpc:{ZЬ˵>;MLȞA/T&mS.kT&J\I[M=iJh*Aߡ0 q3=JU"¨9ezi֚a@>DEX\8Wx V$h`)%ZT :ljxbD~:? @٥e17Io(E}L^b1j RM3֦_OpJtH ]\j9FԗLVHc v l}QS_t2#Z7̨0Ө:ĜW pm&q9䑢7k7_yێ'fE~۩E_g J  /;%-DafAi1{}Ah̪Ϧ X 6#O5OhxAk0Y#V/@q9 l![g1<^trnh ~7sVMhY7f&ZK}v qKIq8K%<|]gn%9֞z(ٸ9 iEqUQ6EH z{/VEU:e#M# ǛnAeU 0!APK Sig Block 42PK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPKx+src/test/resources/com/android/apksig/v2-only-with-rsa-pkcs1-sha512-2048.apk0100644 0000000 0000000 00000010106 13243353143 025212 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ  qYLH@V8t6U ڇpag栓dA\^۰1 T Q5Wxr⻶y@00 50l0  *H  010U rsa-20480 160331145749Z 430817145749Z010U rsa-20480"0  *H 0 5^0#YY2aD%],E sdM,T75P"¥TtImyjDn%X'J5" qrNsߧgBF_3.."/:GMH)x0(hC$nqJ;ߥ_'{7Wzq`haJh\xJb0N vt\%3Xw'ɦ"[z塌8б(!!,[P0N0U-s89j!Y,o0U#0-s89j!Y,o0 U00  *H  dKnBȓ@dyYNFsK'6 , $u*\I&}{y?0}xG)L` 2@*kC l .:waӻZ*Pcz9߱2VŦGEƳqy2LiPJ fsb嚹f:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ  qYLH@V8t6U ڇpag栓dA\^۰1 T Q5Wxr⻶y@00a ga0  *H  010U rsa-30720 160331191458Z 430817191458Z010U rsa-307200  *H 0"Y3o%VRcs=|’Z,KxEK%eG~%&/'ŭ&S(URX6Wkǻ&es4eA˟pav_k漟ԉ [.%{ϟy(AHF%SN,V^U #w1 C;Ah ]CMue_MYc`4C7D$ `djO4c0\֤*'lOhs37nqej'ʦ-\M!gvDm*TٽC3Lr mEV$*x&3_fH,d#w[ Vȇ`)+l[oI~P0N0UOfql5ާS0U#0Ofql5ާS0 U00  *H  "UVkKefڻY,ZcS.Bê͐ŬpÅ_/6Nq `6u'KZz{ҍ*7B>(qDV_I]1TyώUQ|ʗ i}9HЍQo݃^n񀃯iǽM@P'mclpWX _C"֓cXQ)G@($'hL0* ƵnҾJ݂*uoj$gSyyҾ{wc޺yHec@Y{vk&kIq9g\9gp"T_,DG((Fl+-^XO.Xv®FUC'v~ Ha+B,/4{݌ۯ׶F( Z䦷JrD$!}u k/\)2 ތ-k*x#/1?jcrj0,73ec4<& Զ (5wNKqqF6g^Vg7~ʚ_aqr?RdXG՚0XLQF %n)6BSw_Fyk(0Y \SߠmRvRQC#G ѻ-Yz`"$T[B<&ѓ-]* *1#8_}z`);?s*!6,dHrU&a00  *H 0"Y3o%VRcs=|’Z,KxEK%eG~%&/'ŭ&S(URX6Wkǻ&es4eA˟pav_k漟ԉ [.%{ϟy(AHF%SN,V^U #w1 C;Ah ]CMue_MYc`4C7D$ `djO4c0\֤*'lOhs37nqej'ʦ-\M!gvDm*TٽC3Lr mEV$*x&3_fH,d#w[ Vȇ`)+l[oI~APK Sig Block 42PK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPKvsrc/test/resources/com/android/apksig/v2-only-with-rsa-pkcs1-sha512-4096-digest-mismatch.apk0100644 0000000 0000000 00000012106 13243353143 030301 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ   q YLH@^V8t6U ڇpag栓dA\^۰1 T Q5Wxr⻶y@00 q 0  *H  010U rsa-40960 160331152846Z 430817152846Z010U rsa-40960"0  *H 0 >;o'= YOAcCZ3r<\m6P 甚'o:JX`p7Ahذb0^tFp y^i^Y³803W ~ 8^]((=}㽑oϒẒlj}V[JI#nS02i=Qy3Ɂ؆er PuI?sNΣ+!_vʷPA:7s# tܯ˘DL"A=C<d9s  }r"^}yjU[wZ&0"0  *H 0 >;o'= YOAcCZ3r<\m6P 甚'o:JX`p7Ahذb0^tFp y^i^Y³803W ~ 8^]((=:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ   q YLH@V8t6U ڇpag栓dA\^۰1 T Q5Wxr⻶y@00 q 0  *H  010U rsa-40960 160331152846Z 430817152846Z010U rsa-40960"0  *H 0 >;o'= YOAcCZ3r<\m6P 甚'o:JX`p7Ahذb0^tFp y^i^Y³803W ~ 8^]((=XIdF[M %:$K`At W{Ӗ&0"0  *H 0 >;o'= YOAcCZ3r<\m6P 甚'o:JX`p7Ahذb0^tFp y^i^Y³803W ~ 8^]((=:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ  qY LH@V8t6U ڇpag栓dA\^۰1 T Q5Wxr⻶y@ 00 7%0  *H  010U rsa-81920 160404193246Z 430821193246Z010U rsa-81920"0  *H 0 =$֑Zpsq@`DG*Dz =u}tgk"мz_y=Atw2{įTE/NǝpU22U.+]㘿HoNlγbPcs6*~&tG!4Dy+ͩ7,X߬`w.ՍMdjRjCr O6v=||a `U.YAhCD%%sq>]\̖^)cvaw#jAǨtl\PQY̷8Wl"B=@t- \>wD’ouz;ub@z%ڹ2B.Fcڟr =3N!6ȨHv}}96RLZӭIN u~ (&DV_ c$C.̜,nd^' ARҶ `hwՔy Z^(^dT.hd,G A / BTZ=E%wqCJi_єSq䒙]M1 h8=@ʳrC(/\zצC8lڣwx<"ԱϾquN5՞|!˩|m#m .<1>7q:> (hL /P+lK0yڶMۅDO H瞻4<Ulxk;{Ꮿ>bOSS"}kٛm%{+TF)ku--' t6*Zx2I?*\BC|<Xl[S@m*#ѪRqJcP!cZ_7F r^Rth 6rِf(:89[WnP0N0U]FsR$2/~?0U#0]FsR$2/~?0 U00  *H  hU_##B)q q;)1eG+)9RN&~W$`LYR8n \WmӔCzMaOiڒnR FSfv1$>3{ >\~Z{ {J~nt |)`tڝs3)1 tZ֝d:J蕯WSЃ(Ѝ$#&Pt\X2DvGê9ǢcS6w-aDfBHjAn"1_M+%w|]/O߂D*%'Y T=1' L}eI odw g8NR)dlo6"h9 r\h/[5,dGzNS4]sr #F7ԯ7@'Zb8.;:!憌Q]3:+8>6~^ 1V̋4L"rzE1r.7Lv1x =(+1 Kc ~=]#pփ,sy MI3V7up3Hi׈ˊGJpY{ASm!ԍ7YlC{fo;[cG(Rk33eRۃuRߨcvH- i=\hC,*#n'cY"d46S pfy s݉'H?b;Pؠ.  )U)avggVo;d85qڸ /WHNNa4_?oC~ިmU8aKv'˾ɴ9ʸ$sO¾C{`P:l7CEcv*$fibxswڥ-%۽:M]\7!U笑ݖ/vJ6?C$:Bv,2 M,ȥa!b! c*6`4rwX 8dyfc(|I6nF.P/xW[E fqq81bWCL/>|54Q[ x&_\ؒEww- u_*s оg{vwy(JzURXOuӽ4Ei|ND~1#"wjA%i_Ry- BV.}vm`kG4 }M$ wu2l+:bm bLJ$bȇg0Hf@VT @sD\LY=tNtGwk(a.6nmДym`cSC/>v TOOmکQ>6B5k|*p~'d3VҦtXPAʈM;9%!HMM;9ِE2b?V a8]yσ:޺l3";WGtk s]-VM6OдQZka)n}+')g0#cIhh!ʛ.|}cgZYLuBnRt6L䚭 K 6J/ !@HA@).WEiI' h|G^`s-6 -@Ohy",CS{iodBl'TU3T$.DC ~B@́H.-oѰU2AS'kw+V ,Er hbdH#s⻝+񀧘6Sdy-҉/C|=Z4 ɡ#~{]t@8fIF g~xRV<&0"0  *H 0 =$֑Zpsq@`DG*Dz =u}tgk"мz_y=Atw2{įTE/NǝpU22U.+]㘿HoNlγbPcs6*~&tG!4Dy+ͩ7,X߬`w.ՍMdjRjCr O6v=||a `U.YAhCD%%sq>]\̖^)cvaw#jAǨtl\PQY̷8Wl"B=@t- \>wD’ouz;ub@z%ڹ2B.Fcڟr =3N!6ȨHv}}96RLZӭIN u~ (&DV_ c$C.̜,nd^' ARҶ `hwՔy Z^(^dT.hd,G A / BTZ=E%wqCJi_єSq䒙]M1 h8=@ʳrC(/\zצC8lڣwx<"ԱϾquN5՞|!˩|m#m .<1>7q:> (hL /P+lK0yڶMۅDO H瞻4<Ulxk;{Ꮿ>bOSS"}kٛm%{+TF)ku--' t6*Zx2I?*\BC|<Xl[S@m*#ѪRqJcP!cZ_7F r^Rth 6rِf(:89[WnAPK Sig Block 42PK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPKvsrc/test/resources/com/android/apksig/v2-only-with-rsa-pss-sha256-1024.apk0100644 0000000 0000000 00000007035 13243353143 025003 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ z qrn4,( zd@GҦY}$5H{IJm\00] 0  *H  010U rsa-10240 160331161443Z 430817161443Z010U rsa-102400  *H 0㏯> "c{/ ~WU_yAq{{j2'&QW=FS oꊻlp|_f!@o=1 ԷjL8l}  , 5)[.WG) ĊbP0N0UDYxgSoFĿy90U#0DYxgSoFĿy90 U00  *H  4ۀ o=Ms<lkIÍvU)?RON^c*r0 \*B\ڏ. HTfUDa~%j>_@ 8vJdV2.zsV Pn#5|qԇi$5W Iڄ}x,}R%B`nKYw[E ʞ9Ð'J`>kg] >Uu~a!ZzRH1N[00  *H 0㏯> "c{/ ~WU_yAq{{j2'&QW=FS oꊻlp|_f!@o=1 ԷjL8l}  , 5)[.WG) ĊbAPK Sig Block 42PK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPKM src/test/resources/com/android/apksig/v2-only-with-rsa-pss-sha256-16384.apk0100644 0000000 0000000 00000026050 13243353143 025100 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ !! q}!y!;,( zd@GҦY}$5H{IJm\00 wn0  *H  010U rsa-163840 160404193431Z 430821193431Z010U rsa-163840"0  *H 0 س+O)6_vKE_IϷMSgw 1f_FsĒ~8H*ƾ{1Lr/}xV沅0H%]qV'u | v҄k5te: DҜ̒ Z y\ӑ4IT\lpA[*:Y1t/;drwH7xEnP"85k''YfR8,-pN> ݊B?'7IԫceP";0B7{AH >WF,I#mg&yzxgq9/4P$WdFUY &1P48ʲiQ^V.$`K_ĎM.!_#Ü}R3x>[we6`:^}LB6y9 OK@9&]^9MQ$#׊Sq_7zaY2J\ɒ%%o_sU& pѕD<^ɻ9,&`]2{&bEjۆsJfsQ`4 ,=` G}2YBJpXM"Fo7eyC+KJM@+ RNiPȕs}ڕyq qJ88jny -nKSg~B(p/NϽBoc P~wX9j)mM=;BzFIW+d+Ϊv\&r曽Wr`FDE=:Lpc:{ZЬ˵>;MLȞA/T&mS.kT&J\I[M=iJh*Aߡ0 q3=JU"¨9ezi֚a@>DEX\8Wx V$h`)%ZT :ljxbD~:? @٥e17Io(E}L^b1j RM3֦_OpJtH ]\j9FԗLVHc v l}QS_t2#Z7̨0Ө:ĜW pm&q9䑢7k7_yێ'fE~۩E_g J  /;%-DafAi1{}Ah̪Ϧ X 6#O5OhxAk0Y#V/@q9 l![g1<^trnh ~7sVMhY7f&ZK}v qKIq8K%<|]gn%9֞z(ٸ9 iEqUQ6EH z{/VEU:e#M# ǛnAeU 0P0N0U$p_DLs"0S,0U#0$p_DLs"0S,0 U00  *H  G;b$ ENBlWogp/6@HI/ z⾤$* !XxIP<O܆"hmYOg}]ߑ"&$=#e}' hYfy(P{f//s~%ydbqm 74*~N jKmdppdqGF>K*4tORvcm:S[{b>m}+L݈9On;K&fY#,rst^'%hL0@BMQ1Ce9."pnuJIi;9>d"ZZ4L-Y$IQ0{Dky!,Sv>?S)҄ܧ xktHAi(Wc* 5sҿhdf;nUaGJ^TCnLNswGn>ӕWݭ5a=S6գf)_Wr-0͛`RC5/.x$5["=r7P;E3[\bce !pY =iC`yΟK${L%di/]rBqBid.C$L&vW?|ךr|pzs62}PƓ6@GyM?P'O;]ۻykQ3#_Un3;̓G}5 +9f̸2n,sɽ)WJV{.Rnx~zBћ*J-׶<ʆO6&%T nrӘ@>u, " z$ ɻk"hVߜXnM*3f"[*=u(/ Mʹ 7!]`Hjx ༫$xx'<Na? 9A+^BH,x:1$y߽G) sJ~F 14oE^0EܚVH,u Y.`&%ɕ^w=AjieÇw^6]S@ v%xwR,g݋z9k${$e,ˈ,}?t_&>g#"DXwBuQ*M ?. O*j~VzYM0e\b`A VMHd4)-nTȭ@ ՛+,,z4rDA1ARͤIz^_w7.5JN2@ $Sqt4ǰ]S'_8Q~#`Lj891.{;Aݭpm&zA%O hD:"sM申r]:vQj|#SIҌtᜩ ExmQxn=SWm 2VaBqo|zKN&cp$˿—H/Q[*nRܓ$̩Mʥ *oiSzݳd&57 =;UmO@(Œށ=ȶ:%^rM N$bk d;9+ RzYdQkMb13oX/ی^y^< FDnX*2D0`A)dUްA*Չ./07˴dYVϦ/Q8K֑A"N^ODZuk_, jM|& Xk uw䈕-3Φ RD GQYy&n4"7%NaVYNSBgq>˨R]lXW,T8a h D*wzœFJqIJ ̦H(׿U{PK`PUxo|Yi]4zC4He>1M" FgT)D}j$=nF6/xltB?ӺWSB}@_AIafV%jѩbHh&&3H )JAl):5p7KCH Ș[(Գtl0>(8BF/Z> %Šh[!( _ ?a/|}=<Vud]\ijuJ_9iݎƲ)HOf0/oEHbQYZƒGBZﶀŸoF!FZK@2)x$bB`44^u}tϨ`{`-Cn5S_J^DvHQ&] >`kwIp _X:_ kЅ6C6Qa[婤hBaeϞja &bZ>"4**LW+ÙFc_u}s<0z!AZV2jf4CB >~${KD浖YSjxgc$#>iuMZ a)MKN~1S[odžd#kv8[,.u vRH.Z.Je~~;@` !;<*bQ?5X7FiW~wfo*,IHXg_R,r—>1@M3V9rE,lqSp pA&JQ;-Mt\λO'M]ta^`Clg^Q;NgJ)$d\sZؾp]G$19.78fS'1L _o{xRo PCk-A/-5ZxC}}H8ɠMOgk2mts:Q& \kic2ZW$eb. )$Ύeۇ~,Zi&0"0  *H 0 س+O)6_vKE_IϷMSgw 1f_FsĒ~8H*ƾ{1Lr/}xV沅0H%]qV'u | v҄k5te: DҜ̒ Z y\ӑ4IT\lpA[*:Y1t/;drwH7xEnP"85k''YfR8,-pN> ݊B?'7IԫceP";0B7{AH >WF,I#mg&yzxgq9/4P$WdFUY &1P48ʲiQ^V.$`K_ĎM.!_#Ü}R3x>[we6`:^}LB6y9 OK@9&]^9MQ$#׊Sq_7zaY2J\ɒ%%o_sU& pѕD<^ɻ9,&`]2{&bEjۆsJfsQ`4 ,=` G}2YBJpXM"Fo7eyC+KJM@+ RNiPȕs}ڕyq qJ88jny -nKSg~B(p/NϽBoc P~wX9j)mM=;BzFIW+d+Ϊv\&r曽Wr`FDE=:Lpc:{ZЬ˵>;MLȞA/T&mS.kT&J\I[M=iJh*Aߡ0 q3=JU"¨9ezi֚a@>DEX\8Wx V$h`)%ZT :ljxbD~:? @٥e17Io(E}L^b1j RM3֦_OpJtH ]\j9FԗLVHc v l}QS_t2#Z7̨0Ө:ĜW pm&q9䑢7k7_yێ'fE~۩E_g J  /;%-DafAi1{}Ah̪Ϧ X 6#O5OhxAk0Y#V/@q9 l![g1<^trnh ~7sVMhY7f&ZK}v qKIq8K%<|]gn%9֞z(ٸ9 iEqUQ6EH z{/VEU:e#M# ǛnAeU 0!APK Sig Block 42PK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPKX+src/test/resources/com/android/apksig/v2-only-with-rsa-pss-sha256-2048-sig-does-not-verify.apk0100644 0000000 0000000 00000010046 13243353143 030616 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ  q{w9,( zd@GҦY}$5H{IJm\00 50l0  *H  010U rsa-20480 160331145749Z 430817145749Z010U rsa-20480"0  *H 0 5^0#YY2aD%],E sdM,T75P"¥TtImyjDn%X'J5" qrNsߧgBF_3.."/:GMH)x0(hC$nqJ;ߥ_'{7Wzq`haJh\xJb0N vt\%3Xw'ɦ"[z塌8б(!!,[P0N0U-s89j!Y,o0U#0-s89j!Y,o0 U00  *H  dKnBȓ@dyYNFsK'6 , $u*\I&}{y?0}xG)L` 2@*:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ  q{w9,( zd@GҦY}$5H{IJm\00 50l0  *H  010U rsa-20480 160331145749Z 430817145749Z010U rsa-20480"0  *H 0 5^0#YY2aD%],E sdM,T75P"¥TtImyjDn%X'J5" qrNsߧgBF_3.."/:GMH)x0(hC$nqJ;ߥ_'{7Wzq`haJh\xJb0N vt\%3Xw'ɦ"[z塌8б(!!,[P0N0U-s89j!Y,o0U#0-s89j!Y,o0 U00  *H  dKnBȓ@dyYNFsK'6 , $u*\I&}{y?0}xG)L` 2@*{)z$p9]͇Ҏ.z3 [vjj)E^ӄ "y฻kYRM+p3Gn݆Vt̷9 N <=3 66#yQ:*مC%09`_l&0"0  *H 0 5^0#YY2aD%],E sdM,T75P"¥TtImyjDn%X'J5" qrNsߧgBF_3.."/:GMH)x0(hC$nqJ;ߥ_'{7Wzq`haJh\xJb0N vt\%3Xw'ɦ"[z塌8б(!!,[APK Sig Block 42PK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPKVsrc/test/resources/com/android/apksig/v2-only-with-rsa-pss-sha256-3072.apk0100644 0000000 0000000 00000011046 13243353143 025005 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ  q{w9,( zd@GҦY}$5H{IJm\00a ga0  *H  010U rsa-30720 160331191458Z 430817191458Z010U rsa-307200  *H 0"Y3o%VRcs=|’Z,KxEK%eG~%&/'ŭ&S(URX6Wkǻ&es4eA˟pav_k漟ԉ [.%{ϟy(AHF%SN,V^U #w1 C;Ah ]CMue_MYc`4C7D$ `djO4c0\֤*'lOhs37nqej'ʦ-\M!gvDm*TٽC3Lr mEV$*x&3_fH,d#w[ Vȇ`)+l[oI~P0N0UOfql5ާS0U#0Ofql5ާS0 U00  *H  "UVkKefڻY,ZcS.Bê͐ŬpÅ_/6Nq `6u'KZz{ҍ*7B>(qDV_I]1TyώUQ|ʗ i}9HЍQo݃^n񀃯iǽM@P'mclpWX _C"֓cXQ)G@($'hL0* ƵnҾJ݂*uoj$gSyyҾ{wc޺yHec@Y{vk&kIq9g\9gp"T_,DG((Fl+-^XO.Xv®FUC'v~ ~ npyR(O +u Mvo>HA #c9]$D(n֩EEH!,.c%buڹ/~+\J{Ѯb1gw=1ɞ&iڙ @~ ?ZhQ.U\ ʑCWs_sl]q١jd"* 3`&FmO3Dh}^d2XkE0Cm:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ   q{ w 9,( zd@GҦY}$5H{IJm\00 q 0  *H  010U rsa-40960 160331152846Z 430817152846Z010U rsa-40960"0  *H 0 >;o'= YOAcCZ3r<\m6P 甚'o:JX`p7Ahذb0^tFp y^i^Y³803W ~ 8^]((=߱QAa&v7Q뽣dp<zlA~cij쬞W%7P P< ᭂ{bli 2FPG#4͒Jp3SDrEO_0Z{ZXePْ! ;/{FQ>%=Ѽ1R<ʞ$Ԟ" ^gJ% c<컛܊$Wӯyd?~8`rMV*eT%pCS\ߖBWxI$\]I56P;o'= YOAcCZ3r<\m6P 甚'o:JX`p7Ahذb0^tFp y^i^Y³803W ~ 8^]((=:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ  q{w9 ,( zd@GҦY}$5H{IJm\ 00 7%0  *H  010U rsa-81920 160404193246Z 430821193246Z010U rsa-81920"0  *H 0 =$֑Zpsq@`DG*Dz =u}tgk"мz_y=Atw2{įTE/NǝpU22U.+]㘿HoNlγbPcs6*~&tG!4Dy+ͩ7,X߬`w.ՍMdjRjCr O6v=||a `U.YAhCD%%sq>]\̖^)cvaw#jAǨtl\PQY̷8Wl"B=@t- \>wD’ouz;ub@z%ڹ2B.Fcڟr =3N!6ȨHv}}96RLZӭIN u~ (&DV_ c$C.̜,nd^' ARҶ `hwՔy Z^(^dT.hd,G A / BTZ=E%wqCJi_єSq䒙]M1 h8=@ʳrC(/\zצC8lڣwx<"ԱϾquN5՞|!˩|m#m .<1>7q:> (hL /P+lK0yڶMۅDO H瞻4<Ulxk;{Ꮿ>bOSS"}kٛm%{+TF)ku--' t6*Zx2I?*\BC|<Xl[S@m*#ѪRqJcP!cZ_7F r^Rth 6rِf(:89[WnP0N0U]FsR$2/~?0U#0]FsR$2/~?0 U00  *H  hU_##B)q q;)1eG+)9RN&~W$`LYR8n \WmӔCzMaOiڒnR FSfv1$>3{ >\~Z{ {J~nt |)`tڝs3)1 tZ֝d:J蕯WSЃ(Ѝ$#&Pt\X2DvGê9ǢcS6w-aDfBHjAn"1_M+%w|]/O߂D*%'Y T=1' L}eI odw g8NR)dlo6"h9 r\h/[5,dGzNS4]sr #F7ԯ7@'Zb8.;:!憌Q]3:+8>6~^ 1V̋4L"rzE1r.7Lv1x =(+1 Kc ~=]#pփ,sy MI3V7up3Hi׈ˊGJpY{ASm!ԍ7YlC{fo;[cG(Rk33eRۃuRߨcvH- i=\hC,*#n'cY"d46S pfy s݉'H?b;Pؠ.  )U)avggVo;d85qڸ /WHNNa4_?oC~ިmU8aKv'˾ɴ9ʸ$sO¾C{`P:l7CEcv*$fibxswڥ-%۽:M]\7!U笑ݖ/vJ6?C$:Bv,2 M,ȥa!b! c*6`4rwX @֮#+E~ppυ5WFx)E+w>r?&hY)b% sgoG8%v*>lq0@+,Ô{RZRmfj ]nni"iwow:RR -Oā|@v^PS/2z?/Dn]V$ [߁@n{I/C? mZwHÍcjcPQaV>ʆ%j/DiWHw|IS< =Iꌘ< C;]*d3uMnElstv*m<4*au2Lqհ3zjpK[9g!ߴ(bvV$L!=щy8gNU' jy)XbS85a]vfG?@>sZQh٩|#(6KPs]Ǒoz큢3\pp=4S3p >N}"~d( 6جMU2#i`[<.__K7{!@֘>QCcly42#`~*=!wB#B>J*]ŧ[]ĉq?nJV<;w(Of"޵5«;ʻF%.woz?1Il?”Et=Ʀ+#Qy*N:h= ʨ9q-sVUN~QGT٢ J_K :PvO 5?WMXx|KJn-[\C>LA>wûE[/1OҭolQNe}wKڬܶf>wݎ%uaPר *Mc$]\̖^)cvaw#jAǨtl\PQY̷8Wl"B=@t- \>wD’ouz;ub@z%ڹ2B.Fcڟr =3N!6ȨHv}}96RLZӭIN u~ (&DV_ c$C.̜,nd^' ARҶ `hwՔy Z^(^dT.hd,G A / BTZ=E%wqCJi_єSq䒙]M1 h8=@ʳrC(/\zצC8lڣwx<"ԱϾquN5՞|!˩|m#m .<1>7q:> (hL /P+lK0yڶMۅDO H瞻4<Ulxk;{Ꮿ>bOSS"}kٛm%{+TF)ku--' t6*Zx2I?*\BC|<Xl[S@m*#ѪRqJcP!cZ_7F r^Rth 6rِf(:89[WnAPK Sig Block 42PK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPKVsrc/test/resources/com/android/apksig/v2-only-with-rsa-pss-sha512-16384.apk0100644 0000000 0000000 00000026110 13243353143 025070 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ !! q!![LH@V8t6U ڇpag栓dA\^۰1 T Q5Wxr⻶y@00 wn0  *H  010U rsa-163840 160404193431Z 430821193431Z010U rsa-163840"0  *H 0 س+O)6_vKE_IϷMSgw 1f_FsĒ~8H*ƾ{1Lr/}xV沅0H%]qV'u | v҄k5te: DҜ̒ Z y\ӑ4IT\lpA[*:Y1t/;drwH7xEnP"85k''YfR8,-pN> ݊B?'7IԫceP";0B7{AH >WF,I#mg&yzxgq9/4P$WdFUY &1P48ʲiQ^V.$`K_ĎM.!_#Ü}R3x>[we6`:^}LB6y9 OK@9&]^9MQ$#׊Sq_7zaY2J\ɒ%%o_sU& pѕD<^ɻ9,&`]2{&bEjۆsJfsQ`4 ,=` G}2YBJpXM"Fo7eyC+KJM@+ RNiPȕs}ڕyq qJ88jny -nKSg~B(p/NϽBoc P~wX9j)mM=;BzFIW+d+Ϊv\&r曽Wr`FDE=:Lpc:{ZЬ˵>;MLȞA/T&mS.kT&J\I[M=iJh*Aߡ0 q3=JU"¨9ezi֚a@>DEX\8Wx V$h`)%ZT :ljxbD~:? @٥e17Io(E}L^b1j RM3֦_OpJtH ]\j9FԗLVHc v l}QS_t2#Z7̨0Ө:ĜW pm&q9䑢7k7_yێ'fE~۩E_g J  /;%-DafAi1{}Ah̪Ϧ X 6#O5OhxAk0Y#V/@q9 l![g1<^trnh ~7sVMhY7f&ZK}v qKIq8K%<|]gn%9֞z(ٸ9 iEqUQ6EH z{/VEU:e#M# ǛnAeU 0P0N0U$p_DLs"0S,0U#0$p_DLs"0S,0 U00  *H  G;b$ ENBlWogp/6@HI/ z⾤$* !XxIP<O܆"hmYOg}]ߑ"&$=#e}' hYfy(P{f//s~%ydbqm 74*~N jKmdppdqGF>K*4tORvcm:S[{b>m}+L݈9On;K&fY#,rst^'%hL0@BMQ1Ce9."pnuJIi;9>d"ZZ4L-Y$IQ0{Dky!,Sv>?S)҄ܧ xktHAi(Wc* 5sҿhdf;nUaGJ^TCnLNswGn>ӕWݭ5a=S6գf)_Wr-0͛`RC5/.x$5["=r7P;E3[\bce !pY =iC`yΟK${L%di/]rBqBid.C$L&vW?|ךr|pzs62}PƓ6@GyM?P'O;]ۻykQ3#_Un3;̓G}5 +9f̸2n,sɽ)WJV{.Rnx~zBћ*J-׶<ʆO6&%T nrӘ@>u, " z$ ɻk"hVߜXnM*3f"[*=u(/ Mʹ 7!]`Hjx ༫$xx'<Na? 9A+^BH,x:1$m䟩5qu&@; {:@?od V2m ,sP1ˋJnb ֱ I2f'<]y| Ȑй6o`\_b/a(D|L5?ѿ)g مvۡmUx9C-?ZA oVr+mjOUie ) /gMlk ̼Mj+0`<֟xaNY$WXKf%2JKv4 q`*9̑̚p`0ac@0$[輻m,f$m ˤFYɷ>@ -BD&5/>bWZt_ϜDEs,?!ћ_M~PP5(WZPITlS@M-R~zC&ZTcԴ޳]ktjT!0zE(-Bӫw $/6mG2.hfcm#C3,Q-}:箿.G~{3oȬr~RgѮ0YQ (KaLpvA'75aps1/ r5vgD~TMF.ҮZŭETM| Yit|HAC6{{8H!觶n,kHKY.d^uҴ\pQ]~ʗٓ /)؈]4(5'S!&B.գw|†Ғ4+y }l֭Ǥ1& "K,eӄ^"<^_OŊ.췔r A):7oBAL7rp0nu[I KKWTǖg8-&;ùb` v3к}s+to_1 G\l?l ͿY(hdYi2ͮD{s}^K_|8WaQ<ڸxK"9I{URw“*{Y̓ŧ0żRnu1x}4fVCd~pR (}w޴F Lh?'bƿσefFKt/ڴ~]urQYI>1we`HѲG[;N{?QRY&Sޣnm1bR/&R[ %)x'kv 7.pqo~RxݞqMqwPk $cq'zu`|,|zQm<58$WFF`֤aRTkq)~t1z`# u$}6E2E=+%Ȟhڬ< ,vVl*&0"0  *H 0 س+O)6_vKE_IϷMSgw 1f_FsĒ~8H*ƾ{1Lr/}xV沅0H%]qV'u | v҄k5te: DҜ̒ Z y\ӑ4IT\lpA[*:Y1t/;drwH7xEnP"85k''YfR8,-pN> ݊B?'7IԫceP";0B7{AH >WF,I#mg&yzxgq9/4P$WdFUY &1P48ʲiQ^V.$`K_ĎM.!_#Ü}R3x>[we6`:^}LB6y9 OK@9&]^9MQ$#׊Sq_7zaY2J\ɒ%%o_sU& pѕD<^ɻ9,&`]2{&bEjۆsJfsQ`4 ,=` G}2YBJpXM"Fo7eyC+KJM@+ RNiPȕs}ڕyq qJ88jny -nKSg~B(p/NϽBoc P~wX9j)mM=;BzFIW+d+Ϊv\&r曽Wr`FDE=:Lpc:{ZЬ˵>;MLȞA/T&mS.kT&J\I[M=iJh*Aߡ0 q3=JU"¨9ezi֚a@>DEX\8Wx V$h`)%ZT :ljxbD~:? @٥e17Io(E}L^b1j RM3֦_OpJtH ]\j9FԗLVHc v l}QS_t2#Z7̨0Ө:ĜW pm&q9䑢7k7_yێ'fE~۩E_g J  /;%-DafAi1{}Ah̪Ϧ X 6#O5OhxAk0Y#V/@q9 l![g1<^trnh ~7sVMhY7f&ZK}v qKIq8K%<|]gn%9֞z(ٸ9 iEqUQ6EH z{/VEU:e#M# ǛnAeU 0!APK Sig Block 42PK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPKx+src/test/resources/com/android/apksig/v2-only-with-rsa-pss-sha512-2048.apk0100644 0000000 0000000 00000010106 13243353143 024776 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ  qYLH@V8t6U ڇpag栓dA\^۰1 T Q5Wxr⻶y@00 50l0  *H  010U rsa-20480 160331145749Z 430817145749Z010U rsa-20480"0  *H 0 5^0#YY2aD%],E sdM,T75P"¥TtImyjDn%X'J5" qrNsߧgBF_3.."/:GMH)x0(hC$nqJ;ߥ_'{7Wzq`haJh\xJb0N vt\%3Xw'ɦ"[z塌8б(!!,[P0N0U-s89j!Y,o0U#0-s89j!Y,o0 U00  *H  dKnBȓ@dyYNFsK'6 , $u*\I&}{y?0}xG)L` 2@*:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ  qYLH@V8t6U ڇpag栓dA\^۰1 T Q5Wxr⻶y@00a ga0  *H  010U rsa-30720 160331191458Z 430817191458Z010U rsa-307200  *H 0"Y3o%VRcs=|’Z,KxEK%eG~%&/'ŭ&S(URX6Wkǻ&es4eA˟pav_k漟ԉ [.%{ϟy(AHF%SN,V^U #w1 C;Ah ]CMue_MYc`4C7D$ `djO4c0\֤*'lOhs37nqej'ʦ-\M!gvDm*TٽC3Lr mEV$*x&3_fH,d#w[ Vȇ`)+l[oI~P0N0UOfql5ާS0U#0Ofql5ާS0 U00  *H  "UVkKefڻY,ZcS.Bê͐ŬpÅ_/6Nq `6u'KZz{ҍ*7B>(qDV_I]1TyώUQ|ʗ i}9HЍQo݃^n񀃯iǽM@P'mclpWX _C"֓cXQ)G@($'hL0* ƵnҾJ݂*uoj$gSyyҾ{wc޺yHec@Y{vk&kIq9g\9gp"T_,DG((Fl+-^XO.Xv®FUC'v~ 6!]k2`].M;  :. lŘ$0(BG7bHyTbxf1f(h~?t'kk,‚;hj+krP&~Gy;.~m'70= c3QCG/]11| 4սL}$MoGmG4l1x?9]@^p~*(I?vH3F1z5:[uۂ .S.-[<$ Ƒ{2t JBY+ܐ +B~gqĂ3׹ b7%ѼߦUk䜰*u*ԉJ#&H7- Yj~wTdrx%JRٖuq .eHS:cria슁>̦00  *H 0"Y3o%VRcs=|’Z,KxEK%eG~%&/'ŭ&S(URX6Wkǻ&es4eA˟pav_k漟ԉ [.%{ϟy(AHF%SN,V^U #w1 C;Ah ]CMue_MYc`4C7D$ `djO4c0\֤*'lOhs37nqej'ʦ-\M!gvDm*TٽC3Lr mEV$*x&3_fH,d#w[ Vȇ`)+l[oI~APK Sig Block 42PK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPKvsrc/test/resources/com/android/apksig/v2-only-with-rsa-pss-sha512-4096.apk0100644 0000000 0000000 00000012106 13243353143 025005 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ   q YLH@V8t6U ڇpag栓dA\^۰1 T Q5Wxr⻶y@00 q 0  *H  010U rsa-40960 160331152846Z 430817152846Z010U rsa-40960"0  *H 0 >;o'= YOAcCZ3r<\m6P 甚'o:JX`p7Ahذb0^tFp y^i^Y³803W ~ 8^]((=($g]Օl{E5K±H;o'= YOAcCZ3r<\m6P 甚'o:JX`p7Ahذb0^tFp y^i^Y³803W ~ 8^]((=:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ  qY LH@V8t6U ڇpag栓dA\^۰1 T Q5Wxr⻶y@ 00 7%0  *H  010U rsa-81920 160404193246Z 430821193246Z010U rsa-81920"0  *H 0 =$֑Zpsq@`DG*Dz =u}tgk"мz_y=Atw2{įTE/NǝpU22U.+]㘿HoNlγbPcs6*~&tG!4Dy+ͩ7,X߬`w.ՍMdjRjCr O6v=||a `U.YAhCD%%sq>]\̖^)cvaw#jAǨtl\PQY̷8Wl"B=@t- \>wD’ouz;ub@z%ڹ2B.Fcڟr =3N!6ȨHv}}96RLZӭIN u~ (&DV_ c$C.̜,nd^' ARҶ `hwՔy Z^(^dT.hd,G A / BTZ=E%wqCJi_єSq䒙]M1 h8=@ʳrC(/\zצC8lڣwx<"ԱϾquN5՞|!˩|m#m .<1>7q:> (hL /P+lK0yڶMۅDO H瞻4<Ulxk;{Ꮿ>bOSS"}kٛm%{+TF)ku--' t6*Zx2I?*\BC|<Xl[S@m*#ѪRqJcP!cZ_7F r^Rth 6rِf(:89[WnP0N0U]FsR$2/~?0U#0]FsR$2/~?0 U00  *H  hU_##B)q q;)1eG+)9RN&~W$`LYR8n \WmӔCzMaOiڒnR FSfv1$>3{ >\~Z{ {J~nt |)`tڝs3)1 tZ֝d:J蕯WSЃ(Ѝ$#&Pt\X2DvGê9ǢcS6w-aDfBHjAn"1_M+%w|]/O߂D*%'Y T=1' L}eI odw g8NR)dlo6"h9 r\h/[5,dGzNS4]sr #F7ԯ7@'Zb8.;:!憌Q]3:+8>6~^ 1V̋4L"rzE1r.7Lv1x =(+1 Kc ~=]#pփ,sy MI3V7up3Hi׈ˊGJpY{ASm!ԍ7YlC{fo;[cG(Rk33eRۃuRߨcvH- i=\hC,*#n'cY"d46S pfy s݉'H?b;Pؠ.  )U)avggVo;d85qڸ /WHNNa4_?oC~ިmU8aKv'˾ɴ9ʸ$sO¾C{`P:l7CEcv*$fibxswڥ-%۽:M]\7!U笑ݖ/vJ6?C$:Bv,2 M,ȥa!b! c*6`4rwX A-4 %Jq'6" b!t)>%Ww;4<^3 Iym; O{42"o*כە-J/R@'07H~\ݟ,Vv[-ؔ@Nc4v+T4nvy/kk` c4oc.B_ik/)>\-.FLGmDL|`\rW?* uC%٤8V\Yz9S?Jܗ5l}dɀjI(+O C[gJRfLJ~掋 %Q8-pg{(hLBr-e]^X0Z+]x"lk`KMlBG;7PEURH;~-qQ)"1)ߛ-(~edYvЫр*Ms5vӦH[Im2)p8',Z )c捜5[JAt񳶿.C"Pf>$vUB,|I -cAgpDU & k/F6_) (,18͢]umf!'(].V;e}J-혿0B~oym`36\.֧%ҰF+w>ż-:5=ӹ,$^ݻ|ƅPwWgAY𵩬8( L S.3-deDŽcBY f!d%}}Ye^XO=?=Ii-b*&.?J/u{3RM-C~8u]F pH, D|X`d_}=?2YذNp̹!Sr=w"33$mH $G s z( 34&0"0  *H 0 =$֑Zpsq@`DG*Dz =u}tgk"мz_y=Atw2{įTE/NǝpU22U.+]㘿HoNlγbPcs6*~&tG!4Dy+ͩ7,X߬`w.ՍMdjRjCr O6v=||a `U.YAhCD%%sq>]\̖^)cvaw#jAǨtl\PQY̷8Wl"B=@t- \>wD’ouz;ub@z%ڹ2B.Fcڟr =3N!6ȨHv}}96RLZӭIN u~ (&DV_ c$C.̜,nd^' ARҶ `hwՔy Z^(^dT.hd,G A / BTZ=E%wqCJi_єSq䒙]M1 h8=@ʳrC(/\zצC8lڣwx<"ԱϾquN5՞|!˩|m#m .<1>7q:> (hL /P+lK0yڶMۅDO H瞻4<Ulxk;{Ꮿ>bOSS"}kٛm%{+TF)ku--' t6*Zx2I?*\BC|<Xl[S@m*#ѪRqJcP!cZ_7F r^Rth 6rِf(:89[WnAPK Sig Block 42PK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPKvsrc/test/resources/com/android/apksig/v2-only-wrong-apk-sig-block-magic.apk0100644 0000000 0000000 00000012106 13243353143 025676 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:AndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ   q YLH@V8t6U ڇpag栓dA\^۰1 T Q5Wxr⻶y@00 q 0  *H  010U rsa-40960 160331152846Z 430817152846Z010U rsa-40960"0  *H 0 >;o'= YOAcCZ3r<\m6P 甚'o:JX`p7Ahذb0^tFp y^i^Y³803W ~ 8^]((=XIdF[M %:$K`At W{Ӗ&0"0  *H 0 >;o'= YOAcCZ3r<\m6P 甚'o:JX`p7Ahذb0^tFp y^i^Y³803W ~ 8^]((=:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!:Շ  classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!: QMETA-INF/MANIFEST.MFeMK@X,~B?6jY5 -L6323ۯ"( jǔ1jxfڎ@^hzD~ײ$K)2a+k]nvm?!PV|Xx% ޤeՒ74CNA\7~"HL1-NݜQG;%9 jWNM1ݞY$f݋+BEA/Ju~r\VDm s=o+w,B^PKW2PK!:X)%META-INF/CERT.RSA3hbiajhδԠѐ߀3̓1qA_&M03121q2eU 'l(hQTkd`ba 'khf`llhhbjnb%kbl`ahbĨl8QM @q.&FF & 5`Y契FodpQ] ʽ8w:˧1pT<@IҐ%pTf5_v@yy{m`:iOawv,^v#w:ns7[Ǫu;_ƽS\4`WI *yxY~pizyxՏO 2Y'2^ʈ^ /d틐7?.+Y;9Fs.=(oW?lR_Y {6YtM}H' FoDox}qq,0dXXDęt-f[ R#o Vf0k@Հ?Z2{mMOė{O_W'&;0_OR"Oލuf<txUKt?aVHo{q鋏ֶop_ICqϦDo.z=Qy~n/gOJۂk睺?S2(-d[%r>PKX)%PK !:!Oresources.arscPK!:^avAndroidManifest.xmlPK!:Շ  gclasses.dexPK!: Q META-INF/MANIFEST.MFPK!:W2 META-INF/CERT.SFPK!:X)%Z META-INF/CERT.RSAPKysrc/test/resources/com/android/apksig/v2-stripped.apk0100644 0000000 0000000 00000011111 13243353143 021711 0ustar000000000 0000000 PK !:!Oresources.arsc (CTiny App for CTS -[Ţîñý Åþþ ƒöŕ ÇŢŠ one two three] @‏‮Tiny‬‏ ‏‮App‬‏ ‏‮for‬‏ ‏‮CTS‬‏ android.appsecurity.cts.tinyapp T4$attrstring, app_nameL`P8L`P8enXAL`P8arXBPK!:^avAndroidManifest.xmlTnA=8$@c<)k( DHD :c`a |_PQP)sē-x{cf!APŗ`>:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!:Շ  classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!: QMETA-INF/MANIFEST.MFeMK@X,~B?6jY5 -L6323ۯ"( jǔ1jxfڎ@^hzD~ײ$K)2a+k]nvm?!PV|Xx% ޤeՒ74CNA\7~"HL1-NݜQG;%9 jWԬDk k}`V޴YFl+PK-PK!:$zp&META-INF/CERT.RSA3hbiajhδԠѐ߀3̓1qA_&M03121q2eU 'l(hQTkd`ba 'khf`llhhbjnb%kbl`ahbĨl8QM @q.&FF & 5`Y契FodpQ] ʽ8w:˧1pT<@IҐ%pTf5_v@yy{m`:iOawv,^v#w:ns7[Ǫu;_ƽS\4`WI *yxY~pizyxՏO 2Y'2^ʈ^ /d틐7?.+Y;9Fs.=(oW?lR_Y {6YtM}H' FoDox}qq,0dXXDęt-f[ R#o Vf0k@Հ?Z2{mMOė{O_W'&;0_OR"Oލuf<txUKt?aVHo{q鋏ֶop_IC:{oψ}cL|%D1';EJ#^ĀF fD=6̨)xy>[d-Ox"b|:gm")rV{cl \Hp5~zhiҿ}ǴV#0s&j#l Z/DFjKC.ψ6c'Lc3k;Z_JH/CBk5ɷw={phkdsn)+kܯB*rxgJm6#{by7U=԰1*| >e"edcNSld?[{AMyaT9"!Ayyrہ?\9M;˚~[-zp|]QОN2 'g2X2ܬ)~ESǖGYSG:u y\dz$L15#-:q?GO\05;8Z4Zv:ˎυ1uvh}qVKFq_z|՛/zs%o_PK^avPK!: classes.dexMhAnf7i4֚PDŪЦ(ZQ54Ih񠢇lIxQE`UPă JO7;& o}y9g>?p V>g'wn/}xwJbq4T9 &@槎&A} ] 1dX!-Gڗ(\A#ߑ=!B oHE22!ud C"\X ցa$rQ+Z;nS纜2VN􇔟(k(mk bQRϫېe7yw1o"4aǨ?#>"Or5,5HKTj^ϋ?ok@C>{-_ahY,ܜEeݪ2۲oq-uY]߻~re(},[k3}]T+:?Ū;kQJrĬ3.|LM+xVU,o.86@{ceDIi)ЧơڶE!q9($9sJ={xq0/ tHy.|,>`TiY ǡKKÃxXM\⢇Gڱ3Y{ Íuw_J {W$ؙ]&0y`C/Bz"z_4MPB{_KK]&0/οyDO򹢟>8iSi#-'BGPKՇ PK!:META-INF/MANIFEST.MFeMK@X,~B?6jY5 -L6323ۯ"( jǔ1jxfڎ@^hzD~ײ$K)2a+k]nvm?!PV|Xx% ޤeՒ74CNA\7~"HL1-NݜQG;%9 jW-|{^r*e^Z)n)k]NA#6nwvkMSd^<|W*-O