mirror of
https://github.com/GravitLauncher/Launcher
synced 2024-11-15 03:31:15 +03:00
[FEATURE] Use encrypted ClientParams
This commit is contained in:
parent
d40dc09aca
commit
4e50cea93a
5 changed files with 89 additions and 74 deletions
|
@ -19,6 +19,7 @@
|
||||||
import pro.gravit.launcher.core.serialize.HOutput;
|
import pro.gravit.launcher.core.serialize.HOutput;
|
||||||
import pro.gravit.utils.helper.*;
|
import pro.gravit.utils.helper.*;
|
||||||
|
|
||||||
|
import javax.crypto.CipherOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.ServerSocket;
|
import java.net.ServerSocket;
|
||||||
|
@ -30,7 +31,6 @@
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class ClientLauncherProcess {
|
public class ClientLauncherProcess {
|
||||||
|
|
||||||
public final List<String> pre = new LinkedList<>();
|
public final List<String> pre = new LinkedList<>();
|
||||||
public final ClientParams params = new ClientParams();
|
public final ClientParams params = new ClientParams();
|
||||||
public final List<String> jvmArgs = new LinkedList<>();
|
public final List<String> jvmArgs = new LinkedList<>();
|
||||||
|
@ -77,6 +77,7 @@ public ClientLauncherProcess(Path clientDir, Path assetDir, JavaHelper.JavaVersi
|
||||||
this.params.clientDir = this.workDir.toString();
|
this.params.clientDir = this.workDir.toString();
|
||||||
this.params.resourcePackDir = resourcePackDir.toAbsolutePath().toString();
|
this.params.resourcePackDir = resourcePackDir.toAbsolutePath().toString();
|
||||||
this.params.assetDir = assetDir.toAbsolutePath().toString();
|
this.params.assetDir = assetDir.toAbsolutePath().toString();
|
||||||
|
this.params.timestamp = System.currentTimeMillis();
|
||||||
Path nativesPath;
|
Path nativesPath;
|
||||||
if(profile.hasFlag(ClientProfile.CompatibilityFlags.LEGACY_NATIVES_DIR)) {
|
if(profile.hasFlag(ClientProfile.CompatibilityFlags.LEGACY_NATIVES_DIR)) {
|
||||||
nativesPath = workDir.resolve("natives");
|
nativesPath = workDir.resolve("natives");
|
||||||
|
@ -224,7 +225,7 @@ public void runWriteParams(SocketAddress address) throws IOException {
|
||||||
waitWriteParams.notifyAll();
|
waitWriteParams.notifyAll();
|
||||||
}
|
}
|
||||||
Socket socket = serverSocket.accept();
|
Socket socket = serverSocket.accept();
|
||||||
try (HOutput output = new HOutput(socket.getOutputStream())) {
|
try (HOutput output = new HOutput(new CipherOutputStream(socket.getOutputStream(), SecurityHelper.newAESEncryptCipher(SecurityHelper.fromHex(Launcher.getConfig().secretKeyClient))))) {
|
||||||
byte[] serializedMainParams = IOHelper.encode(Launcher.gsonManager.gson.toJson(params));
|
byte[] serializedMainParams = IOHelper.encode(Launcher.gsonManager.gson.toJson(params));
|
||||||
output.writeByteArray(serializedMainParams, 0);
|
output.writeByteArray(serializedMainParams, 0);
|
||||||
params.clientHDir.write(output);
|
params.clientHDir.write(output);
|
||||||
|
@ -235,6 +236,8 @@ public void runWriteParams(SocketAddress address) throws IOException {
|
||||||
output.writeBoolean(true);
|
output.writeBoolean(true);
|
||||||
params.javaHDir.write(output);
|
params.javaHDir.write(output);
|
||||||
}
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new IOException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LauncherEngine.modulesManager.invokeEvent(new ClientProcessBuilderParamsWrittedEvent(this));
|
LauncherEngine.modulesManager.invokeEvent(new ClientProcessBuilderParamsWrittedEvent(this));
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
import pro.gravit.utils.helper.*;
|
import pro.gravit.utils.helper.*;
|
||||||
import pro.gravit.utils.launch.*;
|
import pro.gravit.utils.launch.*;
|
||||||
|
|
||||||
|
import javax.crypto.CipherInputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.invoke.MethodHandle;
|
import java.lang.invoke.MethodHandle;
|
||||||
|
@ -50,7 +51,7 @@ public class ClientLauncherEntryPoint {
|
||||||
private static ClientParams readParams(SocketAddress address) throws IOException {
|
private static ClientParams readParams(SocketAddress address) throws IOException {
|
||||||
try (Socket socket = IOHelper.newSocket()) {
|
try (Socket socket = IOHelper.newSocket()) {
|
||||||
socket.connect(address);
|
socket.connect(address);
|
||||||
try (HInput input = new HInput(socket.getInputStream())) {
|
try (HInput input = new HInput(new CipherInputStream(socket.getInputStream(), SecurityHelper.newAESDecryptCipher(SecurityHelper.fromHex(Launcher.getConfig().secretKeyClient))))) {
|
||||||
byte[] serialized = input.readByteArray(0);
|
byte[] serialized = input.readByteArray(0);
|
||||||
ClientParams params = Launcher.gsonManager.gson.fromJson(IOHelper.decode(serialized), ClientParams.class);
|
ClientParams params = Launcher.gsonManager.gson.fromJson(IOHelper.decode(serialized), ClientParams.class);
|
||||||
params.clientHDir = new HashedDir(input);
|
params.clientHDir = new HashedDir(input);
|
||||||
|
@ -88,6 +89,11 @@ private static void realMain(String[] args) throws Throwable {
|
||||||
if (params.profile.getClassLoaderConfig() != ClientProfile.ClassLoaderConfig.AGENT) {
|
if (params.profile.getClassLoaderConfig() != ClientProfile.ClassLoaderConfig.AGENT) {
|
||||||
ClientLauncherMethods.verifyNoAgent();
|
ClientLauncherMethods.verifyNoAgent();
|
||||||
}
|
}
|
||||||
|
if(params.timestamp > System.currentTimeMillis() || params.timestamp + 30*1000 < System.currentTimeMillis() ) {
|
||||||
|
LogHelper.error("Timestamp failed. Exit");
|
||||||
|
ClientLauncherMethods.exitLauncher(-662);
|
||||||
|
return;
|
||||||
|
}
|
||||||
ClientProfile profile = params.profile;
|
ClientProfile profile = params.profile;
|
||||||
Launcher.profile = profile;
|
Launcher.profile = profile;
|
||||||
AuthService.profile = profile;
|
AuthService.profile = profile;
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
public class ClientParams {
|
public class ClientParams {
|
||||||
|
public long timestamp;
|
||||||
public String assetDir;
|
public String assetDir;
|
||||||
|
|
||||||
public String clientDir;
|
public String clientDir;
|
||||||
|
|
|
@ -33,20 +33,15 @@ public final class SecurityHelper {
|
||||||
public static final String RSA_ALGO = "RSA";
|
public static final String RSA_ALGO = "RSA";
|
||||||
public static final String RSA_SIGN_ALGO = "SHA256withRSA";
|
public static final String RSA_SIGN_ALGO = "SHA256withRSA";
|
||||||
public static final String RSA_CIPHER_ALGO = "RSA/ECB/PKCS1Padding";
|
public static final String RSA_CIPHER_ALGO = "RSA/ECB/PKCS1Padding";
|
||||||
|
public static final String AES_CIPHER_ALGO = "AES/ECB/PKCS5Padding";
|
||||||
|
|
||||||
// Algorithm size constants
|
// Algorithm size constants
|
||||||
public static final int AES_KEY_LENGTH = 8;
|
public static final int AES_KEY_LENGTH = 16;
|
||||||
public static final int TOKEN_STRING_LENGTH = TOKEN_LENGTH << 1;
|
public static final int TOKEN_STRING_LENGTH = TOKEN_LENGTH << 1;
|
||||||
public static final int RSA_KEY_LENGTH_BITS = 2048;
|
public static final int RSA_KEY_LENGTH_BITS = 2048;
|
||||||
public static final int RSA_KEY_LENGTH = RSA_KEY_LENGTH_BITS / Byte.SIZE;
|
public static final int RSA_KEY_LENGTH = RSA_KEY_LENGTH_BITS / Byte.SIZE;
|
||||||
public static final int CRYPTO_MAX_LENGTH = 2048;
|
public static final int CRYPTO_MAX_LENGTH = 2048;
|
||||||
public static final String HEX = "0123456789abcdef";
|
public static final String HEX = "0123456789abcdef";
|
||||||
// Certificate constants
|
|
||||||
public static final byte[] NUMBERS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
|
|
||||||
public static final SecureRandom secureRandom = new SecureRandom();
|
|
||||||
// Random generator constants
|
|
||||||
private static final char[] VOWELS = {'e', 'u', 'i', 'o', 'a'};
|
|
||||||
private static final char[] CONS = {'r', 't', 'p', 's', 'd', 'f', 'g', 'h', 'k', 'l', 'c', 'v', 'b', 'n', 'm'};
|
|
||||||
|
|
||||||
private SecurityHelper() {
|
private SecurityHelper() {
|
||||||
}
|
}
|
||||||
|
@ -189,6 +184,16 @@ private static Cipher newRSACipher(int mode, RSAKey key) {
|
||||||
return cipher;
|
return cipher;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Cipher newAESCipher(int mode, byte[] key) {
|
||||||
|
Cipher cipher = newCipher(AES_CIPHER_ALGO);
|
||||||
|
try {
|
||||||
|
cipher.init(mode, new SecretKeySpec(key, "AES"));
|
||||||
|
} catch (InvalidKeyException e) {
|
||||||
|
throw new InternalError(e);
|
||||||
|
}
|
||||||
|
return cipher;
|
||||||
|
}
|
||||||
|
|
||||||
private static KeyFactory newECDSAKeyFactory() {
|
private static KeyFactory newECDSAKeyFactory() {
|
||||||
try {
|
try {
|
||||||
return KeyFactory.getInstance(EC_ALGO);
|
return KeyFactory.getInstance(EC_ALGO);
|
||||||
|
@ -313,70 +318,6 @@ public static byte[] randomAESKey(Random random) {
|
||||||
return randomBytes(random, AES_KEY_LENGTH);
|
return randomBytes(random, AES_KEY_LENGTH);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static String randomUsername() {
|
|
||||||
return randomUsername(newRandom());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static String randomUsername(Random random) {
|
|
||||||
int usernameLength = 3 + random.nextInt(7); // 3-9
|
|
||||||
|
|
||||||
// Choose prefix
|
|
||||||
String prefix;
|
|
||||||
int prefixType = random.nextInt(7);
|
|
||||||
if (usernameLength >= 5 && prefixType == 6) { // (6) 2-char
|
|
||||||
prefix = random.nextBoolean() ? "Mr" : "Dr";
|
|
||||||
usernameLength -= 2;
|
|
||||||
} else if (usernameLength >= 6 && prefixType == 5) { // (5) 3-char
|
|
||||||
prefix = "Mrs";
|
|
||||||
usernameLength -= 3;
|
|
||||||
} else
|
|
||||||
prefix = "";
|
|
||||||
|
|
||||||
// Choose suffix
|
|
||||||
String suffix;
|
|
||||||
int suffixType = random.nextInt(7); // 0-6, 7 values
|
|
||||||
if (usernameLength >= 5 && suffixType == 6) { // (6) 10-99
|
|
||||||
suffix = String.valueOf(10 + random.nextInt(90));
|
|
||||||
usernameLength -= 2;
|
|
||||||
} else if (usernameLength >= 7 && suffixType == 5) { // (5) 1990-2015
|
|
||||||
suffix = String.valueOf(1990 + random.nextInt(26));
|
|
||||||
usernameLength -= 4;
|
|
||||||
} else
|
|
||||||
suffix = "";
|
|
||||||
|
|
||||||
// Choose name
|
|
||||||
int consRepeat = 0;
|
|
||||||
boolean consPrev = random.nextBoolean();
|
|
||||||
char[] chars = new char[usernameLength];
|
|
||||||
for (int i = 0; i < chars.length; i++) {
|
|
||||||
if (i > 1 && consPrev && random.nextInt(10) == 0) { // Doubled
|
|
||||||
chars[i] = chars[i - 1];
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Choose next char
|
|
||||||
if (consRepeat < 1 && random.nextInt() == 5)
|
|
||||||
consRepeat++;
|
|
||||||
else {
|
|
||||||
consRepeat = 0;
|
|
||||||
consPrev ^= true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Choose char
|
|
||||||
char[] alphabet = consPrev ? CONS : VOWELS;
|
|
||||||
chars[i] = alphabet[random.nextInt(alphabet.length)];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make first letter uppercase
|
|
||||||
if (!prefix.isEmpty() || random.nextBoolean())
|
|
||||||
chars[0] = Character.toUpperCase(chars[0]);
|
|
||||||
|
|
||||||
// Return chosen name (and verify for sure)
|
|
||||||
return VerifyHelper.verifyUsername(prefix + new String(chars) + suffix);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static byte[] sign(byte[] bytes, ECPrivateKey privateKey) {
|
public static byte[] sign(byte[] bytes, ECPrivateKey privateKey) {
|
||||||
Signature signature = newECSignSignature(privateKey);
|
Signature signature = newECSignSignature(privateKey);
|
||||||
try {
|
try {
|
||||||
|
@ -484,6 +425,22 @@ public static Cipher newRSAEncryptCipher(RSAPublicKey publicKey) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Cipher newAESDecryptCipher(byte[] key) {
|
||||||
|
try {
|
||||||
|
return newAESCipher(Cipher.DECRYPT_MODE, key);
|
||||||
|
} catch (SecurityException e) {
|
||||||
|
throw new InternalError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Cipher newAESEncryptCipher(byte[] key) {
|
||||||
|
try {
|
||||||
|
return newAESCipher(Cipher.ENCRYPT_MODE, key);
|
||||||
|
} catch (SecurityException e) {
|
||||||
|
throw new InternalError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//AES
|
//AES
|
||||||
public static byte[] encrypt(String seed, byte[] cleartext) throws Exception {
|
public static byte[] encrypt(String seed, byte[] cleartext) throws Exception {
|
||||||
byte[] rawKey = getAESKey(IOHelper.encode(seed));
|
byte[] rawKey = getAESKey(IOHelper.encode(seed));
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
package pro.gravit.launcher;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import pro.gravit.utils.helper.SecurityHelper;
|
||||||
|
|
||||||
|
import javax.crypto.CipherInputStream;
|
||||||
|
import javax.crypto.CipherOutputStream;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
public class SecurityHelperTests {
|
||||||
|
@Test
|
||||||
|
public void aesLegacyTest() throws Exception {
|
||||||
|
byte[] bytes = SecurityHelper.randomBytes(24);
|
||||||
|
byte[] seed = SecurityHelper.randomBytes(32);
|
||||||
|
byte[] encrypted = SecurityHelper.encrypt(seed, bytes);
|
||||||
|
byte[] decrypted = SecurityHelper.decrypt(seed, encrypted);
|
||||||
|
Assertions.assertArrayEquals(bytes, decrypted);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void aesStreamTest() throws Exception {
|
||||||
|
byte[] bytes = SecurityHelper.randomBytes(128);
|
||||||
|
byte[] seed = SecurityHelper.randomAESKey();
|
||||||
|
byte[] encrypted;
|
||||||
|
ByteArrayOutputStream s = new ByteArrayOutputStream();
|
||||||
|
try(OutputStream o = new CipherOutputStream(s, SecurityHelper.newAESEncryptCipher(seed))) {
|
||||||
|
try(ByteArrayInputStream i = new ByteArrayInputStream(bytes)) {
|
||||||
|
i.transferTo(o);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
encrypted = s.toByteArray();
|
||||||
|
byte[] decrypted;
|
||||||
|
;
|
||||||
|
try(InputStream i = new CipherInputStream(new ByteArrayInputStream(encrypted), SecurityHelper.newAESDecryptCipher(seed))) {
|
||||||
|
ByteArrayOutputStream s2 = new ByteArrayOutputStream();
|
||||||
|
try(s2) {
|
||||||
|
i.transferTo(s2);
|
||||||
|
}
|
||||||
|
decrypted = s2.toByteArray();
|
||||||
|
}
|
||||||
|
Assertions.assertArrayEquals(bytes, decrypted);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue