From 48907be8135777bec5f7950737ba9bba5c18a8b8 Mon Sep 17 00:00:00 2001 From: RK_01 <50594595+RaphiMC@users.noreply.github.com> Date: Sun, 22 Oct 2023 09:51:00 +0000 Subject: [PATCH] Implement support for chat signing (#621) --- .../Protocol1_18_2To1_19.java | 64 +++++++++++++++-- .../storage/NonceStorage.java | 34 +++++++++ .../Protocol1_19_1To1_19_3.java | 27 +++++-- .../packets/EntityPackets1_19_3.java | 19 ++++- .../Protocol1_19To1_19_1.java | 71 +++++++++++-------- 5 files changed, 173 insertions(+), 42 deletions(-) create mode 100644 common/src/main/java/com/viaversion/viabackwards/protocol/protocol1_18_2to1_19/storage/NonceStorage.java diff --git a/common/src/main/java/com/viaversion/viabackwards/protocol/protocol1_18_2to1_19/Protocol1_18_2To1_19.java b/common/src/main/java/com/viaversion/viabackwards/protocol/protocol1_18_2to1_19/Protocol1_18_2To1_19.java index 3c6030e7..edfcb124 100644 --- a/common/src/main/java/com/viaversion/viabackwards/protocol/protocol1_18_2to1_19/Protocol1_18_2To1_19.java +++ b/common/src/main/java/com/viaversion/viabackwards/protocol/protocol1_18_2to1_19/Protocol1_18_2To1_19.java @@ -17,6 +17,7 @@ */ package com.viaversion.viabackwards.protocol.protocol1_18_2to1_19; +import com.google.common.primitives.Longs; import com.viaversion.viabackwards.ViaBackwards; import com.viaversion.viabackwards.api.BackwardsProtocol; import com.viaversion.viabackwards.api.rewriters.SoundRewriter; @@ -26,10 +27,14 @@ import com.viaversion.viabackwards.protocol.protocol1_18_2to1_19.data.CommandRew import com.viaversion.viabackwards.protocol.protocol1_18_2to1_19.packets.BlockItemPackets1_19; import com.viaversion.viabackwards.protocol.protocol1_18_2to1_19.packets.EntityPackets1_19; import com.viaversion.viabackwards.protocol.protocol1_18_2to1_19.storage.DimensionRegistryStorage; +import com.viaversion.viabackwards.protocol.protocol1_18_2to1_19.storage.NonceStorage; import com.viaversion.viabackwards.protocol.protocol1_19to1_19_1.Protocol1_19To1_19_1; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.minecraft.RegistryType; import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_19; +import com.viaversion.viaversion.api.minecraft.signature.model.DecoratableMessage; +import com.viaversion.viaversion.api.minecraft.signature.model.MessageMetadata; +import com.viaversion.viaversion.api.minecraft.signature.storage.ChatSession1_19_0; import com.viaversion.viaversion.api.protocol.packet.State; import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers; import com.viaversion.viaversion.api.type.Type; @@ -48,8 +53,10 @@ import com.viaversion.viaversion.rewriter.CommandRewriter; import com.viaversion.viaversion.rewriter.ComponentRewriter; import com.viaversion.viaversion.rewriter.StatisticsRewriter; import com.viaversion.viaversion.rewriter.TagRewriter; + import java.time.Instant; import java.util.UUID; +import java.util.concurrent.ThreadLocalRandom; public final class Protocol1_18_2To1_19 extends BackwardsProtocol { @@ -223,14 +230,31 @@ public final class Protocol1_18_2To1_19 extends BackwardsProtocol wrapper.write(Type.LONG, Instant.now().toEpochMilli())); // Timestamp create(Type.LONG, 0L); // Salt handler(wrapper -> { + final ChatSession1_19_0 chatSession = wrapper.user().get(ChatSession1_19_0.class); + final String message = wrapper.get(Type.STRING, 0); if (!message.isEmpty() && message.charAt(0) == '/') { wrapper.setPacketType(ServerboundPackets1_19.CHAT_COMMAND); wrapper.set(Type.STRING, 0, message.substring(1)); wrapper.write(Type.VAR_INT, 0); // No signatures } else { - wrapper.write(Type.BYTE_ARRAY_PRIMITIVE, EMPTY_BYTES); // Signature + if (chatSession != null) { + final UUID sender = wrapper.user().getProtocolInfo().getUuid(); + final Instant timestamp = Instant.now(); + final long salt = ThreadLocalRandom.current().nextLong(); + + final MessageMetadata metadata = new MessageMetadata(sender, timestamp, salt); + final DecoratableMessage decoratableMessage = new DecoratableMessage(message); + final byte[] signature = chatSession.signChatMessage(metadata, decoratableMessage); + + wrapper.set(Type.LONG, 0, timestamp.toEpochMilli()); // Timestamp + wrapper.set(Type.LONG, 1, salt); // Salt + wrapper.write(Type.BYTE_ARRAY_PRIMITIVE, signature); // Signature + } else { + wrapper.write(Type.BYTE_ARRAY_PRIMITIVE, EMPTY_BYTES); // Signature + } } + wrapper.write(Type.BOOLEAN, false); // No signed preview }); } @@ -255,20 +279,50 @@ public final class Protocol1_18_2To1_19 extends BackwardsProtocol { + if (wrapper.user().has(ChatSession1_19_0.class)) { + wrapper.user().put(new NonceStorage(wrapper.passthrough(Type.BYTE_ARRAY_PRIMITIVE))); // Nonce + } + }); + } + }); registerServerbound(State.LOGIN, ServerboundLoginPackets.HELLO.getId(), ServerboundLoginPackets.HELLO.getId(), new PacketHandlers() { @Override public void register() { map(Type.STRING); // Name - // Write empty profile key - requires the enforce-secure-profiles option to be disabled on the server - create(Type.OPTIONAL_PROFILE_KEY, null); + handler(wrapper -> { + final ChatSession1_19_0 chatSession = wrapper.user().get(ChatSession1_19_0.class); + wrapper.write(Type.OPTIONAL_PROFILE_KEY, chatSession == null ? null : chatSession.getProfileKey()); // Profile Key + }); } }); registerServerbound(State.LOGIN, ServerboundLoginPackets.ENCRYPTION_KEY.getId(), ServerboundLoginPackets.ENCRYPTION_KEY.getId(), new PacketHandlers() { @Override public void register() { - map(Type.BYTE_ARRAY_PRIMITIVE); // Keys - create(Type.BOOLEAN, true); // Is nonce + map(Type.BYTE_ARRAY_PRIMITIVE); // Public key + handler(wrapper -> { + final ChatSession1_19_0 chatSession = wrapper.user().get(ChatSession1_19_0.class); + + final byte[] verifyToken = wrapper.read(Type.BYTE_ARRAY_PRIMITIVE); // Verify token + wrapper.write(Type.BOOLEAN, chatSession == null); // Is nonce + if (chatSession != null) { + final long salt = ThreadLocalRandom.current().nextLong(); + final byte[] signature = chatSession.sign(signer -> { + signer.accept(wrapper.user().remove(NonceStorage.class).nonce()); + signer.accept(Longs.toByteArray(salt)); + }); + wrapper.write(Type.LONG, salt); // Salt + wrapper.write(Type.BYTE_ARRAY_PRIMITIVE, signature); // Signature + } else { + wrapper.write(Type.BYTE_ARRAY_PRIMITIVE, verifyToken); // Nonce + } + }); } }); } diff --git a/common/src/main/java/com/viaversion/viabackwards/protocol/protocol1_18_2to1_19/storage/NonceStorage.java b/common/src/main/java/com/viaversion/viabackwards/protocol/protocol1_18_2to1_19/storage/NonceStorage.java new file mode 100644 index 00000000..be1e311d --- /dev/null +++ b/common/src/main/java/com/viaversion/viabackwards/protocol/protocol1_18_2to1_19/storage/NonceStorage.java @@ -0,0 +1,34 @@ +/* + * This file is part of ViaBackwards - https://github.com/ViaVersion/ViaBackwards + * Copyright (C) 2016-2023 ViaVersion 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 com.viaversion.viabackwards.protocol.protocol1_18_2to1_19.storage; + +import com.viaversion.viaversion.api.connection.StorableObject; +import org.checkerframework.checker.nullness.qual.Nullable; + +public final class NonceStorage implements StorableObject { + + private final byte[] nonce; + + public NonceStorage(final byte @Nullable [] nonce) { + this.nonce = nonce; + } + + public byte @Nullable [] nonce() { + return nonce; + } +} diff --git a/common/src/main/java/com/viaversion/viabackwards/protocol/protocol1_19_1to1_19_3/Protocol1_19_1To1_19_3.java b/common/src/main/java/com/viaversion/viabackwards/protocol/protocol1_19_1to1_19_3/Protocol1_19_1To1_19_3.java index 55965035..7242ef87 100644 --- a/common/src/main/java/com/viaversion/viabackwards/protocol/protocol1_19_1to1_19_3/Protocol1_19_1To1_19_3.java +++ b/common/src/main/java/com/viaversion/viabackwards/protocol/protocol1_19_1to1_19_3/Protocol1_19_1To1_19_3.java @@ -29,9 +29,12 @@ import com.viaversion.viabackwards.protocol.protocol1_19_1to1_19_3.storage.ChatT import com.viaversion.viabackwards.protocol.protocol1_19_1to1_19_3.storage.NonceStorage; import com.viaversion.viabackwards.protocol.protocol1_19to1_19_1.Protocol1_19To1_19_1; import com.viaversion.viaversion.api.connection.UserConnection; +import com.viaversion.viaversion.api.minecraft.PlayerMessageSignature; import com.viaversion.viaversion.api.minecraft.ProfileKey; import com.viaversion.viaversion.api.minecraft.RegistryType; import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_19_3; +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.protocol.packet.State; import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers; import com.viaversion.viaversion.api.type.Type; @@ -52,6 +55,7 @@ import com.viaversion.viaversion.rewriter.ComponentRewriter; import com.viaversion.viaversion.rewriter.StatisticsRewriter; import com.viaversion.viaversion.rewriter.TagRewriter; import com.viaversion.viaversion.util.CipherUtil; + import java.util.BitSet; public final class Protocol1_19_1To1_19_3 extends BackwardsProtocol { @@ -220,6 +224,7 @@ public final class Protocol1_19_1To1_19_3 extends BackwardsProtocol { + final ChatSession1_19_3 chatSession = wrapper.user().get(ChatSession1_19_3.class); + + if (chatSession != null) { + final String message = wrapper.get(Type.STRING, 0); + final long timestamp = wrapper.get(Type.LONG, 0); + final long salt = wrapper.get(Type.LONG, 1); + + final MessageMetadata metadata = new MessageMetadata(null, timestamp, salt); + final byte[] signature = chatSession.signChatMessage(metadata, message, new PlayerMessageSignature[0]); + + wrapper.write(Protocol1_19_1To1_19_3.OPTIONAL_SIGNATURE_BYTES_TYPE, signature); // Signature + } else { + wrapper.write(Protocol1_19_1To1_19_3.OPTIONAL_SIGNATURE_BYTES_TYPE, null); // Signature + } + //TODO is this fine (probably not)? same for chat_command - final int offset = 0; - final BitSet acknowledged = new BitSet(20); - wrapper.write(Type.VAR_INT, offset); - wrapper.write(new BitSetType(20), acknowledged); + wrapper.write(Type.VAR_INT, 0); // Offset + wrapper.write(new BitSetType(20), new BitSet(20)); // Acknowledged }); } }); diff --git a/common/src/main/java/com/viaversion/viabackwards/protocol/protocol1_19_1to1_19_3/packets/EntityPackets1_19_3.java b/common/src/main/java/com/viaversion/viabackwards/protocol/protocol1_19_1to1_19_3/packets/EntityPackets1_19_3.java index 42b28335..ec2af4ac 100644 --- a/common/src/main/java/com/viaversion/viabackwards/protocol/protocol1_19_1to1_19_3/packets/EntityPackets1_19_3.java +++ b/common/src/main/java/com/viaversion/viabackwards/protocol/protocol1_19_1to1_19_3/packets/EntityPackets1_19_3.java @@ -22,8 +22,9 @@ import com.viaversion.viabackwards.protocol.protocol1_19_1to1_19_3.Protocol1_19_ import com.viaversion.viabackwards.protocol.protocol1_19_1to1_19_3.storage.ChatTypeStorage1_19_3; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.minecraft.ProfileKey; -import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_19_3; import com.viaversion.viaversion.api.minecraft.entities.EntityType; +import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_19_3; +import com.viaversion.viaversion.api.minecraft.signature.storage.ChatSession1_19_3; import com.viaversion.viaversion.api.protocol.packet.PacketWrapper; import com.viaversion.viaversion.api.protocol.remapper.PacketHandlers; import com.viaversion.viaversion.api.type.Type; @@ -37,9 +38,11 @@ import com.viaversion.viaversion.libs.opennbt.tag.builtin.NumberTag; import com.viaversion.viaversion.libs.opennbt.tag.builtin.Tag; import com.viaversion.viaversion.protocols.protocol1_19_1to1_19.ClientboundPackets1_19_1; import com.viaversion.viaversion.protocols.protocol1_19_3to1_19_1.ClientboundPackets1_19_3; +import com.viaversion.viaversion.protocols.protocol1_19_3to1_19_1.ServerboundPackets1_19_3; +import org.checkerframework.checker.nullness.qual.Nullable; + import java.util.BitSet; import java.util.UUID; -import org.checkerframework.checker.nullness.qual.Nullable; public final class EntityPackets1_19_3 extends EntityRewriter { @@ -87,6 +90,16 @@ public final class EntityPackets1_19_3 extends EntityRewriter { + final ChatSession1_19_3 chatSession = wrapper.user().get(ChatSession1_19_3.class); + + if (chatSession != null) { + final PacketWrapper chatSessionUpdate = wrapper.create(ServerboundPackets1_19_3.CHAT_SESSION_UPDATE); + chatSessionUpdate.write(Type.UUID, chatSession.getSessionId()); + chatSessionUpdate.write(Type.PROFILE_KEY, chatSession.getProfileKey()); + chatSessionUpdate.sendToServer(Protocol1_19_1To1_19_3.class); + } + }); } }); @@ -291,4 +304,4 @@ public final class EntityPackets1_19_3 extends EntityRewriter { @@ -194,12 +193,29 @@ public final class Protocol1_19To1_19_1 extends BackwardsProtocol { + final ChatSession1_19_1 chatSession = wrapper.user().get(ChatSession1_19_1.class); final ReceivedMessagesStorage messagesStorage = wrapper.user().get(ReceivedMessagesStorage.class); + + if (chatSession != null) { + final UUID sender = wrapper.user().getProtocolInfo().getUuid(); + final String message = wrapper.get(Type.STRING, 0); + final long timestamp = wrapper.get(Type.LONG, 0); + final long salt = wrapper.get(Type.LONG, 1); + + final MessageMetadata metadata = new MessageMetadata(sender, timestamp, salt); + final DecoratableMessage decoratableMessage = new DecoratableMessage(message); + final byte[] signature = chatSession.signChatMessage(metadata, decoratableMessage, messagesStorage.lastSignatures()); + + wrapper.write(Type.BYTE_ARRAY_PRIMITIVE, signature); // Signature + wrapper.write(Type.BOOLEAN, decoratableMessage.isDecorated()); // Signed preview + } else { + wrapper.write(Type.BYTE_ARRAY_PRIMITIVE, EMPTY_BYTES); // Signature + wrapper.write(Type.BOOLEAN, false); // Signed preview + } + messagesStorage.resetUnacknowledgedCount(); wrapper.write(Type.PLAYER_MESSAGE_SIGNATURE_ARRAY, messagesStorage.lastSignatures()); wrapper.write(Type.OPTIONAL_PLAYER_MESSAGE_SIGNATURE, null); // No last unacknowledged @@ -248,14 +264,16 @@ public final class Protocol1_19To1_19_1 extends BackwardsProtocol { - final ProfileKey profileKey = wrapper.read(Type.OPTIONAL_PROFILE_KEY); - if (profileKey == null) { + final ProfileKey profileKey = wrapper.read(Type.OPTIONAL_PROFILE_KEY); // Profile Key + + final ChatSession1_19_1 chatSession = wrapper.user().get(ChatSession1_19_1.class); + wrapper.write(Type.OPTIONAL_PROFILE_KEY, chatSession == null ? null : chatSession.getProfileKey()); // Profile Key + wrapper.write(Type.OPTIONAL_UUID, chatSession == null ? null : chatSession.getUuid()); // Profile uuid + + if (profileKey == null || chatSession != null) { wrapper.user().put(new NonceStorage(null)); } }); - // Write empty profile key and uuid (since keys are not compatible) - requires the enforce-secure-profiles option to be disabled on the server - create(Type.OPTIONAL_PROFILE_KEY, null); - create(Type.OPTIONAL_UUID, null); } }); @@ -264,8 +282,7 @@ public final class Protocol1_19To1_19_1 extends BackwardsProtocol { - if (wrapper.user().get(NonceStorage.class) != null) { - // Is already using the encrypted nonce + if (wrapper.user().has(NonceStorage.class)) { return; } @@ -282,21 +299,17 @@ public final class Protocol1_19To1_19_1 extends BackwardsProtocol { final NonceStorage nonceStorage = wrapper.user().remove(NonceStorage.class); - final boolean isNonce = wrapper.read(Type.BOOLEAN); - wrapper.write(Type.BOOLEAN, true); - if (isNonce) { - // Nonce, just pass it through - wrapper.passthrough(Type.BYTE_ARRAY_PRIMITIVE); + if (nonceStorage.nonce() == null) { return; } - if (nonceStorage == null || nonceStorage.nonce() == null) { - throw new IllegalArgumentException("Server sent nonce is missing"); + final boolean isNonce = wrapper.read(Type.BOOLEAN); + wrapper.write(Type.BOOLEAN, true); + if (!isNonce) { // Should never be true at this point, but /shrug otherwise + wrapper.read(Type.LONG); // Salt + wrapper.read(Type.BYTE_ARRAY_PRIMITIVE); // Signature + wrapper.write(Type.BYTE_ARRAY_PRIMITIVE, nonceStorage.nonce()); } - - wrapper.read(Type.LONG); // Salt - wrapper.read(Type.BYTE_ARRAY_PRIMITIVE); // Signature - wrapper.write(Type.BYTE_ARRAY_PRIMITIVE, nonceStorage.nonce()); }); } });