mirror of
https://github.com/GeyserMC/Floodgate.git
synced 2025-12-19 14:59:20 +00:00
Added a timestamp to prevent reusing the encrypted Floodgate data
This commit is contained in:
@@ -83,7 +83,6 @@ public class BungeeDataAddon implements InjectorAddon {
|
||||
public void onChannelClosed(Channel channel) {
|
||||
FloodgatePlayer player = channel.attr(playerAttribute).get();
|
||||
if (player != null && api.removePlayer(player)) {
|
||||
api.removeEncryptedData(player.getCorrectUniqueId());
|
||||
logger.translatedInfo("floodgate.ingame.disconnect_name", player.getCorrectUsername());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ import org.geysermc.floodgate.api.player.PropertyKey;
|
||||
import org.geysermc.floodgate.config.ProxyFloodgateConfig;
|
||||
import org.geysermc.floodgate.player.FloodgateHandshakeHandler;
|
||||
import org.geysermc.floodgate.player.FloodgateHandshakeHandler.HandshakeResult;
|
||||
import org.geysermc.floodgate.player.FloodgateHandshakeHandler.ResultType;
|
||||
import org.geysermc.floodgate.util.Constants;
|
||||
import org.geysermc.floodgate.util.ReflectionUtils;
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
@@ -115,14 +115,20 @@ public class BungeeProxyDataHandler extends ChannelInboundHandlerAdapter {
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.getResultType() == ResultType.EXCEPTION) {
|
||||
ctx.channel().attr(kickMessageAttribute).set(
|
||||
config.getDisconnect().getInvalidKey());
|
||||
}
|
||||
|
||||
if (result.getResultType() == ResultType.INVALID_DATA_LENGTH) {
|
||||
ctx.channel().attr(kickMessageAttribute)
|
||||
.set(config.getDisconnect().getInvalidArgumentsLength());
|
||||
switch (result.getResultType()) {
|
||||
case EXCEPTION:
|
||||
ctx.channel().attr(kickMessageAttribute).set(
|
||||
config.getDisconnect().getInvalidKey());
|
||||
break;
|
||||
case INVALID_DATA_LENGTH:
|
||||
ctx.channel().attr(kickMessageAttribute)
|
||||
.set(config.getDisconnect().getInvalidArgumentsLength());
|
||||
break;
|
||||
case TIMESTAMP_DENIED:
|
||||
ctx.channel().attr(kickMessageAttribute).set(Constants.TIMESTAMP_DENIED_MESSAGE);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,8 @@ import net.md_5.bungee.protocol.packet.Handshake;
|
||||
import org.geysermc.floodgate.api.ProxyFloodgateApi;
|
||||
import org.geysermc.floodgate.api.player.FloodgatePlayer;
|
||||
import org.geysermc.floodgate.config.ProxyFloodgateConfig;
|
||||
import org.geysermc.floodgate.player.FloodgatePlayerImpl;
|
||||
import org.geysermc.floodgate.util.BedrockData;
|
||||
import org.geysermc.floodgate.util.ReflectionUtils;
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
@@ -92,7 +94,8 @@ public class BungeeServerDataHandler extends MessageToMessageEncoder<Object> {
|
||||
FloodgatePlayer player = wrapper.getHandle().attr(playerAttribute).get();
|
||||
|
||||
if (player != null) {
|
||||
String encryptedData = api.getEncryptedData(player.getCorrectUniqueId());
|
||||
BedrockData data = player.as(FloodgatePlayerImpl.class).toBedrockData();
|
||||
String encryptedData = api.createEncryptedDataString(data);
|
||||
|
||||
Handshake handshake = (Handshake) packet;
|
||||
String address = handshake.getHost();
|
||||
|
||||
@@ -26,15 +26,11 @@
|
||||
package org.geysermc.floodgate.api;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import org.geysermc.floodgate.crypto.FloodgateCipher;
|
||||
import org.geysermc.floodgate.pluginmessage.PluginMessageManager;
|
||||
import org.geysermc.floodgate.util.BedrockData;
|
||||
|
||||
public final class ProxyFloodgateApi extends SimpleFloodgateApi {
|
||||
private final Map<UUID, String> encryptedData = new HashMap<>();
|
||||
private final FloodgateCipher cipher;
|
||||
|
||||
public ProxyFloodgateApi(PluginMessageManager pluginMessageManager, FloodgateCipher cipher) {
|
||||
@@ -42,25 +38,16 @@ public final class ProxyFloodgateApi extends SimpleFloodgateApi {
|
||||
this.cipher = cipher;
|
||||
}
|
||||
|
||||
public String getEncryptedData(UUID uuid) {
|
||||
return encryptedData.get(uuid);
|
||||
}
|
||||
|
||||
public void addEncryptedData(UUID uuid, String encryptedData) {
|
||||
this.encryptedData.put(uuid, encryptedData); // just override already existing data I guess
|
||||
}
|
||||
|
||||
public void removeEncryptedData(UUID uuid) {
|
||||
encryptedData.remove(uuid);
|
||||
}
|
||||
|
||||
public void updateEncryptedData(UUID uuid, BedrockData bedrockData) {
|
||||
public byte[] createEncryptedData(BedrockData bedrockData) {
|
||||
try {
|
||||
byte[] encryptedData = cipher.encryptFromString(bedrockData.toString());
|
||||
addEncryptedData(uuid, new String(encryptedData, StandardCharsets.UTF_8));
|
||||
return cipher.encryptFromString(bedrockData.toString());
|
||||
} catch (Exception exception) {
|
||||
throw new IllegalStateException("We failed to update the BedrockData, " +
|
||||
"but we can't continue without the updated version!", exception);
|
||||
throw new IllegalStateException("We failed to create the encrypted data, " +
|
||||
"but creating encrypted data is mandatory!", exception);
|
||||
}
|
||||
}
|
||||
|
||||
public String createEncryptedDataString(BedrockData bedrockData) {
|
||||
return new String(createEncryptedData(bedrockData), StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,11 +28,14 @@ package org.geysermc.floodgate.player;
|
||||
import static org.geysermc.floodgate.util.BedrockData.EXPECTED_LENGTH;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
import com.google.common.cache.Cache;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.util.AttributeKey;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
@@ -55,6 +58,12 @@ import org.geysermc.floodgate.util.Utils;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public final class FloodgateHandshakeHandler {
|
||||
private final Cache<String, Long> handleCache =
|
||||
CacheBuilder.newBuilder()
|
||||
.maximumSize(500)
|
||||
.expireAfterWrite(1, TimeUnit.MINUTES)
|
||||
.build();
|
||||
|
||||
private final HandshakeHandlersImpl handshakeHandlers;
|
||||
private final SimpleFloodgateApi api;
|
||||
private final FloodgateCipher cipher;
|
||||
@@ -97,6 +106,29 @@ public final class FloodgateHandshakeHandler {
|
||||
channel, bedrockData, hostname);
|
||||
}
|
||||
|
||||
// timestamp checks
|
||||
|
||||
long timeDifference = System.currentTimeMillis() - bedrockData.getTimestamp();
|
||||
if (timeDifference > 6000 || timeDifference < 0) {
|
||||
return callHandlerAndReturnResult(
|
||||
ResultType.TIMESTAMP_DENIED,
|
||||
channel, bedrockData, hostname);
|
||||
}
|
||||
|
||||
Long cachedTimestamp = handleCache.getIfPresent(bedrockData.getXuid());
|
||||
if (cachedTimestamp != null) {
|
||||
// the cached timestamp is newer than the gotten timestamp
|
||||
// you also can't reuse the data (the timestamp is there to prevent that as well)
|
||||
if (cachedTimestamp >= bedrockData.getTimestamp()) {
|
||||
return callHandlerAndReturnResult(
|
||||
ResultType.TIMESTAMP_DENIED,
|
||||
channel, bedrockData, hostname);
|
||||
}
|
||||
}
|
||||
|
||||
handleCache.put(bedrockData.getXuid(), bedrockData.getTimestamp());
|
||||
|
||||
|
||||
LinkedPlayer linkedPlayer;
|
||||
|
||||
// we'll use the LinkedPlayer provided by Bungee or Velocity (if they included one)
|
||||
@@ -212,6 +244,7 @@ public final class FloodgateHandshakeHandler {
|
||||
EXCEPTION,
|
||||
NOT_FLOODGATE_DATA,
|
||||
INVALID_DATA_LENGTH,
|
||||
TIMESTAMP_DENIED,
|
||||
SUCCESS
|
||||
}
|
||||
|
||||
|
||||
@@ -34,9 +34,9 @@ import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.geysermc.floodgate.api.FloodgateApi;
|
||||
import org.geysermc.floodgate.api.InstanceHolder;
|
||||
import org.geysermc.floodgate.api.ProxyFloodgateApi;
|
||||
import org.geysermc.floodgate.api.SimpleFloodgateApi;
|
||||
import org.geysermc.floodgate.api.handshake.HandshakeData;
|
||||
import org.geysermc.floodgate.api.link.PlayerLink;
|
||||
import org.geysermc.floodgate.api.player.FloodgatePlayer;
|
||||
@@ -84,7 +84,7 @@ public final class FloodgatePlayerImpl implements FloodgatePlayer {
|
||||
BedrockData data,
|
||||
HandshakeData handshakeData) {
|
||||
|
||||
FloodgateApi api = FloodgateApi.getInstance();
|
||||
SimpleFloodgateApi api = InstanceHolder.castApi(SimpleFloodgateApi.class);
|
||||
|
||||
UUID javaUniqueId = Utils.getJavaUuid(data.getXuid());
|
||||
|
||||
@@ -94,18 +94,11 @@ public final class FloodgatePlayerImpl implements FloodgatePlayer {
|
||||
|
||||
LinkedPlayer linkedPlayer = handshakeData.getLinkedPlayer();
|
||||
|
||||
FloodgatePlayerImpl player = new FloodgatePlayerImpl(
|
||||
return new FloodgatePlayerImpl(
|
||||
data.getVersion(), data.getUsername(), handshakeData.getJavaUsername(),
|
||||
javaUniqueId, data.getXuid(), deviceOs, data.getLanguageCode(), uiProfile,
|
||||
inputMode, data.getIp(), data.isFromProxy(), api instanceof ProxyFloodgateApi,
|
||||
linkedPlayer, data.getSubscribeId(), data.getVerifyCode());
|
||||
|
||||
// fromProxy and linked player might have to be changed
|
||||
if (api instanceof ProxyFloodgateApi) {
|
||||
InstanceHolder.castApi(ProxyFloodgateApi.class)
|
||||
.updateEncryptedData(player.getCorrectUniqueId(), player.toBedrockData());
|
||||
}
|
||||
return player;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -31,4 +31,8 @@ public final class Constants {
|
||||
public static final String WEBSOCKET_URL = "wss://api.geysermc.org/ws";
|
||||
|
||||
public static final boolean DEBUG_MODE = true;
|
||||
|
||||
public static final String TIMESTAMP_DENIED_MESSAGE =
|
||||
"Something isn't right with this data." +
|
||||
" Try logging in again or contact a server administrator if the issue persists.";
|
||||
}
|
||||
|
||||
@@ -51,6 +51,7 @@ import org.geysermc.floodgate.config.FloodgateConfig;
|
||||
import org.geysermc.floodgate.player.FloodgateHandshakeHandler;
|
||||
import org.geysermc.floodgate.player.FloodgateHandshakeHandler.HandshakeResult;
|
||||
import org.geysermc.floodgate.util.BedrockData;
|
||||
import org.geysermc.floodgate.util.Constants;
|
||||
import org.geysermc.floodgate.util.ReflectionUtils;
|
||||
import org.geysermc.floodgate.util.SpigotUtils;
|
||||
|
||||
@@ -188,9 +189,15 @@ public final class SpigotDataHandler extends ChannelInboundHandlerAdapter {
|
||||
return;
|
||||
}
|
||||
|
||||
//todo use kickMessageAttribute and let this be common logic
|
||||
|
||||
switch (result.getResultType()) {
|
||||
case SUCCESS:
|
||||
break;
|
||||
case EXCEPTION:
|
||||
logger.info(config.getDisconnect().getInvalidKey());
|
||||
ctx.close();
|
||||
return;
|
||||
case INVALID_DATA_LENGTH:
|
||||
int dataLength = result.getBedrockData().getDataLength();
|
||||
logger.info(
|
||||
@@ -199,6 +206,10 @@ public final class SpigotDataHandler extends ChannelInboundHandlerAdapter {
|
||||
);
|
||||
ctx.close();
|
||||
return;
|
||||
case TIMESTAMP_DENIED:
|
||||
logger.info(Constants.TIMESTAMP_DENIED_MESSAGE);
|
||||
ctx.close();
|
||||
return;
|
||||
default: // only continue when SUCCESS
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -84,7 +84,6 @@ public final class VelocityDataAddon implements InjectorAddon {
|
||||
public void onChannelClosed(Channel channel) {
|
||||
FloodgatePlayer player = channel.attr(playerAttribute).get();
|
||||
if (player != null && api.removePlayer(player)) {
|
||||
api.removeEncryptedData(player.getCorrectUniqueId());
|
||||
logger.translatedInfo("floodgate.ingame.disconnect_name", player.getUsername());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ import org.geysermc.floodgate.api.player.PropertyKey;
|
||||
import org.geysermc.floodgate.config.ProxyFloodgateConfig;
|
||||
import org.geysermc.floodgate.player.FloodgateHandshakeHandler;
|
||||
import org.geysermc.floodgate.player.FloodgateHandshakeHandler.HandshakeResult;
|
||||
import org.geysermc.floodgate.util.Constants;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public final class VelocityProxyDataHandler extends ChannelInboundHandlerAdapter {
|
||||
@@ -112,6 +113,9 @@ public final class VelocityProxyDataHandler extends ChannelInboundHandlerAdapter
|
||||
ctx.channel().attr(kickMessageAttribute)
|
||||
.set(config.getDisconnect().getInvalidArgumentsLength());
|
||||
return;
|
||||
case TIMESTAMP_DENIED:
|
||||
ctx.channel().attr(kickMessageAttribute).set(Constants.TIMESTAMP_DENIED_MESSAGE);
|
||||
return;
|
||||
default: // only continue when SUCCESS
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
|
||||
package org.geysermc.floodgate.addon.data;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static org.geysermc.floodgate.util.ReflectionUtils.castedInvoke;
|
||||
import static org.geysermc.floodgate.util.ReflectionUtils.getCastedValue;
|
||||
@@ -46,6 +45,8 @@ import lombok.RequiredArgsConstructor;
|
||||
import org.geysermc.floodgate.api.ProxyFloodgateApi;
|
||||
import org.geysermc.floodgate.api.player.FloodgatePlayer;
|
||||
import org.geysermc.floodgate.config.ProxyFloodgateConfig;
|
||||
import org.geysermc.floodgate.player.FloodgatePlayerImpl;
|
||||
import org.geysermc.floodgate.util.BedrockData;
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
@RequiredArgsConstructor
|
||||
@@ -116,8 +117,8 @@ public final class VelocityServerDataHandler extends MessageToMessageEncoder<Obj
|
||||
return;
|
||||
}
|
||||
|
||||
String encryptedData = api.getEncryptedData(player.getCorrectUniqueId());
|
||||
checkArgument(encryptedData != null, "Encrypted data cannot be null");
|
||||
BedrockData data = player.as(FloodgatePlayerImpl.class).toBedrockData();
|
||||
String encryptedData = api.createEncryptedDataString(data);
|
||||
|
||||
// use the same system that we use on bungee, our data goes before all the other data
|
||||
int addressFinished = address.indexOf('\0');
|
||||
|
||||
Reference in New Issue
Block a user