mirror of
https://github.com/GeyserMC/Floodgate.git
synced 2026-01-06 15:42:03 +00:00
First commit
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
package org.geysermc.floodgate;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonEnumDefaultValue;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
class FloodgateAPI {
|
||||
static final Map<UUID, FloodgatePlayer> players = new HashMap<>();
|
||||
|
||||
public static FloodgatePlayer getPlayer(UUID uuid) {
|
||||
return players.get(uuid);
|
||||
}
|
||||
|
||||
public static UUID createJavaPlayerId(long xuid) {
|
||||
return new UUID(0, xuid);
|
||||
}
|
||||
|
||||
public enum DeviceOS {
|
||||
@JsonEnumDefaultValue
|
||||
UNKOWN,
|
||||
ANDROID,
|
||||
IOS,
|
||||
OSX,
|
||||
FIREOS,
|
||||
GEARVR,
|
||||
HOLOLENS,
|
||||
WIN10,
|
||||
WIN32,
|
||||
DEDICATED,
|
||||
ORBIS,
|
||||
NX;
|
||||
|
||||
private static final DeviceOS[] VALUES = values();
|
||||
|
||||
public static DeviceOS getById(int id) {
|
||||
return id < VALUES.length ? VALUES[id] : VALUES[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package org.geysermc.floodgate;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
|
||||
import lombok.Getter;
|
||||
import org.geysermc.floodgate.util.EncryptionUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.util.Base64;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
@Getter
|
||||
public class FloodgateConfig {
|
||||
@JsonProperty(value = "key-file-name")
|
||||
private String keyFileName;
|
||||
@JsonProperty(value = "disconnect")
|
||||
private DisconnectMessages messages;
|
||||
|
||||
@JsonIgnore
|
||||
private PrivateKey privateKey = null;
|
||||
|
||||
@Getter
|
||||
public static class DisconnectMessages {
|
||||
@JsonProperty("invalid-key")
|
||||
private String invalidKey;
|
||||
@JsonProperty("invalid-arguments-length")
|
||||
private String invalidArgumentsLength;
|
||||
}
|
||||
|
||||
public static FloodgateConfig load(Logger logger, Path path) {
|
||||
FloodgateConfig config = null;
|
||||
try {
|
||||
try {
|
||||
if (!path.toFile().exists()) {
|
||||
Files.copy(FloodgateConfig.class.getClassLoader().getResourceAsStream("config.yml"), path);
|
||||
|
||||
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
|
||||
generator.initialize(2048);
|
||||
KeyPair keyPair = generator.generateKeyPair();
|
||||
|
||||
String test = "abcdefghijklmnopqrstuvwxyz1234567890";
|
||||
|
||||
String encrypted = EncryptionUtil.encrypt(keyPair.getPublic(), test);
|
||||
String decrypted = new String(EncryptionUtil.decrypt(keyPair.getPrivate(), encrypted));
|
||||
|
||||
if (!test.equals(decrypted)) {
|
||||
System.out.println(test +" "+ decrypted +" "+ encrypted +" " + new String(Base64.getDecoder().decode(encrypted.split("\0")[1])));
|
||||
throw new RuntimeException(
|
||||
"Testing the private and public key failed," +
|
||||
"the original message isn't the same as the decrypted!"
|
||||
);
|
||||
}
|
||||
|
||||
Files.write(path.getParent().resolve("encrypted.txt"), encrypted.getBytes());
|
||||
|
||||
Files.write(path.getParent().resolve("public-key.pem"), keyPair.getPublic().getEncoded());
|
||||
Files.write(path.getParent().resolve("key.pem"), keyPair.getPrivate().getEncoded());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.SEVERE, "Error while creating config", e);
|
||||
}
|
||||
|
||||
config = new ObjectMapper(new YAMLFactory()).readValue(
|
||||
Files.readAllBytes(path), FloodgateConfig.class
|
||||
);
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.SEVERE, "Error while loading config", e);
|
||||
}
|
||||
if (config == null) return null;
|
||||
|
||||
try {
|
||||
config.privateKey = EncryptionUtil.getKeyFromFile(
|
||||
path.getParent().resolve(config.getKeyFileName()),
|
||||
PrivateKey.class
|
||||
);
|
||||
} catch (IOException | InvalidKeySpecException | NoSuchAlgorithmException e) {
|
||||
logger.log(Level.SEVERE, "Error while reading private key", e);
|
||||
}
|
||||
return config;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package org.geysermc.floodgate;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.geysermc.floodgate.util.BedrockData;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@Getter
|
||||
public class FloodgatePlayer {
|
||||
/**
|
||||
* Bedrock version of the client
|
||||
*/
|
||||
private String version;
|
||||
/**
|
||||
* Bedrock username (full version)
|
||||
*/
|
||||
private String username;
|
||||
/**
|
||||
* Bedrock username with > identifier
|
||||
*/
|
||||
private String javaUsername;
|
||||
/**
|
||||
* The Xbox Unique Identifier
|
||||
*/
|
||||
private String xuid;
|
||||
/**
|
||||
* The operation system of the bedrock client
|
||||
*/
|
||||
private FloodgateAPI.DeviceOS deviceOS;
|
||||
/**
|
||||
* The language code of the bedrock client
|
||||
*/
|
||||
private String languageCode;
|
||||
/**
|
||||
* The Java UUID used to identify the bedrock client
|
||||
*/
|
||||
private UUID javaUniqueId;
|
||||
|
||||
FloodgatePlayer(BedrockData data) {
|
||||
version = data.getVersion();
|
||||
username = data.getUsername();
|
||||
javaUsername = "*" + data.getUsername().substring(0, Math.min(data.getUsername().length(), 15));
|
||||
xuid = data.getXuid();
|
||||
deviceOS = FloodgateAPI.DeviceOS.getById(data.getDeviceId());
|
||||
languageCode = data.getLanguageCode();
|
||||
javaUniqueId = FloodgateAPI.createJavaPlayerId(Long.parseLong(data.getXuid()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package org.geysermc.floodgate.util;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public class BedrockData {
|
||||
public static final int EXPECTED_LENGTH = 6;
|
||||
private String version;
|
||||
private String username;
|
||||
private String xuid;
|
||||
private int deviceId;
|
||||
private String languageCode;
|
||||
private int inputMode;
|
||||
private int dataLength;
|
||||
|
||||
public BedrockData(String version, String username, String xuid, int deviceId, String languageCode, int inputMode) {
|
||||
this(version, username, xuid, deviceId, languageCode, inputMode, 6);
|
||||
}
|
||||
|
||||
public static BedrockData fromString(String data) {
|
||||
String[] split = data.split("\0");
|
||||
if (split.length != 5) return null;
|
||||
return new BedrockData(split[0], split[1], split[2], Integer.parseInt(split[3]), split[4], split.length);
|
||||
}
|
||||
|
||||
public static BedrockData fromRawData(byte[] data) {
|
||||
return fromString(new String(data));
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return version +"\0"+ username +"\0"+ xuid +"\0"+ deviceId +"\0"+ languageCode +"\0"+ inputMode;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
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;
|
||||
|
||||
public 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 encryptFromInstance(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 decryptToInstance(Key key, String encryptedData) throws IllegalBlockSizeException,
|
||||
InvalidKeyException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException {
|
||||
return BedrockData.fromRawData(decrypt(key, encryptedData));
|
||||
}
|
||||
|
||||
@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)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package org.geysermc.floodgate.util;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
public class ReflectionUtil {
|
||||
public static Field getField(Class<?> clazz, String fieldName, boolean isPublic) {
|
||||
try {
|
||||
return isPublic ? clazz.getField(fieldName) : clazz.getDeclaredField(fieldName);
|
||||
} catch (NoSuchFieldException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static Field getField(Class<?> clazz, String fieldName) {
|
||||
Field field = getField(clazz, fieldName, false);
|
||||
return field != null ? field : getField(clazz, fieldName, true);
|
||||
}
|
||||
|
||||
public static boolean setValue(Object instance, String fieldName, Object value) {
|
||||
Field field = getField(instance.getClass(), fieldName);
|
||||
if (field != null) {
|
||||
setValue(instance, field, value);
|
||||
}
|
||||
return field != null;
|
||||
}
|
||||
|
||||
public static void setValue(Object instance, Field field, Object value) {
|
||||
if (!field.isAccessible()) field.setAccessible(true);
|
||||
try {
|
||||
field.set(instance, value);
|
||||
} catch (IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
4
common/src/main/resources/config.yml
Normal file
4
common/src/main/resources/config.yml
Normal file
@@ -0,0 +1,4 @@
|
||||
key-file-name: key.pem
|
||||
disconnect:
|
||||
invalid-key: Please connect through the official Geyser
|
||||
invalid-arguments-length: Expected {0} arguments, got {1}. Is Geyser up-to-date?
|
||||
Reference in New Issue
Block a user