Крупное обновление LauncherRequest

Есть совместимость с предыдущими версиями
This commit is contained in:
Gravit 2018-10-25 19:36:57 +07:00
parent 23677bd5a3
commit 96bca9aa5d
No known key found for this signature in database
GPG key ID: 061981E1E85D3216
17 changed files with 328 additions and 104 deletions

View file

@ -4,6 +4,7 @@
import java.nio.file.Path; import java.nio.file.Path;
import ru.gravit.launcher.LauncherAPI; import ru.gravit.launcher.LauncherAPI;
import ru.gravit.launcher.serialize.signed.DigestBytesHolder;
import ru.gravit.utils.helper.IOHelper; import ru.gravit.utils.helper.IOHelper;
import ru.gravit.launcher.serialize.signed.SignedBytesHolder; import ru.gravit.launcher.serialize.signed.SignedBytesHolder;
import ru.gravit.launchserver.LaunchServer; import ru.gravit.launchserver.LaunchServer;
@ -15,8 +16,8 @@ public abstract class LauncherBinary {
protected final Path binaryFile; protected final Path binaryFile;
protected final Path syncBinaryFile; protected final Path syncBinaryFile;
private volatile SignedBytesHolder binary; private volatile DigestBytesHolder binary;
private volatile byte[] hash; private volatile byte[] sign;
protected LauncherBinary(LaunchServer server, Path binaryFile) { protected LauncherBinary(LaunchServer server, Path binaryFile) {
@ -41,19 +42,19 @@ public final boolean exists() {
} }
public final SignedBytesHolder getBytes() { public final DigestBytesHolder getBytes() {
return binary; return binary;
} }
public final byte[] getSign() {
public final byte[] getHash() { return sign;
return hash;
} }
public final boolean sync() throws IOException { public final boolean sync() throws IOException {
boolean exists = exists(); boolean exists = exists();
binary = exists ? new SignedBytesHolder(IOHelper.read(syncBinaryFile), server.privateKey) : null; binary = exists ? new DigestBytesHolder(IOHelper.read(syncBinaryFile), SecurityHelper.DigestAlgorithm.SHA512) : null;
hash = exists ? SecurityHelper.digest(SecurityHelper.DigestAlgorithm.SHA512,IOHelper.newInput(syncBinaryFile)) : null; sign = exists ? SecurityHelper.sign(IOHelper.read(syncBinaryFile),server.privateKey) : null;
return exists; return exists;
} }
} }

View file

