1
0
mirror of https://github.com/GeyserMC/Floodgate.git synced 2025-12-21 07:49:18 +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) { public void onChannelClosed(Channel channel) {
FloodgatePlayer player = channel.attr(playerAttribute).get(); FloodgatePlayer player = channel.attr(playerAttribute).get();
if (player != null && api.removePlayer(player)) { if (player != null && api.removePlayer(player)) {
api.removeEncryptedData(player.getCorrectUniqueId());
logger.translatedInfo("floodgate.ingame.disconnect_name", player.getCorrectUsername()); 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.config.ProxyFloodgateConfig;
import org.geysermc.floodgate.player.FloodgateHandshakeHandler; import org.geysermc.floodgate.player.FloodgateHandshakeHandler;
import org.geysermc.floodgate.player.FloodgateHandshakeHandler.HandshakeResult; 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; import org.geysermc.floodgate.util.ReflectionUtils;
@SuppressWarnings("ConstantConditions") @SuppressWarnings("ConstantConditions")
@@ -115,14 +115,20 @@ public class BungeeProxyDataHandler extends ChannelInboundHandlerAdapter {
return; return;
} }
if (result.getResultType() == ResultType.EXCEPTION) { switch (result.getResultType()) {
case EXCEPTION:
ctx.channel().attr(kickMessageAttribute).set( ctx.channel().attr(kickMessageAttribute).set(
config.getDisconnect().getInvalidKey()); config.getDisconnect().getInvalidKey());
} break;
case INVALID_DATA_LENGTH:
if (result.getResultType() == ResultType.INVALID_DATA_LENGTH) {
ctx.channel().attr(kickMessageAttribute) ctx.channel().attr(kickMessageAttribute)
.set(config.getDisconnect().getInvalidArgumentsLength()); .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.ProxyFloodgateApi;
import org.geysermc.floodgate.api.player.FloodgatePlayer; import org.geysermc.floodgate.api.player.FloodgatePlayer;
import org.geysermc.floodgate.config.ProxyFloodgateConfig; import org.geysermc.floodgate.config.ProxyFloodgateConfig;
import org.geysermc.floodgate.player.FloodgatePlayerImpl;
import org.geysermc.floodgate.util.BedrockData;
import org.geysermc.floodgate.util.ReflectionUtils; import org.geysermc.floodgate.util.ReflectionUtils;
@SuppressWarnings("ConstantConditions") @SuppressWarnings("ConstantConditions")
@@ -92,7 +94,8 @@ public class BungeeServerDataHandler extends MessageToMessageEncoder<Object> {
FloodgatePlayer player = wrapper.getHandle().attr(playerAttribute).get(); FloodgatePlayer player = wrapper.getHandle().attr(playerAttribute).get();
if (player != null) { 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; Handshake handshake = (Handshake) packet;
String address = handshake.getHost(); String address = handshake.getHost();

View File

@@ -26,15 +26,11 @@
package org.geysermc.floodgate.api; package org.geysermc.floodgate.api;
import java.nio.charset.StandardCharsets; 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.crypto.FloodgateCipher;
import org.geysermc.floodgate.pluginmessage.PluginMessageManager; import org.geysermc.floodgate.pluginmessage.PluginMessageManager;
import org.geysermc.floodgate.util.BedrockData; import org.geysermc.floodgate.util.BedrockData;
public final class ProxyFloodgateApi extends SimpleFloodgateApi { public final class ProxyFloodgateApi extends SimpleFloodgateApi {
private final Map<UUID, String> encryptedData = new HashMap<>();
private final FloodgateCipher cipher; private final FloodgateCipher cipher;
public ProxyFloodgateApi(PluginMessageManager pluginMessageManager, FloodgateCipher cipher) { public ProxyFloodgateApi(PluginMessageManager pluginMessageManager, FloodgateCipher cipher) {
@@ -42,25 +38,16 @@ public final class ProxyFloodgateApi extends SimpleFloodgateApi {
this.cipher = cipher; this.cipher = cipher;
} }
public String getEncryptedData(UUID uuid) { public byte[] createEncryptedData(BedrockData bedrockData) {
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) {
try { try {
byte[] encryptedData = cipher.encryptFromString(bedrockData.toString()); return cipher.encryptFromString(bedrockData.toString());
addEncryptedData(uuid, new String(encryptedData, StandardCharsets.UTF_8));
} catch (Exception exception) { } catch (Exception exception) {
throw new IllegalStateException("We failed to update the BedrockData, " + throw new IllegalStateException("We failed to create the encrypted data, " +
"but we can't continue without the updated version!", exception); "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 static org.geysermc.floodgate.util.BedrockData.EXPECTED_LENGTH;
import com.google.common.base.Charsets; 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.channel.Channel;
import io.netty.util.AttributeKey; import io.netty.util.AttributeKey;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import lombok.AccessLevel; import lombok.AccessLevel;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
@@ -55,6 +58,12 @@ import org.geysermc.floodgate.util.Utils;
@RequiredArgsConstructor @RequiredArgsConstructor
public final class FloodgateHandshakeHandler { 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 HandshakeHandlersImpl handshakeHandlers;
private final SimpleFloodgateApi api; private final SimpleFloodgateApi api;
private final FloodgateCipher cipher; private final FloodgateCipher cipher;
@@ -97,6 +106,29 @@ public final class FloodgateHandshakeHandler {
channel, bedrockData, hostname); 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; LinkedPlayer linkedPlayer;
// we'll use the LinkedPlayer provided by Bungee or Velocity (if they included one) // we'll use the LinkedPlayer provided by Bungee or Velocity (if they included one)
@@ -212,6 +244,7 @@ public final class FloodgateHandshakeHandler {
EXCEPTION, EXCEPTION,
NOT_FLOODGATE_DATA, NOT_FLOODGATE_DATA,
INVALID_DATA_LENGTH, INVALID_DATA_LENGTH,
TIMESTAMP_DENIED,
SUCCESS SUCCESS
} }

View File

@@ -34,9 +34,9 @@ import lombok.AccessLevel;
import lombok.Getter; import lombok.Getter;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.Setter; import lombok.Setter;
import org.geysermc.floodgate.api.FloodgateApi;
import org.geysermc.floodgate.api.InstanceHolder; import org.geysermc.floodgate.api.InstanceHolder;
import org.geysermc.floodgate.api.ProxyFloodgateApi; import org.geysermc.floodgate.api.ProxyFloodgateApi;
import org.geysermc.floodgate.api.SimpleFloodgateApi;
import org.geysermc.floodgate.api.handshake.HandshakeData; import org.geysermc.floodgate.api.handshake.HandshakeData;
import org.geysermc.floodgate.api.link.PlayerLink; import org.geysermc.floodgate.api.link.PlayerLink;
import org.geysermc.floodgate.api.player.FloodgatePlayer; import org.geysermc.floodgate.api.player.FloodgatePlayer;
@@ -84,7 +84,7 @@ public final class FloodgatePlayerImpl implements FloodgatePlayer {
BedrockData data, BedrockData data,
HandshakeData handshakeData) { HandshakeData handshakeData) {
FloodgateApi api = FloodgateApi.getInstance(); SimpleFloodgateApi api = InstanceHolder.castApi(SimpleFloodgateApi.class);
UUID javaUniqueId = Utils.getJavaUuid(data.getXuid()); UUID javaUniqueId = Utils.getJavaUuid(data.getXuid());
@@ -94,18 +94,11 @@ public final class FloodgatePlayerImpl implements FloodgatePlayer {
LinkedPlayer linkedPlayer = handshakeData.getLinkedPlayer(); LinkedPlayer linkedPlayer = handshakeData.getLinkedPlayer();
FloodgatePlayerImpl player = new FloodgatePlayerImpl( return new FloodgatePlayerImpl(
data.getVersion(), data.getUsername(), handshakeData.getJavaUsername(), data.getVersion(), data.getUsername(), handshakeData.getJavaUsername(),
javaUniqueId, data.getXuid(), deviceOs, data.getLanguageCode(), uiProfile, javaUniqueId, data.getXuid(), deviceOs, data.getLanguageCode(), uiProfile,
inputMode, data.getIp(), data.isFromProxy(), api instanceof ProxyFloodgateApi, inputMode, data.getIp(), data.isFromProxy(), api instanceof ProxyFloodgateApi,
linkedPlayer, data.getSubscribeId(), data.getVerifyCode()); 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 String WEBSOCKET_URL = "wss://api.geysermc.org/ws";
public static final boolean DEBUG_MODE = true; 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;
import org.geysermc.floodgate.player.FloodgateHandshakeHandler.HandshakeResult; import org.geysermc.floodgate.player.FloodgateHandshakeHandler.HandshakeResult;
import org.geysermc.floodgate.util.BedrockData; import org.geysermc.floodgate.util.BedrockData;
import org.geysermc.floodgate.util.Constants;
import org.geysermc.floodgate.util.ReflectionUtils; import org.geysermc.floodgate.util.ReflectionUtils;
import org.geysermc.floodgate.util.SpigotUtils; import org.geysermc.floodgate.util.SpigotUtils;
@@ -188,9 +189,15 @@ public final class SpigotDataHandler extends ChannelInboundHandlerAdapter {
return; return;
} }
//todo use kickMessageAttribute and let this be common logic
switch (result.getResultType()) { switch (result.getResultType()) {
case SUCCESS: case SUCCESS:
break; break;
case EXCEPTION:
logger.info(config.getDisconnect().getInvalidKey());
ctx.close();
return;
case INVALID_DATA_LENGTH: case INVALID_DATA_LENGTH:
int dataLength = result.getBedrockData().getDataLength(); int dataLength = result.getBedrockData().getDataLength();
logger.info( logger.info(
@@ -199,6 +206,10 @@ public final class SpigotDataHandler extends ChannelInboundHandlerAdapter {
); );
ctx.close(); ctx.close();
return; return;
case TIMESTAMP_DENIED:
logger.info(Constants.TIMESTAMP_DENIED_MESSAGE);
ctx.close();
return;
default: // only continue when SUCCESS default: // only continue when SUCCESS
return; return;
} }

View File

@@ -84,7 +84,6 @@ public final class VelocityDataAddon implements InjectorAddon {
public void onChannelClosed(Channel channel) { public void onChannelClosed(Channel channel) {
FloodgatePlayer player = channel.attr(playerAttribute).get(); FloodgatePlayer player = channel.attr(playerAttribute).get();
if (player != null && api.removePlayer(player)) { if (player != null && api.removePlayer(player)) {
api.removeEncryptedData(player.getCorrectUniqueId());
logger.translatedInfo("floodgate.ingame.disconnect_name", player.getUsername()); 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.config.ProxyFloodgateConfig;
import org.geysermc.floodgate.player.FloodgateHandshakeHandler; import org.geysermc.floodgate.player.FloodgateHandshakeHandler;
import org.geysermc.floodgate.player.FloodgateHandshakeHandler.HandshakeResult; import org.geysermc.floodgate.player.FloodgateHandshakeHandler.HandshakeResult;
import org.geysermc.floodgate.util.Constants;
@RequiredArgsConstructor @RequiredArgsConstructor
public final class VelocityProxyDataHandler extends ChannelInboundHandlerAdapter { public final class VelocityProxyDataHandler extends ChannelInboundHandlerAdapter {
@@ -112,6 +113,9 @@ public final class VelocityProxyDataHandler extends ChannelInboundHandlerAdapter
ctx.channel().attr(kickMessageAttribute) ctx.channel().attr(kickMessageAttribute)
.set(config.getDisconnect().getInvalidArgumentsLength()); .set(config.getDisconnect().getInvalidArgumentsLength());
return; return;
case TIMESTAMP_DENIED:
ctx.channel().attr(kickMessageAttribute).set(Constants.TIMESTAMP_DENIED_MESSAGE);
return;
default: // only continue when SUCCESS default: // only continue when SUCCESS
return; return;
} }

View File

@@ -25,7 +25,6 @@
package org.geysermc.floodgate.addon.data; package org.geysermc.floodgate.addon.data;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
import static org.geysermc.floodgate.util.ReflectionUtils.castedInvoke; import static org.geysermc.floodgate.util.ReflectionUtils.castedInvoke;
import static org.geysermc.floodgate.util.ReflectionUtils.getCastedValue; 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.ProxyFloodgateApi;
import org.geysermc.floodgate.api.player.FloodgatePlayer; import org.geysermc.floodgate.api.player.FloodgatePlayer;
import org.geysermc.floodgate.config.ProxyFloodgateConfig; import org.geysermc.floodgate.config.ProxyFloodgateConfig;
import org.geysermc.floodgate.player.FloodgatePlayerImpl;
import org.geysermc.floodgate.util.BedrockData;
@SuppressWarnings("ConstantConditions") @SuppressWarnings("ConstantConditions")
@RequiredArgsConstructor @RequiredArgsConstructor
@@ -116,8 +117,8 @@ public final class VelocityServerDataHandler extends MessageToMessageEncoder<Obj
return; return;
} }
String encryptedData = api.getEncryptedData(player.getCorrectUniqueId()); BedrockData data = player.as(FloodgatePlayerImpl.class).toBedrockData();
checkArgument(encryptedData != null, "Encrypted data cannot be null"); String encryptedData = api.createEncryptedDataString(data);
// use the same system that we use on bungee, our data goes before all the other data // use the same system that we use on bungee, our data goes before all the other data
int addressFinished = address.indexOf('\0'); int addressFinished = address.indexOf('\0');