Add opt-in hash based module.zip verification system.

pull/55/head
Fox2Code 2 years ago
parent 6414bd8c9a
commit 8b086b7925

@ -47,8 +47,9 @@ public enum ActionButtonType {
if (moduleInfo == null) return; if (moduleInfo == null) return;
String updateZipUrl = moduleHolder.getUpdateZipUrl(); String updateZipUrl = moduleHolder.getUpdateZipUrl();
if (updateZipUrl == null) return; if (updateZipUrl == null) return;
String updateZipChecksum = moduleHolder.getUpdateZipChecksum();
IntentHelper.openInstaller(button.getContext(), updateZipUrl, IntentHelper.openInstaller(button.getContext(), updateZipUrl,
moduleInfo.name, moduleInfo.config); moduleInfo.name, moduleInfo.config, updateZipChecksum);
} }
}, },
UNINSTALL() { UNINSTALL() {

@ -13,6 +13,7 @@ public class Constants {
public static final String EXTRA_INSTALL_PATH = "extra_install_path"; 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_NAME = "extra_install_name";
public static final String EXTRA_INSTALL_CONFIG = "extra_install_config"; 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_PATCH = "extra_install_no_patch";
public static final String EXTRA_INSTALL_NO_EXTENSIONS = "extra_install_no_extensions"; public static final String EXTRA_INSTALL_NO_EXTENSIONS = "extra_install_no_extensions";
public static final String EXTRA_INSTALL_TEST_ROOTLESS = "extra_install_test_rootless"; public static final String EXTRA_INSTALL_TEST_ROOTLESS = "extra_install_test_rootless";

@ -69,10 +69,18 @@ public final class ModuleHolder implements Comparable<ModuleHolder> {
public String getUpdateZipUrl() { public String getUpdateZipUrl() {
return this.moduleInfo == null || (this.repoModule != null && 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; 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() { public String getMainModuleName() {
ModuleInfo moduleInfo = this.getMainModuleInfo(); ModuleInfo moduleInfo = this.getMainModuleInfo();
if (moduleInfo == null || moduleInfo.name == null) if (moduleInfo == null || moduleInfo.name == null)

@ -89,7 +89,7 @@ public enum NotificationType implements NotificationTypeCst {
} else { } else {
IntentHelper.openInstaller(compatActivity, d.getAbsolutePath(), IntentHelper.openInstaller(compatActivity, d.getAbsolutePath(),
compatActivity.getString( 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 BuildConfig.DEBUG && // Use debug mode if no root
InstallerInitializer.peekMagiskPath() == null); InstallerInitializer.peekMagiskPath() == null);
} }
@ -102,7 +102,7 @@ public enum NotificationType implements NotificationTypeCst {
} else if (s == IntentHelper.RESPONSE_URL) { } else if (s == IntentHelper.RESPONSE_URL) {
IntentHelper.openInstaller(compatActivity, u.toString(), IntentHelper.openInstaller(compatActivity, u.toString(),
compatActivity.getString( 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 BuildConfig.DEBUG && // Use debug mode if no root
InstallerInitializer.peekMagiskPath() == null); InstallerInitializer.peekMagiskPath() == null);
} }

@ -79,7 +79,8 @@ public class AndroidacyWebAPI {
moduleUrl + " " + installTitle + " " + checksum); moduleUrl + " " + installTitle + " " + checksum);
Uri uri = Uri.parse(moduleUrl); Uri uri = Uri.parse(moduleUrl);
if (uri.getScheme().equals("https") && uri.getHost().endsWith(".androidacy.com")) { 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 { } else {
this.activity.forceBackPressed(); this.activity.forceBackPressed();
} }

@ -22,6 +22,7 @@ import com.fox2code.mmm.R;
import com.fox2code.mmm.compat.CompatActivity; import com.fox2code.mmm.compat.CompatActivity;
import com.fox2code.mmm.utils.FastException; import com.fox2code.mmm.utils.FastException;
import com.fox2code.mmm.utils.Files; import com.fox2code.mmm.utils.Files;
import com.fox2code.mmm.utils.Hashes;
import com.fox2code.mmm.utils.Http; import com.fox2code.mmm.utils.Http;
import com.fox2code.mmm.utils.IntentHelper; import com.fox2code.mmm.utils.IntentHelper;
import com.google.android.material.progressindicator.LinearProgressIndicator; import com.google.android.material.progressindicator.LinearProgressIndicator;
@ -54,6 +55,7 @@ public class InstallerActivity extends CompatActivity {
final Intent intent = this.getIntent(); final Intent intent = this.getIntent();
final String target; final String target;
final String name; final String name;
final String checksum;
final boolean noPatch; final boolean noPatch;
final boolean noExtensions; final boolean noExtensions;
final boolean rootless; final boolean rootless;
@ -66,6 +68,7 @@ public class InstallerActivity extends CompatActivity {
} }
target = intent.getStringExtra(Constants.EXTRA_INSTALL_PATH); target = intent.getStringExtra(Constants.EXTRA_INSTALL_PATH);
name = intent.getStringExtra(Constants.EXTRA_INSTALL_NAME); name = intent.getStringExtra(Constants.EXTRA_INSTALL_NAME);
checksum = intent.getStringExtra(Constants.EXTRA_INSTALL_CHECKSUM);
noPatch = intent.getBooleanExtra(Constants.EXTRA_INSTALL_NO_PATCH, false); noPatch = intent.getBooleanExtra(Constants.EXTRA_INSTALL_NO_PATCH, false);
noExtensions = intent.getBooleanExtra(// Allow intent to disable extensions noExtensions = intent.getBooleanExtra(// Allow intent to disable extensions
Constants.EXTRA_INSTALL_NO_EXTENSIONS, false); Constants.EXTRA_INSTALL_NO_EXTENSIONS, false);
@ -122,6 +125,15 @@ public class InstallerActivity extends CompatActivity {
this.progressIndicator.setProgressCompat(progress, true); 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) { if (noPatch) {
try (OutputStream outputStream = new FileOutputStream(moduleCache)) { try (OutputStream outputStream = new FileOutputStream(moduleCache)) {
outputStream.write(rawModule); outputStream.write(rawModule);
@ -152,13 +164,31 @@ public class InstallerActivity extends CompatActivity {
} }
}, "Module download Thread").start(); }, "Module download Thread").start();
} else { } 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); this.installerTerminal.addLine("- Installing " + name);
new Thread(() -> this.doInstall( new Thread(() -> this.doInstall(
this.toDelete = new File(target), noExtensions, rootless), this.toDelete = moduleFile, noExtensions, rootless),
"Install Thread").start(); "Install Thread").start();
} }
} }
private void doInstall(File file,boolean noExtensions,boolean rootless) { private void doInstall(File file,boolean noExtensions,boolean rootless) {
Log.i(TAG, "Installing: " + moduleCache.getName()); Log.i(TAG, "Installing: " + moduleCache.getName());
InstallerController installerController = new InstallerController( InstallerController installerController = new InstallerController(

@ -15,6 +15,7 @@ public class LocalModuleInfo extends ModuleInfo {
public long updateVersionCode = Long.MIN_VALUE; public long updateVersionCode = Long.MIN_VALUE;
public String updateZipUrl; public String updateZipUrl;
public String updateChangeLog; public String updateChangeLog;
public String updateChecksum;
public LocalModuleInfo(String id) { public LocalModuleInfo(String id) {
super(id); super(id);
@ -29,6 +30,7 @@ public class LocalModuleInfo extends ModuleInfo {
this.updateVersionCode = jsonUpdate.getLong("versionCode"); this.updateVersionCode = jsonUpdate.getLong("versionCode");
this.updateZipUrl = jsonUpdate.getString("zipUrl"); this.updateZipUrl = jsonUpdate.getString("zipUrl");
this.updateChangeLog = jsonUpdate.optString("changelog"); this.updateChangeLog = jsonUpdate.optString("changelog");
this.updateChecksum = jsonUpdate.optString("checksum");
if (this.updateZipUrl.isEmpty()) throw FastException.INSTANCE; if (this.updateZipUrl.isEmpty()) throw FastException.INSTANCE;
this.updateVersion = PropUtils.shortenVersionName( this.updateVersion = PropUtils.shortenVersionName(
this.updateVersion.trim(), this.updateVersionCode); this.updateVersion.trim(), this.updateVersionCode);
@ -37,6 +39,7 @@ public class LocalModuleInfo extends ModuleInfo {
this.updateVersionCode = Long.MIN_VALUE; this.updateVersionCode = Long.MIN_VALUE;
this.updateZipUrl = null; this.updateZipUrl = null;
this.updateChangeLog = null; this.updateChangeLog = null;
this.updateChecksum = null;
Log.w("LocalModuleInfo", Log.w("LocalModuleInfo",
"Failed update checking for module: " + this.id, e); "Failed update checking for module: " + this.id, e);
} }

@ -118,6 +118,7 @@ public class RepoData {
String moduleNotesUrl = module.getString("notes_url"); String moduleNotesUrl = module.getString("notes_url");
String modulePropsUrl = module.getString("prop_url"); String modulePropsUrl = module.getString("prop_url");
String moduleZipUrl = module.getString("zip_url"); String moduleZipUrl = module.getString("zip_url");
String moduleChecksum = module.optString("checksum");
if (moduleLastUpdateSpecial != null) { // Fix last update time if (moduleLastUpdateSpecial != null) { // Fix last update time
Log.d("RepoData", "Data: " + moduleLastUpdate + " -> " + Log.d("RepoData", "Data: " + moduleLastUpdate + " -> " +
moduleLastUpdateSpecial + " for " + moduleId); moduleLastUpdateSpecial + " for " + moduleId);
@ -147,6 +148,7 @@ public class RepoData {
repoModule.notesUrl = moduleNotesUrl; repoModule.notesUrl = moduleNotesUrl;
repoModule.propUrl = modulePropsUrl; repoModule.propUrl = modulePropsUrl;
repoModule.zipUrl = moduleZipUrl; repoModule.zipUrl = moduleZipUrl;
repoModule.checksum = moduleChecksum;
} }
// Remove no longer existing modules // Remove no longer existing modules
Iterator<RepoModule> moduleInfoIterator = this.moduleHashMap.values().iterator(); Iterator<RepoModule> moduleInfoIterator = this.moduleHashMap.values().iterator();

@ -12,6 +12,7 @@ import com.fox2code.mmm.utils.Http;
import com.fox2code.mmm.utils.PropUtils; import com.fox2code.mmm.utils.PropUtils;
import java.io.File; import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
@ -230,7 +231,8 @@ public final class RepoManager {
case MAGISK_ALT_REPO_JSDELIVR: case MAGISK_ALT_REPO_JSDELIVR:
return "magisk_alt_repo"; return "magisk_alt_repo";
default: default:
return "repo_" + Hashes.hashSha1(url); return "repo_" + Hashes.hashSha1(
url.getBytes(StandardCharsets.UTF_8));
} }
} }

@ -10,6 +10,7 @@ public class RepoModule {
public String propUrl; public String propUrl;
public String zipUrl; public String zipUrl;
public String notesUrl; public String notesUrl;
public String checksum;
boolean processed; boolean processed;
public RepoModule(String id) { public RepoModule(String id) {

@ -1,10 +1,13 @@
package com.fox2code.mmm.utils; package com.fox2code.mmm.utils;
import java.nio.charset.StandardCharsets; import android.util.Log;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.util.Locale;
public class Hashes { public class Hashes {
private static final String TAG = "Hashes";
private static final char[] HEX_ARRAY = "0123456789abcdef".toCharArray(); private static final char[] HEX_ARRAY = "0123456789abcdef".toCharArray();
public static String bytesToHex(byte[] bytes) { public static String bytesToHex(byte[] bytes) {
char[] hexChars = new char[bytes.length * 2]; char[] hexChars = new char[bytes.length * 2];
@ -16,13 +19,69 @@ public class Hashes {
return new String(hexChars); 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 { try {
MessageDigest md = MessageDigest.getInstance("SHA-1"); MessageDigest md = MessageDigest.getInstance("SHA-1");
return bytesToHex(md.digest(input.getBytes(StandardCharsets.UTF_8))); return bytesToHex(md.digest(input));
} catch (NoSuchAlgorithmException e) { } catch (NoSuchAlgorithmException e) {
throw new RuntimeException(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));
}
} }

@ -104,12 +104,13 @@ public class IntentHelper {
} }
} }
public static void openInstaller(Context context, String url, String title, String config) { public static void openInstaller(Context context, String url, String title,
openInstaller(context, url, title, config, false, false); String config, String checksum) {
openInstaller(context, url, title, config, checksum, false, false);
} }
public static void openInstaller(Context context, String url, String title, String config, public static void openInstaller(Context context, String url, String title, String config,
boolean noPatch,boolean testDebug) { String checksum, boolean noPatch,boolean testDebug) {
try { try {
Intent intent = new Intent(context, InstallerActivity.class); Intent intent = new Intent(context, InstallerActivity.class);
intent.setAction(Constants.INTENT_INSTALL_INTERNAL); intent.setAction(Constants.INTENT_INSTALL_INTERNAL);
@ -118,6 +119,8 @@ public class IntentHelper {
intent.putExtra(Constants.EXTRA_INSTALL_NAME, title); intent.putExtra(Constants.EXTRA_INSTALL_NAME, title);
if (config != null && !config.isEmpty()) if (config != null && !config.isEmpty())
intent.putExtra(Constants.EXTRA_INSTALL_CONFIG, config); intent.putExtra(Constants.EXTRA_INSTALL_CONFIG, config);
if (checksum != null && !checksum.isEmpty())
intent.putExtra(Constants.EXTRA_INSTALL_CHECKSUM, checksum);
if (noPatch) if (noPatch)
intent.putExtra(Constants.EXTRA_INSTALL_NO_PATCH, true); intent.putExtra(Constants.EXTRA_INSTALL_NO_PATCH, true);
if (testDebug && BuildConfig.DEBUG) if (testDebug && BuildConfig.DEBUG)

Loading…
Cancel
Save