First Flootgate commit

This commit is contained in:
Tim203 2019-11-30 13:34:45 +01:00
parent 5dad988c2b
commit 7d645fbf16
12 changed files with 219 additions and 42 deletions

1
.gitignore vendored
View File

@ -224,3 +224,4 @@ nbdist/
### Geyser ### ### Geyser ###
config.yml config.yml
logs/ logs/
public-key.pem

View File

@ -0,0 +1,30 @@
package org.geysermc.api;
import lombok.AllArgsConstructor;
import lombok.Getter;
@AllArgsConstructor
@Getter
public enum AuthType {
OFFLINE("offline"),
ONLINE("online"),
FLOODGATE("floodgate");
public static final AuthType[] VALUES = values();
private String name;
public static AuthType getById(int id) {
return id < VALUES.length ? VALUES[id] : OFFLINE;
}
public static AuthType getByName(String name) {
String lowerCase = name.toLowerCase();
for (AuthType type : VALUES) {
if (type.getName().equals(lowerCase)) {
return type;
}
}
return OFFLINE;
}
}

View File

@ -73,4 +73,9 @@ public interface Connector {
* Shuts down the connector * Shuts down the connector
*/ */
void shutdown(); void shutdown();
/**
* The auth type for the remote server
*/
AuthType getAuthType();
} }

View File

@ -22,6 +22,12 @@
<version>1.0-SNAPSHOT</version> <version>1.0-SNAPSHOT</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency>
<groupId>org.geysermc</groupId>
<artifactId>floodgate-common</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency> <dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId> <groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId> <artifactId>jackson-dataformat-yaml</artifactId>

View File

