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 ###
config.yml
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
*/
void shutdown();
/**
* The auth type for the remote server
*/
AuthType getAuthType();
}

View File

@ -22,6 +22,12 @@
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.geysermc</groupId>
<artifactId>floodgate-common</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<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.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<Object, GeyserSession> players = new HashMap<>();
private static GeyserConnector instance;
private final Map<Object, GeyserSession> 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;

View File

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

View File

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

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

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

View File

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

View File

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