mirror of
https://github.com/GeyserMC/Geyser.git
synced 2024-08-14 23:57:35 +00:00
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:
commit
bb20b14e4c
10 changed files with 447 additions and 146 deletions
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -34,11 +34,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;
|
||||
|
@ -65,9 +64,15 @@ public final class BedrockData {
|
|||
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) {
|
||||
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]);
|
||||
// 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");
|
||||
}
|
||||
|
||||
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, null);
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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.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.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.security.Key;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
@ -87,6 +91,8 @@ public class GeyserConnector {
|
|||
@Setter
|
||||
private AuthType authType;
|
||||
|
||||
private FloodgateCipher cipher;
|
||||
|
||||
private boolean shuttingDown = false;
|
||||
|
||||
private final ScheduledExecutorService generalThreadPool;
|
||||
|
@ -171,6 +177,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
|
||||
|
||||
|
|
|
@ -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.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.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;
|
||||
|
||||
@Getter
|
||||
|
@ -336,24 +340,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() {
|
||||
|
@ -361,9 +347,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(),
|
||||
|
@ -373,15 +361,22 @@ public class GeyserSession implements CommandSender {
|
|||
clientData.getCurrentInputMode().ordinal(),
|
||||
upstream.getSession().getAddress().getAddress().getHostAddress(),
|
||||
clientData.getImage("Skin")
|
||||
));
|
||||
).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()
|
||||
));
|
||||
|
|
Loading…
Reference in a new issue