From 1a3fba4250553702d9dcd05731d04347bfc24c9f Mon Sep 17 00:00:00 2001 From: "FivePB (Xer)" Date: Sat, 30 Jul 2022 23:30:03 +0000 Subject: [PATCH] Update to 1.19.1 (#772) * 1.19.1-rc1 * More signature changes * Further 1.19.1 changes I also started on the checkstyle update, see the developers notes for the rest I haven't gotten around to fixing yet. * Fix checkstyle * Checkstyle imports * Fix logic error * Changes 1.19.1-pre2 * 1.19-pre3 * Progress, some parts still WIP * Overlooked changes * Fix ServerData * Fix ServerLogin send check * Workaround the broken behavior of "No Chat Reports" Note that if we ever choose to enforce chat signatures, then the mod will just break again... not our fault if we do that, you get what you pay for. * more Co-authored-by: Shane Freeder Co-authored-by: Andrew Steinborn --- .../api/event/ResultedEvent.java | 1 - .../event/command/CommandExecuteEvent.java | 4 +- .../player/PlayerChannelRegisterEvent.java | 1 - .../api/network/ProtocolVersion.java | 3 +- .../api/permission/Tristate.java | 4 +- .../api/proxy/InboundConnection.java | 1 - .../api/proxy/crypto/IdentifiedKey.java | 42 ++++ .../api/proxy/crypto/KeySigned.java | 3 +- .../api/proxy/player/TabList.java | 6 +- .../api/proxy/player/TabListEntry.java | 1 + .../api/proxy/server/ServerPing.java | 1 - .../api/scheduler/Scheduler.java | 1 - .../com/velocitypowered/api/util/Favicon.java | 2 +- .../api/proxy/server/QueryResponseTest.java | 4 +- config/checkstyle/checkstyle.xml | 232 +++++++++++++----- gradle/checkstyle.gradle | 2 +- .../com/velocitypowered/proxy/Metrics.java | 2 - .../velocitypowered/proxy/VelocityServer.java | 3 - .../command/builtin/ShutdownCommand.java | 3 +- .../command/builtin/VelocityCommand.java | 3 - .../proxy/config/VelocityConfiguration.java | 11 +- .../connection/MinecraftSessionHandler.java | 5 + .../proxy/connection/VelocityConstants.java | 2 + .../backend/BackendPlaySessionHandler.java | 3 +- .../backend/LoginSessionHandler.java | 64 +++-- .../connection/client/AuthSessionHandler.java | 29 +++ .../client/ClientPlaySessionHandler.java | 69 +++++- .../connection/client/ConnectedPlayer.java | 2 +- .../client/InitialLoginSessionHandler.java | 35 ++- .../registry/DimensionRegistry.java | 2 - .../proxy/console/VelocityConsole.java | 1 - .../proxy/crypto/EncryptionUtils.java | 1 - .../proxy/crypto/IdentifiedKeyImpl.java | 73 +++++- .../proxy/crypto/SignaturePair.java | 49 ++++ .../proxy/crypto/SignedChatCommand.java | 17 +- .../proxy/crypto/SignedChatMessage.java | 22 +- .../proxy/plugin/PluginClassLoader.java | 1 - .../proxy/plugin/VelocityPluginManager.java | 2 +- .../proxy/protocol/ProtocolUtils.java | 8 +- .../proxy/protocol/StateRegistry.java | 67 +++-- .../proxy/protocol/netty/GS4QueryHandler.java | 1 - .../proxy/protocol/packet/PlayerListItem.java | 2 +- .../proxy/protocol/packet/ServerData.java | 14 +- .../proxy/protocol/packet/ServerLogin.java | 39 ++- .../packet/brigadier/ArgumentIdentifier.java | 1 - .../packet/brigadier/RegistryKeyArgument.java | 1 - .../RegistryKeyArgumentSerializer.java | 1 - .../protocol/packet/chat/ChatBuilder.java | 2 +- .../protocol/packet/chat/PlayerChat.java | 48 +++- .../packet/chat/PlayerChatCompletion.java | 70 ++++++ .../protocol/packet/chat/PlayerCommand.java | 67 ++++- .../protocol/packet/chat/SystemChat.java | 24 +- .../proxy/scheduler/VelocityScheduler.java | 1 - .../proxy/tablist/VelocityTabList.java | 14 +- .../proxy/util/InformationUtils.java | 1 - .../src/main/resources/default-velocity.toml | 2 +- .../proxy/event/RegistrationTest.java | 1 - .../proxy/protocol/PacketRegistryTest.java | 1 - .../scheduler/VelocitySchedulerTest.java | 2 - .../proxy/util/EncryptionUtilsTest.java | 1 - 60 files changed, 883 insertions(+), 192 deletions(-) create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/crypto/SignaturePair.java create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/PlayerChatCompletion.java diff --git a/api/src/main/java/com/velocitypowered/api/event/ResultedEvent.java b/api/src/main/java/com/velocitypowered/api/event/ResultedEvent.java index 05813dcaf..b22932a64 100644 --- a/api/src/main/java/com/velocitypowered/api/event/ResultedEvent.java +++ b/api/src/main/java/com/velocitypowered/api/event/ResultedEvent.java @@ -11,7 +11,6 @@ import com.google.common.base.Preconditions; import java.util.Optional; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; - import org.checkerframework.checker.nullness.qual.Nullable; /** diff --git a/api/src/main/java/com/velocitypowered/api/event/command/CommandExecuteEvent.java b/api/src/main/java/com/velocitypowered/api/event/command/CommandExecuteEvent.java index df807f5de..29afc41ca 100644 --- a/api/src/main/java/com/velocitypowered/api/event/command/CommandExecuteEvent.java +++ b/api/src/main/java/com/velocitypowered/api/event/command/CommandExecuteEvent.java @@ -74,8 +74,8 @@ public final class CommandExecuteEvent implements ResultedEvent { */ public static final class CommandResult implements ResultedEvent.Result { - private static final CommandResult ALLOWED = new CommandResult(true, false,null); - private static final CommandResult DENIED = new CommandResult(false, false,null); + private static final CommandResult ALLOWED = new CommandResult(true, false, null); + private static final CommandResult DENIED = new CommandResult(false, false, null); private static final CommandResult FORWARD_TO_SERVER = new CommandResult(false, true, null); private @Nullable String command; diff --git a/api/src/main/java/com/velocitypowered/api/event/player/PlayerChannelRegisterEvent.java b/api/src/main/java/com/velocitypowered/api/event/player/PlayerChannelRegisterEvent.java index 5929fa9c0..f6a14c4f2 100644 --- a/api/src/main/java/com/velocitypowered/api/event/player/PlayerChannelRegisterEvent.java +++ b/api/src/main/java/com/velocitypowered/api/event/player/PlayerChannelRegisterEvent.java @@ -10,7 +10,6 @@ package com.velocitypowered.api.event.player; import com.google.common.base.Preconditions; import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.messages.ChannelIdentifier; - import java.util.List; /** diff --git a/api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java b/api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java index d363f83e6..0d5d961f7 100644 --- a/api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java +++ b/api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java @@ -58,7 +58,8 @@ public enum ProtocolVersion { MINECRAFT_1_17_1(756, "1.17.1"), MINECRAFT_1_18(757, "1.18", "1.18.1"), MINECRAFT_1_18_2(758, "1.18.2"), - MINECRAFT_1_19(759, "1.19"); + MINECRAFT_1_19(759, "1.19"), + MINECRAFT_1_19_1(760, "1.19.1"); private static final int SNAPSHOT_BIT = 30; diff --git a/api/src/main/java/com/velocitypowered/api/permission/Tristate.java b/api/src/main/java/com/velocitypowered/api/permission/Tristate.java index 9ad1482a7..c414d8d73 100644 --- a/api/src/main/java/com/velocitypowered/api/permission/Tristate.java +++ b/api/src/main/java/com/velocitypowered/api/permission/Tristate.java @@ -43,7 +43,7 @@ public enum Tristate { * * @param val the boolean value * @return {@link #TRUE} or {@link #FALSE}, if the value is true or - * false, respectively. + * false, respectively. */ public static Tristate fromBoolean(boolean val) { return val ? TRUE : FALSE; @@ -57,7 +57,7 @@ public enum Tristate { * * @param val the boolean value * @return {@link #UNDEFINED}, {@link #TRUE} or {@link #FALSE}, if the value is null, - * true or false, respectively. + * true or false, respectively. */ public static Tristate fromNullableBoolean(@Nullable Boolean val) { if (val == null) { diff --git a/api/src/main/java/com/velocitypowered/api/proxy/InboundConnection.java b/api/src/main/java/com/velocitypowered/api/proxy/InboundConnection.java index b1c47a0f3..f15037241 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/InboundConnection.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/InboundConnection.java @@ -8,7 +8,6 @@ package com.velocitypowered.api.proxy; import com.velocitypowered.api.network.ProtocolVersion; - import java.net.InetSocketAddress; import java.util.Optional; diff --git a/api/src/main/java/com/velocitypowered/api/proxy/crypto/IdentifiedKey.java b/api/src/main/java/com/velocitypowered/api/proxy/crypto/IdentifiedKey.java index 537afc5e5..9050a61a7 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/crypto/IdentifiedKey.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/crypto/IdentifiedKey.java @@ -7,7 +7,12 @@ package com.velocitypowered.api.proxy.crypto; +import com.google.common.collect.ImmutableSet; +import com.velocitypowered.api.network.ProtocolVersion; import java.security.PublicKey; +import java.util.Set; +import java.util.UUID; +import org.checkerframework.checker.nullness.qual.Nullable; /** * Represents session-server cross-signed dated RSA public key. @@ -32,4 +37,41 @@ public interface IdentifiedKey extends KeySigned { */ boolean verifyDataSignature(byte[] signature, byte[]... toVerify); + /** + * Retrieves the signature holders UUID. + * Returns null before the {@link com.velocitypowered.api.event.connection.LoginEvent}. + * + * @return the holder UUID or null if not present + */ + @Nullable + UUID getSignatureHolder(); + + /** + * Retrieves the key revision. + * + * @return the key revision + */ + Revision getKeyRevision(); + + enum Revision { + GENERIC_V1(ImmutableSet.of(), ImmutableSet.of(ProtocolVersion.MINECRAFT_1_19)), + LINKED_V2(ImmutableSet.of(), ImmutableSet.of(ProtocolVersion.MINECRAFT_1_19_1)); + + final Set backwardsCompatibleTo; + final Set applicableTo; + + Revision(Set backwardsCompatibleTo, Set applicableTo) { + this.backwardsCompatibleTo = backwardsCompatibleTo; + this.applicableTo = applicableTo; + } + + public Set getBackwardsCompatibleTo() { + return backwardsCompatibleTo; + } + + public Set getApplicableTo() { + return applicableTo; + } + } + } diff --git a/api/src/main/java/com/velocitypowered/api/proxy/crypto/KeySigned.java b/api/src/main/java/com/velocitypowered/api/proxy/crypto/KeySigned.java index ce8ba57aa..4c6c5a30c 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/crypto/KeySigned.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/crypto/KeySigned.java @@ -8,10 +8,8 @@ package com.velocitypowered.api.proxy.crypto; import com.google.common.annotations.Beta; - import java.security.PublicKey; import java.time.Instant; - import org.checkerframework.checker.nullness.qual.Nullable; public interface KeySigned { @@ -56,6 +54,7 @@ public interface KeySigned { * signer public key. Note: This will **not** check for * expiry. You can check for expiry with {@link KeySigned#hasExpired()}. *

DOES NOT WORK YET FOR MESSAGES AND COMMANDS!

+ * Addendum: Does not work for 1.19.1 until the user has authenticated. * * @return validity of the signature */ diff --git a/api/src/main/java/com/velocitypowered/api/proxy/player/TabList.java b/api/src/main/java/com/velocitypowered/api/proxy/player/TabList.java index 7dfeb81f8..1f386b3e9 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/player/TabList.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/player/TabList.java @@ -49,7 +49,7 @@ public interface TabList { * * @param uuid of the entry * @return {@link Optional} containing the removed {@link TabListEntry} if present, otherwise - * {@link Optional#empty()} + * {@link Optional#empty()} */ Optional removeEntry(UUID uuid); @@ -71,12 +71,12 @@ public interface TabList { /** * Builds a tab list entry. * - * @deprecated Internal usage. Use {@link TabListEntry.Builder} instead. * @param profile profile * @param displayName display name * @param latency latency * @param gameMode game mode * @return entry + * @deprecated Internal usage. Use {@link TabListEntry.Builder} instead. */ @Deprecated TabListEntry buildEntry(GameProfile profile, @Nullable Component displayName, int latency, @@ -85,13 +85,13 @@ public interface TabList { /** * Builds a tab list entry. * - * @deprecated Internal usage. Use {@link TabListEntry.Builder} instead. * @param profile profile * @param displayName display name * @param latency latency * @param gameMode game mode * @param key the player key * @return entry + * @deprecated Internal usage. Use {@link TabListEntry.Builder} instead. */ @Deprecated TabListEntry buildEntry(GameProfile profile, @Nullable Component displayName, int latency, diff --git a/api/src/main/java/com/velocitypowered/api/proxy/player/TabListEntry.java b/api/src/main/java/com/velocitypowered/api/proxy/player/TabListEntry.java index 6d3534f5a..abf503d62 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/player/TabListEntry.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/player/TabListEntry.java @@ -160,6 +160,7 @@ public interface TabListEntry extends KeyIdentifiable { * Sets the {@link IdentifiedKey} of the {@link TabListEntry}. *

This is only intended and only works for players currently not connected to this proxy.

*

For any player currently connected to this proxy this will be filled automatically.

+ *

Will ignore mismatching key revisions data.

* * @param playerKey key to set * @return {@code this}, for chaining diff --git a/api/src/main/java/com/velocitypowered/api/proxy/server/ServerPing.java b/api/src/main/java/com/velocitypowered/api/proxy/server/ServerPing.java index 334ee224c..cf44632ad 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/server/ServerPing.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/server/ServerPing.java @@ -18,7 +18,6 @@ import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.UUID; - import org.checkerframework.checker.nullness.qual.Nullable; /** diff --git a/api/src/main/java/com/velocitypowered/api/scheduler/Scheduler.java b/api/src/main/java/com/velocitypowered/api/scheduler/Scheduler.java index ee3929bfd..6ef8525e3 100644 --- a/api/src/main/java/com/velocitypowered/api/scheduler/Scheduler.java +++ b/api/src/main/java/com/velocitypowered/api/scheduler/Scheduler.java @@ -11,7 +11,6 @@ import java.time.Duration; import java.util.Collection; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; - import org.checkerframework.common.value.qual.IntRange; import org.jetbrains.annotations.NotNull; diff --git a/api/src/main/java/com/velocitypowered/api/util/Favicon.java b/api/src/main/java/com/velocitypowered/api/util/Favicon.java index 62abc845a..569237a49 100644 --- a/api/src/main/java/com/velocitypowered/api/util/Favicon.java +++ b/api/src/main/java/com/velocitypowered/api/util/Favicon.java @@ -80,7 +80,7 @@ public final class Favicon { public static Favicon create(BufferedImage image) { Preconditions.checkNotNull(image, "image"); Preconditions.checkArgument(image.getWidth() == 64 && image.getHeight() == 64, - "Image is not 64x64 (found %sx%s)", image.getWidth(),image.getHeight()); + "Image is not 64x64 (found %sx%s)", image.getWidth(), image.getHeight()); ByteArrayOutputStream os = new ByteArrayOutputStream(); try { ImageIO.write(image, "PNG", os); diff --git a/api/src/test/java/com/velocitypowered/api/proxy/server/QueryResponseTest.java b/api/src/test/java/com/velocitypowered/api/proxy/server/QueryResponseTest.java index b7b912887..955c4fe1a 100644 --- a/api/src/test/java/com/velocitypowered/api/proxy/server/QueryResponseTest.java +++ b/api/src/test/java/com/velocitypowered/api/proxy/server/QueryResponseTest.java @@ -20,7 +20,7 @@ class QueryResponseTest { QueryResponse response = new QueryResponse("test", "test", "test", 1, 2, "test", 1234, ImmutableList.of("tuxed"), "0.0.1", ImmutableList.of(new PluginInformation("test", "1.0.0"), - new PluginInformation("test2", null))); + new PluginInformation("test2", null))); assertEquals(response, response.toBuilder().build()); } -} \ No newline at end of file +} diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml index eaf630f2a..6be8c6f0a 100644 --- a/config/checkstyle/checkstyle.xml +++ b/config/checkstyle/checkstyle.xml @@ -1,83 +1,137 @@ + "-//Checkstyle//DTD Checkstyle Configuration 1.3//EN" + "https://checkstyle.org/dtds/configuration_1_3.dtd"> - + + + + + + + + + + + + + - + + + + + + + - + value="Consider using special escape sequence instead of octal value or Unicode escaped value."/> + - - - - - + + + + value="LITERAL_TRY, LITERAL_FINALLY, LITERAL_IF, LITERAL_ELSE, LITERAL_SWITCH"/> + + + + + + - - + value="CLASS_DEF, METHOD_DEF, CTOR_DEF, LITERAL_FOR, LITERAL_WHILE, STATIC_INIT, + INSTANCE_INIT, ANNOTATION_DEF, ENUM_DEF, INTERFACE_DEF, RECORD_DEF, + COMPACT_CTOR_DEF"/> + + + + + + + + + + + + value="WhitespaceAround: ''{0}'' is not followed by whitespace. Empty blocks + may only be represented as '{}' when not part of a multi-block statement (4.1.3)"/> + value="WhitespaceAround: ''{0}'' is not preceded with whitespace."/> @@ -87,6 +141,10 @@ + @@ -100,13 +158,13 @@ - + - + @@ -119,67 +177,83 @@ + value="Package name ''{0}'' must match pattern ''{1}''."/> + + value="Type name ''{0}'' must match pattern ''{1}''."/> + value="Member name ''{0}'' must match pattern ''{1}''."/> + value="Parameter name ''{0}'' must match pattern ''{1}''."/> + value="Lambda parameter name ''{0}'' must match pattern ''{1}''."/> + value="Catch parameter name ''{0}'' must match pattern ''{1}''."/> - + value="Local variable name ''{0}'' must match pattern ''{1}''."/> + + + + + value="Class type name ''{0}'' must match pattern ''{1}''."/> + + + + + + + + + value="Method type name ''{0}'' must match pattern ''{1}''."/> + value="Interface type name ''{0}'' must match pattern ''{1}''."/> + value="GenericWhitespace ''{0}'' is followed by whitespace."/> + value="GenericWhitespace ''{0}'' is preceded with whitespace."/> + value="GenericWhitespace ''{0}'' should followed by whitespace."/> + value="GenericWhitespace ''{0}'' is not preceded with whitespace."/> - + @@ -188,31 +262,51 @@ + + + + + + - + value="COMMA, SEMI, POST_INC, POST_DEC, DOT, + LABELED_STAT, METHOD_REF"/> - + + + + value="BAND, BOR, BSR, BXOR, DIV, EQUAL, GE, GT, LAND, LE, LITERAL_INSTANCEOF, LOR, + LT, MINUS, MOD, NOT_EQUAL, PLUS, QUESTION, SL, SR, STAR, METHOD_REF, + TYPE_EXTENSION_AND "/> + value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF, CTOR_DEF, + RECORD_DEF, COMPACT_CTOR_DEF"/> @@ -220,37 +314,59 @@ + + value="^@return the *|^This method returns |^A [{]@code [a-zA-Z0-9]+[}]( is a )"/> + + value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF, CTOR_DEF, VARIABLE_DEF"/> - + - + + + + - - - + value="Method name ''{0}'' must match pattern ''{1}''."/> + - + + + + + + + + diff --git a/gradle/checkstyle.gradle b/gradle/checkstyle.gradle index 3a193db55..624ba3c50 100644 --- a/gradle/checkstyle.gradle +++ b/gradle/checkstyle.gradle @@ -1,5 +1,5 @@ checkstyle { - toolVersion '8.14' + toolVersion '10.3.1' configFile new File(project.rootDir, ['config', 'checkstyle', 'checkstyle.xml'].join(File.separator)) // The build should immediately fail if we have errors. diff --git a/proxy/src/main/java/com/velocitypowered/proxy/Metrics.java b/proxy/src/main/java/com/velocitypowered/proxy/Metrics.java index 660587cdd..97da171b4 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/Metrics.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/Metrics.java @@ -18,7 +18,6 @@ package com.velocitypowered.proxy; import com.velocitypowered.proxy.config.VelocityConfiguration; - import java.io.File; import java.io.IOException; import java.nio.file.Path; @@ -26,7 +25,6 @@ import java.util.HashMap; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; - import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.bstats.MetricsBase; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java index 2d36cf8ef..9f0fe9a9d 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java @@ -318,9 +318,6 @@ public class VelocityServer implements ProxyServer, ForwardingAudience { } commandManager.setAnnounceProxyCommands(configuration.isAnnounceProxyCommands()); - if (System.getProperty("auth.forceSecureProfiles") == null) { - System.setProperty("auth.forceSecureProfiles", String.valueOf(configuration.isForceKeyAuthentication())); - } } catch (Exception e) { logger.error("Unable to read/load/save your velocity.toml. The server will shut down.", e); LogManager.shutdown(); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/ShutdownCommand.java b/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/ShutdownCommand.java index 5d3bec66d..c2d80efd6 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/ShutdownCommand.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/ShutdownCommand.java @@ -24,12 +24,11 @@ import com.mojang.brigadier.builder.RequiredArgumentBuilder; import com.velocitypowered.api.command.BrigadierCommand; import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.proxy.VelocityServer; - import net.kyori.adventure.text.minimessage.MiniMessage; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; public final class ShutdownCommand { - private ShutdownCommand(){} + private ShutdownCommand() {} /** * Creates a Velocity Shutdown Command. diff --git a/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/VelocityCommand.java b/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/VelocityCommand.java index 37dca2e72..5b67e5da0 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/VelocityCommand.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/VelocityCommand.java @@ -19,7 +19,6 @@ package com.velocitypowered.proxy.command.builtin; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; - import com.google.common.collect.ImmutableSet; import com.google.common.util.concurrent.MoreExecutors; import com.google.gson.JsonArray; @@ -36,7 +35,6 @@ import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.util.ProxyVersion; import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.util.InformationUtils; - import java.net.ConnectException; import java.net.UnknownHostException; import java.nio.charset.StandardCharsets; @@ -47,7 +45,6 @@ import java.util.Locale; import java.util.Map; import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; - import net.kyori.adventure.identity.Identity; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.TextComponent; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java b/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java index 8f2130b73..b3f566cf9 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java @@ -28,7 +28,6 @@ import com.google.gson.annotations.Expose; import com.velocitypowered.api.proxy.config.ProxyConfig; import com.velocitypowered.api.util.Favicon; import com.velocitypowered.proxy.util.AddressUtil; - import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.io.File; import java.io.IOException; @@ -90,7 +89,7 @@ public class VelocityConfiguration implements ProxyConfig { boolean preventClientProxyConnections, boolean announceForge, PlayerInfoForwarding playerInfoForwardingMode, byte[] forwardingSecret, boolean onlineModeKickExistingPlayers, PingPassthroughMode pingPassthrough, - boolean enablePlayerAddressLogging, Servers servers,ForcedHosts forcedHosts, + boolean enablePlayerAddressLogging, Servers servers, ForcedHosts forcedHosts, Advanced advanced, Query query, Metrics metrics, boolean forceKeyAuthentication) { this.bind = bind; this.motd = motd; @@ -503,6 +502,14 @@ public class VelocityConfiguration implements ProxyConfig { } forwardingSecret = forwardingSecretString.getBytes(StandardCharsets.UTF_8); + if (configVersion == 1.0 || configVersion == 2.0) { + config.set("force-key-authentication", config.getOrElse("force-key-authentication", true)); + config.setComment("force-key-authentication", + "Should the proxy enforce the new public key security standard? By default, this is on."); + config.set("config-version", configVersion == 2.0 ? "2.5" : "1.5"); + mustResave = true; + } + // Handle any cases where the config needs to be saved again if (mustResave) { config.save(); 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 63d3c36a7..b3a15e056 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftSessionHandler.java @@ -48,6 +48,7 @@ import com.velocitypowered.proxy.protocol.packet.TabCompleteRequest; import com.velocitypowered.proxy.protocol.packet.TabCompleteResponse; import com.velocitypowered.proxy.protocol.packet.chat.LegacyChat; import com.velocitypowered.proxy.protocol.packet.chat.PlayerChat; +import com.velocitypowered.proxy.protocol.packet.chat.PlayerChatCompletion; import com.velocitypowered.proxy.protocol.packet.chat.PlayerChatPreview; import com.velocitypowered.proxy.protocol.packet.chat.PlayerCommand; import com.velocitypowered.proxy.protocol.packet.chat.ServerChatPreview; @@ -263,6 +264,10 @@ public interface MinecraftSessionHandler { return false; } + default boolean handle(PlayerChatCompletion packet) { + return false; + } + default boolean handle(ServerData serverData) { return false; } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/VelocityConstants.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/VelocityConstants.java index 039582cf1..76851b561 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/VelocityConstants.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/VelocityConstants.java @@ -26,6 +26,8 @@ public class VelocityConstants { public static final String VELOCITY_IP_FORWARDING_CHANNEL = "velocity:player_info"; public static final int MODERN_FORWARDING_DEFAULT = 1; public static final int MODERN_FORWARDING_WITH_KEY = 2; + public static final int MODERN_FORWARDING_WITH_KEY_V2 = 3; + public static final int MODERN_FORWARDING_MAX_VERSION = MODERN_FORWARDING_WITH_KEY_V2; public static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java index 7d9e9f1c9..d2d654777 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java @@ -55,7 +55,6 @@ import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import io.netty.handler.timeout.ReadTimeoutException; - import java.util.regex.Pattern; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -281,7 +280,7 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler { this.playerConnection.write( new ServerData(pingEvent.getPing().getDescriptionComponent(), pingEvent.getPing().getFavicon().orElse(null), - packet.isPreviewsChat()) + packet.isPreviewsChat(), packet.isSecureChatEnforced()) ), playerConnection.eventLoop()); return true; } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/LoginSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/LoginSessionHandler.java index a3f4056b9..310f6269c 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/LoginSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/LoginSessionHandler.java @@ -29,6 +29,7 @@ import com.velocitypowered.proxy.config.VelocityConfiguration; import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.connection.VelocityConstants; +import com.velocitypowered.proxy.connection.client.ConnectedPlayer; import com.velocitypowered.proxy.connection.util.ConnectionRequestResults; import com.velocitypowered.proxy.connection.util.ConnectionRequestResults.Impl; import com.velocitypowered.proxy.protocol.ProtocolUtils; @@ -81,17 +82,13 @@ public class LoginSessionHandler implements MinecraftSessionHandler { if (configuration.getPlayerInfoForwardingMode() == PlayerInfoForwarding.MODERN && packet.getChannel().equals(VelocityConstants.VELOCITY_IP_FORWARDING_CHANNEL)) { - int proposedForwardingVersion = VelocityConstants.MODERN_FORWARDING_DEFAULT; + int requestedForwardingVersion = VelocityConstants.MODERN_FORWARDING_DEFAULT; // Check version if (packet.content().readableBytes() == 1) { - int requested = packet.content().readByte(); - Preconditions.checkArgument(requested >= VelocityConstants.MODERN_FORWARDING_DEFAULT, - "Invalid modern forwarding version"); - proposedForwardingVersion = Math.min(requested, VelocityConstants.MODERN_FORWARDING_WITH_KEY); + requestedForwardingVersion = packet.content().readByte(); } ByteBuf forwardingData = createForwardingData(configuration.getForwardingSecret(), - serverConn.getPlayerRemoteAddressAsString(), serverConn.getPlayer().getGameProfile(), - serverConn.getPlayer().getIdentifiedKey(), proposedForwardingVersion); + serverConn.getPlayerRemoteAddressAsString(), serverConn.getPlayer(), requestedForwardingVersion); LoginPluginResponse response = new LoginPluginResponse(packet.getId(), true, forwardingData); mc.write(response); @@ -176,24 +173,61 @@ public class LoginSessionHandler implements MinecraftSessionHandler { } } + private static int findForwardingVersion(int requested, ConnectedPlayer player) { + // Ensure we are in range + requested = Math.min(requested, VelocityConstants.MODERN_FORWARDING_MAX_VERSION); + if (requested > VelocityConstants.MODERN_FORWARDING_DEFAULT) { + if (player.getIdentifiedKey() != null) { + // No enhanced switch on java 11 + switch (player.getIdentifiedKey().getKeyRevision()) { + case GENERIC_V1: + return VelocityConstants.MODERN_FORWARDING_WITH_KEY; + // Since V2 is not backwards compatible we have to throw the key if v2 and requested is v1 + case LINKED_V2: + return requested >= VelocityConstants.MODERN_FORWARDING_WITH_KEY_V2 + ? VelocityConstants.MODERN_FORWARDING_WITH_KEY_V2 : VelocityConstants.MODERN_FORWARDING_DEFAULT; + default: + return VelocityConstants.MODERN_FORWARDING_DEFAULT; + } + } else { + return VelocityConstants.MODERN_FORWARDING_DEFAULT; + } + } + return VelocityConstants.MODERN_FORWARDING_DEFAULT; + } + private static ByteBuf createForwardingData(byte[] hmacSecret, String address, - GameProfile profile, @Nullable IdentifiedKey playerKey, - int requestedVersion) { + ConnectedPlayer player, int requestedVersion) { ByteBuf forwarded = Unpooled.buffer(2048); try { - int actualVersion = requestedVersion >= VelocityConstants.MODERN_FORWARDING_WITH_KEY - ? (playerKey != null ? requestedVersion : VelocityConstants.MODERN_FORWARDING_DEFAULT) : requestedVersion; + int actualVersion = findForwardingVersion(requestedVersion, player); ProtocolUtils.writeVarInt(forwarded, actualVersion); ProtocolUtils.writeString(forwarded, address); - ProtocolUtils.writeUuid(forwarded, profile.getId()); - ProtocolUtils.writeString(forwarded, profile.getName()); - ProtocolUtils.writeProperties(forwarded, profile.getProperties()); + ProtocolUtils.writeUuid(forwarded, player.getGameProfile().getId()); + ProtocolUtils.writeString(forwarded, player.getGameProfile().getName()); + ProtocolUtils.writeProperties(forwarded, player.getGameProfile().getProperties()); // This serves as additional redundancy. The key normally is stored in the // login start to the server, but some setups require this. if (actualVersion >= VelocityConstants.MODERN_FORWARDING_WITH_KEY) { - ProtocolUtils.writePlayerKey(forwarded, playerKey); + IdentifiedKey key = player.getIdentifiedKey(); + assert key != null; + ProtocolUtils.writePlayerKey(forwarded, key); + + // Provide the signer UUID since the UUID may differ from the + // assigned UUID. Doing that breaks the signatures anyway but the server + // should be able to verify the key independently. + if (actualVersion >= VelocityConstants.MODERN_FORWARDING_WITH_KEY_V2) { + if (key.getSignatureHolder() != null) { + forwarded.writeBoolean(true); + ProtocolUtils.writeUuid(forwarded, key.getSignatureHolder()); + } else { + // Should only not be provided if the player was connected + // as offline-mode and the signer UUID was not backfilled + forwarded.writeBoolean(false); + } + } } SecretKey key = new SecretKeySpec(hmacSecret, "HmacSHA256"); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/AuthSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/AuthSessionHandler.java index e2c90a98a..322ef8106 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/AuthSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/AuthSessionHandler.java @@ -27,6 +27,7 @@ import com.velocitypowered.api.event.permission.PermissionsSetupEvent; import com.velocitypowered.api.event.player.GameProfileRequestEvent; import com.velocitypowered.api.event.player.PlayerChooseInitialServerEvent; import com.velocitypowered.api.permission.PermissionFunction; +import com.velocitypowered.api.proxy.crypto.IdentifiedKey; import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.util.GameProfile; import com.velocitypowered.api.util.UuidUtils; @@ -35,10 +36,12 @@ import com.velocitypowered.proxy.config.PlayerInfoForwarding; import com.velocitypowered.proxy.config.VelocityConfiguration; import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; +import com.velocitypowered.proxy.crypto.IdentifiedKeyImpl; import com.velocitypowered.proxy.protocol.StateRegistry; import com.velocitypowered.proxy.protocol.packet.ServerLoginSuccess; import com.velocitypowered.proxy.protocol.packet.SetCompression; import io.netty.buffer.ByteBuf; +import java.util.Objects; import java.util.Optional; import java.util.UUID; import java.util.concurrent.CompletableFuture; @@ -131,6 +134,32 @@ public class AuthSessionHandler implements MinecraftSessionHandler { if (configuration.getPlayerInfoForwardingMode() == PlayerInfoForwarding.NONE) { playerUniqueId = UuidUtils.generateOfflinePlayerUuid(player.getUsername()); } + + if (player.getIdentifiedKey() != null) { + IdentifiedKey playerKey = player.getIdentifiedKey(); + if (playerKey.getSignatureHolder() == null) { + if (playerKey instanceof IdentifiedKeyImpl) { + IdentifiedKeyImpl unlinkedKey = (IdentifiedKeyImpl) playerKey; + // Failsafe + if (!unlinkedKey.internalAddHolder(player.getUniqueId())) { + if (onlineMode) { + inbound.disconnect(Component.translatable("multiplayer.disconnect.invalid_public_key")); + return; + } else { + logger.warn("Key for player " + player.getUsername() + " could not be verified!"); + } + } + } else { + logger.warn("A custom key type has been set for player " + player.getUsername()); + } + } else { + if (!Objects.equals(playerKey.getSignatureHolder(), playerUniqueId)) { + logger.warn("UUID for Player " + player.getUsername() + " mismatches! " + + "Chat/Commands signatures will not work correctly for this player!"); + } + } + } + ServerLoginSuccess success = new ServerLoginSuccess(); success.setUsername(player.getUsername()); success.setProperties(player.getGameProfileProperties()); 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 79afe9b29..073a52eee 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 @@ -32,6 +32,7 @@ import com.velocitypowered.api.event.player.PlayerChatEvent; import com.velocitypowered.api.event.player.PlayerClientBrandEvent; import com.velocitypowered.api.event.player.TabCompleteEvent; import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.api.proxy.crypto.IdentifiedKey; import com.velocitypowered.api.proxy.messages.ChannelIdentifier; import com.velocitypowered.api.proxy.messages.LegacyChannelIdentifier; import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier; @@ -67,7 +68,6 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; import io.netty.util.ReferenceCountUtil; - import java.time.Instant; import java.util.ArrayList; import java.util.Collection; @@ -170,15 +170,39 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { if (chatResult.isAllowed()) { Optional eventMsg = pme.getResult().getMessage(); if (eventMsg.isPresent()) { - if (player.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0 - && player.getIdentifiedKey() != null) { - logger.warn("A plugin changed a signed chat message. The server may not accept it."); + String messageNew = eventMsg.get(); + if (player.getIdentifiedKey() != null) { + if (!messageNew.equals(signedMessage.getMessage())) { + if (player.getIdentifiedKey().getKeyRevision().compareTo(IdentifiedKey.Revision.LINKED_V2) >= 0) { + // Bad, very bad. + logger.fatal("A plugin tried to change a signed chat message. " + + "This is no longer possible in 1.19.1 and newer. " + + "Disconnecting player " + player.getUsername()); + player.disconnect(Component.text("A proxy plugin caused an illegal protocol state. " + + "Contact your network administrator.")); + } else { + logger.warn("A plugin changed a signed chat message. The server may not accept it."); + smc.write(ChatBuilder.builder(player.getProtocolVersion()) + .message(messageNew).toServer()); + } + } else { + smc.write(original); + } + } else { + smc.write(ChatBuilder.builder(player.getProtocolVersion()) + .message(messageNew).toServer()); } - smc.write(ChatBuilder.builder(player.getProtocolVersion()) - .message(event.getMessage()).toServer()); } else { smc.write(original); } + } else { + if (player.getIdentifiedKey().getKeyRevision().compareTo(IdentifiedKey.Revision.LINKED_V2) >= 0) { + logger.fatal("A plugin tried to cancel a signed chat message." + + " This is no longer possible in 1.19.1 and newer. " + + "Disconnecting player " + player.getUsername()); + player.disconnect(Component.text("A proxy plugin caused an illegal protocol state. " + + "Contact your network administrator.")); + } } }, smc.eventLoop()) .exceptionally((ex) -> { @@ -702,7 +726,16 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { private CompletableFuture processCommandExecuteResult(String originalCommand, CommandResult result, @Nullable SignedChatCommand signedCommand) { - if (result == CommandResult.denied()) { + IdentifiedKey playerKey = player.getIdentifiedKey(); + if (result == CommandResult.denied() && playerKey != null) { + if (signedCommand != null && playerKey.getKeyRevision() + .compareTo(IdentifiedKey.Revision.LINKED_V2) >= 0) { + logger.fatal("A plugin tried to deny a command with signable component(s). " + + "This is not supported. " + + "Disconnecting player " + player.getUsername()); + player.disconnect(Component.text("A proxy plugin caused an illegal protocol state. " + + "Contact your network administrator.")); + } return CompletableFuture.completedFuture(null); } @@ -724,6 +757,15 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { if (signedCommand != null && commandToRun.equals(signedCommand.getBaseCommand())) { write.message(signedCommand); } else { + if (signedCommand != null && playerKey != null && playerKey.getKeyRevision() + .compareTo(IdentifiedKey.Revision.LINKED_V2) >= 0) { + logger.fatal("A plugin tried to change a command with signed component(s). " + + "This is not supported. " + + "Disconnecting player " + player.getUsername()); + player.disconnect(Component.text("A proxy plugin caused an illegal protocol state. " + + "Contact your network administrator.")); + return CompletableFuture.completedFuture(null); + } write.message("/" + commandToRun); } return CompletableFuture.runAsync(() -> smc.write(write.toServer()), smc.eventLoop()); @@ -738,6 +780,15 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { if (signedCommand != null && commandToRun.equals(signedCommand.getBaseCommand())) { write.message(signedCommand); } else { + if (signedCommand != null && playerKey != null && playerKey.getKeyRevision() + .compareTo(IdentifiedKey.Revision.LINKED_V2) >= 0) { + logger.fatal("A plugin tried to change a command with signed component(s). " + + "This is not supported. " + + "Disconnecting player " + player.getUsername()); + player.disconnect(Component.text("A proxy plugin caused an illegal protocol state. " + + "Contact your network administrator.")); + return; + } write.message("/" + commandToRun); } smc.write(write.toServer()); @@ -746,6 +797,10 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { } } + private void handleCommandForward() { + + } + /** * Immediately send any queued messages to the server. */ diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java index 8c63673bd..0558bb466 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java @@ -1068,7 +1068,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player, } @Override - public IdentifiedKey getIdentifiedKey() { + public @Nullable IdentifiedKey getIdentifiedKey() { return playerKey; } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialLoginSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialLoginSessionHandler.java index 3a4cae894..40292c78f 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialLoginSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/InitialLoginSessionHandler.java @@ -25,14 +25,19 @@ import static com.velocitypowered.proxy.crypto.EncryptionUtils.generateServerId; import com.google.common.base.Preconditions; import com.google.common.primitives.Longs; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.gson.stream.JsonReader; import com.velocitypowered.api.event.connection.PreLoginEvent; import com.velocitypowered.api.event.connection.PreLoginEvent.PreLoginComponentResult; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.proxy.crypto.IdentifiedKey; import com.velocitypowered.api.util.GameProfile; +import com.velocitypowered.api.util.UuidUtils; import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; +import com.velocitypowered.proxy.crypto.IdentifiedKeyImpl; import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder; import com.velocitypowered.proxy.protocol.packet.EncryptionRequest; import com.velocitypowered.proxy.protocol.packet.EncryptionResponse; @@ -45,6 +50,7 @@ import java.security.KeyPair; import java.security.MessageDigest; import java.util.Arrays; import java.util.Optional; +import java.util.UUID; import java.util.concurrent.ExecutionException; import java.util.concurrent.ThreadLocalRandom; import net.kyori.adventure.text.Component; @@ -54,6 +60,7 @@ import org.apache.logging.log4j.Logger; import org.asynchttpclient.ListenableFuture; import org.asynchttpclient.Response; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; +import org.checkerframework.checker.nullness.qual.Nullable; public class InitialLoginSessionHandler implements MinecraftSessionHandler { @@ -75,7 +82,8 @@ public class InitialLoginSessionHandler implements MinecraftSessionHandler { this.server = Preconditions.checkNotNull(server, "server"); this.mcConnection = Preconditions.checkNotNull(mcConnection, "mcConnection"); this.inbound = Preconditions.checkNotNull(inbound, "inbound"); - this.forceKeyAuthentication = Boolean.getBoolean("auth.forceSecureProfiles"); + this.forceKeyAuthentication = System.getProperties().containsKey("auth.forceSecureProfiles") + ? Boolean.getBoolean("auth.forceSecureProfiles") : server.getConfiguration().isForceKeyAuthentication(); } @Override @@ -89,7 +97,16 @@ public class InitialLoginSessionHandler implements MinecraftSessionHandler { return true; } - if (!playerKey.isSignatureValid()) { + boolean isKeyValid; + if (playerKey.getKeyRevision() == IdentifiedKey.Revision.LINKED_V2 + && playerKey instanceof IdentifiedKeyImpl) { + IdentifiedKeyImpl keyImpl = (IdentifiedKeyImpl) playerKey; + isKeyValid = keyImpl.internalAddHolder(packet.getHolderUuid()); + } else { + isKeyValid = playerKey.isSignatureValid(); + } + + if (!isKeyValid) { inbound.disconnect(Component.translatable("multiplayer.disconnect.invalid_public_key")); return true; } @@ -133,7 +150,7 @@ public class InitialLoginSessionHandler implements MinecraftSessionHandler { this.currentState = LoginState.ENCRYPTION_REQUEST_SENT; } else { mcConnection.setSessionHandler(new AuthSessionHandler( - server, inbound, GameProfile.forOfflinePlayer(login.getUsername()), false + server, inbound, GameProfile.forOfflinePlayer(login.getUsername()), false )); } }); @@ -214,9 +231,19 @@ public class InitialLoginSessionHandler implements MinecraftSessionHandler { try { Response profileResponse = hasJoinedResponse.get(); if (profileResponse.getStatusCode() == 200) { + final GameProfile profile = GENERAL_GSON.fromJson(profileResponse.getResponseBody(), GameProfile.class); + // Not so fast, now we verify the public key for 1.19.1+ + if (inbound.getIdentifiedKey() != null + && inbound.getIdentifiedKey().getKeyRevision() == IdentifiedKey.Revision.LINKED_V2 + && inbound.getIdentifiedKey() instanceof IdentifiedKeyImpl) { + IdentifiedKeyImpl key = (IdentifiedKeyImpl) inbound.getIdentifiedKey(); + if (!key.internalAddHolder(profile.getId())) { + inbound.disconnect(Component.translatable("multiplayer.disconnect.invalid_public_key")); + } + } // All went well, initialize the session. mcConnection.setSessionHandler(new AuthSessionHandler( - server, inbound, GENERAL_GSON.fromJson(profileResponse.getResponseBody(), GameProfile.class), true + server, inbound, profile, true )); } else if (profileResponse.getStatusCode() == 204) { // Apparently an offline-mode user logged onto this online-mode proxy. diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/registry/DimensionRegistry.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/registry/DimensionRegistry.java index 4e7fb52a9..28c4c84c5 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/registry/DimensionRegistry.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/registry/DimensionRegistry.java @@ -20,11 +20,9 @@ package com.velocitypowered.proxy.connection.registry; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Maps; - import com.velocitypowered.api.network.ProtocolVersion; import java.util.Map; import java.util.Set; - import net.kyori.adventure.nbt.BinaryTag; import net.kyori.adventure.nbt.BinaryTagTypes; import net.kyori.adventure.nbt.CompoundBinaryTag; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/console/VelocityConsole.java b/proxy/src/main/java/com/velocitypowered/proxy/console/VelocityConsole.java index 06ba34aec..2a72c6848 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/console/VelocityConsole.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/console/VelocityConsole.java @@ -27,7 +27,6 @@ import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.util.ClosestLocaleMatcher; import java.util.List; import java.util.Locale; - import net.kyori.adventure.audience.MessageType; import net.kyori.adventure.identity.Identity; import net.kyori.adventure.permission.PermissionChecker; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/crypto/EncryptionUtils.java b/proxy/src/main/java/com/velocitypowered/proxy/crypto/EncryptionUtils.java index 6bd61604b..7c9636239 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/crypto/EncryptionUtils.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/crypto/EncryptionUtils.java @@ -21,7 +21,6 @@ import com.google.common.base.Preconditions; import com.google.common.io.ByteStreams; import com.velocitypowered.proxy.util.except.QuietDecoderException; import it.unimi.dsi.fastutil.Pair; - import java.io.IOException; import java.math.BigInteger; import java.nio.ByteBuffer; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/crypto/IdentifiedKeyImpl.java b/proxy/src/main/java/com/velocitypowered/proxy/crypto/IdentifiedKeyImpl.java index b22371024..e70434c3f 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/crypto/IdentifiedKeyImpl.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/crypto/IdentifiedKeyImpl.java @@ -18,29 +18,37 @@ package com.velocitypowered.proxy.crypto; import com.google.common.base.Objects; +import com.google.common.base.Preconditions; import com.velocitypowered.api.proxy.crypto.IdentifiedKey; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.nio.charset.StandardCharsets; import java.security.PublicKey; import java.time.Instant; import java.util.Arrays; +import java.util.UUID; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; +import org.checkerframework.checker.nullness.qual.Nullable; public class IdentifiedKeyImpl implements IdentifiedKey { + private final Revision revision; private final PublicKey publicKey; private final byte[] signature; private final Instant expiryTemporal; private @MonotonicNonNull Boolean isSignatureValid; + private @MonotonicNonNull UUID holder; - public IdentifiedKeyImpl(byte[] keyBits, long expiry, + public IdentifiedKeyImpl(Revision revision, byte[] keyBits, long expiry, byte[] signature) { - this(EncryptionUtils.parseRsaPublicKey(keyBits), Instant.ofEpochMilli(expiry), signature); + this(revision, EncryptionUtils.parseRsaPublicKey(keyBits), Instant.ofEpochMilli(expiry), signature); } /** * Creates an Identified key from data. */ - public IdentifiedKeyImpl(PublicKey publicKey, Instant expiryTemporal, byte[] signature) { + public IdentifiedKeyImpl(Revision revision, PublicKey publicKey, Instant expiryTemporal, byte[] signature) { + this.revision = revision; this.publicKey = publicKey; this.expiryTemporal = expiryTemporal; this.signature = signature; @@ -63,19 +71,68 @@ public class IdentifiedKeyImpl implements IdentifiedKey { @Override public byte[] getSignature() { - return signature; + return signature.clone(); + } + + @Override + public @Nullable UUID getSignatureHolder() { + return holder; + } + + @Override + public Revision getKeyRevision() { + return revision; + } + + /** + * Sets the uuid for this key. + * Returns false if incorrect. + */ + public boolean internalAddHolder(UUID holder) { + if (holder == null) { + return false; + } + if (this.holder == null) { + Boolean result = validateData(holder); + if (result == null || !result) { + return false; + } + isSignatureValid = true; + this.holder = holder; + return true; + } + return this.holder.equals(holder) && isSignatureValid(); } @Override public boolean isSignatureValid() { if (isSignatureValid == null) { + isSignatureValid = validateData(holder); + } + return isSignatureValid != null && isSignatureValid; + } + + private Boolean validateData(@Nullable UUID verify) { + if (revision == Revision.GENERIC_V1) { String pemKey = EncryptionUtils.pemEncodeRsaKey(publicKey); long expires = expiryTemporal.toEpochMilli(); byte[] toVerify = ("" + expires + pemKey).getBytes(StandardCharsets.US_ASCII); - isSignatureValid = EncryptionUtils.verifySignature( + return EncryptionUtils.verifySignature( EncryptionUtils.SHA1_WITH_RSA, EncryptionUtils.getYggdrasilSessionKey(), signature, toVerify); + } else { + if (verify == null) { + return null; + } + byte[] keyBytes = publicKey.getEncoded(); + byte[] toVerify = new byte[keyBytes.length + 24]; // length long * 3 + ByteBuffer fixedDataSet = ByteBuffer.wrap(toVerify).order(ByteOrder.BIG_ENDIAN); + fixedDataSet.putLong(verify.getMostSignificantBits()); + fixedDataSet.putLong(verify.getLeastSignificantBits()); + fixedDataSet.putLong(expiryTemporal.toEpochMilli()); + fixedDataSet.put(keyBytes); + return EncryptionUtils.verifySignature(EncryptionUtils.SHA1_WITH_RSA, + EncryptionUtils.getYggdrasilSessionKey(), signature, toVerify); } - return isSignatureValid; } @Override @@ -90,10 +147,12 @@ public class IdentifiedKeyImpl implements IdentifiedKey { @Override public String toString() { return "IdentifiedKeyImpl{" - + "publicKey=" + publicKey + + "revision=" + revision + + ", publicKey=" + publicKey + ", signature=" + Arrays.toString(signature) + ", expiryTemporal=" + expiryTemporal + ", isSignatureValid=" + isSignatureValid + + ", holder=" + holder + '}'; } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/crypto/SignaturePair.java b/proxy/src/main/java/com/velocitypowered/proxy/crypto/SignaturePair.java new file mode 100644 index 000000000..4f45eb895 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/crypto/SignaturePair.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2018 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.crypto; + +import java.util.Arrays; +import java.util.UUID; + +public class SignaturePair { + + private final UUID signer; + private final byte[] signature; + + public SignaturePair(UUID signer, byte[] signature) { + this.signer = signer; + this.signature = signature; + } + + public byte[] getSignature() { + return signature; + } + + public UUID getSigner() { + return signer; + } + + @Override + public String toString() { + return "SignaturePair{" + + "signer=" + signer + + ", signature=" + Arrays.toString(signature) + + '}'; + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/crypto/SignedChatCommand.java b/proxy/src/main/java/com/velocitypowered/proxy/crypto/SignedChatCommand.java index 9394a3ae4..8a0b291f2 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/crypto/SignedChatCommand.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/crypto/SignedChatCommand.java @@ -35,12 +35,17 @@ public class SignedChatCommand implements KeySigned { private final boolean isPreviewSigned; private final Map signatures; + private final SignaturePair[] previousSignatures; + private final @Nullable SignaturePair lastSignature; + /** * Create a signed command from data. */ public SignedChatCommand(String command, PublicKey signer, UUID sender, - Instant expiry, Map signature, byte[] salt, boolean isPreviewSigned) { + Instant expiry, Map signature, byte[] salt, + boolean isPreviewSigned, SignaturePair[] previousSignatures, + @Nullable SignaturePair lastSignature) { this.command = Preconditions.checkNotNull(command); this.signer = Preconditions.checkNotNull(signer); this.sender = Preconditions.checkNotNull(sender); @@ -48,6 +53,8 @@ public class SignedChatCommand implements KeySigned { this.expiry = Preconditions.checkNotNull(expiry); this.salt = Preconditions.checkNotNull(salt); this.isPreviewSigned = isPreviewSigned; + this.previousSignatures = previousSignatures; + this.lastSignature = lastSignature; } @@ -83,4 +90,12 @@ public class SignedChatCommand implements KeySigned { public boolean isPreviewSigned() { return isPreviewSigned; } + + public SignaturePair getLastSignature() { + return lastSignature; + } + + public SignaturePair[] getPreviousSignatures() { + return previousSignatures; + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/crypto/SignedChatMessage.java b/proxy/src/main/java/com/velocitypowered/proxy/crypto/SignedChatMessage.java index b108e5a74..fd0611945 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/crypto/SignedChatMessage.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/crypto/SignedChatMessage.java @@ -18,18 +18,13 @@ package com.velocitypowered.proxy.crypto; import com.google.common.base.Preconditions; -import com.google.common.primitives.Longs; import com.velocitypowered.api.proxy.crypto.SignedMessage; import com.velocitypowered.proxy.util.except.QuietDecoderException; -import java.nio.charset.StandardCharsets; -import java.security.KeyPair; import java.security.PublicKey; import java.time.Duration; import java.time.Instant; import java.time.temporal.TemporalAmount; import java.util.UUID; - -import net.kyori.adventure.text.Component; import org.checkerframework.checker.nullness.qual.Nullable; public class SignedChatMessage implements SignedMessage { @@ -46,13 +41,17 @@ public class SignedChatMessage implements SignedMessage { private final byte[] salt; private final UUID sender; //private final boolean isValid; + private final SignaturePair[] previousSignatures; + private final @Nullable SignaturePair previousSignature; private final boolean isPreviewSigned; /** * Create a signed message from data. */ public SignedChatMessage(String message, PublicKey signer, UUID sender, - Instant expiry, byte[] signature, byte[] salt, boolean isPreviewSigned) { + Instant expiry, byte[] signature, byte[] salt, + boolean isPreviewSigned, @Nullable SignaturePair[] previousSignatures, + @Nullable SignaturePair previousSignature) { this.message = Preconditions.checkNotNull(message); this.signer = Preconditions.checkNotNull(signer); this.sender = Preconditions.checkNotNull(sender); @@ -60,7 +59,8 @@ public class SignedChatMessage implements SignedMessage { this.expiry = Preconditions.checkNotNull(expiry); this.salt = Preconditions.checkNotNull(salt); this.isPreviewSigned = isPreviewSigned; - + this.previousSignatures = previousSignatures; + this.previousSignature = previousSignature; //this.isValid = EncryptionUtils.verifySignature(EncryptionUtils.SHA1_WITH_RSA, signer, // signature, salt, EncryptionUtils.longToBigEndianByteArray( @@ -83,6 +83,14 @@ public class SignedChatMessage implements SignedMessage { return signature; } + public SignaturePair[] getPreviousSignatures() { + return previousSignatures; + } + + public SignaturePair getPreviousSignature() { + return previousSignature; + } + //@Override //public boolean isSignatureValid() { // return isValid; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/plugin/PluginClassLoader.java b/proxy/src/main/java/com/velocitypowered/proxy/plugin/PluginClassLoader.java index 6ac166439..f8e40406d 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/plugin/PluginClassLoader.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/plugin/PluginClassLoader.java @@ -18,7 +18,6 @@ package com.velocitypowered.proxy.plugin; import com.velocitypowered.proxy.Velocity; - import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/plugin/VelocityPluginManager.java b/proxy/src/main/java/com/velocitypowered/proxy/plugin/VelocityPluginManager.java index 16886e2f3..57fadaec2 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/plugin/VelocityPluginManager.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/plugin/VelocityPluginManager.java @@ -138,7 +138,7 @@ public class VelocityPluginManager implements PluginManager { for (PluginContainer container : pluginContainers.keySet()) { bind(PluginContainer.class) .annotatedWith(Names.named(container.getDescription().getId())) - .toInstance(container); + .toInstance(container); } } }; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java index cb12737ef..59153c990 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java @@ -34,7 +34,6 @@ import io.netty.buffer.ByteBufUtil; import io.netty.handler.codec.CorruptedFrameException; import io.netty.handler.codec.DecoderException; import io.netty.handler.codec.EncoderException; - import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; @@ -42,7 +41,6 @@ import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.UUID; - import net.kyori.adventure.nbt.BinaryTagIO; import net.kyori.adventure.nbt.CompoundBinaryTag; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; @@ -557,11 +555,13 @@ public enum ProtocolUtils { * @param buf the buffer * @return the key */ - public static IdentifiedKey readPlayerKey(ByteBuf buf) { + public static IdentifiedKey readPlayerKey(ProtocolVersion version, ByteBuf buf) { long expiry = buf.readLong(); byte[] key = ProtocolUtils.readByteArray(buf); byte[] signature = ProtocolUtils.readByteArray(buf, 4096); - return new IdentifiedKeyImpl(key, expiry, signature); + IdentifiedKey.Revision revision = version.compareTo(ProtocolVersion.MINECRAFT_1_19) == 0 + ? IdentifiedKey.Revision.GENERIC_V1 : IdentifiedKey.Revision.LINKED_V2; + return new IdentifiedKeyImpl(revision, key, expiry, signature); } public enum Direction { 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 5e5f3d078..173fa7610 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java @@ -30,6 +30,7 @@ import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_17; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_18; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_18_2; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_19; +import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_19_1; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_7_2; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_8; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_9; @@ -67,6 +68,7 @@ import com.velocitypowered.proxy.protocol.packet.TabCompleteRequest; import com.velocitypowered.proxy.protocol.packet.TabCompleteResponse; import com.velocitypowered.proxy.protocol.packet.chat.LegacyChat; import com.velocitypowered.proxy.protocol.packet.chat.PlayerChat; +import com.velocitypowered.proxy.protocol.packet.chat.PlayerChatCompletion; import com.velocitypowered.proxy.protocol.packet.chat.PlayerCommand; import com.velocitypowered.proxy.protocol.packet.chat.SystemChat; import com.velocitypowered.proxy.protocol.packet.title.LegacyTitlePacket; @@ -121,7 +123,8 @@ public enum StateRegistry { map(0x01, MINECRAFT_1_12_1, false), map(0x05, MINECRAFT_1_13, false), map(0x06, MINECRAFT_1_14, false), - map(0x08, MINECRAFT_1_19, false)); + map(0x08, MINECRAFT_1_19, false), + map(0x09, MINECRAFT_1_19_1, false)); serverbound.register(LegacyChat.class, LegacyChat::new, map(0x01, MINECRAFT_1_7_2, false), map(0x02, MINECRAFT_1_9, false), @@ -129,16 +132,19 @@ public enum StateRegistry { map(0x02, MINECRAFT_1_12_1, false), map(0x03, MINECRAFT_1_14, MINECRAFT_1_18_2, false)); serverbound.register(PlayerCommand.class, PlayerCommand::new, - map(0x03, MINECRAFT_1_19, false)); + map(0x03, MINECRAFT_1_19, false), + map(0x04, MINECRAFT_1_19_1, false)); serverbound.register(PlayerChat.class, PlayerChat::new, - map(0x04, MINECRAFT_1_19, false)); + map(0x04, MINECRAFT_1_19, false), + map(0x05, MINECRAFT_1_19_1, false)); serverbound.register(ClientSettings.class, ClientSettings::new, map(0x15, MINECRAFT_1_7_2, false), map(0x04, MINECRAFT_1_9, false), map(0x05, MINECRAFT_1_12, false), map(0x04, MINECRAFT_1_12_1, false), map(0x05, MINECRAFT_1_14, false), - map(0x07, MINECRAFT_1_19, false)); + map(0x07, MINECRAFT_1_19, false), + map(0x08, MINECRAFT_1_19_1, false)); serverbound.register(PluginMessage.class, PluginMessage::new, map(0x17, MINECRAFT_1_7_2, false), map(0x09, MINECRAFT_1_9, false), @@ -147,7 +153,8 @@ public enum StateRegistry { map(0x0A, MINECRAFT_1_13, false), map(0x0B, MINECRAFT_1_14, false), map(0x0A, MINECRAFT_1_17, false), - map(0x0C, MINECRAFT_1_19, false)); + map(0x0C, MINECRAFT_1_19, false), + map(0x0D, MINECRAFT_1_19_1, false)); serverbound.register(KeepAlive.class, KeepAlive::new, map(0x00, MINECRAFT_1_7_2, false), map(0x0B, MINECRAFT_1_9, false), @@ -157,7 +164,8 @@ public enum StateRegistry { map(0x0F, MINECRAFT_1_14, false), map(0x10, MINECRAFT_1_16, false), map(0x0F, MINECRAFT_1_17, false), - map(0x11, MINECRAFT_1_19, false)); + map(0x11, MINECRAFT_1_19, false), + map(0x12, MINECRAFT_1_19_1, false)); serverbound.register(ResourcePackResponse.class, ResourcePackResponse::new, map(0x19, MINECRAFT_1_8, false), map(0x16, MINECRAFT_1_9, false), @@ -166,7 +174,8 @@ public enum StateRegistry { map(0x1F, MINECRAFT_1_14, false), map(0x20, MINECRAFT_1_16, false), map(0x21, MINECRAFT_1_16_2, false), - map(0x23, MINECRAFT_1_19, false)); + map(0x23, MINECRAFT_1_19, false), + map(0x24, MINECRAFT_1_19_1, false)); clientbound.register(BossBar.class, BossBar::new, map(0x0C, MINECRAFT_1_9, false), @@ -206,7 +215,8 @@ public enum StateRegistry { map(0x18, MINECRAFT_1_16, false), map(0x17, MINECRAFT_1_16_2, false), map(0x18, MINECRAFT_1_17, false), - map(0x15, MINECRAFT_1_19, false)); + map(0x15, MINECRAFT_1_19, false), + map(0x16, MINECRAFT_1_19_1, false)); clientbound.register(Disconnect.class, Disconnect::new, map(0x40, MINECRAFT_1_7_2, false), map(0x1A, MINECRAFT_1_9, false), @@ -216,7 +226,8 @@ public enum StateRegistry { map(0x1A, MINECRAFT_1_16, false), map(0x19, MINECRAFT_1_16_2, false), map(0x1A, MINECRAFT_1_17, false), - map(0x17, MINECRAFT_1_19, false)); + map(0x17, MINECRAFT_1_19, false), + map(0x19, MINECRAFT_1_19_1, false)); clientbound.register(KeepAlive.class, KeepAlive::new, map(0x00, MINECRAFT_1_7_2, false), map(0x1F, MINECRAFT_1_9, false), @@ -226,7 +237,8 @@ public enum StateRegistry { map(0x20, MINECRAFT_1_16, false), map(0x1F, MINECRAFT_1_16_2, false), map(0x21, MINECRAFT_1_17, false), - map(0x1E, MINECRAFT_1_19, false)); + map(0x1E, MINECRAFT_1_19, false), + map(0x20, MINECRAFT_1_19_1, false)); clientbound.register(JoinGame.class, JoinGame::new, map(0x01, MINECRAFT_1_7_2, false), map(0x23, MINECRAFT_1_9, false), @@ -236,7 +248,8 @@ public enum StateRegistry { map(0x25, MINECRAFT_1_16, false), map(0x24, MINECRAFT_1_16_2, false), map(0x26, MINECRAFT_1_17, false), - map(0x23, MINECRAFT_1_19, false)); + map(0x23, MINECRAFT_1_19, false), + map(0x25, MINECRAFT_1_19_1, false)); clientbound.register(Respawn.class, Respawn::new, map(0x07, MINECRAFT_1_7_2, true), map(0x33, MINECRAFT_1_9, true), @@ -248,7 +261,8 @@ public enum StateRegistry { map(0x3A, MINECRAFT_1_16, true), map(0x39, MINECRAFT_1_16_2, true), map(0x3D, MINECRAFT_1_17, true), - map(0x3B, MINECRAFT_1_19, true)); + map(0x3B, MINECRAFT_1_19, true), + map(0x3E, MINECRAFT_1_19_1, true)); clientbound.register(ResourcePackRequest.class, ResourcePackRequest::new, map(0x48, MINECRAFT_1_8, false), map(0x32, MINECRAFT_1_9, false), @@ -260,7 +274,8 @@ public enum StateRegistry { map(0x39, MINECRAFT_1_16, false), map(0x38, MINECRAFT_1_16_2, false), map(0x3C, MINECRAFT_1_17, false), - map(0x3A, MINECRAFT_1_19, false)); + map(0x3A, MINECRAFT_1_19, false), + map(0x3D, MINECRAFT_1_19_1, false)); clientbound.register(HeaderAndFooter.class, HeaderAndFooter::new, map(0x47, MINECRAFT_1_8, true), map(0x48, MINECRAFT_1_9, true), @@ -273,7 +288,8 @@ public enum StateRegistry { map(0x53, MINECRAFT_1_16, true), map(0x5E, MINECRAFT_1_17, true), map(0x5F, MINECRAFT_1_18, true), - map(0x60, MINECRAFT_1_19, true)); + map(0x60, MINECRAFT_1_19, true), + map(0x63, MINECRAFT_1_19_1, true)); clientbound.register(LegacyTitlePacket.class, LegacyTitlePacket::new, map(0x45, MINECRAFT_1_8, true), map(0x45, MINECRAFT_1_9, true), @@ -285,16 +301,20 @@ public enum StateRegistry { map(0x4F, MINECRAFT_1_16, MINECRAFT_1_16_4, true)); clientbound.register(TitleSubtitlePacket.class, TitleSubtitlePacket::new, map(0x57, MINECRAFT_1_17, true), - map(0x58, MINECRAFT_1_18, true)); + map(0x58, MINECRAFT_1_18, true), + map(0x5B, MINECRAFT_1_19_1, true)); clientbound.register(TitleTextPacket.class, TitleTextPacket::new, map(0x59, MINECRAFT_1_17, true), - map(0x5A, MINECRAFT_1_18, true)); + map(0x5A, MINECRAFT_1_18, true), + map(0x5D, MINECRAFT_1_19_1, true)); clientbound.register(TitleActionbarPacket.class, TitleActionbarPacket::new, map(0x41, MINECRAFT_1_17, true), - map(0x40, MINECRAFT_1_19, true)); + map(0x40, MINECRAFT_1_19, true), + map(0x43, MINECRAFT_1_19_1, true)); clientbound.register(TitleTimesPacket.class, TitleTimesPacket::new, map(0x5A, MINECRAFT_1_17, true), - map(0x5B, MINECRAFT_1_18, true)); + map(0x5B, MINECRAFT_1_18, true), + map(0x5E, MINECRAFT_1_19_1, true)); clientbound.register(TitleClearPacket.class, TitleClearPacket::new, map(0x10, MINECRAFT_1_17, true), map(0x0D, MINECRAFT_1_19, true)); @@ -308,11 +328,16 @@ public enum StateRegistry { map(0x33, MINECRAFT_1_16, false), map(0x32, MINECRAFT_1_16_2, false), map(0x36, MINECRAFT_1_17, false), - map(0x34, MINECRAFT_1_19, false)); + map(0x34, MINECRAFT_1_19, false), + map(0x37, MINECRAFT_1_19_1, false)); clientbound.register(SystemChat.class, SystemChat::new, - map(0x5F, MINECRAFT_1_19, true)); + map(0x5F, MINECRAFT_1_19, true), + map(0x62, MINECRAFT_1_19_1, true)); + clientbound.register(PlayerChatCompletion.class, PlayerChatCompletion::new, + StateRegistry.map(0x15, MINECRAFT_1_19_1, true)); clientbound.register(ServerData.class, ServerData::new, - map(0x3F, MINECRAFT_1_19, false)); + map(0x3F, MINECRAFT_1_19, false), + map(0x42, MINECRAFT_1_19_1, false)); } }, LOGIN { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/GS4QueryHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/GS4QueryHandler.java index 2d92af3f2..22789efe1 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/GS4QueryHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/netty/GS4QueryHandler.java @@ -46,7 +46,6 @@ import java.util.Optional; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; - import org.apache.logging.log4j.LogManager; public class GS4QueryHandler extends SimpleChannelInboundHandler { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/PlayerListItem.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/PlayerListItem.java index 81ae855cc..9f4908244 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/PlayerListItem.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/PlayerListItem.java @@ -78,7 +78,7 @@ public class PlayerListItem implements MinecraftPacket { if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0) { if (buf.readBoolean()) { - item.setPlayerKey(ProtocolUtils.readPlayerKey(buf)); + item.setPlayerKey(ProtocolUtils.readPlayerKey(version, buf)); } } break; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerData.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerData.java index 5f4857bc8..102fb7469 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerData.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerData.java @@ -31,15 +31,17 @@ public class ServerData implements MinecraftPacket { private @Nullable Component description; private @Nullable Favicon favicon; private boolean previewsChat; + private boolean secureChatEnforced; // Added in 1.19.1 public ServerData() { } public ServerData(@Nullable Component description, @Nullable Favicon favicon, - boolean previewsChat) { + boolean previewsChat, boolean secureChatEnforced) { this.description = description; this.favicon = favicon; this.previewsChat = previewsChat; + this.secureChatEnforced = secureChatEnforced; } @Override @@ -53,6 +55,9 @@ public class ServerData implements MinecraftPacket { this.favicon = new Favicon(ProtocolUtils.readString(buf)); } this.previewsChat = buf.readBoolean(); + if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_19_1) >= 0) { + this.secureChatEnforced = buf.readBoolean(); + } } @Override @@ -73,6 +78,9 @@ public class ServerData implements MinecraftPacket { } buf.writeBoolean(this.previewsChat); + if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_19_1) >= 0) { + buf.writeBoolean(this.secureChatEnforced); + } } @Override @@ -91,4 +99,8 @@ public class ServerData implements MinecraftPacket { public boolean isPreviewsChat() { return previewsChat; } + + public boolean isSecureChatEnforced() { + return secureChatEnforced; + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerLogin.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerLogin.java index a948b34c7..de57bf918 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerLogin.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerLogin.java @@ -28,12 +28,15 @@ import com.velocitypowered.proxy.util.except.QuietDecoderException; import io.netty.buffer.ByteBuf; import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.UUID; + public class ServerLogin implements MinecraftPacket { private static final QuietDecoderException EMPTY_USERNAME = new QuietDecoderException("Empty username!"); private @Nullable String username; private @Nullable IdentifiedKey playerKey; // Introduced in 1.19 + private @Nullable UUID holderUuid; // Used for key revision 2 public ServerLogin() { } @@ -58,6 +61,10 @@ public class ServerLogin implements MinecraftPacket { this.playerKey = playerKey; } + public UUID getHolderUuid() { + return holderUuid; + } + @Override public String toString() { return "ServerLogin{" @@ -75,7 +82,13 @@ public class ServerLogin implements MinecraftPacket { if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0) { if (buf.readBoolean()) { - playerKey = ProtocolUtils.readPlayerKey(buf); + playerKey = ProtocolUtils.readPlayerKey(version, buf); + } + + if (version.compareTo(ProtocolVersion.MINECRAFT_1_19_1) >= 0) { + if (buf.readBoolean()) { + holderUuid = ProtocolUtils.readUuid(buf); + } } } } @@ -94,6 +107,15 @@ public class ServerLogin implements MinecraftPacket { } else { buf.writeBoolean(false); } + + if (version.compareTo(ProtocolVersion.MINECRAFT_1_19_1) >= 0) { + if (playerKey != null && playerKey.getSignatureHolder() != null) { + buf.writeBoolean(true); + ProtocolUtils.writeUuid(buf, playerKey.getSignatureHolder()); + } else { + buf.writeBoolean(false); + } + } } } @@ -102,9 +124,20 @@ public class ServerLogin implements MinecraftPacket { // Accommodate the rare (but likely malicious) use of UTF-8 usernames, since it is technically // legal on the protocol level. int base = 1 + (16 * 4); + // Adjustments for Key-authentication if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0) { - return -1; - //TODO: ## 19 + // + 1 for the boolean present/ not present + // + 8 for the long expiry + // + 2 len for varint key size + // + 294 for the key + // + 2 len for varint signature size + // + 512 for signature + base += 1 + 8 + 2 + 294 + 2 + 512; + if (version.compareTo(ProtocolVersion.MINECRAFT_1_19_1) >= 0) { + // +1 boolean uuid optional + // + 2 * 8 for the long msb/lsb + base += 1 + 8 + 8; + } } return base; } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/ArgumentIdentifier.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/ArgumentIdentifier.java index 78277f87b..d3dbd5f6b 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/ArgumentIdentifier.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/ArgumentIdentifier.java @@ -20,7 +20,6 @@ package com.velocitypowered.proxy.protocol.packet.brigadier; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableMap; import com.velocitypowered.api.network.ProtocolVersion; - import java.util.HashMap; import java.util.Map; import org.checkerframework.checker.nullness.qual.Nullable; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/RegistryKeyArgument.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/RegistryKeyArgument.java index 6c6c9f209..dc08785eb 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/RegistryKeyArgument.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/RegistryKeyArgument.java @@ -23,7 +23,6 @@ import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.suggestion.Suggestions; import com.mojang.brigadier.suggestion.SuggestionsBuilder; - import java.util.Arrays; import java.util.Collection; import java.util.List; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/RegistryKeyArgumentSerializer.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/RegistryKeyArgumentSerializer.java index ff5decf4c..3ba179c46 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/RegistryKeyArgumentSerializer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/brigadier/RegistryKeyArgumentSerializer.java @@ -19,7 +19,6 @@ package com.velocitypowered.proxy.protocol.packet.brigadier; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.protocol.ProtocolUtils; - import io.netty.buffer.ByteBuf; public class RegistryKeyArgumentSerializer implements ArgumentPropertySerializer { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/ChatBuilder.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/ChatBuilder.java index 33dff036c..cd6d0ff55 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/ChatBuilder.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/ChatBuilder.java @@ -133,7 +133,7 @@ public class ChatBuilder { if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) >= 0) { // hard override chat > system for now - return new SystemChat(msg, type == ChatType.CHAT ? ChatType.SYSTEM.getId() : type.getId()); + return new SystemChat(msg, type == ChatType.CHAT ? ChatType.SYSTEM : type); } else { return new LegacyChat(ProtocolUtils.getJsonChatSerializer(version).serialize(msg), type.getId(), identity); } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/PlayerChat.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/PlayerChat.java index f0db0bbaf..212f9cf80 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/PlayerChat.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/PlayerChat.java @@ -22,9 +22,11 @@ import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.proxy.crypto.IdentifiedKey; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.crypto.EncryptionUtils; +import com.velocitypowered.proxy.crypto.SignaturePair; import com.velocitypowered.proxy.crypto.SignedChatMessage; import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.ProtocolUtils; +import com.velocitypowered.proxy.util.except.QuietDecoderException; import io.netty.buffer.ByteBuf; import java.time.Instant; import java.util.UUID; @@ -38,6 +40,13 @@ public class PlayerChat implements MinecraftPacket { private @Nullable Instant expiry; private @Nullable byte[] signature; private @Nullable byte[] salt; + private SignaturePair[] previousMessages = new SignaturePair[0]; + private @Nullable SignaturePair lastMessage; + + public static final int MAXIMUM_PREVIOUS_MESSAGE_COUNT = 5; + + public static final QuietDecoderException INVALID_PREVIOUS_MESSAGES = + new QuietDecoderException("Invalid previous messages"); public PlayerChat() { } @@ -58,6 +67,8 @@ public class PlayerChat implements MinecraftPacket { this.salt = message.getSalt(); this.signature = message.getSignature(); this.signedPreview = message.isPreviewSigned(); + this.lastMessage = message.getPreviousSignature(); + this.previousMessages = message.getPreviousSignatures(); } public Instant getExpiry() { @@ -98,6 +109,23 @@ public class PlayerChat implements MinecraftPacket { if (signedPreview && unsigned) { throw EncryptionUtils.PREVIEW_SIGNATURE_MISSING; } + + if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_19_1) >= 0) { + int size = ProtocolUtils.readVarInt(buf); + if (size < 0 || size > MAXIMUM_PREVIOUS_MESSAGE_COUNT) { + throw INVALID_PREVIOUS_MESSAGES; + } + + SignaturePair[] lastSignatures = new SignaturePair[size]; + for (int i = 0; i < size; i++) { + lastSignatures[i] = new SignaturePair(ProtocolUtils.readUuid(buf), ProtocolUtils.readByteArray(buf)); + } + previousMessages = lastSignatures; + + if (buf.readBoolean()) { + lastMessage = new SignaturePair(ProtocolUtils.readUuid(buf), ProtocolUtils.readByteArray(buf)); + } + } } @Override @@ -109,6 +137,23 @@ public class PlayerChat implements MinecraftPacket { ProtocolUtils.writeByteArray(buf, unsigned ? EncryptionUtils.EMPTY : signature); buf.writeBoolean(signedPreview); + + + if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_19_1) >= 0) { + ProtocolUtils.writeVarInt(buf, previousMessages.length); + for (SignaturePair previousMessage : previousMessages) { + ProtocolUtils.writeUuid(buf, previousMessage.getSigner()); + ProtocolUtils.writeByteArray(buf, previousMessage.getSignature()); + } + + if (lastMessage != null) { + buf.writeBoolean(true); + ProtocolUtils.writeUuid(buf, lastMessage.getSigner()); + ProtocolUtils.writeByteArray(buf, lastMessage.getSignature()); + } else { + buf.writeBoolean(false); + } + } } /** @@ -129,7 +174,8 @@ public class PlayerChat implements MinecraftPacket { return null; } - return new SignedChatMessage(message, signer.getSignedPublicKey(), sender, expiry, signature, salt, signedPreview); + return new SignedChatMessage(message, signer.getSignedPublicKey(), sender, expiry, signature, + salt, signedPreview, previousMessages, lastMessage); } @Override diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/PlayerChatCompletion.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/PlayerChatCompletion.java new file mode 100644 index 000000000..212c90476 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/PlayerChatCompletion.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2018 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; + +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; + +public class PlayerChatCompletion implements MinecraftPacket { + + private String[] completions; + private Action action; + + + public String[] getCompletions() { + return completions; + } + + public Action getAction() { + return action; + } + + public void setCompletions(String[] completions) { + this.completions = completions; + } + + public void setAction(Action action) { + this.action = action; + } + + @Override + public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + action = Action.values()[ProtocolUtils.readVarInt(buf)]; + completions = ProtocolUtils.readStringArray(buf); + } + + @Override + public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + ProtocolUtils.writeVarInt(buf, action.ordinal()); + ProtocolUtils.writeStringArray(buf, completions); + } + + @Override + public boolean handle(MinecraftSessionHandler handler) { + return handler.handle(this); + } + + enum Action { + ADD, + REMOVE, + ALTER + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/PlayerCommand.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/PlayerCommand.java index 580b7be3a..38fe91865 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/PlayerCommand.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/PlayerCommand.java @@ -17,12 +17,16 @@ package com.velocitypowered.proxy.protocol.packet.chat; +import static com.velocitypowered.proxy.protocol.packet.chat.PlayerChat.INVALID_PREVIOUS_MESSAGES; +import static com.velocitypowered.proxy.protocol.packet.chat.PlayerChat.MAXIMUM_PREVIOUS_MESSAGE_COUNT; + import com.google.common.collect.ImmutableMap; import com.google.common.primitives.Longs; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.proxy.crypto.IdentifiedKey; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.crypto.EncryptionUtils; +import com.velocitypowered.proxy.crypto.SignaturePair; import com.velocitypowered.proxy.crypto.SignedChatCommand; import com.velocitypowered.proxy.crypto.SignedChatMessage; import com.velocitypowered.proxy.protocol.MinecraftPacket; @@ -30,9 +34,11 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.util.except.QuietDecoderException; import io.netty.buffer.ByteBuf; import java.time.Instant; +import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.UUID; +import org.checkerframework.checker.nullness.qual.Nullable; public class PlayerCommand implements MinecraftPacket { @@ -46,6 +52,8 @@ public class PlayerCommand implements MinecraftPacket { private Instant timestamp; private long salt; private boolean signedPreview; // Good god. Please no. + private SignaturePair[] previousMessages = new SignaturePair[0]; + private @Nullable SignaturePair lastMessage; private Map arguments = ImmutableMap.of(); public boolean isSignedPreview() { @@ -96,6 +104,8 @@ public class PlayerCommand implements MinecraftPacket { this.timestamp = signedCommand.getExpiryTemporal(); this.salt = Longs.fromByteArray(signedCommand.getSalt()); this.signedPreview = signedCommand.isPreviewSigned(); + this.lastMessage = signedCommand.getLastSignature(); + this.previousMessages = signedCommand.getPreviousSignatures(); } @Override @@ -124,6 +134,24 @@ public class PlayerCommand implements MinecraftPacket { if (unsigned && signedPreview) { throw EncryptionUtils.PREVIEW_SIGNATURE_MISSING; } + + if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_19_1) >= 0) { + int size = ProtocolUtils.readVarInt(buf); + if (size < 0 || size > MAXIMUM_PREVIOUS_MESSAGE_COUNT) { + throw INVALID_PREVIOUS_MESSAGES; + } + + SignaturePair[] lastSignatures = new SignaturePair[size]; + for (int i = 0; i < size; i++) { + lastSignatures[i] = new SignaturePair(ProtocolUtils.readUuid(buf), ProtocolUtils.readByteArray(buf)); + } + previousMessages = lastSignatures; + + if (buf.readBoolean()) { + lastMessage = new SignaturePair(ProtocolUtils.readUuid(buf), ProtocolUtils.readByteArray(buf)); + } + } + } @Override @@ -146,6 +174,22 @@ public class PlayerCommand implements MinecraftPacket { buf.writeBoolean(signedPreview); + if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_19_1) >= 0) { + ProtocolUtils.writeVarInt(buf, previousMessages.length); + for (SignaturePair previousMessage : previousMessages) { + ProtocolUtils.writeUuid(buf, previousMessage.getSigner()); + ProtocolUtils.writeByteArray(buf, previousMessage.getSignature()); + } + + if (lastMessage != null) { + buf.writeBoolean(true); + ProtocolUtils.writeUuid(buf, lastMessage.getSigner()); + ProtocolUtils.writeByteArray(buf, lastMessage.getSignature()); + } else { + buf.writeBoolean(false); + } + } + } /** @@ -158,8 +202,12 @@ public class PlayerCommand implements MinecraftPacket { * @throws com.velocitypowered.proxy.util.except.QuietDecoderException when mustSign is {@code true} and the signature * is invalid. */ - public SignedChatCommand signedContainer(IdentifiedKey signer, UUID sender, boolean mustSign) { - if (unsigned) { + public SignedChatCommand signedContainer( + @Nullable IdentifiedKey signer, UUID sender, boolean mustSign) { + // There's a certain mod that is very broken that still signs messages but + // doesn't provide the player key. This is broken and wrong, but we need to + // work around that. + if (unsigned || signer == null) { if (mustSign) { throw EncryptionUtils.INVALID_SIGNATURE; } @@ -167,7 +215,20 @@ public class PlayerCommand implements MinecraftPacket { } return new SignedChatCommand(command, signer.getSignedPublicKey(), sender, timestamp, - arguments, Longs.toByteArray(salt), signedPreview); + arguments, Longs.toByteArray(salt), signedPreview, previousMessages, lastMessage); + } + + @Override + public String toString() { + return "PlayerCommand{" + + "unsigned=" + unsigned + + ", command='" + command + '\'' + + ", timestamp=" + timestamp + + ", salt=" + salt + + ", signedPreview=" + signedPreview + + ", previousMessages=" + Arrays.toString(previousMessages) + + ", arguments=" + arguments + + '}'; } @Override diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/SystemChat.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/SystemChat.java index fdf0c6859..4d877119a 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/SystemChat.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/SystemChat.java @@ -28,15 +28,15 @@ public class SystemChat implements MinecraftPacket { public SystemChat() {} - public SystemChat(Component component, int type) { + public SystemChat(Component component, ChatBuilder.ChatType type) { this.component = component; this.type = type; } private Component component; - private int type; + private ChatBuilder.ChatType type; - public int getType() { + public ChatBuilder.ChatType getType() { return type; } @@ -47,13 +47,27 @@ public class SystemChat implements MinecraftPacket { @Override public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { component = ProtocolUtils.getJsonChatSerializer(protocolVersion).deserialize(ProtocolUtils.readString(buf)); - type = ProtocolUtils.readVarInt(buf); + // System chat is never decoded so this doesn't matter for now + type = ChatBuilder.ChatType.values()[ProtocolUtils.readVarInt(buf)]; } @Override public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { ProtocolUtils.writeString(buf, ProtocolUtils.getJsonChatSerializer(protocolVersion).serialize(component)); - ProtocolUtils.writeVarInt(buf, type); + if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_19_1) >= 0) { + switch (type) { + case SYSTEM: + buf.writeBoolean(false); + break; + case GAME_INFO: + buf.writeBoolean(true); + break; + default: + throw new IllegalArgumentException("Invalid chat type"); + } + } else { + ProtocolUtils.writeVarInt(buf, type.getId()); + } } @Override diff --git a/proxy/src/main/java/com/velocitypowered/proxy/scheduler/VelocityScheduler.java b/proxy/src/main/java/com/velocitypowered/proxy/scheduler/VelocityScheduler.java index 97f592b3c..5e9b0013e 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/scheduler/VelocityScheduler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/scheduler/VelocityScheduler.java @@ -38,7 +38,6 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; - import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.checkerframework.checker.nullness.qual.NonNull; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabList.java b/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabList.java index 5dc9f3d57..eeb059b2d 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabList.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabList.java @@ -18,6 +18,7 @@ package com.velocitypowered.proxy.tablist; import com.google.common.base.Preconditions; +import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.ProxyServer; import com.velocitypowered.api.proxy.crypto.IdentifiedKey; @@ -226,8 +227,19 @@ public class VelocityTabList implements TabList { if (entries.containsKey(entry.getProfile().getId())) { PlayerListItem.Item packetItem = PlayerListItem.Item.from(entry); + IdentifiedKey selectedKey = packetItem.getPlayerKey(); Optional existing = proxyServer.getPlayer(entry.getProfile().getId()); - existing.ifPresent(value -> packetItem.setPlayerKey(value.getIdentifiedKey())); + if (existing.isPresent()) { + selectedKey = existing.get().getIdentifiedKey(); + } + + if (selectedKey != null + && selectedKey.getKeyRevision().getApplicableTo().contains(connection.getProtocolVersion()) + && Objects.equals(selectedKey.getSignatureHolder(), entry.getProfile().getId())) { + packetItem.setPlayerKey(selectedKey); + } else { + packetItem.setPlayerKey(null); + } connection.write(new PlayerListItem(action, Collections.singletonList(packetItem))); } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/util/InformationUtils.java b/proxy/src/main/java/com/velocitypowered/proxy/util/InformationUtils.java index f054e0cc2..2199dfa19 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/util/InformationUtils.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/util/InformationUtils.java @@ -32,7 +32,6 @@ import com.velocitypowered.api.proxy.ProxyServer; import com.velocitypowered.api.proxy.config.ProxyConfig; import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.util.ProxyVersion; - import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; diff --git a/proxy/src/main/resources/default-velocity.toml b/proxy/src/main/resources/default-velocity.toml index 9abc5e4db..b8ac82fa5 100644 --- a/proxy/src/main/resources/default-velocity.toml +++ b/proxy/src/main/resources/default-velocity.toml @@ -1,5 +1,5 @@ # Config version. Do not change this -config-version = "2.0" +config-version = "2.5" # What port should the proxy be bound to? By default, we'll bind to all addresses on port 25577. bind = "0.0.0.0:25577" diff --git a/proxy/src/test/java/com/velocitypowered/proxy/event/RegistrationTest.java b/proxy/src/test/java/com/velocitypowered/proxy/event/RegistrationTest.java index a70724b45..ad9e1de33 100644 --- a/proxy/src/test/java/com/velocitypowered/proxy/event/RegistrationTest.java +++ b/proxy/src/test/java/com/velocitypowered/proxy/event/RegistrationTest.java @@ -31,7 +31,6 @@ import com.velocitypowered.proxy.testutil.FakePluginManager; import java.util.HashSet; import java.util.Set; import java.util.stream.Stream; - import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DynamicNode; import org.junit.jupiter.api.DynamicTest; diff --git a/proxy/src/test/java/com/velocitypowered/proxy/protocol/PacketRegistryTest.java b/proxy/src/test/java/com/velocitypowered/proxy/protocol/PacketRegistryTest.java index b2f6e857a..88679261f 100644 --- a/proxy/src/test/java/com/velocitypowered/proxy/protocol/PacketRegistryTest.java +++ b/proxy/src/test/java/com/velocitypowered/proxy/protocol/PacketRegistryTest.java @@ -38,7 +38,6 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.protocol.packet.Handshake; import com.velocitypowered.proxy.protocol.packet.StatusPing; - import org.junit.jupiter.api.Test; class PacketRegistryTest { diff --git a/proxy/src/test/java/com/velocitypowered/proxy/scheduler/VelocitySchedulerTest.java b/proxy/src/test/java/com/velocitypowered/proxy/scheduler/VelocitySchedulerTest.java index d2895e55d..400b71cd6 100644 --- a/proxy/src/test/java/com/velocitypowered/proxy/scheduler/VelocitySchedulerTest.java +++ b/proxy/src/test/java/com/velocitypowered/proxy/scheduler/VelocitySchedulerTest.java @@ -22,13 +22,11 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import com.velocitypowered.api.scheduler.ScheduledTask; import com.velocitypowered.api.scheduler.TaskStatus; import com.velocitypowered.proxy.testutil.FakePluginManager; - import java.time.Duration; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; - import org.junit.jupiter.api.Test; class VelocitySchedulerTest { diff --git a/proxy/src/test/java/com/velocitypowered/proxy/util/EncryptionUtilsTest.java b/proxy/src/test/java/com/velocitypowered/proxy/util/EncryptionUtilsTest.java index ad0175fd6..fb2f1dde2 100644 --- a/proxy/src/test/java/com/velocitypowered/proxy/util/EncryptionUtilsTest.java +++ b/proxy/src/test/java/com/velocitypowered/proxy/util/EncryptionUtilsTest.java @@ -22,7 +22,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import com.velocitypowered.proxy.crypto.EncryptionUtils; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; - import org.junit.jupiter.api.Test; class EncryptionUtilsTest {