Tests for ApkVerifier
This commit is contained in:
parent
2837a235b4
commit
9235462e34
BIN
app/src/androidTest/assets/org.fdroid.permissions.minmax.apk
Normal file
BIN
app/src/androidTest/assets/org.fdroid.permissions.minmax.apk
Normal file
Binary file not shown.
BIN
app/src/androidTest/assets/org.fdroid.permissions.minmax.zip
Normal file
BIN
app/src/androidTest/assets/org.fdroid.permissions.minmax.zip
Normal file
Binary file not shown.
BIN
app/src/androidTest/assets/org.fdroid.permissions.sdk14.apk
Normal file
BIN
app/src/androidTest/assets/org.fdroid.permissions.sdk14.apk
Normal file
Binary file not shown.
BIN
app/src/androidTest/assets/org.fdroid.permissions.sdk14.zip
Normal file
BIN
app/src/androidTest/assets/org.fdroid.permissions.sdk14.zip
Normal file
Binary file not shown.
39
app/src/androidTest/java/org/fdroid/fdroid/AssetUtils.java
Normal file
39
app/src/androidTest/java/org/fdroid/fdroid/AssetUtils.java
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
package org.fdroid.fdroid;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
|
public class AssetUtils {
|
||||||
|
|
||||||
|
private static final String TAG = "Utils";
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static File copyAssetToDir(Context context, String assetName, File directory) {
|
||||||
|
File tempFile = null;
|
||||||
|
InputStream input = null;
|
||||||
|
OutputStream output = null;
|
||||||
|
try {
|
||||||
|
tempFile = File.createTempFile(assetName, ".testasset", directory);
|
||||||
|
Log.i(TAG, "Copying asset file " + assetName + " to directory " + directory);
|
||||||
|
input = context.getAssets().open(assetName);
|
||||||
|
output = new FileOutputStream(tempFile);
|
||||||
|
Utils.copy(input, output);
|
||||||
|
} catch (IOException e) {
|
||||||
|
fail(e.getMessage());
|
||||||
|
} finally {
|
||||||
|
Utils.closeQuietly(output);
|
||||||
|
Utils.closeQuietly(input);
|
||||||
|
}
|
||||||
|
return tempFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -4,12 +4,10 @@ import android.app.Instrumentation;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Environment;
|
import android.os.Environment;
|
||||||
import android.support.annotation.Nullable;
|
|
||||||
import android.support.test.InstrumentationRegistry;
|
import android.support.test.InstrumentationRegistry;
|
||||||
import android.support.test.runner.AndroidJUnit4;
|
import android.support.test.runner.AndroidJUnit4;
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import org.fdroid.fdroid.Utils;
|
import org.fdroid.fdroid.AssetUtils;
|
||||||
import org.fdroid.fdroid.data.SanitizedFile;
|
import org.fdroid.fdroid.data.SanitizedFile;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
@ -17,10 +15,6 @@ import org.junit.Test;
|
|||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
@ -36,8 +30,6 @@ import static org.junit.Assume.assumeTrue;
|
|||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
public class FileCompatTest {
|
public class FileCompatTest {
|
||||||
|
|
||||||
private static final String TAG = "FileCompatTest";
|
|
||||||
|
|
||||||
private SanitizedFile sourceFile;
|
private SanitizedFile sourceFile;
|
||||||
private SanitizedFile destFile;
|
private SanitizedFile destFile;
|
||||||
|
|
||||||
@ -45,7 +37,8 @@ public class FileCompatTest {
|
|||||||
public void setUp() {
|
public void setUp() {
|
||||||
Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
|
Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
|
||||||
File dir = getWriteableDir(instrumentation);
|
File dir = getWriteableDir(instrumentation);
|
||||||
sourceFile = SanitizedFile.knownSanitized(copyAssetToDir(instrumentation.getContext(), "simpleIndex.jar", dir));
|
sourceFile = SanitizedFile.knownSanitized(
|
||||||
|
AssetUtils.copyAssetToDir(instrumentation.getContext(), "simpleIndex.jar", dir));
|
||||||
destFile = new SanitizedFile(dir, "dest-" + UUID.randomUUID() + ".testproduct");
|
destFile = new SanitizedFile(dir, "dest-" + UUID.randomUUID() + ".testproduct");
|
||||||
assertFalse(destFile.exists());
|
assertFalse(destFile.exists());
|
||||||
assertTrue(sourceFile.getAbsolutePath() + " should exist.", sourceFile.exists());
|
assertTrue(sourceFile.getAbsolutePath() + " should exist.", sourceFile.exists());
|
||||||
@ -82,26 +75,6 @@ public class FileCompatTest {
|
|||||||
assertTrue(destFile.getAbsolutePath() + " should exist after symlinking", destFile.exists());
|
assertTrue(destFile.getAbsolutePath() + " should exist after symlinking", destFile.exists());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private static File copyAssetToDir(Context context, String assetName, File directory) {
|
|
||||||
File tempFile;
|
|
||||||
InputStream input = null;
|
|
||||||
OutputStream output = null;
|
|
||||||
try {
|
|
||||||
tempFile = File.createTempFile(assetName + "-", ".testasset", directory);
|
|
||||||
Log.i(TAG, "Copying asset file " + assetName + " to directory " + directory);
|
|
||||||
input = context.getAssets().open(assetName);
|
|
||||||
output = new FileOutputStream(tempFile);
|
|
||||||
Utils.copy(input, output);
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
return null;
|
|
||||||
} finally {
|
|
||||||
Utils.closeQuietly(output);
|
|
||||||
Utils.closeQuietly(input);
|
|
||||||
}
|
|
||||||
return tempFile;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prefer internal over external storage, because external tends to be FAT filesystems,
|
* Prefer internal over external storage, because external tends to be FAT filesystems,
|
||||||
|
@ -0,0 +1,242 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 3
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||||
|
* MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.fdroid.fdroid.installer;
|
||||||
|
|
||||||
|
import android.app.Instrumentation;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.support.test.InstrumentationRegistry;
|
||||||
|
import android.support.test.runner.AndroidJUnit4;
|
||||||
|
|
||||||
|
import org.fdroid.fdroid.AssetUtils;
|
||||||
|
import org.fdroid.fdroid.data.Apk;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.rules.TemporaryFolder;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This test checks the ApkVerifier by parsing a repo from permissionsRepo.xml
|
||||||
|
* and checking the listed permissions against the ones specified in apks' AndroidManifest,
|
||||||
|
* which have been specifically generated for this test.
|
||||||
|
* - the apk file name must match the package name in the xml
|
||||||
|
* - the versionName of listed apks inside the repo have either a good or bad outcome.
|
||||||
|
* this must be defined in GOOD_VERSION_NAMES and BAD_VERSION_NAMES.
|
||||||
|
* <p/>
|
||||||
|
* NOTE: This androidTest cannot run as a Robolectric test because the
|
||||||
|
* required methods from PackageManger are not included in Robolectric's Android API.
|
||||||
|
* java.lang.NoClassDefFoundError: java/util/jar/StrictJarFile
|
||||||
|
* at android.content.pm.PackageManager.getPackageArchiveInfo(PackageManager.java:3545)
|
||||||
|
*/
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class ApkVerifierTest {
|
||||||
|
|
||||||
|
Instrumentation instrumentation;
|
||||||
|
|
||||||
|
File sdk14Apk;
|
||||||
|
File minMaxApk;
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public TemporaryFolder tempFolder = new TemporaryFolder();
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
instrumentation = InstrumentationRegistry.getInstrumentation();
|
||||||
|
File dir = null;
|
||||||
|
try {
|
||||||
|
dir = tempFolder.newFolder("apks");
|
||||||
|
} catch (IOException e) {
|
||||||
|
fail(e.getMessage());
|
||||||
|
}
|
||||||
|
sdk14Apk = AssetUtils.copyAssetToDir(instrumentation.getContext(),
|
||||||
|
"org.fdroid.permissions.sdk14.apk",
|
||||||
|
dir
|
||||||
|
);
|
||||||
|
minMaxApk = AssetUtils.copyAssetToDir(instrumentation.getContext(),
|
||||||
|
"org.fdroid.permissions.minmax.apk",
|
||||||
|
dir
|
||||||
|
);
|
||||||
|
assertTrue(sdk14Apk.exists());
|
||||||
|
assertTrue(minMaxApk.exists());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWithoutPrefix() {
|
||||||
|
Apk apk = new Apk();
|
||||||
|
apk.packageName = "org.fdroid.permissions.sdk14";
|
||||||
|
apk.targetSdkVersion = 14;
|
||||||
|
apk.permissions = new String[]{
|
||||||
|
"AUTHENTICATE_ACCOUNTS",
|
||||||
|
"MANAGE_ACCOUNTS",
|
||||||
|
"READ_PROFILE",
|
||||||
|
"WRITE_PROFILE",
|
||||||
|
"GET_ACCOUNTS",
|
||||||
|
"READ_CONTACTS",
|
||||||
|
"WRITE_CONTACTS",
|
||||||
|
"WRITE_EXTERNAL_STORAGE",
|
||||||
|
"READ_EXTERNAL_STORAGE",
|
||||||
|
"INTERNET",
|
||||||
|
"ACCESS_NETWORK_STATE",
|
||||||
|
"NFC",
|
||||||
|
"READ_SYNC_SETTINGS",
|
||||||
|
"WRITE_SYNC_SETTINGS",
|
||||||
|
"WRITE_CALL_LOG", // implied-permission!
|
||||||
|
"READ_CALL_LOG", // implied-permission!
|
||||||
|
};
|
||||||
|
|
||||||
|
Uri uri = Uri.fromFile(sdk14Apk);
|
||||||
|
|
||||||
|
ApkVerifier apkVerifier = new ApkVerifier(instrumentation.getContext(), uri, apk);
|
||||||
|
|
||||||
|
try {
|
||||||
|
apkVerifier.verifyApk();
|
||||||
|
} catch (ApkVerifier.ApkVerificationException | ApkVerifier.ApkPermissionUnequalException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
fail(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWithPrefix() {
|
||||||
|
Apk apk = new Apk();
|
||||||
|
apk.packageName = "org.fdroid.permissions.sdk14";
|
||||||
|
apk.targetSdkVersion = 14;
|
||||||
|
apk.permissions = new String[]{
|
||||||
|
"android.permission.AUTHENTICATE_ACCOUNTS",
|
||||||
|
"android.permission.MANAGE_ACCOUNTS",
|
||||||
|
"android.permission.READ_PROFILE",
|
||||||
|
"android.permission.WRITE_PROFILE",
|
||||||
|
"android.permission.GET_ACCOUNTS",
|
||||||
|
"android.permission.READ_CONTACTS",
|
||||||
|
"android.permission.WRITE_CONTACTS",
|
||||||
|
"android.permission.WRITE_EXTERNAL_STORAGE",
|
||||||
|
"android.permission.READ_EXTERNAL_STORAGE",
|
||||||
|
"android.permission.INTERNET",
|
||||||
|
"android.permission.ACCESS_NETWORK_STATE",
|
||||||
|
"android.permission.NFC",
|
||||||
|
"android.permission.READ_SYNC_SETTINGS",
|
||||||
|
"android.permission.WRITE_SYNC_SETTINGS",
|
||||||
|
"android.permission.WRITE_CALL_LOG", // implied-permission!
|
||||||
|
"android.permission.READ_CALL_LOG", // implied-permission!
|
||||||
|
};
|
||||||
|
|
||||||
|
Uri uri = Uri.fromFile(sdk14Apk);
|
||||||
|
|
||||||
|
ApkVerifier apkVerifier = new ApkVerifier(instrumentation.getContext(), uri, apk);
|
||||||
|
|
||||||
|
try {
|
||||||
|
apkVerifier.verifyApk();
|
||||||
|
} catch (ApkVerifier.ApkVerificationException | ApkVerifier.ApkPermissionUnequalException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
fail(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Additional permissions are okay. The user is simply
|
||||||
|
* warned about a permission that is not used inside the apk
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testAdditionalPermission() {
|
||||||
|
Apk apk = new Apk();
|
||||||
|
apk.packageName = "org.fdroid.permissions.sdk14";
|
||||||
|
apk.targetSdkVersion = 14;
|
||||||
|
apk.permissions = new String[]{
|
||||||
|
"android.permission.AUTHENTICATE_ACCOUNTS",
|
||||||
|
"android.permission.MANAGE_ACCOUNTS",
|
||||||
|
"android.permission.READ_PROFILE",
|
||||||
|
"android.permission.WRITE_PROFILE",
|
||||||
|
"android.permission.GET_ACCOUNTS",
|
||||||
|
"android.permission.READ_CONTACTS",
|
||||||
|
"android.permission.WRITE_CONTACTS",
|
||||||
|
"android.permission.WRITE_EXTERNAL_STORAGE",
|
||||||
|
"android.permission.READ_EXTERNAL_STORAGE",
|
||||||
|
"android.permission.INTERNET",
|
||||||
|
"android.permission.ACCESS_NETWORK_STATE",
|
||||||
|
"android.permission.NFC",
|
||||||
|
"android.permission.READ_SYNC_SETTINGS",
|
||||||
|
"android.permission.WRITE_SYNC_SETTINGS",
|
||||||
|
"android.permission.WRITE_CALL_LOG", // implied-permission!
|
||||||
|
"android.permission.READ_CALL_LOG", // implied-permission!
|
||||||
|
"NEW_PERMISSION",
|
||||||
|
};
|
||||||
|
|
||||||
|
Uri uri = Uri.fromFile(sdk14Apk);
|
||||||
|
|
||||||
|
ApkVerifier apkVerifier = new ApkVerifier(instrumentation.getContext(), uri, apk);
|
||||||
|
|
||||||
|
try {
|
||||||
|
apkVerifier.verifyApk();
|
||||||
|
} catch (ApkVerifier.ApkVerificationException | ApkVerifier.ApkPermissionUnequalException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
fail(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Missing permissions are not okay!
|
||||||
|
* The user is then not warned about a permission that the apk uses!
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testMissingPermission() {
|
||||||
|
Apk apk = new Apk();
|
||||||
|
apk.packageName = "org.fdroid.permissions.sdk14";
|
||||||
|
apk.targetSdkVersion = 14;
|
||||||
|
apk.permissions = new String[]{
|
||||||
|
//"android.permission.AUTHENTICATE_ACCOUNTS",
|
||||||
|
"android.permission.MANAGE_ACCOUNTS",
|
||||||
|
"android.permission.READ_PROFILE",
|
||||||
|
"android.permission.WRITE_PROFILE",
|
||||||
|
"android.permission.GET_ACCOUNTS",
|
||||||
|
"android.permission.READ_CONTACTS",
|
||||||
|
"android.permission.WRITE_CONTACTS",
|
||||||
|
"android.permission.WRITE_EXTERNAL_STORAGE",
|
||||||
|
"android.permission.READ_EXTERNAL_STORAGE",
|
||||||
|
"android.permission.INTERNET",
|
||||||
|
"android.permission.ACCESS_NETWORK_STATE",
|
||||||
|
"android.permission.NFC",
|
||||||
|
"android.permission.READ_SYNC_SETTINGS",
|
||||||
|
"android.permission.WRITE_SYNC_SETTINGS",
|
||||||
|
"android.permission.WRITE_CALL_LOG", // implied-permission!
|
||||||
|
"android.permission.READ_CALL_LOG", // implied-permission!
|
||||||
|
};
|
||||||
|
|
||||||
|
Uri uri = Uri.fromFile(sdk14Apk);
|
||||||
|
|
||||||
|
ApkVerifier apkVerifier = new ApkVerifier(instrumentation.getContext(), uri, apk);
|
||||||
|
|
||||||
|
try {
|
||||||
|
apkVerifier.verifyApk();
|
||||||
|
fail();
|
||||||
|
} catch (ApkVerifier.ApkVerificationException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
fail(e.getMessage());
|
||||||
|
} catch (ApkVerifier.ApkPermissionUnequalException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -45,6 +45,10 @@ class ApkVerifier {
|
|||||||
private final Apk expectedApk;
|
private final Apk expectedApk;
|
||||||
private final PackageManager pm;
|
private final PackageManager pm;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IMPORTANT: localApkUri must be available as a File on the file system with an absolute path
|
||||||
|
* to be readable by Android's internal PackageParser.
|
||||||
|
*/
|
||||||
ApkVerifier(Context context, Uri localApkUri, Apk expectedApk) {
|
ApkVerifier(Context context, Uri localApkUri, Apk expectedApk) {
|
||||||
this.localApkUri = localApkUri;
|
this.localApkUri = localApkUri;
|
||||||
this.expectedApk = expectedApk;
|
this.expectedApk = expectedApk;
|
||||||
@ -52,11 +56,18 @@ class ApkVerifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void verifyApk() throws ApkVerificationException, ApkPermissionUnequalException {
|
public void verifyApk() throws ApkVerificationException, ApkPermissionUnequalException {
|
||||||
|
Utils.debugLog(TAG, "localApkUri.getPath: " + localApkUri.getPath());
|
||||||
|
|
||||||
// parse downloaded apk file locally
|
// parse downloaded apk file locally
|
||||||
PackageInfo localApkInfo = pm.getPackageArchiveInfo(
|
PackageInfo localApkInfo = pm.getPackageArchiveInfo(
|
||||||
localApkUri.getPath(), PackageManager.GET_PERMISSIONS);
|
localApkUri.getPath(), PackageManager.GET_PERMISSIONS);
|
||||||
if (localApkInfo == null) {
|
if (localApkInfo == null) {
|
||||||
throw new ApkVerificationException("Parsing apk file failed!");
|
// Unfortunately, more specific errors are not forwarded to us
|
||||||
|
// but the internal PackageParser sometimes shows warnings in logcat such as
|
||||||
|
// "Requires newer sdk version #14 (current version is #11)"
|
||||||
|
throw new ApkVerificationException("Parsing apk file failed!" +
|
||||||
|
"Maybe minSdk of apk is lower than current Sdk?" +
|
||||||
|
"Look into logcat for more specific warnings of Android's PackageParser");
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if the apk has the expected packageName
|
// check if the apk has the expected packageName
|
||||||
|
Loading…
x
Reference in New Issue
Block a user