From 7d645fbf16958079b44d13dc085faefa456de775 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Sat, 30 Nov 2019 13:34:45 +0100 Subject: [PATCH] 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 f003e0142..9b233578c 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 000000000..55d97c204 --- /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 708372e33..9259eefe8 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 60ecfc557..5b4a2bcf5 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 4abe763ff..3b7b1a701 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 b78fc2279..ba5e63b28 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 4990280b9..99fa1c5df 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 8f4544d39..aabc3d668 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 000000000..9e7345ce7 --- /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 b94f13cdc..5ccef71c1 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 b4c9d7f63..e42ca473e 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 f92d6d42f..029a4619e 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