/* * 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.content.Context; import android.net.Uri; import com.nostra13.universalimageloader.utils.StorageUtils; import org.apache.commons.io.FileUtils; import org.fdroid.fdroid.Hasher; import org.fdroid.fdroid.data.Apk; import org.fdroid.fdroid.data.SanitizedFile; import java.io.File; import java.io.IOException; public class ApkCache { private static final String CACHE_DIR = "apks"; /** * 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 * out during the install process. Most likely, apkFile was just downloaded, * so it should still be in the RAM disk cache. */ public static SanitizedFile copyApkFromCacheToFiles(Context context, File apkFile, Apk expectedApk) 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)) { FileUtils.deleteQuietly(apkFile); throw new IOException(apkFile + " failed to verify!"); } // 20 minutes the start of the install process, delete the file final File apkToDelete = sanitizedApkFile; new Thread() { @Override public void run() { android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_LOWEST); try { Thread.sleep(1200000); } catch (InterruptedException ignored) { } finally { FileUtils.deleteQuietly(apkToDelete); } } }.start(); return sanitizedApkFile; } /** * Get the full path for where an APK URL will be downloaded into. */ public static SanitizedFile getApkDownloadPath(Context context, Uri uri) { File dir = new File(getApkCacheDir(context), uri.getHost() + "-" + uri.getPort()); if (!dir.exists()) { dir.mkdirs(); } return new SanitizedFile(dir, uri.getLastPathSegment()); } /** * Verifies the size of the file on disk matches, and then hashes the file to compare with what * we received from the signed repo (i.e. {@link Apk#hash} and {@link Apk#hashType}). * Bails out if the file sizes don't match to prevent having to do the work of hashing the file. */ public static boolean apkIsCached(File apkFile, Apk apkToCheck) { return apkFile.length() == apkToCheck.size && Hasher.isFileMatchingHash(apkFile, apkToCheck.hash, apkToCheck.hashType); } /** * This location is only for caching, do not install directly from this location * because if the file is on the External Storage, any other app could swap out * the APK while the install was in process, allowing malware to install things. * Using {@link Installer#installPackage(Uri, Uri)} * is fine since that does the right thing. */ public static File getApkCacheDir(Context context) { File apkCacheDir = new File(StorageUtils.getCacheDirectory(context, true), CACHE_DIR); if (apkCacheDir.isFile()) { apkCacheDir.delete(); } if (!apkCacheDir.exists()) { apkCacheDir.mkdir(); } return apkCacheDir; } }