3
0
Mirror von https://github.com/GeyserMC/Geyser.git synchronisiert 2024-12-26 16:12:46 +01:00

Added RawSkins, Toppings and renamed the Floodgate plugin name

Dieser Commit ist enthalten in:
Tim203 2020-09-19 14:21:54 +02:00
Ursprung bb20b14e4c
Commit 7fbc401dfa
Es konnte kein GPG-Schlüssel zu dieser Signatur gefunden werden
GPG-Schlüssel-ID: 064EE9F5BF7C3EE8
16 geänderte Dateien mit 261 neuen und 104 gelöschten Zeilen

Datei anzeigen

@ -44,7 +44,7 @@ public class GeyserBungeeConfiguration extends GeyserJacksonConfiguration {
private Path floodgateKey; private Path floodgateKey;
public void loadFloodgate(GeyserBungeePlugin plugin, Configuration configuration) { public void loadFloodgate(GeyserBungeePlugin plugin, Configuration configuration) {
Plugin floodgate = plugin.getProxy().getPluginManager().getPlugin("floodgate-bungee"); Plugin floodgate = plugin.getProxy().getPluginManager().getPlugin("floodgate");
floodgateKey = FloodgateKeyLoader.getKey(plugin.getGeyserLogger(), this, Paths.get(plugin.getDataFolder().toString(), configuration.getString("floodgate-key-file"), "public-key.pem"), floodgate, floodgate != null ? floodgate.getDataFolder().toPath() : null); floodgateKey = FloodgateKeyLoader.getKey(plugin.getGeyserLogger(), this, Paths.get(plugin.getDataFolder().toString(), configuration.getString("floodgate-key-file"), "public-key.pem"), floodgate, floodgate != null ? floodgate.getDataFolder().toPath() : null);
} }

Datei anzeigen

@ -94,7 +94,7 @@ public class GeyserBungeePlugin extends Plugin implements GeyserBootstrap {
this.geyserLogger = new GeyserBungeeLogger(getLogger(), geyserConfig.isDebugMode()); this.geyserLogger = new GeyserBungeeLogger(getLogger(), geyserConfig.isDebugMode());
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
if (geyserConfig.getRemote().getAuthType().equals("floodgate") && getProxy().getPluginManager().getPlugin("floodgate-bungee") == null) { if (geyserConfig.getRemote().getAuthType().equals("floodgate") && getProxy().getPluginManager().getPlugin("floodgate") == null) {
geyserLogger.severe(LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.not_installed") + " " + LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.disabling")); geyserLogger.severe(LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.not_installed") + " " + LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.disabling"));
return; return;
} }

Datei anzeigen

