From 94b4e3d36613b89efc1fb7101a06132b4d6ffc16 Mon Sep 17 00:00:00 2001 From: Luck Date: Sat, 9 Dec 2017 18:36:08 +0000 Subject: [PATCH] Validate checksums of downloaded dependencies --- .../luckperms/bukkit/LPBukkitPlugin.java | 6 +- .../migration/MigrationPowerfulPerms.java | 3 +- .../luckperms/bungee/LPBungeePlugin.java | 6 +- .../common/dependencies/Dependency.java | 154 +++++++++++++++--- .../dependencies/DependencyManager.java | 49 ++++-- .../common/plugin/LuckPermsPlugin.java | 8 + .../luckperms/sponge/LPSpongePlugin.java | 4 +- 7 files changed, 189 insertions(+), 41 deletions(-) diff --git a/bukkit/src/main/java/me/lucko/luckperms/bukkit/LPBukkitPlugin.java b/bukkit/src/main/java/me/lucko/luckperms/bukkit/LPBukkitPlugin.java index fda9e6e37..aed3adf8c 100644 --- a/bukkit/src/main/java/me/lucko/luckperms/bukkit/LPBukkitPlugin.java +++ b/bukkit/src/main/java/me/lucko/luckperms/bukkit/LPBukkitPlugin.java @@ -129,6 +129,7 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin { private DefaultsProvider defaultsProvider; private ChildPermissionProvider childPermissionProvider; private LocaleManager localeManager; + private DependencyManager dependencyManager; private CachedStateManager cachedStateManager; private ContextManager contextManager; private CalculatorFactory calculatorFactory; @@ -153,7 +154,8 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin { senderFactory = new BukkitSenderFactory(this); log = new SenderLogger(this, getConsoleSender()); - DependencyManager.loadDependencies(this, Collections.singleton(Dependency.CAFFEINE)); + dependencyManager = new DependencyManager(this); + dependencyManager.loadDependencies(Collections.singleton(Dependency.CAFFEINE)); } @Override @@ -192,7 +194,7 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin { configuration.init(); Set storageTypes = StorageFactory.getRequiredTypes(this, StorageType.H2); - DependencyManager.loadStorageDependencies(this, storageTypes); + dependencyManager.loadStorageDependencies(storageTypes); // setup the Bukkit defaults hook defaultsProvider = new DefaultsProvider(); diff --git a/bukkit/src/main/java/me/lucko/luckperms/bukkit/migration/MigrationPowerfulPerms.java b/bukkit/src/main/java/me/lucko/luckperms/bukkit/migration/MigrationPowerfulPerms.java index f871ecc54..c1aea69b7 100644 --- a/bukkit/src/main/java/me/lucko/luckperms/bukkit/migration/MigrationPowerfulPerms.java +++ b/bukkit/src/main/java/me/lucko/luckperms/bukkit/migration/MigrationPowerfulPerms.java @@ -40,7 +40,6 @@ import me.lucko.luckperms.common.commands.abstraction.SubCommand; import me.lucko.luckperms.common.commands.impl.migration.MigrationUtils; import me.lucko.luckperms.common.commands.sender.Sender; import me.lucko.luckperms.common.config.ConfigKeys; -import me.lucko.luckperms.common.dependencies.DependencyManager; import me.lucko.luckperms.common.locale.CommandSpec; import me.lucko.luckperms.common.locale.LocaleManager; import me.lucko.luckperms.common.logging.ProgressLogger; @@ -95,7 +94,7 @@ public class MigrationPowerfulPerms extends SubCommand { if (type == null || type != StorageType.MYSQL) { // We need to load the Hikari/MySQL stuff. - DependencyManager.loadStorageDependencies(plugin, ImmutableSet.of(StorageType.MYSQL)); + plugin.getDependencyManager().loadStorageDependencies(ImmutableSet.of(StorageType.MYSQL)); } String address = args.get(0); diff --git a/bungee/src/main/java/me/lucko/luckperms/bungee/LPBungeePlugin.java b/bungee/src/main/java/me/lucko/luckperms/bungee/LPBungeePlugin.java index 9cacd6f96..6cf07f465 100644 --- a/bungee/src/main/java/me/lucko/luckperms/bungee/LPBungeePlugin.java +++ b/bungee/src/main/java/me/lucko/luckperms/bungee/LPBungeePlugin.java @@ -111,6 +111,7 @@ public class LPBungeePlugin extends Plugin implements LuckPermsPlugin { private ApiProvider apiProvider; private Logger log; private LocaleManager localeManager; + private DependencyManager dependencyManager; private CachedStateManager cachedStateManager; private ContextManager contextManager; private CalculatorFactory calculatorFactory; @@ -129,7 +130,8 @@ public class LPBungeePlugin extends Plugin implements LuckPermsPlugin { senderFactory = new BungeeSenderFactory(this); log = new SenderLogger(this, getConsoleSender()); - DependencyManager.loadDependencies(this, Collections.singleton(Dependency.CAFFEINE)); + dependencyManager = new DependencyManager(this); + dependencyManager.loadDependencies(Collections.singleton(Dependency.CAFFEINE)); } @Override @@ -145,7 +147,7 @@ public class LPBungeePlugin extends Plugin implements LuckPermsPlugin { configuration.init(); Set storageTypes = StorageFactory.getRequiredTypes(this, StorageType.H2); - DependencyManager.loadStorageDependencies(this, storageTypes); + dependencyManager.loadStorageDependencies(storageTypes); // register events getProxy().getPluginManager().registerListener(this, new BungeeConnectionListener(this)); diff --git a/common/src/main/java/me/lucko/luckperms/common/dependencies/Dependency.java b/common/src/main/java/me/lucko/luckperms/common/dependencies/Dependency.java index 8999a1827..236bc3c1b 100644 --- a/common/src/main/java/me/lucko/luckperms/common/dependencies/Dependency.java +++ b/common/src/main/java/me/lucko/luckperms/common/dependencies/Dependency.java @@ -25,38 +25,150 @@ package me.lucko.luckperms.common.dependencies; -import lombok.AllArgsConstructor; import lombok.Getter; +import com.google.common.io.ByteStreams; + +import java.io.InputStream; +import java.net.URL; +import java.security.MessageDigest; +import java.util.Arrays; +import java.util.Base64; + @Getter -@AllArgsConstructor public enum Dependency { - CAFFEINE("com.github.ben-manes.caffeine", "caffeine", "2.6.0"), - MARIADB_DRIVER("org.mariadb.jdbc", "mariadb-java-client", "2.2.0"), - MYSQL_DRIVER("mysql", "mysql-connector-java", "5.1.44"), - POSTGRESQL_DRIVER("org.postgresql", "postgresql", "9.4.1212"), - H2_DRIVER("com.h2database", "h2", "1.4.196"), - SQLITE_DRIVER("org.xerial", "sqlite-jdbc", "3.21.0"), - HIKARI("com.zaxxer", "HikariCP", "2.7.3"), - SLF4J_SIMPLE("org.slf4j", "slf4j-simple", "1.7.25"), - SLF4J_API("org.slf4j", "slf4j-api", "1.7.25"), - MONGODB_DRIVER("org.mongodb", "mongo-java-driver", "3.5.0"), - JEDIS("https://github.com/lucko/jedis/releases/download/jedis-2.9.1-shaded/jedis-2.9.1-shaded.jar", "2.9.1-shaded"), - CONFIGURATE_CORE("ninja.leaping.configurate", "configurate-core", "3.3"), - CONFIGURATE_GSON("ninja.leaping.configurate", "configurate-gson", "3.3"), - CONFIGURATE_YAML("ninja.leaping.configurate", "configurate-yaml", "3.3"), - CONFIGURATE_HOCON("ninja.leaping.configurate", "configurate-hocon", "3.3"), - HOCON_CONFIG("com.typesafe", "config", "1.3.1"); - + CAFFEINE( + "com.github.ben-manes.caffeine", + "caffeine", + "2.6.0", + "JmT16VQnCnVBAjRJCQkkkjmSVx2jajpzeBuKwpbzDA8=" + ), + MARIADB_DRIVER( + "org.mariadb.jdbc", + "mariadb-java-client", + "2.2.0", + "/q0LPGHrp3L9rvKr7TuA6urbtXBqvXis92mP4KhxzUw=" + ), + MYSQL_DRIVER( + "mysql", + "mysql-connector-java", + "5.1.44", + "d4RZVzTeWpoHBPB/tQP3mSafNy7L9MDUSOt4Ku9LGCc=" + ), + POSTGRESQL_DRIVER( + "org.postgresql", + "postgresql", + "9.4.1212", + "DLKhWL4xrPIY4KThjI89usaKO8NIBkaHc/xECUsMNl0=" + ), + H2_DRIVER( + "com.h2database", + "h2", + "1.4.196", + "CgX0oNW4WEAUiq3OY6QjtdPDbvRHVjibT6rQjScz+vU=" + ), + SQLITE_DRIVER( + "org.xerial", + "sqlite-jdbc", + "3.21.0", + "bglRaH4Y+vQFZV7TfOdsVLO3rJpauJ+IwjuRULAb45Y=" + ), + HIKARI( + "com.zaxxer", + "HikariCP", + "2.7.4", + "y9JE6/VmbydCqlV1z468+oqdkBswBk6aw+ECT178AT4=" + ), + SLF4J_SIMPLE( + "org.slf4j", + "slf4j-simple", + "1.7.25", + "CWbob/+lvlLT2ee4ndZ02YoD7tCkVPuvfBvZSTvZ2HQ=" + ), + SLF4J_API( + "org.slf4j", + "slf4j-api", + "1.7.25", + "GMSgCV1cHaa4F1kudnuyPSndL1YK1033X/OWHb3iW3k=" + ), + MONGODB_DRIVER( + "org.mongodb", + "mongo-java-driver", + "3.5.0", + "gxrbKVSI/xM6r+6uL7g7I0DzNV+hlNTtfw4UL13XdK8=" + ), + JEDIS( + "https://github.com/lucko/jedis/releases/download/jedis-2.9.1-shaded/jedis-2.9.1-shaded.jar", + "2.9.1-shaded", + "mM19X6LyD97KP4RSbcCR5BTRAwQ0x9y02voX7ePOSjE=" + ), + CONFIGURATE_CORE( + "ninja.leaping.configurate", + "configurate-core", + "3.3", + "4leBJEqj1kVszaifZeKNl4hgHxG5M+Nk5TJKkPW2s4Y=" + ), + CONFIGURATE_GSON( + "ninja.leaping.configurate", + "configurate-gson", + "3.3", + "4HxrW3/ZKdn095x/W4gylQMNskdmteXYVxVv0UKGJA4=" + ), + CONFIGURATE_YAML( + "ninja.leaping.configurate", + "configurate-yaml", + "3.3", + "hgADp3g+xHHPD34bAuxMWtB+OQ718Tlw69jVp2KPJNk=" + ), + CONFIGURATE_HOCON( + "ninja.leaping.configurate", + "configurate-hocon", + "3.3", + "UIy5FVmsBUG6+Z1mpIEE2EXgtOI1ZL0p/eEW+BbtGLU=" + ), + HOCON_CONFIG( + "com.typesafe", + "config", + "1.3.1", + "5vrfxhCCINOmuGqn5OFsnnu4V7pYlViGMIuxOXImSvA=" + ); private final String url; private final String version; + private final byte[] checksum; private static final String MAVEN_CENTRAL_FORMAT = "https://repo1.maven.org/maven2/%s/%s/%s/%s-%s.jar"; - Dependency(String groupId, String artifactId, String version) { - this(String.format(MAVEN_CENTRAL_FORMAT, groupId.replace(".", "/"), artifactId, version, artifactId, version), version); + Dependency(String groupId, String artifactId, String version, String checksum) { + this(String.format(MAVEN_CENTRAL_FORMAT, groupId.replace(".", "/"), artifactId, version, artifactId, version), version, checksum); } + Dependency(String url, String version, String checksum) { + this.url = url; + this.version = version; + this.checksum = Base64.getDecoder().decode(checksum); + } + + public static void main(String[] args) throws Exception { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + + for (Dependency dependency : values()) { + URL url = new URL(dependency.getUrl()); + try (InputStream in = url.openStream()) { + byte[] bytes = ByteStreams.toByteArray(in); + if (bytes.length == 0) { + throw new RuntimeException("Empty stream"); + } + + byte[] hash = digest.digest(bytes); + + if (Arrays.equals(hash, dependency.getChecksum())) { + System.out.println("MATCH " + dependency.name() + ": " + Base64.getEncoder().encodeToString(hash)); + } else { + System.out.println("NO MATCH " + dependency.name() + ": " + Base64.getEncoder().encodeToString(hash)); + } + } + } + } } diff --git a/common/src/main/java/me/lucko/luckperms/common/dependencies/DependencyManager.java b/common/src/main/java/me/lucko/luckperms/common/dependencies/DependencyManager.java index 786b5a836..c65ce256d 100644 --- a/common/src/main/java/me/lucko/luckperms/common/dependencies/DependencyManager.java +++ b/common/src/main/java/me/lucko/luckperms/common/dependencies/DependencyManager.java @@ -25,10 +25,9 @@ package me.lucko.luckperms.common.dependencies; -import lombok.experimental.UtilityClass; - import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.google.common.io.ByteStreams; import me.lucko.luckperms.api.platform.PlatformType; import me.lucko.luckperms.common.config.ConfigKeys; @@ -43,7 +42,10 @@ import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.nio.file.Files; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.util.ArrayList; +import java.util.Arrays; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; @@ -52,17 +54,16 @@ import java.util.Set; /** * Responsible for loading runtime dependencies. */ -@UtilityClass public class DependencyManager { private static final Method ADD_URL_METHOD; static { - Method addUrlMethod = null; + Method addUrlMethod; try { addUrlMethod = URLClassLoader.class.getDeclaredMethod("addURL", URL.class); addUrlMethod.setAccessible(true); } catch (NoSuchMethodException e) { - e.printStackTrace(); + throw new ExceptionInInitializerError(e); } ADD_URL_METHOD = addUrlMethod; } @@ -79,7 +80,19 @@ public class DependencyManager { .put(StorageType.H2, ImmutableList.of(Dependency.H2_DRIVER)) .build(); - public static void loadStorageDependencies(LuckPermsPlugin plugin, Set storageTypes) { + private final LuckPermsPlugin plugin; + private final MessageDigest digest; + + public DependencyManager(LuckPermsPlugin plugin) { + this.plugin = plugin; + try { + this.digest = MessageDigest.getInstance("SHA-1"); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } + + public void loadStorageDependencies(Set storageTypes) { Set dependencies = new LinkedHashSet<>(); for (StorageType storageType : storageTypes) { dependencies.addAll(STORAGE_DEPENDENCIES.get(storageType)); @@ -104,10 +117,10 @@ public class DependencyManager { dependencies.remove(Dependency.HOCON_CONFIG); } - loadDependencies(plugin, dependencies); + loadDependencies(dependencies); } - public static void loadDependencies(LuckPermsPlugin plugin, Set dependencies) { + public void loadDependencies(Set dependencies) { plugin.getLog().info("Identified the following dependencies: " + dependencies.toString()); File libDir = new File(plugin.getDataDirectory(), "lib"); @@ -119,7 +132,7 @@ public class DependencyManager { List filesToLoad = new ArrayList<>(); for (Dependency dependency : dependencies) { try { - filesToLoad.add(downloadDependency(plugin, libDir, dependency)); + filesToLoad.add(downloadDependency(libDir, dependency)); } catch (Throwable e) { plugin.getLog().severe("Exception whilst downloading dependency " + dependency.name()); e.printStackTrace(); @@ -129,7 +142,7 @@ public class DependencyManager { // Load classes. for (File file : filesToLoad) { try { - loadJar(plugin, file); + loadJar(file); } catch (Throwable t) { plugin.getLog().severe("Failed to load dependency jar " + file.getName()); t.printStackTrace(); @@ -137,7 +150,7 @@ public class DependencyManager { } } - private static File downloadDependency(LuckPermsPlugin plugin, File libDir, Dependency dependency) throws Exception { + private File downloadDependency(File libDir, Dependency dependency) throws Exception { String fileName = dependency.name().toLowerCase() + "-" + dependency.getVersion() + ".jar"; File file = new File(libDir, fileName); @@ -149,7 +162,17 @@ public class DependencyManager { plugin.getLog().info("Dependency '" + fileName + "' could not be found. Attempting to download."); try (InputStream in = url.openStream()) { - Files.copy(in, file.toPath()); + byte[] bytes = ByteStreams.toByteArray(in); + if (bytes.length == 0) { + throw new RuntimeException("Empty stream"); + } + + byte[] hash = this.digest.digest(bytes); + if (!Arrays.equals(hash, dependency.getChecksum())) { + throw new RuntimeException("Downloaded file had an invalid hash."); + } + + Files.write(file.toPath(), bytes); } if (!file.exists()) { @@ -160,7 +183,7 @@ public class DependencyManager { } } - private static void loadJar(LuckPermsPlugin plugin, File file) { + private void loadJar(File file) { // get the classloader to load into ClassLoader classLoader = plugin.getClass().getClassLoader(); diff --git a/common/src/main/java/me/lucko/luckperms/common/plugin/LuckPermsPlugin.java b/common/src/main/java/me/lucko/luckperms/common/plugin/LuckPermsPlugin.java index f981085cb..dc05397bb 100644 --- a/common/src/main/java/me/lucko/luckperms/common/plugin/LuckPermsPlugin.java +++ b/common/src/main/java/me/lucko/luckperms/common/plugin/LuckPermsPlugin.java @@ -38,6 +38,7 @@ import me.lucko.luckperms.common.commands.sender.Sender; import me.lucko.luckperms.common.commands.utils.CommandUtils; import me.lucko.luckperms.common.config.LuckPermsConfiguration; import me.lucko.luckperms.common.contexts.ContextManager; +import me.lucko.luckperms.common.dependencies.DependencyManager; import me.lucko.luckperms.common.locale.LocaleManager; import me.lucko.luckperms.common.locale.Message; import me.lucko.luckperms.common.logging.Logger; @@ -146,6 +147,13 @@ public interface LuckPermsPlugin { */ LocaleManager getLocaleManager(); + /** + * Gets the dependency manager for the plugin + * + * @return the dependency manager + */ + DependencyManager getDependencyManager(); + /** * Gets the context manager. * This object handles context accumulation for all players on the platform. diff --git a/sponge/src/main/java/me/lucko/luckperms/sponge/LPSpongePlugin.java b/sponge/src/main/java/me/lucko/luckperms/sponge/LPSpongePlugin.java index 2f0544c1d..7e568a1f9 100644 --- a/sponge/src/main/java/me/lucko/luckperms/sponge/LPSpongePlugin.java +++ b/sponge/src/main/java/me/lucko/luckperms/sponge/LPSpongePlugin.java @@ -178,6 +178,7 @@ public class LPSpongePlugin implements LuckPermsPlugin { private me.lucko.luckperms.common.logging.Logger log; private LuckPermsService service; private LocaleManager localeManager; + private DependencyManager dependencyManager; private CachedStateManager cachedStateManager; private ContextManager contextManager; private CalculatorFactory calculatorFactory; @@ -195,6 +196,7 @@ public class LPSpongePlugin implements LuckPermsPlugin { localeManager = new NoopLocaleManager(); senderFactory = new SpongeSenderFactory(this); log = new SenderLogger(this, getConsoleSender()); + dependencyManager = new DependencyManager(this); LuckPermsPlugin.sendStartupBanner(getConsoleSender(), this); verboseHandler = new VerboseHandler(scheduler.async(), getVersion()); @@ -206,7 +208,7 @@ public class LPSpongePlugin implements LuckPermsPlugin { configuration.init(); Set storageTypes = StorageFactory.getRequiredTypes(this, StorageType.H2); - DependencyManager.loadStorageDependencies(this, storageTypes); + dependencyManager.loadStorageDependencies(storageTypes); // register events game.getEventManager().registerListeners(this, new SpongeConnectionListener(this));