Merge branch 'fix-837--file-provider-uri' into 'master'

Use file provider when bluetoothing apks

See merge request !511
This commit is contained in:
Hans-Christoph Steiner 2017-05-12 20:19:47 +00:00
commit 064be9482d
4 changed files with 54 additions and 18 deletions

View File

@ -54,12 +54,14 @@ import org.fdroid.fdroid.compat.PRNGFixes;
import org.fdroid.fdroid.data.AppProvider;
import org.fdroid.fdroid.data.InstalledAppProviderService;
import org.fdroid.fdroid.data.Repo;
import org.fdroid.fdroid.installer.ApkFileProvider;
import org.fdroid.fdroid.data.SanitizedFile;
import org.fdroid.fdroid.installer.InstallHistoryService;
import org.fdroid.fdroid.net.ImageLoaderForUIL;
import org.fdroid.fdroid.net.WifiStateChangeService;
import sun.net.www.protocol.bluetooth.Handler;
import java.io.IOException;
import java.net.URL;
import java.net.URLStreamHandler;
import java.net.URLStreamHandlerFactory;
@ -368,20 +370,21 @@ public class FDroidApp extends Application {
if (resultCode == Activity.RESULT_CANCELED) {
return;
}
String bluetoothPackageName = null;
String className = null;
boolean found = false;
Intent sendBt = null;
try {
PackageManager pm = getPackageManager();
ApplicationInfo appInfo = pm.getApplicationInfo(packageName,
PackageManager.GET_META_DATA);
ApplicationInfo appInfo = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA);
sendBt = new Intent(Intent.ACTION_SEND);
// The APK type is blocked by stock Android, so use zip
// sendBt.setType("application/vnd.android.package-archive");
// The APK type ("application/vnd.android.package-archive") is blocked by stock Android, so use zip
sendBt.setType("application/zip");
sendBt.putExtra(Intent.EXTRA_STREAM,
Uri.parse("file://" + appInfo.publicSourceDir));
sendBt.putExtra(Intent.EXTRA_STREAM, ApkFileProvider.getSafeUri(this, appInfo));
// not all devices have the same Bluetooth Activities, so
// let's find it
for (ResolveInfo info : pm.queryIntentActivities(sendBt, 0)) {
@ -396,7 +399,11 @@ public class FDroidApp extends Application {
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "Could not get application info to send via bluetooth", e);
found = false;
} catch (IOException e) {
Exception toLog = new RuntimeException("Error preparing file to send via Bluetooth", e);
ACRA.getErrorReporter().handleException(toLog, false);
}
if (sendBt != null) {
if (found) {
sendBt.setClassName(bluetoothPackageName, className);

View File

@ -20,6 +20,7 @@
package org.fdroid.fdroid.installer;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.net.Uri;
import com.nostra13.universalimageloader.utils.StorageUtils;
@ -36,6 +37,15 @@ public class ApkCache {
private static final String CACHE_DIR = "apks";
/**
* Same as {@link #copyApkFromCacheToFiles(Context, File, Apk)}, except it does not need to
* verify the hash after copying. This is because we are copying from an installed apk, which
* other apps do not have permission to modify.
*/
public static SanitizedFile copyInstalledApkToFiles(Context context, ApplicationInfo appInfo) throws IOException {
return copyApkToFiles(context, new File(appInfo.publicSourceDir), false, null, null);
}
/**
* Copy the APK to the safe location inside of the protected area
* of the app to prevent attacks based on other apps swapping the file
@ -44,12 +54,24 @@ public class ApkCache {
*/
public static SanitizedFile copyApkFromCacheToFiles(Context context, File apkFile, Apk expectedApk)
throws IOException {
return copyApkToFiles(context, apkFile, true, expectedApk.hash, expectedApk.hashType);
}
/**
* Copy an APK from {@param apkFile} to our internal files directory for 20 minutes.
* @param verifyHash If the file was just downloaded, then you should mark this as true and
* request the file to be verified once it has finished copying. Otherwise,
* if the app was installed from part of the system where it can't be tampered
* with (e.g. installed apks on disk) then
*/
private static SanitizedFile copyApkToFiles(Context context, File apkFile, boolean verifyHash, String hash, String hashType)
throws IOException {
SanitizedFile sanitizedApkFile = SanitizedFile.knownSanitized(
File.createTempFile("install-", ".apk", context.getFilesDir()));
FileUtils.copyFile(apkFile, sanitizedApkFile);
// verify copied file's hash with expected hash from Apk class
if (!Hasher.isFileMatchingHash(sanitizedApkFile, expectedApk.hash, expectedApk.hashType)) {
if (verifyHash && !Hasher.isFileMatchingHash(sanitizedApkFile, hash, hashType)) {
FileUtils.deleteQuietly(apkFile);
throw new IOException(apkFile + " failed to verify!");
}

View File

@ -21,7 +21,9 @@ package org.fdroid.fdroid.installer;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.net.Uri;
import android.os.Build;
import android.support.v4.content.FileProvider;
import org.fdroid.fdroid.data.Apk;
@ -47,6 +49,11 @@ public class ApkFileProvider extends FileProvider {
private static final String AUTHORITY = "org.fdroid.fdroid.installer.ApkFileProvider";
public static Uri getSafeUri(Context context, ApplicationInfo appInfo) throws IOException {
SanitizedFile tempApkFile = ApkCache.copyInstalledApkToFiles(context, appInfo);
return getSafeUri(context, tempApkFile, Build.VERSION.SDK_INT >= 24);
}
/**
* Copies the APK into private data directory of F-Droid and returns a "file" or "content" Uri
* to be used for installation.
@ -54,14 +61,17 @@ public class ApkFileProvider extends FileProvider {
public static Uri getSafeUri(Context context, Uri localApkUri, Apk expectedApk, boolean useContentUri)
throws IOException {
File apkFile = new File(localApkUri.getPath());
SanitizedFile tempApkFile = ApkCache.copyApkFromCacheToFiles(context, apkFile, expectedApk);
return getSafeUri(context, tempApkFile, useContentUri);
SanitizedFile sanitizedApkFile =
ApkCache.copyApkFromCacheToFiles(context, apkFile, expectedApk);
}
private static Uri getSafeUri(Context context, SanitizedFile tempApkFile, boolean useContentUri) {
if (useContentUri) {
// return a content Uri using support libs FileProvider
Uri apkUri = getUriForFile(context, AUTHORITY, sanitizedApkFile);
Uri apkUri = getUriForFile(context, AUTHORITY, tempApkFile);
context.grantUriPermission("org.fdroid.fdroid.privileged", apkUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
context.grantUriPermission("com.android.bluetooth", apkUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
return apkUri;
}
@ -70,9 +80,9 @@ public class ApkFileProvider extends FileProvider {
// have access is insecure, because apps with permission to write to the external
// storage can overwrite the app between F-Droid asking for it to be installed and
// the installer actually installing it.
sanitizedApkFile.setReadable(true, false);
tempApkFile.setReadable(true, false);
return Uri.fromFile(sanitizedApkFile);
return Uri.fromFile(tempApkFile);
}
}

View File

@ -245,12 +245,10 @@ public abstract class Installer {
try {
// move apk file to private directory for installation and check hash
sanitizedUri = ApkFileProvider.getSafeUri(
context, localApkUri, apk, supportsContentUri());
sanitizedUri = ApkFileProvider.getSafeUri(context, localApkUri, apk, supportsContentUri());
} catch (IOException e) {
Log.e(TAG, e.getMessage(), e);
sendBroadcastInstall(downloadUri, Installer.ACTION_INSTALL_INTERRUPTED,
e.getMessage());
sendBroadcastInstall(downloadUri, Installer.ACTION_INSTALL_INTERRUPTED, e.getMessage());
return;
}
@ -260,8 +258,7 @@ public abstract class Installer {
apkVerifier.verifyApk();
} catch (ApkVerifier.ApkVerificationException e) {
Log.e(TAG, e.getMessage(), e);
sendBroadcastInstall(downloadUri, Installer.ACTION_INSTALL_INTERRUPTED,
e.getMessage());
sendBroadcastInstall(downloadUri, Installer.ACTION_INSTALL_INTERRUPTED, e.getMessage());
return;
} catch (ApkVerifier.ApkPermissionUnequalException e) {
// if permissions of apk are not the ones listed in the repo