moved some code, authenticate online mode before connecting to backend

This commit is contained in:
creeper123123321 2021-02-16 13:57:05 -03:00
parent 33b40e6eea
commit e14f3eb121
11 changed files with 185 additions and 152 deletions

View File

@ -2,7 +2,10 @@ package com.github.creeper123123321.viaaas
import com.github.creeper123123321.viaaas.config.VIAaaSConfig
import com.google.common.base.Preconditions
import com.google.common.net.UrlEscapers
import com.google.common.primitives.Ints
import com.google.gson.JsonObject
import io.ktor.client.request.*
import io.netty.buffer.ByteBuf
import io.netty.channel.Channel
import io.netty.handler.codec.DecoderException
@ -135,4 +138,20 @@ fun writeFlushClose(ch: Channel, obj: Any) {
val secureRandom = if (VIAaaSConfig.useStrongRandom) SecureRandom.getInstanceStrong() else SecureRandom()
fun readableToByteArray(byteBuf: ByteBuf) = ByteArray(byteBuf.readableBytes()).also { byteBuf.readBytes(it) }
fun readableToByteArray(byteBuf: ByteBuf) = ByteArray(byteBuf.readableBytes()).also { byteBuf.readBytes(it) }
suspend fun hasJoined(username: String, hash: String): JsonObject {
return httpClient.get(
"https://sessionserver.mojang.com/session/minecraft/hasJoined?username=" +
UrlEscapers.urlFormParameterEscaper().escape(username) +
"&serverId=$hash"
) ?: throw IllegalArgumentException("Couldn't authenticate with session servers")
}
fun generate128Bits() = ByteArray(16).also { secureRandom.nextBytes(it) }
fun generateServerId() = "VIAaaS" + ByteArray(10).let {
secureRandom.nextBytes(it)
Base64.getEncoder().withoutPadding().encodeToString(it)
// https://developer.mozilla.org/en-US/docs/Glossary/Base64 133% of original
}

View File

@ -1,6 +1,5 @@
package com.github.creeper123123321.viaaas.command.sub
import com.github.creeper123123321.viaaas.command.VIAaaSConsole
import com.github.creeper123123321.viaaas.handler.MinecraftHandler
import us.myles.ViaVersion.api.Via
import us.myles.ViaVersion.api.command.ViaCommandSender
@ -23,8 +22,7 @@ object ConnectionsSubCommand : ViaSubCommand() {
}
val pAddr =
it.channel?.pipeline()?.get(MinecraftHandler::class.java)?.other?.remoteAddress()
val pName = it.channel?.pipeline()?.get(MinecraftHandler::class.java)?.data?.frontName
p0.sendMessage("$pAddr $pVer ($pName) -> $backVer ($backName) $backAddr")
p0.sendMessage("$pAddr $pVer -> $backVer ($backName) $backAddr")
}
return true
}

View File

