Compare commits

...
16 Commits
22 changed files with 102 additions and 57 deletions

No files matched your search

+1 -3
View File
@@ -1,13 +1,11 @@
sudo: false
language: java
jdk:
- openjdk7
- oraclejdk7
- oraclejdk8
deploy:
provider: releases
api_key:
secure: j8y4qeDOXAYjmf6WU6EbpUA6AKTz26yJ4zNb6oAuB4h9Y3PpBRjFjCIBL6jJQnMxZMLjdgk72/3uqS7+//jJ8/iiY/OJhO2L+2us8TJDcQhH5EI1USe/lcZKf60iJjmd9Q9pjh1eHNYhxr6kyNLnpa6fw5b/hIBT6CvNZAJV1qbhRpHwtzbgdlMFaHGnpX192vJNw7E3uuJuSwue5XNK2ybDp81oTrM0Q16JCq/UsQhGq4Ba3JffOhdBhpfg4EI2jJfe3vN8CFqojCYKNSp7eL6DPFPaV4x8Ly8cRIOZ5Zb60m5SyWAcagPKJuUq3ZOTHfeVTjkI9ymZMHe1pla2Mq6eitIBwZIGnonjyMxcTdkR9EJgq9jo+1PzdStR/NlGLakBiwPzk8Ov0DZ+iOwpYUkISUkMU0UgKWwoFoRu18CHtp+ALEsoYZhBQGlcShq+4EGv9VMNBYk9vN3KXNRMVGKqXd6O3T6shvSQgoUn/3mvksOTAuhS5J4U3woHecDrXwl/RfP2aqiHhX/S7p7yY4hVcHu8kiSYpNQMbay+rNd6HxFcRe/U9HOUt6kW8/yo1ROyCxs3ze+N6LtGDHspgzkFaay6I6wMHYNDnzzRP4MC3Zc73zxViXol6dGBk4Tawsm16VaI6b1uyWLBGKvuU23azkmMTHUmGFXl2/BTaYw=
file: universal/target/nuvotifier-universal-2.3.2.jar
file: universal/target/nuvotifier-universal-2.3.2.2.jar
on:
tags: true
+3 -1
View File
@@ -2,6 +2,8 @@
NuVotifier is a Bukkit plugin whose purpose is to be notified (aka *votified*) when a vote is made on a Minecraft server top list for the server. NuVotifier creates a *lightweight* server that waits for connections by Minecraft server lists and uses a simple protocol to get the required information. NuVotifier is *secure*, and makes sure that all vote notifications are delivered by authentic top lists.
[![Build Status](https://travis-ci.org/NuVotifier/NuVotifier.svg?branch=master)](https://travis-ci.org/NuVotifier/NuVotifier)
## Why fork Votifier?
Votifier has lagged in activity, has severe security flaws in its protocol, and is vulnerable to denial of service attacks.
@@ -27,7 +29,7 @@ NuVotifier configures itself the first time it is run. If you have an existing V
If you want to customize NuVotifier, simply the edit `./plugins/(Nu)Votifier/config.yml` file.
An in-depth configuration guide is located in `usage.md` of this repository.
An in-depth configuration guide is located in the [usage](https://github.com/NuVotifier/NuVotifier/blob/master/usage.md) document.
### Tokens
+1 -1
View File
@@ -5,7 +5,7 @@
<parent>
<artifactId>nuvotifier</artifactId>
<groupId>com.vexsoftware</groupId>
<version>2.3.2</version>
<version>2.3.2.2</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -258,7 +258,7 @@ public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, Votifie
public void operationComplete(ChannelFuture future) throws Exception {
if (future.isSuccess()) {
serverChannel = future.channel();
getLogger().info("Votifier enabled.");
getLogger().info("Votifier enabled on socket "+serverChannel.localAddress()+".");
} else {
SocketAddress socketAddress = future.channel().localAddress();
if (socketAddress == null) {
+1 -1
View File
@@ -5,7 +5,7 @@
<parent>
<artifactId>nuvotifier</artifactId>
<groupId>com.vexsoftware</groupId>
<version>2.3.2</version>
<version>2.3.2.2</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -221,7 +221,7 @@ public class NuVotifier extends Plugin implements VoteHandler, VotifierPlugin {
public void operationComplete(ChannelFuture future) throws Exception {
if (future.isSuccess()) {
serverChannel = future.channel();
getLogger().info("Votifier enabled.");
getLogger().info("Votifier enabled on socket "+serverChannel.localAddress()+".");
} else {
SocketAddress socketAddress = future.channel().localAddress();
if (socketAddress == null) {
@@ -293,7 +293,8 @@ public class NuVotifier extends Plugin implements VoteHandler, VotifierPlugin {
continue;
}
ProxyForwardingVoteSource.BackendServer server = new ProxyForwardingVoteSource.BackendServer(s,
new InetSocketAddress(address, section.getShort("port")), KeyCreator.createKeyFrom(section.getString("key")));
new InetSocketAddress(address, section.getShort("port")),
KeyCreator.createKeyFrom(section.getString("token",section.getString("key"))));
serverList.add(server);
}
@@ -76,7 +76,7 @@ public class ProxyForwardingVoteSource implements ForwardingVoteSource {
public void onFailure(Throwable error) {
handleFailure(server, v, error, tries);
}
}));
}, plugin));
}
})
.connect(server.address)
@@ -1,5 +1,6 @@
package com.vexsoftware.votifier.bungee.forwarding.proxy.client;
import com.vexsoftware.votifier.bungee.NuVotifier;
import com.vexsoftware.votifier.model.Vote;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
@@ -8,10 +9,12 @@ import io.netty.handler.codec.CorruptedFrameException;
public class VotifierProtocol2HandshakeHandler extends SimpleChannelInboundHandler<String> {
private final Vote toSend;
private final VotifierResponseHandler responseHandler;
private final NuVotifier nuVotifier;
public VotifierProtocol2HandshakeHandler(Vote toSend, VotifierResponseHandler responseHandler) {
public VotifierProtocol2HandshakeHandler(Vote toSend, VotifierResponseHandler responseHandler, NuVotifier nuVotifier) {
this.toSend = toSend;
this.responseHandler = responseHandler;
this.nuVotifier = nuVotifier;
}
@Override
@@ -22,7 +25,9 @@ public class VotifierProtocol2HandshakeHandler extends SimpleChannelInboundHandl
}
VoteRequest request = new VoteRequest(handshakeContents[2], toSend);
System.out.println(request);
if (nuVotifier.isDebug()) {
nuVotifier.getLogger().info("Sent request: " + request.toString());
}
ctx.writeAndFlush(request);
ctx.pipeline().addLast(new VotifierProtocol2ResponseHandler(responseHandler));
ctx.pipeline().remove(this);
@@ -58,8 +58,8 @@ forwarding:
Hub:
address: 127.0.0.1
port: 8193
key: MyKeyHere
token: MyKeyHere
Hub2:
address: 127.0.0.1
port: 8194
key: AnotherKeyHere
token: AnotherKeyHere
+1 -1
View File
@@ -5,7 +5,7 @@
<parent>
<artifactId>nuvotifier</artifactId>
<groupId>com.vexsoftware</groupId>
<version>2.3.2</version>
<version>2.3.2.2</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -52,9 +52,6 @@ public class Vote {
}
public Vote(String serviceName, String username, String address, String timeStamp) {
if (username.length() > 16) {
throw new IllegalArgumentException("Username is too long.");
}
this.serviceName = serviceName;
this.username = username;
this.address = address;
@@ -105,9 +102,6 @@ public class Vote {
*/
@Deprecated
public void setUsername(String username) {
if (username.length() > 16) {
throw new IllegalArgumentException("Username is too long.");
}
this.username = username;
}
@@ -17,10 +17,14 @@ import java.util.List;
public class VotifierProtocol1Decoder extends ByteToMessageDecoder {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf buf, List<Object> list) throws Exception {
byte[] block = new byte[256];
if (buf.readableBytes() < 256) {
return;
}
byte[] block = new byte[buf.readableBytes()];
buf.getBytes(0, block);
// "Drain" the whole buffer
buf.readerIndex(buf.capacity());
buf.readerIndex(buf.readableBytes());
VotifierPlugin plugin = ctx.channel().attr(VotifierPlugin.KEY).get();
@@ -63,6 +63,10 @@ public class VotifierProtocol2Decoder extends MessageToMessageDecoder<String> {
UUID.fromString(votePayload.getString("uuid"));
}
if (votePayload.getString("username").length() > 16) {
throw new CorruptedFrameException("Username too long");
}
// Create the vote.
Vote vote = new Vote(votePayload);
list.add(vote);
@@ -75,15 +79,18 @@ public class VotifierProtocol2Decoder extends MessageToMessageDecoder<String> {
// This randomizes the byte order to make timing attacks more difficult.
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(key);
byte[] messageCalc = mac.doFinal(message);
byte[] calculatedSig = mac.doFinal(message);
Mac mac2 = Mac.getInstance("HmacSHA256");
// Generate a random key for use in comparison
byte[] randomKey = new byte[32];
RANDOM.nextBytes(randomKey);
// Then generate two HMACs for the different signatures found
Mac mac2 = Mac.getInstance("HmacSHA256");
mac2.init(new SecretKeySpec(randomKey, "HmacSHA256"));
byte[] clientSig = mac2.doFinal(sig);
mac2.reset();
byte[] realSig = mac2.doFinal(messageCalc);
byte[] realSig = mac2.doFinal(calculatedSig);
return Arrays.equals(clientSig, realSig);
}
@@ -33,8 +33,7 @@ public class VotifierProtocolDifferentiator extends ByteToMessageDecoder {
if (readable < 2) {
// Some retarded voting sites (PMC?) seem to send empty buffers for no good reason.
// TODO: How can we handle this?
throw new CorruptedFrameException("Frame is too short");
return;
}
short readMagic = buf.readShort();
@@ -46,6 +45,8 @@ public class VotifierProtocolDifferentiator extends ByteToMessageDecoder {
if (readMagic == PROTOCOL_2_MAGIC) {
// Short 0x733A + Message = Protocol v2 Vote
session.setVersion(VotifierSession.ProtocolVersion.TWO);
if (!testMode) {
ctx.pipeline().addAfter("protocolDifferentiator", "protocol2LengthDecoder", new LengthFieldBasedFrameDecoder(1024, 2, 2, 0, 4));
ctx.pipeline().addAfter("protocol2LengthDecoder", "protocol2StringDecoder", new StringDecoder(StandardCharsets.UTF_8));
@@ -53,17 +54,16 @@ public class VotifierProtocolDifferentiator extends ByteToMessageDecoder {
ctx.pipeline().addAfter("protocol2VoteDecoder", "protocol2StringEncoder", new StringEncoder(StandardCharsets.UTF_8));
ctx.pipeline().remove(this);
}
session.setVersion(VotifierSession.ProtocolVersion.TWO);
} else {
if (!allowv1) {
throw new CorruptedFrameException("This server only accepts well-formed Votifier v2 packets.");
}
// Probably Protocol v1 Vote Message
session.setVersion(VotifierSession.ProtocolVersion.ONE);
if (!testMode) {
ctx.pipeline().addAfter("protocolDifferentiator", "protocol1Handler", new VotifierProtocol1Decoder());
ctx.pipeline().remove(this);
}
session.setVersion(VotifierSession.ProtocolVersion.ONE);
}
}
}
@@ -0,0 +1,31 @@
package com.vexsoftware.votifier.net.protocol;
import com.vexsoftware.votifier.model.Vote;
import com.vexsoftware.votifier.net.protocol.v1crypto.RSA;
import java.nio.charset.StandardCharsets;
import java.security.PublicKey;
import java.util.ArrayList;
import java.util.List;
public class VoteUtil {
public static byte[] encodePOJOv1(Vote vote) throws Exception {
return encodePOJOv1(vote, TestVotifierPlugin.getI().getProtocolV1Key().getPublic());
}
public static byte[] encodePOJOv1(Vote vote, PublicKey key) throws Exception {
List<String> ordered = new ArrayList<>();
ordered.add("VOTE");
ordered.add(vote.getServiceName());
ordered.add(vote.getUsername());
ordered.add(vote.getAddress());
ordered.add(vote.getTimeStamp());
StringBuilder builder = new StringBuilder();
for (String s : ordered) {
builder.append(s).append('\n'); // naive join needed!
}
return RSA.encrypt(builder.toString().getBytes(StandardCharsets.UTF_8), key);
}
}
@@ -29,16 +29,12 @@ public class VotifierProtocol1DecoderTest {
@Test
public void testSuccessfulDecode() throws Exception {
// Encode a test vote.
String voteString = "VOTE\nTest\ntest\ntest\ntest\n";
// For reference, this is the same vote as a POJO:
Vote votePojo = new Vote("Test", "test", "test", "test");
// Send the vote
EmbeddedChannel channel = createChannel();
byte[] encrypted = RSA.encrypt(voteString.getBytes(StandardCharsets.UTF_8), TestVotifierPlugin.getI().getProtocolV1Key().getPublic());
byte[] encrypted = VoteUtil.encodePOJOv1(votePojo);
ByteBuf encryptedByteBuf = Unpooled.wrappedBuffer(encrypted);
assertTrue(channel.writeInbound(encryptedByteBuf));
@@ -81,8 +77,7 @@ public class VotifierProtocol1DecoderTest {
// Send the bad vote
EmbeddedChannel channel = createChannel();
String voteString = "VOTE\nTest\ntest\ntest\ntest\n";
byte[] encrypted = RSA.encrypt(voteString.getBytes(StandardCharsets.UTF_8), badPublicKey);
byte[] encrypted = VoteUtil.encodePOJOv1(new Vote("Test", "test", "test", "test"), badPublicKey);
ByteBuf encryptedByteBuf = Unpooled.wrappedBuffer(encrypted);
try {
@@ -1,5 +1,6 @@
package com.vexsoftware.votifier.net.protocol;
import com.vexsoftware.votifier.model.Vote;
import com.vexsoftware.votifier.net.VotifierSession;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
@@ -62,21 +63,6 @@ public class VotifierProtocolDifferentiatorTest {
channel.close();
}
@Test(expected = DecoderException.class)
public void failOnSmallBufferTest() {
EmbeddedChannel channel = new EmbeddedChannel(new VotifierProtocolDifferentiator(true, false));
ByteBuf buf = Unpooled.buffer(1);
buf.writeByte(0);
try {
channel.writeInbound(buf);
} finally {
buf.release();
channel.close();
}
}
@Test(expected = DecoderException.class)
public void failOnBadPacketTest() {
EmbeddedChannel channel = new EmbeddedChannel(new VotifierProtocolDifferentiator(true, false));
@@ -93,4 +79,22 @@ public class VotifierProtocolDifferentiatorTest {
channel.close();
}
}
@Test
public void tryIdentifyRealVotev1() throws Exception {
EmbeddedChannel channel = new EmbeddedChannel(new VotifierProtocolDifferentiator(true, true));
VotifierSession session = new VotifierSession();
channel.attr(VotifierSession.KEY).set(session);
Vote votePojo = new Vote("Test", "test", "test", "test");
byte[] encrypted = VoteUtil.encodePOJOv1(votePojo);
ByteBuf encryptedByteBuf = Unpooled.wrappedBuffer(encrypted);
channel.writeInbound(encryptedByteBuf);
assertEquals(VotifierSession.ProtocolVersion.ONE, session.getVersion());
encryptedByteBuf.release();
channel.close();
}
}
+1 -1
View File
@@ -4,7 +4,7 @@
<groupId>com.vexsoftware</groupId>
<artifactId>nuvotifier</artifactId>
<packaging>pom</packaging>
<version>2.3.2</version>
<version>2.3.2.2</version>
<modules>
<module>common</module>
<module>bukkit</module>
+1 -1
View File
@@ -5,7 +5,7 @@
<parent>
<artifactId>nuvotifier</artifactId>
<groupId>com.vexsoftware</groupId>
<version>2.3.2</version>
<version>2.3.2.2</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -52,7 +52,7 @@ import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
@Plugin(id = "nuvotifier", name = "NuVotifier", version = "2.3.2")
@Plugin(id = "nuvotifier", name = "NuVotifier", version = "2.3.2.2")
public class VotifierPlugin implements VoteHandler, com.vexsoftware.votifier.VotifierPlugin, ForwardedVoteListener {
private Logger logger;
@Inject
@@ -229,7 +229,7 @@ public class VotifierPlugin implements VoteHandler, com.vexsoftware.votifier.Vot
public void operationComplete(ChannelFuture future) throws Exception {
if (future.isSuccess()) {
serverChannel = future.channel();
logger.info("Votifier enabled.");
logger.info("Votifier enabled on socket "+serverChannel.localAddress()+".");
} else {
SocketAddress socketAddress = future.channel().localAddress();
if (socketAddress == null) {
@@ -21,4 +21,8 @@ public class VotifierEvent extends AbstractEvent {
public Cause getCause() {
return cause;
}
public Vote getVote() {
return vote;
}
}
+1 -1
View File
@@ -5,7 +5,7 @@
<parent>
<artifactId>nuvotifier</artifactId>
<groupId>com.vexsoftware</groupId>
<version>2.3.2</version>
<version>2.3.2.2</version>
</parent>
<modelVersion>4.0.0</modelVersion>