@ -4,8 +4,8 @@
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import ru.gravit.launcher.LauncherAPI;
import ru.gravit.launchserver.response.auth.*; import ru.gravit.launchserver.response.auth.*;
import ru.gravit.launchserver.response.update.*;
import ru.gravit.utils.helper.LogHelper; import ru.gravit.utils.helper.LogHelper;
import ru.gravit.launcher.request.RequestException; import ru.gravit.launcher.request.RequestException;
import ru.gravit.launcher.request.RequestType; import ru.gravit.launcher.request.RequestType;
@ -15,10 +15,6 @@
import ru.gravit.launchserver.response.profile.BatchProfileByUsernameResponse; import ru.gravit.launchserver.response.profile.BatchProfileByUsernameResponse;
import ru.gravit.launchserver.response.profile.ProfileByUUIDResponse; import ru.gravit.launchserver.response.profile.ProfileByUUIDResponse;
import ru.gravit.launchserver.response.profile.ProfileByUsernameResponse; 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 { public abstract class Response {
@FunctionalInterface @FunctionalInterface
@ -47,6 +43,7 @@ public static void registerResponses() {
registerResponse(RequestType.PROFILE_BY_USERNAME.getNumber(), ProfileByUsernameResponse::new); registerResponse(RequestType.PROFILE_BY_USERNAME.getNumber(), ProfileByUsernameResponse::new);
registerResponse(RequestType.PROFILE_BY_UUID.getNumber(), ProfileByUUIDResponse::new); registerResponse(RequestType.PROFILE_BY_UUID.getNumber(), ProfileByUUIDResponse::new);
registerResponse(RequestType.LEGACYLAUNCHER.getNumber(), LegacyLauncherResponse::new);
registerResponse(RequestType.LAUNCHER.getNumber(), LauncherResponse::new); registerResponse(RequestType.LAUNCHER.getNumber(), LauncherResponse::new);
registerResponse(RequestType.UPDATE_LIST.getNumber(), UpdateListResponse::new); registerResponse(RequestType.UPDATE_LIST.getNumber(), UpdateListResponse::new);
registerResponse(RequestType.UPDATE.getNumber(), UpdateResponse::new); registerResponse(RequestType.UPDATE.getNumber(), UpdateResponse::new);

View file

@ -1,16 +1,21 @@
package ru.gravit.launchserver.response.update; 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.profiles.ClientProfile;
import ru.gravit.launcher.serialize.HInput; import ru.gravit.launcher.serialize.HInput;
import ru.gravit.launcher.serialize.HOutput; 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.SignedBytesHolder;
import ru.gravit.launcher.serialize.signed.SignedObjectHolder; import ru.gravit.launcher.serialize.signed.SignedObjectHolder;
import ru.gravit.launchserver.LaunchServer; import ru.gravit.launchserver.LaunchServer;
import ru.gravit.launchserver.manangers.SessionManager;
import ru.gravit.launchserver.response.Response; 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 { public final class LauncherResponse extends Response {
@ -21,25 +26,23 @@ public LauncherResponse(LaunchServer server, long session, HInput input, HOutput
@Override @Override
public void reply() throws IOException { public void reply() throws IOException {
// Resolve launcher binary // Resolve launcher binary
SignedBytesHolder bytes = (input.readBoolean() ? server.launcherEXEBinary : server.launcherBinary).getBytes(); DigestBytesHolder bytes = (input.readBoolean() ? server.launcherEXEBinary : server.launcherBinary).getBytes();
if (bytes == null) { if (bytes == null) {
requestError("Missing launcher binary"); requestError("Missing launcher binary");
return; return;
} }
writeNoError(output); Client client = server.sessionManager.getOrNewClient(session);
byte[] digest = input.readByteArray(0);
// Update launcher binary if(!Arrays.equals(bytes.getDigest(), digest))
output.writeByteArray(bytes.getSign(), -SecurityHelper.RSA_KEY_LENGTH); {
output.flush(); writeNoError(output);
if (input.readBoolean()) { output.writeBoolean(true);
output.writeByteArray(bytes.getBytes(), 0); output.writeByteArray(bytes.getBytes(), 0);
return; // Launcher will be restarted client.checkSign = false;
return;
} }
writeNoError(output);
// Write clients profiles list output.writeBoolean(false);
Collection<SignedObjectHolder<ClientProfile>> profiles = server.getProfiles(); client.checkSign = true;
output.writeLength(profiles.size(), 0);
for (SignedObjectHolder<ClientProfile> profile : profiles)
profile.write(output);
} }
} }

View file

@ -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");
}
}

View file

@ -23,7 +23,7 @@ public void reply() throws IOException {
// Resolve launcher binary // Resolve launcher binary
Client client = server.sessionManager.getClient(session); Client client = server.sessionManager.getClient(session);
input.readBoolean(); 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); LogHelper.warning("User session: %d ip %s try get profiles",session,ip);
requestError("Assess denied"); requestError("Assess denied");
return; return;

View file

@ -10,6 +10,7 @@ public class Client {
public Type type; public Type type;
public ClientProfile profile; public ClientProfile profile;
public boolean isAuth; public boolean isAuth;
public boolean checkSign;
public ClientPermissions permissions; public ClientPermissions permissions;
public String username; public String username;
@ -20,6 +21,7 @@ public Client(long session) {
isAuth = false; isAuth = false;
permissions = ClientPermissions.DEFAULT; permissions = ClientPermissions.DEFAULT;
username = ""; username = "";
checkSign = false;
} }
//Данные ваторизации //Данные ваторизации
public void up() { public void up() {

View file

@ -27,7 +27,7 @@ public void execute(WebSocketService service, ChannelHandlerContext ctx, Client
byte[] bytes = Base64.getDecoder().decode(hash); byte[] bytes = Base64.getDecoder().decode(hash);
if(launcher_type == 1) // JAR 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(hash == null) service.sendObjectAndClose(ctx, new Result(true,JAR_URL));
if(Arrays.equals(bytes, hash)) if(Arrays.equals(bytes, hash))
{ {
@ -38,7 +38,7 @@ public void execute(WebSocketService service, ChannelHandlerContext ctx, Client
} }
} else if(launcher_type == 2) //EXE } 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(hash == null) service.sendObjectAndClose(ctx, new Result(true,EXE_URL));
if(Arrays.equals(bytes, hash)) if(Arrays.equals(bytes, hash))
{ {

View file

@ -147,20 +147,23 @@ function verifyLauncher(e) {
return; return;
} }
settings.lastSign = result.sign; settings.lastSign = result.sign;
settings.lastProfiles = result.profiles; processing.resetOverlay();
// Init offline if set // Init offline if set
if (settings.offline) { if (settings.offline) {
initOffline(); initOffline();
} }
overlay.show(processing.overlay, function(event) makeProfilesRequest(function(result) {
// Update profiles list and hide overlay settings.lastProfiles = result.profiles;
updateProfilesList(result.profiles); // Update profiles list and hide overlay
overlay.hide(0, function() { updateProfilesList(result.profiles);
if (cliParams.autoLogin) { overlay.hide(0, function() {
goAuth(null); if (cliParams.autoLogin) {
} goAuth(null);
}
});
}); });
})); }));
} }

