diff --git a/src/main/java/com/viaversion/aas/codec/CryptoCodec.java b/src/main/java/com/viaversion/aas/codec/CryptoCodec.java index a40731b..4d51584 100644 --- a/src/main/java/com/viaversion/aas/codec/CryptoCodec.java +++ b/src/main/java/com/viaversion/aas/codec/CryptoCodec.java @@ -1,36 +1,60 @@ package com.viaversion.aas.codec; +import com.velocitypowered.natives.encryption.VelocityCipher; +import com.velocitypowered.natives.util.MoreByteBufUtils; +import com.velocitypowered.natives.util.Natives; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.MessageToMessageCodec; -import org.jetbrains.annotations.NotNull; -import javax.crypto.Cipher; +import javax.crypto.SecretKey; import java.util.List; public class CryptoCodec extends MessageToMessageCodec { - private final Cipher cipherEncode; - private final Cipher cipherDecode; + private SecretKey keyEncode; + private SecretKey keyDecode; + private VelocityCipher encoder; + private VelocityCipher decoder; - public CryptoCodec(@NotNull Cipher cipherEncode, @NotNull Cipher cipherDecode) { - this.cipherEncode = cipherEncode; - this.cipherDecode = cipherDecode; + public CryptoCodec(SecretKey keyEncode, SecretKey keyDecode) { + this.keyEncode = keyEncode; + this.keyDecode = keyDecode; + } + + @Override + public void handlerAdded(ChannelHandlerContext ctx) throws Exception { + encoder = Natives.cipher.get().forEncryption(keyEncode); + decoder = Natives.cipher.get().forDecryption(keyDecode); + keyEncode = null; + keyDecode = null; } @Override protected void encode(ChannelHandlerContext ctx, ByteBuf msg, List out) throws Exception { - var i = msg.readerIndex(); - var size = msg.readableBytes(); - msg.writerIndex(i + cipherEncode.update(msg.nioBuffer(), msg.nioBuffer(i, cipherEncode.getOutputSize(size)))); - out.add(msg.retain()); + ByteBuf compatible = MoreByteBufUtils.ensureCompatible(ctx.alloc(), encoder, msg); + try { + encoder.process(compatible); + out.add(compatible.retain()); + } finally { + compatible.release(); + } } @Override protected void decode(ChannelHandlerContext ctx, ByteBuf msg, List out) throws Exception { if (!ctx.channel().isActive()) return; - var i = msg.readerIndex(); - var size = msg.readableBytes(); - msg.writerIndex(i + cipherDecode.update(msg.nioBuffer(), msg.nioBuffer(i, cipherDecode.getOutputSize(size)))); - out.add(msg.retain()); + ByteBuf compatible = MoreByteBufUtils.ensureCompatible(ctx.alloc(), decoder, msg); + try { + decoder.process(compatible); + out.add(compatible.retain()); + } finally { + compatible.release(); + } + } + + @Override + public void handlerRemoved(ChannelHandlerContext ctx) { + encoder.close(); + decoder.close(); } } diff --git a/src/main/kotlin/com/viaversion/aas/AspirinServer.kt b/src/main/kotlin/com/viaversion/aas/AspirinServer.kt index 49ce275..2b7807c 100644 --- a/src/main/kotlin/com/viaversion/aas/AspirinServer.kt +++ b/src/main/kotlin/com/viaversion/aas/AspirinServer.kt @@ -103,9 +103,10 @@ object AspirinServer { .childOption(ChannelOption.TCP_NODELAY, true) .bind(InetAddress.getByName(VIAaaSConfig.bindAddress), VIAaaSConfig.port) - viaaasLogger.info("Using compression: ${Natives.compress.loadedVariant}") - viaaasLogger.info("Binded minecraft into " + chFuture!!.sync().channel().localAddress()) ktorServer = embeddedServer(Netty, commandLineEnvironment(args)) {}.start(false) + + viaaasLogger.info("Using compression: ${Natives.compress.loadedVariant}, crypto: ${Natives.cipher.loadedVariant}") + viaaasLogger.info("Binded minecraft into " + chFuture!!.sync().channel().localAddress()) } fun generateCert() { diff --git a/src/main/kotlin/com/viaversion/aas/Util.kt b/src/main/kotlin/com/viaversion/aas/Util.kt index 3692f48..891c4ff 100644 --- a/src/main/kotlin/com/viaversion/aas/Util.kt +++ b/src/main/kotlin/com/viaversion/aas/Util.kt @@ -7,8 +7,6 @@ import com.google.common.primitives.Ints import com.google.gson.JsonObject import com.viaversion.aas.config.VIAaaSConfig import com.viaversion.aas.util.StacklessException -import com.viaversion.viaversion.api.Via -import com.viaversion.viaversion.api.protocol.packet.State import com.viaversion.viaversion.api.protocol.version.ProtocolVersion import com.viaversion.viaversion.api.type.Type import io.ktor.client.request.* @@ -45,7 +43,6 @@ import java.security.SecureRandom import java.util.* import java.util.concurrent.TimeUnit import javax.crypto.Cipher -import javax.crypto.spec.IvParameterSpec import javax.crypto.spec.SecretKeySpec val badLength = DecoderException("Invalid length!") @@ -53,7 +50,7 @@ val mcLogger = LoggerFactory.getLogger("VIAaaS MC") val webLogger = LoggerFactory.getLogger("VIAaaS Web") val viaaasLogger = LoggerFactory.getLogger("VIAaaS") -val secureRandom = if (VIAaaSConfig.useStrongRandom) SecureRandom.getInstanceStrong() else SecureRandom() +val secureRandom = SecureRandom() suspend fun resolveSrv(hostAndPort: HostAndPort): HostAndPort { if (hostAndPort.host.endsWith(".onion", ignoreCase = true)) return hostAndPort @@ -94,14 +91,7 @@ fun encryptRsa(publicKey: PublicKey, data: ByteArray) = Cipher.getInstance("RSA" it.doFinal(data) } -fun mcCfb8(key: ByteArray, mode: Int): Cipher { - val spec = SecretKeySpec(key, "AES") - val iv = IvParameterSpec(key) - return Cipher.getInstance("AES/CFB8/NoPadding").let { - it.init(mode, spec, iv) - it - } -} +fun aesKey(key: ByteArray) = SecretKeySpec(key, "AES") // https://github.com/VelocityPowered/Velocity/blob/6467335f74a7d1617512a55cc9acef5e109b51ac/api/src/main/java/com/velocitypowered/api/util/UuidUtils.java @OptIn(ExperimentalUnsignedTypes::class) @@ -223,11 +213,11 @@ fun sha512Hex(data: ByteArray): String { } fun eventLoopGroup(): EventLoopGroup { - if (VIAaaSConfig.isNativeTransportMc) { - if (Epoll.isAvailable()) return EpollEventLoopGroup() - if (KQueue.isAvailable()) return KQueueEventLoopGroup() + return when { + Epoll.isAvailable() -> EpollEventLoopGroup() + KQueue.isAvailable() -> KQueueEventLoopGroup() + else -> NioEventLoopGroup() } - return NioEventLoopGroup() } fun channelServerSocketFactory(eventLoop: EventLoopGroup): ChannelFactory { diff --git a/src/main/kotlin/com/viaversion/aas/config/VIAaaSConfig.kt b/src/main/kotlin/com/viaversion/aas/config/VIAaaSConfig.kt index 4361a2f..9dd7a80 100644 --- a/src/main/kotlin/com/viaversion/aas/config/VIAaaSConfig.kt +++ b/src/main/kotlin/com/viaversion/aas/config/VIAaaSConfig.kt @@ -1,8 +1,9 @@ package com.viaversion.aas.config +import com.viaversion.aas.secureRandom import com.viaversion.viaversion.util.Config import java.io.File -import java.security.SecureRandom +import java.net.URI import java.util.* object VIAaaSConfig : Config(File("config/viaaas.yml")) { @@ -10,25 +11,32 @@ object VIAaaSConfig : Config(File("config/viaaas.yml")) { reloadConfig() } - override fun getUnsupportedOptions() = emptyList().toMutableList() + override fun getUnsupportedOptions() = emptyList() override fun getDefaultConfigURL() = VIAaaSConfig::class.java.classLoader.getResource("viaaas.yml")!! override fun handleConfig(map: MutableMap) { + // Migration from older config versions if (map["jwt-secret"]?.toString().isNullOrBlank()) { - map["jwt-secret"] = Base64.getEncoder().encodeToString(ByteArray(64) - .also { SecureRandom().nextBytes(it) }) + map["jwt-secret"] = Base64.getEncoder() + .encodeToString(ByteArray(64) + .also { secureRandom.nextBytes(it) }) } if (map["host-name"] is String) { map["host-name"] = map["host-name"].toString().split(',').map { it.trim() } } + + val oldSocks = map.remove("backend-socks5-proxy-address") + val oldSocksPort = map.remove("backend-socks5-proxy-port") + if (oldSocks is String && oldSocks.isNotBlank()) { + map["backend-proxy"] = "socks5://$oldSocks:$oldSocksPort" + } } - val isNativeTransportMc: Boolean get() = this.getBoolean("native-transport-mc", true) + val port: Int get() = this.getInt("port", 25565) val bindAddress: String get() = this.getString("bind-address", "localhost")!! val hostName: List get() = this.get("host-name", List::class.java, listOf("viaaas.localhost"))!!.map { it.toString() } val mcRsaSize: Int get() = this.getInt("mc-rsa-size", 4096) - val useStrongRandom: Boolean get() = this.getBoolean("use-strong-random", true) val blockLocalAddress: Boolean get() = this.getBoolean("block-local-address", true) val requireHostName: Boolean get() = this.getBoolean("require-host-name", true) val defaultBackendPort: Int? get() = this.getInt("default-backend-port", 25565).let { if (it == -1) null else it } @@ -50,9 +58,6 @@ object VIAaaSConfig : Config(File("config/viaaas.yml")) { val rateLimitWs: Double get() = this.getDouble("rate-limit-ws", 1.0) val rateLimitConnectionMc: Double get() = this.getDouble("rate-limit-connection-mc", 10.0) val listeningWsLimit: Int get() = this.getInt("listening-ws-limit", 16) - val backendSocks5ProxyAddress: String? - get() = this.getString("backend-socks5-proxy-address", "")!!.ifEmpty { null } - val backendSocks5ProxyPort: Int get() = this.getInt("backend-socks5-proxy-port", 9050) val jwtSecret: String get() = this.getString("jwt-secret", null).let { if (it.isNullOrBlank()) throw IllegalStateException("invalid jwt-secret") else it @@ -61,4 +66,6 @@ object VIAaaSConfig : Config(File("config/viaaas.yml")) { val faviconUrl: String? get() = this.getString("favicon-url", "")!!.filter { !it.isWhitespace() }.ifEmpty { null } val maxPlayers: Int? get() = this.getInt("max-players", 20).let { if (it == -1) null else it } + val backendProxy: URI? + get() = this.getString("backend-proxy", "").let { if (it.isNullOrEmpty()) null else URI.create(it) } } diff --git a/src/main/kotlin/com/viaversion/aas/handler/BackEndInit.kt b/src/main/kotlin/com/viaversion/aas/handler/BackEndInit.kt index ba5367f..a2088dd 100644 --- a/src/main/kotlin/com/viaversion/aas/handler/BackEndInit.kt +++ b/src/main/kotlin/com/viaversion/aas/handler/BackEndInit.kt @@ -13,7 +13,7 @@ class BackEndInit(val connectionData: ConnectionData) : ChannelInitializer pipe.addFirst(Socks5ProxyHandler(socket, user, pass)) + "socks4" -> pipe.addFirst(Socks4ProxyHandler(socket, user)) + "http" -> pipe.addFirst(if (user != null) HttpProxyHandler(socket, user, pass) else HttpProxyHandler(socket)) + } } } diff --git a/src/main/kotlin/com/viaversion/aas/handler/autoprotocol/ProtocolDetector.kt b/src/main/kotlin/com/viaversion/aas/handler/autoprotocol/ProtocolDetector.kt index eab69ef..4b6ec6d 100644 --- a/src/main/kotlin/com/viaversion/aas/handler/autoprotocol/ProtocolDetector.kt +++ b/src/main/kotlin/com/viaversion/aas/handler/autoprotocol/ProtocolDetector.kt @@ -10,7 +10,7 @@ import com.viaversion.aas.codec.packet.handshake.Handshake import com.viaversion.aas.codec.packet.status.StatusRequest import com.viaversion.aas.handler.ConnectionData import com.viaversion.aas.handler.MinecraftHandler -import com.viaversion.aas.handler.addSocks5 +import com.viaversion.aas.handler.addProxyHandler import com.viaversion.aas.send import com.viaversion.viaversion.api.protocol.packet.State import com.viaversion.viaversion.api.protocol.version.ProtocolVersion @@ -44,7 +44,7 @@ object ProtocolDetector { state = ProtocolDetectionState(future), frontVer = -1 ) - channel.pipeline().also { addSocks5(it) } + channel.pipeline().also { addProxyHandler(it) } .addLast("timeout", ReadTimeoutHandler(30, TimeUnit.SECONDS)) .addLast("frame", FrameCodec()) .addLast("mc", MinecraftCodec()) diff --git a/src/main/kotlin/com/viaversion/aas/handler/state/LoginState.kt b/src/main/kotlin/com/viaversion/aas/handler/state/LoginState.kt index a5aa936..a11a30a 100644 --- a/src/main/kotlin/com/viaversion/aas/handler/state/LoginState.kt +++ b/src/main/kotlin/com/viaversion/aas/handler/state/LoginState.kt @@ -19,7 +19,6 @@ import kotlinx.coroutines.future.await import kotlinx.coroutines.launch import java.util.* import java.util.concurrent.CompletableFuture -import javax.crypto.Cipher class LoginState : MinecraftConnectionState { val callbackPlayerId = CompletableFuture() @@ -120,11 +119,9 @@ class LoginState : MinecraftConnectionState { val cryptoResponse = CryptoResponse() cryptoResponse.encryptedKey = encryptRsa(backPublicKey, backKey) cryptoResponse.encryptedToken = encryptRsa(backPublicKey, backToken) - val backAesEn = mcCfb8(backKey, Cipher.ENCRYPT_MODE) - val backAesDe = mcCfb8(backKey, Cipher.DECRYPT_MODE) forward(frontHandler, cryptoResponse, true) - backChan.pipeline().addBefore("frame", "crypto", CryptoCodec(backAesEn, backAesDe)) + backChan.pipeline().addBefore("frame", "crypto", CryptoCodec(aesKey(backKey), aesKey(backKey))) } catch (e: Exception) { frontHandler.data.frontChannel.pipeline().fireExceptionCaught(e) } @@ -138,9 +135,8 @@ class LoginState : MinecraftConnectionState { if (!decryptedToken.contentEquals(frontToken)) throw StacklessException("Invalid verification token!") - val aesEn = mcCfb8(frontKey, Cipher.ENCRYPT_MODE) - val aesDe = mcCfb8(frontKey, Cipher.DECRYPT_MODE) - handler.data.frontChannel.pipeline().addBefore("frame", "crypto", CryptoCodec(aesEn, aesDe)) + handler.data.frontChannel.pipeline() + .addBefore("frame", "crypto", CryptoCodec(aesKey(frontKey), aesKey(frontKey))) generateServerHash(frontServerId, frontKey, AspirinServer.mcCryptoKey.public) } diff --git a/src/main/resources/viaaas.yml b/src/main/resources/viaaas.yml index b8187eb..b0c74d4 100644 --- a/src/main/resources/viaaas.yml +++ b/src/main/resources/viaaas.yml @@ -4,43 +4,38 @@ ####################### # ###### -# Network +# Minecraft networking ###### -# Port used for binding Minecraft port +# Port used for binding port: 25565 # Address to bind bind-address: localhost -# Use Netty native transport for Minecraft connections when available. -native-transport-mc: true -# Address of SOCKS5 proxy used for connecting to backend servers. Empty to disable. +# Proxy used to connect to backend servers +# Example: socks5://localhost:9050, socks4://localhost:9050, http://foo:bar@localhost:9080 +backend-proxy: '' +# Migrated to backend-proxy backend-socks5-proxy-address: '' -# Port of SOCKS5 proxy used for connecting to backend servers. backend-socks5-proxy-port: 9050 # ###### # Crypto ###### -# Sets the RSA key size used by client for encrypting the AES symmetric key when using online mode. +# Sets the RSA key size used for encrypting the AES symmetric key. # Minecraft default is 1024. See https://stackoverflow.com/questions/1904516/is-1024-bit-rsa-secure mc-rsa-size: 4096 -# Use SecureRandom.getInstanceStrong(). May block if there's not enough entropy -# See https://wiki.archlinux.org/index.php/Rng-tools -use-strong-random: false # ###### # VIAaaS virtual hosts options ###### # Requires virtual host to contain the value from "host-name" as a suffix. -# A false value will allow virtual hosts with no suffix, connecting to the virtual host sent by client. +# A false value will allow virtual hosts with no suffix, connecting to the hostname sent by client. # A false value could be used for transparent proxying or for MiTM. require-host-name: true -# Host names of this instance, that will be used in the virtual host as a suffix. +# Host names of this instance. Will be used as a suffix. host-name: - viaaas.localhost - via.localhost - via.localho.st -# Requires online mode for front-end connections. May be useful for stopping bots. -force-online-mode: false # Default port to be used when connecting to the backend server. # Use -1 to reuse the port sent by client, useful for transparent proxying. default-backend-port: 25565 @@ -48,9 +43,9 @@ default-backend-port: 25565 ###### # Address filtering ###### -# Blocks backend connection to local addresses (localhost, 0.0.0.0, ::1, 127.(...), 10.(...), etc). +# Blocks backend connection to local addresses (localhost, 0.0.0.0, 10.(...), etc). block-local-address: true -# If some server is in this list, it will be blocked. This has priority over allowed-back-addresses. +# If some server is in this list, it will be blocked. blocked-back-addresses: - "*.hypixel.net" # Only allows the backend address if it matches an address in this list. @@ -82,6 +77,8 @@ listening-ws-limit: 10 ##### # Favicon URL to use in disconnection messages. Should use "data:image/png;base64," and be a 64x64 PNG favicon-url: '' +# Requires online mode for front-end connections. May be useful for stopping bots. +force-online-mode: false # Max players to allow connection. Use -1 to not limit max-players: 20 #