From 50f1b110a2903d8b3ececb6dd06d561bc04a6cf7 Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Wed, 26 Jan 2022 13:37:34 +0100 Subject: [PATCH] implement Hasher.isFileMatchingHash() using commons-codec --- .../main/java/org/fdroid/fdroid/Hasher.java | 36 +--------------- .../main/java/org/fdroid/fdroid/Utils.java | 9 ++++ .../org/fdroid/fdroid/installer/ApkCache.java | 6 +-- .../installer/InstallManagerService.java | 6 ++- .../java/org/fdroid/fdroid/UtilsTest.java | 42 +++++++++++++++++++ 5 files changed, 59 insertions(+), 40 deletions(-) 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