From 1f0a4a8228d9ba572de9673a67a89c35bdff6ed4 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Thu, 9 Aug 2018 05:24:47 -0400 Subject: [PATCH] Add support for HMACed player forwarding data. This provides a small degree of security but also makes Velocity "secure by default", especially on shared hosts. --- ...ingMode.java => PlayerInfoForwarding.java} | 2 +- .../proxy/config/VelocityConfiguration.java | 52 +++++++++----- .../backend/LoginSessionHandler.java | 68 +++++++++++++------ .../connection/backend/ServerConnection.java | 6 +- proxy/src/main/resources/velocity.toml | 5 +- 5 files changed, 92 insertions(+), 41 deletions(-) rename proxy/src/main/java/com/velocitypowered/proxy/config/{IPForwardingMode.java => PlayerInfoForwarding.java} (69%) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/config/IPForwardingMode.java b/proxy/src/main/java/com/velocitypowered/proxy/config/PlayerInfoForwarding.java similarity index 69% rename from proxy/src/main/java/com/velocitypowered/proxy/config/IPForwardingMode.java rename to proxy/src/main/java/com/velocitypowered/proxy/config/PlayerInfoForwarding.java index 87d449c2e..078c239c5 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/config/IPForwardingMode.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/config/PlayerInfoForwarding.java @@ -1,6 +1,6 @@ package com.velocitypowered.proxy.config; -public enum IPForwardingMode { +public enum PlayerInfoForwarding { NONE, LEGACY, MODERN 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 8f2a5aa6d..13c97eff5 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java @@ -5,6 +5,7 @@ import com.moandjiezana.toml.Toml; import com.velocitypowered.api.server.Favicon; import com.velocitypowered.proxy.util.AddressUtil; import com.velocitypowered.api.util.LegacyChatColorUtils; +import io.netty.buffer.ByteBufUtil; import net.kyori.text.Component; import net.kyori.text.serializer.ComponentSerializers; import org.apache.logging.log4j.LogManager; @@ -28,7 +29,7 @@ public class VelocityConfiguration { private final String motd; private final int showMaxPlayers; private final boolean onlineMode; - private final IPForwardingMode ipForwardingMode; + private final PlayerInfoForwarding playerInfoForwardingMode; private final Map servers; private final List attemptConnectionOrder; private final int compressionThreshold; @@ -40,21 +41,25 @@ public class VelocityConfiguration { private Component motdAsComponent; private Favicon favicon; + private final byte[] forwardingSecret; + private VelocityConfiguration(String bind, String motd, int showMaxPlayers, boolean onlineMode, - IPForwardingMode ipForwardingMode, Map servers, + PlayerInfoForwarding playerInfoForwardingMode, Map servers, List attemptConnectionOrder, int compressionThreshold, - int compressionLevel, boolean queryEnabled, int queryPort) { + int compressionLevel, boolean queryEnabled, int queryPort, + byte[] forwardingSecret) { this.bind = bind; this.motd = motd; this.showMaxPlayers = showMaxPlayers; this.onlineMode = onlineMode; - this.ipForwardingMode = ipForwardingMode; + this.playerInfoForwardingMode = playerInfoForwardingMode; this.servers = servers; this.attemptConnectionOrder = attemptConnectionOrder; this.compressionThreshold = compressionThreshold; this.compressionLevel = compressionLevel; this.queryEnabled = queryEnabled; this.queryPort = queryPort; + this.forwardingSecret = forwardingSecret; } public boolean validate() { @@ -76,9 +81,15 @@ public class VelocityConfiguration { logger.info("Proxy is running in offline mode!"); } - switch (ipForwardingMode) { + switch (playerInfoForwardingMode) { case NONE: - logger.info("IP forwarding is disabled! All players will appear to be connecting from the proxy and will have offline-mode UUIDs."); + logger.info("Player info forwarding is disabled! All players will appear to be connecting from the proxy and will have offline-mode UUIDs."); + break; + case MODERN: + if (forwardingSecret.length == 0) { + logger.error("You don't have a forwarding secret set."); + valid = false; + } break; } @@ -178,8 +189,8 @@ public class VelocityConfiguration { return onlineMode; } - public IPForwardingMode getIpForwardingMode() { - return ipForwardingMode; + public PlayerInfoForwarding getPlayerInfoForwardingMode() { + return playerInfoForwardingMode; } public Map getServers() { @@ -202,6 +213,10 @@ public class VelocityConfiguration { return favicon; } + public byte[] getForwardingSecret() { + return forwardingSecret; + } + @Override public String toString() { return "VelocityConfiguration{" + @@ -209,7 +224,7 @@ public class VelocityConfiguration { ", motd='" + motd + '\'' + ", showMaxPlayers=" + showMaxPlayers + ", onlineMode=" + onlineMode + - ", ipForwardingMode=" + ipForwardingMode + + ", playerInfoForwardingMode=" + playerInfoForwardingMode + ", servers=" + servers + ", attemptConnectionOrder=" + attemptConnectionOrder + ", compressionThreshold=" + compressionThreshold + @@ -218,6 +233,7 @@ public class VelocityConfiguration { ", queryPort=" + queryPort + ", motdAsComponent=" + motdAsComponent + ", favicon=" + favicon + + ", forwardingSecret=" + ByteBufUtil.hexDump(forwardingSecret) + '}'; } @@ -236,18 +252,22 @@ public class VelocityConfiguration { } } + byte[] forwardingSecret = toml.getString("player-info-forwarding-secret", "5up3r53cr3t") + .getBytes(StandardCharsets.UTF_8); + return new VelocityConfiguration( - toml.getString("bind"), - toml.getString("motd"), - toml.getLong("show-max-players").intValue(), - toml.getBoolean("online-mode"), - IPForwardingMode.valueOf(toml.getString("ip-forwarding").toUpperCase()), + toml.getString("bind", "0.0.0.0:25577"), + toml.getString("motd", "&3A Velocity Server"), + toml.getLong("show-max-players", 500L).intValue(), + toml.getBoolean("online-mode", true), + PlayerInfoForwarding.valueOf(toml.getString("player-info-forwarding", "MODERN").toUpperCase()), ImmutableMap.copyOf(servers), toml.getTable("servers").getList("try"), toml.getTable("advanced").getLong("compression-threshold", 1024L).intValue(), toml.getTable("advanced").getLong("compression-level", -1L).intValue(), - toml.getTable("query").getBoolean("enabled"), - toml.getTable("query").getLong("port", 25577L).intValue()); + toml.getTable("query").getBoolean("enabled", false), + toml.getTable("query").getLong("port", 25577L).intValue(), + forwardingSecret); } } } 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 e4219dd16..cf3e776b5 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 @@ -2,7 +2,8 @@ package com.velocitypowered.proxy.connection.backend; import com.velocitypowered.api.proxy.ConnectionRequestBuilder; import com.velocitypowered.proxy.VelocityServer; -import com.velocitypowered.proxy.config.IPForwardingMode; +import com.velocitypowered.proxy.config.PlayerInfoForwarding; +import com.velocitypowered.proxy.config.VelocityConfiguration; import com.velocitypowered.proxy.connection.VelocityConstants; import com.velocitypowered.proxy.connection.client.ClientPlaySessionHandler; import com.velocitypowered.proxy.connection.util.ConnectionRequestResults; @@ -17,6 +18,11 @@ import io.netty.buffer.Unpooled; import io.netty.channel.ChannelPipeline; import net.kyori.text.TextComponent; +import javax.crypto.Mac; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; @@ -30,7 +36,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler { @Override public void activated() { - if (VelocityServer.getServer().getConfiguration().getIpForwardingMode() == IPForwardingMode.MODERN) { + if (VelocityServer.getServer().getConfiguration().getPlayerInfoForwardingMode() == PlayerInfoForwarding.MODERN) { forwardingCheckTask = connection.getMinecraftConnection().getChannel().eventLoop().schedule(() -> { connection.getProxyPlayer().handleConnectionException(connection.getServerInfo(), TextComponent.of("Your server did not send the forwarding request in time. Is it set up correctly?")); @@ -44,12 +50,14 @@ public class LoginSessionHandler implements MinecraftSessionHandler { throw new IllegalStateException("Backend server is online-mode!"); } else if (packet instanceof LoginPluginMessage) { LoginPluginMessage message = (LoginPluginMessage) packet; - if (VelocityServer.getServer().getConfiguration().getIpForwardingMode() == IPForwardingMode.MODERN && + VelocityConfiguration configuration = VelocityServer.getServer().getConfiguration(); + if (configuration.getPlayerInfoForwardingMode() == PlayerInfoForwarding.MODERN && message.getChannel().equals(VelocityConstants.VELOCITY_IP_FORWARDING_CHANNEL)) { LoginPluginResponse response = new LoginPluginResponse(); response.setSuccess(true); response.setId(message.getId()); - response.setData(createForwardingData(connection.getProxyPlayer().getRemoteAddress().getHostString(), + response.setData(createForwardingData(configuration.getForwardingSecret(), + connection.getProxyPlayer().getRemoteAddress().getHostString(), connection.getProxyPlayer().getProfile())); connection.getMinecraftConnection().write(response); cancelForwardingCheck(); @@ -122,23 +130,43 @@ public class LoginSessionHandler implements MinecraftSessionHandler { } } - private static ByteBuf createForwardingData(String address, GameProfile profile) { - ByteBuf buf = Unpooled.buffer(); - ProtocolUtils.writeString(buf, address); - ProtocolUtils.writeUuid(buf, profile.idAsUuid()); - ProtocolUtils.writeString(buf, profile.getName()); - ProtocolUtils.writeVarInt(buf, profile.getProperties().size()); - for (GameProfile.Property property : profile.getProperties()) { - ProtocolUtils.writeString(buf, property.getName()); - ProtocolUtils.writeString(buf, property.getValue()); - String signature = property.getSignature(); - if (signature != null) { - buf.writeBoolean(true); - ProtocolUtils.writeString(buf, signature); - } else { - buf.writeBoolean(false); + static ByteBuf createForwardingData(byte[] hmacSecret, String address, GameProfile profile) { + ByteBuf dataToForward = Unpooled.buffer(); + ByteBuf finalData = Unpooled.buffer(); + try { + ProtocolUtils.writeString(dataToForward, address); + ProtocolUtils.writeUuid(dataToForward, profile.idAsUuid()); + ProtocolUtils.writeString(dataToForward, profile.getName()); + ProtocolUtils.writeVarInt(dataToForward, profile.getProperties().size()); + for (GameProfile.Property property : profile.getProperties()) { + ProtocolUtils.writeString(dataToForward, property.getName()); + ProtocolUtils.writeString(dataToForward, property.getValue()); + String signature = property.getSignature(); + if (signature != null) { + dataToForward.writeBoolean(true); + ProtocolUtils.writeString(dataToForward, signature); + } else { + dataToForward.writeBoolean(false); + } } + + SecretKey key = new SecretKeySpec(hmacSecret, "HmacSHA256"); + Mac mac = Mac.getInstance("HmacSHA256"); + mac.init(key); + mac.update(dataToForward.array(), dataToForward.arrayOffset(), dataToForward.readableBytes()); + byte[] sig = mac.doFinal(); + finalData.writeBytes(sig); + finalData.writeBytes(dataToForward); + return finalData; + } catch (InvalidKeyException e) { + finalData.release(); + throw new RuntimeException("Unable to authenticate data", e); + } catch (NoSuchAlgorithmException e) { + // Should never happen + finalData.release(); + throw new AssertionError(e); + } finally { + dataToForward.release(); } - return buf; } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/ServerConnection.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/ServerConnection.java index 08f58325f..1ee76af1a 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/ServerConnection.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/ServerConnection.java @@ -1,7 +1,7 @@ package com.velocitypowered.proxy.connection.backend; import com.velocitypowered.api.proxy.ConnectionRequestBuilder; -import com.velocitypowered.proxy.config.IPForwardingMode; +import com.velocitypowered.proxy.config.PlayerInfoForwarding; import com.velocitypowered.proxy.connection.MinecraftConnectionAssociation; import com.velocitypowered.proxy.connection.util.ConnectionRequestResults; import com.velocitypowered.proxy.protocol.ProtocolConstants; @@ -97,7 +97,7 @@ public class ServerConnection implements MinecraftConnectionAssociation { Handshake handshake = new Handshake(); handshake.setNextStatus(StateRegistry.LOGIN_ID); handshake.setProtocolVersion(proxyPlayer.getConnection().getProtocolVersion()); - if (VelocityServer.getServer().getConfiguration().getIpForwardingMode() == IPForwardingMode.LEGACY) { + if (VelocityServer.getServer().getConfiguration().getPlayerInfoForwardingMode() == PlayerInfoForwarding.LEGACY) { handshake.setServerAddress(createBungeeForwardingAddress()); } else { handshake.setServerAddress(serverInfo.getAddress().getHostString()); @@ -111,7 +111,7 @@ public class ServerConnection implements MinecraftConnectionAssociation { // Send the server login packet for <=1.12.2 and for 1.13+ servers not using "modern" forwarding. if (protocolVersion <= ProtocolConstants.MINECRAFT_1_12_2 || - VelocityServer.getServer().getConfiguration().getIpForwardingMode() != IPForwardingMode.MODERN) { + VelocityServer.getServer().getConfiguration().getPlayerInfoForwardingMode() != PlayerInfoForwarding.MODERN) { ServerLogin login = new ServerLogin(); login.setUsername(proxyPlayer.getUsername()); minecraftConnection.write(login); diff --git a/proxy/src/main/resources/velocity.toml b/proxy/src/main/resources/velocity.toml index 18aac1e6d..3bdd006e1 100644 --- a/proxy/src/main/resources/velocity.toml +++ b/proxy/src/main/resources/velocity.toml @@ -19,7 +19,10 @@ online-mode = true # servers using Minecraft 1.12 or lower. # - "modern": Forward player IPs and UUIDs as part of the login process using Velocity's native # forwarding. Only applicable for Minecraft 1.13 or higher. -ip-forwarding = "modern" +player-info-forwarding = "modern" + +# If you are using modern IP forwarding, configure an unique secret here. +player-info-forwarding-secret = "5up3r53cr3t" [servers] # Configure your servers here.