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