From a47dd2ecdb8a35b531ba52b844b7de5b08bf5458 Mon Sep 17 00:00:00 2001 From: Nassim Jahnke Date: Fri, 15 Jul 2022 21:01:55 +0200 Subject: [PATCH] Support 1.19.1 clients on 1.19.0 servers --- .../viaversion/ViaVersionPlugin.java | 4 +- .../Protocol1_19_1To1_19.java | 108 ++++++++++++------ .../storage/NonceStorage.java | 34 ++++++ .../Protocol1_19To1_18_2.java | 25 +--- .../viaversion/util/CipherUtil.java | 46 ++++++++ 5 files changed, 160 insertions(+), 57 deletions(-) create mode 100644 common/src/main/java/com/viaversion/viaversion/protocols/protocol1_19_1to1_19/storage/NonceStorage.java create mode 100644 common/src/main/java/com/viaversion/viaversion/util/CipherUtil.java diff --git a/bukkit/src/main/java/com/viaversion/viaversion/ViaVersionPlugin.java b/bukkit/src/main/java/com/viaversion/viaversion/ViaVersionPlugin.java index 1a1c81fbe..f62d6cd20 100644 --- a/bukkit/src/main/java/com/viaversion/viaversion/ViaVersionPlugin.java +++ b/bukkit/src/main/java/com/viaversion/viaversion/ViaVersionPlugin.java @@ -293,8 +293,8 @@ public class ViaVersionPlugin extends JavaPlugin implements ViaPlatform .addClassName("yatopiamc.org.yatopia.server.YatopiaConfig") // Only the best kind of software relocates its own classes to hide itself :tinfoilhat: .addMethod("org.bukkit.Server", "getLastTickTime").build()); list.add(new UnsupportedPlugin.Builder().name("software to mess with message signing").reason(UnsupportedPlugin.Reason.SECURE_CHAT_BYPASS) - .addPlugin("NoEncryption") - .addPlugin("NoChatReports").build()); + .addPlugin("NoEncryption").addPlugin("NoReport") + .addPlugin("NoChatReports").addPlugin("NoChatReport").build()); return Collections.unmodifiableList(list); } diff --git a/common/src/main/java/com/viaversion/viaversion/protocols/protocol1_19_1to1_19/Protocol1_19_1To1_19.java b/common/src/main/java/com/viaversion/viaversion/protocols/protocol1_19_1to1_19/Protocol1_19_1To1_19.java index 3a7a97a8e..8a5925e83 100644 --- a/common/src/main/java/com/viaversion/viaversion/protocols/protocol1_19_1to1_19/Protocol1_19_1To1_19.java +++ b/common/src/main/java/com/viaversion/viaversion/protocols/protocol1_19_1to1_19/Protocol1_19_1To1_19.java @@ -19,14 +19,18 @@ package com.viaversion.viaversion.protocols.protocol1_19_1to1_19; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.google.gson.JsonElement; +import com.viaversion.viaversion.api.minecraft.ProfileKey; import com.viaversion.viaversion.api.minecraft.nbt.BinaryTagIO; import com.viaversion.viaversion.api.protocol.AbstractProtocol; import com.viaversion.viaversion.api.protocol.packet.State; import com.viaversion.viaversion.api.protocol.remapper.PacketRemapper; import com.viaversion.viaversion.api.type.Type; +import com.viaversion.viaversion.protocols.base.ClientboundLoginPackets; import com.viaversion.viaversion.protocols.base.ServerboundLoginPackets; +import com.viaversion.viaversion.protocols.protocol1_19_1to1_19.storage.NonceStorage; import com.viaversion.viaversion.protocols.protocol1_19to1_18_2.ClientboundPackets1_19; import com.viaversion.viaversion.protocols.protocol1_19to1_18_2.ServerboundPackets1_19; +import com.viaversion.viaversion.util.CipherUtil; import java.io.IOException; @@ -75,7 +79,6 @@ public final class Protocol1_19_1To1_19 extends AbstractProtocol1.19.1 translation registerClientbound(ClientboundPackets1_19.SYSTEM_CHAT, new PacketRemapper() { @Override public void registerMap() { @@ -87,17 +90,16 @@ public final class Protocol1_19_1To1_19 extends AbstractProtocol { + // Back to system chat final JsonElement signedContnet = wrapper.read(Type.COMPONENT); final JsonElement unsignedContent = wrapper.read(Type.OPTIONAL_COMPONENT); wrapper.write(Type.COMPONENT, unsignedContent != null ? unsignedContent : signedContnet); - // Can only be 1 (chat) or 2 (game info) as per 1.18.2->1.19.0 transformer + // Can only be 1 (chat) or 2 (game info) as per 1.18.2->1.19 transformer final int type = wrapper.read(Type.VAR_INT); wrapper.write(Type.BOOLEAN, type == 1); // Overlay }); @@ -109,33 +111,6 @@ public final class Protocol1_19_1To1_19 extends AbstractProtocol { - // Replace chat types - final CompoundTag tag = wrapper.get(Type.NBT, 0); - tag.put("minecraft:chat_type", CHAT_REGISTRY.clone()); - }); - } - }); - - registerServerbound(State.LOGIN, ServerboundLoginPackets.HELLO.getId(), ServerboundLoginPackets.HELLO.getId(), new PacketRemapper() { - @Override - public void registerMap() { - map(Type.STRING); // Name - map(Type.OPTIONAL_PROFILE_KEY); // Public profile key - read(Type.OPTIONAL_UUID); // Profile uuid - } - }); - registerServerbound(ServerboundPackets1_19_1.CHAT_MESSAGE, new PacketRemapper() { @Override public void registerMap() { @@ -148,7 +123,6 @@ public final class Protocol1_19_1To1_19 extends AbstractProtocol { + // Replace chat types - not worth the effort of handling them properly + final CompoundTag tag = wrapper.get(Type.NBT, 0); + tag.put("minecraft:chat_type", CHAT_REGISTRY.clone()); + }); + } + }); + + registerServerbound(State.LOGIN, ServerboundLoginPackets.HELLO.getId(), ServerboundLoginPackets.HELLO.getId(), new PacketRemapper() { + @Override + public void registerMap() { + map(Type.STRING); // Name + handler(wrapper -> { + // Profile keys are not compatible; replace it with an empty one + final ProfileKey profileKey = wrapper.read(Type.OPTIONAL_PROFILE_KEY); + wrapper.write(Type.OPTIONAL_PROFILE_KEY, null); + if (profileKey == null) { + // Modified client that doesn't include the profile key, or already done in 1.18->1.19 protocol; no need to map it + wrapper.user().put(new NonceStorage(null)); + } + }); + read(Type.OPTIONAL_UUID); // Profile uuid + } + }); + registerClientbound(State.LOGIN, ClientboundLoginPackets.HELLO.getId(), ClientboundLoginPackets.HELLO.getId(), new PacketRemapper() { + @Override + public void registerMap() { + map(Type.STRING); // Server id + handler(wrapper -> { + if (wrapper.user().has(NonceStorage.class)) { + return; + } + + final byte[] publicKey = wrapper.passthrough(Type.BYTE_ARRAY_PRIMITIVE); + final byte[] nonce = wrapper.passthrough(Type.BYTE_ARRAY_PRIMITIVE); + wrapper.user().put(new NonceStorage(CipherUtil.signNonce(publicKey, nonce))); + }); + } + }); + registerServerbound(State.LOGIN, ServerboundLoginPackets.ENCRYPTION_KEY.getId(), ServerboundLoginPackets.ENCRYPTION_KEY.getId(), new PacketRemapper() { + @Override + public void registerMap() { + map(Type.BYTE_ARRAY_PRIMITIVE); // Keys + handler(wrapper -> { + final NonceStorage nonceStorage = wrapper.user().remove(NonceStorage.class); + if (nonceStorage.nonce() == null) { + return; + } + + 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()); + } + }); + } + }); } } diff --git a/common/src/main/java/com/viaversion/viaversion/protocols/protocol1_19_1to1_19/storage/NonceStorage.java b/common/src/main/java/com/viaversion/viaversion/protocols/protocol1_19_1to1_19/storage/NonceStorage.java new file mode 100644 index 000000000..57845bb46 --- /dev/null +++ b/common/src/main/java/com/viaversion/viaversion/protocols/protocol1_19_1to1_19/storage/NonceStorage.java @@ -0,0 +1,34 @@ +/* + * This file is part of ViaVersion - https://github.com/ViaVersion/ViaVersion + * Copyright (C) 2016-2022 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.viaversion.protocols.protocol1_19_1to1_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/viaversion/protocols/protocol1_19to1_18_2/Protocol1_19To1_18_2.java b/common/src/main/java/com/viaversion/viaversion/protocols/protocol1_19to1_18_2/Protocol1_19To1_18_2.java index 9e58b2d24..01cef499c 100644 --- a/common/src/main/java/com/viaversion/viaversion/protocols/protocol1_19to1_18_2/Protocol1_19To1_18_2.java +++ b/common/src/main/java/com/viaversion/viaversion/protocols/protocol1_19to1_18_2/Protocol1_19To1_18_2.java @@ -51,28 +51,13 @@ import com.viaversion.viaversion.rewriter.CommandRewriter; import com.viaversion.viaversion.rewriter.SoundRewriter; import com.viaversion.viaversion.rewriter.StatisticsRewriter; import com.viaversion.viaversion.rewriter.TagRewriter; +import com.viaversion.viaversion.util.CipherUtil; -import javax.crypto.Cipher; -import java.security.KeyFactory; -import java.security.NoSuchAlgorithmException; -import java.security.PublicKey; -import java.security.spec.EncodedKeySpec; -import java.security.spec.X509EncodedKeySpec; import java.util.concurrent.ThreadLocalRandom; public final class Protocol1_19To1_18_2 extends AbstractProtocol { public static final MappingData MAPPINGS = new MappingDataBase("1.18", "1.19", true); - private static final KeyFactory RSA_FACTORY; - - static { - try { - RSA_FACTORY = KeyFactory.getInstance("RSA"); - } catch (final NoSuchAlgorithmException e) { - throw new RuntimeException(e); - } - } - private final EntityPackets entityRewriter = new EntityPackets(this); private final InventoryPackets itemRewriter = new InventoryPackets(this); @@ -253,13 +238,9 @@ public final class Protocol1_19To1_18_2 extends AbstractProtocol { - final byte[] pubKey = wrapper.passthrough(Type.BYTE_ARRAY_PRIMITIVE); + final byte[] publicKey = wrapper.passthrough(Type.BYTE_ARRAY_PRIMITIVE); final byte[] nonce = wrapper.passthrough(Type.BYTE_ARRAY_PRIMITIVE); - final EncodedKeySpec keySpec = new X509EncodedKeySpec(pubKey); - final PublicKey key = RSA_FACTORY.generatePublic(keySpec); - final Cipher cipher = Cipher.getInstance(key.getAlgorithm()); - cipher.init(Cipher.ENCRYPT_MODE, key); - wrapper.user().put(new NonceStorage(cipher.doFinal(nonce))); + wrapper.user().put(new NonceStorage(CipherUtil.signNonce(publicKey, nonce))); }); } }); diff --git a/common/src/main/java/com/viaversion/viaversion/util/CipherUtil.java b/common/src/main/java/com/viaversion/viaversion/util/CipherUtil.java new file mode 100644 index 000000000..93ba320c2 --- /dev/null +++ b/common/src/main/java/com/viaversion/viaversion/util/CipherUtil.java @@ -0,0 +1,46 @@ +/* + * This file is part of ViaVersion - https://github.com/ViaVersion/ViaVersion + * Copyright (C) 2016-2022 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.viaversion.util; + +import javax.crypto.Cipher; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; +import java.security.spec.EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; + +public final class CipherUtil { + + private static final KeyFactory RSA_FACTORY; + + static { + try { + RSA_FACTORY = KeyFactory.getInstance("RSA"); + } catch (final NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } + + public static byte[] signNonce(final byte[] publicKeyBytes, final byte[] nonceBytes) throws Exception { + final EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKeyBytes); + final PublicKey key = RSA_FACTORY.generatePublic(keySpec); + final Cipher cipher = Cipher.getInstance(key.getAlgorithm()); + cipher.init(Cipher.ENCRYPT_MODE, key); + return cipher.doFinal(nonceBytes); + } +}