From 4991123cda841726c741da0503fff9ecd0dc13b4 Mon Sep 17 00:00:00 2001 From: Evan Goode Date: Mon, 25 May 2026 15:32:25 -0400 Subject: [PATCH] Fix "Invalid signature" when not forwarding UUIDs Filter the ServerboundChatSessionUpdatePacket when player info forwarding is disabled, since the client's profile key signature will not be valid for the player's offline UUID. --- .../connection/MinecraftSessionHandler.java | 5 ++ .../client/ClientPlaySessionHandler.java | 26 +++++++++ .../proxy/protocol/StateRegistry.java | 14 +++++ .../chat/session/ChatSessionUpdatePacket.java | 57 +++++++++++++++++++ 4 files changed, 102 insertions(+) create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/session/ChatSessionUpdatePacket.java diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftSessionHandler.java index d1101d6a5a..5ef7e1ce6a 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftSessionHandler.java @@ -67,6 +67,7 @@ import com.velocitypowered.proxy.protocol.packet.chat.keyed.KeyedPlayerChatPacket; import com.velocitypowered.proxy.protocol.packet.chat.keyed.KeyedPlayerCommandPacket; import com.velocitypowered.proxy.protocol.packet.chat.legacy.LegacyChatPacket; +import com.velocitypowered.proxy.protocol.packet.chat.session.ChatSessionUpdatePacket; import com.velocitypowered.proxy.protocol.packet.chat.session.SessionPlayerChatPacket; import com.velocitypowered.proxy.protocol.packet.chat.session.SessionPlayerCommandPacket; import com.velocitypowered.proxy.protocol.packet.config.ActiveFeaturesPacket; @@ -280,6 +281,10 @@ default boolean handle(SessionPlayerChatPacket packet) { return false; } + default boolean handle(ChatSessionUpdatePacket packet) { + return false; + } + default boolean handle(SystemChatPacket packet) { return false; } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java index 8880233bb3..4025f8314b 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java @@ -31,6 +31,7 @@ import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.proxy.messages.ChannelIdentifier; import com.velocitypowered.proxy.VelocityServer; +import com.velocitypowered.proxy.config.PlayerInfoForwarding; import com.velocitypowered.proxy.connection.ConnectionTypes; import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; @@ -65,6 +66,7 @@ import com.velocitypowered.proxy.protocol.packet.chat.legacy.LegacyChatHandler; import com.velocitypowered.proxy.protocol.packet.chat.legacy.LegacyChatPacket; import com.velocitypowered.proxy.protocol.packet.chat.legacy.LegacyCommandHandler; +import com.velocitypowered.proxy.protocol.packet.chat.session.ChatSessionUpdatePacket; import com.velocitypowered.proxy.protocol.packet.chat.session.SessionChatHandler; import com.velocitypowered.proxy.protocol.packet.chat.session.SessionCommandHandler; import com.velocitypowered.proxy.protocol.packet.chat.session.SessionPlayerChatPacket; @@ -241,6 +243,30 @@ public boolean handle(ClientSettingsPacket packet) { return true; // will forward onto the server } + @Override + public boolean handle(ChatSessionUpdatePacket packet) { + // If client UUIDs are not forwarded to the backend, the client players's + // profile key signature will not be valid for the backend player's offline + // UUID, and the backend will kick the client with "Invalid signature for + // profile public key". The Notchian server does not require the client to + // send this packet if enforce-secure-profile=false, so we can simply + // filter it. + + // Note: if the Notchian client did not receive a valid profile key pair + // from the authentication server, it also will not send this packet to the + // server (net/minecraft/client/multiplayer/ClientPacketListener.java). + if (server.getConfiguration().getPlayerInfoForwardingMode() == PlayerInfoForwarding.NONE) { + return true; + } + + VelocityServerConnection serverConnection = player.getConnectedServer(); + if (serverConnection == null) { + return true; + } + serverConnection.ensureConnected().write(packet); + return true; + } + @Override public boolean handle(SessionPlayerCommandPacket packet) { if (player.getCurrentServer().isEmpty()) { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java index 2971087685..97e65de46d 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java @@ -102,6 +102,7 @@ import com.velocitypowered.proxy.protocol.packet.chat.keyed.KeyedPlayerChatPacket; import com.velocitypowered.proxy.protocol.packet.chat.keyed.KeyedPlayerCommandPacket; import com.velocitypowered.proxy.protocol.packet.chat.legacy.LegacyChatPacket; +import com.velocitypowered.proxy.protocol.packet.chat.session.ChatSessionUpdatePacket; import com.velocitypowered.proxy.protocol.packet.chat.session.SessionPlayerChatPacket; import com.velocitypowered.proxy.protocol.packet.chat.session.SessionPlayerCommandPacket; import com.velocitypowered.proxy.protocol.packet.chat.session.UnsignedPlayerCommandPacket; @@ -321,6 +322,19 @@ public enum StateRegistry { map(0x07, MINECRAFT_1_21_2, false), map(0x08, MINECRAFT_1_21_6, false), map(0x09, MINECRAFT_26_1, false)); + serverbound.register( + ChatSessionUpdatePacket.class, + ChatSessionUpdatePacket::new, + map(0x20, MINECRAFT_1_19_3, false), + map(0x06, MINECRAFT_1_19_4, false), + map(0x06, MINECRAFT_1_20_2, false), + map(0x07, MINECRAFT_1_20_5, false), + map(0x07, MINECRAFT_1_21, false), + map(0x08, MINECRAFT_1_21_2, false), + map(0x08, MINECRAFT_1_21_4, false), + map(0x09, MINECRAFT_1_21_6, false), + map(0x09, MINECRAFT_1_21_9, false), + map(0x0A, MINECRAFT_26_1, false)); serverbound.register( ClientSettingsPacket.class, ClientSettingsPacket::new, diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/session/ChatSessionUpdatePacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/session/ChatSessionUpdatePacket.java new file mode 100644 index 0000000000..40156dd903 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/session/ChatSessionUpdatePacket.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2018-2023 Velocity 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 com.velocitypowered.proxy.protocol.packet.chat.session; + +import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.proxy.connection.MinecraftSessionHandler; +import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import io.netty.buffer.ByteBuf; +import java.util.UUID; + +public class ChatSessionUpdatePacket implements MinecraftPacket { + + private UUID sessionId; + private long expiresAt; + private byte[] publicKey; + private byte[] keySignature; + + public ChatSessionUpdatePacket() { + } + + @Override + public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + this.sessionId = ProtocolUtils.readUuid(buf); + this.expiresAt = buf.readLong(); + this.publicKey = ProtocolUtils.readByteArray(buf, 512); + this.keySignature = ProtocolUtils.readByteArray(buf, 4096); + } + + @Override + public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + ProtocolUtils.writeUuid(buf, sessionId); + buf.writeLong(expiresAt); + ProtocolUtils.writeByteArray(buf, publicKey); + ProtocolUtils.writeByteArray(buf, keySignature); + } + + @Override + public boolean handle(MinecraftSessionHandler handler) { + return handler.handle(this); + } +}