@ -28,9 +28,9 @@ package org.geysermc.connector;
import com.nukkitx.protocol.bedrock.BedrockPacketCodec; import com.nukkitx.protocol.bedrock.BedrockPacketCodec;
import com.nukkitx.protocol.bedrock.BedrockServer; import com.nukkitx.protocol.bedrock.BedrockServer;
import com.nukkitx.protocol.bedrock.v388.Bedrock_v388; import com.nukkitx.protocol.bedrock.v388.Bedrock_v388;
import lombok.Getter; import lombok.Getter;
import org.fusesource.jansi.AnsiConsole; import org.fusesource.jansi.AnsiConsole;
import org.geysermc.api.AuthType;
import org.geysermc.api.Connector; import org.geysermc.api.Connector;
import org.geysermc.api.Geyser; import org.geysermc.api.Geyser;
import org.geysermc.api.Player; import org.geysermc.api.Player;
@ -66,17 +66,16 @@ import java.util.concurrent.TimeUnit;
@Getter @Getter
public class GeyserConnector implements Connector { public class GeyserConnector implements Connector {
public static final BedrockPacketCodec BEDROCK_PACKET_CODEC = Bedrock_v388.V388_CODEC; public static final BedrockPacketCodec BEDROCK_PACKET_CODEC = Bedrock_v388.V388_CODEC;
public static final String NAME = "Geyser"; public static final String NAME = "Geyser";
public static final String VERSION = "1.0-SNAPSHOT"; public static final String VERSION = "1.0-SNAPSHOT";
private final Map<Object, GeyserSession> players = new HashMap<>();
private static GeyserConnector instance; private static GeyserConnector instance;
private final Map<Object, GeyserSession> players = new HashMap<>();
private RemoteJavaServer remoteServer; private RemoteJavaServer remoteServer;
private AuthType authType;
private Logger logger; private Logger logger;
@ -133,6 +132,7 @@ public class GeyserConnector implements Connector {
commandMap = new GeyserCommandMap(this); commandMap = new GeyserCommandMap(this);
remoteServer = new RemoteJavaServer(config.getRemote().getAddress(), config.getRemote().getPort()); remoteServer = new RemoteJavaServer(config.getRemote().getAddress(), config.getRemote().getPort());
authType = AuthType.getByName(config.getRemote().getAuthType());
Geyser.setConnector(this); Geyser.setConnector(this);
@ -158,7 +158,7 @@ public class GeyserConnector implements Connector {
metrics = new Metrics("GeyserMC", config.getMetrics().getUUID(), false, java.util.logging.Logger.getLogger("")); metrics = new Metrics("GeyserMC", config.getMetrics().getUUID(), false, java.util.logging.Logger.getLogger(""));
metrics.addCustomChart(new Metrics.SingleLineChart("servers", () -> 1)); metrics.addCustomChart(new Metrics.SingleLineChart("servers", () -> 1));
metrics.addCustomChart(new Metrics.SingleLineChart("players", Geyser::getPlayerCount)); metrics.addCustomChart(new Metrics.SingleLineChart("players", Geyser::getPlayerCount));
metrics.addCustomChart(new Metrics.SimplePie("authMode", config.getRemote()::getAuthType)); metrics.addCustomChart(new Metrics.SimplePie("authMode", getAuthType()::getName));
} }
double completeTime = (System.currentTimeMillis() - startupTime) / 1000D; double completeTime = (System.currentTimeMillis() - startupTime) / 1000D;

View File

@ -37,6 +37,9 @@ public class GeyserConfiguration {
private BedrockConfiguration bedrock; private BedrockConfiguration bedrock;
private RemoteConfiguration remote; private RemoteConfiguration remote;
@JsonProperty("floodgate-key-file")
private String floodgateKeyFile;
private Map<String, UserAuthenticationInfo> userAuths; private Map<String, UserAuthenticationInfo> userAuths;
@JsonProperty("ping-passthrough") @JsonProperty("ping-passthrough")

View File

@ -127,9 +127,4 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
boolean defaultHandler(BedrockPacket packet) { boolean defaultHandler(BedrockPacket packet) {
return translateAndDefault(packet); return translateAndDefault(packet);
} }
@Override
public boolean handle(InventoryTransactionPacket packet) {
return translateAndDefault(packet);
}
} }

View File

@ -29,11 +29,9 @@ import com.github.steveice10.mc.auth.data.GameProfile;
import com.github.steveice10.mc.auth.exception.request.RequestException; import com.github.steveice10.mc.auth.exception.request.RequestException;
import com.github.steveice10.mc.protocol.MinecraftProtocol; import com.github.steveice10.mc.protocol.MinecraftProtocol;
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
import com.github.steveice10.mc.protocol.packet.handshake.client.HandshakePacket;
import com.github.steveice10.packetlib.Client; import com.github.steveice10.packetlib.Client;
import com.github.steveice10.packetlib.event.session.ConnectedEvent; import com.github.steveice10.packetlib.event.session.*;
import com.github.steveice10.packetlib.event.session.DisconnectedEvent;
import com.github.steveice10.packetlib.event.session.PacketReceivedEvent;
import com.github.steveice10.packetlib.event.session.SessionAdapter;
import com.github.steveice10.packetlib.packet.Packet; import com.github.steveice10.packetlib.packet.Packet;
import com.github.steveice10.packetlib.tcp.TcpSessionFactory; import com.github.steveice10.packetlib.tcp.TcpSessionFactory;
import com.nukkitx.math.vector.Vector2f; import com.nukkitx.math.vector.Vector2f;
@ -44,15 +42,10 @@ import com.nukkitx.nbt.tag.CompoundTag;
import com.nukkitx.protocol.bedrock.BedrockServerSession; import com.nukkitx.protocol.bedrock.BedrockServerSession;
import com.nukkitx.protocol.bedrock.data.GamePublishSetting; import com.nukkitx.protocol.bedrock.data.GamePublishSetting;
import com.nukkitx.protocol.bedrock.data.GameRule; import com.nukkitx.protocol.bedrock.data.GameRule;
import com.nukkitx.protocol.bedrock.packet.AvailableEntityIdentifiersPacket; import com.nukkitx.protocol.bedrock.packet.*;
import com.nukkitx.protocol.bedrock.packet.BiomeDefinitionListPacket;
import com.nukkitx.protocol.bedrock.packet.LevelChunkPacket;
import com.nukkitx.protocol.bedrock.packet.NetworkChunkPublisherUpdatePacket;
import com.nukkitx.protocol.bedrock.packet.PlayStatusPacket;
import com.nukkitx.protocol.bedrock.packet.StartGamePacket;
import com.nukkitx.protocol.bedrock.packet.TextPacket;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import org.geysermc.api.AuthType;
import org.geysermc.api.Player; import org.geysermc.api.Player;
import org.geysermc.api.RemoteServer; import org.geysermc.api.RemoteServer;
import org.geysermc.api.session.AuthData; import org.geysermc.api.session.AuthData;
@ -60,22 +53,30 @@ import org.geysermc.api.window.FormWindow;
import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.entity.PlayerEntity; import org.geysermc.connector.entity.PlayerEntity;
import org.geysermc.connector.inventory.PlayerInventory; import org.geysermc.connector.inventory.PlayerInventory;
import org.geysermc.connector.network.session.auth.BedrockClientData;
import org.geysermc.connector.network.session.cache.*; import org.geysermc.connector.network.session.cache.*;
import org.geysermc.connector.network.translators.Registry; import org.geysermc.connector.network.translators.Registry;
import org.geysermc.connector.network.translators.TranslatorsInit; import org.geysermc.connector.network.translators.TranslatorsInit;
import org.geysermc.connector.utils.Toolbox; import org.geysermc.connector.utils.Toolbox;
import org.geysermc.floodgate.util.BedrockData;
import org.geysermc.floodgate.util.EncryptionUtil;
import java.io.IOException;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.nio.file.Paths;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.util.UUID; import java.util.UUID;
@Getter @Getter
public class GeyserSession implements Player { public class GeyserSession implements Player {
private final GeyserConnector connector; private final GeyserConnector connector;
private final UpstreamSession upstream; private final UpstreamSession upstream;
private RemoteServer remoteServer; private RemoteServer remoteServer;
private Client downstream; private Client downstream;
private AuthData authenticationData; @Setter private AuthData authenticationData;
@Setter private BedrockClientData clientData;
private PlayerEntity playerEntity; private PlayerEntity playerEntity;
private PlayerInventory inventory; private PlayerInventory inventory;
@ -127,7 +128,7 @@ public class GeyserSession implements Player {
public void connect(RemoteServer remoteServer) { public void connect(RemoteServer remoteServer) {
startGame(); startGame();
this.remoteServer = remoteServer; this.remoteServer = remoteServer;
if (!(connector.getConfig().getRemote().getAuthType().hashCode() == "online".hashCode())) { if (connector.getAuthType() == AuthType.OFFLINE) {
connector.getLogger().info("Attempting to login using offline mode... authentication is disabled."); connector.getLogger().info("Attempting to login using offline mode... authentication is disabled.");
authenticate(authenticationData.getName()); authenticate(authenticationData.getName());
} }
@ -181,8 +182,51 @@ public class GeyserSession implements Player {
protocol = new MinecraftProtocol(username); protocol = new MinecraftProtocol(username);
} }
boolean floodgate = connector.getAuthType() == AuthType.FLOODGATE;
final PublicKey publicKey;
if (floodgate) {
PublicKey key = null;
try {
key = EncryptionUtil.getKeyFromFile(
Paths.get(connector.getConfig().getFloodgateKeyFile()),
PublicKey.class
);
} catch (IOException | InvalidKeySpecException | NoSuchAlgorithmException e) {
connector.getLogger().error("Error while reading Floodgate key file", e);
}
publicKey = key;
} else publicKey = null;
downstream = new Client(remoteServer.getAddress(), remoteServer.getPort(), protocol, new TcpSessionFactory()); downstream = new Client(remoteServer.getAddress(), remoteServer.getPort(), protocol, new TcpSessionFactory());
downstream.getSession().addListener(new SessionAdapter() { downstream.getSession().addListener(new SessionAdapter() {
@Override
public void packetSending(PacketSendingEvent event) {
//todo move this somewhere else
if (event.getPacket() instanceof HandshakePacket && floodgate) {
String encrypted = "";
try {
encrypted = EncryptionUtil.encryptFromInstance(publicKey, new BedrockData(
clientData.getGameVersion(),
authenticationData.getName(),
authenticationData.getXboxUUID(),
clientData.getDeviceOS().ordinal(),
clientData.getLanguageCode(),
clientData.getCurrentInputMode().ordinal()
));
} catch (Exception e) {
connector.getLogger().error("Failed to encrypt message", e);
}
HandshakePacket handshakePacket = event.getPacket();
event.setPacket(new HandshakePacket(
handshakePacket.getProtocolVersion(),
handshakePacket.getHostname() + '\0' + "Geyser-Floodgate" + '\0' + encrypted,
handshakePacket.getPort(),
handshakePacket.getIntent()
));
}
}
@Override @Override
public void connected(ConnectedEvent event) { public void connected(ConnectedEvent event) {
@ -231,18 +275,10 @@ public class GeyserSession implements Player {
closed = true; closed = true;
} }
public boolean isClosed() {
return closed;
}
public void close() { public void close() {
disconnect("Server closed."); disconnect("Server closed.");
} }
public void setAuthenticationData(AuthData authData) {
authenticationData = authData;
}
@Override @Override
public String getName() { public String getName() {
return authenticationData.getName(); return authenticationData.getName();

View File

@ -0,0 +1,91 @@
package org.geysermc.connector.network.session.auth;
import com.fasterxml.jackson.annotation.JsonEnumDefaultValue;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter;
import java.util.UUID;
@JsonIgnoreProperties(ignoreUnknown = true)
@Getter
public class BedrockClientData {
@JsonProperty(value = "GameVersion")
private String gameVersion;
@JsonProperty(value = "ServerAddress")
private String serverAddress;
@JsonProperty(value = "ThirdPartyName")
private String username;
@JsonProperty(value = "LanguageCode")
private String languageCode;
@JsonProperty(value = "SkinId")
private String skinId;
@JsonProperty(value = "SkinData")
private String skinData;
@JsonProperty(value = "CapeId")
private String capeId;
@JsonProperty(value = "CapeData")
private byte[] capeData;
@JsonProperty(value = "CapeOnClassicSkin")
private boolean capeOnClassicSkin;
@JsonProperty(value = "SkinResourcePatch")
private String geometryName;
@JsonProperty(value = "SkinGeometryData")
private String geometryData;
@JsonProperty(value = "PremiumSkin")
private boolean premiumSkin;
@JsonProperty(value = "DeviceId")
private UUID deviceId;
@JsonProperty(value = "DeviceModel")
private String deviceModel;
@JsonProperty(value = "DeviceOS")
private DeviceOS deviceOS;
@JsonProperty(value = "UIProfile")
private UIProfile uiProfile;
@JsonProperty(value = "GuiScale")
private int guiScale;
@JsonProperty(value = "CurrentInputMode")
private InputMode currentInputMode;
@JsonProperty(value = "DefaultInputMode")
private InputMode defaultInputMode;
@JsonProperty("PlatformOnlineId")
private String platformOnlineId;
@JsonProperty(value = "PlatformOfflineId")
private String platformOfflineId;
@JsonProperty(value = "SelfSignedId")
private UUID selfSignedId;
@JsonProperty(value = "ClientRandomId")
private long clientRandomId;
public enum UIProfile {
@JsonEnumDefaultValue
CLASSIC,
POCKET
}
public enum DeviceOS {
@JsonEnumDefaultValue
UNKOWN,
ANDROID,
IOS,
OSX,
FIREOS,
GEARVR,
HOLOLENS,
WIN10,
WIN32,
DEDICATED,
ORBIS,
NX
}
public enum InputMode {
@JsonEnumDefaultValue
UNKNOWN,
KEYBOARD_MOUSE,
TOUCH, // I guess Touch?
CONTROLLER
}
}

View File

@ -9,7 +9,6 @@ import com.nukkitx.network.util.Preconditions;
import com.nukkitx.protocol.bedrock.packet.LoginPacket; import com.nukkitx.protocol.bedrock.packet.LoginPacket;
import com.nukkitx.protocol.bedrock.packet.ServerToClientHandshakePacket; import com.nukkitx.protocol.bedrock.packet.ServerToClientHandshakePacket;
import com.nukkitx.protocol.bedrock.util.EncryptionUtils; import com.nukkitx.protocol.bedrock.util.EncryptionUtils;
import net.minidev.json.JSONObject;
import org.geysermc.api.events.player.PlayerFormResponseEvent; import org.geysermc.api.events.player.PlayerFormResponseEvent;
import org.geysermc.api.window.CustomFormBuilder; import org.geysermc.api.window.CustomFormBuilder;
import org.geysermc.api.window.CustomFormWindow; import org.geysermc.api.window.CustomFormWindow;
@ -20,6 +19,7 @@ import org.geysermc.api.window.response.CustomFormResponse;
import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.session.auth.BedrockAuthData; import org.geysermc.connector.network.session.auth.BedrockAuthData;
import org.geysermc.connector.network.session.auth.BedrockClientData;
import org.geysermc.connector.network.session.cache.WindowCache; import org.geysermc.connector.network.session.cache.WindowCache;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
@ -72,7 +72,7 @@ public class LoginEncryptionUtils {
encryptConnectionWithCert(connector, session, loginPacket.getSkinData().toString(), certChainData); encryptConnectionWithCert(connector, session, loginPacket.getSkinData().toString(), certChainData);
} }
private static void encryptConnectionWithCert(GeyserConnector connector, GeyserSession session, String playerSkin, JsonNode certChainData) { private static void encryptConnectionWithCert(GeyserConnector connector, GeyserSession session, String clientData, JsonNode certChainData) {
try { try {
boolean validChain = validateChainData(certChainData); boolean validChain = validateChainData(certChainData);
@ -85,17 +85,23 @@ public class LoginEncryptionUtils {
throw new RuntimeException("AuthData was not found!"); throw new RuntimeException("AuthData was not found!");
} }
JSONObject extraData = (JSONObject) jwt.getPayload().toJSONObject().get("extraData"); JsonNode extraData = payload.get("extraData");
session.setAuthenticationData(new BedrockAuthData(extraData.getAsString("displayName"), UUID.fromString(extraData.getAsString("identity")), extraData.getAsString("XUID"))); session.setAuthenticationData(new BedrockAuthData(
extraData.get("displayName").asText(),
UUID.fromString(extraData.get("identity").asText()),
extraData.get("XUID").asText()
));
if (payload.get("identityPublicKey").getNodeType() != JsonNodeType.STRING) { if (payload.get("identityPublicKey").getNodeType() != JsonNodeType.STRING) {
throw new RuntimeException("Identity Public Key was not found!"); throw new RuntimeException("Identity Public Key was not found!");
} }
ECPublicKey identityPublicKey = EncryptionUtils.generateKey(payload.get("identityPublicKey").textValue()); ECPublicKey identityPublicKey = EncryptionUtils.generateKey(payload.get("identityPublicKey").textValue());
JWSObject clientJwt = JWSObject.parse(playerSkin); JWSObject clientJwt = JWSObject.parse(clientData);
EncryptionUtils.verifyJwt(clientJwt, identityPublicKey); EncryptionUtils.verifyJwt(clientJwt, identityPublicKey);
session.setClientData(JSON_MAPPER.convertValue(JSON_MAPPER.readTree(clientJwt.getPayload().toBytes()), BedrockClientData.class));
if (EncryptionUtils.canUseEncryption()) { if (EncryptionUtils.canUseEncryption()) {
LoginEncryptionUtils.startEncryptionHandshake(session, identityPublicKey); LoginEncryptionUtils.startEncryptionHandshake(session, identityPublicKey);
} }

View File

@ -8,8 +8,8 @@ import com.nukkitx.protocol.bedrock.packet.PlayerListPacket;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
import org.apache.commons.codec.Charsets; import org.apache.commons.codec.Charsets;
import org.geysermc.api.AuthType;
import org.geysermc.api.Geyser; import org.geysermc.api.Geyser;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.entity.PlayerEntity; import org.geysermc.connector.entity.PlayerEntity;
import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.session.GeyserSession;
@ -96,7 +96,7 @@ public class SkinUtils {
return new GameProfileData(skinUrl, capeUrl, isAlex); return new GameProfileData(skinUrl, capeUrl, isAlex);
} catch (Exception exception) { } catch (Exception exception) {
if (!((GeyserConnector) Geyser.getConnector()).getConfig().getRemote().getAuthType().equals("offline")) { if (Geyser.getConnector().getAuthType() != AuthType.OFFLINE) {
Geyser.getLogger().debug("Got invalid texture data for " + profile.getName() + " " + exception.getMessage()); Geyser.getLogger().debug("Got invalid texture data for " + profile.getName() + " " + exception.getMessage());
} }
// return default skin with default cape when texture data is invalid // return default skin with default cape when texture data is invalid

View File

@ -20,10 +20,14 @@ remote:
address: 127.0.0.1 address: 127.0.0.1
# The port of the remote (Java Edition) server # The port of the remote (Java Edition) server
port: 25565 port: 25565
# Authentication type. Can be offline, online, or floodgate (see the wiki).
# Authentication type. Can be offline, online, or hybrid (see the wiki).
auth-type: online auth-type: online
# Floodgate uses encryption to ensure use from authorised sources.
# This should point to the public key generated by Floodgate (Bungee or CraftBukkit)
# You can ignore this when not using Floodgate.
floodgate-key-file: public-key.pem
## the Xbox/MCPE username is the key for the Java server auth-info ## the Xbox/MCPE username is the key for the Java server auth-info
## this allows automatic configuration/login to the remote Java server ## this allows automatic configuration/login to the remote Java server
## if you are brave/stupid enough to put your Mojang account info into ## if you are brave/stupid enough to put your Mojang account info into