mirror of
https://github.com/ViaVersion/ViaProxy.git
synced 2024-11-14 19:15:08 -05:00
Improved chat signing implementation and lazy load account runtime data
This commit is contained in:
parent
d4660a9b97
commit
9510c672a7
15 changed files with 181 additions and 201 deletions
|
@ -51,12 +51,12 @@ public class Options {
|
|||
public static Account MC_ACCOUNT;
|
||||
public static String CLASSIC_MP_PASS;
|
||||
public static Boolean LEGACY_SKIN_LOADING;
|
||||
public static boolean CHAT_SIGNING;
|
||||
|
||||
// CLI only config options
|
||||
public static int COMPRESSION_THRESHOLD = 256;
|
||||
public static boolean SRV_MODE; // Example: lenni0451.net_25565_1.8.x.viaproxy.127.0.0.1.nip.io
|
||||
public static boolean INTERNAL_SRV_MODE; // Example: ip\7port\7version\7mppass
|
||||
public static boolean LOCAL_SOCKET_AUTH;
|
||||
public static String RESOURCE_PACK_URL; // Example: http://example.com/resourcepack.zip
|
||||
public static boolean SERVER_HAPROXY_PROTOCOL;
|
||||
public static boolean LEGACY_CLIENT_PASSTHROUGH;
|
||||
|
@ -75,7 +75,6 @@ public class Options {
|
|||
final OptionSpec<Integer> connectPort = parser.acceptsAll(asList("connect_port", "target_port", "cp", "p"), "The port of the target server").withRequiredArg().ofType(Integer.class);
|
||||
final OptionSpec<VersionEnum> version = parser.acceptsAll(asList("version", "v"), "The version of the target server").withRequiredArg().withValuesConvertedBy(new VersionEnumConverter()).required();
|
||||
final OptionSpec<Void> openAuthModAuth = parser.acceptsAll(asList("openauthmod_auth", "oam_auth"), "Enable OpenAuthMod authentication");
|
||||
final OptionSpec<Void> localSocketAuth = parser.accepts("local_socket_auth", "Enable authentication over a local socket");
|
||||
final OptionSpec<Void> betaCraftAuth = parser.accepts("betacraft_auth", "Use BetaCraft authentication for classic servers");
|
||||
final OptionSpec<String> resourcePackUrl = parser.acceptsAll(asList("resource_pack_url", "resource_pack", "rpu", "rp"), "URL of a resource pack which clients can optionally download").withRequiredArg().ofType(String.class);
|
||||
final OptionSpec<String> proxyUrl = parser.acceptsAll(asList("proxy_url", "proxy"), "URL of a SOCKS(4/5)/HTTP(S) proxy which will be used for backend TCP connections").withRequiredArg().ofType(String.class);
|
||||
|
@ -113,7 +112,6 @@ public class Options {
|
|||
}
|
||||
COMPRESSION_THRESHOLD = options.valueOf(compressionThreshold);
|
||||
OPENAUTHMOD_AUTH = options.has(openAuthModAuth);
|
||||
LOCAL_SOCKET_AUTH = options.has(localSocketAuth);
|
||||
BETACRAFT_AUTH = options.has(betaCraftAuth);
|
||||
if (options.has(resourcePackUrl)) {
|
||||
RESOURCE_PACK_URL = options.valueOf(resourcePackUrl);
|
||||
|
|
|
@ -18,7 +18,6 @@
|
|||
package net.raphimc.viaproxy.proxy.client2proxy;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import com.mojang.authlib.exceptions.AuthenticationException;
|
||||
import com.viaversion.viaversion.api.protocol.version.ProtocolVersion;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
|
@ -68,8 +67,6 @@ import java.nio.channels.UnresolvedAddressException;
|
|||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyPair;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
@ -266,7 +263,7 @@ public class Client2ProxyHandler extends SimpleChannelInboundHandler<IPacket> {
|
|||
this.proxyConnection.setConnectionState(packet.intendedState);
|
||||
}
|
||||
|
||||
private void handleLoginHello(C2SLoginHelloPacket1_7 packet) throws NoSuchAlgorithmException, InvalidKeySpecException, AuthenticationException {
|
||||
private void handleLoginHello(C2SLoginHelloPacket1_7 packet) {
|
||||
if (this.loginState != LoginState.FIRST_PACKET) throw CloseAndReturn.INSTANCE;
|
||||
this.loginState = LoginState.SENT_HELLO;
|
||||
|
||||
|
@ -277,16 +274,22 @@ public class Client2ProxyHandler extends SimpleChannelInboundHandler<IPacket> {
|
|||
}
|
||||
}
|
||||
|
||||
ExternalInterface.fillPlayerData(packet, this.proxyConnection);
|
||||
proxyConnection.setLoginHelloPacket(packet);
|
||||
if (packet instanceof C2SLoginHelloPacket1_19_3) {
|
||||
proxyConnection.setGameProfile(new GameProfile(((C2SLoginHelloPacket1_19_3) packet).uuid, packet.name));
|
||||
} else {
|
||||
proxyConnection.setGameProfile(new GameProfile(null, packet.name));
|
||||
}
|
||||
|
||||
if (Options.ONLINE_MODE) {
|
||||
this.proxyConnection.getC2P().writeAndFlush(new S2CLoginKeyPacket1_8("", KEY_PAIR.getPublic().getEncoded(), this.verifyToken)).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
|
||||
} else {
|
||||
ExternalInterface.fillPlayerData(this.proxyConnection);
|
||||
this.proxyConnection.getChannel().writeAndFlush(this.proxyConnection.getLoginHelloPacket()).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleLoginKey(final C2SLoginKeyPacket1_7 packet) throws GeneralSecurityException, InterruptedException {
|
||||
private void handleLoginKey(final C2SLoginKeyPacket1_7 packet) throws GeneralSecurityException {
|
||||
if (this.proxyConnection.getClientVersion().isOlderThanOrEqualTo(VersionEnum.r1_12_2) && new String(packet.encryptedNonce, StandardCharsets.UTF_8).equals(OpenAuthModConstants.DATA_CHANNEL)) { // 1.8-1.12.2 OpenAuthMod response handling
|
||||
final ByteBuf byteBuf = Unpooled.wrappedBuffer(packet.encryptedSecretKey);
|
||||
this.proxyConnection.handleCustomPayload(PacketTypes.readVarInt(byteBuf), byteBuf);
|
||||
|
@ -329,6 +332,7 @@ public class Client2ProxyHandler extends SimpleChannelInboundHandler<IPacket> {
|
|||
throw new RuntimeException("Failed to make session request for user '" + userName + "'!", e);
|
||||
}
|
||||
|
||||
ExternalInterface.fillPlayerData(this.proxyConnection);
|
||||
this.proxyConnection.getChannel().writeAndFlush(this.proxyConnection.getLoginHelloPacket()).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
|
||||
}
|
||||
|
||||
|
|
|
@ -17,27 +17,20 @@
|
|||
*/
|
||||
package net.raphimc.viaproxy.proxy.external_interface;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.primitives.Longs;
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import com.mojang.authlib.exceptions.AuthenticationException;
|
||||
import com.mojang.authlib.minecraft.InsecurePublicKeyException;
|
||||
import com.mojang.authlib.minecraft.UserApiService;
|
||||
import com.mojang.authlib.yggdrasil.response.KeyPairResponse;
|
||||
import com.mojang.util.UUIDTypeAdapter;
|
||||
import com.viaversion.viaversion.api.connection.UserConnection;
|
||||
import com.viaversion.viaversion.api.minecraft.ProfileKey;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import net.raphimc.mcauth.step.bedrock.StepMCChain;
|
||||
import net.raphimc.mcauth.step.java.StepPlayerCertificates;
|
||||
import net.raphimc.mcauth.util.MicrosoftConstants;
|
||||
import net.raphimc.netminecraft.netty.crypto.CryptUtil;
|
||||
import net.raphimc.netminecraft.packet.PacketTypes;
|
||||
import net.raphimc.netminecraft.packet.impl.login.C2SLoginHelloPacket1_19_3;
|
||||
import net.raphimc.netminecraft.packet.impl.login.C2SLoginHelloPacket1_7;
|
||||
import net.raphimc.netminecraft.packet.impl.login.C2SLoginKeyPacket1_19;
|
||||
import net.raphimc.viabedrock.protocol.storage.AuthChainData;
|
||||
import net.raphimc.vialoader.util.VersionEnum;
|
||||
import net.raphimc.viaproxy.ViaProxy;
|
||||
import net.raphimc.viaproxy.cli.options.Options;
|
||||
import net.raphimc.viaproxy.plugins.PluginManager;
|
||||
import net.raphimc.viaproxy.plugins.events.FillPlayerDataEvent;
|
||||
|
@ -47,15 +40,13 @@ import net.raphimc.viaproxy.protocolhack.viaproxy.signature.storage.ChatSession1
|
|||
import net.raphimc.viaproxy.proxy.session.ProxyConnection;
|
||||
import net.raphimc.viaproxy.saves.impl.accounts.BedrockAccount;
|
||||
import net.raphimc.viaproxy.saves.impl.accounts.MicrosoftAccount;
|
||||
import net.raphimc.viaproxy.util.LocalSocketClient;
|
||||
import net.raphimc.viaproxy.util.logging.Logger;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
|
||||
import java.security.*;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.SignatureException;
|
||||
import java.time.Instant;
|
||||
import java.util.Base64;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
@ -64,67 +55,48 @@ import java.util.concurrent.TimeoutException;
|
|||
|
||||
public class ExternalInterface {
|
||||
|
||||
public static void fillPlayerData(final C2SLoginHelloPacket1_7 loginHello, final ProxyConnection proxyConnection) throws NoSuchAlgorithmException, InvalidKeySpecException, AuthenticationException {
|
||||
proxyConnection.setLoginHelloPacket(loginHello);
|
||||
if (proxyConnection.getLoginHelloPacket() instanceof C2SLoginHelloPacket1_19_3) {
|
||||
proxyConnection.setGameProfile(new GameProfile(((C2SLoginHelloPacket1_19_3) proxyConnection.getLoginHelloPacket()).uuid, proxyConnection.getLoginHelloPacket().name));
|
||||
} else {
|
||||
proxyConnection.setGameProfile(new GameProfile(null, loginHello.name));
|
||||
}
|
||||
|
||||
public static void fillPlayerData(final ProxyConnection proxyConnection) {
|
||||
Logger.u_info("auth", proxyConnection.getC2P().remoteAddress(), proxyConnection.getGameProfile(), "Filling player data");
|
||||
try {
|
||||
if (Options.LOCAL_SOCKET_AUTH) {
|
||||
String[] response = new LocalSocketClient(48941).request("getusername");
|
||||
if (response != null && response[0].equals("success")) {
|
||||
proxyConnection.setGameProfile(new GameProfile(null, response[1]));
|
||||
}
|
||||
|
||||
response = new LocalSocketClient(48941).request("get_public_key_data");
|
||||
if (response != null && response[0].equals("success")) {
|
||||
final String name = proxyConnection.getGameProfile().getName();
|
||||
final UUID uuid = UUIDTypeAdapter.fromString(response[1]);
|
||||
final PublicKey publicKey = KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(Base64.getDecoder().decode(response[2])));
|
||||
final byte[] keySignature = Base64.getDecoder().decode(response[3]);
|
||||
final Instant expiresAt = Instant.ofEpochMilli(Long.parseLong(response[4]));
|
||||
|
||||
proxyConnection.setGameProfile(new GameProfile(uuid, name));
|
||||
proxyConnection.setLoginHelloPacket(new C2SLoginHelloPacket1_19_3(name, expiresAt, publicKey, keySignature, uuid));
|
||||
}
|
||||
} else if (Options.MC_ACCOUNT != null) {
|
||||
if (Options.MC_ACCOUNT != null) {
|
||||
proxyConnection.setGameProfile(Options.MC_ACCOUNT.getGameProfile());
|
||||
final UserConnection user = proxyConnection.getUserConnection();
|
||||
|
||||
if (Options.MC_ACCOUNT instanceof MicrosoftAccount && proxyConnection.getServerVersion().isBetweenInclusive(VersionEnum.r1_19, VersionEnum.r1_19_3)) {
|
||||
if (Options.CHAT_SIGNING && proxyConnection.getServerVersion().isNewerThanOrEqualTo(VersionEnum.r1_19) && Options.MC_ACCOUNT instanceof MicrosoftAccount) {
|
||||
final MicrosoftAccount microsoftAccount = (MicrosoftAccount) Options.MC_ACCOUNT;
|
||||
final UserApiService userApiService = AuthLibServices.AUTHENTICATION_SERVICE.createUserApiService(microsoftAccount.getMcProfile().prevResult().prevResult().access_token());
|
||||
final KeyPairResponse keyPair = userApiService.getKeyPair();
|
||||
if (keyPair == null) {
|
||||
throw new InsecurePublicKeyException.MissingException();
|
||||
} else if (Strings.isNullOrEmpty(keyPair.getPublicKey()) || keyPair.getPublicKeySignature() == null || keyPair.getPublicKeySignature().array().length == 0) {
|
||||
throw new InsecurePublicKeyException.InvalidException("Invalid public key data");
|
||||
synchronized (ViaProxy.saveManager.accountsSave) {
|
||||
try (final CloseableHttpClient httpClient = MicrosoftConstants.createHttpClient()) {
|
||||
microsoftAccount.refreshRuntimeData(httpClient);
|
||||
}
|
||||
ViaProxy.saveManager.accountsSave.save();
|
||||
}
|
||||
|
||||
final Instant expiresAt = Instant.parse(keyPair.getExpiresAt());
|
||||
final long expiresAtMillis = expiresAt.toEpochMilli();
|
||||
final PublicKey publicKey = CryptUtil.decodeRsaPublicKeyPem(keyPair.getPublicKey());
|
||||
final StepPlayerCertificates.PlayerCertificates playerCertificates = microsoftAccount.getPlayerCertificates();
|
||||
final Instant expiresAt = Instant.ofEpochMilli(playerCertificates.expireTimeMs());
|
||||
final long expiresAtMillis = playerCertificates.expireTimeMs();
|
||||
final PublicKey publicKey = playerCertificates.publicKey();
|
||||
final byte[] publicKeyBytes = publicKey.getEncoded();
|
||||
final byte[] keySignature = keyPair.getPublicKeySignature().array();
|
||||
final PrivateKey privateKey = CryptUtil.decodeRsaPrivateKeyPem(keyPair.getPrivateKey());
|
||||
final byte[] keySignature = playerCertificates.publicKeySignature();
|
||||
final PrivateKey privateKey = playerCertificates.privateKey();
|
||||
final UUID uuid = proxyConnection.getGameProfile().getId();
|
||||
|
||||
proxyConnection.setLoginHelloPacket(new C2SLoginHelloPacket1_19_3(proxyConnection.getGameProfile().getName(), expiresAt, publicKey, keySignature, proxyConnection.getGameProfile().getId()));
|
||||
byte[] loginHelloKeySignature = keySignature;
|
||||
if (proxyConnection.getClientVersion().equals(VersionEnum.r1_19)) {
|
||||
loginHelloKeySignature = playerCertificates.legacyPublicKeySignature();
|
||||
}
|
||||
proxyConnection.setLoginHelloPacket(new C2SLoginHelloPacket1_19_3(proxyConnection.getGameProfile().getName(), expiresAt, publicKey, loginHelloKeySignature, proxyConnection.getGameProfile().getId()));
|
||||
|
||||
user.put(new ChatSession1_19_3(user, uuid, privateKey, new ProfileKey(expiresAtMillis, publicKeyBytes, keySignature)));
|
||||
user.put(new ChatSession1_19_0(user, uuid, privateKey, new ProfileKey(expiresAtMillis, publicKeyBytes, playerCertificates.legacyPublicKeySignature())));
|
||||
user.put(new ChatSession1_19_1(user, uuid, privateKey, new ProfileKey(expiresAtMillis, publicKeyBytes, keySignature)));
|
||||
if (keyPair.getLegacyPublicKeySignature() != null && keyPair.getLegacyPublicKeySignature().array().length != 0) {
|
||||
user.put(new ChatSession1_19_0(user, uuid, privateKey, new ProfileKey(expiresAtMillis, publicKeyBytes, keyPair.getLegacyPublicKeySignature().array())));
|
||||
}
|
||||
} else if (Options.MC_ACCOUNT instanceof BedrockAccount && proxyConnection.getServerVersion().equals(VersionEnum.bedrockLatest)) {
|
||||
user.put(new ChatSession1_19_3(user, uuid, privateKey, new ProfileKey(expiresAtMillis, publicKeyBytes, keySignature)));
|
||||
} else if (proxyConnection.getServerVersion().equals(VersionEnum.bedrockLatest) && Options.MC_ACCOUNT instanceof BedrockAccount) {
|
||||
final BedrockAccount bedrockAccount = (BedrockAccount) Options.MC_ACCOUNT;
|
||||
final StepMCChain.MCChain mcChain;
|
||||
try (final CloseableHttpClient httpClient = MicrosoftConstants.createHttpClient()) {
|
||||
mcChain = new StepMCChain(null).applyStep(httpClient, bedrockAccount.getMcChain().prevResult());
|
||||
synchronized (ViaProxy.saveManager.accountsSave) {
|
||||
try (final CloseableHttpClient httpClient = MicrosoftConstants.createHttpClient()) {
|
||||
bedrockAccount.refreshRuntimeData(httpClient);
|
||||
}
|
||||
ViaProxy.saveManager.accountsSave.save();
|
||||
}
|
||||
final StepMCChain.MCChain mcChain = bedrockAccount.getMcChain();
|
||||
|
||||
final UUID deviceId = mcChain.prevResult().initialXblSession().prevResult2().id();
|
||||
final String playFabId = bedrockAccount.getPlayFabToken().playFabId();
|
||||
|
@ -134,7 +106,8 @@ public class ExternalInterface {
|
|||
|
||||
PluginManager.EVENT_MANAGER.call(new FillPlayerDataEvent(proxyConnection));
|
||||
} catch (Throwable e) {
|
||||
throw new RuntimeException("Failed to fill player data. This might be caused by outdated account tokens. Please restart the proxy and try again.", e);
|
||||
Logger.LOGGER.error("Failed to fill player data", e);
|
||||
proxyConnection.kickClient("§cFailed to fill player data. This might be caused by outdated account tokens. Please restart ViaProxy and try again.");
|
||||
}
|
||||
|
||||
proxyConnection.getLoginHelloPacket().name = proxyConnection.getGameProfile().getName();
|
||||
|
@ -153,8 +126,6 @@ public class ExternalInterface {
|
|||
} catch (TimeoutException e) {
|
||||
proxyConnection.kickClient("§cAuthentication cancelled! You need to install OpenAuthMod in order to join this server.");
|
||||
}
|
||||
} else if (Options.LOCAL_SOCKET_AUTH) {
|
||||
new LocalSocketClient(48941).request("authenticate", serverIdHash);
|
||||
} else if (Options.MC_ACCOUNT instanceof MicrosoftAccount) {
|
||||
final MicrosoftAccount microsoftAccount = (MicrosoftAccount) Options.MC_ACCOUNT;
|
||||
try {
|
||||
|
@ -176,26 +147,23 @@ public class ExternalInterface {
|
|||
if (!response.readBoolean()) throw new TimeoutException();
|
||||
packet.salt = response.readLong();
|
||||
packet.signature = PacketTypes.readByteArray(response);
|
||||
} catch (TimeoutException ignored) {
|
||||
} catch (TimeoutException e) {
|
||||
proxyConnection.kickClient("§cAuthentication cancelled! You need to install OpenAuthMod in order to join this server.");
|
||||
}
|
||||
} else if (Options.LOCAL_SOCKET_AUTH) {
|
||||
final String[] response = new LocalSocketClient(48941).request("sign_nonce", Base64.getEncoder().encodeToString(nonce));
|
||||
if (response != null && response[0].equals("success")) {
|
||||
packet.salt = Long.valueOf(response[1]);
|
||||
packet.signature = Base64.getDecoder().decode(response[2]);
|
||||
}
|
||||
} else if (Options.MC_ACCOUNT instanceof MicrosoftAccount) {
|
||||
} else if (Options.CHAT_SIGNING && Options.MC_ACCOUNT instanceof MicrosoftAccount) {
|
||||
final UserConnection user = proxyConnection.getUserConnection();
|
||||
final ChatSession1_19_1 chatSession = user.get(ChatSession1_19_1.class);
|
||||
if (chatSession == null) return;
|
||||
|
||||
final long salt = ThreadLocalRandom.current().nextLong();
|
||||
final byte[] signature = chatSession.sign(updater -> {
|
||||
updater.accept(nonce);
|
||||
updater.accept(Longs.toByteArray(salt));
|
||||
});
|
||||
packet.salt = salt;
|
||||
packet.signature = signature;
|
||||
if (user.has(ChatSession1_19_0.class)) {
|
||||
final long salt = ThreadLocalRandom.current().nextLong();
|
||||
packet.signature = user.get(ChatSession1_19_0.class).sign(updater -> {
|
||||
updater.accept(nonce);
|
||||
updater.accept(Longs.toByteArray(salt));
|
||||
});
|
||||
packet.salt = salt;
|
||||
} else {
|
||||
proxyConnection.kickClient("§cFailed to sign nonce");
|
||||
}
|
||||
} else {
|
||||
proxyConnection.kickClient("§cThis server requires a signed nonce. Please enable chat signing in the config and select a valid authentication mode.");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -254,7 +254,7 @@ public class ProxyConnection extends NetClient {
|
|||
future = this.c2p.newSucceededFuture();
|
||||
}
|
||||
|
||||
future.channel().close();
|
||||
future.addListener(ChannelFutureListener.CLOSE);
|
||||
throw CloseAndReturn.INSTANCE;
|
||||
}
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ package net.raphimc.viaproxy.saves;
|
|||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
import net.raphimc.viaproxy.saves.impl.accounts.BedrockAccount;
|
||||
import net.raphimc.viaproxy.saves.impl.accounts.MicrosoftAccount;
|
||||
import net.raphimc.viaproxy.util.logging.Logger;
|
||||
|
||||
public class SaveMigrator {
|
||||
|
@ -38,6 +39,13 @@ public class SaveMigrator {
|
|||
newObject.addProperty("account_type", type);
|
||||
accountsArray.set(i, newObject);
|
||||
}
|
||||
if (MicrosoftAccount.class.getName().equals(type) && !object.has("mc_profile")) {
|
||||
final JsonObject newObject = new JsonObject();
|
||||
object.remove("account_type");
|
||||
newObject.add("mc_profile", object);
|
||||
newObject.addProperty("account_type", type);
|
||||
accountsArray.set(i, newObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
|
|
|
@ -20,11 +20,9 @@ package net.raphimc.viaproxy.saves.impl;
|
|||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import net.raphimc.mcauth.step.java.StepMCProfile;
|
||||
import net.raphimc.mcauth.util.MicrosoftConstants;
|
||||
import net.raphimc.viaproxy.saves.AbstractSave;
|
||||
import net.raphimc.viaproxy.saves.impl.accounts.Account;
|
||||
import net.raphimc.viaproxy.saves.impl.accounts.MicrosoftAccount;
|
||||
import net.raphimc.viaproxy.saves.impl.accounts.OfflineAccount;
|
||||
import net.raphimc.viaproxy.util.logging.Logger;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
|
@ -64,16 +62,6 @@ public class NewAccountsSave extends AbstractSave {
|
|||
return array;
|
||||
}
|
||||
|
||||
public Account addAccount(final StepMCProfile.MCProfile mcProfile) {
|
||||
if (mcProfile.prevResult().items().isEmpty()) {
|
||||
return this.addAccount(mcProfile.name());
|
||||
} else {
|
||||
final Account account = new MicrosoftAccount(mcProfile);
|
||||
this.accounts.add(account);
|
||||
return account;
|
||||
}
|
||||
}
|
||||
|
||||
public Account addAccount(final String username) {
|
||||
final Account account = new OfflineAccount(username);
|
||||
this.accounts.add(account);
|
||||
|
|
|
@ -57,41 +57,49 @@ public class UISave extends AbstractSave {
|
|||
}
|
||||
|
||||
public void loadTextField(final String key, final JTextField textField) {
|
||||
try {
|
||||
String value = this.values.get(key);
|
||||
if (value != null) textField.setText(value);
|
||||
} catch (Throwable ignored) {
|
||||
if (this.values.containsKey(key)) {
|
||||
try {
|
||||
String value = this.values.get(key);
|
||||
if (value != null) textField.setText(value);
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void loadComboBox(final String key, final JComboBox<?> comboBox) {
|
||||
try {
|
||||
int index = Integer.parseInt(this.values.get(key));
|
||||
if (index >= 0 && index < comboBox.getItemCount()) comboBox.setSelectedIndex(index);
|
||||
} catch (Throwable ignored) {
|
||||
if (this.values.containsKey(key)) {
|
||||
try {
|
||||
int index = Integer.parseInt(this.values.get(key));
|
||||
if (index >= 0 && index < comboBox.getItemCount()) comboBox.setSelectedIndex(index);
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void loadSpinner(final String key, final JSpinner spinner) {
|
||||
try {
|
||||
Integer value = Integer.valueOf(this.values.get(key));
|
||||
if (spinner.getModel() instanceof SpinnerNumberModel) {
|
||||
SpinnerNumberModel model = (SpinnerNumberModel) spinner.getModel();
|
||||
Comparable<Integer> minimum = (Comparable<Integer>) model.getMinimum();
|
||||
Comparable<Integer> maximum = (Comparable<Integer>) model.getMaximum();
|
||||
if (minimum.compareTo(value) <= 0 && maximum.compareTo(value) >= 0) spinner.setValue(value);
|
||||
} else {
|
||||
spinner.setValue(value);
|
||||
if (this.values.containsKey(key)) {
|
||||
try {
|
||||
Integer value = Integer.valueOf(this.values.get(key));
|
||||
if (spinner.getModel() instanceof SpinnerNumberModel) {
|
||||
SpinnerNumberModel model = (SpinnerNumberModel) spinner.getModel();
|
||||
Comparable<Integer> minimum = (Comparable<Integer>) model.getMinimum();
|
||||
Comparable<Integer> maximum = (Comparable<Integer>) model.getMaximum();
|
||||
if (minimum.compareTo(value) <= 0 && maximum.compareTo(value) >= 0) spinner.setValue(value);
|
||||
} else {
|
||||
spinner.setValue(value);
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
public void loadCheckBox(final String key, final JCheckBox checkBox) {
|
||||
try {
|
||||
boolean value = Boolean.parseBoolean(this.values.get(key));
|
||||
checkBox.setSelected(value);
|
||||
} catch (Throwable ignored) {
|
||||
if (this.values.containsKey(key)) {
|
||||
try {
|
||||
boolean value = Boolean.parseBoolean(this.values.get(key));
|
||||
checkBox.setSelected(value);
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -42,4 +42,6 @@ public abstract class Account {
|
|||
|
||||
public abstract void refresh(final CloseableHttpClient httpClient) throws Throwable;
|
||||
|
||||
public abstract void refreshRuntimeData(final CloseableHttpClient httpClient) throws Throwable;
|
||||
|
||||
}
|
||||
|
|
|
@ -20,7 +20,8 @@ package net.raphimc.viaproxy.saves.impl.accounts;
|
|||
import com.google.gson.JsonObject;
|
||||
import net.raphimc.mcauth.MinecraftAuth;
|
||||
import net.raphimc.mcauth.step.bedrock.StepMCChain;
|
||||
import net.raphimc.mcauth.step.bedrock.playfab.StepPlayFabToken;
|
||||
import net.raphimc.mcauth.step.bedrock.StepPlayFabToken;
|
||||
import net.raphimc.viaproxy.util.logging.Logger;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
|
||||
import java.util.UUID;
|
||||
|
@ -33,20 +34,25 @@ public class BedrockAccount extends Account {
|
|||
public BedrockAccount(final JsonObject jsonObject) throws Throwable {
|
||||
this.mcChain = MinecraftAuth.BEDROCK_DEVICE_CODE_LOGIN.fromJson(jsonObject.getAsJsonObject("mc_chain"));
|
||||
if (jsonObject.has("play_fab_token")) {
|
||||
this.playFabToken = MinecraftAuth.BEDROCK_PLAY_FAB_TOKEN.fromJson(jsonObject.getAsJsonObject("play_fab_token"));
|
||||
try {
|
||||
this.playFabToken = MinecraftAuth.BEDROCK_PLAY_FAB_TOKEN.fromJson(jsonObject.getAsJsonObject("play_fab_token"));
|
||||
} catch (Throwable e) {
|
||||
Logger.LOGGER.warn("Failed to load PlayFab token for Bedrock account. It will be regenerated.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public BedrockAccount(final StepMCChain.MCChain mcChain, final StepPlayFabToken.PlayFabToken playFabToken) {
|
||||
public BedrockAccount(final StepMCChain.MCChain mcChain) {
|
||||
this.mcChain = mcChain;
|
||||
this.playFabToken = playFabToken;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonObject toJson() {
|
||||
final JsonObject jsonObject = new JsonObject();
|
||||
jsonObject.add("mc_chain", this.mcChain.toJson());
|
||||
jsonObject.add("play_fab_token", this.playFabToken.toJson());
|
||||
if (this.playFabToken != null) {
|
||||
jsonObject.add("play_fab_token", this.playFabToken.toJson());
|
||||
}
|
||||
return jsonObject;
|
||||
}
|
||||
|
||||
|
@ -76,12 +82,18 @@ public class BedrockAccount extends Account {
|
|||
@Override
|
||||
public void refresh(CloseableHttpClient httpClient) throws Exception {
|
||||
this.mcChain = MinecraftAuth.BEDROCK_DEVICE_CODE_LOGIN.refresh(httpClient, this.mcChain);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refreshRuntimeData(CloseableHttpClient httpClient) throws Throwable {
|
||||
this.mcChain = new StepMCChain(null).applyStep(httpClient, this.mcChain.prevResult()); // Force refresh, because some servers check the issuedAt field
|
||||
try {
|
||||
if (this.playFabToken == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
this.playFabToken = MinecraftAuth.BEDROCK_PLAY_FAB_TOKEN.refresh(httpClient, this.playFabToken);
|
||||
} catch (Throwable e) {
|
||||
this.playFabToken = null;
|
||||
this.playFabToken = MinecraftAuth.BEDROCK_PLAY_FAB_TOKEN.getFromInput(httpClient, this.mcChain.prevResult().fullXblSession());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,8 @@ package net.raphimc.viaproxy.saves.impl.accounts;
|
|||
import com.google.gson.JsonObject;
|
||||
import net.raphimc.mcauth.MinecraftAuth;
|
||||
import net.raphimc.mcauth.step.java.StepMCProfile;
|
||||
import net.raphimc.mcauth.step.java.StepPlayerCertificates;
|
||||
import net.raphimc.viaproxy.util.logging.Logger;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
|
||||
import java.util.UUID;
|
||||
|
@ -27,9 +29,17 @@ import java.util.UUID;
|
|||
public class MicrosoftAccount extends Account {
|
||||
|
||||
private StepMCProfile.MCProfile mcProfile;
|
||||
private StepPlayerCertificates.PlayerCertificates playerCertificates;
|
||||
|
||||
public MicrosoftAccount(final JsonObject jsonObject) throws Throwable {
|
||||
this.mcProfile = MinecraftAuth.JAVA_DEVICE_CODE_LOGIN.fromJson(jsonObject);
|
||||
this.mcProfile = MinecraftAuth.JAVA_DEVICE_CODE_LOGIN.fromJson(jsonObject.getAsJsonObject("mc_profile"));
|
||||
if (jsonObject.has("player_certificates")) {
|
||||
try {
|
||||
this.playerCertificates = MinecraftAuth.JAVA_PLAYER_CERTIFICATES.fromJson(jsonObject.getAsJsonObject("player_certificates"));
|
||||
} catch (Throwable e) {
|
||||
Logger.LOGGER.warn("Failed to load player certificates for Microsoft account. They will be regenerated.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public MicrosoftAccount(final StepMCProfile.MCProfile mcProfile) {
|
||||
|
@ -38,7 +48,12 @@ public class MicrosoftAccount extends Account {
|
|||
|
||||
@Override
|
||||
public JsonObject toJson() {
|
||||
return this.mcProfile.toJson();
|
||||
final JsonObject jsonObject = new JsonObject();
|
||||
jsonObject.add("mc_profile", this.mcProfile.toJson());
|
||||
if (this.playerCertificates != null) {
|
||||
jsonObject.add("player_certificates", this.playerCertificates.toJson());
|
||||
}
|
||||
return jsonObject;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -55,6 +70,10 @@ public class MicrosoftAccount extends Account {
|
|||
return this.mcProfile;
|
||||
}
|
||||
|
||||
public StepPlayerCertificates.PlayerCertificates getPlayerCertificates() {
|
||||
return this.playerCertificates;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayString() {
|
||||
return this.getName() + " (Microsoft)";
|
||||
|
@ -65,4 +84,17 @@ public class MicrosoftAccount extends Account {
|
|||
this.mcProfile = MinecraftAuth.JAVA_DEVICE_CODE_LOGIN.refresh(httpClient, this.mcProfile);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refreshRuntimeData(CloseableHttpClient httpClient) throws Throwable {
|
||||
try {
|
||||
if (this.playerCertificates == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
this.playerCertificates = MinecraftAuth.JAVA_PLAYER_CERTIFICATES.refresh(httpClient, this.playerCertificates);
|
||||
} catch (Throwable e) {
|
||||
this.playerCertificates = null;
|
||||
this.playerCertificates = MinecraftAuth.JAVA_PLAYER_CERTIFICATES.getFromInput(httpClient, this.mcProfile.prevResult().prevResult());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -65,4 +65,8 @@ public class OfflineAccount extends Account {
|
|||
public void refresh(CloseableHttpClient httpClient) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refreshRuntimeData(CloseableHttpClient httpClient) {
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -18,14 +18,13 @@
|
|||
package net.raphimc.viaproxy.ui.impl;
|
||||
|
||||
import net.raphimc.mcauth.MinecraftAuth;
|
||||
import net.raphimc.mcauth.step.bedrock.StepMCChain;
|
||||
import net.raphimc.mcauth.step.bedrock.playfab.StepPlayFabToken;
|
||||
import net.raphimc.mcauth.step.msa.StepMsaDeviceCode;
|
||||
import net.raphimc.mcauth.util.MicrosoftConstants;
|
||||
import net.raphimc.viaproxy.ViaProxy;
|
||||
import net.raphimc.viaproxy.cli.options.Options;
|
||||
import net.raphimc.viaproxy.saves.impl.accounts.Account;
|
||||
import net.raphimc.viaproxy.saves.impl.accounts.BedrockAccount;
|
||||
import net.raphimc.viaproxy.saves.impl.accounts.MicrosoftAccount;
|
||||
import net.raphimc.viaproxy.ui.AUITab;
|
||||
import net.raphimc.viaproxy.ui.ViaProxyUI;
|
||||
import net.raphimc.viaproxy.ui.popups.AddAccountPopup;
|
||||
|
@ -39,7 +38,6 @@ import java.awt.event.MouseAdapter;
|
|||
import java.awt.event.MouseEvent;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class AccountsTab extends AUITab {
|
||||
|
||||
|
@ -197,9 +195,9 @@ public class AccountsTab extends AUITab {
|
|||
this.addMicrosoftAccountButton.setEnabled(false);
|
||||
this.handleLogin(msaDeviceCodeConsumer -> {
|
||||
try (final CloseableHttpClient httpClient = MicrosoftConstants.createHttpClient()) {
|
||||
return MinecraftAuth.JAVA_DEVICE_CODE_LOGIN.getFromInput(httpClient, new StepMsaDeviceCode.MsaDeviceCodeCallback(msaDeviceCodeConsumer));
|
||||
return new MicrosoftAccount(MinecraftAuth.JAVA_DEVICE_CODE_LOGIN.getFromInput(httpClient, new StepMsaDeviceCode.MsaDeviceCodeCallback(msaDeviceCodeConsumer)));
|
||||
}
|
||||
}, profile -> ViaProxy.saveManager.accountsSave.addAccount(profile));
|
||||
});
|
||||
});
|
||||
addButtons.add(this.addMicrosoftAccountButton);
|
||||
}
|
||||
|
@ -210,12 +208,9 @@ public class AccountsTab extends AUITab {
|
|||
this.addBedrockAccountButton.setEnabled(false);
|
||||
this.handleLogin(msaDeviceCodeConsumer -> {
|
||||
try (final CloseableHttpClient httpClient = MicrosoftConstants.createHttpClient()) {
|
||||
final StepMCChain.MCChain mcChain = MinecraftAuth.BEDROCK_DEVICE_CODE_LOGIN.getFromInput(httpClient, new StepMsaDeviceCode.MsaDeviceCodeCallback(msaDeviceCodeConsumer));
|
||||
final StepPlayFabToken.PlayFabToken playFabToken = MinecraftAuth.BEDROCK_PLAY_FAB_TOKEN.getFromInput(httpClient, mcChain.prevResult().fullXblSession());
|
||||
|
||||
return new BedrockAccount(mcChain, playFabToken);
|
||||
return new BedrockAccount(MinecraftAuth.BEDROCK_DEVICE_CODE_LOGIN.getFromInput(httpClient, new StepMsaDeviceCode.MsaDeviceCodeCallback(msaDeviceCodeConsumer)));
|
||||
}
|
||||
}, account -> ViaProxy.saveManager.accountsSave.addAccount(account));
|
||||
});
|
||||
});
|
||||
addButtons.add(this.addBedrockAccountButton);
|
||||
}
|
||||
|
@ -291,10 +286,10 @@ public class AccountsTab extends AUITab {
|
|||
}
|
||||
}
|
||||
|
||||
private <T> void handleLogin(final TFunction<Consumer<StepMsaDeviceCode.MsaDeviceCode>, T> requestHandler, final Function<T, Account> addHandler) {
|
||||
private void handleLogin(final TFunction<Consumer<StepMsaDeviceCode.MsaDeviceCode>, Account> requestHandler) {
|
||||
this.addThread = new Thread(() -> {
|
||||
try {
|
||||
T profile = requestHandler.apply(msaDeviceCode -> {
|
||||
final Account account = requestHandler.apply(msaDeviceCode -> {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
new AddAccountPopup(this.frame, msaDeviceCode, popup -> this.addAccountPopup = popup, () -> {
|
||||
this.closePopup();
|
||||
|
@ -304,7 +299,7 @@ public class AccountsTab extends AUITab {
|
|||
});
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
this.closePopup();
|
||||
Account account = addHandler.apply(profile);
|
||||
ViaProxy.saveManager.accountsSave.addAccount(account);
|
||||
ViaProxy.saveManager.save();
|
||||
this.addAccount(account);
|
||||
this.frame.showInfo("The account " + account.getName() + " was added successfully.");
|
||||
|
|
|
@ -40,6 +40,7 @@ public class AdvancedTab extends AUITab {
|
|||
JTextField proxy;
|
||||
JCheckBox proxyOnlineMode;
|
||||
JCheckBox legacySkinLoading;
|
||||
JCheckBox chatSigning;
|
||||
JButton viaVersionDumpButton;
|
||||
JButton uploadLogsButton;
|
||||
|
||||
|
@ -95,6 +96,14 @@ public class AdvancedTab extends AUITab {
|
|||
ViaProxy.saveManager.uiSave.loadCheckBox("legacy_skin_loading", this.legacySkinLoading);
|
||||
contentPane.add(this.legacySkinLoading);
|
||||
}
|
||||
{
|
||||
this.chatSigning = new JCheckBox("Chat signing");
|
||||
this.chatSigning.setBounds(10, 170, 465, 20);
|
||||
this.chatSigning.setToolTipText("Enables sending signed chat messages on >= 1.19 servers");
|
||||
this.chatSigning.setSelected(true);
|
||||
ViaProxy.saveManager.uiSave.loadCheckBox("chat_signing", this.chatSigning);
|
||||
contentPane.add(this.chatSigning);
|
||||
}
|
||||
{
|
||||
this.viaVersionDumpButton = new JButton("Create ViaVersion dump");
|
||||
this.viaVersionDumpButton.setBounds(10, 250, 225, 20);
|
||||
|
@ -163,6 +172,8 @@ public class AdvancedTab extends AUITab {
|
|||
save.put("bind_port", String.valueOf(this.bindPort.getValue()));
|
||||
save.put("proxy", this.proxy.getText());
|
||||
save.put("proxy_online_mode", String.valueOf(this.proxyOnlineMode.isSelected()));
|
||||
save.put("legacy_skin_loading", String.valueOf(this.legacySkinLoading.isSelected()));
|
||||
save.put("chat_signing", String.valueOf(this.chatSigning.isSelected()));
|
||||
ViaProxy.saveManager.save();
|
||||
}
|
||||
|
||||
|
|
|
@ -189,6 +189,8 @@ public class GeneralTab extends AUITab {
|
|||
this.betaCraftAuth.setEnabled(state);
|
||||
ViaProxy.ui.advancedTab.proxyOnlineMode.setEnabled(state);
|
||||
ViaProxy.ui.advancedTab.proxy.setEnabled(state);
|
||||
ViaProxy.ui.advancedTab.legacySkinLoading.setEnabled(state);
|
||||
ViaProxy.ui.advancedTab.chatSigning.setEnabled(state);
|
||||
if (state) this.serverVersion.getActionListeners()[0].actionPerformed(null);
|
||||
}
|
||||
|
||||
|
@ -229,6 +231,7 @@ public class GeneralTab extends AUITab {
|
|||
final boolean proxyOnlineMode = ViaProxy.ui.advancedTab.proxyOnlineMode.isSelected();
|
||||
final boolean legacySkinLoading = ViaProxy.ui.advancedTab.legacySkinLoading.isSelected();
|
||||
final String proxyUrl = ViaProxy.ui.advancedTab.proxy.getText().trim();
|
||||
final boolean chatSigning = ViaProxy.ui.advancedTab.chatSigning.isSelected();
|
||||
|
||||
try {
|
||||
try {
|
||||
|
@ -266,6 +269,7 @@ public class GeneralTab extends AUITab {
|
|||
Options.BETACRAFT_AUTH = betaCraftAuth;
|
||||
Options.LEGACY_SKIN_LOADING = legacySkinLoading;
|
||||
Options.OPENAUTHMOD_AUTH = authMethod == 2;
|
||||
Options.CHAT_SIGNING = chatSigning;
|
||||
|
||||
if (!proxyUrl.isEmpty()) {
|
||||
try {
|
||||
|
|
|
@ -1,54 +0,0 @@
|
|||
/*
|
||||
* This file is part of ViaProxy - https://github.com/RaphiMC/ViaProxy
|
||||
* Copyright (C) 2023 RK_01/RaphiMC and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package net.raphimc.viaproxy.util;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
|
||||
public class LocalSocketClient {
|
||||
|
||||
private final int port;
|
||||
|
||||
public LocalSocketClient(final int port) {
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
public String[] request(final String command, final String... args) {
|
||||
try {
|
||||
final Socket socket = new Socket();
|
||||
socket.setSoTimeout(500);
|
||||
socket.connect(new InetSocketAddress("127.0.0.1", this.port), 500);
|
||||
final DataOutputStream out = new DataOutputStream(socket.getOutputStream());
|
||||
final DataInputStream in = new DataInputStream(socket.getInputStream());
|
||||
|
||||
out.writeUTF(command);
|
||||
out.writeInt(args.length);
|
||||
for (String s : args) out.writeUTF(s);
|
||||
|
||||
final String[] response = new String[in.readInt()];
|
||||
for (int i = 0; i < response.length; i++) response[i] = in.readUTF();
|
||||
socket.close();
|
||||
return response;
|
||||
} catch (Throwable e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in a new issue