@ -48,7 +48,7 @@ public class GeyserSpigotConfiguration extends GeyserJacksonConfiguration {
private Path floodgateKey; private Path floodgateKey;
public void loadFloodgate(GeyserSpigotPlugin plugin) { public void loadFloodgate(GeyserSpigotPlugin plugin) {
Plugin floodgate = Bukkit.getPluginManager().getPlugin("floodgate-spigot"); Plugin floodgate = Bukkit.getPluginManager().getPlugin("floodgate");
floodgateKey = FloodgateKeyLoader.getKey(plugin.getGeyserLogger(), this, Paths.get(plugin.getDataFolder().toString(), plugin.getConfig().getString("floodgate-key-file", "public-key.pem")), floodgate, floodgate != null ? floodgate.getDataFolder().toPath() : null); floodgateKey = FloodgateKeyLoader.getKey(plugin.getGeyserLogger(), this, Paths.get(plugin.getDataFolder().toString(), plugin.getConfig().getString("floodgate-key-file", "public-key.pem")), floodgate, floodgate != null ? floodgate.getDataFolder().toPath() : null);
} }

Datei anzeigen

@ -93,7 +93,7 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap {
this.geyserLogger = new GeyserSpigotLogger(getLogger(), geyserConfig.isDebugMode()); this.geyserLogger = new GeyserSpigotLogger(getLogger(), geyserConfig.isDebugMode());
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
if (geyserConfig.getRemote().getAuthType().equals("floodgate") && Bukkit.getPluginManager().getPlugin("floodgate-spigot") == null) { if (geyserConfig.getRemote().getAuthType().equals("floodgate") && Bukkit.getPluginManager().getPlugin("floodgate") == null) {
geyserLogger.severe(LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.not_installed") + " " + LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.disabling")); geyserLogger.severe(LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.not_installed") + " " + LanguageUtils.getLocaleStringLog("geyser.bootstrap.floodgate.disabling"));
this.getPluginLoader().disablePlugin(this); this.getPluginLoader().disablePlugin(this);
return; return;

Datei anzeigen

@ -26,7 +26,7 @@
package org.geysermc.floodgate.crypto; package org.geysermc.floodgate.crypto;
import org.geysermc.floodgate.util.InvalidHeaderException; import lombok.RequiredArgsConstructor;
import javax.crypto.Cipher; import javax.crypto.Cipher;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
@ -35,12 +35,14 @@ import java.nio.ByteBuffer;
import java.security.Key; import java.security.Key;
import java.security.SecureRandom; import java.security.SecureRandom;
@RequiredArgsConstructor
public final class AesCipher implements FloodgateCipher { public final class AesCipher implements FloodgateCipher {
private static final int IV_LENGTH = 12; public static final int IV_LENGTH = 12;
private static final int TAG_BIT_LENGTH = 128; private static final int TAG_BIT_LENGTH = 128;
private static final String CIPHER_NAME = "AES/GCM/NoPadding"; private static final String CIPHER_NAME = "AES/GCM/NoPadding";
private final SecureRandom secureRandom = new SecureRandom(); private final SecureRandom secureRandom = new SecureRandom();
private final Topping topping;
private SecretKey secretKey; private SecretKey secretKey;
public void init(Key key) { public void init(Key key) {
@ -62,32 +64,57 @@ public final class AesCipher implements FloodgateCipher {
cipher.init(Cipher.ENCRYPT_MODE, secretKey, spec); cipher.init(Cipher.ENCRYPT_MODE, secretKey, spec);
byte[] cipherText = cipher.doFinal(data); byte[] cipherText = cipher.doFinal(data);
return ByteBuffer.allocate(iv.length + cipherText.length + HEADER_LENGTH) if (topping != null) {
.put(IDENTIFIER).put(VERSION) // header iv = topping.encode(iv);
cipherText = topping.encode(cipherText);
}
return ByteBuffer.allocate(iv.length + cipherText.length + HEADER_LENGTH + 1)
.put(IDENTIFIER) // header
.put(iv) .put(iv)
.put((byte) 0x21)
.put(cipherText) .put(cipherText)
.array(); .array();
} }
public byte[] decrypt(byte[] cipherTextWithIv) throws Exception { public byte[] decrypt(byte[] cipherTextWithIv) throws Exception {
HeaderResult pair = checkHeader(cipherTextWithIv); checkHeader(cipherTextWithIv);
if (pair.getVersion() != VERSION) {
throw new InvalidHeaderException(
"Expected version " + VERSION + ", got " + pair.getVersion()
);
}
Cipher cipher = Cipher.getInstance(CIPHER_NAME); Cipher cipher = Cipher.getInstance(CIPHER_NAME);
int bufferLength = cipherTextWithIv.length - HEADER_LENGTH; int bufferLength = cipherTextWithIv.length - HEADER_LENGTH;
ByteBuffer buffer = ByteBuffer.wrap(cipherTextWithIv, HEADER_LENGTH, bufferLength); ByteBuffer buffer = ByteBuffer.wrap(cipherTextWithIv, HEADER_LENGTH, bufferLength);
byte[] iv = new byte[IV_LENGTH]; int ivLength = IV_LENGTH;
if (topping != null) {
int mark = buffer.position();
// we need the first index, the second is for the optional RawSkin
boolean found = false;
while (buffer.hasRemaining() && !found) {
if (buffer.get() == 0x21) {
found = true;
}
}
ivLength = buffer.position() - mark - 1; // don't include the splitter itself
buffer.position(mark); // reset to the pre-while index
}
byte[] iv = new byte[ivLength];
buffer.get(iv); buffer.get(iv);
buffer.position(buffer.position() + 1); // skip splitter
byte[] cipherText = new byte[buffer.remaining()]; byte[] cipherText = new byte[buffer.remaining()];
buffer.get(cipherText); buffer.get(cipherText);
if (topping != null) {
iv = topping.decode(iv);
cipherText = topping.decode(cipherText);
}
GCMParameterSpec spec = new GCMParameterSpec(TAG_BIT_LENGTH, iv); GCMParameterSpec spec = new GCMParameterSpec(TAG_BIT_LENGTH, iv);
cipher.init(Cipher.DECRYPT_MODE, secretKey, spec); cipher.init(Cipher.DECRYPT_MODE, secretKey, spec);
return cipher.doFinal(cipherText); return cipher.doFinal(cipherText);

Datei anzeigen

@ -0,0 +1,40 @@
/*
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.floodgate.crypto;
import java.util.Base64;
public final class Base64Topping implements Topping {
@Override
public byte[] encode(byte[] data) {
return Base64.getEncoder().encode(data);
}
@Override
public byte[] decode(byte[] data) {
return Base64.getDecoder().decode(data);
}
}

Datei anzeigen

@ -28,7 +28,7 @@ package org.geysermc.floodgate.crypto;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import org.geysermc.floodgate.util.InvalidHeaderException; import org.geysermc.floodgate.util.InvalidFormatException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.security.Key; import java.security.Key;
@ -38,9 +38,7 @@ import java.security.Key;
*/ */
public interface FloodgateCipher { public interface FloodgateCipher {
byte[] IDENTIFIER = "Floodgate".getBytes(StandardCharsets.UTF_8); byte[] IDENTIFIER = "Floodgate".getBytes(StandardCharsets.UTF_8);
byte VERSION = 2; int HEADER_LENGTH = IDENTIFIER.length;
int HEADER_LENGTH = IDENTIFIER.length + 1; // one byte for version
/** /**
* Initializes the instance by giving it the key it needs to encrypt or decrypt data * Initializes the instance by giving it the key it needs to encrypt or decrypt data
@ -110,19 +108,20 @@ public interface FloodgateCipher {
} }
/** /**
* Checks if the header is valid and return a IntPair containing the header version * Checks if the header is valid.
* and the index to start reading the actual encrypted data from. * This method will throw an InvalidFormatException when the header is invalid.
* *
* @param data the data to check * @param data the data to check
* @return IntPair. x = version number, y = the index to start reading from. * @throws InvalidFormatException when the header is invalid
* @throws InvalidHeaderException when the header is invalid
*/ */
default HeaderResult checkHeader(byte[] data) throws InvalidHeaderException { default void checkHeader(byte[] data) throws InvalidFormatException {
final int identifierLength = IDENTIFIER.length; final int identifierLength = IDENTIFIER.length;
if (data.length <= HEADER_LENGTH) { if (data.length <= HEADER_LENGTH) {
throw new InvalidHeaderException("Data length is smaller then header." + throw new InvalidFormatException("Data length is smaller then header." +
"Needed " + HEADER_LENGTH + ", got " + data.length); "Needed " + HEADER_LENGTH + ", got " + data.length,
true
);
} }
for (int i = 0; i < identifierLength; i++) { for (int i = 0; i < identifierLength; i++) {
@ -132,15 +131,15 @@ public interface FloodgateCipher {
receivedIdentifier.append(b); receivedIdentifier.append(b);
} }
throw new InvalidHeaderException(String.format( throw new InvalidFormatException(
"Expected identifier %s, got %s", String.format("Expected identifier %s, got %s",
new String(IDENTIFIER, StandardCharsets.UTF_8), new String(IDENTIFIER, StandardCharsets.UTF_8),
receivedIdentifier.toString() receivedIdentifier.toString()
)); ),
true
);
} }
} }
return new HeaderResult(data[identifierLength], HEADER_LENGTH);
} }
static boolean hasHeader(String data) { static boolean hasHeader(String data) {

Datei anzeigen

@ -0,0 +1,31 @@
/*
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.floodgate.crypto;
public interface Topping {
byte[] encode(byte[] data);
byte[] decode(byte[] data);
}

Datei anzeigen

@ -50,25 +50,23 @@ public final class BedrockData {
private final LinkedPlayer linkedPlayer; private final LinkedPlayer linkedPlayer;
private final int dataLength; private final int dataLength;
private RawSkin skin;
public BedrockData(String version, String username, String xuid, int deviceOs, public BedrockData(String version, String username, String xuid, int deviceOs,
String languageCode, int uiProfile, int inputMode, String ip, String languageCode, int uiProfile, int inputMode, String ip,
LinkedPlayer linkedPlayer, RawSkin skin) { LinkedPlayer linkedPlayer) {
this(version, username, xuid, deviceOs, languageCode, this(version, username, xuid, deviceOs, languageCode,
inputMode, uiProfile, ip, linkedPlayer, EXPECTED_LENGTH, skin); inputMode, uiProfile, ip, linkedPlayer, EXPECTED_LENGTH);
} }
public BedrockData(String version, String username, String xuid, int deviceOs, public BedrockData(String version, String username, String xuid, int deviceOs,
String languageCode, int uiProfile, int inputMode, String ip, RawSkin skin) { String languageCode, int uiProfile, int inputMode, String ip) {
this(version, username, xuid, deviceOs, languageCode, uiProfile, inputMode, ip, null, skin); this(version, username, xuid, deviceOs, languageCode, uiProfile, inputMode, ip, null);
} }
public boolean hasPlayerLink() { public boolean hasPlayerLink() {
return linkedPlayer != null; return linkedPlayer != null;
} }
public static BedrockData fromString(String data, String skin) { public static BedrockData fromString(String data) {
String[] split = data.split("\0"); String[] split = data.split("\0");
if (split.length != EXPECTED_LENGTH) { if (split.length != EXPECTED_LENGTH) {
return emptyData(split.length); return emptyData(split.length);
@ -79,14 +77,10 @@ public final class BedrockData {
return new BedrockData( return new BedrockData(
split[0], split[1], split[2], Integer.parseInt(split[3]), split[4], split[0], split[1], split[2], Integer.parseInt(split[3]), split[4],
Integer.parseInt(split[5]), Integer.parseInt(split[6]), split[7], Integer.parseInt(split[5]), Integer.parseInt(split[6]), split[7],
linkedPlayer, split.length, RawSkin.parse(skin) linkedPlayer, split.length
); );
} }
public static BedrockData fromRawData(byte[] data, String skin) {
return fromString(new String(data), skin);
}
@Override @Override
public String toString() { public String toString() {
// The format is the same as the order of the fields in this class // The format is the same as the order of the fields in this class
@ -96,6 +90,6 @@ public final class BedrockData {
} }
private static BedrockData emptyData(int dataLength) { private static BedrockData emptyData(int dataLength) {
return new BedrockData(null, null, null, -1, null, -1, -1, null, null, dataLength, null); return new BedrockData(null, null, null, -1, null, -1, -1, null, null, dataLength);
} }
} }

Datei anzeigen

@ -26,16 +26,26 @@
package org.geysermc.floodgate.util; package org.geysermc.floodgate.util;
public class InvalidHeaderException extends Exception { import lombok.Getter;
public InvalidHeaderException() {
@Getter
public class InvalidFormatException extends Exception {
private boolean header = false;
public InvalidFormatException() {
super(); super();
} }
public InvalidHeaderException(String message) { public InvalidFormatException(String message) {
super(message); super(message);
} }
public InvalidHeaderException(String message, Throwable cause) { public InvalidFormatException(String message, boolean header) {
super(message);
this.header = header;
}
public InvalidFormatException(String message, Throwable cause) {
super(message, cause); super(message, cause);
} }
} }

Datei anzeigen

@ -26,31 +26,62 @@
package org.geysermc.floodgate.util; package org.geysermc.floodgate.util;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.ToString;
import java.nio.charset.StandardCharsets; import java.nio.ByteBuffer;
import java.util.Base64;
@AllArgsConstructor @AllArgsConstructor
public class RawSkin { @ToString
public final class RawSkin {
public int width; public int width;
public int height; public int height;
public byte[] data; public byte[] data;
private RawSkin() {} private RawSkin() {}
public static RawSkin parse(String data) { public static RawSkin decode(byte[] data) throws InvalidFormatException {
if (data == null) return null; if (data == null) {
String[] split = data.split(":"); return null;
if (split.length != 3) return null; }
int maxEncodedLength = 4 * (((64 * 64 * 4 + 8) + 2) / 3);
// if the RawSkin is longer then the max Java Edition skin length
if (data.length > maxEncodedLength) {
throw new InvalidFormatException(
"Encoded data cannot be longer then " + maxEncodedLength + " bytes!"
);
}
// if the encoded data doesn't even contain the width and height (8 bytes, 2 ints)
if (data.length < 4 * ((8 + 2) / 3)) {
throw new InvalidFormatException("Encoded data must be at least 12 bytes long!");
}
data = Base64.getDecoder().decode(data);
ByteBuffer buffer = ByteBuffer.wrap(data);
RawSkin skin = new RawSkin(); RawSkin skin = new RawSkin();
skin.width = Integer.parseInt(split[0]); skin.width = buffer.getInt();
skin.height = Integer.parseInt(split[1]); skin.height = buffer.getInt();
skin.data = split[2].getBytes(StandardCharsets.UTF_8); if (buffer.remaining() != (skin.width * skin.height * 4)) {
throw new InvalidFormatException(String.format(
"Expected skin length to be %s, got %s",
(skin.width * skin.height * 4), buffer.remaining()
));
}
skin.data = new byte[buffer.remaining()];
buffer.get(skin.data);
return skin; return skin;
} }
@Override public byte[] encode() {
public String toString() { // 2 x int = 8 bytes
return Integer.toString(width) + ':' + height + ':' + new String(data); ByteBuffer buffer = ByteBuffer.allocate(8 + data.length);
buffer.putInt(width);
buffer.putInt(height);
buffer.put(data);
return Base64.getEncoder().encode(buffer.array());
} }
} }

Datei anzeigen

@ -57,6 +57,7 @@ import org.geysermc.connector.utils.LanguageUtils;
import org.geysermc.connector.utils.LocaleUtils; import org.geysermc.connector.utils.LocaleUtils;
import org.geysermc.floodgate.crypto.AesCipher; import org.geysermc.floodgate.crypto.AesCipher;
import org.geysermc.floodgate.crypto.AesKeyProducer; import org.geysermc.floodgate.crypto.AesKeyProducer;
import org.geysermc.floodgate.crypto.Base64Topping;
import org.geysermc.floodgate.crypto.FloodgateCipher; import org.geysermc.floodgate.crypto.FloodgateCipher;
import javax.naming.directory.Attribute; import javax.naming.directory.Attribute;
@ -180,7 +181,7 @@ public class GeyserConnector {
if (authType == AuthType.FLOODGATE) { if (authType == AuthType.FLOODGATE) {
try { try {
Key key = new AesKeyProducer().produceFrom(config.getFloodgateKeyFile()); Key key = new AesKeyProducer().produceFrom(config.getFloodgateKeyFile());
cipher = new AesCipher(); cipher = new AesCipher(new Base64Topping());
cipher.init(key); cipher.init(key);
logger.info(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.loaded_key")); logger.info(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.loaded_key"));
} catch (Exception exception) { } catch (Exception exception) {

Datei anzeigen

@ -77,15 +77,8 @@ import org.geysermc.floodgate.crypto.FloodgateCipher;
import org.geysermc.floodgate.util.BedrockData; import org.geysermc.floodgate.util.BedrockData;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.util.*;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.*;
import java.util.Base64;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
@Getter @Getter
@ -359,8 +352,7 @@ public class GeyserSession implements CommandSender {
clientData.getLanguageCode(), clientData.getLanguageCode(),
clientData.getUiProfile().ordinal(), clientData.getUiProfile().ordinal(),
clientData.getCurrentInputMode().ordinal(), clientData.getCurrentInputMode().ordinal(),
upstream.getSession().getAddress().getAddress().getHostAddress(), upstream.getSession().getAddress().getAddress().getHostAddress()
clientData.getImage("Skin")
).toString()); ).toString());
} catch (Exception e) { } catch (Exception e) {
connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.encrypt_fail"), e); connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.encrypt_fail"), e);
@ -368,15 +360,18 @@ public class GeyserSession implements CommandSender {
return; return;
} }
String encrypted = new String( byte[] rawSkin = clientData.getAndTransformImage("Skin").encode();
Base64.getEncoder().encode(encryptedData), byte[] finalData = new byte[encryptedData.length + rawSkin.length + 1];
StandardCharsets.UTF_8 System.arraycopy(encryptedData, 0, finalData, 0, encryptedData.length);
); finalData[encryptedData.length] = 0x21; // splitter
System.arraycopy(rawSkin, 0, finalData, encryptedData.length + 1, rawSkin.length);
String finalDataString = new String(finalData, StandardCharsets.UTF_8);
HandshakePacket handshakePacket = event.getPacket(); HandshakePacket handshakePacket = event.getPacket();
event.setPacket(new HandshakePacket( event.setPacket(new HandshakePacket(
handshakePacket.getProtocolVersion(), handshakePacket.getProtocolVersion(),
handshakePacket.getHostname() + '\0' + encrypted, handshakePacket.getHostname() + '\0' + finalDataString,
handshakePacket.getPort(), handshakePacket.getPort(),
handshakePacket.getIntent() handshakePacket.getIntent()
)); ));

Datei anzeigen

@ -28,13 +28,15 @@ package org.geysermc.connector.network.session.auth;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.Getter; import lombok.Getter;
import net.minidev.json.JSONObject; import org.geysermc.connector.utils.SkinProvider;
import org.geysermc.floodgate.util.DeviceOs; import org.geysermc.floodgate.util.DeviceOs;
import org.geysermc.floodgate.util.InputMode; import org.geysermc.floodgate.util.InputMode;
import org.geysermc.floodgate.util.RawSkin; import org.geysermc.floodgate.util.RawSkin;
import org.geysermc.floodgate.util.UiProfile; import org.geysermc.floodgate.util.UiProfile;
import java.awt.image.BufferedImage;
import java.util.Base64; import java.util.Base64;
import java.util.UUID; import java.util.UUID;
@ -42,7 +44,7 @@ import java.util.UUID;
@Getter @Getter
public final class BedrockClientData { public final class BedrockClientData {
@JsonIgnore @JsonIgnore
private JSONObject jsonData; private JsonNode jsonData;
@JsonProperty(value = "GameVersion") @JsonProperty(value = "GameVersion")
private String gameVersion; private String gameVersion;
@ -112,31 +114,17 @@ public final class BedrockClientData {
@JsonProperty(value = "ThirdPartyNameOnly") @JsonProperty(value = "ThirdPartyNameOnly")
private boolean thirdPartyNameOnly; private boolean thirdPartyNameOnly;
public void setJsonData(JSONObject data) { public void setJsonData(JsonNode data) {
if (this.jsonData != null && data != null) { if (this.jsonData == null && data != null) {
this.jsonData = data; this.jsonData = data;
} }
} }
/**
* Taken from https://github.com/NukkitX/Nukkit/blob/master/src/main/java/cn/nukkit/network/protocol/LoginPacket.java<br>
* Internally only used for Skins, but can be used for Capes too
*/
public RawSkin getImage(String name) {
if (jsonData == null || !jsonData.containsKey(name + "Data")) return null;
byte[] image = Base64.getDecoder().decode(jsonData.getAsString(name + "Data"));
if (jsonData.containsKey(name + "ImageWidth") && jsonData.containsKey(name + "ImageHeight")) {
return new RawSkin(
(int) jsonData.getAsNumber(name + "ImageWidth"),
(int) jsonData.get(name + "ImageHeight"),
image
);
}
return getLegacyImage(image);
}
private static RawSkin getLegacyImage(byte[] imageData) { private static RawSkin getLegacyImage(byte[] imageData) {
if (imageData == null) return null; if (imageData == null) {
return null;
}
// width * height * 4 (rgba) // width * height * 4 (rgba)
switch (imageData.length) { switch (imageData.length) {
case 8192: case 8192:
@ -151,4 +139,44 @@ public final class BedrockClientData {
throw new IllegalArgumentException("Unknown legacy skin size"); throw new IllegalArgumentException("Unknown legacy skin size");
} }
} }
/**
* Taken from https://github.com/NukkitX/Nukkit/blob/master/src/main/java/cn/nukkit/network/protocol/LoginPacket.java<br>
* Internally only used for Skins, but can be used for Capes too
*/
public RawSkin getImage(String name) {
System.out.println(jsonData.toString());
if (jsonData == null || !jsonData.has(name + "Data")) {
return null;
}
byte[] image = Base64.getDecoder().decode(jsonData.get(name + "Data").asText());
if (jsonData.has(name + "ImageWidth") && jsonData.has(name + "ImageHeight")) {
return new RawSkin(
jsonData.get(name + "ImageWidth").asInt(),
jsonData.get(name + "ImageHeight").asInt(),
image
);
}
return getLegacyImage(image);
}
public RawSkin getAndTransformImage(String name) {
RawSkin skin = getImage(name);
if (skin != null && (skin.width > 64 || skin.height > 64)) {
BufferedImage scaledImage = SkinProvider.imageDataToBufferedImage(skin.data, skin.width, skin.height);
int max = Math.max(skin.width, skin.height);
while (max > 64) {
max /= 2;
scaledImage = SkinProvider.scale(scaledImage);
}
byte[] skinData = SkinProvider.bufferedImageToImageData(scaledImage);
skin.width = scaledImage.getWidth();
skin.height = scaledImage.getHeight();
skin.data = skinData;
}
return skin;
}
} }

Datei anzeigen

@ -131,8 +131,9 @@ public class LoginEncryptionUtils {
JWSObject clientJwt = JWSObject.parse(clientData); JWSObject clientJwt = JWSObject.parse(clientData);
EncryptionUtils.verifyJwt(clientJwt, identityPublicKey); EncryptionUtils.verifyJwt(clientJwt, identityPublicKey);
BedrockClientData data = JSON_MAPPER.convertValue(JSON_MAPPER.readTree(clientJwt.getPayload().toBytes()), BedrockClientData.class); JsonNode clientDataJson = JSON_MAPPER.readTree(clientJwt.getPayload().toBytes());
data.setJsonData(clientJwt.getPayload().toJSONObject()); BedrockClientData data = JSON_MAPPER.convertValue(clientDataJson, BedrockClientData.class);
data.setJsonData(clientDataJson);
session.setClientData(data); session.setClientData(data);
if (EncryptionUtils.canUseEncryption()) { if (EncryptionUtils.canUseEncryption()) {

Datei anzeigen

@ -460,7 +460,7 @@ public class SkinProvider {
return null; return null;
} }
private static BufferedImage scale(BufferedImage bufferedImage) { public static BufferedImage scale(BufferedImage bufferedImage) {
BufferedImage resized = new BufferedImage(bufferedImage.getWidth() / 2, bufferedImage.getHeight() / 2, BufferedImage.TYPE_INT_ARGB); BufferedImage resized = new BufferedImage(bufferedImage.getWidth() / 2, bufferedImage.getHeight() / 2, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = resized.createGraphics(); Graphics2D g2 = resized.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);