36 Commits
Author SHA1 Message Date
Fuzzlemann c5cd43f2fa Merge remote-tracking branch 'origin/master' 2020-12-02 14:12:51 +01:00
Fuzzlemann d69d4f7ba8 Removes unused config keys 2020-12-02 14:12:35 +01:00
Paul Zhang 52325a8a2c Merge pull request #24 from TCPShield/ip-validation
TCPShield v2.4
2020-12-01 23:22:08 +01:00
Fuzzlemann b07fe80979 Future-proofs HandshakePacketHandler for possible proxy-side changes in the handshake hostname data 2020-12-01 23:13:39 +01:00
Fuzzlemann c6471d8e15 Fixes compatibility on some BungeeCord forks like FlameCord 2020-12-01 23:11:48 +01:00
Fuzzlemann 0dd3c25421 Removes the empty line on fetching the TCPShield IP list 2020-12-01 22:14:23 +01:00
Fuzzlemann 42530caa57 Reverts re-activation of native Paper support 2020-11-30 17:11:53 +01:00
Fuzzlemann e58319cabe Bumps version to v2.4 2020-11-30 17:11:23 +01:00
Fuzzlemann 1de480d575 Merge remote-tracking branch 'origin/ip-validation' into ip-validation 2020-11-29 19:34:30 +01:00
Fuzzlemann 1959106ac8 Fixes that the updated IPs are appended instead of replaced 2020-11-29 19:33:50 +01:00
Paul Zhang 5750391e43 Update README.md
Updated because of the re-introduction of native paper support
2020-11-29 15:30:00 +01:00
Fuzzlemann 00f50110fe Adds checks for the origin IP (if it's originating from TCPShield nodes) in replacement of signature checks + timestamp validation. Custom CIDR records can be added by creating a file in the "ip-whitelist" folder and adding one CIDR entry per line. 2020-11-29 15:16:11 +01:00
Paul Zhang e14d937a70 Merge pull request #16 from JasperJH/master
Fixes searchFieldByClass looping forever and incorrect cache keys
2020-11-05 23:19:10 +01:00
Fuzzlemann 3d5dab69bb Changes wrong static final field naming 2020-11-01 19:09:00 +01:00
Fuzzlemann a4302e4de8 Merge branch 'master' of https://github.com/TCPShield/RealIP 2020-11-01 18:20:37 +01:00
Fuzzlemann e859a1c95a Fixes ReflectionUtils#searchFieldByClass cache (#16 by JasperJH) 2020-11-01 18:19:16 +01:00
Fuzzlemann a1e9b84b47 Refactors some test strings in TestConstants.java 2020-11-01 18:17:47 +01:00
Fuzzlemann af998585e9 Adds Maven repository to build.gradle 2020-11-01 18:17:04 +01:00
Fuzzlemann 62b18b0842 Version bump to 2.3 2020-11-01 18:16:46 +01:00
Steven Ferguson 8c0d98fda4 Update README.md 2020-10-30 14:34:47 -06:00
JasperJH 78d22e627c Fixes searchFieldByClass looping forever and incorrect cache keys 2020-09-11 12:12:16 +02:00
Fuzzlemann a6dc0865f7 Fixes some compatibility issues with 1.16.2 where in some cases NetworkManagerServer is passed 2020-08-24 22:16:43 +02:00
Fuzzlemann 7907d8f731 Merge branch 'master' of https://github.com/TCPShield/RealIP 2020-08-13 11:27:28 +02:00
Fuzzlemann 32142f9961 Fixes some exceptions regarding legacy connections on Velocity 1.7 clients 2020-08-13 11:27:03 +02:00
Paul Zhang f8d32f5e96 Update README.md 2020-08-11 15:11:46 +02:00
Fuzzlemann f8da84de07 Fixes an IllegalArgumentException because of a missing SocketAddress field on some BungeeCord versions (= 1.7, e.g. zBungeeCord) 2020-08-11 13:19:40 +02:00
Fuzzlemann cafc278c32 Fixes 1.7 support on some BungeeCord versions 2020-08-10 15:10:25 +02:00
Fuzzlemann e958718bcb Fixes issues with compatibility on 1.7 2020-07-30 19:42:49 +02:00
Fuzzlemann 031c2ed091 Change -64 to the constant EventPriority.LOWEST 2020-07-25 13:03:01 +02:00
Fuzzlemann faf7568c81 Add Velocity event orders (priorities) to ensure better compatibility 2020-07-15 17:17:48 +02:00
Fuzzlemann 812f5c353f Fixes error message when ProtocolLib's not installed 2020-07-15 17:14:43 +02:00
Fuzzlemann 6216b0c01e Bump version to 2.2 2020-07-11 23:09:54 +02:00
Fuzzlemann 901ce0cfeb Fix naming of the static final field 2020-07-11 23:09:12 +02:00
Fuzzlemann a537099297 Fixes TCPShield on servers using Java versions higher than or equal to 12 2020-07-11 23:08:01 +02:00
Fuzzlemann 7a14a45338 Removes the modified logger on Bungee 2020-07-09 23:45:14 +02:00
Fuzzlemann 8f35cf9532 Fixes forced hosts in the Velocity implementation when the player is e.g. a forge user 2020-07-06 21:02:37 +02:00
48 changed files with 516 additions and 434 deletions

No files matched your search

+3 -3
View File
@@ -6,12 +6,12 @@ It also parses passed IP addresses so the server is aware of the real player IP
### Compatibility
TCPShield is compatible with Paper, Spigot / CraftBukkit, BungeeCord and Velocity.
TCPShield is compatible with Spigot / CraftBukkit, BungeeCord and Velocity.
When using Spigot / CraftBukkit, [ProtocolLib](https://github.com/aadnk/ProtocolLib) needs to be installed. This is not necessary when Paper is being used.
When using Spigot / CraftBukkit, [ProtocolLib](https://github.com/aadnk/ProtocolLib) needs to be installed.
### Setup
Setting up the plugin is easy as cake. Please follow [these](https://docs.tcpshield.com/onboarding-1/tcpshield-plugin) guidelines.
Setting up the plugin is easy as pie. Please follow [these](https://docs.tcpshield.com/onboarding/tcpshield-plugin) guidelines.
### Compiling
In order to compile TCPShield, [install Gradle](https://docs.gradle.org/current/userguide/installation.html) and run the following command in the project folder:
+4 -1
View File
@@ -21,6 +21,9 @@ plugins {
}
repositories {
maven {
url = 'https://repo1.maven.org/maven2/'
}
maven {
url = 'https://hub.spigotmc.org/nexus/content/repositories/snapshots/'
}
@@ -65,7 +68,7 @@ configurations {
testImplementation.extendsFrom compileOnly
}
version = '2.1'
version = '2.4'
group = 'net.tcpshield.tcpshield'
archivesBaseName = 'TCPShield'
@@ -1,7 +0,0 @@
package net.tcpshield.tcpshield;
public class Constants {
public static long MAX_TIME_DIFFERENCE = 3; // in seconds
}
@@ -4,23 +4,22 @@ import net.tcpshield.tcpshield.abstraction.IPacket;
import net.tcpshield.tcpshield.abstraction.IPlayer;
import net.tcpshield.tcpshield.abstraction.TCPShieldConfig;
import net.tcpshield.tcpshield.exception.*;
import net.tcpshield.tcpshield.validation.TimestampValidation;
import net.tcpshield.tcpshield.validation.IPValidation;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.logging.Logger;
public class HandshakePacketHandler {
private final Logger logger;
private final SignatureVerifier signatureVerifier;
private final TimestampValidation timestampValidation;
private final IPValidation ipValidation;
private final TCPShieldConfig config;
public HandshakePacketHandler(Logger logger, TCPShieldConfig config) {
try {
this.logger = logger;
this.signatureVerifier = new SignatureVerifier();
this.timestampValidation = new TimestampValidation(config);
this.ipValidation = new IPValidation(config, logger);
this.config = config;
} catch (Exception e) {
throw new TCPShieldInitializationException(e);
@@ -31,6 +30,12 @@ public class HandshakePacketHandler {
String rawPayload = packet.getRawPayload();
try {
InetAddress inetAddress = InetAddress.getByName(player.getIP());
if (!ipValidation.validateIP(inetAddress)) throw new InvalidIPException();
if (rawPayload == null)
return; // raw payload is passed when it's not available -> e.g. Paper server list ping events
String extraData = null;
// fix for e.g. incoming FML tagged packets
@@ -44,41 +49,24 @@ public class HandshakePacketHandler {
}
String[] payload = cleanedPayload.split("///", 4);
if (payload.length != 4)
throw new MalformedPayloadException("payload.length != 4. Raw payload = \"" + rawPayload + "\"");
if (payload.length < 2)
throw new MalformedPayloadException("payload.length < 2. Raw payload = \"" + rawPayload + "\"");
String hostname = payload[0];
String ipData = payload[1];
int timestamp;
try {
timestamp = Integer.parseInt(payload[2]);
} catch (NumberFormatException e) {
throw new MalformedPayloadException(e);
}
String signature = payload[3];
if (!timestampValidation.checkTimestamp(timestamp))
throw new InvalidTimestampException(timestamp, timestampValidation.getTime());
String[] hostnameParts = ipData.split(":");
String host = hostnameParts[0];
int port = Integer.parseInt(hostnameParts[1]);
String reconstructedPayload = hostname + "///" + host + ":" + port + "///" + timestamp;
if (!signatureVerifier.verify(reconstructedPayload, signature))
throw new SigningVerificationFailureException();
InetSocketAddress newIP = new InetSocketAddress(host, port);
player.setIP(newIP);
if (extraData != null) hostname = hostname + extraData;
packet.modifyOriginalPacket(hostname);
} catch (InvalidTimestampException e) {
handleInvalidTimestamp(player, e.getTimestamp(), e.getCurrentTime());
} catch (SigningVerificationFailureException e) {
handleSigningVerificationFailure(player, rawPayload);
} catch (InvalidIPException e) {
handleInvalidIPException(player, rawPayload);
} catch (ConnectionNotProxiedException e) {
handleNotProxiedConnection(player, rawPayload);
} catch (IPModificationFailureException e) {
@@ -89,20 +77,9 @@ public class HandshakePacketHandler {
}
}
private void handleInvalidTimestamp(IPlayer player, long timestamp, long currentTime) {
private void handleInvalidIPException(IPlayer player, String rawPayload) {
if (config.isDebug()) {
this.logger.warning(String.format("%s[%s/%s] provided valid handshake information, but timestamp was not valid. " +
"Provided timestamp: %d vs. system timestamp: %d. Please check your machine time. Timestamp validation mode: %s", player.getName(), player.getUUID(), player.getIP(), timestamp, currentTime, config.getTimestampValidationMode()));
}
if (config.isOnlyProxy()) {
player.disconnect();
}
}
private void handleSigningVerificationFailure(IPlayer player, String rawPayload) {
if (config.isDebug()) {
this.logger.warning(String.format("%s[%s/%s] provided valid handshake information, but signing check failed. Raw payload = \"%s\"", player.getName(), player.getUUID(), player.getIP(), rawPayload));
this.logger.warning(String.format("%s[%s/%s] failed the ip check. Raw payload = \"%s\"", player.getName(), player.getUUID(), player.getIP(), rawPayload));
}
if (config.isOnlyProxy()) {
@@ -2,24 +2,48 @@ package net.tcpshield.tcpshield;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Table;
import net.tcpshield.tcpshield.exception.TCPShieldInitializationException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
public class ReflectionUtils {
private static final Table<Class<?>, String, Field> CACHED_FIELDS = HashBasedTable.create();
private static final Table<Class<?>, String, Field> CACHED_FIELDS_BY_NAME = HashBasedTable.create();
private static final Table<Class<?>, Class<?>, Field> CACHED_FIELDS_BY_CLASS = HashBasedTable.create();
private static final Field MODIFIERS_FIELD;
static {
Field tempModifiersField;
try {
tempModifiersField = getDeclaredField(Field.class, "modifiers");
} catch (NoSuchFieldException e) { // workaround for when searching for the modifiers field on Java 12 or higher
try {
Method getDeclaredFields0 = Class.class.getDeclaredMethod("getDeclaredFields0", boolean.class);
getDeclaredFields0.setAccessible(true);
Field[] fields = (Field[]) getDeclaredFields0.invoke(Field.class, false);
tempModifiersField = Arrays.stream(fields).filter(field -> field.getName().equals("modifiers")).findFirst().orElseThrow(() -> new TCPShieldInitializationException("Could not find the modifiers field"));
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e2) {
throw new TCPShieldInitializationException(e2);
}
}
MODIFIERS_FIELD = tempModifiersField;
MODIFIERS_FIELD.setAccessible(true);
}
public static void setFinalField(Object object, String fieldName, Object value) throws NoSuchFieldException, IllegalAccessException {
setFinalField(object, getPrivateField(object.getClass(), fieldName), value);
}
public static void setFinalField(Object object, Field field, Object value) throws IllegalAccessException, NoSuchFieldException {
public static void setFinalField(Object object, Field field, Object value) throws IllegalAccessException {
field.setAccessible(true);
if (Modifier.isFinal(field.getModifiers())) {
Field modifiersField = getDeclaredField(Field.class, "modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
MODIFIERS_FIELD.setInt(field, field.getModifiers() & ~Modifier.FINAL);
}
setField(object, field, value);
@@ -45,17 +69,26 @@ public class ReflectionUtils {
}
public static Field searchFieldByClass(Class<?> clazz, Class<?> searchFor) {
for (Field f : clazz.getDeclaredFields()) {
if (f.getType() == searchFor) {
return f;
Field cachedField = CACHED_FIELDS_BY_CLASS.get(clazz, searchFor);
if (cachedField != null) return cachedField;
Class<?> currentClass = clazz;
do {
for (Field field : currentClass.getDeclaredFields()) {
if (!searchFor.isAssignableFrom(field.getType())) continue;
CACHED_FIELDS_BY_CLASS.put(clazz, searchFor, field);
return field;
}
}
currentClass = currentClass.getSuperclass();
} while (currentClass != null);
throw new IllegalArgumentException("no " + searchFor.getName() + " field for clazz = " + clazz.getName() + " found");
}
public static Field getDeclaredField(Class<?> clazz, String fieldName) throws NoSuchFieldException {
Field cachedField = CACHED_FIELDS.get(clazz, fieldName);
Field cachedField = CACHED_FIELDS_BY_NAME.get(clazz, fieldName);
if (cachedField != null) return cachedField;
Field field;
@@ -70,7 +103,7 @@ public class ReflectionUtils {
}
}
CACHED_FIELDS.put(clazz, fieldName, field);
CACHED_FIELDS_BY_NAME.put(clazz, fieldName, field);
return field;
}
}
@@ -1,41 +0,0 @@
package net.tcpshield.tcpshield;
import com.google.common.io.ByteStreams;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
public class SignatureVerifier {
private final PublicKey publicKey;
public SignatureVerifier() throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
byte[] encodedKey = ByteStreams.toByteArray(SignatureVerifier.class.getResourceAsStream("/signing_pub.key"));
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(encodedKey);
KeyFactory keyFactory = KeyFactory.getInstance("EC");
publicKey = keyFactory.generatePublic(keySpec);
}
public boolean verify(String str, String signature) {
return verify(str.getBytes(StandardCharsets.UTF_8), signature);
}
private boolean verify(byte[] data, String signature) {
try {
byte[] decodedSignature = Base64.getDecoder().decode(signature);
Signature sig = Signature.getInstance("SHA512withECDSA");
sig.initVerify(publicKey);
sig.update(data);
return sig.verify(decodedSignature);
} catch (IllegalArgumentException | SignatureException | NoSuchAlgorithmException | InvalidKeyException e) { // catching for illegal base64 encoding or invalid encoding for signatures
return false;
}
}
}
@@ -1,17 +1,19 @@
package net.tcpshield.tcpshield.abstraction;
import java.io.File;
public abstract class TCPShieldConfig {
protected boolean onlyProxy;
protected String timestampValidationMode;
protected File ipWhitelistFolder;
protected boolean debug;
public boolean isOnlyProxy() {
return this.onlyProxy;
}
public String getTimestampValidationMode() {
return this.timestampValidationMode;
public File getIPWhitelistFolder() {
return ipWhitelistFolder;
}
public boolean isDebug() {
@@ -2,6 +2,7 @@ package net.tcpshield.tcpshield.bukkit;
import net.tcpshield.tcpshield.bukkit.paper.TCPShieldPaper;
import net.tcpshield.tcpshield.bukkit.protocollib.TCPShieldProtocolLib;
import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin;
public class TCPShieldBukkit extends JavaPlugin {
@@ -11,12 +12,17 @@ public class TCPShieldBukkit extends JavaPlugin {
if (isPaper()) {
new TCPShieldPaper(this).load();
} else {
if (Bukkit.getPluginManager().getPlugin("ProtocolLib") == null) {
this.getLogger().warning("TCPShield not loading because ProtocolLib is not installed. Either use Paper to enable native compatibility or install ProtocolLib.");
return;
}
new TCPShieldProtocolLib(this).load();
}
}
private boolean isPaper() {
if (true) return false; // disabled for now
if (1 == 1) return false;
try {
Class.forName("com.destroystokyo.paper.event.player.PlayerHandshakeEvent");
@@ -4,6 +4,8 @@ import net.tcpshield.tcpshield.abstraction.TCPShieldConfig;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.plugin.java.JavaPlugin;
import java.io.File;
public class BukkitConfigImpl extends TCPShieldConfig {
public BukkitConfigImpl(JavaPlugin javaPlugin) {
@@ -12,7 +14,7 @@ public class BukkitConfigImpl extends TCPShieldConfig {
FileConfiguration config = javaPlugin.getConfig();
this.onlyProxy = config.getBoolean("only-allow-proxy-connections");
this.timestampValidationMode = config.getString("timestamp-validation");
this.ipWhitelistFolder = new File(javaPlugin.getDataFolder(), "ip-whitelist");
this.debug = config.getBoolean("debug-mode");
}
}
@@ -8,6 +8,8 @@ import net.tcpshield.tcpshield.abstraction.IPlayer;
import net.tcpshield.tcpshield.bukkit.impl.BukkitConfigImpl;
import net.tcpshield.tcpshield.bukkit.paper.impl.PaperPacketImpl;
import net.tcpshield.tcpshield.bukkit.paper.impl.PaperPlayerImpl;
import net.tcpshield.tcpshield.bukkit.paper.impl.PaperServerListPacketImpl;
import net.tcpshield.tcpshield.bukkit.paper.impl.PaperServerListPingPlayerImpl;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
@@ -32,5 +34,10 @@ public class PaperHandshakePacketHandler implements Listener {
@EventHandler(priority = EventPriority.HIGHEST)
public void onServerPing(PaperServerListPingEvent e) {
if (e.getClient().isLegacy()) e.setCancelled(true);
IPacket packet = new PaperServerListPacketImpl(e);
IPlayer player = new PaperServerListPingPlayerImpl(e);
handshakePacketHandler.onHandshake(packet, player);
}
}
@@ -0,0 +1,23 @@
package net.tcpshield.tcpshield.bukkit.paper.impl;
import com.destroystokyo.paper.event.server.PaperServerListPingEvent;
import net.tcpshield.tcpshield.abstraction.IPacket;
public class PaperServerListPacketImpl implements IPacket {
private final PaperServerListPingEvent pingEvent;
public PaperServerListPacketImpl(PaperServerListPingEvent pingEvent) {
this.pingEvent = pingEvent;
}
@Override
public String getRawPayload() {
return null;
}
@Override
public void modifyOriginalPacket(String hostname) {
pingEvent.setCancelled(false);
}
}
@@ -0,0 +1,40 @@
package net.tcpshield.tcpshield.bukkit.paper.impl;
import com.destroystokyo.paper.event.server.PaperServerListPingEvent;
import net.tcpshield.tcpshield.abstraction.IPlayer;
import java.net.InetSocketAddress;
public class PaperServerListPingPlayerImpl implements IPlayer {
private final PaperServerListPingEvent pingEvent;
public PaperServerListPingPlayerImpl(PaperServerListPingEvent pingEvent) {
this.pingEvent = pingEvent;
}
@Override
public String getUUID() {
return "unknown";
}
@Override
public String getName() {
return "unknown";
}
@Override
public String getIP() {
return pingEvent.getAddress().getHostAddress();
}
@Override
public void setIP(InetSocketAddress ip) {
// not necessary since this class is only for server list pings
}
@Override
public void disconnect() {
pingEvent.setCancelled(true);
}
}
@@ -1,6 +1,5 @@
package net.tcpshield.tcpshield.bukkit.protocollib;
import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin;
public class TCPShieldProtocolLib {
@@ -12,11 +11,6 @@ public class TCPShieldProtocolLib {
}
public void load() {
if (Bukkit.getPluginManager().getPlugin("ProtocolLib") == null) {
plugin.getLogger().warning("TCPShield not loading because ProtocolLib is not installed. Either use Paper to enable native compatibility or install ProtocolLib.");
return;
}
ProtocolLibHandshakePacketHandler packetHandler = new ProtocolLibHandshakePacketHandler(plugin);
com.comphenix.protocol.ProtocolLibrary.getProtocolManager().addPacketListener(packetHandler);
}
@@ -2,10 +2,10 @@ package net.tcpshield.tcpshield.bukkit.protocollib.impl;
import com.comphenix.protocol.injector.server.SocketInjector;
import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
import io.netty.channel.AbstractChannel;
import net.tcpshield.tcpshield.ReflectionUtils;
import net.tcpshield.tcpshield.abstraction.IPlayer;
import net.tcpshield.tcpshield.exception.IPModificationFailureException;
import net.tcpshield.tcpshield.exception.TCPShieldInitializationException;
import org.bukkit.entity.Player;
import java.lang.reflect.Field;
@@ -15,9 +15,23 @@ import java.net.SocketAddress;
public class ProtocolLibPlayerImpl implements IPlayer {
private static Field socketAddressField;
private static Field remoteAddressField;
private static Class<?> abstractChannelClass;
private final Player player;
private String ip;
static {
try {
abstractChannelClass = Class.forName("io.netty.channel.AbstractChannel");
} catch (ClassNotFoundException e) {
try {
abstractChannelClass = Class.forName("net.minecraft.util.io.netty.channel.AbstractChannel");
} catch (ClassNotFoundException e2) {
throw new TCPShieldInitializationException(e2);
}
}
}
public ProtocolLibPlayerImpl(Player player) {
this.player = player;
this.ip = player.getAddress().getAddress().getHostAddress();
@@ -46,14 +60,11 @@ public class ProtocolLibPlayerImpl implements IPlayer {
SocketInjector ignored = TemporaryPlayerFactory.getInjectorFromPlayer(player);
Object injector = ReflectionUtils.getObjectInPrivateField(ignored, "injector");
Object networkManager = ReflectionUtils.getObjectInPrivateField(injector, "networkManager");
if (socketAddressField == null) {
socketAddressField = ReflectionUtils.searchFieldByClass(networkManager.getClass(), SocketAddress.class);
}
ReflectionUtils.setFinalField(networkManager, socketAddressField, ip);
ReflectionUtils.setFinalField(networkManager, ReflectionUtils.searchFieldByClass(networkManager.getClass(), SocketAddress.class), ip);
Object channel = ReflectionUtils.getObjectInPrivateField(injector, "originalChannel");
ReflectionUtils.setFinalField(channel, ReflectionUtils.getDeclaredField(AbstractChannel.class, "remoteAddress"), ip);
ReflectionUtils.setFinalField(channel, ReflectionUtils.getDeclaredField(abstractChannelClass, "remoteAddress"), ip);
} catch (Exception e) {
throw new IPModificationFailureException(e);
}
@@ -6,6 +6,7 @@ import net.md_5.bungee.api.event.ProxyPingEvent;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.event.EventHandler;
import net.md_5.bungee.event.EventPriority;
import net.tcpshield.tcpshield.HandshakePacketHandler;
import net.tcpshield.tcpshield.abstraction.IPacket;
import net.tcpshield.tcpshield.abstraction.IPlayer;
@@ -21,7 +22,7 @@ public class BungeeHandshakePacketHandler implements Listener {
this.handshakePacketHandler = new HandshakePacketHandler(plugin.getLogger(), new BungeeConfigImpl(plugin));
}
@EventHandler(priority = -64)
@EventHandler(priority = EventPriority.LOWEST)
public void onProxyPingEvent(ProxyPingEvent e) {
PendingConnection connection = e.getConnection();
if (!connection.isLegacy()) return;
@@ -29,7 +30,7 @@ public class BungeeHandshakePacketHandler implements Listener {
connection.disconnect();
}
@EventHandler(priority = -64)
@EventHandler(priority = EventPriority.LOWEST)
public void onPlayerHandshake(PlayerHandshakeEvent e) {
IPacket packet = new BungeePacketImpl(e.getHandshake(), e.getConnection());
IPlayer player = new BungeePlayerImpl(e.getConnection());
@@ -1,13 +1,8 @@
package net.tcpshield.tcpshield.bungee;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.api.plugin.PluginManager;
import net.tcpshield.tcpshield.ReflectionUtils;
import java.util.logging.Level;
import java.util.logging.Logger;
public class TCPShieldBungee extends Plugin implements Listener {
@@ -15,22 +10,5 @@ public class TCPShieldBungee extends Plugin implements Listener {
public void onEnable() {
PluginManager pluginManager = this.getProxy().getPluginManager();
pluginManager.registerListener(this, new BungeeHandshakePacketHandler(this));
Logger logger = ProxyServer.getInstance().getLogger();
Logger newLogger = new Logger("BungeeCord", null) {
public void log(Level level, String msg, Object param1) {
if ((msg.equals("{0} has connected")) && (param1.getClass().getSimpleName().equals("InitialHandler"))) {
return;
}
super.log(level, msg, param1);
}
};
newLogger.setParent(logger);
try {
ReflectionUtils.setField(ProxyServer.getInstance(), "logger", newLogger);
} catch (Exception e) {
e.printStackTrace();
}
}
}
@@ -22,7 +22,7 @@ public class BungeeConfigImpl extends TCPShieldConfig {
Configuration config = loadConfig();
this.onlyProxy = config.getBoolean("only-allow-proxy-connections");
this.timestampValidationMode = config.getString("timestamp-validation");
this.ipWhitelistFolder = new File(plugin.getDataFolder(), "ip-whitelist");
this.debug = config.getBoolean("debug-mode");
}
@@ -6,7 +6,9 @@ import net.tcpshield.tcpshield.ReflectionUtils;
import net.tcpshield.tcpshield.abstraction.IPlayer;
import net.tcpshield.tcpshield.exception.IPModificationFailureException;
import java.lang.reflect.Field;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.UUID;
public class BungeePlayerImpl implements IPlayer {
@@ -45,7 +47,13 @@ public class BungeePlayerImpl implements IPlayer {
Object channelWrapper = ReflectionUtils.getObjectInPrivateField(pendingConnection, "ch");
Object channel = ReflectionUtils.getObjectInPrivateField(channelWrapper, "ch");
ReflectionUtils.setFinalField(channelWrapper, "remoteAddress", ip);
try {
Field socketAddressField = ReflectionUtils.searchFieldByClass(channelWrapper.getClass(), SocketAddress.class);
ReflectionUtils.setFinalField(channelWrapper, socketAddressField, ip);
} catch (IllegalArgumentException ignored) {
// Some BungeeCord versions, notably those on 1.7 (e.g. zBungeeCord) don't have an SocketAddress field in the ChannelWrapper class
}
ReflectionUtils.setFinalField(channel, ReflectionUtils.getPrivateField(AbstractChannel.class, "remoteAddress"), ip);
ReflectionUtils.setFinalField(channel, ReflectionUtils.getPrivateField(AbstractChannel.class, "localAddress"), ip);
} catch (Exception e) {
@@ -0,0 +1,4 @@
package net.tcpshield.tcpshield.exception;
public class InvalidIPException extends ConnectionNotProxiedException {
}
@@ -1,20 +0,0 @@
package net.tcpshield.tcpshield.exception;
public class InvalidTimestampException extends SigningVerificationFailureException {
private final long timestamp;
private final long currentTime;
public InvalidTimestampException(long timestamp, long currentTime) {
this.timestamp = timestamp;
this.currentTime = currentTime;
}
public long getTimestamp() {
return timestamp;
}
public long getCurrentTime() {
return currentTime;
}
}
@@ -1,8 +0,0 @@
package net.tcpshield.tcpshield.exception;
public class SigningVerificationFailureException extends MalformedPayloadException {
public SigningVerificationFailureException() {
super();
}
}
@@ -0,0 +1,9 @@
package net.tcpshield.tcpshield.exception;
public class TCPShieldIPListUpdaterException extends RuntimeException {
public TCPShieldIPListUpdaterException(Throwable cause) {
super(cause);
}
}
@@ -5,4 +5,8 @@ public class TCPShieldInitializationException extends RuntimeException {
public TCPShieldInitializationException(Throwable cause) {
super(cause);
}
public TCPShieldInitializationException(String message) {
super(message);
}
}
@@ -0,0 +1,24 @@
package net.tcpshield.tcpshield.validation;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;
public class IPListAutoUpdater {
private final IPListUpdater ipListUpdater;
public IPListAutoUpdater(IPListUpdater ipListUpdater) {
this.ipListUpdater = ipListUpdater;
}
public void init() {
new Timer().scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
ipListUpdater.update();
}
}, TimeUnit.HOURS.toMillis(12), TimeUnit.HOURS.toMillis(12));
}
}
@@ -0,0 +1,61 @@
package net.tcpshield.tcpshield.validation;
import net.tcpshield.tcpshield.abstraction.TCPShieldConfig;
import net.tcpshield.tcpshield.exception.TCPShieldIPListUpdaterException;
import javax.net.ssl.HttpsURLConnection;
import java.io.*;
import java.net.URL;
import java.util.logging.Logger;
import java.util.stream.Collectors;
public class IPListUpdater {
private final TCPShieldConfig config;
private final IPValidation ipValidation;
private final Logger logger;
public IPListUpdater(TCPShieldConfig config, IPValidation ipValidation, Logger logger) {
this.config = config;
this.ipValidation = ipValidation;
this.logger = logger;
}
public void update() {
try {
String ipList = getIPList();
File ipWhitelistFolder = config.getIPWhitelistFolder();
File tcpShieldIPList = new File(ipWhitelistFolder, "tcpshield-ips.list");
if (tcpShieldIPList.createNewFile()) {
logger.info("[TCPShield] Fetched updated IP Whitelist from https://tcpshield.com/v4/");
}
try (FileWriter writer = new FileWriter(tcpShieldIPList, false)) {
writer.write(ipList);
}
ipValidation.readIPWhitelist();
} catch (Exception e) {
throw new TCPShieldIPListUpdaterException(e);
}
}
public String getIPList() throws IOException {
URL url = new URL("https://tcpshield.com/v4/");
HttpsURLConnection connection = null;
try {
connection = (HttpsURLConnection) url.openConnection();
try (InputStream inputStream = connection.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream))) {
return bufferedReader.lines().filter(str -> !str.isEmpty()).collect(Collectors.joining("\n"));
}
} finally {
if (connection != null)
connection.disconnect();
}
}
}
@@ -0,0 +1,69 @@
package net.tcpshield.tcpshield.validation;
import net.tcpshield.tcpshield.abstraction.TCPShieldConfig;
import net.tcpshield.tcpshield.exception.TCPShieldInitializationException;
import net.tcpshield.tcpshield.validation.cidr.CIDRChecker;
import java.io.File;
import java.io.IOException;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Scanner;
import java.util.logging.Logger;
public class IPValidation {
private final TCPShieldConfig config;
private final IPListUpdater ipListUpdater;
private final CIDRChecker cidrChecker;
private List<String> ipWhitelist = new ArrayList<>();
public IPValidation(TCPShieldConfig config, Logger logger) {
this.config = config;
this.ipListUpdater = new IPListUpdater(config, this, logger);
new IPListAutoUpdater(ipListUpdater).init();
checkIPList();
try {
readIPWhitelist();
this.cidrChecker = new CIDRChecker(ipWhitelist);
} catch (IOException e) {
throw new TCPShieldInitializationException(e);
}
}
public boolean validateIP(InetAddress inetAddress) {
return cidrChecker.check(inetAddress);
}
private List<String> getIPWhitelist() throws IOException {
List<String> ipWhitelist = new ArrayList<>();
for (File file : Objects.requireNonNull(config.getIPWhitelistFolder().listFiles())) {
if (file.isDirectory()) continue;
try (Scanner scanner = new Scanner(file)) {
while (scanner.hasNextLine()) {
String cidrEntry = scanner.nextLine();
ipWhitelist.add(cidrEntry);
}
}
}
return ipWhitelist;
}
public void readIPWhitelist() throws IOException {
this.ipWhitelist = getIPWhitelist();
}
private void checkIPList() {
if (config.getIPWhitelistFolder().mkdirs()) {
ipListUpdater.update();
}
}
}
@@ -1,11 +0,0 @@
package net.tcpshield.tcpshield.validation;
public interface ITimestampValidator {
boolean validateTimestamp(long checkedTimestamp);
default long getTime() {
return System.currentTimeMillis() / 1000;
}
}
@@ -1,34 +0,0 @@
package net.tcpshield.tcpshield.validation;
import net.tcpshield.tcpshield.abstraction.TCPShieldConfig;
import net.tcpshield.tcpshield.validation.impl.DisabledTimestampValidatorImpl;
import net.tcpshield.tcpshield.validation.impl.HTPDateTimestampValidatorImpl;
import net.tcpshield.tcpshield.validation.impl.SystemTimeTimestampValidatorImpl;
public class TimestampValidation {
private final ITimestampValidator timestampValidator;
public TimestampValidation(TCPShieldConfig config) {
switch (config.getTimestampValidationMode().toLowerCase()) {
case "system":
this.timestampValidator = new SystemTimeTimestampValidatorImpl();
break;
case "off":
this.timestampValidator = new DisabledTimestampValidatorImpl();
break;
case "htpdate":
default:
this.timestampValidator = new HTPDateTimestampValidatorImpl();
}
}
public boolean checkTimestamp(long timestamp) {
return timestampValidator.validateTimestamp(timestamp);
}
public long getTime() {
return timestampValidator.getTime();
}
}
@@ -0,0 +1,38 @@
package net.tcpshield.tcpshield.validation.cidr;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class CIDRChecker {
private final List<CIDRMatcher> cidrMatchers = new ArrayList<>();
private final Set<String> cache = new HashSet<>(); // quick cache for already verified IPs; HashSet is O(1) on HashSet#contains() operations
public CIDRChecker(List<String> cidrList) {
for (String cidr : cidrList) {
CIDRMatcher cidrMatcher = new CIDRMatcher(cidr);
cidrMatchers.add(cidrMatcher);
}
}
public boolean check(InetAddress inetAddress) {
String ip = inetAddress.getHostAddress();
if (cache.contains(ip)) return true;
if (!checkUncached(inetAddress)) return false;
cache.add(ip);
return true;
}
private boolean checkUncached(InetAddress inetAddress) {
for (CIDRMatcher cidrMatcher : cidrMatchers) {
if (cidrMatcher.matches(inetAddress)) return true;
}
return false;
}
}
@@ -0,0 +1,59 @@
package net.tcpshield.tcpshield.validation.cidr;
import net.tcpshield.tcpshield.exception.TCPShieldInitializationException;
import java.net.InetAddress;
import java.net.UnknownHostException;
public class CIDRMatcher {
private final int maskBits;
private final int maskBytes;
private final boolean simpleCIDR;
private final InetAddress cidrAddress;
public CIDRMatcher(String ipAddress) {
String[] split = ipAddress.split("/");
String parsedIPAddress;
if (split.length != 0) {
parsedIPAddress = split[0];
this.maskBits = Integer.parseInt(split[1]);
this.simpleCIDR = maskBits == 32;
} else {
parsedIPAddress = ipAddress;
this.maskBits = -1;
this.simpleCIDR = true;
}
this.maskBytes = simpleCIDR ? -1 : maskBits / 8;
try {
cidrAddress = InetAddress.getByName(parsedIPAddress);
} catch (UnknownHostException e) {
throw new TCPShieldInitializationException(e);
}
}
public boolean matches(InetAddress inetAddress) {
if (!cidrAddress.getClass().equals(inetAddress.getClass())) return false; // check if IP is IPv4 or IPv6
if (simpleCIDR) return inetAddress.equals(cidrAddress); // check for equality if it's a simple CIDR
byte[] inetAddressBytes = inetAddress.getAddress();
byte[] requiredAddressBytes = cidrAddress.getAddress();
byte finalByte = (byte) (0xFF00 >> (maskBits & 0x07));
for (int i = 0; i < maskBytes; i++) {
if (inetAddressBytes[i] != requiredAddressBytes[i]) return false;
}
if (finalByte != 0)
return (inetAddressBytes[maskBytes] & finalByte) == (requiredAddressBytes[maskBytes] & finalByte);
return true;
}
}
@@ -1,13 +0,0 @@
package net.tcpshield.tcpshield.validation.impl;
import net.tcpshield.tcpshield.validation.ITimestampValidator;
/**
* @author Fuzzlemann
*/
public class DisabledTimestampValidatorImpl implements ITimestampValidator {
@Override
public boolean validateTimestamp(long checkedTimestamp) {
return true;
}
}
@@ -1,81 +0,0 @@
package net.tcpshield.tcpshield.validation.impl;
import net.tcpshield.tcpshield.Constants;
import net.tcpshield.tcpshield.validation.ITimestampValidator;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.ForkJoinPool;
import java.util.stream.Collectors;
/**
* inspired by https://github.com/iridium77/htpdate/blob/master/htpdate.c
*/
public class HTPDateTimestampValidatorImpl implements ITimestampValidator {
private volatile long timestampOffset = 0;
public HTPDateTimestampValidatorImpl() {
ForkJoinPool.commonPool().submit(() -> {
try {
syncTimestamp();
} catch (Exception e) {
e.printStackTrace();
}
}); // do not execute network requests on the main thread
}
@Override
public boolean validateTimestamp(long checkedTimestamp) {
long currentTime = getTime();
return checkedTimestamp >= (currentTime - Constants.MAX_TIME_DIFFERENCE) && checkedTimestamp <= (currentTime + Constants.MAX_TIME_DIFFERENCE);
}
private void syncTimestamp() throws IOException {
try (Socket socket = new Socket("google.com", 80)) {
// no-cache forces the server to return time
String payload = "HEAD http://google.com/ HTTP/1.1\r\nHost: google.com\r\nUser-Agent: tcpshield/1.0\r\nPragma: no-cache\r\nCache-Control: no-cache\r\nConnection: close\r\n\r\n";
socket.getOutputStream().write(payload.getBytes());
long readTime = System.currentTimeMillis(); // assuming server -> client time is negligible
List<String> response = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8))
.lines()
.collect(Collectors.toList());
Date serverDate = extractDate(response);
timestampOffset = Math.round((serverDate.getTime() - readTime) / 1000.0) * 1000; // the HTTP protocol only returns time in seconds; round offset
}
}
private Date extractDate(List<String> response) {
for (String line : response) {
if (!line.startsWith("Date: ")) continue;
String dateString = line.substring("Date: ".length());
SimpleDateFormat sdf = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.ENGLISH);
try {
return sdf.parse(dateString);
} catch (ParseException e) {
throw new IllegalStateException(e);
}
}
throw new IllegalArgumentException("no date line found - response: " + response);
}
@Override
public long getTime() {
return (System.currentTimeMillis() + timestampOffset) / 1000;
}
}
@@ -1,14 +0,0 @@
package net.tcpshield.tcpshield.validation.impl;
import net.tcpshield.tcpshield.Constants;
import net.tcpshield.tcpshield.validation.ITimestampValidator;
public class SystemTimeTimestampValidatorImpl implements ITimestampValidator {
@Override
public boolean validateTimestamp(long checkedTimestamp) {
long currentTime = getTime();
return checkedTimestamp >= (currentTime - Constants.MAX_TIME_DIFFERENCE) && checkedTimestamp <= (currentTime + Constants.MAX_TIME_DIFFERENCE);
}
}
@@ -1,5 +1,6 @@
package net.tcpshield.tcpshield.velocity;
import com.velocitypowered.api.event.PostOrder;
import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.connection.ConnectionHandshakeEvent;
import com.velocitypowered.api.event.proxy.ProxyPingEvent;
@@ -21,13 +22,13 @@ public class VelocityHandshakePacketHandler {
this.handshakePacketHandler = new HandshakePacketHandler(logger, new VelocityConfigImpl(dataFolder));
}
@Subscribe
@Subscribe(order = PostOrder.FIRST)
public void onHandshake(ConnectionHandshakeEvent e) {
InboundConnection connection = e.getConnection();
handleEvent(connection);
}
@Subscribe
@Subscribe(order = PostOrder.FIRST)
public void onProxyPing(ProxyPingEvent e) {
InboundConnection connection = e.getConnection();
handleEvent(connection);
@@ -20,7 +20,7 @@ public class VelocityConfigImpl extends TCPShieldConfig {
saveDefaultConfig();
Toml toml = loadConfig();
this.onlyProxy = toml.getBoolean("only-allow-proxy-connections");
this.timestampValidationMode = toml.getString("timestamp-validation");
this.ipWhitelistFolder = new File(dataFolder, "ip-whitelist");
this.debug = toml.getBoolean("debug-mode");
}
@@ -44,9 +44,29 @@ public class VelocityPacketImpl implements IPacket {
@Override
public void modifyOriginalPacket(String hostname) throws Exception {
ReflectionUtils.setFinalField(inboundConnection, CLEANED_ADDRESS_FIELD, hostname);
ReflectionUtils.setFinalField(inboundConnection, CLEANED_ADDRESS_FIELD, cleanAddress(hostname));
Object handshake = HANDSHAKE_FIELD.get(inboundConnection);
HOSTNAME_FIELD.set(handshake, hostname);
}
/**
* Adapted from https://github.com/VelocityPowered/Velocity/blob/17e6944daea8130e03903ccdfbf63f111c573849/proxy/src/main/java/com/velocitypowered/proxy/connection/client/HandshakeSessionHandler.java
*/
private String cleanAddress(String hostname) {
// Clean out any anything after any zero bytes (this includes BungeeCord forwarding and the
// legacy Forge handshake indicator).
String cleaned = hostname;
int zeroIdx = cleaned.indexOf('\0');
if (zeroIdx > -1) {
cleaned = hostname.substring(0, zeroIdx);
}
// If we connect through an SRV record, there will be a period at the end (DNS usually elides
// this ending octet).
if (!cleaned.isEmpty() && cleaned.charAt(cleaned.length() - 1) == '.') {
cleaned = cleaned.substring(0, cleaned.length() - 1);
}
return cleaned;
}
}
@@ -12,6 +12,7 @@ import java.net.InetSocketAddress;
public class VelocityPlayerImpl implements IPlayer {
private static final Class<?> INITIAL_INBOUND_CONNECTION_CLASS;
private static final Field MINECRAFT_CONNECTION_FIELD;
private static final Field LEGACY_MINECRAFT_CONNECTION_FIELD;
private static final Field REMOTE_ADDRESS_FIELD;
@@ -19,7 +20,8 @@ public class VelocityPlayerImpl implements IPlayer {
static {
try {
MINECRAFT_CONNECTION_FIELD = ReflectionUtils.getPrivateField(Class.forName("com.velocitypowered.proxy.connection.client.InitialInboundConnection"), "connection");
INITIAL_INBOUND_CONNECTION_CLASS = Class.forName("com.velocitypowered.proxy.connection.client.InitialInboundConnection");
MINECRAFT_CONNECTION_FIELD = ReflectionUtils.getPrivateField(INITIAL_INBOUND_CONNECTION_CLASS, "connection");
LEGACY_MINECRAFT_CONNECTION_FIELD = ReflectionUtils.getPrivateField(Class.forName("com.velocitypowered.proxy.connection.client.HandshakeSessionHandler$LegacyInboundConnection"), "connection");
Class<?> minecraftConnection = Class.forName("com.velocitypowered.proxy.connection.MinecraftConnection");
@@ -36,7 +38,7 @@ public class VelocityPlayerImpl implements IPlayer {
public VelocityPlayerImpl(InboundConnection inboundConnection) {
this.inboundConnection = inboundConnection;
this.legacy = inboundConnection.getProtocolVersion().isUnknown() || inboundConnection.getProtocolVersion().isLegacy();
this.legacy = inboundConnection.getClass() != INITIAL_INBOUND_CONNECTION_CLASS;
this.ip = inboundConnection.getRemoteAddress().getAddress().getHostAddress();
}
+1 -1
View File
@@ -1,4 +1,4 @@
name: TCPShield
version: '2.1'
version: '2.4'
main: net.tcpshield.tcpshield.bungee.TCPShieldBungee
author: https://tcpshield.com
-3
View File
@@ -1,8 +1,5 @@
# Please keep this as true in while using TCPShield!
only-allow-proxy-connections = true
# available modes: system (uses the system time), htpdate (uses a synchronized date) & off (deactivates timestamp validation)
timestamp-validation = 'htpdate'
# Turn on to diagnose connection issues
debug-mode = false
-3
View File
@@ -1,8 +1,5 @@
# Please keep this as true in while using TCPShield!
only-allow-proxy-connections: true
# available modes: system (uses the system time), htpdate (uses a synchronized date) & off (deactivates timestamp validation)
timestamp-validation: 'htpdate'
# Turn on to diagnose connection issues
debug-mode: false
+1 -1
View File
@@ -1,5 +1,5 @@
name: TCPShield
version: '2.1'
version: '2.4'
main: net.tcpshield.tcpshield.bukkit.TCPShieldBukkit
softdepend:
- ProtocolLib
Binary file not shown.
+1 -1
View File
@@ -1,7 +1,7 @@
{
"id": "tcpshield",
"name": "TCPShield",
"version": "2.0",
"version": "2.4",
"description": "TCPShield IP parsing capabilities for Velocity",
"authors": [
"TCPShield"
@@ -9,27 +9,25 @@ import org.junit.jupiter.api.Test;
import java.util.logging.Logger;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
public class HandshakePacketHandlerTest {
private final TCPShieldConfig defaultTestConfig = new TestConfigImpl("system");
private final TCPShieldConfig skippingTimestampCheckTestConfig = new TestConfigImpl("off");
private final TCPShieldConfig defaultTestConfig = new TestConfigImpl();
private final HandshakePacketHandler standardHandshakePacketHandler = new HandshakePacketHandler(Logger.getLogger("TCPShield"), defaultTestConfig);
private final HandshakePacketHandler skippingTimestampCheckHandshakePacketHandler = new HandshakePacketHandler(Logger.getLogger("TCPShield"), skippingTimestampCheckTestConfig);
private TestPlayerImpl player;
@BeforeEach
public void setUp() {
player = new TestPlayerImpl();
player = new TestPlayerImpl(TestConstants.VALID_IP);
}
@Test
public void testSuccessfulHandshake() {
TestPacketImpl packet = new TestPacketImpl(TestConstants.VALID_PAYLOAD_INVALID_TIMESTAMP, TestConstants.STANDARD_HOSTNAME);
TestPacketImpl packet = new TestPacketImpl(TestConstants.VALID_PAYLOAD, TestConstants.STANDARD_HOSTNAME);
skippingTimestampCheckHandshakePacketHandler.onHandshake(packet, player);
standardHandshakePacketHandler.onHandshake(packet, player);
assertEquals(TestConstants.PAYLOAD_HOSTNAME, packet.getHostname());
assertEquals(TestConstants.PAYLOAD_IP, player.getIP());
@@ -39,15 +37,15 @@ public class HandshakePacketHandlerTest {
public void testSuccessfulFMLTaggedHandshake() {
TestPacketImpl packet = new TestPacketImpl(TestConstants.VALID_PAYLOAD_INVALID_TIMESTAMP_FML_TAGGED, TestConstants.STANDARD_HOSTNAME);
skippingTimestampCheckHandshakePacketHandler.onHandshake(packet, player);
standardHandshakePacketHandler.onHandshake(packet, player);
assertEquals(TestConstants.PAYLOAD_FML_HOSTNAME, packet.getHostname());
assertEquals(TestConstants.PAYLOAD_IP, player.getIP());
}
@Test
public void testUnsuccessfulHandshakeFailedTimestampCheck() {
TestPacketImpl packet = new TestPacketImpl(TestConstants.VALID_PAYLOAD_INVALID_TIMESTAMP, TestConstants.STANDARD_HOSTNAME);
public void testUnsuccessfulHandshakeMalformedPayload() {
TestPacketImpl packet = new TestPacketImpl(TestConstants.INVALID_PAYLOAD_MALFORMED_PAYLOAD, TestConstants.STANDARD_HOSTNAME);
standardHandshakePacketHandler.onHandshake(packet, player);
@@ -55,19 +53,11 @@ public class HandshakePacketHandlerTest {
}
@Test
public void testUnsuccessfulHandshakeMalformedPayload() {
TestPacketImpl packet = new TestPacketImpl(TestConstants.INVALID_PAYLOAD_MALFORMED_PAYLOAD, TestConstants.STANDARD_HOSTNAME);
public void testUnsuccessfulHandshakeInvalidIP() {
TestPacketImpl packet = new TestPacketImpl(TestConstants.VALID_PAYLOAD, TestConstants.STANDARD_HOSTNAME);
skippingTimestampCheckHandshakePacketHandler.onHandshake(packet, player);
assertFalse(player.isConnected());
}
@Test
public void testUnsuccessfulHandshakeInvalidSignature() {
TestPacketImpl packet = new TestPacketImpl(TestConstants.INVALID_PAYLOAD_INVALID_SIGNATURE, TestConstants.STANDARD_HOSTNAME);
skippingTimestampCheckHandshakePacketHandler.onHandshake(packet, player);
TestPlayerImpl player = new TestPlayerImpl(TestConstants.INVALID_IP);
standardHandshakePacketHandler.onHandshake(packet, player);
assertFalse(player.isConnected());
}
@@ -1,60 +0,0 @@
package net.tcpshield.tcpshield;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class SignatureObjectVerifierTest {
private static SignatureVerifier signatureVerifier;
@BeforeAll
public static void setUp() throws NoSuchAlgorithmException, IOException, InvalidKeySpecException {
signatureVerifier = new SignatureVerifier();
}
@Test
public void testVerificationSuccess() {
SignatureObject signatureObject = createSignature(TestConstants.VALID_PAYLOAD_INVALID_TIMESTAMP);
assertTrue(signatureVerifier.verify(signatureObject.getReconstructedPayload(), signatureObject.getSignature()));
}
@Test
public void testVerificationFailure() {
SignatureObject signatureObject = createSignature(TestConstants.INVALID_PAYLOAD_INVALID_SIGNATURE);
assertFalse(signatureVerifier.verify(signatureObject.getReconstructedPayload(), signatureObject.getSignature()));
}
private SignatureObject createSignature(String payload) {
int signatureIndex = payload.lastIndexOf("///");
String signature = payload.substring(signatureIndex + 3);
String reconstructedPayload = payload.substring(0, signatureIndex);
return new SignatureObject(signature, reconstructedPayload);
}
private static class SignatureObject {
private final String signature;
private final String reconstructedPayload;
private SignatureObject(String signature, String reconstructedPayload) {
this.signature = signature;
this.reconstructedPayload = reconstructedPayload;
}
public String getSignature() {
return signature;
}
public String getReconstructedPayload() {
return reconstructedPayload;
}
}
}
@@ -4,13 +4,15 @@ public class TestConstants {
public static final String STANDARD_HOSTNAME = "example.org";
public static final String VALID_IP = "51.161.19.224";
public static final String INVALID_IP = "1.1.1.1";
public static final String PAYLOAD_HOSTNAME = "homeserver.fuzzlemann.de";
public static final String PAYLOAD_FML_HOSTNAME = "homeserver.fuzzlemann.de\0FML\0";
public static final String PAYLOAD_IP = "79.227.33.179";
public static final String VALID_PAYLOAD_INVALID_TIMESTAMP = "homeserver.fuzzlemann.de///79.227.33.179:53462///1589573485///MGQCMHFac1If9Y8vGAkZMTVapmC6l4M7EfD0YJ0b3pQy73IFjEPsOQn+yxp9lAKSZz7X3AIwGJ2rSxGu4JvlW5sm0pyzqUrc6mfd0xHUWC9JkusrSBSKDfiFV7TCmSZc0gGivgTs";
public static final String VALID_PAYLOAD_INVALID_TIMESTAMP_FML_TAGGED = VALID_PAYLOAD_INVALID_TIMESTAMP + "\0FML\0";
public static final String INVALID_PAYLOAD_INVALID_SIGNATURE = "homeserver.fuzzlemann.de///79.227.33.179:53462///1589573485///MGQCMHFac1If9Y8vGAkZMTVapmC6l4M7EfD0YJ0b3pQy73IFjEPsOQn+yxp9lAKSZz7X3AIwGJ2rSxGu4JvlW5sm0pyzqUrc6mfd0xHUWC9JkusrSBSKDfiFV7TCmSZc0gGivgTsEXCEPTION";
public static final String INVALID_PAYLOAD_MALFORMED_PAYLOAD = "malformed_payload";
public static final String VALID_PAYLOAD = "homeserver.fuzzlemann.de///79.227.33.179:53462///1589573485///MGQCMHFac1If9Y8vGAkZMTVapmC6l4M7EfD0YJ0b3pQy73IFjEPsOQn+yxp9lAKSZz7X3AIwGJ2rSxGu4JvlW5sm0pyzqUrc6mfd0xHUWC9JkusrSBSKDfiFV7TCmSZc0gGivgTs";
public static final String VALID_PAYLOAD_INVALID_TIMESTAMP_FML_TAGGED = VALID_PAYLOAD + "\0FML\0";
public static final String INVALID_PAYLOAD_MALFORMED_PAYLOAD = "MALFORMED_PAYLOAD";
}
@@ -2,10 +2,12 @@ package net.tcpshield.tcpshield.impl;
import net.tcpshield.tcpshield.abstraction.TCPShieldConfig;
import java.io.File;
public class TestConfigImpl extends TCPShieldConfig {
public TestConfigImpl(String timestampValidationMode) {
this.timestampValidationMode = timestampValidationMode;
public TestConfigImpl() {
this.ipWhitelistFolder = new File("ip-whitelist");
this.debug = true;
this.onlyProxy = true;
}
@@ -2,15 +2,22 @@ package net.tcpshield.tcpshield.impl;
import net.tcpshield.tcpshield.abstraction.IPlayer;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
public class TestPlayerImpl implements IPlayer {
private InetSocketAddress ip;
private InetAddress ip;
private boolean connected;
public TestPlayerImpl() {
public TestPlayerImpl(String ip) {
this.connected = true;
try {
this.ip = InetAddress.getByName(ip);
} catch (UnknownHostException e) {
e.printStackTrace();
}
}
@Override
@@ -27,12 +34,12 @@ public class TestPlayerImpl implements IPlayer {
public String getIP() {
if (ip == null) return "ip";
return ip.getAddress().getHostAddress();
return ip.getHostAddress();
}
@Override
public void setIP(InetSocketAddress ip) {
this.ip = ip;
this.ip = ip.getAddress();
}
@Override