From 94109f0faabbbb3ffb885d4c6186b4658a3d113a Mon Sep 17 00:00:00 2001 From: Minecon724 Date: Sun, 10 Nov 2024 10:02:55 +0100 Subject: [PATCH] Updater for old Java --- pom.xml | 2 +- .../java/eu/m724/giants/GiantsPlugin.java | 52 ++++--- .../eu/m724/giants/updater/JarVerifier.java | 140 ------------------ .../eu/m724/giants/updater/PluginUpdater.java | 44 ++---- .../eu/m724/giants/updater/UpdateCommand.java | 5 + 5 files changed, 51 insertions(+), 192 deletions(-) delete mode 100644 src/main/java/eu/m724/giants/updater/JarVerifier.java diff --git a/pom.xml b/pom.xml index e96f916..b38032a 100644 --- a/pom.xml +++ b/pom.xml @@ -57,7 +57,7 @@ eu.m724 jarupdater - 0.1.7 + 0.1.10 diff --git a/src/main/java/eu/m724/giants/GiantsPlugin.java b/src/main/java/eu/m724/giants/GiantsPlugin.java index 8b20c64..aed8ad7 100644 --- a/src/main/java/eu/m724/giants/GiantsPlugin.java +++ b/src/main/java/eu/m724/giants/GiantsPlugin.java @@ -1,8 +1,8 @@ package eu.m724.giants; -import eu.m724.giants.updater.JarVerifier; import eu.m724.giants.updater.PluginUpdater; import eu.m724.giants.updater.UpdateCommand; +import eu.m724.jarupdater.verify.VerificationException; import org.bstats.bukkit.Metrics; import org.bukkit.Location; import org.bukkit.command.CommandExecutor; @@ -28,33 +28,41 @@ public class GiantsPlugin extends JavaPlugin implements CommandExecutor { configuration.load(); - UpdateCommand updateCommand = null; - if (configuration.updater != null) { - PluginUpdater updater = PluginUpdater.build(this, getFile(), configuration.updater); - updater.initNotifier(); - updateCommand = new UpdateCommand(updater); - } - - getCommand("giants").setExecutor(new GiantsCommand(this, configuration, updateCommand)); - giantProcessor.start(); + // bStats new Metrics(this, 14131); - try (InputStream keyInputStream = getResource("verifies_downloaded_jars.pem")) { - JarVerifier.verifyWithRsaKey( - getFile().getPath().replace(".paper-remapped/", ""), // paper remapping removes data from manifest - keyInputStream - ); - } catch (IOException e) { - getLogger().warning(e.getMessage()); - getLogger().warning("Failed checking JAR signature. This is not important right now, but it usually indicates future problems. If this persists, re-download the JAR from SpigotMC."); - } catch (JarVerifier.VerificationException e) { - getLogger().warning(e.getMessage()); - getLogger().warning("Plugin JAR is of invalid signature. If this persists, re-download the JAR from SpigotMC."); - getLogger().warning("Did you update from 2.0.7? If yes, you must re-download 2.0.9+ from SpigotMC, then delete plugins/.paper-remapped"); + // updater + PluginUpdater updater = null; + + if (configuration.updater != null) { + try (InputStream keyInputStream = getResource("verifies_downloaded_jars.pem")) { + updater = PluginUpdater.build(this, getFile(), configuration.updater, keyInputStream); + } catch (IOException e) { + e.printStackTrace(); + getLogger().severe("Failed to load updater"); + } + + if (updater != null) { + try { + updater.verifyJar( + getFile().getPath().replace(".paper-remapped/", "") // paper remapping removes data from manifest + ); + } catch (VerificationException e) { + getLogger().warning(e.getMessage()); + getLogger().warning("Plugin JAR is of invalid signature. If this persists, re-download the JAR from SpigotMC."); + getLogger().warning("Did you update from 2.0.7? If yes, you must re-download 2.0.9+ from SpigotMC, then delete plugins/.paper-remapped"); + + } + updater.initNotifier(); + } } + UpdateCommand updateCommand = new UpdateCommand(updater); + + getCommand("giants").setExecutor(new GiantsCommand(this, configuration, updateCommand)); + /* bStats is optional. not anymore try { Class clazz = Class.forName("eu.m724.giants.bukkit.Metrics"); diff --git a/src/main/java/eu/m724/giants/updater/JarVerifier.java b/src/main/java/eu/m724/giants/updater/JarVerifier.java deleted file mode 100644 index 21086f3..0000000 --- a/src/main/java/eu/m724/giants/updater/JarVerifier.java +++ /dev/null @@ -1,140 +0,0 @@ -package eu.m724.giants.updater; - -import java.io.IOException; -import java.io.InputStream; -import java.security.CodeSigner; -import java.security.GeneralSecurityException; -import java.security.KeyFactory; -import java.security.PublicKey; -import java.security.cert.Certificate; -import java.security.cert.X509Certificate; -import java.security.interfaces.RSAPublicKey; -import java.security.spec.X509EncodedKeySpec; -import java.util.ArrayList; -import java.util.Base64; -import java.util.Enumeration; -import java.util.List; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; -import java.util.jar.Manifest; - -public class JarVerifier { - /** - * Loads an RSA public key from a PEM file - * - * @param keyInputStream inputStream of the public key file - * @return {@link RSAPublicKey} instance of the public key - * @throws IOException if reading the key input stream failed - */ - private static RSAPublicKey loadPublicKey(InputStream keyInputStream) throws IOException { - // Read the key file - String keyContent = new String(keyInputStream.readAllBytes()); - - // Remove PEM headers and newlines - keyContent = keyContent.replace("-----BEGIN PUBLIC KEY-----", "") - .replace("-----END PUBLIC KEY-----", "") - .replaceAll("\\s+", ""); - - // Decode the key - byte[] keyBytes = Base64.getDecoder().decode(keyContent); - - // Create public key specification - X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes); - - // Generate public key - try { - KeyFactory kf = KeyFactory.getInstance("RSA"); - return (RSAPublicKey) kf.generatePublic(spec); - } catch (GeneralSecurityException e) { - // because this shouldn't happen - throw new RuntimeException(e); - } - } - - /** - * Verifies if a JAR file's signature matches an RSA public key - * - * @param jarPath the path of the JAR file - * @param keyInputStream inputStream of the public key file - * @throws VerificationException if verification failed - */ - public static void verifyWithRsaKey(String jarPath, InputStream keyInputStream) throws VerificationException { - try { - // Load the RSA public key - RSAPublicKey publicKey = loadPublicKey(keyInputStream); - - // Open the JAR file - try (JarFile jarFile = new JarFile(jarPath, true)) { - byte[] buffer = new byte[8192]; - Enumeration entries = jarFile.entries(); - - // Get manifest to check signature files - Manifest manifest = jarFile.getManifest(); - if (manifest == null) { - throw new VerificationException("JAR has no manifest"); - } - - while (entries.hasMoreElements()) { - JarEntry entry = entries.nextElement(); - - if (entry.isDirectory() || entry.getName().startsWith("META-INF/")) { - continue; - } - - int bytesRead = 0; - // Read entry to trigger signature verification - try (InputStream is = jarFile.getInputStream(entry)) { - while ((bytesRead += is.read(buffer)) != -1) { - if (bytesRead > 1024 * 1024 * 100) { // unusual for a file to have >100 MiB - throw new IOException("File too large: " + entry.getName()); - } - } - } - - // Get signers for this entry - CodeSigner[] signers = entry.getCodeSigners(); - if (signers == null || signers.length == 0) { - throw new VerificationException("Unsigned entry: " + entry.getName()); - } - - // Check if any signer's public key matches our RSA key - boolean keyMatch = false; - List signerPublicKeys = new ArrayList<>(); - - for (CodeSigner signer : signers) { - for (Certificate cert : signer.getSignerCertPath().getCertificates()) { - PublicKey certPublicKey = cert.getPublicKey(); - if (certPublicKey instanceof RSAPublicKey) { - RSAPublicKey rsaKey = (RSAPublicKey) certPublicKey; - signerPublicKeys.add(Base64.getEncoder().encodeToString(rsaKey.getEncoded())); - if (rsaKey.getModulus().equals(publicKey.getModulus()) && - rsaKey.getPublicExponent().equals(publicKey.getPublicExponent())) { - keyMatch = true; - break; - } - } - } - if (keyMatch) break; - } - - if (!keyMatch) { - throw new VerificationException("Entry " + entry.getName() + " signed with " + String.join(", ", signerPublicKeys) + ", none of which match " + Base64.getEncoder().encodeToString(publicKey.getEncoded())); - } - } - } - - } catch (IOException e) { - throw new VerificationException("Verification error: " + e.getMessage(), e); - } - } - - public static class VerificationException extends Exception { - public VerificationException(String message) { - super(message); - } - - public VerificationException(String message, Exception exception) { - super(message, exception); - } - } -} \ No newline at end of file diff --git a/src/main/java/eu/m724/giants/updater/PluginUpdater.java b/src/main/java/eu/m724/giants/updater/PluginUpdater.java index 9af7f19..1891503 100644 --- a/src/main/java/eu/m724/giants/updater/PluginUpdater.java +++ b/src/main/java/eu/m724/giants/updater/PluginUpdater.java @@ -7,55 +7,41 @@ import eu.m724.jarupdater.live.GiteaMetadataDAO; import eu.m724.jarupdater.live.MetadataDAO; import eu.m724.jarupdater.live.MetadataFacade; import eu.m724.jarupdater.updater.Updater; +import eu.m724.jarupdater.verify.SignatureVerifier; +import eu.m724.jarupdater.verify.VerificationException; +import eu.m724.jarupdater.verify.Verifier; import org.bukkit.plugin.Plugin; import java.io.File; -import java.nio.file.NoSuchFileException; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionException; +import java.io.IOException; +import java.io.InputStream; public class PluginUpdater extends Updater { private final Plugin plugin; boolean updatePending = false; - private PluginUpdater(Environment environment, MetadataFacade metadataProvider, Downloader downloader, Plugin plugin) { - super(environment, metadataProvider, downloader); + private PluginUpdater(Environment environment, MetadataFacade metadataProvider, Downloader downloader, Verifier verifier, Plugin plugin) { + super(environment, metadataProvider, downloader, verifier); this.plugin = plugin; } - public static PluginUpdater build(Plugin plugin, File file, String channel) { + public static PluginUpdater build(Plugin plugin, File file, String channel, InputStream keyInputStream) throws IOException { Environment environment = new PluginEnvironment(plugin, channel, file.toPath()); MetadataDAO metadataDAO = new GiteaMetadataDAO("https://git.m724.eu/Minecon724/giants-metadata", "master"); MetadataFacade metadataFacade = new MetadataFacade(environment, metadataDAO); Downloader downloader = new SimpleDownloader("giants"); + SignatureVerifier verifier = new SignatureVerifier(); + verifier.loadPublicKey(keyInputStream); - return new PluginUpdater(environment, metadataFacade, downloader, plugin); + return new PluginUpdater(environment, metadataFacade, downloader, verifier, plugin); + } + + public void verifyJar(String jarPath) throws VerificationException { + verifier.verify(jarPath); } public void initNotifier() { UpdateNotifier updateNotifier = new UpdateNotifier(plugin, this, (version) -> {}); updateNotifier.register(); } - - @Override - public CompletableFuture installLatestVersion() throws NoSuchFileException { - return installLatestVersion(true); - } - - public CompletableFuture installLatestVersion(boolean verify) throws NoSuchFileException { - if (this.downloaded == null) { - throw new NoSuchFileException("Download it first"); - } else { - return this.downloaded.thenCompose((file) -> { - if (verify) { - try { - JarVerifier.verifyWithRsaKey(file.getPath(), plugin.getResource("verifies_downloaded_jars.pem")); - } catch (JarVerifier.VerificationException e) { - throw new CompletionException(e); - } - } - return this.downloader.install(file, this.environment.getRunningJarFilePath().toFile()); - }); - } - } } diff --git a/src/main/java/eu/m724/giants/updater/UpdateCommand.java b/src/main/java/eu/m724/giants/updater/UpdateCommand.java index a6fcd2d..f0ff445 100644 --- a/src/main/java/eu/m724/giants/updater/UpdateCommand.java +++ b/src/main/java/eu/m724/giants/updater/UpdateCommand.java @@ -38,6 +38,11 @@ public class UpdateCommand { } public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + if (updater == null) { + sender.sendMessage("Updater is disabled"); + return true; + } + sender.sendMessage("Please wait..."); sender.sendMessage("Channel: " + updater.getEnvironment().getChannel());