Merge remote-tracking branch 'fork/floodgate-2.0' into floodgate-2.0

# Conflicts:
#	common/src/main/java/org/geysermc/floodgate/util/BedrockData.java
#	common/src/main/java/org/geysermc/floodgate/util/EncryptionUtil.java
#	connector/src/main/java/org/geysermc/connector/GeyserConnector.java
#	connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java
This commit is contained in:
Tim203 2020-09-12 15:57:16 +02:00
commit bb20b14e4c
No known key found for this signature in database
GPG key ID: 064EE9F5BF7C3EE8
10 changed files with 447 additions and 146 deletions

View file

@ -0,0 +1,95 @@
/*
* 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/Floodgate
*
*/
package org.geysermc.floodgate.crypto;
import org.geysermc.floodgate.util.InvalidHeaderException;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import java.nio.ByteBuffer;
import java.security.Key;
import java.security.SecureRandom;
public final class AesCipher implements FloodgateCipher {
private static final int IV_LENGTH = 12;
private static final int TAG_BIT_LENGTH = 128;
private static final String CIPHER_NAME = "AES/GCM/NoPadding";
private final SecureRandom secureRandom = new SecureRandom();
private SecretKey secretKey;
public void init(Key key) {
if (!"AES".equals(key.getAlgorithm())) {
throw new RuntimeException(
"Algorithm was expected to be AES, but got " + key.getAlgorithm()
);
}
secretKey = (SecretKey) key;
}
public byte[] encrypt(byte[] data) throws Exception {
Cipher cipher = Cipher.getInstance(CIPHER_NAME);
byte[] iv = new byte[IV_LENGTH];
secureRandom.nextBytes(iv);
GCMParameterSpec spec = new GCMParameterSpec(TAG_BIT_LENGTH, iv);
cipher.init(Cipher.ENCRYPT_MODE, secretKey, spec);
byte[] cipherText = cipher.doFinal(data);
return ByteBuffer.allocate(iv.length + cipherText.length + HEADER_LENGTH)
.put(IDENTIFIER).put(VERSION) // header
.put(iv)
.put(cipherText)
.array();
}
public byte[] decrypt(byte[] cipherTextWithIv) throws Exception {
HeaderResult pair = checkHeader(cipherTextWithIv);
if (pair.getVersion() != VERSION) {
throw new InvalidHeaderException(
"Expected version " + VERSION + ", got " + pair.getVersion()
);
}
Cipher cipher = Cipher.getInstance(CIPHER_NAME);
int bufferLength = cipherTextWithIv.length - HEADER_LENGTH;
ByteBuffer buffer = ByteBuffer.wrap(cipherTextWithIv, HEADER_LENGTH, bufferLength);
byte[] iv = new byte[IV_LENGTH];
buffer.get(iv);
byte[] cipherText = new byte[buffer.remaining()];
buffer.get(cipherText);
GCMParameterSpec spec = new GCMParameterSpec(TAG_BIT_LENGTH, iv);
cipher.init(Cipher.DECRYPT_MODE, secretKey, spec);
return cipher.doFinal(cipherText);
}
}

View file

@ -0,0 +1,56 @@
/*
* 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/Floodgate
*
*/
package org.geysermc.floodgate.crypto;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;
public final class AesKeyProducer implements KeyProducer {
public static int KEY_SIZE = 128;
@Override
public SecretKey produce() {
try {
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
keyGenerator.init(KEY_SIZE, SecureRandom.getInstanceStrong());
return keyGenerator.generateKey();
} catch (Exception exception) {
throw new RuntimeException(exception);
}
}
@Override
public SecretKey produceFrom(byte[] keyFileData) {
try {
return new SecretKeySpec(keyFileData, "AES");
} catch (Exception exception) {
throw new RuntimeException(exception);
}
}
}

View file