View file

@ -83,6 +83,22 @@ function makeLauncherRequest(callback) {
task.updateMessage("Обновление списка серверов"); task.updateMessage("Обновление списка серверов");
startTask(task); 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) { function makeAuthRequest(login, rsaPassword, callback) {
var task = rsaPassword === null ? newTask(offlineAuthRequest(login)) : var task = rsaPassword === null ? newTask(offlineAuthRequest(login)) :

View file

@ -24,6 +24,7 @@ var JoinServerRequest = JoinServerRequestClass.static;
var CheckServerRequest = CheckServerRequestClass.static; var CheckServerRequest = CheckServerRequestClass.static;
var UpdateRequest = UpdateRequestClass.static; var UpdateRequest = UpdateRequestClass.static;
var LauncherRequest = LauncherRequestClass.static; var LauncherRequest = LauncherRequestClass.static;
var ProfilesRequest = ProfilesRequestClass.static;
var ProfileByUsernameRequest = ProfileByUsernameRequestClass.static; var ProfileByUsernameRequest = ProfileByUsernameRequestClass.static;
var ProfileByUUIDRequest = ProfileByUUIDRequestClass.static; var ProfileByUUIDRequest = ProfileByUUIDRequestClass.static;
var BatchProfileByUsernameRequest = BatchProfileByUsernameRequestClass.static; var BatchProfileByUsernameRequest = BatchProfileByUsernameRequestClass.static;

View file

@ -3,8 +3,6 @@
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.net.URL; import java.net.URL;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Duration; import java.time.Duration;
import java.time.Instant; import java.time.Instant;
import java.util.*; import java.util.*;
@ -23,6 +21,8 @@
import ru.gravit.launcher.hasher.HashedDir; import ru.gravit.launcher.hasher.HashedDir;
import ru.gravit.launcher.hasher.HashedEntry; import ru.gravit.launcher.hasher.HashedEntry;
import ru.gravit.launcher.hasher.HashedFile; 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.HTTPRequest;
import ru.gravit.utils.helper.CommonHelper; import ru.gravit.utils.helper.CommonHelper;
import ru.gravit.utils.helper.EnvHelper; import ru.gravit.utils.helper.EnvHelper;
@ -42,7 +42,7 @@
import ru.gravit.launcher.request.auth.AuthRequest; import ru.gravit.launcher.request.auth.AuthRequest;
import ru.gravit.launcher.request.auth.CheckServerRequest; import ru.gravit.launcher.request.auth.CheckServerRequest;
import ru.gravit.launcher.request.auth.JoinServerRequest; 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.update.UpdateRequest;
import ru.gravit.launcher.request.uuid.BatchProfileByUsernameRequest; import ru.gravit.launcher.request.uuid.BatchProfileByUsernameRequest;
import ru.gravit.launcher.request.uuid.ProfileByUUIDRequest; import ru.gravit.launcher.request.uuid.ProfileByUUIDRequest;
@ -90,6 +90,7 @@ public static void addLauncherClassBindings(Map<String, Object> bindings) {
bindings.put("CheckServerRequestClass", CheckServerRequest.class); bindings.put("CheckServerRequestClass", CheckServerRequest.class);
bindings.put("UpdateRequestClass", UpdateRequest.class); bindings.put("UpdateRequestClass", UpdateRequest.class);
bindings.put("LauncherRequestClass", LauncherRequest.class); bindings.put("LauncherRequestClass", LauncherRequest.class);
bindings.put("ProfilesRequestClass", ProfilesRequest.class);
bindings.put("ProfileByUsernameRequestClass", ProfileByUsernameRequest.class); bindings.put("ProfileByUsernameRequestClass", ProfileByUsernameRequest.class);
bindings.put("ProfileByUUIDRequestClass", ProfileByUUIDRequest.class); bindings.put("ProfileByUUIDRequestClass", ProfileByUUIDRequest.class);
bindings.put("BatchProfileByUsernameRequestClass", BatchProfileByUsernameRequest.class); bindings.put("BatchProfileByUsernameRequestClass", BatchProfileByUsernameRequest.class);

View file

@ -1,6 +1,5 @@
package ru.gravit.launcher.client; package ru.gravit.launcher.client;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.lang.ProcessBuilder.Redirect; import java.lang.ProcessBuilder.Redirect;
import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandle;
@ -8,7 +7,6 @@
import java.lang.invoke.MethodType; import java.lang.invoke.MethodType;
import java.net.*; import java.net.*;
import java.nio.file.FileVisitResult; import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor; import java.nio.file.SimpleFileVisitor;
@ -35,10 +33,9 @@
import ru.gravit.utils.helper.JVMHelper.OS; import ru.gravit.utils.helper.JVMHelper.OS;
import ru.gravit.utils.helper.LogHelper; import ru.gravit.utils.helper.LogHelper;
import ru.gravit.utils.helper.SecurityHelper; import ru.gravit.utils.helper.SecurityHelper;
import ru.gravit.utils.helper.VerifyHelper;
import ru.gravit.launcher.profiles.ClientProfile; import ru.gravit.launcher.profiles.ClientProfile;
import ru.gravit.launcher.profiles.PlayerProfile; 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.HInput;
import ru.gravit.launcher.serialize.HOutput; import ru.gravit.launcher.serialize.HOutput;
import ru.gravit.launcher.serialize.signed.SignedObjectHolder; import ru.gravit.launcher.serialize.signed.SignedObjectHolder;
@ -440,7 +437,7 @@ public static void main(String... args) throws Throwable {
Launcher.modulesManager.initModules(); Launcher.modulesManager.initModules();
// Verify ClientLauncher sign and classpath // Verify ClientLauncher sign and classpath
LogHelper.debug("Verifying 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<Path> classPath = resolveClassPathList(params.clientDir, profile.object.getClassPath()); LinkedList<Path> classPath = resolveClassPathList(params.clientDir, profile.object.getClassPath());
for (Path classpathURL : classPath) { for (Path classpathURL : classPath) {
LauncherAgent.addJVMClassPath(classpathURL.toAbsolutePath().toString()); LauncherAgent.addJVMClassPath(classpathURL.toAbsolutePath().toString());
@ -479,7 +476,7 @@ public void launchLocal(SignedObjectHolder<HashedDir> assetHDir, SignedObjectHol
SignedObjectHolder<ClientProfile> profile, Params params) throws Throwable { SignedObjectHolder<ClientProfile> profile, Params params) throws Throwable {
RSAPublicKey publicKey = Launcher.getConfig().publicKey; RSAPublicKey publicKey = Launcher.getConfig().publicKey;
LogHelper.debug("Verifying 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<Path> classPath = resolveClassPathList(params.clientDir, profile.object.getClassPath()); LinkedList<Path> classPath = resolveClassPathList(params.clientDir, profile.object.getClassPath());
for (Path classpathURL : classPath) { for (Path classpathURL : classPath) {
LauncherAgent.addJVMClassPath(classpathURL.toAbsolutePath().toString()); LauncherAgent.addJVMClassPath(classpathURL.toAbsolutePath().toString());

View file

@ -5,8 +5,7 @@
import ru.gravit.launcher.hasher.FileNameMatcher; import ru.gravit.launcher.hasher.FileNameMatcher;
import ru.gravit.launcher.hasher.HashedDir; import ru.gravit.launcher.hasher.HashedDir;
import ru.gravit.launcher.request.Request; import ru.gravit.launcher.request.Request;
import ru.gravit.launcher.request.RequestException; import ru.gravit.launcher.request.update.LegacyLauncherRequest;
import ru.gravit.launcher.request.update.LauncherRequest;
import ru.gravit.launcher.serialize.signed.SignedObjectHolder; import ru.gravit.launcher.serialize.signed.SignedObjectHolder;
import ru.gravit.utils.helper.SecurityHelper; import ru.gravit.utils.helper.SecurityHelper;
@ -30,17 +29,17 @@ public HashedDirRunnable offlineUpdateRequest(String dirName, Path dir, SignedOb
}; };
} }
@LauncherAPI @LauncherAPI
public LauncherRequest.Result offlineLauncherRequest() throws IOException, SignatureException { public LegacyLauncherRequest.Result offlineLauncherRequest() throws IOException, SignatureException {
if (settings.lastSign == null || settings.lastProfiles.isEmpty()) { if (settings.lastSign == null || settings.lastProfiles.isEmpty()) {
Request.requestError("Запуск в оффлайн-режиме невозможен"); Request.requestError("Запуск в оффлайн-режиме невозможен");
} }
// Verify launcher signature // Verify launcher signature
SecurityHelper.verifySign(LauncherRequest.BINARY_PATH, SecurityHelper.verifySign(LegacyLauncherRequest.BINARY_PATH,
settings.lastSign, Launcher.getConfig().publicKey); settings.lastSign, Launcher.getConfig().publicKey);
// Return last sign and profiles // Return last sign and profiles
return new LauncherRequest.Result(null,settings.lastSign,settings.lastProfiles); return new LegacyLauncherRequest.Result(null,settings.lastSign,settings.lastProfiles);
} }
@FunctionalInterface @FunctionalInterface
public interface HashedDirRunnable { public interface HashedDirRunnable {

View file

@ -1,39 +1,34 @@
package ru.gravit.launcher.request.update; 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.io.IOException;
import java.nio.file.Path; import java.nio.file.Path;
import java.security.SignatureException; import java.security.SignatureException;
import java.security.interfaces.RSAPublicKey; import java.security.interfaces.RSAPublicKey;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import ru.gravit.launcher.Launcher; public final class LauncherRequest extends Request<LauncherRequest.Result> {
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<Result> {
public static final class Result { public static final class Result {
@LauncherAPI
public final List<SignedObjectHolder<ClientProfile>> profiles;
private final byte[] binary; private final byte[] binary;
private final byte[] sign; private final byte[] digest;
public Result(byte[] binary, byte[] sign, List<SignedObjectHolder<ClientProfile>> profiles) { public Result(byte[] binary, byte[] sign) {
this.binary = binary == null ? null : binary.clone(); this.binary = binary == null ? null : binary.clone();
this.sign = sign.clone(); this.digest = sign.clone();
this.profiles = Collections.unmodifiableList(profiles);
} }
@LauncherAPI @LauncherAPI
@ -42,8 +37,8 @@ public byte[] getBinary() {
} }
@LauncherAPI @LauncherAPI
public byte[] getSign() { public byte[] getDigest() {
return sign.clone(); return digest.clone();
} }
} }
@ -55,9 +50,6 @@ public byte[] getSign() {
@LauncherAPI @LauncherAPI
public static void update(LauncherConfig config, Result result) throws SignatureException, IOException { 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<String> args = new ArrayList<>(8); List<String> args = new ArrayList<>(8);
args.add(IOHelper.resolveJavaBin(null).toString()); args.add(IOHelper.resolveJavaBin(null).toString());
if (LogHelper.isDebugEnabled()) if (LogHelper.isDebugEnabled())
@ -96,31 +88,23 @@ public Integer getType() {
@Override @Override
@SuppressWarnings("CallToSystemExit") @SuppressWarnings("CallToSystemExit")
protected Result requestDo(HInput input, HOutput output) throws Exception { 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.writeBoolean(EXE_BINARY);
output.writeByteArray(digest,0);
output.flush(); output.flush();
readError(input); readError(input);
// Verify launcher sign // Verify launcher sign
RSAPublicKey publicKey = config.publicKey; boolean shouldUpdate = input.readBoolean();
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) { if (shouldUpdate) {
byte[] binary = input.readByteArray(0); byte[] binary = input.readByteArray(0);
SecurityHelper.verifySign(binary, sign, config.publicKey); return new Result(binary, digest);
return new Result(binary, sign, Collections.emptyList());
} }
// Read clients profiles list
int count = input.readLength(0);
List<SignedObjectHolder<ClientProfile>> profiles = new ArrayList<>(count);
for (int i = 0; i < count; i++)
profiles.add(new SignedObjectHolder<>(input, publicKey, ClientProfile.RO_ADAPTER));
// Return request result // Return request result
return new Result(null, sign, profiles); return new Result(null, digest);
} }
} }

View file

@ -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<Result> {
public static final class Result {
@LauncherAPI
public final List<SignedObjectHolder<ClientProfile>> profiles;
private final byte[] binary;
private final byte[] sign;
public Result(byte[] binary, byte[] sign, List<SignedObjectHolder<ClientProfile>> 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<String> 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<SignedObjectHolder<ClientProfile>> 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);
}
}

View file

@ -8,10 +8,10 @@
public enum RequestType implements EnumSerializer.Itf { public enum RequestType implements EnumSerializer.Itf {
PING(0), // Ping request 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 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 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 CUSTOM(255); // Custom requests
private static final EnumSerializer<RequestType> SERIALIZER = new EnumSerializer<>(RequestType.class); private static final EnumSerializer<RequestType> SERIALIZER = new EnumSerializer<>(RequestType.class);

View file

@ -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);
}
}