From 7d645fbf16958079b44d13dc085faefa456de775 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Sat, 30 Nov 2019 13:34:45 +0100 Subject: [PATCH 1/7] First Flootgate commit --- .gitignore | 1 + .../main/java/org/geysermc/api/AuthType.java | 30 ++++++ .../main/java/org/geysermc/api/Connector.java | 5 + connector/pom.xml | 6 ++ .../geysermc/connector/GeyserConnector.java | 12 +-- .../configuration/GeyserConfiguration.java | 3 + .../network/UpstreamPacketHandler.java | 5 - .../network/session/GeyserSession.java | 80 +++++++++++----- .../session/auth/BedrockClientData.java | 91 +++++++++++++++++++ .../connector/utils/LoginEncryptionUtils.java | 16 +++- .../geysermc/connector/utils/SkinUtils.java | 4 +- connector/src/main/resources/config.yml | 8 +- 12 files changed, 219 insertions(+), 42 deletions(-) create mode 100644 api/src/main/java/org/geysermc/api/AuthType.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java diff --git a/.gitignore b/.gitignore index f003e014..9b233578 100644 --- a/.gitignore +++ b/.gitignore @@ -224,3 +224,4 @@ nbdist/ ### Geyser ### config.yml logs/ +public-key.pem diff --git a/api/src/main/java/org/geysermc/api/AuthType.java b/api/src/main/java/org/geysermc/api/AuthType.java new file mode 100644 index 00000000..55d97c20 --- /dev/null +++ b/api/src/main/java/org/geysermc/api/AuthType.java @@ -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; + } +} \ No newline at end of file diff --git a/api/src/main/java/org/geysermc/api/Connector.java b/api/src/main/java/org/geysermc/api/Connector.java index 708372e3..9259eefe 100644 --- a/api/src/main/java/org/geysermc/api/Connector.java +++ b/api/src/main/java/org/geysermc/api/Connector.java @@ -73,4 +73,9 @@ public interface Connector { * Shuts down the connector */ void shutdown(); + + /** + * The auth type for the remote server + */ + AuthType getAuthType(); } diff --git a/connector/pom.xml b/connector/pom.xml index 60ecfc55..5b4a2bcf 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -22,6 +22,12 @@ 1.0-SNAPSHOT compile + + org.geysermc + floodgate-common + 1.0-SNAPSHOT + compile + com.fasterxml.jackson.dataformat jackson-dataformat-yaml diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java index 4abe763f..3b7b1a70 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -28,9 +28,9 @@ package org.geysermc.connector; import com.nukkitx.protocol.bedrock.BedrockPacketCodec; import com.nukkitx.protocol.bedrock.BedrockServer; import com.nukkitx.protocol.bedrock.v388.Bedrock_v388; - import lombok.Getter; import org.fusesource.jansi.AnsiConsole; +import org.geysermc.api.AuthType; import org.geysermc.api.Connector; import org.geysermc.api.Geyser; import org.geysermc.api.Player; @@ -66,17 +66,16 @@ import java.util.concurrent.TimeUnit; @Getter public class GeyserConnector implements Connector { - public static final BedrockPacketCodec BEDROCK_PACKET_CODEC = Bedrock_v388.V388_CODEC; - public static final String NAME = "Geyser"; public static final String VERSION = "1.0-SNAPSHOT"; - private final Map players = new HashMap<>(); - private static GeyserConnector instance; + private final Map players = new HashMap<>(); + private RemoteJavaServer remoteServer; + private AuthType authType; private Logger logger; @@ -133,6 +132,7 @@ public class GeyserConnector implements Connector { commandMap = new GeyserCommandMap(this); remoteServer = new RemoteJavaServer(config.getRemote().getAddress(), config.getRemote().getPort()); + authType = AuthType.getByName(config.getRemote().getAuthType()); 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.addCustomChart(new Metrics.SingleLineChart("servers", () -> 1)); 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; diff --git a/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java b/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java index b78fc227..ba5e63b2 100644 --- a/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java +++ b/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java @@ -37,6 +37,9 @@ public class GeyserConfiguration { private BedrockConfiguration bedrock; private RemoteConfiguration remote; + @JsonProperty("floodgate-key-file") + private String floodgateKeyFile; + private Map userAuths; @JsonProperty("ping-passthrough") diff --git a/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java b/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java index 4990280b..99fa1c5d 100644 --- a/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java +++ b/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java @@ -127,9 +127,4 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { boolean defaultHandler(BedrockPacket packet) { return translateAndDefault(packet); } - - @Override - public boolean handle(InventoryTransactionPacket packet) { - return translateAndDefault(packet); - } } \ No newline at end of file diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index 8f4544d3..aabc3d66 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -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.protocol.MinecraftProtocol; 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.event.session.ConnectedEvent; -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.event.session.*; import com.github.steveice10.packetlib.packet.Packet; import com.github.steveice10.packetlib.tcp.TcpSessionFactory; 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.data.GamePublishSetting; import com.nukkitx.protocol.bedrock.data.GameRule; -import com.nukkitx.protocol.bedrock.packet.AvailableEntityIdentifiersPacket; -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 com.nukkitx.protocol.bedrock.packet.*; import lombok.Getter; import lombok.Setter; +import org.geysermc.api.AuthType; import org.geysermc.api.Player; import org.geysermc.api.RemoteServer; 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.entity.PlayerEntity; 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.translators.Registry; import org.geysermc.connector.network.translators.TranslatorsInit; 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.nio.file.Paths; +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; +import java.security.spec.InvalidKeySpecException; import java.util.UUID; @Getter public class GeyserSession implements Player { - private final GeyserConnector connector; private final UpstreamSession upstream; private RemoteServer remoteServer; private Client downstream; - private AuthData authenticationData; + @Setter private AuthData authenticationData; + @Setter private BedrockClientData clientData; private PlayerEntity playerEntity; private PlayerInventory inventory; @@ -127,7 +128,7 @@ public class GeyserSession implements Player { public void connect(RemoteServer remoteServer) { startGame(); 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."); authenticate(authenticationData.getName()); } @@ -181,8 +182,51 @@ public class GeyserSession implements Player { 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.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 public void connected(ConnectedEvent event) { @@ -231,18 +275,10 @@ public class GeyserSession implements Player { closed = true; } - public boolean isClosed() { - return closed; - } - public void close() { disconnect("Server closed."); } - public void setAuthenticationData(AuthData authData) { - authenticationData = authData; - } - @Override public String getName() { return authenticationData.getName(); diff --git a/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java b/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java new file mode 100644 index 00000000..9e7345ce --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java @@ -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 + } +} diff --git a/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java b/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java index b94f13cd..5ccef71c 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java @@ -9,7 +9,6 @@ import com.nukkitx.network.util.Preconditions; import com.nukkitx.protocol.bedrock.packet.LoginPacket; import com.nukkitx.protocol.bedrock.packet.ServerToClientHandshakePacket; import com.nukkitx.protocol.bedrock.util.EncryptionUtils; -import net.minidev.json.JSONObject; import org.geysermc.api.events.player.PlayerFormResponseEvent; import org.geysermc.api.window.CustomFormBuilder; 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.network.session.GeyserSession; 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 javax.crypto.SecretKey; @@ -72,7 +72,7 @@ public class LoginEncryptionUtils { 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 { boolean validChain = validateChainData(certChainData); @@ -85,17 +85,23 @@ public class LoginEncryptionUtils { throw new RuntimeException("AuthData was not found!"); } - JSONObject extraData = (JSONObject) jwt.getPayload().toJSONObject().get("extraData"); - session.setAuthenticationData(new BedrockAuthData(extraData.getAsString("displayName"), UUID.fromString(extraData.getAsString("identity")), extraData.getAsString("XUID"))); + JsonNode extraData = payload.get("extraData"); + 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) { throw new RuntimeException("Identity Public Key was not found!"); } ECPublicKey identityPublicKey = EncryptionUtils.generateKey(payload.get("identityPublicKey").textValue()); - JWSObject clientJwt = JWSObject.parse(playerSkin); + JWSObject clientJwt = JWSObject.parse(clientData); EncryptionUtils.verifyJwt(clientJwt, identityPublicKey); + session.setClientData(JSON_MAPPER.convertValue(JSON_MAPPER.readTree(clientJwt.getPayload().toBytes()), BedrockClientData.class)); + if (EncryptionUtils.canUseEncryption()) { LoginEncryptionUtils.startEncryptionHandshake(session, identityPublicKey); } diff --git a/connector/src/main/java/org/geysermc/connector/utils/SkinUtils.java b/connector/src/main/java/org/geysermc/connector/utils/SkinUtils.java index b4c9d7f6..e42ca473 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/SkinUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/SkinUtils.java @@ -8,8 +8,8 @@ import com.nukkitx.protocol.bedrock.packet.PlayerListPacket; import lombok.AllArgsConstructor; import lombok.Getter; import org.apache.commons.codec.Charsets; +import org.geysermc.api.AuthType; import org.geysermc.api.Geyser; -import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.entity.PlayerEntity; import org.geysermc.connector.network.session.GeyserSession; @@ -96,7 +96,7 @@ public class SkinUtils { return new GameProfileData(skinUrl, capeUrl, isAlex); } 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()); } // return default skin with default cape when texture data is invalid diff --git a/connector/src/main/resources/config.yml b/connector/src/main/resources/config.yml index f92d6d42..029a4619 100644 --- a/connector/src/main/resources/config.yml +++ b/connector/src/main/resources/config.yml @@ -20,10 +20,14 @@ remote: address: 127.0.0.1 # The port of the remote (Java Edition) server port: 25565 - - # Authentication type. Can be offline, online, or hybrid (see the wiki). + # Authentication type. Can be offline, online, or floodgate (see the wiki). 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 ## this allows automatic configuration/login to the remote Java server ## if you are brave/stupid enough to put your Mojang account info into From b12256bc1e969157e36321ce1317c1222ab86ad0 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Sat, 30 Nov 2019 15:32:13 +0100 Subject: [PATCH 2/7] Changed a method name --- .../org/geysermc/connector/network/session/GeyserSession.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index aabc3d66..fc1e687d 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -206,7 +206,7 @@ public class GeyserSession implements Player { if (event.getPacket() instanceof HandshakePacket && floodgate) { String encrypted = ""; try { - encrypted = EncryptionUtil.encryptFromInstance(publicKey, new BedrockData( + encrypted = EncryptionUtil.encryptBedrockData(publicKey, new BedrockData( clientData.getGameVersion(), authenticationData.getName(), authenticationData.getXboxUUID(), From 1c48279a9fd6144f09f6f5bad7a8a691049d800c Mon Sep 17 00:00:00 2001 From: Tim203 Date: Sat, 30 Nov 2019 18:38:09 +0100 Subject: [PATCH 3/7] Little changes --- api/src/main/java/org/geysermc/api/AuthType.java | 14 +++++--------- .../org/geysermc/connector/GeyserConnector.java | 2 +- .../connector/network/session/GeyserSession.java | 4 ++++ .../network/session/auth/BedrockClientData.java | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/api/src/main/java/org/geysermc/api/AuthType.java b/api/src/main/java/org/geysermc/api/AuthType.java index 55d97c20..6364ba15 100644 --- a/api/src/main/java/org/geysermc/api/AuthType.java +++ b/api/src/main/java/org/geysermc/api/AuthType.java @@ -1,27 +1,23 @@ package org.geysermc.api; -import lombok.AllArgsConstructor; import lombok.Getter; -@AllArgsConstructor @Getter public enum AuthType { - OFFLINE("offline"), - ONLINE("online"), - FLOODGATE("floodgate"); + OFFLINE, + ONLINE, + 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(); + String upperCase = name.toUpperCase(); for (AuthType type : VALUES) { - if (type.getName().equals(lowerCase)) { + if (type.name().equals(upperCase)) { return type; } } diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java index 3b7b1a70..5ec21476 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -158,7 +158,7 @@ public class GeyserConnector implements Connector { 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("players", Geyser::getPlayerCount)); - metrics.addCustomChart(new Metrics.SimplePie("authMode", getAuthType()::getName)); + metrics.addCustomChart(new Metrics.SimplePie("authMode", getAuthType()::name)); } double completeTime = (System.currentTimeMillis() - startupTime) / 1000D; diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index fc1e687d..e748d2a1 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -198,6 +198,10 @@ public class GeyserSession implements Player { publicKey = key; } else publicKey = null; + if (publicKey != null) { + connector.getLogger().info("Loaded Floodgate key!"); + } + downstream = new Client(remoteServer.getAddress(), remoteServer.getPort(), protocol, new TcpSessionFactory()); downstream.getSession().addListener(new SessionAdapter() { @Override diff --git a/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java b/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java index 9e7345ce..574ba544 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java @@ -37,7 +37,7 @@ public class BedrockClientData { private boolean premiumSkin; @JsonProperty(value = "DeviceId") - private UUID deviceId; + private String deviceId; @JsonProperty(value = "DeviceModel") private String deviceModel; @JsonProperty(value = "DeviceOS") From f1b19fc04e654912c32c7c8117ac19241c4c9654 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Sat, 30 Nov 2019 19:10:29 +0100 Subject: [PATCH 4/7] Little changes --- .../java/org/geysermc/connector/GeyserConnector.java | 2 +- .../connector/network/session/GeyserSession.java | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java index 5ec21476..1af7bb02 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -158,7 +158,7 @@ public class GeyserConnector implements Connector { 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("players", Geyser::getPlayerCount)); - metrics.addCustomChart(new Metrics.SimplePie("authMode", getAuthType()::name)); + metrics.addCustomChart(new Metrics.SimplePie("authMode", () -> getAuthType().name().toLowerCase())); } double completeTime = (System.currentTimeMillis() - startupTime) / 1000D; diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index e748d2a1..11e1852a 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -128,8 +128,13 @@ public class GeyserSession implements Player { public void connect(RemoteServer remoteServer) { startGame(); this.remoteServer = remoteServer; - if (connector.getAuthType() == AuthType.OFFLINE) { - connector.getLogger().info("Attempting to login using offline mode... authentication is disabled."); + if (connector.getAuthType() != AuthType.ONLINE) { + connector.getLogger().info( + "Attempting to login using " + connector.getAuthType().name().toLowerCase() + " mode... " + + (connector.getAuthType() == AuthType.OFFLINE ? + "authentication is disabled." : "authentication will be encrypted" + ) + ); authenticate(authenticationData.getName()); } From 9a13b566cff7ab84e4dc37ed446b111794368a4a Mon Sep 17 00:00:00 2001 From: Tim203 Date: Sat, 30 Nov 2019 21:30:25 +0100 Subject: [PATCH 5/7] Little changes --- common/pom.xml | 6 ++ .../geysermc/floodgate/util/BedrockData.java | 35 +++++++++ .../floodgate/util/EncryptionUtil.java | 76 +++++++++++++++++++ connector/pom.xml | 6 -- 4 files changed, 117 insertions(+), 6 deletions(-) create mode 100644 common/src/main/java/org/geysermc/floodgate/util/BedrockData.java create mode 100644 common/src/main/java/org/geysermc/floodgate/util/EncryptionUtil.java diff --git a/common/pom.xml b/common/pom.xml index 6a0a6ff0..d54730ff 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -10,6 +10,12 @@ common + + org.projectlombok + lombok + 1.18.4 + providec + com.github.steveice10 opennbt diff --git a/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java b/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java new file mode 100644 index 00000000..f4efa971 --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java @@ -0,0 +1,35 @@ +package org.geysermc.floodgate.util; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public class BedrockData { + public static final int EXPECTED_LENGTH = 6; + private String version; + private String username; + private String xuid; + private int deviceId; + private String languageCode; + private int inputMode; + private int dataLength; + + public BedrockData(String version, String username, String xuid, int deviceId, String languageCode, int inputMode) { + this(version, username, xuid, deviceId, languageCode, inputMode, EXPECTED_LENGTH); + } + + public static BedrockData fromString(String data) { + String[] split = data.split("\0"); + if (split.length != EXPECTED_LENGTH) return null; + return new BedrockData(split[0], split[1], split[2], Integer.parseInt(split[3]), split[4], Integer.parseInt(split[5]), split.length); + } + + public static BedrockData fromRawData(byte[] data) { + return fromString(new String(data)); + } + + public String toString() { + return version +'\0'+ username +'\0'+ xuid +'\0'+ deviceId +'\0'+ languageCode +'\0'+ inputMode; + } +} diff --git a/common/src/main/java/org/geysermc/floodgate/util/EncryptionUtil.java b/common/src/main/java/org/geysermc/floodgate/util/EncryptionUtil.java new file mode 100644 index 00000000..881d01ba --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/util/EncryptionUtil.java @@ -0,0 +1,76 @@ +package org.geysermc.floodgate.util; + +import javax.crypto.*; +import javax.crypto.spec.SecretKeySpec; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.*; +import java.security.spec.EncodedKeySpec; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.Base64; + +public class EncryptionUtil { + public static String encrypt(Key key, String data) throws IllegalBlockSizeException, + InvalidKeyException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException { + KeyGenerator generator = KeyGenerator.getInstance("AES"); + generator.init(128); + SecretKey secretKey = generator.generateKey(); + + Cipher cipher = Cipher.getInstance("AES"); + cipher.init(Cipher.ENCRYPT_MODE, secretKey); + byte[] encryptedText = cipher.doFinal(data.getBytes()); + + cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); + cipher.init(key instanceof PublicKey ? Cipher.PUBLIC_KEY : Cipher.PRIVATE_KEY, key); + return Base64.getEncoder().encodeToString(cipher.doFinal(secretKey.getEncoded())) + '\0' + + Base64.getEncoder().encodeToString(encryptedText); + } + + public static String encryptBedrockData(Key key, BedrockData data) throws IllegalBlockSizeException, + InvalidKeyException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException { + return encrypt(key, data.toString()); + } + + public static byte[] decrypt(Key key, String encryptedData) throws IllegalBlockSizeException, + InvalidKeyException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException { + String[] split = encryptedData.split("\0"); + if (split.length != 2) { + throw new IllegalArgumentException("Expected two arguments, got " + split.length); + } + + Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); + cipher.init(key instanceof PublicKey ? Cipher.PUBLIC_KEY : Cipher.PRIVATE_KEY, key); + byte[] decryptedKey = cipher.doFinal(Base64.getDecoder().decode(split[0])); + + SecretKey secretKey = new SecretKeySpec(decryptedKey, 0, decryptedKey.length, "AES"); + cipher = Cipher.getInstance("AES"); + cipher.init(Cipher.DECRYPT_MODE, secretKey); + return cipher.doFinal(Base64.getDecoder().decode(split[1])); + } + + public static BedrockData decryptBedrockData(Key key, String encryptedData) throws IllegalBlockSizeException, + InvalidKeyException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException { + return BedrockData.fromRawData(decrypt(key, encryptedData)); + } + + @SuppressWarnings("unchecked") + public static T getKeyFromFile(Path fileLocation, Class keyType) throws + IOException, InvalidKeySpecException, NoSuchAlgorithmException { + boolean isPublicKey = keyType == PublicKey.class; + if (!isPublicKey && keyType != PrivateKey.class) { + throw new RuntimeException("I can only read public and private keys!"); + } + + byte[] key = Files.readAllBytes(fileLocation); + + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + EncodedKeySpec keySpec = isPublicKey ? new X509EncodedKeySpec(key) : new PKCS8EncodedKeySpec(key); + return (T) (isPublicKey ? + keyFactory.generatePublic(keySpec) : + keyFactory.generatePrivate(keySpec) + ); + } +} diff --git a/connector/pom.xml b/connector/pom.xml index 5b4a2bcf..60ecfc55 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -22,12 +22,6 @@ 1.0-SNAPSHOT compile - - org.geysermc - floodgate-common - 1.0-SNAPSHOT - compile - com.fasterxml.jackson.dataformat jackson-dataformat-yaml From b7a746535b43ec23a5c008d7247dc22d5bf83216 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Sat, 30 Nov 2019 21:44:48 +0100 Subject: [PATCH 6/7] Little changes --- common/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/pom.xml b/common/pom.xml index d54730ff..9a972bf2 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -14,7 +14,7 @@ org.projectlombok lombok 1.18.4 - providec + provided com.github.steveice10 From 64b04330c333f31dd965f4ac9b56c813b844fe4c Mon Sep 17 00:00:00 2001 From: Tim203 Date: Tue, 17 Dec 2019 23:27:29 +0100 Subject: [PATCH 7/7] Added the player his IP to the transferable data --- .../geysermc/floodgate/util/BedrockData.java | 19 ++++++++++++++----- .../network/session/GeyserSession.java | 5 +++-- .../connector/world/chunk/BlockStorage.java | 2 +- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java b/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java index f4efa971..8beeb998 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java +++ b/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java @@ -6,30 +6,39 @@ import lombok.Getter; @AllArgsConstructor @Getter public class BedrockData { - public static final int EXPECTED_LENGTH = 6; + public static final int EXPECTED_LENGTH = 7; + public static final String FLOODGATE_IDENTIFIER = "Geyser-Floodgate"; + private String version; private String username; private String xuid; private int deviceId; private String languageCode; private int inputMode; + private String ip; private int dataLength; - public BedrockData(String version, String username, String xuid, int deviceId, String languageCode, int inputMode) { - this(version, username, xuid, deviceId, languageCode, inputMode, EXPECTED_LENGTH); + public BedrockData(String version, String username, String xuid, int deviceId, String languageCode, int inputMode, String ip) { + this(version, username, xuid, deviceId, languageCode, inputMode, ip, EXPECTED_LENGTH); } public static BedrockData fromString(String data) { String[] split = data.split("\0"); if (split.length != EXPECTED_LENGTH) return null; - return new BedrockData(split[0], split[1], split[2], Integer.parseInt(split[3]), split[4], Integer.parseInt(split[5]), split.length); + + return new BedrockData( + split[0], split[1], split[2], Integer.parseInt(split[3]), + split[4], Integer.parseInt(split[5]), split[6], split.length + ); } public static BedrockData fromRawData(byte[] data) { return fromString(new String(data)); } + @Override public String toString() { - return version +'\0'+ username +'\0'+ xuid +'\0'+ deviceId +'\0'+ languageCode +'\0'+ inputMode; + return version +'\0'+ username +'\0'+ xuid +'\0'+ deviceId +'\0'+ languageCode +'\0'+ + inputMode +'\0'+ ip; } } diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index 14654c6a..8fc6e5e2 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -221,7 +221,8 @@ public class GeyserSession implements Player { authenticationData.getXboxUUID(), clientData.getDeviceOS().ordinal(), clientData.getLanguageCode(), - clientData.getCurrentInputMode().ordinal() + clientData.getCurrentInputMode().ordinal(), + upstream.getSession().getAddress().getAddress().getHostAddress() )); } catch (Exception e) { connector.getLogger().error("Failed to encrypt message", e); @@ -230,7 +231,7 @@ public class GeyserSession implements Player { HandshakePacket handshakePacket = event.getPacket(); event.setPacket(new HandshakePacket( handshakePacket.getProtocolVersion(), - handshakePacket.getHostname() + '\0' + "Geyser-Floodgate" + '\0' + encrypted, + handshakePacket.getHostname() + '\0' + BedrockData.FLOODGATE_IDENTIFIER + '\0' + encrypted, handshakePacket.getPort(), handshakePacket.getIntent() )); diff --git a/connector/src/main/java/org/geysermc/connector/world/chunk/BlockStorage.java b/connector/src/main/java/org/geysermc/connector/world/chunk/BlockStorage.java index dbb967d4..089b2ec6 100644 --- a/connector/src/main/java/org/geysermc/connector/world/chunk/BlockStorage.java +++ b/connector/src/main/java/org/geysermc/connector/world/chunk/BlockStorage.java @@ -92,7 +92,7 @@ public class BlockStorage { } private int legacyIdFor(int index) { - int runtimeId = this.palette.get(index); + int runtimeId = this.palette.getInt(index); return GlobalBlockPalette.getLegacyId(runtimeId); }