Skip to content

Commit

Permalink
Add chat signing support for 1.20.x clients on 1.20.x servers
Browse files Browse the repository at this point in the history
  • Loading branch information
RaphiMC committed Nov 28, 2023
1 parent 0b88a9d commit 058466b
Show file tree
Hide file tree
Showing 3 changed files with 119 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,9 @@ private void handleHandshake(final C2SHandshakePacket packet) {
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));

Expand Down
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/
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<ChannelFutureListener> 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<ChannelFutureListener> 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;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,10 @@
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;
Expand Down Expand Up @@ -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<ChannelFutureListener> 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;

Expand Down

0 comments on commit 058466b

Please sign in to comment.