From 058466b51f2ca43a5d09e91041bb57a51954eb98 Mon Sep 17 00:00:00 2001 From: RaphiMC <50594595+RaphiMC@users.noreply.github.com> Date: Tue, 28 Nov 2023 22:31:02 +0100 Subject: [PATCH] Add chat signing support for 1.20.x clients on 1.20.x servers --- .../client2proxy/Client2ProxyHandler.java | 3 + .../ChatSignaturePacketHandler.java | 115 ++++++++++++++++++ .../packethandler/LoginPacketHandler.java | 11 +- 3 files changed, 119 insertions(+), 10 deletions(-) create mode 100644 src/main/java/net/raphimc/viaproxy/proxy/packethandler/ChatSignaturePacketHandler.java diff --git a/src/main/java/net/raphimc/viaproxy/proxy/client2proxy/Client2ProxyHandler.java b/src/main/java/net/raphimc/viaproxy/proxy/client2proxy/Client2ProxyHandler.java index 0be0971..fb3026d 100644 --- a/src/main/java/net/raphimc/viaproxy/proxy/client2proxy/Client2ProxyHandler.java +++ b/src/main/java/net/raphimc/viaproxy/proxy/client2proxy/Client2ProxyHandler.java @@ -201,6 +201,9 @@ public class Client2ProxyHandler extends SimpleChannelInboundHandler { if (clientVersion.isNewerThanOrEqualTo(VersionEnum.r1_20_2) || serverVersion.isNewerThanOrEqualTo(VersionEnum.r1_20_2)) { this.proxyConnection.getPacketHandlers().add(new ConfigurationPacketHandler(this.proxyConnection)); } + if (clientVersion.isNewerThanOrEqualTo(VersionEnum.r1_19_3) && serverVersion.isNewerThanOrEqualTo(VersionEnum.r1_19_3)) { + this.proxyConnection.getPacketHandlers().add(new ChatSignaturePacketHandler(this.proxyConnection)); + } this.proxyConnection.getPacketHandlers().add(new ResourcePackPacketHandler(this.proxyConnection)); this.proxyConnection.getPacketHandlers().add(new UnexpectedPacketHandler(this.proxyConnection)); diff --git a/src/main/java/net/raphimc/viaproxy/proxy/packethandler/ChatSignaturePacketHandler.java b/src/main/java/net/raphimc/viaproxy/proxy/packethandler/ChatSignaturePacketHandler.java new file mode 100644 index 0000000..09af3e3 --- /dev/null +++ b/src/main/java/net/raphimc/viaproxy/proxy/packethandler/ChatSignaturePacketHandler.java @@ -0,0 +1,115 @@ +/* + * 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 . + */ +package net.raphimc.viaproxy.proxy.packethandler; + +import com.viaversion.viaversion.api.connection.UserConnection; +import com.viaversion.viaversion.api.minecraft.PlayerMessageSignature; +import com.viaversion.viaversion.api.minecraft.signature.model.MessageMetadata; +import com.viaversion.viaversion.api.minecraft.signature.storage.ChatSession1_19_3; +import com.viaversion.viaversion.api.type.Type; +import com.viaversion.viaversion.api.type.types.BitSetType; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelFutureListener; +import net.raphimc.netminecraft.constants.ConnectionState; +import net.raphimc.netminecraft.constants.MCPackets; +import net.raphimc.netminecraft.constants.MCPipeline; +import net.raphimc.netminecraft.packet.IPacket; +import net.raphimc.netminecraft.packet.PacketTypes; +import net.raphimc.netminecraft.packet.UnknownPacket; +import net.raphimc.viaproxy.proxy.session.ProxyConnection; + +import java.util.BitSet; +import java.util.List; + +public class ChatSignaturePacketHandler extends PacketHandler { + + private final int joinGameId; + private final int chatSessionUpdateId; + private final int chatMessageId; + + public ChatSignaturePacketHandler(ProxyConnection proxyConnection) { + super(proxyConnection); + + this.joinGameId = MCPackets.S2C_JOIN_GAME.getId(proxyConnection.getClientVersion().getVersion()); + this.chatSessionUpdateId = MCPackets.C2S_CHAT_SESSION_UPDATE.getId(proxyConnection.getClientVersion().getVersion()); + this.chatMessageId = MCPackets.C2S_CHAT_MESSAGE.getId(proxyConnection.getClientVersion().getVersion()); + } + + @Override + public boolean handleC2P(IPacket packet, List listeners) throws Exception { + if (packet instanceof UnknownPacket unknownPacket && this.proxyConnection.getC2pConnectionState() == ConnectionState.PLAY) { + final UserConnection user = this.proxyConnection.getUserConnection(); + + if (unknownPacket.packetId == this.chatSessionUpdateId && (!this.isP2sEncrypted() || user.has(ChatSession1_19_3.class))) { + return false; + } else if (unknownPacket.packetId == this.chatMessageId && user.has(ChatSession1_19_3.class)) { + final ChatSession1_19_3 chatSession = user.get(ChatSession1_19_3.class); + + final ByteBuf oldChatMessage = Unpooled.wrappedBuffer(unknownPacket.data); + final String message = PacketTypes.readString(oldChatMessage, 256); // message + final long timestamp = oldChatMessage.readLong(); // timestamp + final long salt = oldChatMessage.readLong(); // salt + + final MessageMetadata metadata = new MessageMetadata(null, timestamp, salt); + final byte[] signature = chatSession.signChatMessage(metadata, message, new PlayerMessageSignature[0]); + + final ByteBuf newChatMessage = Unpooled.buffer(); + PacketTypes.writeVarInt(newChatMessage, this.chatMessageId); + PacketTypes.writeString(newChatMessage, message); // message + newChatMessage.writeLong(timestamp); // timestamp + newChatMessage.writeLong(salt); // salt + Type.OPTIONAL_SIGNATURE_BYTES.write(newChatMessage, signature); // signature + PacketTypes.writeVarInt(newChatMessage, 0); // offset + new BitSetType(20).write(newChatMessage, new BitSet(20)); // acknowledged + this.proxyConnection.getChannel().writeAndFlush(newChatMessage).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); + + return false; + } + } + + return true; + } + + @Override + public boolean handleP2S(IPacket packet, List listeners) { + if (packet instanceof UnknownPacket unknownPacket && this.proxyConnection.getC2pConnectionState() == ConnectionState.PLAY) { + final UserConnection user = this.proxyConnection.getUserConnection(); + + if (unknownPacket.packetId == this.joinGameId && this.isP2sEncrypted() && user.has(ChatSession1_19_3.class)) { + final ChatSession1_19_3 chatSession = user.get(ChatSession1_19_3.class); + listeners.add(f -> { + if (f.isSuccess()) { + final ByteBuf chatSessionUpdate = Unpooled.buffer(); + PacketTypes.writeVarInt(chatSessionUpdate, this.chatSessionUpdateId); + PacketTypes.writeUuid(chatSessionUpdate, chatSession.getSessionId()); // session id + Type.PROFILE_KEY.write(chatSessionUpdate, chatSession.getProfileKey()); // profile key + this.proxyConnection.getChannel().writeAndFlush(chatSessionUpdate).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); + } + }); + } + } + + return true; + } + + private boolean isP2sEncrypted() { + return this.proxyConnection.getChannel().attr(MCPipeline.ENCRYPTION_ATTRIBUTE_KEY).get() != null; + } + +} diff --git a/src/main/java/net/raphimc/viaproxy/proxy/packethandler/LoginPacketHandler.java b/src/main/java/net/raphimc/viaproxy/proxy/packethandler/LoginPacketHandler.java index a72de7b..b76f6f2 100644 --- a/src/main/java/net/raphimc/viaproxy/proxy/packethandler/LoginPacketHandler.java +++ b/src/main/java/net/raphimc/viaproxy/proxy/packethandler/LoginPacketHandler.java @@ -20,12 +20,10 @@ package net.raphimc.viaproxy.proxy.packethandler; import com.mojang.authlib.GameProfile; import io.netty.channel.ChannelFutureListener; import net.raphimc.netminecraft.constants.ConnectionState; -import net.raphimc.netminecraft.constants.MCPackets; import net.raphimc.netminecraft.constants.MCPipeline; import net.raphimc.netminecraft.netty.crypto.AESEncryption; import net.raphimc.netminecraft.netty.crypto.CryptUtil; import net.raphimc.netminecraft.packet.IPacket; -import net.raphimc.netminecraft.packet.UnknownPacket; import net.raphimc.netminecraft.packet.impl.login.*; import net.raphimc.vialegacy.protocols.release.protocol1_7_2_5to1_6_4.storage.ProtocolMetadataStorage; import net.raphimc.vialoader.util.VersionEnum; @@ -61,22 +59,15 @@ public class LoginPacketHandler extends PacketHandler { private final byte[] verifyToken = new byte[4]; private LoginState loginState = LoginState.FIRST_PACKET; - private final int chatSessionUpdateId; - public LoginPacketHandler(ProxyConnection proxyConnection) { super(proxyConnection); RANDOM.nextBytes(this.verifyToken); - this.chatSessionUpdateId = MCPackets.C2S_CHAT_SESSION_UPDATE.getId(proxyConnection.getClientVersion().getVersion()); } @Override public boolean handleC2P(IPacket packet, List listeners) throws GeneralSecurityException { - if (packet instanceof UnknownPacket unknownPacket && this.proxyConnection.getC2pConnectionState() == ConnectionState.PLAY) { - if (unknownPacket.packetId == this.chatSessionUpdateId && this.proxyConnection.getChannel().attr(MCPipeline.ENCRYPTION_ATTRIBUTE_KEY).get() == null) { - return false; - } - } else if (packet instanceof C2SLoginHelloPacket1_7 loginHelloPacket) { + if (packet instanceof C2SLoginHelloPacket1_7 loginHelloPacket) { if (this.loginState != LoginState.FIRST_PACKET) throw CloseAndReturn.INSTANCE; this.loginState = LoginState.SENT_HELLO;