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 Feature patch == AT == private-f net.minecraft.network.CompressionDecoder inflater diff --git a/build.gradle.kts b/build.gradle.kts index 9b514a9aa1d386481a1ee5077178564cd569117c..9e6c2a4630ce75e4115f76b5e7a1e0b50e8b3197 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -50,6 +50,11 @@ dependencies { runtimeOnly("org.xerial:sqlite-jdbc:3.46.1.3") runtimeOnly("com.mysql:mysql-connector-j:9.1.0") runtimeOnly("com.lmax:disruptor:3.4.4") // Paper + // Paper start - Use Velocity cipher + implementation("com.velocitypowered:velocity-native:3.3.0-SNAPSHOT") { + isTransitive = false + } + // Paper end - Use Velocity cipher runtimeOnly("org.apache.maven:maven-resolver-provider:3.9.6") runtimeOnly("org.apache.maven.resolver:maven-resolver-connector-basic:1.9.18") diff --git a/src/main/java/net/minecraft/network/CipherDecoder.java b/src/main/java/net/minecraft/network/CipherDecoder.java index 778beb445eac5769b9e4e07b4d1294c50ae2602b..7b895b6a626da6297f07582e96d98ecfb6c8c951 100644 --- a/src/main/java/net/minecraft/network/CipherDecoder.java +++ b/src/main/java/net/minecraft/network/CipherDecoder.java @@ -7,13 +7,29 @@ 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 - Use Velocity cipher - public CipherDecoder(Cipher cipher) { - this.cipher = new CipherBase(cipher); + public CipherDecoder(com.velocitypowered.natives.encryption.VelocityCipher cipher) { // Paper - Use Velocity cipher + this.cipher = cipher; // Paper - Use Velocity cipher } protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List list) throws Exception { - list.add(this.cipher.decipher(channelHandlerContext, byteBuf)); + // Paper start - Use Velocity cipher + 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 - Use Velocity cipher } + + // Paper start - Use Velocity cipher + @Override + public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { + cipher.close(); + } + // Paper end - Use Velocity cipher } diff --git a/src/main/java/net/minecraft/network/CipherEncoder.java b/src/main/java/net/minecraft/network/CipherEncoder.java index 0f3d502a9680006bcdcd7d272240a2e5c3b46790..ffa1c48585fbbc1d30826d435043527f6183a3ee 100644 --- a/src/main/java/net/minecraft/network/CipherEncoder.java +++ b/src/main/java/net/minecraft/network/CipherEncoder.java @@ -4,15 +4,32 @@ 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 - Use Velocity cipher; change superclass + private final com.velocitypowered.natives.encryption.VelocityCipher cipher; // Paper - Use Velocity cipher - public CipherEncoder(Cipher cipher) { - this.cipher = new CipherBase(cipher); + public CipherEncoder(com.velocitypowered.natives.encryption.VelocityCipher cipher) { // Paper - Use Velocity cipher + this.cipher = cipher; // Paper - Use Velocity cipher } - 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 - Use Velocity cipher + 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 - Use Velocity cipher } + + // Paper start - Use Velocity cipher + @Override + public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { + cipher.close(); + } + // Paper end - Use Velocity cipher } diff --git a/src/main/java/net/minecraft/network/CompressionDecoder.java b/src/main/java/net/minecraft/network/CompressionDecoder.java index a57e8516b3fafd7060dc1b30550164e74e17fc84..3a853d7401a8275b1a3d525bbda8095de12d0038 100644 --- a/src/main/java/net/minecraft/network/CompressionDecoder.java +++ b/src/main/java/net/minecraft/network/CompressionDecoder.java @@ -13,13 +13,21 @@ public class CompressionDecoder extends ByteToMessageDecoder { public static final int MAXIMUM_COMPRESSED_LENGTH = 2097152; public static final int MAXIMUM_UNCOMPRESSED_LENGTH = 8388608; private Inflater inflater; + private com.velocitypowered.natives.compression.VelocityCompressor compressor; // Paper - Use Velocity cipher private int threshold; private boolean validateDecompressed; + // Paper start - Use Velocity cipher + @io.papermc.paper.annotation.DoNotUse public CompressionDecoder(int compressionThreshold, boolean rejectsBadPackets) { + this(null, compressionThreshold, rejectsBadPackets); + } + public CompressionDecoder(com.velocitypowered.natives.compression.VelocityCompressor compressor, int compressionThreshold, boolean rejectsBadPackets) { this.threshold = compressionThreshold; this.validateDecompressed = rejectsBadPackets; - this.inflater = new Inflater(); + this.inflater = compressor == null ? new Inflater() : null; + this.compressor = compressor; + // Paper end - Use Velocity cipher } protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List list) throws Exception { @@ -38,14 +46,42 @@ public class CompressionDecoder extends ByteToMessageDecoder { } } + if (inflater != null) { // Paper - Use Velocity cipher; fallback to vanilla inflater this.setupInflaterInput(byteBuf); ByteBuf byteBuf2 = this.inflate(channelHandlerContext, i); this.inflater.reset(); list.add(byteBuf2); + return; // Paper - Use Velocity cipher + } // Paper - use velocity compression + + // Paper start - Use Velocity cipher + 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 - Use Velocity cipher } } } + // Paper start - Use Velocity cipher + @Override + public void handlerRemoved0(ChannelHandlerContext ctx) throws Exception { + if (this.compressor != null) { + this.compressor.close(); + } + } + // Paper end - Use Velocity cipher + private void setupInflaterInput(ByteBuf buf) { ByteBuffer byteBuffer; if (buf.nioBufferCount() > 0) { @@ -82,7 +118,13 @@ public class CompressionDecoder extends ByteToMessageDecoder { } } - public void setThreshold(int compressionThreshold, boolean rejectsBadPackets) { + // Paper start - Use Velocity cipher + public void setThreshold(com.velocitypowered.natives.compression.VelocityCompressor compressor, int compressionThreshold, boolean rejectsBadPackets) { + if (this.compressor == null && compressor != null) { // Only re-configure once. Re-reconfiguring would require closing the native compressor. + this.compressor = compressor; + this.inflater = null; + } + // Paper end - Use Velocity cipher this.threshold = compressionThreshold; this.validateDecompressed = rejectsBadPackets; } diff --git a/src/main/java/net/minecraft/network/CompressionEncoder.java b/src/main/java/net/minecraft/network/CompressionEncoder.java index 34a80d2f34555663ab1b394972957089214cb337..ec30c291188ac3bba7f1c3bc397576d1edb0a57f 100644 --- a/src/main/java/net/minecraft/network/CompressionEncoder.java +++ b/src/main/java/net/minecraft/network/CompressionEncoder.java @@ -6,16 +6,31 @@ import io.netty.handler.codec.MessageToByteEncoder; import java.util.zip.Deflater; public class CompressionEncoder extends MessageToByteEncoder { - private final byte[] encodeBuf = new byte[8192]; + @javax.annotation.Nullable private final byte[] encodeBuf; // Paper - Use Velocity cipher + @javax.annotation.Nullable // Paper - Use Velocity cipher private final Deflater deflater; + @javax.annotation.Nullable // Paper - Use Velocity cipher + private final com.velocitypowered.natives.compression.VelocityCompressor compressor; // Paper - Use Velocity cipher private int threshold; + // Paper start - Use Velocity cipher public CompressionEncoder(int compressionThreshold) { + this(null, compressionThreshold); + } + public CompressionEncoder(@javax.annotation.Nullable 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 - Use Velocity cipher } - protected void encode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, ByteBuf byteBuf2) { + protected void encode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, ByteBuf byteBuf2) throws Exception { // Paper - Use Velocity cipher int i = byteBuf.readableBytes(); if (i > 8388608) { throw new IllegalArgumentException("Packet too big (is " + i + ", should be less than 8388608)"); @@ -24,6 +39,7 @@ public class CompressionEncoder extends MessageToByteEncoder { VarInt.write(byteBuf2, 0); byteBuf2.writeBytes(byteBuf); } else { + if (this.deflater != null) { // Paper - Use Velocity cipher byte[] bs = new byte[i]; byteBuf.readBytes(bs); VarInt.write(byteBuf2, bs.length); @@ -36,10 +52,47 @@ public class CompressionEncoder extends MessageToByteEncoder { } this.deflater.reset(); + // Paper start - Use Velocity cipher + return; + } + + VarInt.write(byteBuf2, i); + final ByteBuf compatibleIn = com.velocitypowered.natives.util.MoreByteBufUtils.ensureCompatible(channelHandlerContext.alloc(), this.compressor, byteBuf); + try { + this.compressor.deflate(compatibleIn, byteBuf2); + } finally { + compatibleIn.release(); + } } } } + @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. + final 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 - Use Velocity cipher + } + } + 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 7dc7aeb1d94d26cf54bd4e4ab13972a3a60c1f98..36624d3dc7e3f2a64f88b01c5e906018fcee0015 100644 --- a/src/main/java/net/minecraft/network/Connection.java +++ b/src/main/java/net/minecraft/network/Connection.java @@ -792,11 +792,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 - Use Velocity cipher +// 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 - Use Velocity cipher public boolean isEncrypted() { return this.encrypted; @@ -829,14 +846,15 @@ public class Connection extends SimpleChannelInboundHandler> { public void setupCompression(int compressionThreshold, boolean rejectsBadPackets) { if (compressionThreshold >= 0) { + com.velocitypowered.natives.compression.VelocityCompressor compressor = com.velocitypowered.natives.util.Natives.compress.get().create(io.papermc.paper.configuration.GlobalConfiguration.get().misc.compressionLevel.or(-1)); // Paper - Use Velocity cipher ChannelHandler channelhandler = this.channel.pipeline().get("decompress"); if (channelhandler instanceof CompressionDecoder) { CompressionDecoder packetdecompressor = (CompressionDecoder) channelhandler; - packetdecompressor.setThreshold(compressionThreshold, rejectsBadPackets); + packetdecompressor.setThreshold(compressor, compressionThreshold, rejectsBadPackets); // Paper - Use Velocity cipher } else { - this.channel.pipeline().addAfter("splitter", "decompress", new CompressionDecoder(compressionThreshold, rejectsBadPackets)); + this.channel.pipeline().addAfter("splitter", "decompress", new CompressionDecoder(compressor, compressionThreshold, rejectsBadPackets)); // Paper - Use Velocity cipher } channelhandler = this.channel.pipeline().get("compress"); @@ -845,7 +863,7 @@ public class Connection extends SimpleChannelInboundHandler> { packetcompressor.setThreshold(compressionThreshold); } else { - this.channel.pipeline().addAfter("prepender", "compress", new CompressionEncoder(compressionThreshold)); + this.channel.pipeline().addAfter("prepender", "compress", new CompressionEncoder(compressor, compressionThreshold)); // Paper - Use Velocity cipher } this.channel.pipeline().fireUserEventTriggered(io.papermc.paper.network.ConnectionEvent.COMPRESSION_THRESHOLD_SET); // Paper - Add Channel initialization listeners } else { diff --git a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java index 4abe4f75956e4c16f8b4e0b2f77ad64d7914aa65..3a9e25b436f366fffe08c3b0c1fce11ed42ee646 100644 --- a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java +++ b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java @@ -112,6 +112,11 @@ public class ServerConnectionListener { } // Paper end - Warn people with console access that HAProxy is in use. + // Paper start - Use Velocity cipher + 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 - Use Velocity cipher + 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 4a89b73d972f366e70f4d2bd96c6ee413593baba..033755682c61c889723c3669b5cff4de147f637e 100644 --- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java @@ -287,12 +287,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 - Use Velocity cipher +// Cipher cipher = Crypt.getCipher(2, secretkey); +// Cipher cipher1 = Crypt.getCipher(1, secretkey); + // Paper end - Use Velocity cipher 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 - Use Velocity cipher } catch (CryptException cryptographyexception) { throw new IllegalStateException("Protocol error", cryptographyexception); }