1
0
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:
Tim203
2021-02-16 17:47:15 +01:00
parent 8290740775
commit f149e94964
11 changed files with 86 additions and 46 deletions

View File

@@ -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());
}
}

View File

@@ -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;
}
}
}

View File

@@ -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();

View File

@@ -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);
}
}

View File

@@ -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
}

View File

@@ -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;
}
/**

View File

@@ -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.";
}

View File

@@ -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;
}

View File

@@ -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());
}
}

View File

@@ -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;
}

View File

@@ -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');