From 4c4aef5314d513111d1ac959c8785fb7030ee21b Mon Sep 17 00:00:00 2001
From: Hans-Christoph Steiner <hans@eds.org>
Date: Fri, 30 Sep 2016 12:46:58 +0200
Subject: [PATCH] refactor into reusable static method for checking file hashes

This takes the APK file hash checker and turns it into a generic static
utility method for checking that a given file matches a given hash.  This
will be needed as F-Droid handles other file types, like OBB and media.
---
 .../main/java/org/fdroid/fdroid/Hasher.java   | 15 ++++
 .../org/fdroid/fdroid/installer/ApkCache.java | 73 +++++++------------
 2 files changed, 40 insertions(+), 48 deletions(-)

diff --git a/app/src/main/java/org/fdroid/fdroid/Hasher.java b/app/src/main/java/org/fdroid/fdroid/Hasher.java
index a6bf80635..4f4e34abc 100644
--- a/app/src/main/java/org/fdroid/fdroid/Hasher.java
+++ b/app/src/main/java/org/fdroid/fdroid/Hasher.java
@@ -97,6 +97,21 @@ public class Hasher {
         return hashCache.equals(otherHash.toLowerCase(Locale.ENGLISH));
     }
 
+    /**
+     * Checks the file against the provided hash, returning whether it is a match.
+     */
+    public static boolean isFileMatchingHash(File file, String hash, String hashType) {
+        if (!file.exists()) {
+            return false;
+        }
+        try {
+            Hasher hasher = new Hasher(hashType, file);
+            return hasher.match(hash);
+        } catch (NoSuchAlgorithmException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
     public static String hex(Certificate cert) {
         byte[] encoded;
         try {
diff --git a/app/src/main/java/org/fdroid/fdroid/installer/ApkCache.java b/app/src/main/java/org/fdroid/fdroid/installer/ApkCache.java
index 47f6fb32e..c920ba13d 100644
--- a/app/src/main/java/org/fdroid/fdroid/installer/ApkCache.java
+++ b/app/src/main/java/org/fdroid/fdroid/installer/ApkCache.java
@@ -31,7 +31,6 @@ import org.fdroid.fdroid.data.SanitizedFile;
 
 import java.io.File;
 import java.io.IOException;
-import java.security.NoSuchAlgorithmException;
 
 public class ApkCache {
 
@@ -45,50 +44,32 @@ public class ApkCache {
      */
     public static SanitizedFile copyApkFromCacheToFiles(Context context, File apkFile, Apk expectedApk)
             throws IOException {
-        SanitizedFile sanitizedApkFile = null;
+        SanitizedFile sanitizedApkFile = SanitizedFile.knownSanitized(
+                File.createTempFile("install-", ".apk", context.getFilesDir()));
+        FileUtils.copyFile(apkFile, sanitizedApkFile);
 
-        try {
-            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!");
+        }
 
-            // verify copied file's hash with expected hash from Apk class
-            if (!verifyApkFile(sanitizedApkFile, expectedApk.hash, expectedApk.hashType)) {
-                FileUtils.deleteQuietly(apkFile);
-                throw new IOException(apkFile + " failed to verify!");
-            }
-
-            return sanitizedApkFile;
-        } catch (NoSuchAlgorithmException e) {
-            throw new RuntimeException(e);
-        } finally {
-            // 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);
-                    }
+        // 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();
-        }
-    }
+            }
+        }.start();
 
-    /**
-     * Checks the APK file against the provided hash, returning whether it is a match.
-     */
-    private static boolean verifyApkFile(File apkFile, String hash, String hashType)
-            throws NoSuchAlgorithmException {
-        if (!apkFile.exists()) {
-            return false;
-        }
-        Hasher hasher = new Hasher(hashType, apkFile);
-        return hasher.match(hash);
+        return sanitizedApkFile;
     }
 
     /**
@@ -108,19 +89,15 @@ public class ApkCache {
      * 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) {
-        try {
-            return apkFile.length() == apkToCheck.size &&
-                    verifyApkFile(apkFile, apkToCheck.hash, apkToCheck.hashType);
-        } catch (NoSuchAlgorithmException e) {
-            throw new RuntimeException(e);
-        }
+        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, Apk)}
+     * Using {@link Installer#installPackage(Uri, Uri)}
      * is fine since that does the right thing.
      */
     public static File getApkCacheDir(Context context) {