diff --git a/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/Protocol.java b/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/Protocol.java index fd59974..d5c6dd3 100644 --- a/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/Protocol.java +++ b/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/Protocol.java @@ -8,6 +8,7 @@ import net.azisaba.azipluginmessaging.api.protocol.handler.ProxyboundCheckRankExpirationPacket; import net.azisaba.azipluginmessaging.api.protocol.handler.ProxyboundClearPrefixPacket; import net.azisaba.azipluginmessaging.api.protocol.handler.ProxyboundEncryptionPacket; +import net.azisaba.azipluginmessaging.api.protocol.handler.ProxyboundEncryptionResponsePacket; import net.azisaba.azipluginmessaging.api.protocol.handler.ProxyboundGiveGamingSaraPacket; import net.azisaba.azipluginmessaging.api.protocol.handler.ProxyboundGiveNitroSaraPacket; import net.azisaba.azipluginmessaging.api.protocol.handler.ProxyboundGiveSaraPacket; @@ -21,6 +22,8 @@ import net.azisaba.azipluginmessaging.api.protocol.handler.ServerboundActionResponsePacket; import net.azisaba.azipluginmessaging.api.protocol.handler.ServerboundCheckRankExpirationPacket; import net.azisaba.azipluginmessaging.api.protocol.handler.ServerboundEncryptionPacket; +import net.azisaba.azipluginmessaging.api.protocol.handler.ServerboundEncryptionResponsePacket; +import net.azisaba.azipluginmessaging.api.protocol.message.EmptyMessage; import net.azisaba.azipluginmessaging.api.protocol.message.EncryptionMessage; import net.azisaba.azipluginmessaging.api.protocol.message.Message; import net.azisaba.azipluginmessaging.api.protocol.message.PlayerMessage; @@ -47,6 +50,7 @@ import java.io.IOException; import java.util.Collection; import java.util.Map; +import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; public final class Protocol, M extends Message> { @@ -64,21 +68,23 @@ public final class Protocol, M extends Message> { public static final String CHANNEL_ID = "azipm:main"; public static final Protocol P_ENCRYPTION = new Protocol<>(PacketFlow.TO_PROXY, 0x00, new ProxyboundEncryptionPacket()); - public static final Protocol P_SET_RANK = new Protocol<>(PacketFlow.TO_PROXY, 0x01, new ProxyboundSetRankPacket()); - public static final Protocol P_GIVE_GAMING_SARA = new Protocol<>(PacketFlow.TO_PROXY, 0x02, new ProxyboundGiveGamingSaraPacket()); - public static final Protocol P_GIVE_SARA = new Protocol<>(PacketFlow.TO_PROXY, 0x03, new ProxyboundGiveSaraPacket()); - public static final Protocol P_TOGGLE_GAMING_SARA = new Protocol<>(PacketFlow.TO_PROXY, 0x04, new ProxyboundToggleGamingSaraPacket()); - public static final Protocol P_TOGGLE_SARA_HIDE = new Protocol<>(PacketFlow.TO_PROXY, 0x05, new ProxyboundToggleSaraHidePacket()); // non-contextual - public static final Protocol P_TOGGLE_SARA_SHOW = new Protocol<>(PacketFlow.TO_PROXY, 0x06, new ProxyboundToggleSaraShowPacket()); // contextual - public static final Protocol P_SET_PREFIX = new Protocol<>(PacketFlow.TO_PROXY, 0x07, new ProxyboundSetPrefixPacket()); // may be contextual - public static final Protocol P_CLEAR_PREFIX = new Protocol<>(PacketFlow.TO_PROXY, 0x08, new ProxyboundClearPrefixPacket()); // may be contextual - public static final Protocol P_GIVE_NITRO_SARA = new Protocol<>(PacketFlow.TO_PROXY, 0x09, new ProxyboundGiveNitroSaraPacket()); - public static final Protocol P_TOGGLE_NITRO_SARA = new Protocol<>(PacketFlow.TO_PROXY, 0x0A, new ProxyboundToggleNitroSaraPacket()); - public static final Protocol P_CHECK_RANK_EXPIRATION = new Protocol<>(PacketFlow.TO_PROXY, 0x0B, new ProxyboundCheckRankExpirationPacket()); + public static final Protocol P_ENCRYPTION_RESPONSE = new Protocol<>(PacketFlow.TO_PROXY, 0x01, new ProxyboundEncryptionResponsePacket()); + public static final Protocol P_SET_RANK = new Protocol<>(PacketFlow.TO_PROXY, 0x02, new ProxyboundSetRankPacket()); + public static final Protocol P_GIVE_GAMING_SARA = new Protocol<>(PacketFlow.TO_PROXY, 0x03, new ProxyboundGiveGamingSaraPacket()); + public static final Protocol P_GIVE_SARA = new Protocol<>(PacketFlow.TO_PROXY, 0x04, new ProxyboundGiveSaraPacket()); + public static final Protocol P_TOGGLE_GAMING_SARA = new Protocol<>(PacketFlow.TO_PROXY, 0x05, new ProxyboundToggleGamingSaraPacket()); + public static final Protocol P_TOGGLE_SARA_HIDE = new Protocol<>(PacketFlow.TO_PROXY, 0x06, new ProxyboundToggleSaraHidePacket()); // non-contextual + public static final Protocol P_TOGGLE_SARA_SHOW = new Protocol<>(PacketFlow.TO_PROXY, 0x07, new ProxyboundToggleSaraShowPacket()); // contextual + public static final Protocol P_SET_PREFIX = new Protocol<>(PacketFlow.TO_PROXY, 0x08, new ProxyboundSetPrefixPacket()); // may be contextual + public static final Protocol P_CLEAR_PREFIX = new Protocol<>(PacketFlow.TO_PROXY, 0x09, new ProxyboundClearPrefixPacket()); // may be contextual + public static final Protocol P_GIVE_NITRO_SARA = new Protocol<>(PacketFlow.TO_PROXY, 0x0A, new ProxyboundGiveNitroSaraPacket()); + public static final Protocol P_TOGGLE_NITRO_SARA = new Protocol<>(PacketFlow.TO_PROXY, 0x0B, new ProxyboundToggleNitroSaraPacket()); + public static final Protocol P_CHECK_RANK_EXPIRATION = new Protocol<>(PacketFlow.TO_PROXY, 0x0C, new ProxyboundCheckRankExpirationPacket()); public static final Protocol S_ENCRYPTION = new Protocol<>(PacketFlow.TO_SERVER, 0x00, new ServerboundEncryptionPacket()); - public static final Protocol S_ACTION_RESPONSE = new Protocol<>(PacketFlow.TO_SERVER, 0x01, new ServerboundActionResponsePacket()); - public static final Protocol S_CHECK_RANK_EXPIRATION = new Protocol<>(PacketFlow.TO_SERVER, 0x02, new ServerboundCheckRankExpirationPacket()); + public static final Protocol S_ENCRYPTION_RESPONSE = new Protocol<>(PacketFlow.TO_SERVER, 0x01, new ServerboundEncryptionResponsePacket()); + public static final Protocol S_ACTION_RESPONSE = new Protocol<>(PacketFlow.TO_SERVER, 0x02, new ServerboundActionResponsePacket()); + public static final Protocol S_CHECK_RANK_EXPIRATION = new Protocol<>(PacketFlow.TO_SERVER, 0x03, new ServerboundCheckRankExpirationPacket()); private final PacketFlow packetFlow; private final byte id; @@ -186,7 +192,8 @@ public boolean sendPacket(@Nullable PacketSender sender, @NotNull M msg) { */ public static void handleProxySide(ServerConnection server, byte[] rawData) { byte[] data; - if (server.isEncrypted()) { + boolean encrypted = server.isEncrypted() || server.consumeEncryptedOnce(); + if (encrypted) { try { data = EncryptionUtil.decrypt(rawData, server.getKeyPair().getPrivate()); } catch (Exception e) { @@ -198,7 +205,7 @@ public static void handleProxySide(ServerConnection server, byte[] rawData) { try (ByteArrayInputStream bin = new ByteArrayInputStream(data); DataInputStream in = new DataInputStream(bin)) { byte id = (byte) (in.readByte() & 0xFF); - if (id != 0 && !server.isEncrypted()) { + if (id != 0 && !encrypted) { throw new RuntimeException("Packet " + id + " must be received encrypted (server: " + server + ")"); } Protocol protocol = Protocol.getById(PacketFlow.TO_PROXY, id); @@ -219,6 +226,7 @@ public static void handleProxySide(ServerConnection server, byte[] rawData) { @SuppressWarnings("unchecked") ProxyMessageHandler handler = (ProxyMessageHandler) protocol.getHandler(); Message message = handler.read(server, in); + Objects.requireNonNull(message, "handler.read(in) returned null"); handler.handle(server, message); } catch (Exception | AssertionError e) { Logger.getCurrentLogger().warn("Failed to handle plugin message from " + server, e); @@ -233,7 +241,8 @@ public static void handleProxySide(ServerConnection server, byte[] rawData) { */ public static void handleServerSide(@NotNull PacketSender sender, byte[] rawData) { byte[] data; - if (sender.isEncrypted()) { + boolean encrypted = sender.isEncrypted() || sender.consumeEncryptedOnce(); + if (encrypted) { try { data = EncryptionUtil.decrypt(rawData, sender.getKeyPair().getPrivate()); } catch (Exception e) { @@ -245,7 +254,7 @@ public static void handleServerSide(@NotNull PacketSender sender, byte[] rawData try (ByteArrayInputStream bin = new ByteArrayInputStream(data); DataInputStream in = new DataInputStream(bin)) { byte id = (byte) (in.readByte() & 0xFF); - if (id != 0 && !sender.isEncrypted()) { + if (id != 0 && !encrypted) { throw new RuntimeException("Packet " + id + " must be received encrypted (sender: " + sender + ")"); } Protocol protocol = Protocol.getById(PacketFlow.TO_SERVER, id); @@ -264,6 +273,7 @@ public static void handleServerSide(@NotNull PacketSender sender, byte[] rawData @SuppressWarnings("unchecked") ServerMessageHandler handler = (ServerMessageHandler) protocol.getHandler(); Message message = handler.read(in); + Objects.requireNonNull(message, "handler.read(in) returned null"); handler.handle(sender, message); } catch (Exception | AssertionError e) { Logger.getCurrentLogger().warn("Failed to handle plugin message from " + sender, e); diff --git a/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/handler/ProxyboundEncryptionPacket.java b/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/handler/ProxyboundEncryptionPacket.java index e55df37..0ace932 100644 --- a/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/handler/ProxyboundEncryptionPacket.java +++ b/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/handler/ProxyboundEncryptionPacket.java @@ -1,6 +1,5 @@ package net.azisaba.azipluginmessaging.api.protocol.handler; -import net.azisaba.azipluginmessaging.api.AziPluginMessagingConfig; import net.azisaba.azipluginmessaging.api.Logger; import net.azisaba.azipluginmessaging.api.protocol.Protocol; import net.azisaba.azipluginmessaging.api.protocol.message.EncryptionMessage; @@ -40,11 +39,7 @@ public void handle(@NotNull PacketSender sender, @NotNull EncryptionMessage msg) Logger.getCurrentLogger().warn("Failed to send public key to the server " + sender); } - // Enable encryption - sender.setEncrypted(true); - - if (AziPluginMessagingConfig.debug) { - Logger.getCurrentLogger().info("Encryption enabled for " + sender); - } + // Enable encryption here, otherwise we will not be able to receive the encrypted response + sender.setEncryptedOnce(); } } diff --git a/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/handler/ProxyboundEncryptionResponsePacket.java b/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/handler/ProxyboundEncryptionResponsePacket.java new file mode 100644 index 0000000..e04b47f --- /dev/null +++ b/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/handler/ProxyboundEncryptionResponsePacket.java @@ -0,0 +1,32 @@ +package net.azisaba.azipluginmessaging.api.protocol.handler; + +import net.azisaba.azipluginmessaging.api.AziPluginMessagingConfig; +import net.azisaba.azipluginmessaging.api.Logger; +import net.azisaba.azipluginmessaging.api.protocol.Protocol; +import net.azisaba.azipluginmessaging.api.protocol.message.EmptyMessage; +import net.azisaba.azipluginmessaging.api.server.PacketSender; +import net.azisaba.azipluginmessaging.api.server.ServerConnection; +import org.jetbrains.annotations.NotNull; + +import java.io.DataInputStream; +import java.io.IOException; + +public class ProxyboundEncryptionResponsePacket implements ProxyMessageHandler { + @Override + public @NotNull EmptyMessage read(@NotNull ServerConnection server, @NotNull DataInputStream in) throws IOException { + return EmptyMessage.INSTANCE; + } + + @Override + public void handle(@NotNull PacketSender sender, @NotNull EmptyMessage msg) throws Exception { + // Enable encryption + sender.setEncrypted(true); + + if (AziPluginMessagingConfig.debug) { + Logger.getCurrentLogger().info("Encryption enabled for " + sender); + } + + // Send response to server to set the "encrypted" flag to true + Protocol.S_ENCRYPTION_RESPONSE.sendPacket(sender, EmptyMessage.INSTANCE); + } +} diff --git a/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/handler/ServerboundEncryptionPacket.java b/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/handler/ServerboundEncryptionPacket.java index 563dc35..6ac7487 100644 --- a/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/handler/ServerboundEncryptionPacket.java +++ b/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/handler/ServerboundEncryptionPacket.java @@ -1,9 +1,8 @@ package net.azisaba.azipluginmessaging.api.protocol.handler; -import net.azisaba.azipluginmessaging.api.AziPluginMessagingConfig; -import net.azisaba.azipluginmessaging.api.AziPluginMessagingProvider; -import net.azisaba.azipluginmessaging.api.Logger; import net.azisaba.azipluginmessaging.api.entity.Player; +import net.azisaba.azipluginmessaging.api.protocol.Protocol; +import net.azisaba.azipluginmessaging.api.protocol.message.EmptyMessage; import net.azisaba.azipluginmessaging.api.protocol.message.EncryptionMessage; import net.azisaba.azipluginmessaging.api.server.PacketSender; import org.jetbrains.annotations.NotNull; @@ -33,13 +32,15 @@ public void handle(@NotNull PacketSender sender, @NotNull EncryptionMessage msg) // Set the public key from the proxy sender.setRemotePublicKey(msg.getPublicKey()); - // Enable encryption + // Enable encryption to be able to send packet sender.setEncrypted(true); - - if (AziPluginMessagingConfig.debug) { - Logger.getCurrentLogger().info("Encryption enabled for " + sender); + sender.setEncryptedOnce(); + try { + // Send response to proxy + Protocol.P_ENCRYPTION_RESPONSE.sendPacket(sender, EmptyMessage.INSTANCE); + } finally { + // Disable encryption because response is not guaranteed to be received on our side + sender.setEncrypted(false); } - - AziPluginMessagingProvider.get().getPacketQueue().flush(sender); } } diff --git a/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/handler/ServerboundEncryptionResponsePacket.java b/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/handler/ServerboundEncryptionResponsePacket.java new file mode 100644 index 0000000..c720ece --- /dev/null +++ b/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/handler/ServerboundEncryptionResponsePacket.java @@ -0,0 +1,30 @@ +package net.azisaba.azipluginmessaging.api.protocol.handler; + +import net.azisaba.azipluginmessaging.api.AziPluginMessagingConfig; +import net.azisaba.azipluginmessaging.api.AziPluginMessagingProvider; +import net.azisaba.azipluginmessaging.api.Logger; +import net.azisaba.azipluginmessaging.api.protocol.message.EmptyMessage; +import net.azisaba.azipluginmessaging.api.server.PacketSender; +import org.jetbrains.annotations.NotNull; + +import java.io.DataInputStream; +import java.io.IOException; + +public class ServerboundEncryptionResponsePacket implements ServerMessageHandler { + @Override + public @NotNull EmptyMessage read(@NotNull DataInputStream in) throws IOException { + return EmptyMessage.INSTANCE; + } + + @Override + public void handle(@NotNull PacketSender sender, @NotNull EmptyMessage msg) throws Exception { + // Enable encryption + sender.setEncrypted(true); + + if (AziPluginMessagingConfig.debug) { + Logger.getCurrentLogger().info("Encryption enabled for " + sender); + } + + AziPluginMessagingProvider.get().getPacketQueue().flush(sender); + } +} diff --git a/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/message/EmptyMessage.java b/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/message/EmptyMessage.java new file mode 100644 index 0000000..b5ca4bc --- /dev/null +++ b/api/src/main/java/net/azisaba/azipluginmessaging/api/protocol/message/EmptyMessage.java @@ -0,0 +1,17 @@ +package net.azisaba.azipluginmessaging.api.protocol.message; + +import org.jetbrains.annotations.NotNull; + +import java.io.DataOutputStream; +import java.io.IOException; + +public class EmptyMessage implements Message { + public static final EmptyMessage INSTANCE = new EmptyMessage(); + + private EmptyMessage() {} + + @Override + public void write(@NotNull DataOutputStream out) throws IOException { + // no-op + } +} diff --git a/api/src/main/java/net/azisaba/azipluginmessaging/api/server/Connection.java b/api/src/main/java/net/azisaba/azipluginmessaging/api/server/Connection.java index 553ed12..1746aaa 100644 --- a/api/src/main/java/net/azisaba/azipluginmessaging/api/server/Connection.java +++ b/api/src/main/java/net/azisaba/azipluginmessaging/api/server/Connection.java @@ -21,6 +21,10 @@ public interface Connection { */ boolean isEncrypted(); + void setEncryptedOnce(); + + boolean consumeEncryptedOnce(); + /** * Gets the local key pair for encrypting packets. * @return the local key pair diff --git a/api/src/main/java/net/azisaba/azipluginmessaging/api/server/PacketSender.java b/api/src/main/java/net/azisaba/azipluginmessaging/api/server/PacketSender.java index a7733b9..a18e974 100644 --- a/api/src/main/java/net/azisaba/azipluginmessaging/api/server/PacketSender.java +++ b/api/src/main/java/net/azisaba/azipluginmessaging/api/server/PacketSender.java @@ -2,7 +2,6 @@ import org.jetbrains.annotations.NotNull; -import java.security.KeyPair; import java.security.PublicKey; /** diff --git a/api/src/test/java/net/azisaba/azipluginmessaging/test/ProtocolTest.java b/api/src/test/java/net/azisaba/azipluginmessaging/test/ProtocolTest.java index 7535d82..d06a319 100644 --- a/api/src/test/java/net/azisaba/azipluginmessaging/test/ProtocolTest.java +++ b/api/src/test/java/net/azisaba/azipluginmessaging/test/ProtocolTest.java @@ -7,6 +7,7 @@ public class ProtocolTest { @Test public void load() { + // check packets Protocol.getById(PacketFlow.TO_SERVER, (byte) 0); } } diff --git a/build.gradle.kts b/build.gradle.kts index 3b9280a..e38723e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,7 +6,7 @@ plugins { } group = "net.azisaba.azipluginmessaging" -version = "2.3.1-SNAPSHOT" +version = "3.0.0" repositories { mavenCentral() diff --git a/spigot/build.gradle.kts b/spigot/build.gradle.kts index e91a0d1..e153d8c 100644 --- a/spigot/build.gradle.kts +++ b/spigot/build.gradle.kts @@ -14,6 +14,18 @@ dependencies { } tasks { + processResources { + from(sourceSets.main.get().resources.srcDirs) { + include("**") + val tokenReplacementMap = mapOf( + "VERSION" to project.version + ) + filter("tokens" to tokenReplacementMap) + } + filteringCharset = "UTF-8" + duplicatesStrategy = DuplicatesStrategy.INCLUDE + } + shadowJar { archiveFileName.set("AziPluginMessaging-Spigot-${project.version}.jar") } diff --git a/spigot/src/main/java/net/azisaba/azipluginmessaging/spigot/entity/PlayerImpl.java b/spigot/src/main/java/net/azisaba/azipluginmessaging/spigot/entity/PlayerImpl.java index 74a333a..ad0d209 100644 --- a/spigot/src/main/java/net/azisaba/azipluginmessaging/spigot/entity/PlayerImpl.java +++ b/spigot/src/main/java/net/azisaba/azipluginmessaging/spigot/entity/PlayerImpl.java @@ -15,6 +15,7 @@ import java.util.Objects; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; public class PlayerImpl implements Player, PacketSender { @@ -25,6 +26,7 @@ public class PlayerImpl implements Player, PacketSender { private boolean encrypted = false; public String challenge = null; public AtomicInteger joins = new AtomicInteger(); + private final AtomicBoolean encryptedOnce = new AtomicBoolean(false); @Contract(value = "null -> fail", pure = true) private PlayerImpl(@Nullable org.bukkit.entity.Player handle) { @@ -90,6 +92,16 @@ public boolean isEncrypted() { return this.encrypted; } + @Override + public void setEncryptedOnce() { + encryptedOnce.set(true); + } + + @Override + public boolean consumeEncryptedOnce() { + return encryptedOnce.compareAndSet(true, false); + } + @NotNull @Override public PublicKey getRemotePublicKey() { diff --git a/spigot/src/main/resources/plugin.yml b/spigot/src/main/resources/plugin.yml index 2dd380a..e9e9473 100644 --- a/spigot/src/main/resources/plugin.yml +++ b/spigot/src/main/resources/plugin.yml @@ -1,6 +1,6 @@ name: AziPluginMessaging main: net.azisaba.azipluginmessaging.spigot.SpigotPlugin -version: 2.3.0-SNAPSHOT +version: @VERSION@ depend: - LuckPerms commands: diff --git a/velocity/src/main/java/net/azisaba/azipluginmessaging/velocity/server/ServerConnectionImpl.java b/velocity/src/main/java/net/azisaba/azipluginmessaging/velocity/server/ServerConnectionImpl.java index 0f77c57..c591190 100644 --- a/velocity/src/main/java/net/azisaba/azipluginmessaging/velocity/server/ServerConnectionImpl.java +++ b/velocity/src/main/java/net/azisaba/azipluginmessaging/velocity/server/ServerConnectionImpl.java @@ -18,6 +18,7 @@ import java.util.Map; import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; public class ServerConnectionImpl implements ServerConnection { private static long lastCleaned = System.currentTimeMillis(); @@ -26,6 +27,7 @@ public class ServerConnectionImpl implements ServerConnection { private KeyPair keyPair; private PublicKey publicKey; private boolean encrypted = false; + private final AtomicBoolean encryptedOnce = new AtomicBoolean(false); public ServerConnectionImpl(@NotNull com.velocitypowered.api.proxy.ServerConnection handle) { this.handle = handle; @@ -85,6 +87,16 @@ public boolean isEncrypted() { return encrypted; } + @Override + public void setEncryptedOnce() { + encryptedOnce.set(true); + } + + @Override + public boolean consumeEncryptedOnce() { + return encryptedOnce.compareAndSet(true, false); + } + @Override public @NotNull KeyPair getKeyPair() { return Objects.requireNonNull(keyPair);