@ -0,0 +1,165 @@
/*
* 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/Floodgate
*
*/
package org.geysermc.floodgate.crypto;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.geysermc.floodgate.util.InvalidHeaderException;
import java.nio.charset.StandardCharsets;
import java.security.Key;
/**
* Responsible for both encrypting and decrypting data
*/
public interface FloodgateCipher {
byte[] IDENTIFIER = "Floodgate".getBytes(StandardCharsets.UTF_8);
byte VERSION = 2;
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
*
* @param key the key used to encrypt and decrypt data
*/
void init(Key key);
/**
* Encrypts the given data using the Key provided in {@link #init(Key)}
*
* @param data the data to encrypt
* @return the encrypted data
* @throws Exception when the encryption failed
*/
byte[] encrypt(byte[] data) throws Exception;
/**
* Encrypts data from a String.<br>
* This method internally calls {@link #encrypt(byte[])}
*
* @param data the data to encrypt
* @return the encrypted data
* @throws Exception when the encryption failed
*/
default byte[] encryptFromString(String data) throws Exception {
return encrypt(data.getBytes(StandardCharsets.UTF_8));
}
/**
* Decrypts the given data using the Key provided in {@link #init(Key)}
*
* @param data the data to decrypt
* @return the decrypted data
* @throws Exception when the decrypting failed
*/
byte[] decrypt(byte[] data) throws Exception;
/**
* Decrypts a byte[] and turn it into a String.<br>
* This method internally calls {@link #decrypt(byte[])}
* and converts the returned byte[] into a String.
*
* @param data the data to encrypt
* @return the decrypted data in a UTF-8 String
* @throws Exception when the decrypting failed
*/
default String decryptToString(byte[] data) throws Exception {
byte[] decrypted = decrypt(data);
if (decrypted == null) {
return null;
}
return new String(decrypted, StandardCharsets.UTF_8);
}
/**
* Decrypts a String.<br>
* This method internally calls {@link #decrypt(byte[])}
* by converting the UTF-8 String into a byte[]
*
* @param data the data to decrypt
* @return the decrypted data in a byte[]
* @throws Exception when the decrypting failed
*/
default byte[] decryptFromString(String data) throws Exception {
return decrypt(data.getBytes(StandardCharsets.UTF_8));
}
/**
* Checks if the header is valid and return a IntPair containing the header version
* and the index to start reading the actual encrypted data from.
*
* @param data the data to check
* @return IntPair. x = version number, y = the index to start reading from.
* @throws InvalidHeaderException when the header is invalid
*/
default HeaderResult checkHeader(byte[] data) throws InvalidHeaderException {
final int identifierLength = IDENTIFIER.length;
if (data.length <= HEADER_LENGTH) {
throw new InvalidHeaderException("Data length is smaller then header." +
"Needed " + HEADER_LENGTH + ", got " + data.length);
}
for (int i = 0; i < identifierLength; i++) {
if (IDENTIFIER[i] != data[i]) {
StringBuilder receivedIdentifier = new StringBuilder();
for (byte b : IDENTIFIER) {
receivedIdentifier.append(b);
}
throw new InvalidHeaderException(String.format(
"Expected identifier %s, got %s",
new String(IDENTIFIER, StandardCharsets.UTF_8),
receivedIdentifier.toString()
));
}
}
return new HeaderResult(data[identifierLength], HEADER_LENGTH);
}
static boolean hasHeader(String data) {
if (data.length() < IDENTIFIER.length) {
return false;
}
for (int i = 0; i < IDENTIFIER.length; i++) {
if (IDENTIFIER[i] != data.charAt(i)) {
return false;
}
}
return true;
}
@Data
@AllArgsConstructor
class HeaderResult {
private int version;
private int startIndex;
}
}

View file

@ -0,0 +1,41 @@
/*
* 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/Floodgate
*
*/
package org.geysermc.floodgate.crypto;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.Key;
public interface KeyProducer {
Key produce();
Key produceFrom(byte[] keyFileData);
default Key produceFrom(Path keyFileLocation) throws IOException {
return produceFrom(Files.readAllBytes(keyFileLocation));
}
}

View file

@ -34,11 +34,10 @@ import lombok.Getter;
* This class is only used internally, and you should look at FloodgatePlayer instead * This class is only used internally, and you should look at FloodgatePlayer instead
* (FloodgatePlayer is present in the common module in the Floodgate repo) * (FloodgatePlayer is present in the common module in the Floodgate repo)
*/ */
@AllArgsConstructor(access = AccessLevel.PRIVATE) @AllArgsConstructor(access = AccessLevel.PACKAGE)
@Getter @Getter
public final class BedrockData { public final class BedrockData {
public static final int EXPECTED_LENGTH = 9; public static final int EXPECTED_LENGTH = 9;
public static final String FLOODGATE_IDENTIFIER = "Geyser-Floodgate";
private final String version; private final String version;
private final String username; private final String username;
@ -65,9 +64,15 @@ public final class BedrockData {
this(version, username, xuid, deviceOs, languageCode, uiProfile, inputMode, ip, null, skin); this(version, username, xuid, deviceOs, languageCode, uiProfile, inputMode, ip, null, skin);
} }
public boolean hasPlayerLink() {
return linkedPlayer != null;
}
public static BedrockData fromString(String data, String skin) { public static BedrockData fromString(String data, String skin) {
String[] split = data.split("\0"); String[] split = data.split("\0");
if (split.length != EXPECTED_LENGTH) return emptyData(split.length); if (split.length != EXPECTED_LENGTH) {
return emptyData(split.length);
}
LinkedPlayer linkedPlayer = LinkedPlayer.fromString(split[8]); LinkedPlayer linkedPlayer = LinkedPlayer.fromString(split[8]);
// 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
@ -90,10 +95,6 @@ public final class BedrockData {
(linkedPlayer != null ? linkedPlayer.toString() : "null"); (linkedPlayer != null ? linkedPlayer.toString() : "null");
} }
public boolean hasPlayerLink() {
return linkedPlayer != null;
}
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, null);
} }

