Improved chat signing implementation and lazy load account runtime data

This commit is contained in:
RaphiMC 2023-06-16 15:32:55 +02:00
parent d4660a9b97
commit 9510c672a7
No known key found for this signature in database
GPG key ID: 0F6BB0657A03AC94
15 changed files with 181 additions and 201 deletions

View file

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

View file

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

View file

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

View file

@ -254,7 +254,7 @@ public class ProxyConnection extends NetClient {
future = this.c2p.newSucceededFuture();
}
future.channel().close();
future.addListener(ChannelFutureListener.CLOSE);
throw CloseAndReturn.INSTANCE;
}

View file

@ -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) {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -65,4 +65,8 @@ public class OfflineAccount extends Account {
public void refresh(CloseableHttpClient httpClient) {
}
@Override
public void refreshRuntimeData(CloseableHttpClient httpClient) {
}
}

View file

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

View file

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

View file

@ -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 {

View file

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