mirror of
https://github.com/ViaVersion/VIAaaS.git
synced 2025-01-09 19:48:37 +01:00
use native velocity crypto, http+socks4 proxy support
This commit is contained in:
parent
8f94c736b4
commit
8da87cb070
@ -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<ByteBuf, ByteBuf> {
|
||||
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<Object> 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<Object> 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();
|
||||
}
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -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<ServerSocketChannel> {
|
||||
|
@ -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<String>().toMutableList()
|
||||
override fun getUnsupportedOptions() = emptyList<String>()
|
||||
override fun getDefaultConfigURL() = VIAaaSConfig::class.java.classLoader.getResource("viaaas.yml")!!
|
||||
override fun handleConfig(map: MutableMap<String, Any>) {
|
||||
// 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<String>
|
||||
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) }
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ class BackEndInit(val connectionData: ConnectionData) : ChannelInitializer<Chann
|
||||
override fun initChannel(ch: Channel) {
|
||||
val user = UserConnectionImpl(ch, true)
|
||||
ProtocolPipelineImpl(user)
|
||||
ch.pipeline().also { addSocks5(it) }
|
||||
ch.pipeline().also { addProxyHandler(it) }
|
||||
// "crypto"
|
||||
.addLast("frame", FrameCodec())
|
||||
// compress
|
||||
|
@ -1,5 +1,6 @@
|
||||
package com.viaversion.aas.handler
|
||||
|
||||
import com.viaversion.aas.AspirinServer
|
||||
import com.viaversion.aas.config.VIAaaSConfig
|
||||
import com.viaversion.aas.codec.packet.Packet
|
||||
import com.viaversion.aas.readRemainingBytes
|
||||
@ -9,6 +10,8 @@ import com.viaversion.viaversion.api.type.Type
|
||||
import io.netty.buffer.ByteBufAllocator
|
||||
import io.netty.buffer.Unpooled
|
||||
import io.netty.channel.ChannelPipeline
|
||||
import io.netty.handler.proxy.HttpProxyHandler
|
||||
import io.netty.handler.proxy.Socks4ProxyHandler
|
||||
import io.netty.handler.proxy.Socks5ProxyHandler
|
||||
import java.net.InetSocketAddress
|
||||
|
||||
@ -18,10 +21,17 @@ fun forward(handler: MinecraftHandler, packet: Packet, flush: Boolean = false) {
|
||||
|
||||
fun is17(handler: MinecraftHandler) = handler.data.frontVer!! <= ProtocolVersion.v1_7_6.version
|
||||
|
||||
fun addSocks5(pipe: ChannelPipeline) {
|
||||
val addr = VIAaaSConfig.backendSocks5ProxyAddress
|
||||
if (addr != null) {
|
||||
pipe.addFirst(Socks5ProxyHandler(InetSocketAddress(addr, VIAaaSConfig.backendSocks5ProxyPort)))
|
||||
fun addProxyHandler(pipe: ChannelPipeline) {
|
||||
val proxyUri = VIAaaSConfig.backendProxy
|
||||
if (proxyUri != null) {
|
||||
val socket = InetSocketAddress(AspirinServer.dnsResolver.resolve(proxyUri.host).get(), proxyUri.port)
|
||||
val user = proxyUri.userInfo?.substringBefore(':')
|
||||
val pass = proxyUri.userInfo?.substringAfter(':')
|
||||
when (proxyUri.scheme) {
|
||||
"socks5" -> 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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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())
|
||||
|
@ -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<UUID>()
|
||||
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
#
|
||||
|
Loading…
Reference in New Issue
Block a user