Verify permissions of downloaded apk

This commit is contained in:
Dominik Schürmann 2016-06-08 13:54:56 +02:00
parent 1652c32d51
commit 4bed9d67c5
3 changed files with 39 additions and 13 deletions

View File

@ -5,12 +5,12 @@ import android.content.ContentValues;
import android.database.Cursor; import android.database.Cursor;
import android.os.Build; import android.os.Build;
import android.os.Parcelable; import android.os.Parcelable;
import android.util.Log;
import org.fdroid.fdroid.Utils; import org.fdroid.fdroid.Utils;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.HashSet;
public class Apk extends ValueObject implements Comparable<Apk> { public class Apk extends ValueObject implements Comparable<Apk> {
@ -145,7 +145,7 @@ public class Apk extends ValueObject implements Comparable<Apk> {
public ArrayList<String> getFullPermissionList() { public ArrayList<String> getFullPermissionList() {
if (this.permissions == null) { if (this.permissions == null) {
return null; return new ArrayList<>();
} }
ArrayList<String> permissionsFull = new ArrayList<>(); ArrayList<String> permissionsFull = new ArrayList<>();
@ -157,13 +157,13 @@ public class Apk extends ValueObject implements Comparable<Apk> {
public String[] getFullPermissionsArray() { public String[] getFullPermissionsArray() {
ArrayList<String> fullPermissions = getFullPermissionList(); ArrayList<String> fullPermissions = getFullPermissionList();
if (fullPermissions == null) {
return null;
}
return fullPermissions.toArray(new String[fullPermissions.size()]); return fullPermissions.toArray(new String[fullPermissions.size()]);
} }
public HashSet<String> getFullPermissionsSet() {
return new HashSet<>(getFullPermissionList());
}
/** /**
* It appears that the default Android permissions in android.Manifest.permissions * It appears that the default Android permissions in android.Manifest.permissions
* are prefixed with "android.permission." and then the constant name. * are prefixed with "android.permission." and then the constant name.

View File

@ -23,31 +23,37 @@ import android.content.Context;
import android.content.pm.PackageInfo; import android.content.pm.PackageInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.net.Uri; import android.net.Uri;
import android.util.Log;
import org.apache.commons.io.FileUtils; import org.apache.commons.io.FileUtils;
import org.fdroid.fdroid.Hasher; import org.fdroid.fdroid.Hasher;
import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.data.Apk; import org.fdroid.fdroid.data.Apk;
import org.fdroid.fdroid.data.SanitizedFile; import org.fdroid.fdroid.data.SanitizedFile;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.HashSet;
public class ApkVerifier { public class ApkVerifier {
private static final String TAG = "ApkVerifier";
Context context; Context context;
Uri localApkUri; Uri localApkUri;
Apk apk; Apk expectedApk;
PackageManager pm; PackageManager pm;
ApkVerifier(Context context, Uri localApkUri, Apk apk) { ApkVerifier(Context context, Uri localApkUri, Apk expectedApk) {
this.context = context; this.context = context;
this.localApkUri = localApkUri; this.localApkUri = localApkUri;
this.apk = apk; this.expectedApk = expectedApk;
this.pm = context.getPackageManager(); this.pm = context.getPackageManager();
} }
public void basicVerify() throws ApkVerificationException { public void verifyApk() throws ApkVerificationException {
PackageInfo localApkInfo = pm.getPackageArchiveInfo( PackageInfo localApkInfo = pm.getPackageArchiveInfo(
localApkUri.getPath(), PackageManager.GET_PERMISSIONS); localApkUri.getPath(), PackageManager.GET_PERMISSIONS);
if (localApkInfo == null) { if (localApkInfo == null) {
@ -55,13 +61,33 @@ public class ApkVerifier {
} }
// check if the apk has the expected packageName // check if the apk has the expected packageName
if (localApkInfo.packageName == null || !apk.packageName.equals(localApkInfo.packageName)) { if (localApkInfo.packageName == null || !localApkInfo.packageName.equals(expectedApk.packageName)) {
throw new ApkVerificationException("apk has unexpected packageName!"); throw new ApkVerificationException("apk has unexpected packageName!");
} }
if (localApkInfo.versionCode < 0) { if (localApkInfo.versionCode < 0) {
throw new ApkVerificationException("apk has no valid versionCode!"); throw new ApkVerificationException("apk has no valid versionCode!");
} }
// verify permissions, important for unattended installer
HashSet<String> localPermissions = getLocalPermissionsSet(localApkInfo);
HashSet<String> expectedPermissions = expectedApk.getFullPermissionsSet();
Utils.debugLog(TAG, "localPermissions: " + localPermissions);
Utils.debugLog(TAG, "expectedPermissions: " + expectedPermissions);
if (!localPermissions.equals(expectedPermissions)) {
throw new ApkVerificationException("permissions of apk not equals expected permissions!");
}
// TODO: check target sdk
}
private HashSet<String> getLocalPermissionsSet(PackageInfo localApkInfo) {
String[] localPermissions = localApkInfo.requestedPermissions;
if (localPermissions == null) {
return new HashSet<>();
}
return new HashSet<>(Arrays.asList(localApkInfo.requestedPermissions));
} }
public Uri getSafeUri() throws ApkVerificationException { public Uri getSafeUri() throws ApkVerificationException {
@ -77,7 +103,7 @@ public class ApkVerifier {
sanitizedApkFile = SanitizedFile.knownSanitized(File.createTempFile("install-", ".apk", sanitizedApkFile = SanitizedFile.knownSanitized(File.createTempFile("install-", ".apk",
context.getFilesDir())); context.getFilesDir()));
FileUtils.copyFile(apkFile, sanitizedApkFile); FileUtils.copyFile(apkFile, sanitizedApkFile);
if (!verifyApkFile(sanitizedApkFile, apk.hash, apk.hashType)) { if (!verifyApkFile(sanitizedApkFile, expectedApk.hash, expectedApk.hashType)) {
FileUtils.deleteQuietly(apkFile); FileUtils.deleteQuietly(apkFile);
throw new ApkVerificationException(apkFile + " failed to verify!"); throw new ApkVerificationException(apkFile + " failed to verify!");
} }

View File

@ -241,7 +241,7 @@ public class InstallManagerService extends Service {
Uri sanitizedUri; Uri sanitizedUri;
try { try {
ApkVerifier apkVerifier = new ApkVerifier(context, localApkUri, apk); ApkVerifier apkVerifier = new ApkVerifier(context, localApkUri, apk);
apkVerifier.basicVerify(); apkVerifier.verifyApk();
sanitizedUri = apkVerifier.getSafeUri(); sanitizedUri = apkVerifier.getSafeUri();
} catch (ApkVerifier.ApkVerificationException e) { } catch (ApkVerifier.ApkVerificationException e) {
Log.e(TAG, "ApkVerifier failed", e); Log.e(TAG, "ApkVerifier failed", e);