diff --git a/pom.xml b/pom.xml index a2ecf20..aa74f66 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 com.vexsoftware votifier - 1.9 + 2.0 Votifier UTF-8 @@ -20,7 +20,7 @@ org.bukkit - bukkit + craftbukkit 1.7.2-R0.1 jar compile diff --git a/src/main/java/com/vexsoftware/votifier/Votifier.java b/src/main/java/com/vexsoftware/votifier/Votifier.java index 6e7fe54..ce4563a 100644 --- a/src/main/java/com/vexsoftware/votifier/Votifier.java +++ b/src/main/java/com/vexsoftware/votifier/Votifier.java @@ -18,25 +18,31 @@ package com.vexsoftware.votifier; -import java.io.*; +import java.io.File; +import java.net.URL; import java.security.KeyPair; +import java.security.PublicKey; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; -import java.util.logging.*; +import java.util.Map; +import java.util.Map.Entry; +import java.util.logging.Level; +import java.util.logging.Logger; + import org.bukkit.Bukkit; -import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.plugin.java.JavaPlugin; -import com.vexsoftware.votifier.crypto.RSAIO; -import com.vexsoftware.votifier.crypto.RSAKeygen; + import com.vexsoftware.votifier.model.ListenerLoader; import com.vexsoftware.votifier.model.VoteListener; -import com.vexsoftware.votifier.net.VoteReceiver; +import com.vexsoftware.votifier.net.VoteAcceptor; +import com.vexsoftware.votifier.util.LogFilter; +import com.vexsoftware.votifier.util.rsa.RSAIO; +import com.vexsoftware.votifier.util.rsa.RSAKeygen; /** * The main Votifier plugin class. - * - * @author Blake Beaupain - * @author Kramer Campbell */ public class Votifier extends JavaPlugin { @@ -46,23 +52,26 @@ public class Votifier extends JavaPlugin { /** Log entry prefix */ private static final String logPrefix = "[Votifier] "; - /** The Votifier instance. */ - private static Votifier instance; - /** The current Votifier version. */ private String version; /** The vote listeners. */ - private final List listeners = new ArrayList(); + private List listeners = new ArrayList(); + + /** The websites mapped to their public key. */ + private Map websites = new HashMap(); /** The vote receiver. */ - private VoteReceiver voteReceiver; + private VoteAcceptor voteAcceptor; /** The RSA key pair. */ private KeyPair keyPair; /** Debug mode flag */ private boolean debug; + + /** Old protocol flag */ + private boolean oldProtocol; /** * Attach custom log filter to logger. @@ -70,11 +79,47 @@ public class Votifier extends JavaPlugin { static { LOG.setFilter(new LogFilter(logPrefix)); } + + @Override + public void onLoad() { + FileConfiguration config = super.getConfig(); + + if (!new File(getDataFolder() + "/config.yml").exists()) { + LOG.info("Configuring Votifier for the first time..."); + LOG.info("------------------------------------------------------------------------------"); + LOG.info("Assigning Votifier to listen on port 8192. If you are hosting Craftbukkit on a"); + LOG.info("shared server please check with your hosting provider to verify that this port"); + LOG.info("is available for your use. Chances are that your hosting provider will assign"); + LOG.info("a different port, which you need to specify in config.yml"); + LOG.info("------------------------------------------------------------------------------"); + + /* + * Use IP address from server.properties as a default for + * configurations. Do not use InetAddress.getLocalHost() as it most + * likely will return the main server address instead of the address + * assigned to the server. + */ + String hostAddr = Bukkit.getServer().getIp(); + if (hostAddr == null || hostAddr.length() == 0) { + hostAddr = "0.0.0.0"; + } + config.set("host", hostAddr); + + // Replace to remove a bug with Windows paths - SmilingDevil + config.set("listener_folder", getDataFolder().toString().replace("\\", "/") + "/listeners"); + } + + if (config.contains("websites")) { + return; + } + + config.options().copyDefaults(true); + saveConfig(); + reloadConfig(); + } @Override public void onEnable() { - Votifier.instance = this; - // Set the plugin version. version = getDescription().getVersion(); @@ -82,60 +127,10 @@ public void onEnable() { if (!getDataFolder().exists()) { getDataFolder().mkdir(); } - File config = new File(getDataFolder() + "/config.yml"); - YamlConfiguration cfg = YamlConfiguration.loadConfiguration(config); + FileConfiguration config = super.getConfig(); File rsaDirectory = new File(getDataFolder() + "/rsa"); - // Replace to remove a bug with Windows paths - SmilingDevil - String listenerDirectory = getDataFolder().toString() - .replace("\\", "/") + "/listeners"; - - /* - * Use IP address from server.properties as a default for - * configurations. Do not use InetAddress.getLocalHost() as it most - * likely will return the main server address instead of the address - * assigned to the server. - */ - String hostAddr = Bukkit.getServer().getIp(); - if (hostAddr == null || hostAddr.length() == 0) - hostAddr = "0.0.0.0"; - - /* - * Create configuration file if it does not exists; otherwise, load it - */ - if (!config.exists()) { - try { - // First time run - do some initialization. - LOG.info("Configuring Votifier for the first time..."); - - // Initialize the configuration file. - config.createNewFile(); - - cfg.set("host", hostAddr); - cfg.set("port", 8192); - cfg.set("debug", false); - - /* - * Remind hosted server admins to be sure they have the right - * port number. - */ - LOG.info("------------------------------------------------------------------------------"); - LOG.info("Assigning Votifier to listen on port 8192. If you are hosting Craftbukkit on a"); - LOG.info("shared server please check with your hosting provider to verify that this port"); - LOG.info("is available for your use. Chances are that your hosting provider will assign"); - LOG.info("a different port, which you need to specify in config.yml"); - LOG.info("------------------------------------------------------------------------------"); - - cfg.set("listener_folder", listenerDirectory); - cfg.save(config); - } catch (Exception ex) { - LOG.log(Level.SEVERE, "Error creating configuration file", ex); - gracefulExit(); - return; - } - } else { - // Load configuration. - cfg = YamlConfiguration.loadConfiguration(config); - } + File listenerDirectory = new File(config.getString("listener_folder")); + listenerDirectory.mkdir(); /* * Create RSA directory and keys if it does not exist; otherwise, read @@ -144,36 +139,52 @@ public void onEnable() { try { if (!rsaDirectory.exists()) { rsaDirectory.mkdir(); - new File(listenerDirectory).mkdir(); keyPair = RSAKeygen.generate(2048); RSAIO.save(rsaDirectory, keyPair); } else { keyPair = RSAIO.load(rsaDirectory); } - } catch (Exception ex) { + } catch (Exception exception) { LOG.log(Level.SEVERE, - "Error reading configuration file or RSA keys", ex); + "Error reading configuration file or RSA keys", + exception); gracefulExit(); return; } // Load the vote listeners. - listenerDirectory = cfg.getString("listener_folder"); listeners.addAll(ListenerLoader.load(listenerDirectory)); + + // Load the website public keys + for (Entry website : config.getConfigurationSection("websites").getValues(false).entrySet()) { + try { + websites.put(website.getKey(), RSAIO.loadPublicKey(new URL((String) website.getValue()))); + if (!website.getKey().startsWith("https://")) { + LOG.warning("You are loading a public key (" + website.getKey() + ") over a non-SSL connection. This is insecure!"); + } + LOG.info("Loaded public key for website: " + website.getKey()); + } catch (Exception exception) { + LOG.log(Level.WARNING, + "Error loading public key for website: " + website.getKey(), + exception); + } + } - // Initialize the receiver. - String host = cfg.getString("host", hostAddr); - int port = cfg.getInt("port", 8192); - debug = cfg.getBoolean("debug", false); - if (debug) + // Initialize the acceptor. + String host = config.getString("host"); + int port = config.getInt("port"); + oldProtocol = config.getBoolean("enable_old_protocol"); + debug = config.getBoolean("debug"); + if (debug) { LOG.info("DEBUG mode enabled!"); + } try { - voteReceiver = new VoteReceiver(this, host, port); - voteReceiver.start(); + voteAcceptor = new VoteAcceptor(this, host, port); + voteAcceptor.start(); LOG.info("Votifier enabled."); - } catch (Exception ex) { + } catch (Exception exception) { gracefulExit(); return; } @@ -181,9 +192,9 @@ public void onEnable() { @Override public void onDisable() { - // Interrupt the vote receiver. - if (voteReceiver != null) { - voteReceiver.shutdown(); + // Interrupt the vote acceptor. + if (voteAcceptor != null) { + voteAcceptor.shutdown(); } LOG.info("Votifier disabled."); } @@ -192,15 +203,6 @@ private void gracefulExit() { LOG.log(Level.SEVERE, "Votifier did not initialize properly!"); } - /** - * Gets the instance. - * - * @return The instance - */ - public static Votifier getInstance() { - return instance; - } - /** * Gets the version. * @@ -218,14 +220,23 @@ public String getVersion() { public List getListeners() { return listeners; } + + /** + * Gets the websites mapped to their public key + * + * @return The websites + */ + public Map getWebsites() { + return websites; + } /** - * Gets the vote receiver. + * Gets the vote acceptor. * - * @return The vote receiver + * @return The vote acceptor */ - public VoteReceiver getVoteReceiver() { - return voteReceiver; + public VoteAcceptor getVoteAcceptor() { + return voteAcceptor; } /** @@ -240,5 +251,9 @@ public KeyPair getKeyPair() { public boolean isDebug() { return debug; } + + public boolean isOldProtocol() { + return oldProtocol; + } } diff --git a/src/main/java/com/vexsoftware/votifier/crypto/RSAIO.java b/src/main/java/com/vexsoftware/votifier/crypto/RSAIO.java deleted file mode 100644 index a5727ee..0000000 --- a/src/main/java/com/vexsoftware/votifier/crypto/RSAIO.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (C) 2011 Vex Software LLC - * This file is part of Votifier. - * - * Votifier is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Votifier is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Votifier. If not, see . - */ - -package com.vexsoftware.votifier.crypto; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.security.KeyFactory; -import java.security.KeyPair; -import java.security.PrivateKey; -import java.security.PublicKey; -import java.security.spec.PKCS8EncodedKeySpec; -import java.security.spec.X509EncodedKeySpec; - -import javax.xml.bind.DatatypeConverter; - -/** - * Static utility methods for saving and loading RSA key pairs. - * - * @author Blake Beaupain - */ -public class RSAIO { - - /** - * Saves the key pair to the disk. - * - * @param directory - * The directory to save to - * @param keyPair - * The key pair to save - * @throws Exception - * If an error occurs - */ - public static void save(File directory, KeyPair keyPair) throws Exception { - PrivateKey privateKey = keyPair.getPrivate(); - PublicKey publicKey = keyPair.getPublic(); - - // Store the public key. - X509EncodedKeySpec publicSpec = new X509EncodedKeySpec( - publicKey.getEncoded()); - FileOutputStream out = new FileOutputStream(directory + "/public.key"); - out.write(DatatypeConverter.printBase64Binary(publicSpec.getEncoded()) - .getBytes()); - out.close(); - - // Store the private key. - PKCS8EncodedKeySpec privateSpec = new PKCS8EncodedKeySpec( - privateKey.getEncoded()); - out = new FileOutputStream(directory + "/private.key"); - out.write(DatatypeConverter.printBase64Binary(privateSpec.getEncoded()) - .getBytes()); - out.close(); - } - - /** - * Loads an RSA key pair from a directory. The directory must have the files - * "public.key" and "private.key". - * - * @param directory - * The directory to load from - * @return The key pair - * @throws Exception - * If an error occurs - */ - public static KeyPair load(File directory) throws Exception { - // Read the public key file. - File publicKeyFile = new File(directory + "/public.key"); - FileInputStream in = new FileInputStream(directory + "/public.key"); - byte[] encodedPublicKey = new byte[(int) publicKeyFile.length()]; - in.read(encodedPublicKey); - encodedPublicKey = DatatypeConverter.parseBase64Binary(new String( - encodedPublicKey)); - in.close(); - - // Read the private key file. - File privateKeyFile = new File(directory + "/private.key"); - in = new FileInputStream(directory + "/private.key"); - byte[] encodedPrivateKey = new byte[(int) privateKeyFile.length()]; - in.read(encodedPrivateKey); - encodedPrivateKey = DatatypeConverter.parseBase64Binary(new String( - encodedPrivateKey)); - in.close(); - - // Instantiate and return the key pair. - KeyFactory keyFactory = KeyFactory.getInstance("RSA"); - X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec( - encodedPublicKey); - PublicKey publicKey = keyFactory.generatePublic(publicKeySpec); - PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec( - encodedPrivateKey); - PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec); - return new KeyPair(publicKey, privateKey); - } - -} diff --git a/src/main/java/com/vexsoftware/votifier/model/ListenerLoader.java b/src/main/java/com/vexsoftware/votifier/model/ListenerLoader.java index ceba7c2..75a611c 100644 --- a/src/main/java/com/vexsoftware/votifier/model/ListenerLoader.java +++ b/src/main/java/com/vexsoftware/votifier/model/ListenerLoader.java @@ -1,15 +1,16 @@ package com.vexsoftware.votifier.model; import java.io.File; -import java.net.*; -import java.util.*; -import java.util.logging.*; -import com.vexsoftware.votifier.Votifier; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; /** * Loads vote listeners. Listeners that cannot be instantiated will be skipped. - * - * @author Blake Beaupain */ public class ListenerLoader { @@ -22,35 +23,32 @@ public class ListenerLoader { * @param directory * The directory */ - public static List load(String directory) /* throws Exception */{ + @SuppressWarnings("resource") + public static List load(File dir) { List listeners = new ArrayList(); - File dir = new File(directory); // Verify configured vote listener directory exists if (!dir.exists()) { - LOG.log(Level.WARNING, - "No listeners loaded! Cannot find listener directory '" - + dir + "' "); + LOG.warning("No listeners loaded! Cannot find listener directory '" + dir + "' "); return listeners; } // Load the vote listener instances. ClassLoader loader; try { - loader = new URLClassLoader(new URL[] { dir.toURI().toURL() }, - VoteListener.class.getClassLoader()); - } catch (MalformedURLException ex) { + loader = new URLClassLoader(new URL[] { dir.toURI().toURL() }, VoteListener.class.getClassLoader()); + } catch (MalformedURLException exception) { LOG.log(Level.SEVERE, - "Error while configuring listener class loader", ex); + "Error while configuring listener class loader", + exception); return listeners; } + for (File file : dir.listFiles()) { if (!file.getName().endsWith(".class")) { continue; // Only load class files! } - String name = file.getName().substring(0, - file.getName().lastIndexOf(".")); - + String name = file.getName().substring(0, file.getName().lastIndexOf(".")); try { Class clazz = loader.loadClass(name); Object object = clazz.newInstance(); @@ -60,19 +58,16 @@ public static List load(String directory) /* throws Exception */{ } VoteListener listener = (VoteListener) object; listeners.add(listener); - LOG.info("Loaded vote listener: " - + listener.getClass().getSimpleName()); + LOG.info("Loaded vote listener: " + listener.getClass().getSimpleName()); } /* * Catch the usual definition and dependency problems with a loader * and skip the problem listener. */ - catch (Exception ex) { - LOG.log(Level.WARNING, "Error loading '" + name - + "' listener! Listener disabled."); - } catch (Error ex) { - LOG.log(Level.WARNING, "Error loading '" + name - + "' listener! Listener disabled."); + catch (Exception exception) { + LOG.warning("Error loading '" + name + "' listener! Listener disabled."); + } catch (Error exception) { + LOG.warning("Error loading '" + name + "' listener! Listener disabled."); } } return listeners; diff --git a/src/main/java/com/vexsoftware/votifier/model/Vote.java b/src/main/java/com/vexsoftware/votifier/model/Vote.java index c8ef4b0..06d0c56 100644 --- a/src/main/java/com/vexsoftware/votifier/model/Vote.java +++ b/src/main/java/com/vexsoftware/votifier/model/Vote.java @@ -20,8 +20,6 @@ /** * A model for a vote. - * - * @author Blake Beaupain */ public class Vote { @@ -34,13 +32,19 @@ public class Vote { /** The address of the voter. */ private String address; - /** The date and time of the vote. */ + /** The unix timeStamp of the vote. */ private String timeStamp; - @Override - public String toString() { - return "Vote (from:" + serviceName + " username:" + username - + " address:" + address + " timeStamp:" + timeStamp + ")"; + @Deprecated + public Vote() { + + } + + public Vote(String serviceName, String username, String address, String timeStamp) { + this.serviceName = serviceName; + this.username = username.length() <= 16 ? username : username.substring(0, 16); + this.address = address; + this.timeStamp = timeStamp; } /** @@ -49,6 +53,7 @@ public String toString() { * @param serviceName * The new serviceName */ + @Deprecated public void setServiceName(String serviceName) { this.serviceName = serviceName; } @@ -68,6 +73,7 @@ public String getServiceName() { * @param username * The new username */ + @Deprecated public void setUsername(String username) { this.username = username.length() <= 16 ? username : username.substring(0, 16); } @@ -87,6 +93,7 @@ public String getUsername() { * @param address * The new address */ + @Deprecated public void setAddress(String address) { this.address = address; } @@ -106,6 +113,7 @@ public String getAddress() { * @param timeStamp * The new time stamp */ + @Deprecated public void setTimeStamp(String timeStamp) { this.timeStamp = timeStamp; } @@ -119,4 +127,10 @@ public String getTimeStamp() { return timeStamp; } + @Override + public String toString() { + return "Vote (from:" + serviceName + " username:" + username + + " address:" + address + " timeStamp:" + timeStamp + ")"; + } + } diff --git a/src/main/java/com/vexsoftware/votifier/model/VoteListener.java b/src/main/java/com/vexsoftware/votifier/model/VoteListener.java index b6c8da4..a54fb25 100644 --- a/src/main/java/com/vexsoftware/votifier/model/VoteListener.java +++ b/src/main/java/com/vexsoftware/votifier/model/VoteListener.java @@ -20,8 +20,6 @@ /** * A listener for votes. - * - * @author Blake Beaupain */ public interface VoteListener { diff --git a/src/main/java/com/vexsoftware/votifier/model/VotifierEvent.java b/src/main/java/com/vexsoftware/votifier/model/VotifierEvent.java index eaa4f95..57c4d80 100644 --- a/src/main/java/com/vexsoftware/votifier/model/VotifierEvent.java +++ b/src/main/java/com/vexsoftware/votifier/model/VotifierEvent.java @@ -6,11 +6,9 @@ * {@code VotifierEvent} is a custom Bukkit event class that is sent * synchronously to CraftBukkit's main thread allowing other plugins to listener * for votes. - * - * @author frelling - * */ public class VotifierEvent extends Event { + /** * Event listener handler list. */ @@ -48,4 +46,5 @@ public HandlerList getHandlers() { public static HandlerList getHandlerList() { return handlers; } + } diff --git a/src/main/java/com/vexsoftware/votifier/model/listeners/BasicVoteListener.java b/src/main/java/com/vexsoftware/votifier/model/listeners/BasicVoteListener.java index e81cffb..8943581 100644 --- a/src/main/java/com/vexsoftware/votifier/model/listeners/BasicVoteListener.java +++ b/src/main/java/com/vexsoftware/votifier/model/listeners/BasicVoteListener.java @@ -31,7 +31,7 @@ public class BasicVoteListener implements VoteListener { /** The logger instance. */ - private Logger log = Logger.getLogger("BasicVoteListener"); + private static final Logger log = Logger.getLogger("BasicVoteListener"); public void voteMade(Vote vote) { log.info("Received: " + vote); diff --git a/src/main/java/com/vexsoftware/votifier/net/Protocol.java b/src/main/java/com/vexsoftware/votifier/net/Protocol.java new file mode 100644 index 0000000..95d9a19 --- /dev/null +++ b/src/main/java/com/vexsoftware/votifier/net/Protocol.java @@ -0,0 +1,30 @@ +package com.vexsoftware.votifier.net; + +import java.io.InputStream; +import java.io.OutputStream; + +import com.vexsoftware.votifier.Votifier; +import com.vexsoftware.votifier.model.Vote; + +/** + * An interface to implement the Votifier protocol. + */ +public interface Protocol { + + /** + * Handle the protocol + * + * @param plugin + * Votifier plugin + * @param in + * The receiving connection's input stream + * @param out + * The receiving connection's output stream + * @param challenge + * The challenge issued in this connection + * @throws Exception + * If an error occurs + */ + public Vote handleProtocol(Votifier plugin, InputStream in, OutputStream out, String challenge) throws Exception; + +} diff --git a/src/main/java/com/vexsoftware/votifier/net/VoteAcceptor.java b/src/main/java/com/vexsoftware/votifier/net/VoteAcceptor.java new file mode 100644 index 0000000..d8b1fdf --- /dev/null +++ b/src/main/java/com/vexsoftware/votifier/net/VoteAcceptor.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2012 Vex Software LLC + * This file is part of Votifier. + * + * Votifier is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Votifier is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Votifier. If not, see . + */ + +package com.vexsoftware.votifier.net; + +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketException; +import java.util.logging.*; + +import com.vexsoftware.votifier.Votifier; + +/** + * The vote receiving acceptor. + */ +public class VoteAcceptor extends Thread { + + /** The logger instance. */ + private static final Logger LOG = Logger.getLogger("Votifier"); + + /** The plugin. */ + private final Votifier plugin; + + /** The host to listen on. */ + private final String host; + + /** The port to listen on. */ + private final int port; + + /** The server socket. */ + private ServerSocket server; + + /** The running flag. */ + private boolean running = true; + + /** + * Instantiates a new vote receiver. + * + * @param host + * The host to listen on + * @param port + * The port to listen on + */ + public VoteAcceptor(final Votifier plugin, String host, int port) throws Exception { + this.plugin = plugin; + this.host = host; + this.port = port; + + initialize(); + } + + private void initialize() throws Exception { + try { + server = new ServerSocket(); + server.bind(new InetSocketAddress(host, port)); + } catch (Exception exception) { + LOG.severe("Error initializing vote receiver. Please verify that the configured"); + LOG.severe("IP address and port are not already in use. This is a common problem"); + LOG.log(Level.SEVERE, + "with hosting services and, if so, you should check with your hosting provider.", + exception); + throw exception; + } + } + + /** + * Shuts the vote receiver down cleanly. + */ + public void shutdown() { + running = false; + if (server == null) { + return; + } + try { + server.close(); + } catch (Exception exception) { + LOG.warning("Unable to shut down vote receiver cleanly."); + } + } + + public void run() { + while (running) { + try { + Socket socket = server.accept(); + socket.setSoTimeout(5000); // Don't hang on slow connections. + new Thread(new VoteClientThread(plugin, socket)).start(); + } catch (SocketException exception) { + LOG.warning("Protocol error. Ignoring packet - " + exception.getLocalizedMessage()); + } catch (Exception exception) { + LOG.log(Level.WARNING, + "Exception caught while receiving a vote notification", + exception); + } + } + } + +} diff --git a/src/main/java/com/vexsoftware/votifier/net/VoteClientThread.java b/src/main/java/com/vexsoftware/votifier/net/VoteClientThread.java new file mode 100644 index 0000000..41d992e --- /dev/null +++ b/src/main/java/com/vexsoftware/votifier/net/VoteClientThread.java @@ -0,0 +1,133 @@ +package com.vexsoftware.votifier.net; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.math.BigInteger; +import java.net.Socket; +import java.security.SecureRandom; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.crypto.BadPaddingException; + +import com.vexsoftware.votifier.Votifier; +import com.vexsoftware.votifier.model.Vote; +import com.vexsoftware.votifier.model.VoteListener; +import com.vexsoftware.votifier.model.VotifierEvent; +import com.vexsoftware.votifier.net.v1.ProtocolV1; +import com.vexsoftware.votifier.net.v2.ProtocolV2; + +/** + * The vote client thread. + */ +public class VoteClientThread implements Runnable { + + /** The logger instance. */ + private static final Logger LOG = Logger.getLogger("Votifier"); + + /** The random instance. */ + private static final SecureRandom RANDOM = new SecureRandom(); + + /** The plugin. */ + private Votifier plugin; + + /** The socket. */ + private Socket socket; + + public VoteClientThread(Votifier plugin, Socket socket) { + this.plugin = plugin; + this.socket = socket; + } + + public void run() { + InputStream inputStream = null; + OutputStream outputStream = null; + try { + inputStream = new BufferedInputStream(socket.getInputStream()); + outputStream = new BufferedOutputStream(socket.getOutputStream()); + + // Generate challenge + String challenge = new BigInteger(130, RANDOM).toString(32); // This seems pretty strong and is seeded from SecureRandom + + // Send them our version. + outputStream.write(("VOTIFIER " + plugin.getVersion() + " " + challenge + "\r\n").getBytes("UTF-8")); + outputStream.flush(); + + // Which protocol do we use? + inputStream.mark(1); + int firstByte = inputStream.read(); + inputStream.reset(); + + Protocol protocol; + if (firstByte == (int) '{') { + protocol = ProtocolV2.INSTANCE; + } else if (this.plugin.isOldProtocol()) { + protocol = ProtocolV1.INSTANCE; + } else { + throw new Exception("Unknown protocol"); + } + + // Read the vote + final Vote vote = protocol.handleProtocol(plugin, inputStream, outputStream, challenge); + + if (plugin.isDebug()) { + LOG.info("Received vote record -> " + vote); + } + + // Dispatch the vote to all listeners. + for (VoteListener listener : plugin.getListeners()) { + try { + listener.voteMade(vote); + } catch (Exception exception) { + String vlName = listener.getClass().getSimpleName(); + LOG.log(Level.WARNING, + "Exception caught while sending the vote notification to the '" + vlName + "' listener", + exception); + } + } + + // Call event in a synchronized fashion to ensure that the + // custom event runs in the + // the main server thread, not this one. + plugin.getServer().getScheduler().runTask(plugin, new Runnable() { + public void run() { + plugin.getServer().getPluginManager().callEvent(new VotifierEvent(vote)); + } + }); + } catch (BadPaddingException exception) { + LOG.warning("Unable to decrypt vote record. Make sure that that your public key"); + LOG.log(Level.WARNING, + "matches the one you gave the server list.", + exception); + } catch(Exception exception) { + LOG.log(Level.WARNING, + "Exception caught while receiving a vote notification", + exception); + } finally { + if (outputStream != null) { + try { + inputStream.close(); + } catch(Exception exception) { + // ignore + } + } + if (outputStream != null) { + try { + outputStream.close(); + } catch(Exception exception) { + // ignore + } + } + if (socket != null) { + try { + socket.close(); + } catch(Exception exception) { + // ignore + } + } + } + } + +} diff --git a/src/main/java/com/vexsoftware/votifier/net/VoteReceiver.java b/src/main/java/com/vexsoftware/votifier/net/VoteReceiver.java deleted file mode 100644 index 60173d8..0000000 --- a/src/main/java/com/vexsoftware/votifier/net/VoteReceiver.java +++ /dev/null @@ -1,222 +0,0 @@ -/* - * Copyright (C) 2012 Vex Software LLC - * This file is part of Votifier. - * - * Votifier is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Votifier is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Votifier. If not, see . - */ - -package com.vexsoftware.votifier.net; - -import java.io.BufferedWriter; -import java.io.InputStream; -import java.io.OutputStreamWriter; -import java.net.InetSocketAddress; -import java.net.ServerSocket; -import java.net.Socket; -import java.net.SocketException; -import java.util.logging.*; -import javax.crypto.BadPaddingException; -import org.bukkit.Bukkit; - -import com.vexsoftware.votifier.Votifier; -import com.vexsoftware.votifier.crypto.RSA; -import com.vexsoftware.votifier.model.*; - -/** - * The vote receiving server. - * - * @author Blake Beaupain - * @author Kramer Campbell - */ -public class VoteReceiver extends Thread { - - /** The logger instance. */ - private static final Logger LOG = Logger.getLogger("Votifier"); - - private final Votifier plugin; - - /** The host to listen on. */ - private final String host; - - /** The port to listen on. */ - private final int port; - - /** The server socket. */ - private ServerSocket server; - - /** The running flag. */ - private boolean running = true; - - /** - * Instantiates a new vote receiver. - * - * @param host - * The host to listen on - * @param port - * The port to listen on - */ - public VoteReceiver(final Votifier plugin, String host, int port) - throws Exception { - this.plugin = plugin; - this.host = host; - this.port = port; - - initialize(); - } - - private void initialize() throws Exception { - try { - server = new ServerSocket(); - server.bind(new InetSocketAddress(host, port)); - } catch (Exception ex) { - LOG.log(Level.SEVERE, - "Error initializing vote receiver. Please verify that the configured"); - LOG.log(Level.SEVERE, - "IP address and port are not already in use. This is a common problem"); - LOG.log(Level.SEVERE, - "with hosting services and, if so, you should check with your hosting provider.", - ex); - throw new Exception(ex); - } - } - - /** - * Shuts the vote receiver down cleanly. - */ - public void shutdown() { - running = false; - if (server == null) - return; - try { - server.close(); - } catch (Exception ex) { - LOG.log(Level.WARNING, "Unable to shut down vote receiver cleanly."); - } - } - - @Override - public void run() { - - // Main loop. - while (running) { - try { - Socket socket = server.accept(); - socket.setSoTimeout(5000); // Don't hang on slow connections. - BufferedWriter writer = new BufferedWriter( - new OutputStreamWriter(socket.getOutputStream())); - InputStream in = socket.getInputStream(); - - // Send them our version. - writer.write("VOTIFIER " + Votifier.getInstance().getVersion()); - writer.newLine(); - writer.flush(); - - // Read the 256 byte block. - byte[] block = new byte[256]; - in.read(block, 0, block.length); - - // Decrypt the block. - block = RSA.decrypt(block, Votifier.getInstance().getKeyPair() - .getPrivate()); - int position = 0; - - // Perform the opcode check. - String opcode = readString(block, position); - position += opcode.length() + 1; - if (!opcode.equals("VOTE")) { - // Something went wrong in RSA. - throw new Exception("Unable to decode RSA"); - } - - // Parse the block. - String serviceName = readString(block, position); - position += serviceName.length() + 1; - String username = readString(block, position); - position += username.length() + 1; - String address = readString(block, position); - position += address.length() + 1; - String timeStamp = readString(block, position); - position += timeStamp.length() + 1; - - // Create the vote. - final Vote vote = new Vote(); - vote.setServiceName(serviceName); - vote.setUsername(username); - vote.setAddress(address); - vote.setTimeStamp(timeStamp); - - if (plugin.isDebug()) - LOG.info("Received vote record -> " + vote); - - // Dispatch the vote to all listeners. - for (VoteListener listener : Votifier.getInstance() - .getListeners()) { - try { - listener.voteMade(vote); - } catch (Exception ex) { - String vlName = listener.getClass().getSimpleName(); - LOG.log(Level.WARNING, - "Exception caught while sending the vote notification to the '" - + vlName + "' listener", ex); - } - } - - // Call event in a synchronized fashion to ensure that the - // custom event runs in the - // the main server thread, not this one. - plugin.getServer().getScheduler() - .scheduleSyncDelayedTask(plugin, new Runnable() { - public void run() { - Bukkit.getServer().getPluginManager() - .callEvent(new VotifierEvent(vote)); - } - }); - - // Clean up. - writer.close(); - in.close(); - socket.close(); - } catch (SocketException ex) { - LOG.log(Level.WARNING, "Protocol error. Ignoring packet - " - + ex.getLocalizedMessage()); - } catch (BadPaddingException ex) { - LOG.log(Level.WARNING, - "Unable to decrypt vote record. Make sure that that your public key"); - LOG.log(Level.WARNING, - "matches the one you gave the server list.", ex); - } catch (Exception ex) { - LOG.log(Level.WARNING, - "Exception caught while receiving a vote notification", - ex); - } - } - } - - /** - * Reads a string from a block of data. - * - * @param data - * The data to read from - * @return The string - */ - private String readString(byte[] data, int offset) { - StringBuilder builder = new StringBuilder(); - for (int i = offset; i < data.length; i++) { - if (data[i] == '\n') - break; // Delimiter reached. - builder.append((char) data[i]); - } - return builder.toString(); - } -} diff --git a/src/main/java/com/vexsoftware/votifier/net/v1/ProtocolV1.java b/src/main/java/com/vexsoftware/votifier/net/v1/ProtocolV1.java new file mode 100644 index 0000000..1770f71 --- /dev/null +++ b/src/main/java/com/vexsoftware/votifier/net/v1/ProtocolV1.java @@ -0,0 +1,87 @@ +package com.vexsoftware.votifier.net.v1; + +import java.io.InputStream; +import java.io.OutputStream; + +import com.vexsoftware.votifier.Votifier; +import com.vexsoftware.votifier.model.Vote; +import com.vexsoftware.votifier.net.Protocol; +import com.vexsoftware.votifier.util.rsa.RSA; + +/** + * Version 1 of the Votifier protocol. + */ +public class ProtocolV1 implements Protocol { + + /** + * Singleton. + */ + public static final ProtocolV1 INSTANCE = new ProtocolV1(); + + private ProtocolV1() { + // private + } + + /** + * Handle the protocol + * + * @param plugin + * Votifier plugin + * @param in + * The receiving connection's input stream + * @param out + * The receiving connection's output stream + * @param challenge + * The challenge issued in this connection + * @throws Exception + * If an error occurs + */ + public Vote handleProtocol(Votifier plugin, InputStream in, OutputStream out, String challenge) throws Exception { + // Read the 256 byte block. + byte[] block = new byte[256]; + in.read(block, 0, block.length); + + // Decrypt the block. + block = RSA.decrypt(block, plugin.getKeyPair().getPrivate()); + int position = 0; + + // Perform the opcode check. + String opcode = readString(block, position); + position += opcode.length() + 1; + if (!opcode.equals("VOTE")) { + // Something went wrong in RSA. + throw new Exception("Unable to decode RSA"); + } + + // Parse the block. + String serviceName = readString(block, position); + position += serviceName.length() + 1; + String username = readString(block, position); + position += username.length() + 1; + String address = readString(block, position); + position += address.length() + 1; + String timeStamp = readString(block, position); + position += timeStamp.length() + 1; + + return new Vote(serviceName, username, address, timeStamp); + } + + /** + * Reads a string from a block of data. + * + * @param data + * The data to read from + * @return The string + */ + private static String readString(byte[] data, int offset) { + StringBuilder builder = new StringBuilder(); + for (int i = offset; i < data.length; i++) { + if (data[i] == '\n') { + break; + } + builder.append((char) data[i]); + } + return builder.toString(); + } + +} diff --git a/src/main/java/com/vexsoftware/votifier/net/v2/JsonMessage.java b/src/main/java/com/vexsoftware/votifier/net/v2/JsonMessage.java new file mode 100644 index 0000000..5b9b27e --- /dev/null +++ b/src/main/java/com/vexsoftware/votifier/net/v2/JsonMessage.java @@ -0,0 +1,47 @@ +package com.vexsoftware.votifier.net.v2; + +/** + * Json message + */ +public class JsonMessage { + + /** + * The service. + */ + public String service; + + /** + * The signature of the payload. + */ + public String signature; + + /** + * Json representation of a VoteMessagePayload + */ + public String payload; + + /** + * + * @return The service. + */ + public String getService() { + return service; + } + + /** + * + * @return The signature of the payload. + */ + public String getSignature() { + return signature; + } + + /** + * + * @return Json representation of a VoteMessagePayload + */ + public String getPayload() { + return payload; + } + +} diff --git a/src/main/java/com/vexsoftware/votifier/net/v2/JsonMessagePayload.java b/src/main/java/com/vexsoftware/votifier/net/v2/JsonMessagePayload.java new file mode 100644 index 0000000..ed6cf84 --- /dev/null +++ b/src/main/java/com/vexsoftware/votifier/net/v2/JsonMessagePayload.java @@ -0,0 +1,73 @@ +package com.vexsoftware.votifier.net.v2; + +/** + * Json message payload + */ +public class JsonMessagePayload { + + /** + * Vote host. + */ + public String host; + + /** + * Vote username. + */ + public String username; + + /** + * Vote address. + */ + public String address; + + /** + * Vote challenge. + */ + private String challenge; + + /** + * Vote unix timestamp. + */ + public long timestamp; + + /** + * + * @return Vote host. + */ + public String getHost() { + return host; + } + + /** + * + * @return Vote username. + */ + public String getUsername() { + return username; + } + + /** + * + * @return Vote address. + */ + public String getAddress() { + return address; + } + + /** + * + * @return Vote challenge. + */ + public String getChallenge() { + return challenge; + } + + /** + * + * @return Vote unix timestamp. + */ + public long getTimestamp() { + return timestamp; + } + +} diff --git a/src/main/java/com/vexsoftware/votifier/net/v2/ProtocolV2.java b/src/main/java/com/vexsoftware/votifier/net/v2/ProtocolV2.java new file mode 100644 index 0000000..a091ea2 --- /dev/null +++ b/src/main/java/com/vexsoftware/votifier/net/v2/ProtocolV2.java @@ -0,0 +1,90 @@ +package com.vexsoftware.votifier.net.v2; + +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.security.PublicKey; +import java.util.HashSet; +import java.util.Set; + +import javax.xml.bind.DatatypeConverter; + +import org.bukkit.craftbukkit.libs.com.google.gson.Gson; + +import com.vexsoftware.votifier.Votifier; +import com.vexsoftware.votifier.model.Vote; +import com.vexsoftware.votifier.net.Protocol; +import com.vexsoftware.votifier.util.rsa.RSA; + +/** + * Version 2 of the Votifier protocol. + */ +public class ProtocolV2 implements Protocol { + + /** + * Singleton. + */ + public static final ProtocolV2 INSTANCE = new ProtocolV2(); + + /** + * Our instance of Gson. + */ + public static final Gson GSON = new Gson(); + + private Set replayCache = new HashSet(); + + private ProtocolV2() { + // private + } + + /** + * Handle the protocol + * + * @param plugin + * Votifier plugin + * @param in + * The receiving connection's input stream + * @param out + * The receiving connection's output stream + * @param challenge + * The challenge issued in this connection + * @throws Exception + * If an error occurs + */ + public Vote handleProtocol(Votifier plugin, InputStream in, OutputStream out, String challenge) throws Exception { + BufferedReader reader = null; + try { + reader = new BufferedReader(new InputStreamReader(in)); + JsonMessage jsonMessage = GSON.fromJson(reader.readLine(), JsonMessage.class); + + String service = jsonMessage.getService(); + PublicKey publicKey = plugin.getWebsites().get(service); + // Check if the service exists + if (publicKey == null) { + throw new Exception("Unknown service: " + service); + } + String payload = jsonMessage.getPayload(); + // Check the RSA signature + if (!RSA.verify(payload.getBytes("UTF-8"), DatatypeConverter.parseBase64Binary(jsonMessage.getSignature().trim()), publicKey)) { + throw new Exception("Signature not valid"); + } + + JsonMessagePayload jsonMessagePayload = GSON.fromJson(jsonMessage.getPayload(), JsonMessagePayload.class); + if(!jsonMessagePayload.getChallenge().equals(challenge)) { + throw new Exception("Challenge not valid"); + } + + return new Vote(service, jsonMessagePayload.getUsername(), jsonMessagePayload.getAddress(), Long.toString(jsonMessagePayload.getTimestamp())); + } finally { + if(reader != null) { + try { + reader.close(); + } catch(Exception exception) { + // ignore + } + } + } + } + +} diff --git a/src/main/java/com/vexsoftware/votifier/LogFilter.java b/src/main/java/com/vexsoftware/votifier/util/LogFilter.java similarity index 79% rename from src/main/java/com/vexsoftware/votifier/LogFilter.java rename to src/main/java/com/vexsoftware/votifier/util/LogFilter.java index efda930..4175739 100644 --- a/src/main/java/com/vexsoftware/votifier/LogFilter.java +++ b/src/main/java/com/vexsoftware/votifier/util/LogFilter.java @@ -1,14 +1,11 @@ -package com.vexsoftware.votifier; +package com.vexsoftware.votifier.util; import java.util.logging.*; /** * A custom log filter for prepending plugin identifier on all log messages. - * - * @author frelling - * */ -class LogFilter implements Filter { +public class LogFilter implements Filter { private String prefix; @@ -29,4 +26,5 @@ public boolean isLoggable(LogRecord record) { record.setMessage(prefix + record.getMessage()); return true; } + } diff --git a/src/main/java/com/vexsoftware/votifier/crypto/RSA.java b/src/main/java/com/vexsoftware/votifier/util/rsa/RSA.java similarity index 63% rename from src/main/java/com/vexsoftware/votifier/crypto/RSA.java rename to src/main/java/com/vexsoftware/votifier/util/rsa/RSA.java index 41df187..4bba904 100644 --- a/src/main/java/com/vexsoftware/votifier/crypto/RSA.java +++ b/src/main/java/com/vexsoftware/votifier/util/rsa/RSA.java @@ -16,18 +16,17 @@ * along with Votifier. If not, see . */ -package com.vexsoftware.votifier.crypto; +package com.vexsoftware.votifier.util.rsa; import java.security.PrivateKey; import java.security.PublicKey; +import java.security.Signature; import javax.crypto.Cipher; /** * Static RSA utility methods for encrypting and decrypting blocks of * information. - * - * @author Blake Beaupain */ public class RSA { @@ -38,9 +37,10 @@ public class RSA { * The data to encrypt * @param key * The key to encrypt with - * @return The encrypted data + * @return + * The encrypted data * @throws Exception - * If an error occurs + * If an error occurs */ public static byte[] encrypt(byte[] data, PublicKey key) throws Exception { Cipher cipher = Cipher.getInstance("RSA"); @@ -55,14 +55,36 @@ public static byte[] encrypt(byte[] data, PublicKey key) throws Exception { * The data to decrypt * @param key * The key to decrypt with - * @return The decrypted data + * @return + * The decrypted data * @throws Exception - * If an error occurs + * If an error occurs */ public static byte[] decrypt(byte[] data, PrivateKey key) throws Exception { Cipher cipher = Cipher.getInstance("RSA"); cipher.init(Cipher.DECRYPT_MODE, key); return cipher.doFinal(data); } + + /** + * Verify if the signature against the public key matches the data + * + * @param data + * The data to compare against + * @param signatureData + * The signature of the data + * @param publicKey + * The keypair's public key used to generate the signature + * @return + * If the signature against the public key matches the data + * @throws Exception + * If an error occurs + */ + public static boolean verify(byte[] data, byte[] signatureData, PublicKey publicKey) throws Exception { + Signature signature = Signature.getInstance("SHA256withRSA"); + signature.initVerify(publicKey); + signature.update(data); + return signature.verify(signatureData); + } } diff --git a/src/main/java/com/vexsoftware/votifier/util/rsa/RSAIO.java b/src/main/java/com/vexsoftware/votifier/util/rsa/RSAIO.java new file mode 100644 index 0000000..ca0d134 --- /dev/null +++ b/src/main/java/com/vexsoftware/votifier/util/rsa/RSAIO.java @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2011 Vex Software LLC + * This file is part of Votifier. + * + * Votifier is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Votifier is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Votifier. If not, see . + */ + +package com.vexsoftware.votifier.util.rsa; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.net.URL; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; + +import javax.xml.bind.DatatypeConverter; + +import net.minecraft.util.org.apache.commons.io.IOUtils; + +/** + * Static utility methods for saving and loading RSA key pairs. + */ +public class RSAIO { + + /** + * Saves the key pair to the disk. + * + * @param directory + * The directory to save to + * @param keyPair + * The key pair to save + * @throws Exception + * If an error occurs + */ + public static void save(File directory, KeyPair keyPair) throws Exception { + PrivateKey privateKey = keyPair.getPrivate(); + PublicKey publicKey = keyPair.getPublic(); + + // Store the public key. + X509EncodedKeySpec publicSpec = new X509EncodedKeySpec(publicKey.getEncoded()); + FileOutputStream out = null; + try { + out = new FileOutputStream(directory + "/public.key"); + out.write(DatatypeConverter.printBase64Binary(publicSpec.getEncoded()).getBytes()); + } finally { + try { + out.close(); + } catch(Exception exception) { + // ignore + } + } + + // Store the private key. + PKCS8EncodedKeySpec privateSpec = new PKCS8EncodedKeySpec(privateKey.getEncoded()); + try { + out = new FileOutputStream(directory + "/private.key"); + out.write(DatatypeConverter.printBase64Binary(privateSpec.getEncoded()).getBytes()); + } finally { + try { + out.close(); + } catch(Exception exception) { + // ignore + } + } + } + + /** + * Loads an RSA key pair from a directory. The directory must have the files + * "public.key" and "private.key". + * + * @param directory + * The directory to load from + * @return The key pair + * @throws Exception + * If an error occurs + */ + public static KeyPair load(File directory) throws Exception { + // Read the public key file. + File publicKeyFile = new File(directory + "/public.key"); + FileInputStream in = null; + byte[] encodedPublicKey; + try { + in = new FileInputStream(directory + "/public.key"); + encodedPublicKey = new byte[(int) publicKeyFile.length()]; + in.read(encodedPublicKey); + encodedPublicKey = DatatypeConverter.parseBase64Binary(new String(encodedPublicKey)); + } finally { + try { + in.close(); + } catch(Exception exception) { + // ignore + } + } + + // Read the private key file. + File privateKeyFile = new File(directory + "/private.key"); + byte[] encodedPrivateKey; + try { + in = new FileInputStream(directory + "/private.key"); + encodedPrivateKey = new byte[(int) privateKeyFile.length()]; + in.read(encodedPrivateKey); + encodedPrivateKey = DatatypeConverter.parseBase64Binary(new String(encodedPrivateKey)); + } finally { + try { + in.close(); + } catch(Exception exception) { + // ignore + } + } + + // Instantiate and return the key pair. + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(encodedPublicKey); + PublicKey publicKey = keyFactory.generatePublic(publicKeySpec); + PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(encodedPrivateKey); + PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec); + return new KeyPair(publicKey, privateKey); + } + + /** + * Loads an RSA public key from a URL. + * + * @param url + * The URL that has the public key + * @return + * The public key + * @throws Exception + * If an error occurs + */ + public static PublicKey loadPublicKey(URL url) throws Exception { + String publicKey = new String(IOUtils.toByteArray(url), "UTF-8").replaceAll("(-+BEGIN PUBLIC KEY-+\\r?\\n|-+END PUBLIC KEY-+\\r?\\n?)", ""); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(DatatypeConverter.parseBase64Binary(publicKey)); + return keyFactory.generatePublic(publicKeySpec); + } + +} diff --git a/src/main/java/com/vexsoftware/votifier/crypto/RSAKeygen.java b/src/main/java/com/vexsoftware/votifier/util/rsa/RSAKeygen.java similarity index 93% rename from src/main/java/com/vexsoftware/votifier/crypto/RSAKeygen.java rename to src/main/java/com/vexsoftware/votifier/util/rsa/RSAKeygen.java index b8d4dd3..114cbfd 100644 --- a/src/main/java/com/vexsoftware/votifier/crypto/RSAKeygen.java +++ b/src/main/java/com/vexsoftware/votifier/util/rsa/RSAKeygen.java @@ -16,7 +16,7 @@ * along with Votifier. If not, see . */ -package com.vexsoftware.votifier.crypto; +package com.vexsoftware.votifier.util.rsa; import java.security.KeyPair; import java.security.KeyPairGenerator; @@ -25,8 +25,6 @@ /** * An RSA key pair generator. - * - * @author Blake Beaupain */ public class RSAKeygen { @@ -43,8 +41,7 @@ public class RSAKeygen { public static KeyPair generate(int bits) throws Exception { LOG.info("Votifier is generating an RSA key pair..."); KeyPairGenerator keygen = KeyPairGenerator.getInstance("RSA"); - RSAKeyGenParameterSpec spec = new RSAKeyGenParameterSpec(bits, - RSAKeyGenParameterSpec.F4); + RSAKeyGenParameterSpec spec = new RSAKeyGenParameterSpec(bits, RSAKeyGenParameterSpec.F4); keygen.initialize(spec); return keygen.generateKeyPair(); } diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml new file mode 100644 index 0000000..a8c6133 --- /dev/null +++ b/src/main/resources/config.yml @@ -0,0 +1,7 @@ +host: '' +port: 8192 +debug: false +listener_folder: '' +enable_old_protocol: true +websites: + Minestatus: https://www.minestatus.net/votifier/key \ No newline at end of file