@ -21,7 +21,7 @@ class BackEndInit(val connectionData: ConnectionData) : ChannelInitializer<Chann
.addLast("via-codec", ViaCodec(user))
.addLast("mc", MinecraftCodec())
.also {
if (connectionData.backVer == null) {
if (connectionData.viaBackServerVer == null) {
it.addLast("protocol-detector", ProtocolDetectorHandler(connectionData))
}
}

View File

@ -8,11 +8,8 @@ class ConnectionData(
val frontChannel: Channel,
var backChannel: Channel? = null,
var state: MinecraftConnectionState = HandshakeState(),
var frontOnline: Boolean? = null, // todo
var frontName: String? = null,
var backName: String? = null,
var frontVer: Int? = null,
var backVer: Int? = null,
var viaBackServerVer: Int? = null,
) {
val frontHandler get() = frontChannel.pipeline().get(MinecraftHandler::class.java)
val backHandler get() = backChannel?.pipeline()?.get(MinecraftHandler::class.java)

View File

@ -32,9 +32,9 @@ class ProtocolDetectorHandler(val connectionData: ConnectionData) : ChannelDuple
ProtocolDetector.detectVersion(ctx.channel().remoteAddress() as InetSocketAddress)
.whenComplete { protocol, _ ->
if (protocol != null && protocol.version != -1) {
connectionData.backVer = protocol.version
connectionData.viaBackServerVer = protocol.version
} else {
connectionData.backVer = 47 // fallback
connectionData.viaBackServerVer = 47 // fallback
}
ctx.pipeline().remove(this)

View File

@ -1,36 +1,15 @@
package com.github.creeper123123321.viaaas.handler.state
import com.github.creeper123123321.viaaas.*
import com.github.creeper123123321.viaaas.packet.handshake.Handshake
import com.github.creeper123123321.viaaas.packet.Packet
import com.github.creeper123123321.viaaas.VIAaaSAddress
import com.github.creeper123123321.viaaas.config.VIAaaSConfig
import com.github.creeper123123321.viaaas.handler.BackEndInit
import com.github.creeper123123321.viaaas.handler.MinecraftHandler
import com.github.creeper123123321.viaaas.handler.forward
import io.netty.bootstrap.Bootstrap
import io.netty.channel.ChannelFuture
import com.github.creeper123123321.viaaas.mcLogger
import com.github.creeper123123321.viaaas.packet.Packet
import com.github.creeper123123321.viaaas.packet.handshake.Handshake
import io.netty.channel.ChannelHandlerContext
import io.netty.channel.ChannelOption
import io.netty.channel.socket.SocketChannel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import us.myles.ViaVersion.packets.State
import java.net.InetAddress
import java.net.InetSocketAddress
class HandshakeState : MinecraftConnectionState {
fun connectBack(handler: MinecraftHandler, socketAddr: InetSocketAddress): ChannelFuture {
return Bootstrap()
.handler(BackEndInit(handler.data))
.channelFactory(channelSocketFactory())
.group(handler.data.frontChannel.eventLoop())
.option(ChannelOption.IP_TOS, 0x18)
.option(ChannelOption.TCP_NODELAY, true)
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 15_000) // Half of mc timeout
.connect(socketAddr)
}
override val state: State
get() = State.HANDSHAKE
@ -55,55 +34,30 @@ class HandshakeState : MinecraftConnectionState {
VIAaaSConfig.defaultBackendPort
}
handler.data.backVer = backProto
handler.data.frontOnline = parsed.online
if (VIAaaSConfig.forceOnlineMode) handler.data.frontOnline = true
handler.data.backName = parsed.username
handler.data.viaBackServerVer = backProto
var frontOnline = parsed.online
if (VIAaaSConfig.forceOnlineMode) frontOnline = true
(handler.data.state as? LoginState)?.also {
it.frontOnline = frontOnline
it.backName = parsed.username
it.backAddress = packet.address to packet.port
}
val playerAddr = handler.data.frontHandler.remoteAddress
mcLogger.info("Connecting ${handler.data.state.state} $playerAddr (${handler.data.frontVer}) -> ${packet.address}:${packet.port} ($backProto)")
mcLogger.info(
"Handshake: ${handler.data.state.state} $playerAddr (${handler.data.frontVer}," +
" O: ${
frontOnline.toString().substring(0, 1)
}) -> ${packet.address}:${packet.port} (${backProto ?: "AUTO"})"
)
if (!hadHostname && VIAaaSConfig.requireHostName) {
throw UnsupportedOperationException("This VIAaaS instance requires you to use the hostname")
}
handler.data.frontChannel.setAutoRead(false)
GlobalScope.launch(Dispatchers.IO) {
try {
val srvResolved = resolveSrv(packet.address, packet.port)
packet.address = srvResolved.first
packet.port = srvResolved.second
val socketAddr = InetSocketAddress(InetAddress.getByName(packet.address), packet.port)
if (checkLocalAddress(socketAddr.address)
|| matchesAddress(socketAddr, VIAaaSConfig.blockedBackAddresses)
|| !matchesAddress(socketAddr, VIAaaSConfig.allowedBackAddresses)
) {
throw SecurityException("Not allowed")
}
val future = connectBack(handler, socketAddr)
future.addListener {
if (it.isSuccess) {
mcLogger.info("Connected ${handler.remoteAddress} -> $socketAddr")
handler.data.backChannel = future.channel() as SocketChannel
forward(handler, packet, true)
handler.data.frontChannel.setAutoRead(true)
} else {
// We're in the event loop
handler.disconnect("Couldn't connect: " + it.cause().toString())
}
}
} catch (e: Exception) {
handler.data.frontChannel.eventLoop().submit {
handler.disconnect("Couldn't connect: $e")
}
}
if (packet.nextState == State.STATUS) {
connectBack(handler, packet.address, packet.port, packet.nextState) {}
}
}

View File

@ -3,13 +3,11 @@ package com.github.creeper123123321.viaaas.handler.state
import com.github.creeper123123321.viaaas.*
import com.github.creeper123123321.viaaas.codec.CompressionCodec
import com.github.creeper123123321.viaaas.codec.CryptoCodec
import com.github.creeper123123321.viaaas.packet.*
import com.github.creeper123123321.viaaas.packet.login.*
import com.github.creeper123123321.viaaas.handler.MinecraftHandler
import com.github.creeper123123321.viaaas.handler.forward
import com.google.common.net.UrlEscapers
import com.github.creeper123123321.viaaas.packet.*
import com.github.creeper123123321.viaaas.packet.login.*
import com.google.gson.Gson
import com.google.gson.JsonObject
import io.ktor.client.request.*
import io.netty.channel.ChannelHandlerContext
import kotlinx.coroutines.Dispatchers
@ -19,11 +17,17 @@ import us.myles.ViaVersion.packets.State
import java.util.*
import java.util.concurrent.CompletableFuture
import javax.crypto.Cipher
import io.netty.channel.Channel
class LoginState : MinecraftConnectionState {
val callbackPlayerId = CompletableFuture<String>()
lateinit var frontToken: ByteArray
lateinit var frontServerId: String
var frontOnline: Boolean? = null
lateinit var frontName: String
lateinit var backAddress: Pair<String, Int>
var backName: String? = null
var started = false
override val state: State
get() = State.LOGIN
@ -67,71 +71,58 @@ class LoginState : MinecraftConnectionState {
}
}
fun authenticateOnlineFront(frontHandler: MinecraftHandler) {
val id = "VIAaaS" + ByteArray(10).let {
secureRandom.nextBytes(it)
Base64.getEncoder().withoutPadding().encodeToString(it)
// https://developer.mozilla.org/en-US/docs/Glossary/Base64 133% of original
}
fun authenticateOnlineFront(frontChannel: Channel) {
// We'll use non-vanilla server id, public key size and token size
val token = ByteArray(16).let {
secureRandom.nextBytes(it)
it
}
frontToken = token
frontServerId = id
frontToken = generate128Bits()
frontServerId = generateServerId()
val cryptoRequest = CryptoRequest()
cryptoRequest.serverId = id
cryptoRequest.serverId = frontServerId
cryptoRequest.publicKey = mcCryptoKey.public
cryptoRequest.token = token
cryptoRequest.token = frontToken
send(frontHandler.data.frontChannel, cryptoRequest, true)
send(frontChannel, cryptoRequest, true)
}
fun handleCryptoRequest(handler: MinecraftHandler, cryptoRequest: CryptoRequest) {
val data = handler.data
val backServerId = cryptoRequest.serverId
val backPublicKey = cryptoRequest.publicKey
val backToken = cryptoRequest.token
if (data.frontOnline == null) {
authenticateOnlineFront(handler)
if (frontOnline == null) {
authenticateOnlineFront(handler.data.frontChannel)
}
val backKey = ByteArray(16).let {
secureRandom.nextBytes(it)
it
}
val backHash = generateServerHash(backServerId, backKey, backPublicKey)
callbackPlayerId.whenComplete { playerId, e ->
if (e != null) return@whenComplete
val frontHandler = handler.data.frontHandler
val backChan = handler.data.backChannel!!
GlobalScope.launch(Dispatchers.IO) {
try {
val sessionJoin = viaWebServer.requestSessionJoin(
parseUndashedId(playerId),
handler.data.backName!!,
backHash,
frontHandler.remoteAddress!!, // Frontend handler
backPublicKey
)
val backKey = generate128Bits()
val backHash = generateServerHash(backServerId, backKey, backPublicKey)
val backChan = handler.data.backChannel!!
sessionJoin.whenCompleteAsync({ _, throwable ->
viaWebServer.requestSessionJoin(
parseUndashedId(playerId),
backName!!,
backHash,
frontHandler.remoteAddress!!,
backPublicKey
).whenCompleteAsync({ _, throwable ->
if (throwable != null) {
frontHandler.data.backHandler!!.disconnect("Online mode error: $throwable")
} else {
val cryptoResponse = CryptoResponse()
cryptoResponse.encryptedKey = encryptRsa(backPublicKey, backKey)
cryptoResponse.encryptedToken = encryptRsa(backPublicKey, backToken)
forward(frontHandler, cryptoResponse, true)
val backAesEn = mcCfb8(backKey, Cipher.ENCRYPT_MODE)
val backAesDe = mcCfb8(backKey, Cipher.DECRYPT_MODE)
backChan.pipeline().addBefore("frame", "crypto", CryptoCodec(backAesDe, backAesEn))
return@whenCompleteAsync
}
val cryptoResponse = CryptoResponse()
cryptoResponse.encryptedKey = encryptRsa(backPublicKey, backKey)
cryptoResponse.encryptedToken = encryptRsa(backPublicKey, backToken)
forward(frontHandler, cryptoResponse, true)
val backAesEn = mcCfb8(backKey, Cipher.ENCRYPT_MODE)
val backAesDe = mcCfb8(backKey, Cipher.DECRYPT_MODE)
backChan.pipeline().addBefore("frame", "crypto", CryptoCodec(backAesDe, backAesEn))
}, backChan.eventLoop())
} catch (e: Exception) {
frontHandler.disconnect("Online mode error: $e")
@ -143,14 +134,12 @@ class LoginState : MinecraftConnectionState {
fun handleCryptoResponse(handler: MinecraftHandler, cryptoResponse: CryptoResponse) {
val frontHash = let {
val frontKey = decryptRsa(mcCryptoKey.private, cryptoResponse.encryptedKey)
// RSA token - wat??? why is it encrypted with RSA if it was sent unencrypted?
val decryptedToken = decryptRsa(mcCryptoKey.private, cryptoResponse.encryptedToken)
if (!decryptedToken.contentEquals(frontToken)) throw IllegalStateException("invalid token!")
if (!decryptedToken.contentEquals(frontToken)) throw IllegalStateException("Invalid token!")
val aesEn = mcCfb8(frontKey, Cipher.ENCRYPT_MODE)
val aesDe = mcCfb8(frontKey, Cipher.DECRYPT_MODE)
handler.data.frontChannel.pipeline().addBefore("frame", "crypto", CryptoCodec(aesDe, aesEn))
generateServerHash(frontServerId, frontKey, mcCryptoKey.public)
@ -159,14 +148,9 @@ class LoginState : MinecraftConnectionState {
handler.data.frontChannel.setAutoRead(false)
GlobalScope.launch(Dispatchers.IO) {
try {
val profile = httpClient.get<JsonObject?>(
"https://sessionserver.mojang.com/session/minecraft/hasJoined?username=" +
UrlEscapers.urlFormParameterEscaper().escape(handler.data.frontName!!) +
"&serverId=$frontHash"
) ?: throw IllegalArgumentException("Couldn't authenticate with session servers")
val profile = hasJoined(frontName, frontHash)
val id = profile.get("id")!!.asString
mcLogger.info("Validated front-end session: ${handler.data.frontName} $id")
callbackPlayerId.complete(id)
} catch (e: Exception) {
callbackPlayerId.completeExceptionally(e)
@ -177,26 +161,34 @@ class LoginState : MinecraftConnectionState {
fun handleLoginStart(handler: MinecraftHandler, loginStart: LoginStart) {
if (loginStart.username.length > 16) throw badLength
if (handler.data.frontName != null) throw IllegalStateException("Login already started")
if (started) throw IllegalStateException("Login already started")
started = true
handler.data.frontName = loginStart.username
handler.data.backName = handler.data.backName ?: handler.data.frontName
frontName = loginStart.username
backName = backName ?: frontName
loginStart.username = handler.data.backName!!
callbackPlayerId.whenComplete { _, e -> if (e != null) disconnect(handler, "Profile error: $e") }
if (handler.data.frontOnline == false) {
callbackPlayerId.complete(generateOfflinePlayerUuid(handler.data.frontName!!).toString().replace("-", ""))
val connect = {
connectBack(handler, backAddress.first, backAddress.second, State.LOGIN) {
loginStart.username = backName!!
send(handler.data.backChannel!!, loginStart, true)
}
}
if (handler.data.frontOnline == true) { // forced
authenticateOnlineFront(handler.data.backHandler!!)
callbackPlayerId.whenComplete { _, e ->
if (e == null) forward(handler, loginStart, true)
callbackPlayerId.whenComplete { id, e ->
if (e != null) {
disconnect(handler, "Profile error: $e")
} else {
mcLogger.info("Login: ${handler.remoteAddress} $frontName $id")
if (frontOnline != null) {
connect()
}
}
} else {
forward(handler, loginStart)
}
when (frontOnline) {
false -> callbackPlayerId.complete(generateOfflinePlayerUuid(frontName).toString().replace("-", ""))
true -> authenticateOnlineFront(handler.data.frontChannel) // forced
null -> connect() // Connect then authenticate
}
}

View File

@ -37,7 +37,7 @@ object PlayState : MinecraftConnectionState {
Type.STRING.read(Unpooled.wrappedBuffer(pluginMessage.data))
} + " (VIAaaS C: ${ProtocolVersion.getProtocol(handler.data.frontVer!!)} S: ${
ProtocolVersion.getProtocol(
handler.data.backVer!!
handler.data.viaBackServerVer!!
)
})"

View File

@ -38,7 +38,7 @@ object StatusState : MinecraftConnectionState {
it.addProperty(
"name",
"§9VIAaaS§r (C: §7${ProtocolVersion.getProtocol(handler.data.frontVer!!)}§r S: §7${
ProtocolVersion.getProtocol(handler.data.backVer!!)
ProtocolVersion.getProtocol(handler.data.viaBackServerVer!!)
}§r)"
)
})

View File

@ -0,0 +1,73 @@
package com.github.creeper123123321.viaaas.handler.state
import com.github.creeper123123321.viaaas.*
import com.github.creeper123123321.viaaas.config.VIAaaSConfig
import com.github.creeper123123321.viaaas.handler.BackEndInit
import com.github.creeper123123321.viaaas.handler.MinecraftHandler
import com.github.creeper123123321.viaaas.handler.forward
import com.github.creeper123123321.viaaas.packet.handshake.Handshake
import io.netty.bootstrap.Bootstrap
import io.netty.channel.ChannelFuture
import io.netty.channel.ChannelFutureListener
import io.netty.channel.ChannelOption
import io.netty.channel.socket.SocketChannel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import us.myles.ViaVersion.packets.State
import java.net.InetAddress
import java.net.InetSocketAddress
fun createBackChannel(handler: MinecraftHandler, socketAddr: InetSocketAddress, state: State): ChannelFuture {
return Bootstrap()
.handler(BackEndInit(handler.data))
.channelFactory(channelSocketFactory())
.group(handler.data.frontChannel.eventLoop())
.option(ChannelOption.IP_TOS, 0x18)
.option(ChannelOption.TCP_NODELAY, true)
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 15_000) // Half of mc timeout
.connect(socketAddr)
.addListener(ChannelFutureListener {
if (it.isSuccess) {
mcLogger.info("Connected ${handler.remoteAddress} -> $socketAddr")
handler.data.backChannel = it.channel() as SocketChannel
val packet = Handshake()
packet.nextState = state
packet.protocolId = handler.data.frontVer!!
packet.address = socketAddr.hostString
packet.port = socketAddr.port
forward(handler, packet, true)
handler.data.frontChannel.setAutoRead(true)
} else {
// We're in the event loop
handler.disconnect("Couldn't connect: " + it.cause().toString())
}
})
}
fun connectBack(handler: MinecraftHandler, address: String, port: Int, state: State, success: () -> Unit) {
handler.data.frontChannel.setAutoRead(false)
GlobalScope.launch(Dispatchers.IO) {
try {
val srvResolved = resolveSrv(address, port)
val socketAddr = InetSocketAddress(InetAddress.getByName(srvResolved.first), srvResolved.second)
if (checkLocalAddress(socketAddr.address)
|| matchesAddress(socketAddr, VIAaaSConfig.blockedBackAddresses)
|| !matchesAddress(socketAddr, VIAaaSConfig.allowedBackAddresses)
) {
throw SecurityException("Not allowed")
}
createBackChannel(handler, socketAddr, state).addListener { if (it.isSuccess) success() }
} catch (e: Exception) {
handler.data.frontChannel.eventLoop().submit {
handler.disconnect("Couldn't connect: $e")
}
}
}
}

View File

@ -6,7 +6,7 @@ import us.myles.ViaVersion.protocols.base.VersionProvider
object AspirinVersionProvider : VersionProvider() {
override fun getServerProtocol(connection: UserConnection): Int {
val ver = connection.channel!!.pipeline().get(MinecraftHandler::class.java).data.backVer
val ver = connection.channel!!.pipeline().get(MinecraftHandler::class.java).data.viaBackServerVer
if (ver != null) return ver
return super.getServerProtocol(connection)
}