diff --git a/Launcher/src/main/java/pro/gravit/launcher/runtime/client/ClientLauncherProcess.java b/Launcher/src/main/java/pro/gravit/launcher/runtime/client/ClientLauncherProcess.java index 001502ca..ac85b898 100644 --- a/Launcher/src/main/java/pro/gravit/launcher/runtime/client/ClientLauncherProcess.java +++ b/Launcher/src/main/java/pro/gravit/launcher/runtime/client/ClientLauncherProcess.java @@ -19,6 +19,7 @@ import pro.gravit.launcher.core.serialize.HOutput; import pro.gravit.utils.helper.*; +import javax.crypto.CipherOutputStream; import java.io.File; import java.io.IOException; import java.net.ServerSocket; @@ -30,7 +31,6 @@ import java.util.stream.Collectors; public class ClientLauncherProcess { - public final List pre = new LinkedList<>(); public final ClientParams params = new ClientParams(); public final List jvmArgs = new LinkedList<>(); @@ -77,6 +77,7 @@ public ClientLauncherProcess(Path clientDir, Path assetDir, JavaHelper.JavaVersi this.params.clientDir = this.workDir.toString(); this.params.resourcePackDir = resourcePackDir.toAbsolutePath().toString(); this.params.assetDir = assetDir.toAbsolutePath().toString(); + this.params.timestamp = System.currentTimeMillis(); Path nativesPath; if(profile.hasFlag(ClientProfile.CompatibilityFlags.LEGACY_NATIVES_DIR)) { nativesPath = workDir.resolve("natives"); @@ -224,7 +225,7 @@ public void runWriteParams(SocketAddress address) throws IOException { waitWriteParams.notifyAll(); } 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)); output.writeByteArray(serializedMainParams, 0); params.clientHDir.write(output); @@ -235,6 +236,8 @@ public void runWriteParams(SocketAddress address) throws IOException { output.writeBoolean(true); params.javaHDir.write(output); } + } catch (Exception e) { + throw new IOException(e); } } LauncherEngine.modulesManager.invokeEvent(new ClientProcessBuilderParamsWrittedEvent(this)); diff --git a/LauncherClient/src/main/java/pro/gravit/launcher/client/ClientLauncherEntryPoint.java b/LauncherClient/src/main/java/pro/gravit/launcher/client/ClientLauncherEntryPoint.java index cc0a1b28..e67726dc 100644 --- a/LauncherClient/src/main/java/pro/gravit/launcher/client/ClientLauncherEntryPoint.java +++ b/LauncherClient/src/main/java/pro/gravit/launcher/client/ClientLauncherEntryPoint.java @@ -24,6 +24,7 @@ import pro.gravit.utils.helper.*; import pro.gravit.utils.launch.*; +import javax.crypto.CipherInputStream; import java.io.File; import java.io.IOException; import java.lang.invoke.MethodHandle; @@ -50,7 +51,7 @@ public class ClientLauncherEntryPoint { private static ClientParams readParams(SocketAddress address) throws IOException { try (Socket socket = IOHelper.newSocket()) { 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); ClientParams params = Launcher.gsonManager.gson.fromJson(IOHelper.decode(serialized), ClientParams.class); params.clientHDir = new HashedDir(input); @@ -88,6 +89,11 @@ private static void realMain(String[] args) throws Throwable { if (params.profile.getClassLoaderConfig() != ClientProfile.ClassLoaderConfig.AGENT) { 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; Launcher.profile = profile; AuthService.profile = profile; diff --git a/LauncherClient/src/main/java/pro/gravit/launcher/client/ClientParams.java b/LauncherClient/src/main/java/pro/gravit/launcher/client/ClientParams.java index ee8381d9..9f259dcb 100644 --- a/LauncherClient/src/main/java/pro/gravit/launcher/client/ClientParams.java +++ b/LauncherClient/src/main/java/pro/gravit/launcher/client/ClientParams.java @@ -14,6 +14,7 @@ import java.util.*; public class ClientParams { + public long timestamp; public String assetDir; public String clientDir; diff --git a/LauncherCore/src/main/java/pro/gravit/utils/helper/SecurityHelper.java b/LauncherCore/src/main/java/pro/gravit/utils/helper/SecurityHelper.java index f21eb4fc..9a959230 100644 --- a/LauncherCore/src/main/java/pro/gravit/utils/helper/SecurityHelper.java +++ b/LauncherCore/src/main/java/pro/gravit/utils/helper/SecurityHelper.java @@ -33,20 +33,15 @@ public final class SecurityHelper { public static final String RSA_ALGO = "RSA"; public static final String RSA_SIGN_ALGO = "SHA256withRSA"; public static final String RSA_CIPHER_ALGO = "RSA/ECB/PKCS1Padding"; + public static final String AES_CIPHER_ALGO = "AES/ECB/PKCS5Padding"; // 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 RSA_KEY_LENGTH_BITS = 2048; public static final int RSA_KEY_LENGTH = RSA_KEY_LENGTH_BITS / Byte.SIZE; public static final int CRYPTO_MAX_LENGTH = 2048; 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() { } @@ -189,6 +184,16 @@ private static Cipher newRSACipher(int mode, RSAKey key) { 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() { try { return KeyFactory.getInstance(EC_ALGO); @@ -313,70 +318,6 @@ public static byte[] randomAESKey(Random random) { 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) { Signature signature = newECSignSignature(privateKey); 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 public static byte[] encrypt(String seed, byte[] cleartext) throws Exception { byte[] rawKey = getAESKey(IOHelper.encode(seed)); diff --git a/LauncherCore/src/test/java/pro/gravit/launcher/SecurityHelperTests.java b/LauncherCore/src/test/java/pro/gravit/launcher/SecurityHelperTests.java new file mode 100644 index 00000000..4a78442d --- /dev/null +++ b/LauncherCore/src/test/java/pro/gravit/launcher/SecurityHelperTests.java @@ -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); + } +}