From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Mon, 26 Jul 2021 02:15:17 -0400 Subject: [PATCH] Use Velocity compression and cipher natives diff --git a/build.gradle.kts b/build.gradle.kts index c7ebad49e04c36c27341cabc4a024f170d762f3f..f46f11c32560da6f4955284d93da3dd7e22555a3 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -55,6 +55,7 @@ dependencies { implementation("io.netty:netty-all:4.1.65.Final") // Paper implementation("net.fabricmc:mapping-io:0.3.0") // Paper - needed to read mappings for stacktrace deobfuscation + implementation("com.velocitypowered:velocity-native:1.1.0-SNAPSHOT") // Paper testImplementation("io.github.classgraph:classgraph:4.8.47") // Paper - mob goal test testImplementation("junit:junit:4.13.1") diff --git a/src/main/java/net/minecraft/network/CipherDecoder.java b/src/main/java/net/minecraft/network/CipherDecoder.java index 06d545bc7206dd0d56cf27c31935c0f5ed21ef08..201e5eb2d0ec985ea60c59ac89593bd88ad3ee4d 100644 --- a/src/main/java/net/minecraft/network/CipherDecoder.java +++ b/src/main/java/net/minecraft/network/CipherDecoder.java @@ -7,14 +7,30 @@ import java.util.List; import javax.crypto.Cipher; public class CipherDecoder extends MessageToMessageDecoder { - private final CipherBase cipher; + private final com.velocitypowered.natives.encryption.VelocityCipher cipher; // Paper - public CipherDecoder(Cipher cipher) { - this.cipher = new CipherBase(cipher); + public CipherDecoder(com.velocitypowered.natives.encryption.VelocityCipher cipher) { // Paper + this.cipher = cipher; // Paper } @Override protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List list) throws Exception { - list.add(this.cipher.decipher(channelHandlerContext, byteBuf)); + // Paper start + ByteBuf compatible = com.velocitypowered.natives.util.MoreByteBufUtils.ensureCompatible(channelHandlerContext.alloc(), cipher, byteBuf); + try { + cipher.process(compatible); + list.add(compatible); + } catch (Exception e) { + compatible.release(); // compatible will never be used if we throw an exception + throw e; + } + // Paper end } + + // Paper start + @Override + public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { + cipher.close(); + } + // Paper end } diff --git a/src/main/java/net/minecraft/network/CipherEncoder.java b/src/main/java/net/minecraft/network/CipherEncoder.java index 50a7058b18a8ca05363b73eaefbd812ef50d53f1..7319da27dfa5029c9893ec04bfd4325531af8a98 100644 --- a/src/main/java/net/minecraft/network/CipherEncoder.java +++ b/src/main/java/net/minecraft/network/CipherEncoder.java @@ -4,16 +4,33 @@ import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.MessageToByteEncoder; import javax.crypto.Cipher; +import java.util.List; -public class CipherEncoder extends MessageToByteEncoder { - private final CipherBase cipher; +public class CipherEncoder extends io.netty.handler.codec.MessageToMessageEncoder { // Paper - change superclass + private final com.velocitypowered.natives.encryption.VelocityCipher cipher; // Paper - public CipherEncoder(Cipher cipher) { - this.cipher = new CipherBase(cipher); + public CipherEncoder(com.velocitypowered.natives.encryption.VelocityCipher cipher) { // Paper + this.cipher = cipher; // Paper } @Override - protected void encode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, ByteBuf byteBuf2) throws Exception { - this.cipher.encipher(byteBuf, byteBuf2); + protected void encode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List list) throws Exception { + // Paper start + ByteBuf compatible = com.velocitypowered.natives.util.MoreByteBufUtils.ensureCompatible(channelHandlerContext.alloc(), cipher, byteBuf); + try { + cipher.process(compatible); + list.add(compatible); + } catch (Exception e) { + compatible.release(); // compatible will never be used if we throw an exception + throw e; + } + // Paper end } + + // Paper start + @Override + public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { + cipher.close(); + } + // Paper end } diff --git a/src/main/java/net/minecraft/network/CompressionDecoder.java b/src/main/java/net/minecraft/network/CompressionDecoder.java index efd05c8c1114aab4c237ccbc2e4e935a08c076ee..3569fc49e87b773dce0e59b9069797834ae32613 100644 --- a/src/main/java/net/minecraft/network/CompressionDecoder.java +++ b/src/main/java/net/minecraft/network/CompressionDecoder.java @@ -12,13 +12,20 @@ public class CompressionDecoder extends ByteToMessageDecoder { public static final int MAXIMUM_COMPRESSED_LENGTH = 2097152; public static final int MAXIMUM_UNCOMPRESSED_LENGTH = 8388608; private final Inflater inflater; + private final com.velocitypowered.natives.compression.VelocityCompressor compressor; // Paper private int threshold; private boolean validateDecompressed; + // Paper start public CompressionDecoder(int compressionThreshold, boolean bl) { + this(null, compressionThreshold, bl); + } + public CompressionDecoder(com.velocitypowered.natives.compression.VelocityCompressor compressor, int compressionThreshold, boolean bl) { this.threshold = compressionThreshold; this.validateDecompressed = bl; - this.inflater = new Inflater(); + this.inflater = compressor == null ? new Inflater() : null; + this.compressor = compressor; + // Paper end } @Override @@ -39,6 +46,8 @@ public class CompressionDecoder extends ByteToMessageDecoder { } } + // Paper start + if (this.inflater != null) { byte[] bs = new byte[friendlyByteBuf.readableBytes()]; friendlyByteBuf.readBytes(bs); this.inflater.setInput(bs); @@ -46,10 +55,36 @@ public class CompressionDecoder extends ByteToMessageDecoder { this.inflater.inflate(cs); list.add(Unpooled.wrappedBuffer(cs)); this.inflater.reset(); + return; + } + + int claimedUncompressedSize = i; // OBFHELPER + ByteBuf compatibleIn = com.velocitypowered.natives.util.MoreByteBufUtils.ensureCompatible(channelHandlerContext.alloc(), this.compressor, byteBuf); + ByteBuf uncompressed = com.velocitypowered.natives.util.MoreByteBufUtils.preferredBuffer(channelHandlerContext.alloc(), this.compressor, claimedUncompressedSize); + try { + this.compressor.inflate(compatibleIn, uncompressed, claimedUncompressedSize); + list.add(uncompressed); + byteBuf.clear(); + } catch (Exception e) { + uncompressed.release(); + throw e; + } finally { + compatibleIn.release(); + } + // Paper end } } } + // Paper start + @Override + public void handlerRemoved0(ChannelHandlerContext ctx) throws Exception { + if (this.compressor != null) { + this.compressor.close(); + } + } + // Paper end + public void setThreshold(int compressionThreshold, boolean bl) { this.threshold = compressionThreshold; this.validateDecompressed = bl; diff --git a/src/main/java/net/minecraft/network/CompressionEncoder.java b/src/main/java/net/minecraft/network/CompressionEncoder.java index 524c0c674f63cfcb601416a18348f37aabb4e3ff..89bf5066b83e8d79c77c90fce1f7858c06b01730 100644 --- a/src/main/java/net/minecraft/network/CompressionEncoder.java +++ b/src/main/java/net/minecraft/network/CompressionEncoder.java @@ -6,23 +6,38 @@ import io.netty.handler.codec.MessageToByteEncoder; import java.util.zip.Deflater; public class CompressionEncoder extends MessageToByteEncoder { - private final byte[] encodeBuf = new byte[8192]; + private final byte[] encodeBuf; // Paper private final Deflater deflater; + private final com.velocitypowered.natives.compression.VelocityCompressor compressor; // Paper private int threshold; + // Paper start public CompressionEncoder(int compressionThreshold) { + this(null, compressionThreshold); + } + public CompressionEncoder(com.velocitypowered.natives.compression.VelocityCompressor compressor, int compressionThreshold) { this.threshold = compressionThreshold; - this.deflater = new Deflater(); + if (compressor == null) { + this.encodeBuf = new byte[8192]; + this.deflater = new Deflater(); + } else { + this.encodeBuf = null; + this.deflater = null; + } + this.compressor = compressor; + // Paper end } @Override - protected void encode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, ByteBuf byteBuf2) { + protected void encode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, ByteBuf byteBuf2) throws Exception { // Paper int i = byteBuf.readableBytes(); FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(byteBuf2); if (i < this.threshold) { friendlyByteBuf.writeVarInt(0); friendlyByteBuf.writeBytes(byteBuf); } else { + // Paper start + if (this.deflater != null) { byte[] bs = new byte[i]; byteBuf.readBytes(bs); friendlyByteBuf.writeVarInt(bs.length); @@ -35,10 +50,48 @@ public class CompressionEncoder extends MessageToByteEncoder { } this.deflater.reset(); + return; + } + + friendlyByteBuf.writeVarInt(i); + ByteBuf compatibleIn = com.velocitypowered.natives.util.MoreByteBufUtils.ensureCompatible(channelHandlerContext.alloc(), this.compressor, byteBuf); + try { + this.compressor.deflate(compatibleIn, byteBuf2); + } finally { + compatibleIn.release(); + } + // Paper end } } + // Paper start + @Override + protected ByteBuf allocateBuffer(ChannelHandlerContext ctx, ByteBuf msg, boolean preferDirect) throws Exception{ + if (this.compressor != null) { + // We allocate bytes to be compressed plus 1 byte. This covers two cases: + // + // - Compression + // According to https://github.com/ebiggers/libdeflate/blob/master/libdeflate.h#L103, + // if the data compresses well (and we do not have some pathological case) then the maximum + // size the compressed size will ever be is the input size minus one. + // - Uncompressed + // This is fairly obvious - we will then have one more than the uncompressed size. + int initialBufferSize = msg.readableBytes() + 1; + return com.velocitypowered.natives.util.MoreByteBufUtils.preferredBuffer(ctx.alloc(), this.compressor, initialBufferSize); + } + + return super.allocateBuffer(ctx, msg, preferDirect); + } + + @Override + public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { + if (this.compressor != null) { + this.compressor.close(); + } + } + // Paper end + public int getThreshold() { return this.threshold; } diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java index 0dae504f49a4a3c66a01eb03896833a010cd5821..5f43459884daea61178c3fdb913204e64f833f61 100644 --- a/src/main/java/net/minecraft/network/Connection.java +++ b/src/main/java/net/minecraft/network/Connection.java @@ -657,11 +657,28 @@ public class Connection extends SimpleChannelInboundHandler> { return networkmanager; } - public void setEncryptionKey(Cipher decryptionCipher, Cipher encryptionCipher) { - this.encrypted = true; - this.channel.pipeline().addBefore("splitter", "decrypt", new CipherDecoder(decryptionCipher)); - this.channel.pipeline().addBefore("prepender", "encrypt", new CipherEncoder(encryptionCipher)); + // Paper start +// public void setEncryptionKey(Cipher decryptionCipher, Cipher encryptionCipher) { +// this.encrypted = true; +// this.channel.pipeline().addBefore("splitter", "decrypt", new CipherDecoder(decryptionCipher)); +// this.channel.pipeline().addBefore("prepender", "encrypt", new CipherEncoder(encryptionCipher)); +// } + + public void setupEncryption(javax.crypto.SecretKey key) throws net.minecraft.util.CryptException { + if (!this.encrypted) { + try { + com.velocitypowered.natives.encryption.VelocityCipher decryption = com.velocitypowered.natives.util.Natives.cipher.get().forDecryption(key); + com.velocitypowered.natives.encryption.VelocityCipher encryption = com.velocitypowered.natives.util.Natives.cipher.get().forEncryption(key); + + this.encrypted = true; + this.channel.pipeline().addBefore("splitter", "decrypt", new CipherDecoder(decryption)); + this.channel.pipeline().addBefore("prepender", "encrypt", new CipherEncoder(encryption)); + } catch (java.security.GeneralSecurityException e) { + throw new net.minecraft.util.CryptException(e); + } + } } + // Paper end public boolean isEncrypted() { return this.encrypted; @@ -690,16 +707,17 @@ public class Connection extends SimpleChannelInboundHandler> { public void setupCompression(int compressionThreshold, boolean flag) { if (compressionThreshold >= 0) { + com.velocitypowered.natives.compression.VelocityCompressor compressor = com.velocitypowered.natives.util.Natives.compress.get().create(-1); // Paper if (this.channel.pipeline().get("decompress") instanceof CompressionDecoder) { ((CompressionDecoder) this.channel.pipeline().get("decompress")).setThreshold(compressionThreshold, flag); } else { - this.channel.pipeline().addBefore("decoder", "decompress", new CompressionDecoder(compressionThreshold, flag)); + this.channel.pipeline().addBefore("decoder", "decompress", new CompressionDecoder(compressor, compressionThreshold, flag)); // Paper } if (this.channel.pipeline().get("compress") instanceof CompressionEncoder) { ((CompressionEncoder) this.channel.pipeline().get("compress")).setThreshold(compressionThreshold); } else { - this.channel.pipeline().addBefore("encoder", "compress", new CompressionEncoder(compressionThreshold)); + this.channel.pipeline().addBefore("encoder", "compress", new CompressionEncoder(compressor, compressionThreshold)); // Paper } } else { if (this.channel.pipeline().get("decompress") instanceof CompressionDecoder) { diff --git a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java index 961660f6f9e00b93252519e38b74c66c53388ed2..a7046da8c097254907e01cd17f4107c8744f4a6e 100644 --- a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java +++ b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java @@ -104,6 +104,11 @@ public class ServerConnectionListener { ServerConnectionListener.LOGGER.info("Using default channel type"); } + // Paper start - indicate Velocity natives in use + ServerConnectionListener.LOGGER.info("Paper: Using " + com.velocitypowered.natives.util.Natives.compress.getLoadedVariant() + " compression from Velocity."); + ServerConnectionListener.LOGGER.info("Paper: Using " + com.velocitypowered.natives.util.Natives.cipher.getLoadedVariant() + " cipher from Velocity."); + // Paper end + this.channels.add(((ServerBootstrap) ((ServerBootstrap) (new ServerBootstrap()).channel(oclass)).childHandler(new ChannelInitializer() { protected void initChannel(Channel channel) { try { diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java index 45e77d96f673ce68cf15ce3d45fd1eeffed4d8d8..01fee879c946b6640da34d5890d686f0152437dc 100644 --- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java @@ -275,12 +275,14 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener } SecretKey secretkey = packet.getSecretKey(privatekey); - Cipher cipher = Crypt.getCipher(2, secretkey); - Cipher cipher1 = Crypt.getCipher(1, secretkey); + // Paper start +// Cipher cipher = Crypt.getCipher(2, secretkey); +// Cipher cipher1 = Crypt.getCipher(1, secretkey); + // Paper end s = (new BigInteger(Crypt.digestData("", this.server.getKeyPair().getPublic(), secretkey))).toString(16); this.state = ServerLoginPacketListenerImpl.State.AUTHENTICATING; - this.connection.setEncryptionKey(cipher, cipher1); + this.connection.setupEncryption(secretkey); // Paper } catch (CryptException cryptographyexception) { throw new IllegalStateException("Protocol error", cryptographyexception); }