3
0
Mirror von https://github.com/ViaVersion/ViaVersion.git synchronisiert 2024-12-28 00:50:13 +01:00

Send unsigned messages if possible

The server cannot guarantee that the last seen values are correct, so in order to avoid kicks from invalid signatures, we will just strip them if possible.
Dieser Commit ist enthalten in:
Nassim Jahnke 2024-04-07 22:01:48 +02:00
Ursprung dbe8a389ce
Commit 33e2a1fc13
Es konnte kein GPG-Schlüssel zu dieser Signatur gefunden werden
GPG-Schlüssel-ID: EF6771C01F6EF02F
2 geänderte Dateien mit 135 neuen und 26 gelöschten Zeilen

Datei anzeigen

@ -18,6 +18,7 @@
package com.viaversion.viaversion.protocols.protocol1_20_5to1_20_3; package com.viaversion.viaversion.protocols.protocol1_20_5to1_20_3;
import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.connection.UserConnection;
import com.viaversion.viaversion.api.minecraft.ProfileKey;
import com.viaversion.viaversion.api.minecraft.RegistryType; import com.viaversion.viaversion.api.minecraft.RegistryType;
import com.viaversion.viaversion.api.minecraft.data.StructuredDataKey; import com.viaversion.viaversion.api.minecraft.data.StructuredDataKey;
import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_20_5; import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_20_5;
@ -52,6 +53,7 @@ import com.viaversion.viaversion.protocols.protocol1_20_5to1_20_3.storage.Acknow
import com.viaversion.viaversion.rewriter.SoundRewriter; import com.viaversion.viaversion.rewriter.SoundRewriter;
import com.viaversion.viaversion.rewriter.StatisticsRewriter; import com.viaversion.viaversion.rewriter.StatisticsRewriter;
import com.viaversion.viaversion.rewriter.TagRewriter; import com.viaversion.viaversion.rewriter.TagRewriter;
import java.util.UUID;
import static com.viaversion.viaversion.util.ProtocolUtil.packetTypeMap; import static com.viaversion.viaversion.util.ProtocolUtil.packetTypeMap;
@ -89,7 +91,15 @@ public final class Protocol1_20_5To1_20_3 extends AbstractProtocol<ClientboundPa
registerClientbound(ClientboundPackets1_20_3.SERVER_DATA, wrapper -> { registerClientbound(ClientboundPackets1_20_3.SERVER_DATA, wrapper -> {
wrapper.passthrough(Type.TAG); // MOTD wrapper.passthrough(Type.TAG); // MOTD
wrapper.passthrough(Type.OPTIONAL_BYTE_ARRAY_PRIMITIVE); // Icon wrapper.passthrough(Type.OPTIONAL_BYTE_ARRAY_PRIMITIVE); // Icon
wrapper.read(Type.BOOLEAN); // Enforces secure chat - moved to join game
// Moved to join game
final boolean enforcesSecureChat = wrapper.read(Type.BOOLEAN);
final AcknowledgedMessagesStorage storage = wrapper.user().get(AcknowledgedMessagesStorage.class);
storage.setSecureChatEnforced(enforcesSecureChat);
if (enforcesSecureChat) {
// Only send the chat session to the server if we know that it is required
storage.sendQueuedChatSession(wrapper);
}
}); });
// Big problem with this update: Without access to the client, this cannot 100% predict the // Big problem with this update: Without access to the client, this cannot 100% predict the
@ -98,43 +108,65 @@ public final class Protocol1_20_5To1_20_3 extends AbstractProtocol<ClientboundPa
wrapper.passthrough(Type.UUID); // Sender wrapper.passthrough(Type.UUID); // Sender
wrapper.passthrough(Type.VAR_INT); // Index wrapper.passthrough(Type.VAR_INT); // Index
final byte[] signature = wrapper.passthrough(Type.OPTIONAL_SIGNATURE_BYTES); final byte[] signature = wrapper.passthrough(Type.OPTIONAL_SIGNATURE_BYTES);
if (signature == null) {
return;
}
// Mimic client behavior for acknowledgements
final AcknowledgedMessagesStorage storage = wrapper.user().get(AcknowledgedMessagesStorage.class); final AcknowledgedMessagesStorage storage = wrapper.user().get(AcknowledgedMessagesStorage.class);
if (signature != null && signature.length != 0) { if (storage.add(signature) && storage.offset() > 64) {
if (storage.add(signature) && storage.offset() > 64) { final PacketWrapper chatAck = wrapper.create(ServerboundPackets1_20_3.CHAT_ACK);
final PacketWrapper chatAck = wrapper.create(ServerboundPackets1_20_3.CHAT_ACK); chatAck.write(Type.VAR_INT, storage.offset());
chatAck.write(Type.VAR_INT, storage.offset()); chatAck.sendToServer(Protocol1_20_5To1_20_3.class);
chatAck.sendToServer(Protocol1_20_5To1_20_3.class);
storage.clearOffset(); storage.clearOffset();
}
} }
}); });
registerServerbound(ServerboundPackets1_20_5.CHAT_MESSAGE, wrapper -> { registerServerbound(ServerboundPackets1_20_5.CHAT_MESSAGE, wrapper -> {
wrapper.passthrough(Type.STRING); // Message wrapper.passthrough(Type.STRING); // Message
wrapper.passthrough(Type.LONG); // Timestamp wrapper.passthrough(Type.LONG); // Timestamp
wrapper.passthrough(Type.LONG); // Salt
wrapper.passthrough(Type.OPTIONAL_SIGNATURE_BYTES); // Signature
// Remove original acknowledgement final AcknowledgedMessagesStorage storage = wrapper.user().get(AcknowledgedMessagesStorage.class);
wrapper.read(Type.VAR_INT); // Offset final long salt = wrapper.read(Type.LONG);
wrapper.read(Type.ACKNOWLEDGED_BIT_SET); // Acknowledged final byte[] signature = wrapper.read(Type.OPTIONAL_SIGNATURE_BYTES);
writeChatAck(wrapper); if (storage.isSecureChatEnforced()) {
// Fake it till you make it
wrapper.write(Type.LONG, salt);
wrapper.write(Type.OPTIONAL_SIGNATURE_BYTES, signature);
} else {
// Go the safer route and strip the signature. No signature means no verification
wrapper.write(Type.LONG, 0L);
wrapper.write(Type.OPTIONAL_SIGNATURE_BYTES, null);
}
replaceChatAck(wrapper, storage);
}); });
registerServerbound(ServerboundPackets1_20_5.CHAT_COMMAND_SIGNED, ServerboundPackets1_20_3.CHAT_COMMAND, wrapper -> { registerServerbound(ServerboundPackets1_20_5.CHAT_COMMAND_SIGNED, ServerboundPackets1_20_3.CHAT_COMMAND, wrapper -> {
wrapper.passthrough(Type.STRING); // Command wrapper.passthrough(Type.STRING); // Command
wrapper.passthrough(Type.LONG); // Timestamp wrapper.passthrough(Type.LONG); // Timestamp
wrapper.passthrough(Type.LONG); // Salt
final int signatures = wrapper.passthrough(Type.VAR_INT); // See above, strip signatures if we can to prevent verification of possibly bad signatures
for (int i = 0; i < signatures; i++) { final AcknowledgedMessagesStorage storage = wrapper.user().get(AcknowledgedMessagesStorage.class);
wrapper.passthrough(Type.STRING); // Argument name final long salt = wrapper.read(Type.LONG);
wrapper.passthrough(Type.SIGNATURE_BYTES); // Signature final int signatures = wrapper.read(Type.VAR_INT);
if (storage.isSecureChatEnforced()) {
wrapper.write(Type.LONG, salt);
wrapper.write(Type.VAR_INT, signatures);
for (int i = 0; i < signatures; i++) {
wrapper.passthrough(Type.STRING); // Argument name
wrapper.passthrough(Type.SIGNATURE_BYTES); // Signature
}
} else {
// Remove signatures
wrapper.write(Type.LONG, 0L);
wrapper.write(Type.VAR_INT, 0); // No signatures
for (int i = 0; i < signatures; i++) {
wrapper.read(Type.STRING); // Argument name
wrapper.read(Type.SIGNATURE_BYTES); // Signature
}
} }
// Remove original acknowledgement replaceChatAck(wrapper, storage);
wrapper.read(Type.VAR_INT); // Offset
wrapper.read(Type.ACKNOWLEDGED_BIT_SET); // Acknowledged
writeChatAck(wrapper);
}); });
registerServerbound(ServerboundPackets1_20_5.CHAT_COMMAND, wrapper -> { registerServerbound(ServerboundPackets1_20_5.CHAT_COMMAND, wrapper -> {
wrapper.passthrough(Type.STRING); // Command wrapper.passthrough(Type.STRING); // Command
@ -142,7 +174,23 @@ public final class Protocol1_20_5To1_20_3 extends AbstractProtocol<ClientboundPa
wrapper.write(Type.LONG, System.currentTimeMillis()); // Timestamp wrapper.write(Type.LONG, System.currentTimeMillis()); // Timestamp
wrapper.write(Type.LONG, 0L); // Salt wrapper.write(Type.LONG, 0L); // Salt
wrapper.write(Type.VAR_INT, 0); // No signatures wrapper.write(Type.VAR_INT, 0); // No signatures
writeChatAck(wrapper);
writeChatAck(wrapper, wrapper.user().get(AcknowledgedMessagesStorage.class));
});
registerServerbound(ServerboundPackets1_20_5.CHAT_SESSION_UPDATE, wrapper -> {
// Delay this until we know whether the server enforces secure chat
// The server sends this info in SERVER_DATA, but the client already sends this after receiving the game login
final AcknowledgedMessagesStorage storage = wrapper.user().get(AcknowledgedMessagesStorage.class);
if (storage.secureChatEnforced() != null && storage.secureChatEnforced()) {
// We already know that secure chat is enforced, let it through
return;
}
final UUID sessionId = wrapper.read(Type.UUID);
final ProfileKey profileKey = wrapper.read(Type.PROFILE_KEY);
storage.queueChatSession(sessionId, profileKey);
wrapper.cancel();
}); });
cancelServerbound(ServerboundPackets1_20_5.CHAT_ACK); cancelServerbound(ServerboundPackets1_20_5.CHAT_ACK);
@ -157,8 +205,13 @@ public final class Protocol1_20_5To1_20_3 extends AbstractProtocol<ClientboundPa
cancelServerbound(ServerboundPackets1_20_5.DEBUG_SAMPLE_SUBSCRIPTION); cancelServerbound(ServerboundPackets1_20_5.DEBUG_SAMPLE_SUBSCRIPTION);
} }
private void writeChatAck(final PacketWrapper wrapper) { private void replaceChatAck(final PacketWrapper wrapper, final AcknowledgedMessagesStorage storage) throws Exception {
final AcknowledgedMessagesStorage storage = wrapper.user().get(AcknowledgedMessagesStorage.class); wrapper.read(Type.VAR_INT); // Offset
wrapper.read(Type.ACKNOWLEDGED_BIT_SET); // Acknowledged
writeChatAck(wrapper, storage);
}
private void writeChatAck(final PacketWrapper wrapper, final AcknowledgedMessagesStorage storage) {
wrapper.write(Type.VAR_INT, storage.offset()); wrapper.write(Type.VAR_INT, storage.offset());
wrapper.write(Type.ACKNOWLEDGED_BIT_SET, storage.toAck()); wrapper.write(Type.ACKNOWLEDGED_BIT_SET, storage.toAck());
storage.clearOffset(); storage.clearOffset();

Datei anzeigen

@ -18,12 +18,21 @@
package com.viaversion.viaversion.protocols.protocol1_20_5to1_20_3.storage; package com.viaversion.viaversion.protocols.protocol1_20_5to1_20_3.storage;
import com.viaversion.viaversion.api.connection.StorableObject; import com.viaversion.viaversion.api.connection.StorableObject;
import com.viaversion.viaversion.api.minecraft.ProfileKey;
import com.viaversion.viaversion.api.protocol.packet.PacketWrapper;
import com.viaversion.viaversion.api.type.Type;
import com.viaversion.viaversion.protocols.protocol1_20_3to1_20_2.packet.ServerboundPackets1_20_3;
import com.viaversion.viaversion.protocols.protocol1_20_5to1_20_3.Protocol1_20_5To1_20_3;
import java.util.Arrays; import java.util.Arrays;
import java.util.BitSet; import java.util.BitSet;
import java.util.UUID;
import org.checkerframework.checker.nullness.qual.Nullable;
public final class AcknowledgedMessagesStorage implements StorableObject { public final class AcknowledgedMessagesStorage implements StorableObject {
private static final int MAX_HISTORY = 20; private static final int MAX_HISTORY = 20;
private final boolean[] trackedMessages = new boolean[MAX_HISTORY]; private final boolean[] trackedMessages = new boolean[MAX_HISTORY];
private Boolean secureChatEnforced;
private ChatSession chatSession;
private int offset; private int offset;
private int tail; private int tail;
private byte[] lastMessage; private byte[] lastMessage;
@ -55,4 +64,51 @@ public final class AcknowledgedMessagesStorage implements StorableObject {
public void clearOffset() { public void clearOffset() {
this.offset = 0; this.offset = 0;
} }
public void setSecureChatEnforced(final boolean secureChatEnforced) {
this.secureChatEnforced = secureChatEnforced;
}
public @Nullable Boolean secureChatEnforced() {
return this.secureChatEnforced;
}
public boolean isSecureChatEnforced() {
// Assume it is enforced by default
return this.secureChatEnforced == null || this.secureChatEnforced;
}
public void queueChatSession(final UUID sessionId, final ProfileKey profileKey) {
this.chatSession = new ChatSession(sessionId, profileKey);
}
public void sendQueuedChatSession(final PacketWrapper wrapper) throws Exception {
if (chatSession == null) {
return;
}
final PacketWrapper chatSessionUpdate = wrapper.create(ServerboundPackets1_20_3.CHAT_SESSION_UPDATE);
chatSessionUpdate.write(Type.UUID, chatSession.sessionId());
chatSessionUpdate.write(Type.PROFILE_KEY, chatSession.profileKey());
chatSessionUpdate.sendToServer(Protocol1_20_5To1_20_3.class);
chatSession = null;
}
public static final class ChatSession {
private final UUID sessionId;
private final ProfileKey profileKey;
public ChatSession(final UUID sessionId, final ProfileKey profileKey) {
this.sessionId = sessionId;
this.profileKey = profileKey;
}
public UUID sessionId() {
return sessionId;
}
public ProfileKey profileKey() {
return profileKey;
}
}
} }