View file

@ -1,110 +0,0 @@
/*
* 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.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;
/**
* The class which contains all the encryption and decryption method used in Geyser and Floodgate
* (for Floodgate data). This is only used internally and doesn't serve a purpose for anything else
*/
public final 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, boolean includeSkin) throws IllegalBlockSizeException,
InvalidKeyException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException {
return encrypt(key, data.toString()) + (includeSkin ? data.getSkin() : "");
}
public static String encryptBedrockData(Key key, BedrockData data) throws IllegalBlockSizeException,
InvalidKeyException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException {
return encryptBedrockData(key, data, true);
}
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, String skin) throws IllegalBlockSizeException,
InvalidKeyException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException {
return BedrockData.fromRawData(decrypt(key, encryptedData), skin);
}
@SuppressWarnings("unchecked")
public static <T extends Key> T getKeyFromFile(Path fileLocation, Class<T> 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)
);
}
}

View file

@ -0,0 +1,41 @@
/*
* 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.util;
public class InvalidHeaderException extends Exception {
public InvalidHeaderException() {
super();
}
public InvalidHeaderException(String message) {
super(message);
}
public InvalidHeaderException(String message, Throwable cause) {
super(message, cause);
}
}

View file

@ -26,9 +26,7 @@
package org.geysermc.floodgate.util; package org.geysermc.floodgate.util;
import lombok.AccessLevel;
import lombok.Getter; import lombok.Getter;
import lombok.Setter;
import java.util.UUID; import java.util.UUID;
@ -50,7 +48,6 @@ public final class LinkedPlayer {
* If the LinkedPlayer is send from a different platform. * If the LinkedPlayer is send from a different platform.
* For example the LinkedPlayer is from Bungee but the data has been sent to the Bukkit server. * For example the LinkedPlayer is from Bungee but the data has been sent to the Bukkit server.
*/ */
@Setter(AccessLevel.PRIVATE)
private boolean fromDifferentPlatform = false; private boolean fromDifferentPlatform = false;
public LinkedPlayer(String javaUsername, UUID javaUniqueId, UUID bedrockId) { public LinkedPlayer(String javaUsername, UUID javaUniqueId, UUID bedrockId) {
@ -60,12 +57,15 @@ public final class LinkedPlayer {
} }
static LinkedPlayer fromString(String data) { static LinkedPlayer fromString(String data) {
if (data.length() == 4) return null; if (data.length() != 3) {
return null;
}
String[] split = data.split(";"); String[] split = data.split(";");
LinkedPlayer player = new LinkedPlayer( LinkedPlayer player = new LinkedPlayer(
split[0], UUID.fromString(split[1]), UUID.fromString(split[2]) split[0], UUID.fromString(split[1]), UUID.fromString(split[2])
); );
player.setFromDifferentPlatform(true); player.fromDifferentPlatform = true;
return player; return player;
} }

View file

@ -55,12 +55,16 @@ import org.geysermc.connector.network.translators.world.block.entity.BlockEntity
import org.geysermc.connector.utils.DimensionUtils; import org.geysermc.connector.utils.DimensionUtils;
import org.geysermc.connector.utils.LanguageUtils; 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.AesKeyProducer;
import org.geysermc.floodgate.crypto.FloodgateCipher;
import javax.naming.directory.Attribute; import javax.naming.directory.Attribute;
import javax.naming.directory.InitialDirContext; import javax.naming.directory.InitialDirContext;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.security.Key;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -87,6 +91,8 @@ public class GeyserConnector {
@Setter @Setter
private AuthType authType; private AuthType authType;
private FloodgateCipher cipher;
private boolean shuttingDown = false; private boolean shuttingDown = false;
private final ScheduledExecutorService generalThreadPool; private final ScheduledExecutorService generalThreadPool;
@ -171,6 +177,17 @@ public class GeyserConnector {
remoteServer = new RemoteServer(config.getRemote().getAddress(), remotePort); remoteServer = new RemoteServer(config.getRemote().getAddress(), remotePort);
authType = AuthType.getByName(config.getRemote().getAuthType()); authType = AuthType.getByName(config.getRemote().getAuthType());
if (authType == AuthType.FLOODGATE) {
try {
Key key = new AesKeyProducer().produceFrom(config.getFloodgateKeyFile());
cipher = new AesCipher();
cipher.init(key);
logger.info(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.loaded_key"));
} catch (Exception exception) {
logger.severe(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.bad_key"), exception);
}
}
if (config.isAboveBedrockNetherBuilding()) if (config.isAboveBedrockNetherBuilding())
DimensionUtils.changeBedrockNetherId(); // Apply End dimension ID workaround to Nether DimensionUtils.changeBedrockNetherId(); // Apply End dimension ID workaround to Nether

View file

@ -73,15 +73,19 @@ import org.geysermc.connector.network.translators.PacketTranslatorRegistry;
import org.geysermc.connector.network.translators.item.ItemRegistry; import org.geysermc.connector.network.translators.item.ItemRegistry;
import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.network.translators.world.block.BlockTranslator;
import org.geysermc.connector.utils.*; import org.geysermc.connector.utils.*;
import org.geysermc.floodgate.crypto.FloodgateCipher;
import org.geysermc.floodgate.util.BedrockData; import org.geysermc.floodgate.util.BedrockData;
import org.geysermc.floodgate.util.EncryptionUtil;
import java.io.IOException;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.PublicKey; import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException; import java.security.spec.InvalidKeySpecException;
import java.util.*; import java.util.*;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
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
@ -336,24 +340,6 @@ public class GeyserSession implements CommandSender {
} }
boolean floodgate = connector.getAuthType() == AuthType.FLOODGATE; boolean floodgate = connector.getAuthType() == AuthType.FLOODGATE;
final PublicKey publicKey;
if (floodgate) {
PublicKey key = null;
try {
key = EncryptionUtil.getKeyFromFile(
connector.getConfig().getFloodgateKeyFile(),
PublicKey.class
);
} catch (IOException | InvalidKeySpecException | NoSuchAlgorithmException e) {
connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.bad_key"), e);
}
publicKey = key;
} else publicKey = null;
if (publicKey != null) {
connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.loaded_key"));
}
downstream = new Client(remoteServer.getAddress(), remoteServer.getPort(), protocol, new TcpSessionFactory()); downstream = new Client(remoteServer.getAddress(), remoteServer.getPort(), protocol, new TcpSessionFactory());
downstream.getSession().addListener(new SessionAdapter() { downstream.getSession().addListener(new SessionAdapter() {
@ -361,9 +347,11 @@ public class GeyserSession implements CommandSender {
public void packetSending(PacketSendingEvent event) { public void packetSending(PacketSendingEvent event) {
//todo move this somewhere else //todo move this somewhere else
if (event.getPacket() instanceof HandshakePacket && floodgate) { if (event.getPacket() instanceof HandshakePacket && floodgate) {
String encrypted = ""; byte[] encryptedData;
try { try {
encrypted = EncryptionUtil.encryptBedrockData(publicKey, new BedrockData( FloodgateCipher cipher = connector.getCipher();
encryptedData = cipher.encryptFromString(new BedrockData(
clientData.getGameVersion(), clientData.getGameVersion(),
authData.getName(), authData.getName(),
authData.getXboxUUID(), authData.getXboxUUID(),
@ -373,15 +361,22 @@ public class GeyserSession implements CommandSender {
clientData.getCurrentInputMode().ordinal(), clientData.getCurrentInputMode().ordinal(),
upstream.getSession().getAddress().getAddress().getHostAddress(), upstream.getSession().getAddress().getAddress().getHostAddress(),
clientData.getImage("Skin") clientData.getImage("Skin")
)); ).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);
disconnect(LanguageUtils.getPlayerLocaleString("geyser.auth.floodgate.encryption_fail", getClientData().getLanguageCode()));
return;
} }
String encrypted = new String(
Base64.getEncoder().encode(encryptedData),
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' + BedrockData.FLOODGATE_IDENTIFIER + '\0' + encrypted, handshakePacket.getHostname() + '\0' + encrypted,
handshakePacket.getPort(), handshakePacket.getPort(),
handshakePacket.getIntent() handshakePacket.getIntent()
)); ));