diff --git a/app/src/main/java/com/fox2code/mmm/ActionButtonType.java b/app/src/main/java/com/fox2code/mmm/ActionButtonType.java index a3786f8..105367c 100644 --- a/app/src/main/java/com/fox2code/mmm/ActionButtonType.java +++ b/app/src/main/java/com/fox2code/mmm/ActionButtonType.java @@ -47,8 +47,9 @@ public enum ActionButtonType { if (moduleInfo == null) return; String updateZipUrl = moduleHolder.getUpdateZipUrl(); if (updateZipUrl == null) return; + String updateZipChecksum = moduleHolder.getUpdateZipChecksum(); IntentHelper.openInstaller(button.getContext(), updateZipUrl, - moduleInfo.name, moduleInfo.config); + moduleInfo.name, moduleInfo.config, updateZipChecksum); } }, UNINSTALL() { diff --git a/app/src/main/java/com/fox2code/mmm/Constants.java b/app/src/main/java/com/fox2code/mmm/Constants.java index 37e3a3b..1b87bb4 100644 --- a/app/src/main/java/com/fox2code/mmm/Constants.java +++ b/app/src/main/java/com/fox2code/mmm/Constants.java @@ -13,6 +13,7 @@ public class Constants { public static final String EXTRA_INSTALL_PATH = "extra_install_path"; public static final String EXTRA_INSTALL_NAME = "extra_install_name"; public static final String EXTRA_INSTALL_CONFIG = "extra_install_config"; + public static final String EXTRA_INSTALL_CHECKSUM = "extra_install_checksum"; public static final String EXTRA_INSTALL_NO_PATCH = "extra_install_no_patch"; public static final String EXTRA_INSTALL_NO_EXTENSIONS = "extra_install_no_extensions"; public static final String EXTRA_INSTALL_TEST_ROOTLESS = "extra_install_test_rootless"; diff --git a/app/src/main/java/com/fox2code/mmm/ModuleHolder.java b/app/src/main/java/com/fox2code/mmm/ModuleHolder.java index 82b884d..0de0fa6 100644 --- a/app/src/main/java/com/fox2code/mmm/ModuleHolder.java +++ b/app/src/main/java/com/fox2code/mmm/ModuleHolder.java @@ -69,10 +69,18 @@ public final class ModuleHolder implements Comparable { public String getUpdateZipUrl() { return this.moduleInfo == null || (this.repoModule != null && - this.moduleInfo.updateVersionCode < this.repoModule.lastUpdated) ? + this.moduleInfo.updateVersionCode < + this.repoModule.moduleInfo.versionCode) ? this.repoModule.zipUrl : this.moduleInfo.updateZipUrl; } + public String getUpdateZipChecksum() { + return this.moduleInfo == null || (this.repoModule != null && + this.moduleInfo.updateVersionCode < + this.repoModule.moduleInfo.versionCode) ? + this.repoModule.checksum : this.moduleInfo.updateChecksum; + } + public String getMainModuleName() { ModuleInfo moduleInfo = this.getMainModuleInfo(); if (moduleInfo == null || moduleInfo.name == null) diff --git a/app/src/main/java/com/fox2code/mmm/NotificationType.java b/app/src/main/java/com/fox2code/mmm/NotificationType.java index f0acb20..13c8391 100644 --- a/app/src/main/java/com/fox2code/mmm/NotificationType.java +++ b/app/src/main/java/com/fox2code/mmm/NotificationType.java @@ -89,7 +89,7 @@ public enum NotificationType implements NotificationTypeCst { } else { IntentHelper.openInstaller(compatActivity, d.getAbsolutePath(), compatActivity.getString( - R.string.local_install_title), null, false, + R.string.local_install_title), null, null, false, BuildConfig.DEBUG && // Use debug mode if no root InstallerInitializer.peekMagiskPath() == null); } @@ -102,7 +102,7 @@ public enum NotificationType implements NotificationTypeCst { } else if (s == IntentHelper.RESPONSE_URL) { IntentHelper.openInstaller(compatActivity, u.toString(), compatActivity.getString( - R.string.remote_install_title), null, false, + R.string.remote_install_title), null, null, false, BuildConfig.DEBUG && // Use debug mode if no root InstallerInitializer.peekMagiskPath() == null); } diff --git a/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyWebAPI.java b/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyWebAPI.java index 11d35f0..797d355 100644 --- a/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyWebAPI.java +++ b/app/src/main/java/com/fox2code/mmm/androidacy/AndroidacyWebAPI.java @@ -79,7 +79,8 @@ public class AndroidacyWebAPI { moduleUrl + " " + installTitle + " " + checksum); Uri uri = Uri.parse(moduleUrl); if (uri.getScheme().equals("https") && uri.getHost().endsWith(".androidacy.com")) { - IntentHelper.openInstaller(this.activity, moduleUrl, installTitle, null); + IntentHelper.openInstaller(this.activity, + moduleUrl, installTitle, null, checksum); } else { this.activity.forceBackPressed(); } diff --git a/app/src/main/java/com/fox2code/mmm/installer/InstallerActivity.java b/app/src/main/java/com/fox2code/mmm/installer/InstallerActivity.java index 03cc408..6cfa7dc 100644 --- a/app/src/main/java/com/fox2code/mmm/installer/InstallerActivity.java +++ b/app/src/main/java/com/fox2code/mmm/installer/InstallerActivity.java @@ -22,6 +22,7 @@ import com.fox2code.mmm.R; import com.fox2code.mmm.compat.CompatActivity; import com.fox2code.mmm.utils.FastException; import com.fox2code.mmm.utils.Files; +import com.fox2code.mmm.utils.Hashes; import com.fox2code.mmm.utils.Http; import com.fox2code.mmm.utils.IntentHelper; import com.google.android.material.progressindicator.LinearProgressIndicator; @@ -54,6 +55,7 @@ public class InstallerActivity extends CompatActivity { final Intent intent = this.getIntent(); final String target; final String name; + final String checksum; final boolean noPatch; final boolean noExtensions; final boolean rootless; @@ -66,6 +68,7 @@ public class InstallerActivity extends CompatActivity { } target = intent.getStringExtra(Constants.EXTRA_INSTALL_PATH); name = intent.getStringExtra(Constants.EXTRA_INSTALL_NAME); + checksum = intent.getStringExtra(Constants.EXTRA_INSTALL_CHECKSUM); noPatch = intent.getBooleanExtra(Constants.EXTRA_INSTALL_NO_PATCH, false); noExtensions = intent.getBooleanExtra(// Allow intent to disable extensions Constants.EXTRA_INSTALL_NO_EXTENSIONS, false); @@ -122,6 +125,15 @@ public class InstallerActivity extends CompatActivity { this.progressIndicator.setProgressCompat(progress, true); }); }); + if (checksum != null && !checksum.isEmpty()) { + Log.d(TAG, "Checking for checksum: " + checksum); + this.installerTerminal.addLine("- Checking file integrity"); + if (!Hashes.checkSumMatch(rawModule, checksum)) { + this.setInstallStateFinished(false, + "! File integrity check failed", ""); + return; + } + } if (noPatch) { try (OutputStream outputStream = new FileOutputStream(moduleCache)) { outputStream.write(rawModule); @@ -152,13 +164,31 @@ public class InstallerActivity extends CompatActivity { } }, "Module download Thread").start(); } else { + final File moduleFile = new File(target); + if (checksum != null && !checksum.isEmpty()) { + Log.d(TAG, "Checking for checksum: " + checksum); + this.installerTerminal.addLine("- Checking file integrity"); + try { + if (!Hashes.checkSumMatch(Files.readSU(moduleFile), checksum)) { + this.setInstallStateFinished(false, + "! File integrity check failed", ""); + return; + } + } catch (IOException e) { + Log.e(TAG, "Failed to read file for checksum check", e); + this.setInstallStateFinished(false, + "! File integrity check failed", ""); + return; + } + } this.installerTerminal.addLine("- Installing " + name); new Thread(() -> this.doInstall( - this.toDelete = new File(target), noExtensions, rootless), + this.toDelete = moduleFile, noExtensions, rootless), "Install Thread").start(); } } + private void doInstall(File file,boolean noExtensions,boolean rootless) { Log.i(TAG, "Installing: " + moduleCache.getName()); InstallerController installerController = new InstallerController( diff --git a/app/src/main/java/com/fox2code/mmm/manager/LocalModuleInfo.java b/app/src/main/java/com/fox2code/mmm/manager/LocalModuleInfo.java index 8c8afc2..b29c6a0 100644 --- a/app/src/main/java/com/fox2code/mmm/manager/LocalModuleInfo.java +++ b/app/src/main/java/com/fox2code/mmm/manager/LocalModuleInfo.java @@ -15,6 +15,7 @@ public class LocalModuleInfo extends ModuleInfo { public long updateVersionCode = Long.MIN_VALUE; public String updateZipUrl; public String updateChangeLog; + public String updateChecksum; public LocalModuleInfo(String id) { super(id); @@ -29,6 +30,7 @@ public class LocalModuleInfo extends ModuleInfo { this.updateVersionCode = jsonUpdate.getLong("versionCode"); this.updateZipUrl = jsonUpdate.getString("zipUrl"); this.updateChangeLog = jsonUpdate.optString("changelog"); + this.updateChecksum = jsonUpdate.optString("checksum"); if (this.updateZipUrl.isEmpty()) throw FastException.INSTANCE; this.updateVersion = PropUtils.shortenVersionName( this.updateVersion.trim(), this.updateVersionCode); @@ -37,6 +39,7 @@ public class LocalModuleInfo extends ModuleInfo { this.updateVersionCode = Long.MIN_VALUE; this.updateZipUrl = null; this.updateChangeLog = null; + this.updateChecksum = null; Log.w("LocalModuleInfo", "Failed update checking for module: " + this.id, e); } diff --git a/app/src/main/java/com/fox2code/mmm/repo/RepoData.java b/app/src/main/java/com/fox2code/mmm/repo/RepoData.java index 457fc23..dc43cf2 100644 --- a/app/src/main/java/com/fox2code/mmm/repo/RepoData.java +++ b/app/src/main/java/com/fox2code/mmm/repo/RepoData.java @@ -118,6 +118,7 @@ public class RepoData { String moduleNotesUrl = module.getString("notes_url"); String modulePropsUrl = module.getString("prop_url"); String moduleZipUrl = module.getString("zip_url"); + String moduleChecksum = module.optString("checksum"); if (moduleLastUpdateSpecial != null) { // Fix last update time Log.d("RepoData", "Data: " + moduleLastUpdate + " -> " + moduleLastUpdateSpecial + " for " + moduleId); @@ -147,6 +148,7 @@ public class RepoData { repoModule.notesUrl = moduleNotesUrl; repoModule.propUrl = modulePropsUrl; repoModule.zipUrl = moduleZipUrl; + repoModule.checksum = moduleChecksum; } // Remove no longer existing modules Iterator moduleInfoIterator = this.moduleHashMap.values().iterator(); diff --git a/app/src/main/java/com/fox2code/mmm/repo/RepoManager.java b/app/src/main/java/com/fox2code/mmm/repo/RepoManager.java index a9f5c6e..334e829 100644 --- a/app/src/main/java/com/fox2code/mmm/repo/RepoManager.java +++ b/app/src/main/java/com/fox2code/mmm/repo/RepoManager.java @@ -12,6 +12,7 @@ import com.fox2code.mmm.utils.Http; import com.fox2code.mmm.utils.PropUtils; import java.io.File; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; @@ -230,7 +231,8 @@ public final class RepoManager { case MAGISK_ALT_REPO_JSDELIVR: return "magisk_alt_repo"; default: - return "repo_" + Hashes.hashSha1(url); + return "repo_" + Hashes.hashSha1( + url.getBytes(StandardCharsets.UTF_8)); } } diff --git a/app/src/main/java/com/fox2code/mmm/repo/RepoModule.java b/app/src/main/java/com/fox2code/mmm/repo/RepoModule.java index db00f2f..2bcab7b 100644 --- a/app/src/main/java/com/fox2code/mmm/repo/RepoModule.java +++ b/app/src/main/java/com/fox2code/mmm/repo/RepoModule.java @@ -10,6 +10,7 @@ public class RepoModule { public String propUrl; public String zipUrl; public String notesUrl; + public String checksum; boolean processed; public RepoModule(String id) { diff --git a/app/src/main/java/com/fox2code/mmm/utils/Hashes.java b/app/src/main/java/com/fox2code/mmm/utils/Hashes.java index d92402a..50cdc63 100644 --- a/app/src/main/java/com/fox2code/mmm/utils/Hashes.java +++ b/app/src/main/java/com/fox2code/mmm/utils/Hashes.java @@ -1,10 +1,13 @@ package com.fox2code.mmm.utils; -import java.nio.charset.StandardCharsets; +import android.util.Log; + import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.util.Locale; public class Hashes { + private static final String TAG = "Hashes"; private static final char[] HEX_ARRAY = "0123456789abcdef".toCharArray(); public static String bytesToHex(byte[] bytes) { char[] hexChars = new char[bytes.length * 2]; @@ -16,13 +19,69 @@ public class Hashes { return new String(hexChars); } - public static String hashSha1(String input) { + public static String hashMd5(byte[] input) { + try { + MessageDigest md = MessageDigest.getInstance("MD5"); + + return bytesToHex(md.digest(input)); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } + + public static String hashSha1(byte[] input) { try { MessageDigest md = MessageDigest.getInstance("SHA-1"); - return bytesToHex(md.digest(input.getBytes(StandardCharsets.UTF_8))); + return bytesToHex(md.digest(input)); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } } + + public static String hashSha256(byte[] input) { + try { + MessageDigest md = MessageDigest.getInstance("SHA-256"); + + return bytesToHex(md.digest(input)); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } + + public static String hashSha512(byte[] input) { + try { + MessageDigest md = MessageDigest.getInstance("SHA-512"); + + return bytesToHex(md.digest(input)); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } + + /** + * Check if the checksum match a file by picking the correct + * hashing algorithm depending on the length of the checksum + */ + public static boolean checkSumMatch(byte[] data, String checksum) { + String hash; + switch (checksum.length()) { + case 0: + return true; // No checksum + case 32: + hash = Hashes.hashMd5(data); break; + case 40: + hash = Hashes.hashSha1(data); break; + case 64: + hash = Hashes.hashSha256(data); break; + case 128: + hash = Hashes.hashSha512(data); break; + default: + Log.e(TAG, "No hash algorithm for " + + checksum.length() * 8 + "bit checksums"); + return false; + } + Log.d(TAG, "Checksum result (data: " + hash+ ",expected: " + checksum); + return hash.equals(checksum.toLowerCase(Locale.ROOT)); + } } diff --git a/app/src/main/java/com/fox2code/mmm/utils/IntentHelper.java b/app/src/main/java/com/fox2code/mmm/utils/IntentHelper.java index d4d68ab..127c9b3 100644 --- a/app/src/main/java/com/fox2code/mmm/utils/IntentHelper.java +++ b/app/src/main/java/com/fox2code/mmm/utils/IntentHelper.java @@ -104,12 +104,13 @@ public class IntentHelper { } } - public static void openInstaller(Context context, String url, String title, String config) { - openInstaller(context, url, title, config, false, false); + public static void openInstaller(Context context, String url, String title, + String config, String checksum) { + openInstaller(context, url, title, config, checksum, false, false); } public static void openInstaller(Context context, String url, String title, String config, - boolean noPatch,boolean testDebug) { + String checksum, boolean noPatch,boolean testDebug) { try { Intent intent = new Intent(context, InstallerActivity.class); intent.setAction(Constants.INTENT_INSTALL_INTERNAL); @@ -118,6 +119,8 @@ public class IntentHelper { intent.putExtra(Constants.EXTRA_INSTALL_NAME, title); if (config != null && !config.isEmpty()) intent.putExtra(Constants.EXTRA_INSTALL_CONFIG, config); + if (checksum != null && !checksum.isEmpty()) + intent.putExtra(Constants.EXTRA_INSTALL_CHECKSUM, checksum); if (noPatch) intent.putExtra(Constants.EXTRA_INSTALL_NO_PATCH, true); if (testDebug && BuildConfig.DEBUG)