diff --git a/LaunchServer/src/main/java/ru/gravit/launchserver/binary/LauncherBinary.java b/LaunchServer/src/main/java/ru/gravit/launchserver/binary/LauncherBinary.java index 15a97eb0..676248c8 100644 --- a/LaunchServer/src/main/java/ru/gravit/launchserver/binary/LauncherBinary.java +++ b/LaunchServer/src/main/java/ru/gravit/launchserver/binary/LauncherBinary.java @@ -4,6 +4,7 @@ import java.nio.file.Path; import ru.gravit.launcher.LauncherAPI; +import ru.gravit.launcher.serialize.signed.DigestBytesHolder; import ru.gravit.utils.helper.IOHelper; import ru.gravit.launcher.serialize.signed.SignedBytesHolder; import ru.gravit.launchserver.LaunchServer; @@ -15,8 +16,8 @@ public abstract class LauncherBinary { protected final Path binaryFile; protected final Path syncBinaryFile; - private volatile SignedBytesHolder binary; - private volatile byte[] hash; + private volatile DigestBytesHolder binary; + private volatile byte[] sign; protected LauncherBinary(LaunchServer server, Path binaryFile) { @@ -41,19 +42,19 @@ public final boolean exists() { } - public final SignedBytesHolder getBytes() { + public final DigestBytesHolder getBytes() { return binary; } - - public final byte[] getHash() { - return hash; + public final byte[] getSign() { + return sign; } public final boolean sync() throws IOException { boolean exists = exists(); - binary = exists ? new SignedBytesHolder(IOHelper.read(syncBinaryFile), server.privateKey) : null; - hash = exists ? SecurityHelper.digest(SecurityHelper.DigestAlgorithm.SHA512,IOHelper.newInput(syncBinaryFile)) : null; + binary = exists ? new DigestBytesHolder(IOHelper.read(syncBinaryFile), SecurityHelper.DigestAlgorithm.SHA512) : null; + sign = exists ? SecurityHelper.sign(IOHelper.read(syncBinaryFile),server.privateKey) : null; + return exists; } } diff --git a/LaunchServer/src/main/java/ru/gravit/launchserver/response/Response.java b/LaunchServer/src/main/java/ru/gravit/launchserver/response/Response.java index 7e760bba..b490abda 100644 --- a/LaunchServer/src/main/java/ru/gravit/launchserver/response/Response.java +++ b/LaunchServer/src/main/java/ru/gravit/launchserver/response/Response.java @@ -4,8 +4,8 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import ru.gravit.launcher.LauncherAPI; import ru.gravit.launchserver.response.auth.*; +import ru.gravit.launchserver.response.update.*; import ru.gravit.utils.helper.LogHelper; import ru.gravit.launcher.request.RequestException; import ru.gravit.launcher.request.RequestType; @@ -15,10 +15,6 @@ import ru.gravit.launchserver.response.profile.BatchProfileByUsernameResponse; import ru.gravit.launchserver.response.profile.ProfileByUUIDResponse; import ru.gravit.launchserver.response.profile.ProfileByUsernameResponse; -import ru.gravit.launchserver.response.update.LauncherResponse; -import ru.gravit.launchserver.response.update.ProfilesResponse; -import ru.gravit.launchserver.response.update.UpdateListResponse; -import ru.gravit.launchserver.response.update.UpdateResponse; public abstract class Response { @FunctionalInterface @@ -47,6 +43,7 @@ public static void registerResponses() { registerResponse(RequestType.PROFILE_BY_USERNAME.getNumber(), ProfileByUsernameResponse::new); registerResponse(RequestType.PROFILE_BY_UUID.getNumber(), ProfileByUUIDResponse::new); + registerResponse(RequestType.LEGACYLAUNCHER.getNumber(), LegacyLauncherResponse::new); registerResponse(RequestType.LAUNCHER.getNumber(), LauncherResponse::new); registerResponse(RequestType.UPDATE_LIST.getNumber(), UpdateListResponse::new); registerResponse(RequestType.UPDATE.getNumber(), UpdateResponse::new); diff --git a/LaunchServer/src/main/java/ru/gravit/launchserver/response/update/LauncherResponse.java b/LaunchServer/src/main/java/ru/gravit/launchserver/response/update/LauncherResponse.java index 0485ab93..1f170fde 100644 --- a/LaunchServer/src/main/java/ru/gravit/launchserver/response/update/LauncherResponse.java +++ b/LaunchServer/src/main/java/ru/gravit/launchserver/response/update/LauncherResponse.java @@ -1,16 +1,21 @@ package ru.gravit.launchserver.response.update; -import java.io.IOException; -import java.util.Collection; - -import ru.gravit.utils.helper.SecurityHelper; import ru.gravit.launcher.profiles.ClientProfile; import ru.gravit.launcher.serialize.HInput; import ru.gravit.launcher.serialize.HOutput; +import ru.gravit.launcher.serialize.signed.DigestBytesHolder; import ru.gravit.launcher.serialize.signed.SignedBytesHolder; import ru.gravit.launcher.serialize.signed.SignedObjectHolder; import ru.gravit.launchserver.LaunchServer; +import ru.gravit.launchserver.manangers.SessionManager; import ru.gravit.launchserver.response.Response; +import ru.gravit.launchserver.socket.Client; +import ru.gravit.utils.helper.SecurityHelper; + +import java.io.IOException; +import java.security.SignatureException; +import java.util.Arrays; +import java.util.Collection; public final class LauncherResponse extends Response { @@ -21,25 +26,23 @@ public LauncherResponse(LaunchServer server, long session, HInput input, HOutput @Override public void reply() throws IOException { // Resolve launcher binary - SignedBytesHolder bytes = (input.readBoolean() ? server.launcherEXEBinary : server.launcherBinary).getBytes(); + DigestBytesHolder bytes = (input.readBoolean() ? server.launcherEXEBinary : server.launcherBinary).getBytes(); if (bytes == null) { requestError("Missing launcher binary"); return; } - writeNoError(output); - - // Update launcher binary - output.writeByteArray(bytes.getSign(), -SecurityHelper.RSA_KEY_LENGTH); - output.flush(); - if (input.readBoolean()) { + Client client = server.sessionManager.getOrNewClient(session); + byte[] digest = input.readByteArray(0); + if(!Arrays.equals(bytes.getDigest(), digest)) + { + writeNoError(output); + output.writeBoolean(true); output.writeByteArray(bytes.getBytes(), 0); - return; // Launcher will be restarted + client.checkSign = false; + return; } - - // Write clients profiles list - Collection> profiles = server.getProfiles(); - output.writeLength(profiles.size(), 0); - for (SignedObjectHolder profile : profiles) - profile.write(output); + writeNoError(output); + output.writeBoolean(false); + client.checkSign = true; } } diff --git a/LaunchServer/src/main/java/ru/gravit/launchserver/response/update/LegacyLauncherResponse.java b/LaunchServer/src/main/java/ru/gravit/launchserver/response/update/LegacyLauncherResponse.java new file mode 100644 index 00000000..6f50ba53 --- /dev/null +++ b/LaunchServer/src/main/java/ru/gravit/launchserver/response/update/LegacyLauncherResponse.java @@ -0,0 +1,42 @@ +package ru.gravit.launchserver.response.update; + +import java.io.IOException; +import java.util.Collection; + +import ru.gravit.launcher.serialize.signed.DigestBytesHolder; +import ru.gravit.launchserver.binary.LauncherBinary; +import ru.gravit.utils.helper.SecurityHelper; +import ru.gravit.launcher.profiles.ClientProfile; +import ru.gravit.launcher.serialize.HInput; +import ru.gravit.launcher.serialize.HOutput; +import ru.gravit.launcher.serialize.signed.SignedBytesHolder; +import ru.gravit.launcher.serialize.signed.SignedObjectHolder; +import ru.gravit.launchserver.LaunchServer; +import ru.gravit.launchserver.response.Response; + +public final class LegacyLauncherResponse extends Response { + + public LegacyLauncherResponse(LaunchServer server, long session, HInput input, HOutput output, String ip) { + super(server, session, input, output, ip); + } + + @Override + public void reply() throws IOException { + // Resolve launcher binary + LauncherBinary bytes = (input.readBoolean() ? server.launcherEXEBinary : server.launcherBinary); + if (bytes == null) { + requestError("Missing launcher binary"); + return; + } + writeNoError(output); + + // Update launcher binary + output.writeByteArray(bytes.getSign(), -SecurityHelper.RSA_KEY_LENGTH); + output.flush(); + if (input.readBoolean()) { + output.writeByteArray(bytes.getBytes().getBytes(), 0); + return; // Launcher will be restarted + } + requestError("You must update"); + } +} diff --git a/LaunchServer/src/main/java/ru/gravit/launchserver/response/update/ProfilesResponse.java b/LaunchServer/src/main/java/ru/gravit/launchserver/response/update/ProfilesResponse.java index f991d833..a37e7a32 100644 --- a/LaunchServer/src/main/java/ru/gravit/launchserver/response/update/ProfilesResponse.java +++ b/LaunchServer/src/main/java/ru/gravit/launchserver/response/update/ProfilesResponse.java @@ -23,7 +23,7 @@ public void reply() throws IOException { // Resolve launcher binary Client client = server.sessionManager.getClient(session); input.readBoolean(); - if(client.type == Client.Type.USER) { + if(client.type == Client.Type.USER && !client.checkSign) { LogHelper.warning("User session: %d ip %s try get profiles",session,ip); requestError("Assess denied"); return; diff --git a/LaunchServer/src/main/java/ru/gravit/launchserver/socket/Client.java b/LaunchServer/src/main/java/ru/gravit/launchserver/socket/Client.java index 7feb8a78..2775aa24 100644 --- a/LaunchServer/src/main/java/ru/gravit/launchserver/socket/Client.java +++ b/LaunchServer/src/main/java/ru/gravit/launchserver/socket/Client.java @@ -10,6 +10,7 @@ public class Client { public Type type; public ClientProfile profile; public boolean isAuth; + public boolean checkSign; public ClientPermissions permissions; public String username; @@ -20,6 +21,7 @@ public Client(long session) { isAuth = false; permissions = ClientPermissions.DEFAULT; username = ""; + checkSign = false; } //Данные ваторизации public void up() { diff --git a/LaunchServer/src/main/java/ru/gravit/launchserver/socket/websocket/json/update/LauncherResponse.java b/LaunchServer/src/main/java/ru/gravit/launchserver/socket/websocket/json/update/LauncherResponse.java index c8f505e6..6af9eadc 100644 --- a/LaunchServer/src/main/java/ru/gravit/launchserver/socket/websocket/json/update/LauncherResponse.java +++ b/LaunchServer/src/main/java/ru/gravit/launchserver/socket/websocket/json/update/LauncherResponse.java @@ -27,7 +27,7 @@ public void execute(WebSocketService service, ChannelHandlerContext ctx, Client byte[] bytes = Base64.getDecoder().decode(hash); if(launcher_type == 1) // JAR { - byte[] hash = LaunchServer.server.launcherBinary.getHash(); + byte[] hash = LaunchServer.server.launcherBinary.getBytes().getDigest(); if(hash == null) service.sendObjectAndClose(ctx, new Result(true,JAR_URL)); if(Arrays.equals(bytes, hash)) { @@ -38,7 +38,7 @@ public void execute(WebSocketService service, ChannelHandlerContext ctx, Client } } else if(launcher_type == 2) //EXE { - byte[] hash = LaunchServer.server.launcherEXEBinary.getHash(); + byte[] hash = LaunchServer.server.launcherEXEBinary.getBytes().getDigest(); if(hash == null) service.sendObjectAndClose(ctx, new Result(true,EXE_URL)); if(Arrays.equals(bytes, hash)) { diff --git a/Launcher/runtime/dialog/dialog.js b/Launcher/runtime/dialog/dialog.js index b9b37cf1..f93b71e5 100644 --- a/Launcher/runtime/dialog/dialog.js +++ b/Launcher/runtime/dialog/dialog.js @@ -147,20 +147,23 @@ function verifyLauncher(e) { return; } settings.lastSign = result.sign; - settings.lastProfiles = result.profiles; - + processing.resetOverlay(); // Init offline if set if (settings.offline) { - initOffline(); + initOffline(); } - - // Update profiles list and hide overlay - updateProfilesList(result.profiles); - overlay.hide(0, function() { - if (cliParams.autoLogin) { - goAuth(null); - } + overlay.show(processing.overlay, function(event) makeProfilesRequest(function(result) { + settings.lastProfiles = result.profiles; + // Update profiles list and hide overlay + updateProfilesList(result.profiles); + overlay.hide(0, function() { + if (cliParams.autoLogin) { + goAuth(null); + } + }); }); + + })); } diff --git a/Launcher/runtime/dialog/overlay/processing/processing.js b/Launcher/runtime/dialog/overlay/processing/processing.js index 51c47117..6fbc1453 100644 --- a/Launcher/runtime/dialog/overlay/processing/processing.js +++ b/Launcher/runtime/dialog/overlay/processing/processing.js @@ -83,6 +83,22 @@ function makeLauncherRequest(callback) { task.updateMessage("Обновление списка серверов"); startTask(task); } +function makeProfilesRequest(callback) { + var task = newRequestTask(new ProfilesRequest()); + + // Set task properties and start + processing.setTaskProperties(task, callback, function() { + if (settings.offline) { + return; + } + + // Repeat request, but in offline mode + settings.offline = true; + overlay.swap(2500, processing.overlay, function() makeProfilesRequest(callback)); + }, false); + task.updateMessage("Обновление списка серверов"); + startTask(task); +} function makeAuthRequest(login, rsaPassword, callback) { var task = rsaPassword === null ? newTask(offlineAuthRequest(login)) : diff --git a/Launcher/runtime/engine/api.js b/Launcher/runtime/engine/api.js index f334a90c..7883ab19 100644 --- a/Launcher/runtime/engine/api.js +++ b/Launcher/runtime/engine/api.js @@ -24,6 +24,7 @@ var JoinServerRequest = JoinServerRequestClass.static; var CheckServerRequest = CheckServerRequestClass.static; var UpdateRequest = UpdateRequestClass.static; var LauncherRequest = LauncherRequestClass.static; +var ProfilesRequest = ProfilesRequestClass.static; var ProfileByUsernameRequest = ProfileByUsernameRequestClass.static; var ProfileByUUIDRequest = ProfileByUUIDRequestClass.static; var BatchProfileByUsernameRequest = BatchProfileByUsernameRequestClass.static; diff --git a/Launcher/src/main/java/ru/gravit/launcher/LauncherEngine.java b/Launcher/src/main/java/ru/gravit/launcher/LauncherEngine.java index 5301e9a1..994cc45d 100644 --- a/Launcher/src/main/java/ru/gravit/launcher/LauncherEngine.java +++ b/Launcher/src/main/java/ru/gravit/launcher/LauncherEngine.java @@ -3,8 +3,6 @@ import java.io.BufferedReader; import java.io.IOException; import java.net.URL; -import java.nio.file.Path; -import java.nio.file.Paths; import java.time.Duration; import java.time.Instant; import java.util.*; @@ -23,6 +21,8 @@ import ru.gravit.launcher.hasher.HashedDir; import ru.gravit.launcher.hasher.HashedEntry; import ru.gravit.launcher.hasher.HashedFile; +import ru.gravit.launcher.request.update.LauncherRequest; +import ru.gravit.launcher.request.update.ProfilesRequest; import ru.gravit.utils.HTTPRequest; import ru.gravit.utils.helper.CommonHelper; import ru.gravit.utils.helper.EnvHelper; @@ -42,7 +42,7 @@ import ru.gravit.launcher.request.auth.AuthRequest; import ru.gravit.launcher.request.auth.CheckServerRequest; import ru.gravit.launcher.request.auth.JoinServerRequest; -import ru.gravit.launcher.request.update.LauncherRequest; +import ru.gravit.launcher.request.update.LegacyLauncherRequest; import ru.gravit.launcher.request.update.UpdateRequest; import ru.gravit.launcher.request.uuid.BatchProfileByUsernameRequest; import ru.gravit.launcher.request.uuid.ProfileByUUIDRequest; @@ -90,6 +90,7 @@ public static void addLauncherClassBindings(Map bindings) { bindings.put("CheckServerRequestClass", CheckServerRequest.class); bindings.put("UpdateRequestClass", UpdateRequest.class); bindings.put("LauncherRequestClass", LauncherRequest.class); + bindings.put("ProfilesRequestClass", ProfilesRequest.class); bindings.put("ProfileByUsernameRequestClass", ProfileByUsernameRequest.class); bindings.put("ProfileByUUIDRequestClass", ProfileByUUIDRequest.class); bindings.put("BatchProfileByUsernameRequestClass", BatchProfileByUsernameRequest.class); diff --git a/Launcher/src/main/java/ru/gravit/launcher/client/ClientLauncher.java b/Launcher/src/main/java/ru/gravit/launcher/client/ClientLauncher.java index 302c627c..5e9b26a4 100644 --- a/Launcher/src/main/java/ru/gravit/launcher/client/ClientLauncher.java +++ b/Launcher/src/main/java/ru/gravit/launcher/client/ClientLauncher.java @@ -1,6 +1,5 @@ package ru.gravit.launcher.client; -import java.io.File; import java.io.IOException; import java.lang.ProcessBuilder.Redirect; import java.lang.invoke.MethodHandle; @@ -8,7 +7,6 @@ import java.lang.invoke.MethodType; import java.net.*; import java.nio.file.FileVisitResult; -import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.SimpleFileVisitor; @@ -35,10 +33,9 @@ import ru.gravit.utils.helper.JVMHelper.OS; import ru.gravit.utils.helper.LogHelper; import ru.gravit.utils.helper.SecurityHelper; -import ru.gravit.utils.helper.VerifyHelper; import ru.gravit.launcher.profiles.ClientProfile; import ru.gravit.launcher.profiles.PlayerProfile; -import ru.gravit.launcher.request.update.LauncherRequest; +import ru.gravit.launcher.request.update.LegacyLauncherRequest; import ru.gravit.launcher.serialize.HInput; import ru.gravit.launcher.serialize.HOutput; import ru.gravit.launcher.serialize.signed.SignedObjectHolder; @@ -440,7 +437,7 @@ public static void main(String... args) throws Throwable { Launcher.modulesManager.initModules(); // Verify ClientLauncher sign and classpath LogHelper.debug("Verifying ClientLauncher sign and classpath"); - SecurityHelper.verifySign(LauncherRequest.BINARY_PATH, params.launcherSign, publicKey); + SecurityHelper.verifySign(LegacyLauncherRequest.BINARY_PATH, params.launcherSign, publicKey); LinkedList classPath = resolveClassPathList(params.clientDir, profile.object.getClassPath()); for (Path classpathURL : classPath) { LauncherAgent.addJVMClassPath(classpathURL.toAbsolutePath().toString()); @@ -479,7 +476,7 @@ public void launchLocal(SignedObjectHolder assetHDir, SignedObjectHol SignedObjectHolder profile, Params params) throws Throwable { RSAPublicKey publicKey = Launcher.getConfig().publicKey; LogHelper.debug("Verifying ClientLauncher sign and classpath"); - SecurityHelper.verifySign(LauncherRequest.BINARY_PATH, params.launcherSign, publicKey); + SecurityHelper.verifySign(LegacyLauncherRequest.BINARY_PATH, params.launcherSign, publicKey); LinkedList classPath = resolveClassPathList(params.clientDir, profile.object.getClassPath()); for (Path classpathURL : classPath) { LauncherAgent.addJVMClassPath(classpathURL.toAbsolutePath().toString()); diff --git a/Launcher/src/main/java/ru/gravit/launcher/client/FunctionalBridge.java b/Launcher/src/main/java/ru/gravit/launcher/client/FunctionalBridge.java index c9fb4f39..13035952 100644 --- a/Launcher/src/main/java/ru/gravit/launcher/client/FunctionalBridge.java +++ b/Launcher/src/main/java/ru/gravit/launcher/client/FunctionalBridge.java @@ -5,8 +5,7 @@ import ru.gravit.launcher.hasher.FileNameMatcher; import ru.gravit.launcher.hasher.HashedDir; import ru.gravit.launcher.request.Request; -import ru.gravit.launcher.request.RequestException; -import ru.gravit.launcher.request.update.LauncherRequest; +import ru.gravit.launcher.request.update.LegacyLauncherRequest; import ru.gravit.launcher.serialize.signed.SignedObjectHolder; import ru.gravit.utils.helper.SecurityHelper; @@ -30,17 +29,17 @@ public HashedDirRunnable offlineUpdateRequest(String dirName, Path dir, SignedOb }; } @LauncherAPI - public LauncherRequest.Result offlineLauncherRequest() throws IOException, SignatureException { + public LegacyLauncherRequest.Result offlineLauncherRequest() throws IOException, SignatureException { if (settings.lastSign == null || settings.lastProfiles.isEmpty()) { Request.requestError("Запуск в оффлайн-режиме невозможен"); } // Verify launcher signature - SecurityHelper.verifySign(LauncherRequest.BINARY_PATH, + SecurityHelper.verifySign(LegacyLauncherRequest.BINARY_PATH, settings.lastSign, Launcher.getConfig().publicKey); // Return last sign and profiles - return new LauncherRequest.Result(null,settings.lastSign,settings.lastProfiles); + return new LegacyLauncherRequest.Result(null,settings.lastSign,settings.lastProfiles); } @FunctionalInterface public interface HashedDirRunnable { diff --git a/LauncherAPI/src/main/java/ru/gravit/launcher/request/update/LauncherRequest.java b/LauncherAPI/src/main/java/ru/gravit/launcher/request/update/LauncherRequest.java index 272eef1c..edb6d13f 100644 --- a/LauncherAPI/src/main/java/ru/gravit/launcher/request/update/LauncherRequest.java +++ b/LauncherAPI/src/main/java/ru/gravit/launcher/request/update/LauncherRequest.java @@ -1,39 +1,34 @@ package ru.gravit.launcher.request.update; +import ru.gravit.launcher.Launcher; +import ru.gravit.launcher.LauncherAPI; +import ru.gravit.launcher.LauncherConfig; +import ru.gravit.launcher.profiles.ClientProfile; +import ru.gravit.launcher.request.Request; +import ru.gravit.launcher.request.RequestType; +import ru.gravit.launcher.serialize.HInput; +import ru.gravit.launcher.serialize.HOutput; +import ru.gravit.launcher.serialize.signed.SignedObjectHolder; +import ru.gravit.utils.helper.IOHelper; +import ru.gravit.utils.helper.JVMHelper; +import ru.gravit.utils.helper.LogHelper; +import ru.gravit.utils.helper.SecurityHelper; + import java.io.IOException; import java.nio.file.Path; import java.security.SignatureException; import java.security.interfaces.RSAPublicKey; import java.util.ArrayList; -import java.util.Collections; import java.util.List; -import ru.gravit.launcher.Launcher; -import ru.gravit.launcher.LauncherAPI; -import ru.gravit.launcher.LauncherConfig; -import ru.gravit.utils.helper.IOHelper; -import ru.gravit.utils.helper.JVMHelper; -import ru.gravit.utils.helper.LogHelper; -import ru.gravit.utils.helper.SecurityHelper; -import ru.gravit.launcher.profiles.ClientProfile; -import ru.gravit.launcher.request.Request; -import ru.gravit.launcher.request.RequestType; -import ru.gravit.launcher.request.update.LauncherRequest.Result; -import ru.gravit.launcher.serialize.HInput; -import ru.gravit.launcher.serialize.HOutput; -import ru.gravit.launcher.serialize.signed.SignedObjectHolder; - -public final class LauncherRequest extends Request { +public final class LauncherRequest extends Request { public static final class Result { - @LauncherAPI - public final List> profiles; private final byte[] binary; - private final byte[] sign; + private final byte[] digest; - public Result(byte[] binary, byte[] sign, List> profiles) { + public Result(byte[] binary, byte[] sign) { this.binary = binary == null ? null : binary.clone(); - this.sign = sign.clone(); - this.profiles = Collections.unmodifiableList(profiles); + this.digest = sign.clone(); } @LauncherAPI @@ -42,8 +37,8 @@ public byte[] getBinary() { } @LauncherAPI - public byte[] getSign() { - return sign.clone(); + public byte[] getDigest() { + return digest.clone(); } } @@ -55,9 +50,6 @@ public byte[] getSign() { @LauncherAPI public static void update(LauncherConfig config, Result result) throws SignatureException, IOException { - SecurityHelper.verifySign(result.binary, result.sign, config.publicKey); - - // Prepare process builder to start new instance (java -jar works for Launch4J's EXE too) List args = new ArrayList<>(8); args.add(IOHelper.resolveJavaBin(null).toString()); if (LogHelper.isDebugEnabled()) @@ -96,31 +88,23 @@ public Integer getType() { @Override @SuppressWarnings("CallToSystemExit") protected Result requestDo(HInput input, HOutput output) throws Exception { + RSAPublicKey publicKey = config.publicKey; + Path launcherPath = IOHelper.getCodeSource(LauncherRequest.class); + byte[] digest = SecurityHelper.digest(SecurityHelper.DigestAlgorithm.SHA512,launcherPath); + byte[] sign; output.writeBoolean(EXE_BINARY); + output.writeByteArray(digest,0); output.flush(); readError(input); // Verify launcher sign - RSAPublicKey publicKey = config.publicKey; - byte[] sign = input.readByteArray(-SecurityHelper.RSA_KEY_LENGTH); - boolean shouldUpdate = !SecurityHelper.isValidSign(BINARY_PATH, sign, publicKey); - - // Update launcher if need - output.writeBoolean(shouldUpdate); - output.flush(); + boolean shouldUpdate = input.readBoolean(); if (shouldUpdate) { byte[] binary = input.readByteArray(0); - SecurityHelper.verifySign(binary, sign, config.publicKey); - return new Result(binary, sign, Collections.emptyList()); + return new Result(binary, digest); } - // Read clients profiles list - int count = input.readLength(0); - List> profiles = new ArrayList<>(count); - for (int i = 0; i < count; i++) - profiles.add(new SignedObjectHolder<>(input, publicKey, ClientProfile.RO_ADAPTER)); - // Return request result - return new Result(null, sign, profiles); + return new Result(null, digest); } } diff --git a/LauncherAPI/src/main/java/ru/gravit/launcher/request/update/LegacyLauncherRequest.java b/LauncherAPI/src/main/java/ru/gravit/launcher/request/update/LegacyLauncherRequest.java new file mode 100644 index 00000000..8a3109d2 --- /dev/null +++ b/LauncherAPI/src/main/java/ru/gravit/launcher/request/update/LegacyLauncherRequest.java @@ -0,0 +1,126 @@ +package ru.gravit.launcher.request.update; + +import java.io.IOException; +import java.nio.file.Path; +import java.security.SignatureException; +import java.security.interfaces.RSAPublicKey; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import ru.gravit.launcher.Launcher; +import ru.gravit.launcher.LauncherAPI; +import ru.gravit.launcher.LauncherConfig; +import ru.gravit.utils.helper.IOHelper; +import ru.gravit.utils.helper.JVMHelper; +import ru.gravit.utils.helper.LogHelper; +import ru.gravit.utils.helper.SecurityHelper; +import ru.gravit.launcher.profiles.ClientProfile; +import ru.gravit.launcher.request.Request; +import ru.gravit.launcher.request.RequestType; +import ru.gravit.launcher.request.update.LegacyLauncherRequest.Result; +import ru.gravit.launcher.serialize.HInput; +import ru.gravit.launcher.serialize.HOutput; +import ru.gravit.launcher.serialize.signed.SignedObjectHolder; + +public final class LegacyLauncherRequest extends Request { + public static final class Result { + @LauncherAPI + public final List> profiles; + private final byte[] binary; + private final byte[] sign; + + public Result(byte[] binary, byte[] sign, List> profiles) { + this.binary = binary == null ? null : binary.clone(); + this.sign = sign.clone(); + this.profiles = Collections.unmodifiableList(profiles); + } + + @LauncherAPI + public byte[] getBinary() { + return binary == null ? null : binary.clone(); + } + + @LauncherAPI + public byte[] getSign() { + return sign.clone(); + } + } + + @LauncherAPI + public static final Path BINARY_PATH = IOHelper.getCodeSource(Launcher.class); + + @LauncherAPI + public static final boolean EXE_BINARY = IOHelper.hasExtension(BINARY_PATH, "exe"); + + @LauncherAPI + public static void update(LauncherConfig config, Result result) throws SignatureException, IOException { + SecurityHelper.verifySign(result.binary, result.sign, config.publicKey); + + // Prepare process builder to start new instance (java -jar works for Launch4J's EXE too) + List args = new ArrayList<>(8); + args.add(IOHelper.resolveJavaBin(null).toString()); + if (LogHelper.isDebugEnabled()) + args.add(JVMHelper.jvmProperty(LogHelper.DEBUG_PROPERTY, Boolean.toString(LogHelper.isDebugEnabled()))); + if (LauncherConfig.ADDRESS_OVERRIDE != null) + args.add(JVMHelper.jvmProperty(LauncherConfig.ADDRESS_OVERRIDE_PROPERTY, LauncherConfig.ADDRESS_OVERRIDE)); + args.add("-jar"); + args.add(BINARY_PATH.toString()); + ProcessBuilder builder = new ProcessBuilder(args.toArray(new String[0])); + builder.inheritIO(); + + // Rewrite and start new instance + IOHelper.write(BINARY_PATH, result.binary); + builder.start(); + + // Kill current instance + JVMHelper.RUNTIME.exit(255); + throw new AssertionError("Why Launcher wasn't restarted?!"); + } + + @LauncherAPI + public LegacyLauncherRequest() { + this(null); + } + + @LauncherAPI + public LegacyLauncherRequest(LauncherConfig config) { + super(config); + } + + @Override + public Integer getType() { + return RequestType.LEGACYLAUNCHER.getNumber(); + } + + @Override + @SuppressWarnings("CallToSystemExit") + protected Result requestDo(HInput input, HOutput output) throws Exception { + output.writeBoolean(EXE_BINARY); + output.flush(); + readError(input); + + // Verify launcher sign + RSAPublicKey publicKey = config.publicKey; + byte[] sign = input.readByteArray(-SecurityHelper.RSA_KEY_LENGTH); + boolean shouldUpdate = !SecurityHelper.isValidSign(BINARY_PATH, sign, publicKey); + + // Update launcher if need + output.writeBoolean(shouldUpdate); + output.flush(); + if (shouldUpdate) { + byte[] binary = input.readByteArray(0); + SecurityHelper.verifySign(binary, sign, config.publicKey); + return new Result(binary, sign, Collections.emptyList()); + } + + // Read clients profiles list + int count = input.readLength(0); + List> profiles = new ArrayList<>(count); + for (int i = 0; i < count; i++) + profiles.add(new SignedObjectHolder<>(input, publicKey, ClientProfile.RO_ADAPTER)); + + // Return request result + return new Result(null, sign, profiles); + } +} diff --git a/libLauncher/src/main/java/ru/gravit/launcher/request/RequestType.java b/libLauncher/src/main/java/ru/gravit/launcher/request/RequestType.java index ce465dd7..ccedb14f 100644 --- a/libLauncher/src/main/java/ru/gravit/launcher/request/RequestType.java +++ b/libLauncher/src/main/java/ru/gravit/launcher/request/RequestType.java @@ -8,10 +8,10 @@ public enum RequestType implements EnumSerializer.Itf { PING(0), // Ping request - LAUNCHER(1), UPDATE(2), UPDATE_LIST(3), // Update requests + LEGACYLAUNCHER(1), UPDATE(2), UPDATE_LIST(3), // Update requests AUTH(4), JOIN_SERVER(5), CHECK_SERVER(6), // Auth requests PROFILE_BY_USERNAME(7), PROFILE_BY_UUID(8), BATCH_PROFILE_BY_USERNAME(9), // Profile requests - PROFILES(10),SERVERAUTH(11), SETPROFILE(12), + PROFILES(10),SERVERAUTH(11), SETPROFILE(12),LAUNCHER(13), CUSTOM(255); // Custom requests private static final EnumSerializer SERIALIZER = new EnumSerializer<>(RequestType.class); diff --git a/libLauncher/src/main/java/ru/gravit/launcher/serialize/signed/DigestBytesHolder.java b/libLauncher/src/main/java/ru/gravit/launcher/serialize/signed/DigestBytesHolder.java new file mode 100644 index 00000000..d74f59d9 --- /dev/null +++ b/libLauncher/src/main/java/ru/gravit/launcher/serialize/signed/DigestBytesHolder.java @@ -0,0 +1,52 @@ +package ru.gravit.launcher.serialize.signed; + +import ru.gravit.launcher.LauncherAPI; +import ru.gravit.launcher.serialize.HInput; +import ru.gravit.launcher.serialize.HOutput; +import ru.gravit.launcher.serialize.stream.StreamObject; +import ru.gravit.utils.helper.SecurityHelper; + +import java.io.IOException; +import java.security.SignatureException; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.util.Arrays; + +public class DigestBytesHolder extends StreamObject { + protected final byte[] bytes; + private final byte[] digest; + + @LauncherAPI + public DigestBytesHolder(byte[] bytes, byte[] digest, SecurityHelper.DigestAlgorithm algorithm) throws SignatureException { + if(Arrays.equals(SecurityHelper.digest(algorithm,bytes),digest)) throw new SignatureException("Invalid digest"); + this.bytes = bytes.clone(); + this.digest = digest.clone(); + } + + @LauncherAPI + public DigestBytesHolder(byte[] bytes, SecurityHelper.DigestAlgorithm algorithm) { + this.bytes = bytes.clone(); + this.digest = SecurityHelper.digest(algorithm,bytes); + } + + @LauncherAPI + public DigestBytesHolder(HInput input, SecurityHelper.DigestAlgorithm algorithm) throws IOException, SignatureException { + this(input.readByteArray(0), input.readByteArray(-SecurityHelper.RSA_KEY_LENGTH),algorithm); + } + + @LauncherAPI + public final byte[] getBytes() { + return bytes.clone(); + } + + @LauncherAPI + public final byte[] getDigest() { + return digest.clone(); + } + + @Override + public final void write(HOutput output) throws IOException { + output.writeByteArray(bytes, 0); + output.writeByteArray(digest, -SecurityHelper.RSA_KEY_LENGTH); + } +}