From 47b7f256c54c0ea935a158a4a615811462085719 Mon Sep 17 00:00:00 2001 From: Maik Marschner Date: Fri, 27 Oct 2023 23:33:48 +0200 Subject: [PATCH] Add sha256 support for library verification to the launcher and a flag to disable it. (#1656) * Add sha256 checksum validation for libraries in the launcher. * Add a system property to skip library verification. * Replace the system property with a cli option to disable library validation. * Fix copy paste javadoc. --- .../llbit/chunky/main/CommandLineOptions.java | 4 ++- .../llbit/chunky/launcher/ChunkyDeployer.java | 8 +++--- .../llbit/chunky/launcher/ChunkyLauncher.java | 4 +++ .../se/llbit/chunky/launcher/VersionInfo.java | 25 ++++++++++++++----- .../launcher/ui/UpdateDialogController.java | 2 +- .../chunky/launcher/LauncherSettings.java | 2 ++ lib/src/se/llbit/util/Util.java | 25 ++++++++++++++++++- .../src/releasetools/ReleaseBuilder.java | 1 + 8 files changed, 58 insertions(+), 13 deletions(-) diff --git a/chunky/src/java/se/llbit/chunky/main/CommandLineOptions.java b/chunky/src/java/se/llbit/chunky/main/CommandLineOptions.java index 5fcfcc5d15..9c2293fde8 100644 --- a/chunky/src/java/se/llbit/chunky/main/CommandLineOptions.java +++ b/chunky/src/java/se/llbit/chunky/main/CommandLineOptions.java @@ -121,7 +121,9 @@ enum Mode { " --launcher forces the launcher window to be displayed", " --version print the launcher version and exit", " --verbose verbose logging in the launcher", - " --console show the GUI console in headless mode"); + " --console show the GUI console in headless mode", + " --dangerouslyDisableLibraryValidation", + " disable library checksum validation (not recommended)"); /** * True if any command line option provided was invalid. diff --git a/launcher/src/se/llbit/chunky/launcher/ChunkyDeployer.java b/launcher/src/se/llbit/chunky/launcher/ChunkyDeployer.java index 995b4b8be4..b383e832f0 100644 --- a/launcher/src/se/llbit/chunky/launcher/ChunkyDeployer.java +++ b/launcher/src/se/llbit/chunky/launcher/ChunkyDeployer.java @@ -98,7 +98,7 @@ public static boolean checkVersionIntegrity(String version) { case INCOMPLETE_INFO: System.err.println("Missing library name or checksum"); return false; - case MD5_MISMATCH: + case CHECKSUM_MISMATCH: System.err.println("Library MD5 checksum mismatch"); return false; case MISSING: @@ -473,7 +473,7 @@ public static boolean canLaunch(VersionInfo version, ChunkyLauncherController la // Version not available! System.err.println("Found no installed Chunky version."); if (reportErrors) { - launcher.launcherError("No Chunky Available", + launcher.launcherError("No Chunky version available", "There is no local Chunky version installed. Please try updating."); } return false; @@ -482,9 +482,9 @@ public static boolean canLaunch(VersionInfo version, ChunkyLauncherController la // TODO: add a way to fix this (delete corrupt version and then update)! System.err.println("Version integrity check failed for version " + version.name); if (reportErrors) { - launcher.launcherError("Chunky Version is Corrupt", + launcher.launcherError("Chunky version is corrupt", "Version integrity check failed for version " - + version.name + ". Please select another version."); + + version.name + ". Please select another version or check for updates to repair it."); } return false; } diff --git a/launcher/src/se/llbit/chunky/launcher/ChunkyLauncher.java b/launcher/src/se/llbit/chunky/launcher/ChunkyLauncher.java index 2134603bbc..53a02c63b4 100644 --- a/launcher/src/se/llbit/chunky/launcher/ChunkyLauncher.java +++ b/launcher/src/se/llbit/chunky/launcher/ChunkyLauncher.java @@ -163,6 +163,10 @@ else if (!settings.javaOptions.contains(args[i + 1])) System.err.println("This does not appear to be a 64-bit JVM."); } System.exit(is64Bit ? 0 : -1); + case "--dangerouslyDisableLibraryValidation": + System.out.println("Library validation is disabled."); + LauncherSettings.disableLibraryValidation = true; + break; default: if (!headlessOptions.isEmpty()) { headlessOptions += " "; diff --git a/launcher/src/se/llbit/chunky/launcher/VersionInfo.java b/launcher/src/se/llbit/chunky/launcher/VersionInfo.java index d0634f5298..c776f4460d 100644 --- a/launcher/src/se/llbit/chunky/launcher/VersionInfo.java +++ b/launcher/src/se/llbit/chunky/launcher/VersionInfo.java @@ -42,7 +42,7 @@ public class VersionInfo implements Comparable { public enum LibraryStatus { PASSED("Installed"), - MD5_MISMATCH("To be downloaded (checksum mismatch)"), + CHECKSUM_MISMATCH("To be downloaded (checksum mismatch)"), INCOMPLETE_INFO("Incomplete library information"), MISSING("To be downloaded (file missing)"), MALFORMED_URL("Download failed (malformed URL)"), @@ -64,12 +64,14 @@ public String downloadStatus() { public static class Library { public final String name; public final String md5; + public final String sha256; public final String url; public final int size; - public Library(String name, String md5, int size) { + public Library(String name, String md5, String sha256, int size) { this.name = name; this.md5 = md5; + this.sha256 = sha256; this.url = ""; this.size = size; } @@ -77,6 +79,7 @@ public Library(String name, String md5, int size) { public Library(JsonObject obj) { name = obj.get("name").stringValue(""); md5 = obj.get("md5").stringValue(""); + sha256 = obj.get("sha256").stringValue(""); url = obj.get("url").stringValue(""); size = obj.get("size").intValue(1); } @@ -86,16 +89,25 @@ public File getFile(File libDir) { } public LibraryStatus testIntegrity(File libDir) { - if (name.isEmpty() || md5.isEmpty()) { + if (name.isEmpty() || (!LauncherSettings.disableLibraryValidation && md5.isEmpty() && sha256.isEmpty())) { return LibraryStatus.INCOMPLETE_INFO; } File library = new File(libDir, name); if (!library.isFile()) { return LibraryStatus.MISSING; } - String libMD5 = Util.md5sum(library); - if (!libMD5.equalsIgnoreCase(md5)) { - return LibraryStatus.MD5_MISMATCH; + if (!LauncherSettings.disableLibraryValidation) { + if (!sha256.isEmpty()) { + String libSha256 = Util.sha256sum(library); + if (!libSha256.equalsIgnoreCase(sha256)) { + return LibraryStatus.CHECKSUM_MISMATCH; + } + } else { + String libMD5 = Util.md5sum(library); + if (!libMD5.equalsIgnoreCase(md5)) { + return LibraryStatus.CHECKSUM_MISMATCH; + } + } } return LibraryStatus.PASSED; } @@ -104,6 +116,7 @@ public JsonObject json() { JsonObject obj = new JsonObject(); obj.add("name", name); obj.add("md5", md5); + obj.add("sha256", sha256); obj.add("url", url); obj.add("size", size); return obj; diff --git a/launcher/src/se/llbit/chunky/launcher/ui/UpdateDialogController.java b/launcher/src/se/llbit/chunky/launcher/ui/UpdateDialogController.java index 1a056f7ba2..b42a303313 100644 --- a/launcher/src/se/llbit/chunky/launcher/ui/UpdateDialogController.java +++ b/launcher/src/se/llbit/chunky/launcher/ui/UpdateDialogController.java @@ -123,7 +123,7 @@ public UpdateDialogController(ChunkyLauncherController launcher, VersionInfo ver case DOWNLOADED_OK: imageStream = getClass().getResourceAsStream("cached.png"); break; - case MD5_MISMATCH: + case CHECKSUM_MISMATCH: case MISSING: imageStream = getClass().getResourceAsStream("refresh.png"); break; diff --git a/lib/src/se/llbit/chunky/launcher/LauncherSettings.java b/lib/src/se/llbit/chunky/launcher/LauncherSettings.java index 76a421baf0..2f49ac434b 100644 --- a/lib/src/se/llbit/chunky/launcher/LauncherSettings.java +++ b/lib/src/se/llbit/chunky/launcher/LauncherSettings.java @@ -40,6 +40,8 @@ public class LauncherSettings { static final ReleaseChannel SNAPSHOT_RELEASE_CHANNEL = new ReleaseChannel( "snapshot", "Snapshot", "snapshot.json", "Latest nightly snapshot of Chunky."); + public static boolean disableLibraryValidation = false; + public int settingsRevision = 0; public String javaDir = ""; diff --git a/lib/src/se/llbit/util/Util.java b/lib/src/se/llbit/util/Util.java index c323020058..79efdec384 100644 --- a/lib/src/se/llbit/util/Util.java +++ b/lib/src/se/llbit/util/Util.java @@ -44,7 +44,7 @@ public static String md5sum(File library) { try { MessageDigest digest = MessageDigest.getInstance("MD5"); try (FileInputStream in = new FileInputStream(library); - DigestInputStream dis = new DigestInputStream(in, digest)) { + DigestInputStream dis = new DigestInputStream(in, digest)) { byte[] buf = new byte[2048]; int n; do { @@ -59,6 +59,29 @@ public static String md5sum(File library) { } } + /** + * @return the SHA256 hash sum of the given file, in hexadecimal format. + * Returns an error message if there was an error computing the checksum. + */ + public static String sha256sum(File library) { + try { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + try (FileInputStream in = new FileInputStream(library); + DigestInputStream dis = new DigestInputStream(in, digest)) { + byte[] buf = new byte[2048]; + int n; + do { + n = dis.read(buf); + } while (n != -1); + return byteArrayToHexString(digest.digest()); + } catch (IOException e) { + return "sha256 compute error: " + e.getMessage(); + } + } catch (NoSuchAlgorithmException e) { + return "sha256 compute error: " + e.getMessage(); + } + } + private static byte[] NIBBLE_TO_HEX = "0123456789ABCDEF".getBytes(); /** diff --git a/releasetools/src/releasetools/ReleaseBuilder.java b/releasetools/src/releasetools/ReleaseBuilder.java index 19d802e0de..1de168c2c0 100644 --- a/releasetools/src/releasetools/ReleaseBuilder.java +++ b/releasetools/src/releasetools/ReleaseBuilder.java @@ -161,6 +161,7 @@ private static JsonObject libraryJson(File lib) { JsonObject library = new JsonObject(); library.add("name", lib.getName()); library.add("md5", Util.md5sum(lib)); + library.add("sha256", Util.sha256sum(lib)); library.add("size", (int) Math.min(Integer.MAX_VALUE, lib.length())); return library; }