diff --git a/common/src/main/java/org/geysermc/floodgate/crypto/AesCipher.java b/common/src/main/java/org/geysermc/floodgate/crypto/AesCipher.java
new file mode 100644
index 000000000..3e6fef01a
--- /dev/null
+++ b/common/src/main/java/org/geysermc/floodgate/crypto/AesCipher.java
@@ -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);
+ }
+}
diff --git a/common/src/main/java/org/geysermc/floodgate/crypto/AesKeyProducer.java b/common/src/main/java/org/geysermc/floodgate/crypto/AesKeyProducer.java
new file mode 100644
index 000000000..5217b4cf7
--- /dev/null
+++ b/common/src/main/java/org/geysermc/floodgate/crypto/AesKeyProducer.java
@@ -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);
+ }
+ }
+}
diff --git a/common/src/main/java/org/geysermc/floodgate/crypto/FloodgateCipher.java b/common/src/main/java/org/geysermc/floodgate/crypto/FloodgateCipher.java
new file mode 100644
index 000000000..23e57fb64
--- /dev/null
+++ b/common/src/main/java/org/geysermc/floodgate/crypto/FloodgateCipher.java
@@ -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.
+ * 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.
+ * 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.
+ * 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;
+ }
+}
diff --git a/common/src/main/java/org/geysermc/floodgate/crypto/KeyProducer.java b/common/src/main/java/org/geysermc/floodgate/crypto/KeyProducer.java
new file mode 100644
index 000000000..fc2ac512d
--- /dev/null
+++ b/common/src/main/java/org/geysermc/floodgate/crypto/KeyProducer.java
@@ -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));
+ }
+}
diff --git a/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java b/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java
index 44544291d..f730d75bd 100644
--- a/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java
+++ b/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java
@@ -9,11 +9,10 @@ import lombok.Getter;
* This class is only used internally, and you should look at FloodgatePlayer instead
* (FloodgatePlayer is present in the common module in the Floodgate repo)
*/
-@AllArgsConstructor(access = AccessLevel.PRIVATE)
+@AllArgsConstructor(access = AccessLevel.PACKAGE)
@Getter
public final class BedrockData {
public static final int EXPECTED_LENGTH = 9;
- public static final String FLOODGATE_IDENTIFIER = "Geyser-Floodgate";
private final String version;
private final String username;
@@ -38,9 +37,15 @@ public final class BedrockData {
this(version, username, xuid, deviceOs, languageCode, uiProfile, inputMode, ip, null);
}
+ public boolean hasPlayerLink() {
+ return linkedPlayer != null;
+ }
+
public static BedrockData fromString(String data) {
String[] split = data.split("\0");
- if (split.length != EXPECTED_LENGTH) return emptyData(split.length);
+ if (split.length != BedrockData.EXPECTED_LENGTH) {
+ return emptyData(split.length);
+ }
LinkedPlayer linkedPlayer = LinkedPlayer.fromString(split[8]);
// The format is the same as the order of the fields in this class
@@ -51,10 +56,6 @@ public final class BedrockData {
);
}
- public static BedrockData fromRawData(byte[] data) {
- return fromString(new String(data));
- }
-
@Override
public String toString() {
// The format is the same as the order of the fields in this class
@@ -63,10 +64,6 @@ public final class BedrockData {
(linkedPlayer != null ? linkedPlayer.toString() : "null");
}
- public boolean hasPlayerLink() {
- return linkedPlayer != null;
- }
-
private static BedrockData emptyData(int dataLength) {
return new BedrockData(null, null, null, -1, null, -1, -1, null, null, dataLength);
}
diff --git a/common/src/main/java/org/geysermc/floodgate/util/EncryptionUtil.java b/common/src/main/java/org/geysermc/floodgate/util/EncryptionUtil.java
deleted file mode 100644
index 619c7011a..000000000
--- a/common/src/main/java/org/geysermc/floodgate/util/EncryptionUtil.java
+++ /dev/null
@@ -1,80 +0,0 @@
-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) throws IllegalBlockSizeException,
- InvalidKeyException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException {
- return encrypt(key, data.toString());
- }
-
- public static byte[] decrypt(Key key, String encryptedData) throws IllegalBlockSizeException,
- InvalidKeyException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException {
- String[] split = encryptedData.split("\0");
- if (split.length != 2) {
- throw new IllegalArgumentException("Expected two arguments, got " + split.length);
- }
-
- Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
- cipher.init(key instanceof PublicKey ? Cipher.PUBLIC_KEY : Cipher.PRIVATE_KEY, key);
- byte[] decryptedKey = cipher.doFinal(Base64.getDecoder().decode(split[0]));
-
- SecretKey secretKey = new SecretKeySpec(decryptedKey, 0, decryptedKey.length, "AES");
- cipher = Cipher.getInstance("AES");
- cipher.init(Cipher.DECRYPT_MODE, secretKey);
- return cipher.doFinal(Base64.getDecoder().decode(split[1]));
- }
-
- public static BedrockData decryptBedrockData(Key key, String encryptedData) throws IllegalBlockSizeException,
- InvalidKeyException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException {
- return BedrockData.fromRawData(decrypt(key, encryptedData));
- }
-
- @SuppressWarnings("unchecked")
- public static T getKeyFromFile(Path fileLocation, Class keyType) throws
- IOException, InvalidKeySpecException, NoSuchAlgorithmException {
- boolean isPublicKey = keyType == PublicKey.class;
- if (!isPublicKey && keyType != PrivateKey.class) {
- throw new RuntimeException("I can only read public and private keys!");
- }
-
- byte[] key = Files.readAllBytes(fileLocation);
-
- KeyFactory keyFactory = KeyFactory.getInstance("RSA");
- EncodedKeySpec keySpec = isPublicKey ? new X509EncodedKeySpec(key) : new PKCS8EncodedKeySpec(key);
- return (T) (isPublicKey ?
- keyFactory.generatePublic(keySpec) :
- keyFactory.generatePrivate(keySpec)
- );
- }
-}
diff --git a/common/src/main/java/org/geysermc/floodgate/util/InvalidHeaderException.java b/common/src/main/java/org/geysermc/floodgate/util/InvalidHeaderException.java
new file mode 100644
index 000000000..30dbf0726
--- /dev/null
+++ b/common/src/main/java/org/geysermc/floodgate/util/InvalidHeaderException.java
@@ -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);
+ }
+}
diff --git a/common/src/main/java/org/geysermc/floodgate/util/LinkedPlayer.java b/common/src/main/java/org/geysermc/floodgate/util/LinkedPlayer.java
index a930b013a..53a167f9b 100644
--- a/common/src/main/java/org/geysermc/floodgate/util/LinkedPlayer.java
+++ b/common/src/main/java/org/geysermc/floodgate/util/LinkedPlayer.java
@@ -26,9 +26,7 @@
package org.geysermc.floodgate.util;
-import lombok.AccessLevel;
import lombok.Getter;
-import lombok.Setter;
import java.util.UUID;
@@ -50,7 +48,6 @@ public final class LinkedPlayer {
* 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.
*/
- @Setter(AccessLevel.PRIVATE)
private boolean fromDifferentPlatform = false;
public LinkedPlayer(String javaUsername, UUID javaUniqueId, UUID bedrockId) {
@@ -60,12 +57,15 @@ public final class LinkedPlayer {
}
static LinkedPlayer fromString(String data) {
- if (data.length() == 4) return null;
+ if (data.length() != 3) {
+ return null;
+ }
+
String[] split = data.split(";");
LinkedPlayer player = new LinkedPlayer(
split[0], UUID.fromString(split[1]), UUID.fromString(split[2])
);
- player.setFromDifferentPlatform(true);
+ player.fromDifferentPlatform = true;
return player;
}
diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java
index ee687dbbd..622add139 100644
--- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java
+++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java
@@ -57,10 +57,14 @@ import org.geysermc.connector.utils.DimensionUtils;
import org.geysermc.connector.utils.DockerCheck;
import org.geysermc.connector.utils.LanguageUtils;
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.InitialDirContext;
import java.net.InetSocketAddress;
+import java.security.Key;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
@@ -77,7 +81,7 @@ public class GeyserConnector {
public static final BedrockPacketCodec BEDROCK_PACKET_CODEC = Bedrock_v407.V407_CODEC;
public static final String NAME = "Geyser";
- public static final String VERSION = "DEV"; // A fallback for running in IDEs
+ public static final String VERSION = "1.0.0 (git-master-35d8edd)"; // A fallback for running in IDEs
private static final String IP_REGEX = "\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b";
@@ -89,6 +93,8 @@ public class GeyserConnector {
@Setter
private AuthType authType;
+ private FloodgateCipher cipher;
+
private boolean shuttingDown = false;
private final ScheduledExecutorService generalThreadPool;
@@ -165,6 +171,17 @@ public class GeyserConnector {
remoteServer = new RemoteServer(config.getRemote().getAddress(), remotePort);
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())
DimensionUtils.changeBedrockNetherId(); // Apply End dimension ID workaround to Nether
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 623bf16a9..a02a356fb 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
@@ -71,15 +71,13 @@ import org.geysermc.connector.network.translators.PacketTranslatorRegistry;
import org.geysermc.connector.network.translators.item.ItemRegistry;
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
import org.geysermc.connector.utils.*;
+import org.geysermc.floodgate.crypto.FloodgateCipher;
import org.geysermc.floodgate.util.BedrockData;
-import org.geysermc.floodgate.util.EncryptionUtil;
-import java.io.IOException;
import java.net.InetSocketAddress;
-import java.security.NoSuchAlgorithmException;
-import java.security.PublicKey;
-import java.security.spec.InvalidKeySpecException;
+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;
@@ -294,24 +292,6 @@ public class GeyserSession implements CommandSender {
}
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.getSession().addListener(new SessionAdapter() {
@@ -319,9 +299,11 @@ public class GeyserSession implements CommandSender {
public void packetSending(PacketSendingEvent event) {
//todo move this somewhere else
if (event.getPacket() instanceof HandshakePacket && floodgate) {
- String encrypted = "";
+ byte[] encryptedData;
+
try {
- encrypted = EncryptionUtil.encryptBedrockData(publicKey, new BedrockData(
+ FloodgateCipher cipher = connector.getCipher();
+ encryptedData = cipher.encryptFromString(new BedrockData(
clientData.getGameVersion(),
authData.getName(),
authData.getXboxUUID(),
@@ -330,15 +312,22 @@ public class GeyserSession implements CommandSender {
clientData.getUiProfile().ordinal(),
clientData.getCurrentInputMode().ordinal(),
upstream.getSession().getAddress().getAddress().getHostAddress()
- ));
+ ).toString());
} catch (Exception 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();
event.setPacket(new HandshakePacket(
handshakePacket.getProtocolVersion(),
- handshakePacket.getHostname() + '\0' + BedrockData.FLOODGATE_IDENTIFIER + '\0' + encrypted,
+ handshakePacket.getHostname() + '\0' + encrypted,
handshakePacket.getPort(),
handshakePacket.getIntent()
));