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 {