diff --git a/app/src/main/java/org/fdroid/fdroid/Hasher.java b/app/src/main/java/org/fdroid/fdroid/Hasher.java
index ade76c33f..0a05745d4 100644
--- a/app/src/main/java/org/fdroid/fdroid/Hasher.java
+++ b/app/src/main/java/org/fdroid/fdroid/Hasher.java
@@ -28,20 +28,14 @@ import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
-import java.util.Locale;
public class Hasher {
private MessageDigest digest;
private File file;
- private byte[] array;
+ private final byte[] array;
private String hashCache;
- public Hasher(String type, File f) throws NoSuchAlgorithmException {
- init(type);
- this.file = f;
- }
-
public Hasher(String type, byte[] a) throws NoSuchAlgorithmException {
init(type);
this.array = a;
@@ -84,34 +78,6 @@ public class Hasher {
return hashCache;
}
- // Compare the calculated hash to another string, ignoring case,
- // returning true if they are equal. The empty string and null are
- // considered non-matching.
- public boolean match(String otherHash) {
- if (otherHash == null) {
- return false;
- }
- if (hashCache == null) {
- getHash();
- }
- 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/Utils.java b/app/src/main/java/org/fdroid/fdroid/Utils.java
index c5aa41abf..c1d13c538 100644
--- a/app/src/main/java/org/fdroid/fdroid/Utils.java
+++ b/app/src/main/java/org/fdroid/fdroid/Utils.java
@@ -418,6 +418,15 @@ public final class Utils {
return ret;
}
+ /**
+ * 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 == null || !file.exists() || TextUtils.isEmpty(hash)) {
+ return false;
+ }
+ return hash.equals(getFileHexDigest(file, hashType));
+ }
/**
* Get the fingerprint used to represent an APK signing key in F-Droid.
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 e7eaefd2f..24fff30b4 100644
--- a/app/src/main/java/org/fdroid/fdroid/installer/ApkCache.java
+++ b/app/src/main/java/org/fdroid/fdroid/installer/ApkCache.java
@@ -25,7 +25,7 @@ import android.content.pm.PackageInfo;
import android.net.Uri;
import org.apache.commons.io.FileUtils;
-import org.fdroid.fdroid.Hasher;
+import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.data.Apk;
import org.fdroid.fdroid.data.App;
import org.fdroid.fdroid.data.AppProvider;
@@ -89,7 +89,7 @@ public class ApkCache {
FileUtils.copyFile(apkFile, sanitizedApkFile);
// verify copied file's hash with expected hash from Apk class
- if (verifyHash && !Hasher.isFileMatchingHash(sanitizedApkFile, hash, hashType)) {
+ if (verifyHash && !Utils.isFileMatchingHash(sanitizedApkFile, hash, hashType)) {
FileUtils.deleteQuietly(apkFile);
throw new IOException(apkFile + " failed to verify!");
}
@@ -137,7 +137,7 @@ public class ApkCache {
*/
public static boolean apkIsCached(File apkFile, Apk apkToCheck) {
return apkFile.length() == apkToCheck.size &&
- Hasher.isFileMatchingHash(apkFile, apkToCheck.hash, apkToCheck.hashType);
+ Utils.isFileMatchingHash(apkFile, apkToCheck.hash, apkToCheck.hashType);
}
/**
diff --git a/app/src/main/java/org/fdroid/fdroid/installer/InstallManagerService.java b/app/src/main/java/org/fdroid/fdroid/installer/InstallManagerService.java
index c9a2c7671..f79e02cc2 100644
--- a/app/src/main/java/org/fdroid/fdroid/installer/InstallManagerService.java
+++ b/app/src/main/java/org/fdroid/fdroid/installer/InstallManagerService.java
@@ -17,7 +17,6 @@ import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.WildcardFileFilter;
import org.fdroid.fdroid.AppUpdateStatusManager;
import org.fdroid.fdroid.FDroidApp;
-import org.fdroid.fdroid.Hasher;
import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.compat.PackageManagerCompat;
import org.fdroid.fdroid.data.Apk;
@@ -33,6 +32,8 @@ import java.io.IOException;
import androidx.annotation.NonNull;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+import static vendored.org.apache.commons.codec.digest.MessageDigestAlgorithms.SHA_256;
+
/**
* Manages the whole process when a background update triggers an install or the user
* requests an APK to be installed. It handles checking whether the APK is cached,
@@ -86,6 +87,7 @@ import androidx.localbroadcastmanager.content.LocalBroadcastManager;
* has a different name/description/summary, etc).
*
* @see APK Expansion Files
+ * @see forced to vendor Apache Commons Codec
*/
@SuppressWarnings("LineLength")
public class InstallManagerService extends Service {
@@ -281,7 +283,7 @@ public class InstallManagerService extends Service {
+ " to " + localApkUri);
try {
- if (Hasher.isFileMatchingHash(localFile, hash, "sha256")) {
+ if (Utils.isFileMatchingHash(localFile, hash, SHA_256)) {
Utils.debugLog(TAG, "Installing OBB " + localFile + " to " + obbDestFile);
FileUtils.forceMkdirParent(obbDestFile);
FileUtils.copyFile(localFile, obbDestFile);
diff --git a/app/src/test/java/org/fdroid/fdroid/UtilsTest.java b/app/src/test/java/org/fdroid/fdroid/UtilsTest.java
index 19a5bec6d..3d1242aaa 100644
--- a/app/src/test/java/org/fdroid/fdroid/UtilsTest.java
+++ b/app/src/test/java/org/fdroid/fdroid/UtilsTest.java
@@ -169,6 +169,48 @@ public class UtilsTest {
Utils.formatFingerprint(context, "3082035e30820246a00302010202044c49cd00300d06092a864886f70d01010505003071310b300906035504061302554b3110300e06035504081307556e6b6e6f776e3111300f0603550407130857657468657262793110300e060355040a1307556e6b6e6f776e3110300e060355040b1307556e6b6e6f776e311930170603550403131043696172616e2047756c746e69656b73301e170d3130303732333137313032345a170d3337313230383137313032345a3071310b300906035504061302554b3110300e06035504081307556e6b6e6f776e3111300f0603550407130857657468657262793110300e060355040a1307556e6b6e6f776e3110300e060355040b1307556e6b6e6f776e311930170603550403131043696172616e2047756c746e69656b7330820122300d06092a864886f70d01010105000382010f003082010a028201010096d075e47c014e7822c89fd67f795d23203e2a8843f53ba4e6b1bf5f2fd0e225938267cfcae7fbf4fe596346afbaf4070fdb91f66fbcdf2348a3d92430502824f80517b156fab00809bdc8e631bfa9afd42d9045ab5fd6d28d9e140afc1300917b19b7c6c4df4a494cf1f7cb4a63c80d734265d735af9e4f09455f427aa65a53563f87b336ca2c19d244fcbba617ba0b19e56ed34afe0b253ab91e2fdb1271f1b9e3c3232027ed8862a112f0706e234cf236914b939bcf959821ecb2a6c18057e070de3428046d94b175e1d89bd795e535499a091f5bc65a79d539a8d43891ec504058acb28c08393b5718b57600a211e803f4a634e5c57f25b9b8c4422c6fd90203010001300d06092a864886f70d0101050500038201010008e4ef699e9807677ff56753da73efb2390d5ae2c17e4db691d5df7a7b60fc071ae509c5414be7d5da74df2811e83d3668c4a0b1abc84b9fa7d96b4cdf30bba68517ad2a93e233b042972ac0553a4801c9ebe07bf57ebe9a3b3d6d663965260e50f3b8f46db0531761e60340a2bddc3426098397fda54044a17e5244549f9869b460ca5e6e216b6f6a2db0580b480ca2afe6ec6b46eedacfa4aa45038809ece0c5978653d6c85f678e7f5a2156d1bedd8117751e64a4b0dcd140f3040b021821a8d93aed8d01ba36db6c82372211fed714d9a32607038cdfd565bd529ffc637212aaa2c224ef22b603eccefb5bf1e085c191d4b24fe742b17ab3f55d4e6f05ef"));
}
+ @Test
+ public void testIsFileMatchingHash() {
+ Utils.isFileMatchingHash(null, null, null);
+ Utils.isFileMatchingHash(new File("/"), "", null);
+
+ assertFalse(Utils.isFileMatchingHash(null, null, ""));
+ assertFalse(Utils.isFileMatchingHash(null, null, SHA_256));
+ assertFalse(Utils.isFileMatchingHash(new File("/"), null, SHA_256));
+ assertFalse(Utils.isFileMatchingHash(new File("/"), "", SHA_256));
+
+ assertTrue(Utils.isFileMatchingHash(TestUtils.copyResourceToTempFile("Norway_bouvet_europe_2.obf.zip"),
+ "6e8a584e004c6cd26d3822a04b0591e355dc5d07b5a3d0f8e309443f47ad1208", SHA_256));
+ assertTrue(Utils.isFileMatchingHash(TestUtils.copyResourceToTempFile("install_history_all"),
+ "4ad118d4a600dcc104834635d248a89e337fc91b173163d646996b9c54d77372", SHA_256));
+ assertFalse("wrong sha256 value",
+ Utils.isFileMatchingHash(TestUtils.copyResourceToTempFile("simpleIndex.jar"),
+ "6e8a584e004c6cd26d3822a04b0591e355dc5d07b5a3d0f8e309443f47ad1208", SHA_256));
+
+ File f = TestUtils.copyResourceToTempFile("additional_repos.xml");
+ assertTrue(Utils.isFileMatchingHash(f,
+ "47ad2284d3042373e6280012cc10e9b82f75352db6d6d9bab1e06934b7b1dab7", SHA_256));
+ assertFalse("uppercase fails",
+ Utils.isFileMatchingHash(f,
+ "47AD2284D3042373E6280012CC10E9B82F75352DB6D6D9BAB1E06934B7B1DAB7", SHA_256));
+ assertFalse("one uppercase digit fails",
+ Utils.isFileMatchingHash(f,
+ "47Ad2284d3042373e6280012cc10e9b82f75352db6d6d9bab1e06934b7b1dab7", SHA_256));
+ assertFalse("missing digit fails",
+ Utils.isFileMatchingHash(f,
+ "47ad2284d3042373e6280012cc10e9b82f75352db6d6d9bab1e06934b7b1dab", SHA_256));
+ assertFalse("extra digit fails",
+ Utils.isFileMatchingHash(f,
+ "47ad2284d3042373e6280012cc10e9b82f75352db6d6d9bab1e06934b7b1dab71", SHA_256));
+ assertFalse("all zeros fails",
+ Utils.isFileMatchingHash(f,
+ "0000000000000000000000000000000000000000000000000000000000000000", SHA_256));
+ assertFalse("null fails",
+ Utils.isFileMatchingHash(f, null, SHA_256));
+ assertFalse("empty string fails",
+ Utils.isFileMatchingHash(f, "", SHA_256));
+ }
+
@Test
public void testCalcFingerprintString() {
// these should pass