diff --git a/src/main/java/com/velocitypowered/proxy/VelocityServer.java b/src/main/java/com/velocitypowered/proxy/VelocityServer.java index 524bf4e5b..00226aef0 100644 --- a/src/main/java/com/velocitypowered/proxy/VelocityServer.java +++ b/src/main/java/com/velocitypowered/proxy/VelocityServer.java @@ -4,6 +4,7 @@ import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.protocol.StateRegistry; import com.velocitypowered.proxy.protocol.netty.MinecraftPipelineUtils; import com.velocitypowered.proxy.connection.client.HandshakeSessionHandler; +import com.velocitypowered.proxy.protocol.packets.EncryptionRequest; import io.netty.bootstrap.Bootstrap; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.*; @@ -30,6 +31,10 @@ public class VelocityServer { return server; } + public KeyPair getServerKeyPair() { + return serverKeyPair; + } + public void initialize() { // Create a key pair try { diff --git a/src/main/java/com/velocitypowered/proxy/connection/client/LoginSessionHandler.java b/src/main/java/com/velocitypowered/proxy/connection/client/LoginSessionHandler.java index 8fe8bd57b..3831b1a4f 100644 --- a/src/main/java/com/velocitypowered/proxy/connection/client/LoginSessionHandler.java +++ b/src/main/java/com/velocitypowered/proxy/connection/client/LoginSessionHandler.java @@ -3,6 +3,7 @@ package com.velocitypowered.proxy.connection.client; import com.google.common.base.Preconditions; import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.StateRegistry; +import com.velocitypowered.proxy.protocol.packets.EncryptionRequest; import com.velocitypowered.proxy.protocol.packets.ServerLogin; import com.velocitypowered.proxy.protocol.packets.ServerLoginSuccess; import com.velocitypowered.proxy.connection.MinecraftConnection; @@ -13,9 +14,11 @@ import com.velocitypowered.proxy.data.ServerInfo; import com.velocitypowered.proxy.util.UuidUtils; import java.net.InetSocketAddress; +import java.util.concurrent.ThreadLocalRandom; public class LoginSessionHandler implements MinecraftSessionHandler { private final MinecraftConnection inbound; + private ServerLogin login; public LoginSessionHandler(MinecraftConnection inbound) { this.inbound = Preconditions.checkNotNull(inbound, "inbound"); @@ -23,12 +26,27 @@ public class LoginSessionHandler implements MinecraftSessionHandler { @Override public void handle(MinecraftPacket packet) { - Preconditions.checkArgument(packet instanceof ServerLogin, "Expected a ServerLogin packet, not " + packet.getClass().getName()); + if (packet instanceof ServerLogin) { + this.login = (ServerLogin) packet; + // TODO: Online-mode checks + handleSuccessfulLogin(); + } + } - // TODO: Encryption + private EncryptionRequest generateRequest() { + byte[] verify = new byte[4]; + ThreadLocalRandom.current().nextBytes(verify); + + EncryptionRequest request = new EncryptionRequest(); + request.setPublicKey(VelocityServer.getServer().getServerKeyPair().getPublic().getEncoded()); + request.setVerifyToken(verify); + return request; + } + + private void handleSuccessfulLogin() { inbound.setCompressionThreshold(256); - String username = ((ServerLogin) packet).getUsername(); + String username = login.getUsername(); ServerLoginSuccess success = new ServerLoginSuccess(); success.setUsername(username); success.setUuid(UuidUtils.generateOfflinePlayerUuid(username)); @@ -43,5 +61,4 @@ public class LoginSessionHandler implements MinecraftSessionHandler { inbound.setSessionHandler(new InitialConnectSessionHandler(player)); connection.connect(); } - } diff --git a/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java b/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java index 89df63337..ff2d598cf 100644 --- a/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java +++ b/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java @@ -34,6 +34,7 @@ public enum StateRegistry { LOGIN { { TO_SERVER.register(0x00, ServerLogin.class, ServerLogin::new); + TO_SERVER.register(0x01, EncryptionResponse.class, EncryptionResponse::new); TO_CLIENT.register(0x00, Disconnect.class, Disconnect::new); TO_CLIENT.register(0x01, EncryptionRequest.class, EncryptionRequest::new); diff --git a/src/main/java/com/velocitypowered/proxy/protocol/compression/VelocityCompressor.java b/src/main/java/com/velocitypowered/proxy/protocol/compression/VelocityCompressor.java index ca3872b5a..8c1eaca15 100644 --- a/src/main/java/com/velocitypowered/proxy/protocol/compression/VelocityCompressor.java +++ b/src/main/java/com/velocitypowered/proxy/protocol/compression/VelocityCompressor.java @@ -1,13 +1,12 @@ package com.velocitypowered.proxy.protocol.compression; +import com.velocitypowered.proxy.util.Disposable; import io.netty.buffer.ByteBuf; import java.util.zip.DataFormatException; -public interface VelocityCompressor { +public interface VelocityCompressor extends Disposable { void inflate(ByteBuf source, ByteBuf destination) throws DataFormatException; void deflate(ByteBuf source, ByteBuf destination) throws DataFormatException; - - void dispose(); } diff --git a/src/main/java/com/velocitypowered/proxy/protocol/encryption/VelocityEncryptor.java b/src/main/java/com/velocitypowered/proxy/protocol/encryption/VelocityEncryptor.java new file mode 100644 index 000000000..e05bc0e40 --- /dev/null +++ b/src/main/java/com/velocitypowered/proxy/protocol/encryption/VelocityEncryptor.java @@ -0,0 +1,8 @@ +package com.velocitypowered.proxy.protocol.encryption; + +import com.velocitypowered.proxy.util.Disposable; +import io.netty.buffer.ByteBuf; + +public interface VelocityEncryptor extends Disposable { + void process(ByteBuf source, ByteBuf destination); +} diff --git a/src/main/java/com/velocitypowered/proxy/protocol/packets/EncryptionResponse.java b/src/main/java/com/velocitypowered/proxy/protocol/packets/EncryptionResponse.java new file mode 100644 index 000000000..a91157c8f --- /dev/null +++ b/src/main/java/com/velocitypowered/proxy/protocol/packets/EncryptionResponse.java @@ -0,0 +1,23 @@ +package com.velocitypowered.proxy.protocol.packets; + +import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.ProtocolConstants; +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import io.netty.buffer.ByteBuf; + +public class EncryptionResponse implements MinecraftPacket { + private byte[] sharedSecret; + private byte[] verifyToken; + + @Override + public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { + this.sharedSecret = ProtocolUtils.readByteArray(buf, 256); + this.verifyToken = ProtocolUtils.readByteArray(buf, 4); + } + + @Override + public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { + ProtocolUtils.writeByteArray(buf, sharedSecret); + ProtocolUtils.writeByteArray(buf, verifyToken); + } +} diff --git a/src/main/java/com/velocitypowered/proxy/util/Disposable.java b/src/main/java/com/velocitypowered/proxy/util/Disposable.java new file mode 100644 index 000000000..bca174a6d --- /dev/null +++ b/src/main/java/com/velocitypowered/proxy/util/Disposable.java @@ -0,0 +1,9 @@ +package com.velocitypowered.proxy.util; + +/** + * This marker interface indicates that this object should be explicitly disposed before the object can no longer be used. + * Not disposing these objects will likely leak native resources and eventually lead to resource exhaustion. + */ +public interface Disposable { + void dispose(); +} diff --git a/src/main/java/com/velocitypowered/proxy/util/EncryptionUtils.java b/src/main/java/com/velocitypowered/proxy/util/EncryptionUtils.java new file mode 100644 index 000000000..0746ea4d7 --- /dev/null +++ b/src/main/java/com/velocitypowered/proxy/util/EncryptionUtils.java @@ -0,0 +1,9 @@ +package com.velocitypowered.proxy.util; + +import java.math.BigInteger; + +public enum EncryptionUtils { ; + public static String twoComplementsSha1Digest(byte[] digest) { + return new BigInteger(digest).toString(16); + } +} diff --git a/src/test/java/com/velocitypowered/proxy/util/EncryptionUtilsTest.java b/src/test/java/com/velocitypowered/proxy/util/EncryptionUtilsTest.java new file mode 100644 index 000000000..203f6606e --- /dev/null +++ b/src/test/java/com/velocitypowered/proxy/util/EncryptionUtilsTest.java @@ -0,0 +1,25 @@ +package com.velocitypowered.proxy.util; + +import org.junit.Assert; +import org.junit.Test; + +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; + +public class EncryptionUtilsTest { + @Test + public void twoComplementsSha1Digest() throws Exception { + String notchHash = hexDigest("Notch"); + Assert.assertEquals("4ed1f46bbe04bc756bcb17c0c7ce3e4632f06a48", notchHash); + + String jebHash = hexDigest("jeb_"); + Assert.assertEquals("-7c9d5b0044c130109a5d7b5fb5c317c02b4e28c1", jebHash); + } + + private String hexDigest(String str) throws Exception { + MessageDigest digest = MessageDigest.getInstance("SHA-1"); + digest.update(str.getBytes(StandardCharsets.UTF_8)); + byte[] digested = digest.digest(); + return EncryptionUtils.twoComplementsSha1Digest(digested); + } +} \ No newline at end of file