mirror of
https://github.com/ViaVersion/VIAaaS.git
synced 2025-02-19 02:11:50 +01:00
close #34 close #33 close #31, add option to allow local addresses and an option to require hostname
This commit is contained in:
parent
90fe7e80ce
commit
2bdc547b66
10
README.md
10
README.md
@ -1,7 +1,7 @@
|
|||||||
VIAaaS
|
VIAaaS
|
||||||
---
|
---
|
||||||
Idea: server.example.com._p25565._v1_12_2._otrue._uBACKUSERNAME.viaaas.example.com (default backend 25565 port and version
|
How to use: server.example.com._p25565._v1_12_2._uBACKUSERNAME.viaaas.example.com (similar to tor to web proxies)
|
||||||
default as auto, online-mode can be optional/required) (similar to tor to web proxies)
|
Default WS URL: wss://localhost:25543/ws
|
||||||
|
|
||||||
- TODO: _o option for disabling online mode only in front end, protocol auto detection
|
- TODO: _o option for disabling online mode only in front end, protocol auto detection
|
||||||
|
|
||||||
@ -28,9 +28,9 @@ Usage for online mode (may block your Mojang account):
|
|||||||
https://www.curseforge.com/minecraft/mc-mods/auth-me for reauthenticate the client.)
|
https://www.curseforge.com/minecraft/mc-mods/auth-me for reauthenticate the client.)
|
||||||
- You should set up a CORS Proxy (something like https://github.com/Rob--W/cors-anywhere) on local machine.
|
- You should set up a CORS Proxy (something like https://github.com/Rob--W/cors-anywhere) on local machine.
|
||||||
- Go to https://localhost:25543/auth.html, configure the CORS Proxy URL (something like http://localhost:8080/) and listen to
|
- Go to https://localhost:25543/auth.html, configure the CORS Proxy URL (something like http://localhost:8080/) and listen to
|
||||||
the username you're using to connect.
|
the username A that you're using to connect to the proxy.
|
||||||
- Add web page the account you used in _u parameter.
|
- Add web page the account B you used in _u parameter.
|
||||||
- Connect to mc.example.com._v1_8.viaaas._u(BACKUSERNAME).localhost
|
- Connect to mc.example.com._v1_8.viaaas._u(account B).localhost
|
||||||
- Approve the login
|
- Approve the login
|
||||||
- There are some information about Mojang password resetting: https://github.com/GeyserMC/Geyser/wiki/Common-Issues#mojang-resetting-account-credentials and https://mobile.twitter.com/MojangSupport/status/863697596350517248
|
- There are some information about Mojang password resetting: https://github.com/GeyserMC/Geyser/wiki/Common-Issues#mojang-resetting-account-credentials and https://mobile.twitter.com/MojangSupport/status/863697596350517248
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package com.github.creeper123123321.viaaas
|
package com.github.creeper123123321.viaaas
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf
|
import io.netty.buffer.ByteBuf
|
||||||
import io.netty.buffer.Unpooled
|
|
||||||
import io.netty.channel.Channel
|
import io.netty.channel.Channel
|
||||||
import io.netty.channel.ChannelHandlerContext
|
import io.netty.channel.ChannelHandlerContext
|
||||||
import io.netty.channel.ChannelInitializer
|
import io.netty.channel.ChannelInitializer
|
||||||
@ -25,12 +24,12 @@ object ChannelInit : ChannelInitializer<Channel>() {
|
|||||||
val user = UserConnection(ch)
|
val user = UserConnection(ch)
|
||||||
CloudPipeline(user)
|
CloudPipeline(user)
|
||||||
ch.pipeline().addLast("timeout", ReadTimeoutHandler(30, TimeUnit.SECONDS))
|
ch.pipeline().addLast("timeout", ReadTimeoutHandler(30, TimeUnit.SECONDS))
|
||||||
// "crypto"
|
// "crypto"
|
||||||
.addLast("frame", FrameCodec())
|
.addLast("frame", FrameCodec())
|
||||||
// "compress" / dummy "decompress"
|
// "compress" / dummy "decompress"
|
||||||
.addLast("flow-handler", FlowControlHandler())
|
.addLast("flow-handler", FlowControlHandler())
|
||||||
.addLast("via-codec", CloudViaCodec(user))
|
.addLast("via-codec", CloudViaCodec(user))
|
||||||
.addLast("handler", CloudMinecraftHandler(user, null, frontEnd = true))
|
.addLast("handler", CloudMinecraftHandler(user, null, frontEnd = true))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,22 +52,23 @@ class CloudCrypto(val cipherDecode: Cipher, var cipherEncode: Cipher) : MessageT
|
|||||||
class BackendInit(val user: UserConnection) : ChannelInitializer<Channel>() {
|
class BackendInit(val user: UserConnection) : ChannelInitializer<Channel>() {
|
||||||
override fun initChannel(ch: Channel) {
|
override fun initChannel(ch: Channel) {
|
||||||
ch.pipeline().addLast("timeout", ReadTimeoutHandler(30, TimeUnit.SECONDS))
|
ch.pipeline().addLast("timeout", ReadTimeoutHandler(30, TimeUnit.SECONDS))
|
||||||
// "crypto"
|
// "crypto"
|
||||||
.addLast("frame", FrameCodec())
|
.addLast("frame", FrameCodec())
|
||||||
// compress
|
// compress
|
||||||
.addLast("handler", CloudMinecraftHandler(user, null, frontEnd = false))
|
.addLast("handler", CloudMinecraftHandler(user, null, frontEnd = false))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class CloudCompressionCodec(val threshold: Int) : MessageToMessageCodec<ByteBuf, ByteBuf>() {
|
class CloudCompressionCodec(val threshold: Int) : MessageToMessageCodec<ByteBuf, ByteBuf>() {
|
||||||
// https://github.com/Gerrygames/ClientViaVersion/blob/master/src/main/java/de/gerrygames/the5zig/clientviaversion/netty/CompressionEncoder.java
|
// https://github.com/Gerrygames/ClientViaVersion/blob/master/src/main/java/de/gerrygames/the5zig/clientviaversion/netty/CompressionEncoder.java
|
||||||
private val inflater: Inflater = Inflater()// https://github.com/Gerrygames/ClientViaVersion/blob/master/src/main/java/de/gerrygames/the5zig/clientviaversion/netty/CompressionEncoder.java
|
private val inflater: Inflater =
|
||||||
|
Inflater()// https://github.com/Gerrygames/ClientViaVersion/blob/master/src/main/java/de/gerrygames/the5zig/clientviaversion/netty/CompressionEncoder.java
|
||||||
private val deflater: Deflater = Deflater()
|
private val deflater: Deflater = Deflater()
|
||||||
|
|
||||||
@Throws(Exception::class)
|
@Throws(Exception::class)
|
||||||
override fun encode(ctx: ChannelHandlerContext, input: ByteBuf, out: MutableList<Any>) {
|
override fun encode(ctx: ChannelHandlerContext, input: ByteBuf, out: MutableList<Any>) {
|
||||||
val frameLength = input.readableBytes()
|
val frameLength = input.readableBytes()
|
||||||
val outBuf = ctx.alloc().heapBuffer()
|
val outBuf = ctx.alloc().buffer()
|
||||||
try {
|
try {
|
||||||
if (frameLength < threshold) {
|
if (frameLength < threshold) {
|
||||||
outBuf.writeByte(0)
|
outBuf.writeByte(0)
|
||||||
@ -77,15 +77,12 @@ class CloudCompressionCodec(val threshold: Int) : MessageToMessageCodec<ByteBuf,
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
Type.VAR_INT.writePrimitive(outBuf, frameLength)
|
Type.VAR_INT.writePrimitive(outBuf, frameLength)
|
||||||
val inBytes = ByteArray(frameLength)
|
deflater.setInput(input.nioBuffer())
|
||||||
input.readBytes(inBytes)
|
|
||||||
deflater.setInput(inBytes, 0, frameLength)
|
|
||||||
deflater.finish()
|
deflater.finish()
|
||||||
while (!deflater.finished()) {
|
while (!deflater.finished()) {
|
||||||
outBuf.ensureWritable(8192)
|
outBuf.ensureWritable(8192)
|
||||||
val wIndex = outBuf.writerIndex()
|
val wIndex = outBuf.writerIndex()
|
||||||
outBuf.writerIndex(wIndex + deflater.deflate(outBuf.array(),
|
outBuf.writerIndex(wIndex + deflater.deflate(outBuf.nioBuffer(wIndex, outBuf.writableBytes())))
|
||||||
outBuf.arrayOffset() + wIndex, outBuf.writableBytes()))
|
|
||||||
}
|
}
|
||||||
out.add(outBuf.retain())
|
out.add(outBuf.retain())
|
||||||
} finally {
|
} finally {
|
||||||
@ -110,13 +107,16 @@ class CloudCompressionCodec(val threshold: Int) : MessageToMessageCodec<ByteBuf,
|
|||||||
throw DecoderException("Badly compressed packet - size of $outLength is larger than protocol maximum of 2097152")
|
throw DecoderException("Badly compressed packet - size of $outLength is larger than protocol maximum of 2097152")
|
||||||
}
|
}
|
||||||
|
|
||||||
val temp = ByteArray(input.readableBytes())
|
inflater.setInput(input.nioBuffer())
|
||||||
input.readBytes(temp)
|
val output = ctx.alloc().buffer(outLength, outLength)
|
||||||
inflater.setInput(temp)
|
try {
|
||||||
val output = ByteArray(outLength)
|
output.writerIndex(output.writerIndex() + inflater.inflate(
|
||||||
inflater.inflate(output)
|
output.nioBuffer(output.writerIndex(), output.writableBytes())))
|
||||||
out.add(Unpooled.wrappedBuffer(output))
|
out.add(output.retain())
|
||||||
inflater.reset()
|
} finally {
|
||||||
|
inflater.reset()
|
||||||
|
output.release()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,15 +19,17 @@ import kotlinx.coroutines.launch
|
|||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import us.myles.ViaVersion.api.data.StoredObject
|
import us.myles.ViaVersion.api.data.StoredObject
|
||||||
import us.myles.ViaVersion.api.data.UserConnection
|
import us.myles.ViaVersion.api.data.UserConnection
|
||||||
|
import us.myles.ViaVersion.api.protocol.ProtocolVersion
|
||||||
import us.myles.ViaVersion.api.type.Type
|
import us.myles.ViaVersion.api.type.Type
|
||||||
import us.myles.ViaVersion.exception.CancelCodecException
|
import us.myles.ViaVersion.exception.CancelCodecException
|
||||||
|
import us.myles.ViaVersion.packets.State
|
||||||
|
import java.math.BigInteger
|
||||||
import java.net.InetAddress
|
import java.net.InetAddress
|
||||||
import java.net.InetSocketAddress
|
import java.net.InetSocketAddress
|
||||||
import java.net.SocketAddress
|
import java.net.SocketAddress
|
||||||
import java.security.KeyFactory
|
import java.security.MessageDigest
|
||||||
import java.security.PrivateKey
|
import java.security.PrivateKey
|
||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
import java.security.spec.X509EncodedKeySpec
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import javax.crypto.Cipher
|
import javax.crypto.Cipher
|
||||||
import javax.crypto.spec.IvParameterSpec
|
import javax.crypto.spec.IvParameterSpec
|
||||||
@ -37,27 +39,30 @@ import javax.naming.directory.InitialDirContext
|
|||||||
|
|
||||||
val chLogger = LoggerFactory.getLogger("VIAaaS MC Handler")
|
val chLogger = LoggerFactory.getLogger("VIAaaS MC Handler")
|
||||||
|
|
||||||
class HandlerData(userConnection: UserConnection,
|
class HandlerData(
|
||||||
var state: MinecraftConnectionState,
|
userConnection: UserConnection,
|
||||||
var protocolId: Int? = null,
|
var state: MinecraftConnectionState,
|
||||||
var frontName: String? = null,
|
var protocolId: Int? = null,
|
||||||
var backName: String? = null,
|
var frontName: String? = null,
|
||||||
var backServerId: String? = null,
|
var backName: String? = null,
|
||||||
var backPublicKey: PublicKey? = null,
|
var backServerId: String? = null,
|
||||||
var backToken: ByteArray? = null,
|
var backPublicKey: PublicKey? = null,
|
||||||
var frontToken: ByteArray? = null,
|
var backToken: ByteArray? = null,
|
||||||
var frontId: String? = null
|
var frontToken: ByteArray? = null,
|
||||||
|
var frontId: String? = null
|
||||||
) : StoredObject(userConnection)
|
) : StoredObject(userConnection)
|
||||||
|
|
||||||
class CloudMinecraftHandler(val user: UserConnection,
|
class CloudMinecraftHandler(
|
||||||
var other: Channel?,
|
val user: UserConnection,
|
||||||
val frontEnd: Boolean) : SimpleChannelInboundHandler<ByteBuf>() {
|
var other: Channel?,
|
||||||
val data get() = user.get(HandlerData::class.java)
|
val frontEnd: Boolean
|
||||||
|
) : SimpleChannelInboundHandler<ByteBuf>() {
|
||||||
|
val data get() = user.get(HandlerData::class.java)!!
|
||||||
var address: SocketAddress? = null
|
var address: SocketAddress? = null
|
||||||
|
|
||||||
override fun channelRead0(ctx: ChannelHandlerContext, msg: ByteBuf) {
|
override fun channelRead0(ctx: ChannelHandlerContext, msg: ByteBuf) {
|
||||||
if (ctx.channel().isActive && !user.isPendingDisconnect && msg.isReadable) {
|
if (ctx.channel().isActive && !user.isPendingDisconnect && msg.isReadable) {
|
||||||
data!!.state.handleMessage(this, ctx, msg)
|
data.state.handleMessage(this, ctx, msg)
|
||||||
if (msg.isReadable) throw IllegalStateException("Remaining bytes!!!")
|
if (msg.isReadable) throw IllegalStateException("Remaining bytes!!!")
|
||||||
//other?.write(msg.retain())
|
//other?.write(msg.retain())
|
||||||
}
|
}
|
||||||
@ -65,14 +70,14 @@ class CloudMinecraftHandler(val user: UserConnection,
|
|||||||
|
|
||||||
override fun channelActive(ctx: ChannelHandlerContext) {
|
override fun channelActive(ctx: ChannelHandlerContext) {
|
||||||
address = ctx.channel().remoteAddress()
|
address = ctx.channel().remoteAddress()
|
||||||
if (data == null) {
|
if (user.get(HandlerData::class.java) == null) {
|
||||||
user.put(HandlerData(user, HandshakeState()))
|
user.put(HandlerData(user, HandshakeState()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun channelInactive(ctx: ChannelHandlerContext) {
|
override fun channelInactive(ctx: ChannelHandlerContext) {
|
||||||
other?.close()
|
other?.close()
|
||||||
data!!.state.onInactivated(this)
|
data.state.onInactivated(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun channelReadComplete(ctx: ChannelHandlerContext?) {
|
override fun channelReadComplete(ctx: ChannelHandlerContext?) {
|
||||||
@ -90,13 +95,15 @@ class CloudMinecraftHandler(val user: UserConnection,
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun disconnect(s: String) {
|
fun disconnect(s: String) {
|
||||||
data!!.state.disconnect(this, s)
|
data.state.disconnect(this, s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface MinecraftConnectionState {
|
interface MinecraftConnectionState {
|
||||||
fun handleMessage(handler: CloudMinecraftHandler, ctx: ChannelHandlerContext,
|
fun handleMessage(
|
||||||
msg: ByteBuf)
|
handler: CloudMinecraftHandler, ctx: ChannelHandlerContext,
|
||||||
|
msg: ByteBuf
|
||||||
|
)
|
||||||
|
|
||||||
fun disconnect(handler: CloudMinecraftHandler, msg: String) {
|
fun disconnect(handler: CloudMinecraftHandler, msg: String) {
|
||||||
chLogger.info("Disconnected ${handler.address}: $msg")
|
chLogger.info("Disconnected ${handler.address}: $msg")
|
||||||
@ -110,14 +117,17 @@ interface MinecraftConnectionState {
|
|||||||
|
|
||||||
class HandshakeState : MinecraftConnectionState {
|
class HandshakeState : MinecraftConnectionState {
|
||||||
override fun handleMessage(handler: CloudMinecraftHandler, ctx: ChannelHandlerContext, msg: ByteBuf) {
|
override fun handleMessage(handler: CloudMinecraftHandler, ctx: ChannelHandlerContext, msg: ByteBuf) {
|
||||||
if (!handler.frontEnd || Type.VAR_INT.readPrimitive(msg) != 0) throw IllegalArgumentException("Invalid packet ID!")
|
val packet = PacketRegistry.decode(
|
||||||
handler.data!!.protocolId = Type.VAR_INT.readPrimitive(msg)
|
msg,
|
||||||
val backAddr = Type.STRING.read(msg)
|
ProtocolVersion.getProtocol(handler.user.protocolInfo!!.serverProtocolVersion),
|
||||||
val backPort = Type.UNSIGNED_SHORT.read(msg)
|
State.HANDSHAKE,
|
||||||
val nextAddr = Type.VAR_INT.readPrimitive(msg)
|
handler.frontEnd
|
||||||
when (nextAddr) {
|
)
|
||||||
1 -> handler.data!!.state = StatusState
|
if (packet !is HandshakePacket) throw IllegalArgumentException("Invalid packet!")
|
||||||
2 -> handler.data!!.state = LoginState
|
handler.data.protocolId = packet.protocolId
|
||||||
|
when (packet.nextState.ordinal) {
|
||||||
|
1 -> handler.data.state = StatusState
|
||||||
|
2 -> handler.data.state = LoginState
|
||||||
else -> throw IllegalStateException("Invalid next state")
|
else -> throw IllegalStateException("Invalid next state")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,13 +135,13 @@ class HandshakeState : MinecraftConnectionState {
|
|||||||
GlobalScope.launch(Dispatchers.IO) {
|
GlobalScope.launch(Dispatchers.IO) {
|
||||||
val frontHandler = handler.user.channel!!.pipeline().get(CloudMinecraftHandler::class.java)
|
val frontHandler = handler.user.channel!!.pipeline().get(CloudMinecraftHandler::class.java)
|
||||||
try {
|
try {
|
||||||
var srvResolvedAddr = backAddr
|
var srvResolvedAddr = packet.address
|
||||||
var srvResolvedPort = backPort
|
var srvResolvedPort = packet.port
|
||||||
if (srvResolvedPort == 25565) {
|
if (srvResolvedPort == 25565) {
|
||||||
try {
|
try {
|
||||||
// https://github.com/GeyserMC/Geyser/blob/99e72f35b308542cf0dbfb5b58816503c3d6a129/connector/src/main/java/org/geysermc/connector/GeyserConnector.java
|
// https://github.com/GeyserMC/Geyser/blob/99e72f35b308542cf0dbfb5b58816503c3d6a129/connector/src/main/java/org/geysermc/connector/GeyserConnector.java
|
||||||
val attr = InitialDirContext()
|
val attr = InitialDirContext()
|
||||||
.getAttributes("dns:///_minecraft._tcp.$backAddr", arrayOf("SRV"))["SRV"]
|
.getAttributes("dns:///_minecraft._tcp.$srvResolvedAddr", arrayOf("SRV"))["SRV"]
|
||||||
if (attr != null && attr.size() > 0) {
|
if (attr != null && attr.size() > 0) {
|
||||||
val record = (attr.get(0) as String).split(" ")
|
val record = (attr.get(0) as String).split(" ")
|
||||||
srvResolvedAddr = record[3]
|
srvResolvedAddr = record[3]
|
||||||
@ -142,16 +152,17 @@ class HandshakeState : MinecraftConnectionState {
|
|||||||
}
|
}
|
||||||
val socketAddr = InetSocketAddress(InetAddress.getByName(srvResolvedAddr), srvResolvedPort)
|
val socketAddr = InetSocketAddress(InetAddress.getByName(srvResolvedAddr), srvResolvedPort)
|
||||||
val addrInfo = socketAddr.address
|
val addrInfo = socketAddr.address
|
||||||
if (addrInfo.isSiteLocalAddress
|
if (VIAaaSConfig.blockLocalAddress && (addrInfo.isSiteLocalAddress
|
||||||
|| addrInfo.isLoopbackAddress
|
|| addrInfo.isLoopbackAddress
|
||||||
|| addrInfo.isLinkLocalAddress
|
|| addrInfo.isLinkLocalAddress
|
||||||
|| addrInfo.isAnyLocalAddress) throw SecurityException("Local addresses aren't allowed")
|
|| addrInfo.isAnyLocalAddress)
|
||||||
|
) throw SecurityException("Local addresses aren't allowed")
|
||||||
|
|
||||||
val bootstrap = Bootstrap().handler(BackendInit(handler.user))
|
val bootstrap = Bootstrap().handler(BackendInit(handler.user))
|
||||||
.channelFactory(channelSocketFactory())
|
.channelFactory(channelSocketFactory())
|
||||||
.group(handler.user.channel!!.eventLoop())
|
.group(handler.user.channel!!.eventLoop())
|
||||||
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 15_000) // Half of mc timeout
|
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 15_000) // Half of mc timeout
|
||||||
.connect(socketAddr)
|
.connect(socketAddr)
|
||||||
|
|
||||||
bootstrap.addListener {
|
bootstrap.addListener {
|
||||||
if (it.isSuccess) {
|
if (it.isSuccess) {
|
||||||
@ -161,23 +172,15 @@ class HandshakeState : MinecraftConnectionState {
|
|||||||
backChan.pipeline().get(CloudMinecraftHandler::class.java).other = handler.user.channel
|
backChan.pipeline().get(CloudMinecraftHandler::class.java).other = handler.user.channel
|
||||||
frontHandler.other = backChan
|
frontHandler.other = backChan
|
||||||
|
|
||||||
val backHandshake = ByteBufAllocator.DEFAULT.buffer()
|
packet.address = srvResolvedAddr
|
||||||
try {
|
packet.port = srvResolvedPort
|
||||||
backHandshake.writeByte(0) // Packet 0 handshake
|
forward(handler, packet)
|
||||||
Type.VAR_INT.writePrimitive(backHandshake, handler.data!!.protocolId!!)
|
backChan.flush()
|
||||||
Type.STRING.write(backHandshake, srvResolvedAddr) // Server Address
|
|
||||||
backHandshake.writeShort(srvResolvedPort)
|
|
||||||
Type.VAR_INT.writePrimitive(backHandshake, nextAddr)
|
|
||||||
backChan.writeAndFlush(backHandshake.retain())
|
|
||||||
} finally {
|
|
||||||
backHandshake.release()
|
|
||||||
}
|
|
||||||
|
|
||||||
handler.user.channel!!.setAutoRead(true)
|
handler.user.channel!!.setAutoRead(true)
|
||||||
} else {
|
} else {
|
||||||
handler.user.channel!!.eventLoop().submit {
|
// We're in the event loop
|
||||||
frontHandler.disconnect("Couldn't connect: " + it.cause().toString())
|
frontHandler.disconnect("Couldn't connect: " + it.cause().toString())
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@ -199,50 +202,49 @@ class HandshakeState : MinecraftConnectionState {
|
|||||||
|
|
||||||
object LoginState : MinecraftConnectionState {
|
object LoginState : MinecraftConnectionState {
|
||||||
override fun handleMessage(handler: CloudMinecraftHandler, ctx: ChannelHandlerContext, msg: ByteBuf) {
|
override fun handleMessage(handler: CloudMinecraftHandler, ctx: ChannelHandlerContext, msg: ByteBuf) {
|
||||||
msg.markReaderIndex()
|
val packet = PacketRegistry.decode(
|
||||||
val id = Type.VAR_INT.readPrimitive(msg)
|
msg,
|
||||||
when {
|
ProtocolVersion.getProtocol(handler.user.protocolInfo!!.serverProtocolVersion),
|
||||||
handler.frontEnd && id == 0 -> handleLoginStart(handler, msg)
|
State.LOGIN,
|
||||||
handler.frontEnd && id == 1 -> handleCryptoResponse(handler, msg)
|
handler.frontEnd
|
||||||
handler.frontEnd && id == 2 -> forward(handler, msg) // Plugin response
|
)
|
||||||
!handler.frontEnd && id == 0 -> forward(handler, msg) // Disconnect
|
|
||||||
!handler.frontEnd && id == 1 -> handleCryptoRequest(handler, msg)
|
when (packet) {
|
||||||
!handler.frontEnd && id == 2 -> handleLoginSuccess(handler, msg)
|
is LoginStart -> handleLoginStart(handler, packet)
|
||||||
!handler.frontEnd && id == 3 -> handleCompression(handler, msg)
|
is CryptoResponse -> handleCryptoResponse(handler, packet)
|
||||||
!handler.frontEnd && id == 4 -> forward(handler, msg) // Plugin request
|
is PluginResponse -> forward(handler, packet)
|
||||||
else -> throw IllegalArgumentException("Invalid packet ID")
|
is LoginDisconnect -> forward(handler, packet)
|
||||||
|
is CryptoRequest -> handleCryptoRequest(handler, packet)
|
||||||
|
is LoginSuccess -> handleLoginSuccess(handler, packet)
|
||||||
|
is SetCompression -> handleCompression(handler, packet)
|
||||||
|
is PluginRequest -> forward(handler, packet)
|
||||||
|
else -> throw IllegalArgumentException("Invalid packet!")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun forward(handler: CloudMinecraftHandler, msg: ByteBuf) {
|
private fun handleLoginSuccess(handler: CloudMinecraftHandler, loginSuccess: LoginSuccess) {
|
||||||
msg.resetReaderIndex()
|
handler.data.state = PlayState
|
||||||
handler.other!!.write(msg.retain())
|
forward(handler, loginSuccess)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleLoginSuccess(handler: CloudMinecraftHandler, msg: ByteBuf) {
|
private fun handleCompression(handler: CloudMinecraftHandler, setCompression: SetCompression) {
|
||||||
handler.data!!.state = PlayState
|
|
||||||
forward(handler, msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun handleCompression(handler: CloudMinecraftHandler, msg: ByteBuf) {
|
|
||||||
val pipe = handler.user.channel!!.pipeline()
|
val pipe = handler.user.channel!!.pipeline()
|
||||||
val threshold = Type.VAR_INT.readPrimitive(msg)
|
val threshold = setCompression.threshold
|
||||||
|
|
||||||
val backPipe = pipe.get(CloudMinecraftHandler::class.java).other!!.pipeline()
|
val backPipe = pipe.get(CloudMinecraftHandler::class.java).other!!.pipeline()
|
||||||
backPipe.addAfter("frame", "compress", CloudCompressionCodec(threshold))
|
backPipe.addAfter("frame", "compress", CloudCompressionCodec(threshold))
|
||||||
|
|
||||||
forward(handler, msg)
|
forward(handler, setCompression)
|
||||||
|
|
||||||
pipe.addAfter("frame", "compress", CloudCompressionCodec(threshold))
|
pipe.addAfter("frame", "compress", CloudCompressionCodec(threshold))
|
||||||
pipe.addAfter("frame", "decompress", EmptyChannelHandler()) // ViaRewind compat workaround
|
pipe.addAfter("frame", "decompress", EmptyChannelHandler()) // ViaRewind compat workaround
|
||||||
}
|
}
|
||||||
|
|
||||||
fun handleCryptoRequest(handler: CloudMinecraftHandler, msg: ByteBuf) {
|
fun handleCryptoRequest(handler: CloudMinecraftHandler, cryptoRequest: CryptoRequest) {
|
||||||
val data = handler.data!!
|
val data = handler.data
|
||||||
data.backServerId = Type.STRING.read(msg)
|
data.backServerId = cryptoRequest.serverId
|
||||||
data.backPublicKey = KeyFactory.getInstance("RSA")
|
data.backPublicKey = cryptoRequest.publicKey
|
||||||
.generatePublic(X509EncodedKeySpec(Type.BYTE_ARRAY_PRIMITIVE.read(msg)))
|
data.backToken = cryptoRequest.token
|
||||||
data.backToken = Type.BYTE_ARRAY_PRIMITIVE.read(msg)
|
|
||||||
|
|
||||||
val id = "VIAaaS" + ByteArray(10).let {
|
val id = "VIAaaS" + ByteArray(10).let {
|
||||||
secureRandom.nextBytes(it)
|
secureRandom.nextBytes(it)
|
||||||
@ -257,32 +259,27 @@ object LoginState : MinecraftConnectionState {
|
|||||||
data.frontToken = token
|
data.frontToken = token
|
||||||
data.frontId = id
|
data.frontId = id
|
||||||
|
|
||||||
val backMsg = ByteBufAllocator.DEFAULT.buffer()
|
cryptoRequest.serverId = id
|
||||||
try {
|
cryptoRequest.publicKey = mcCryptoKey.public
|
||||||
backMsg.writeByte(1) // Packet id
|
cryptoRequest.token = token
|
||||||
Type.STRING.write(backMsg, id)
|
|
||||||
Type.BYTE_ARRAY_PRIMITIVE.write(backMsg, mcCryptoKey.public.encoded)
|
forward(handler, cryptoRequest)
|
||||||
Type.BYTE_ARRAY_PRIMITIVE.write(backMsg, token)
|
|
||||||
handler.other!!.write(backMsg.retain())
|
|
||||||
} finally {
|
|
||||||
backMsg.release()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun handleCryptoResponse(handler: CloudMinecraftHandler, msg: ByteBuf) {
|
fun handleCryptoResponse(handler: CloudMinecraftHandler, cryptoResponse: CryptoResponse) {
|
||||||
val frontHash = let {
|
val frontHash = let {
|
||||||
val frontKey = decryptRsa(mcCryptoKey.private, Type.BYTE_ARRAY_PRIMITIVE.read(msg))
|
val frontKey = decryptRsa(mcCryptoKey.private, cryptoResponse.encryptedKey)
|
||||||
// RSA token - wat??? why is it encrypted with RSA if it was sent unencrypted?
|
// RSA token - wat??? why is it encrypted with RSA if it was sent unencrypted?
|
||||||
val decryptedToken = decryptRsa(mcCryptoKey.private, Type.BYTE_ARRAY_PRIMITIVE.read(msg))
|
val decryptedToken = decryptRsa(mcCryptoKey.private, cryptoResponse.encryptedToken)
|
||||||
|
|
||||||
if (!decryptedToken.contentEquals(handler.data!!.frontToken!!)) throw IllegalStateException("invalid token!")
|
if (!decryptedToken.contentEquals(handler.data.frontToken!!)) throw IllegalStateException("invalid token!")
|
||||||
|
|
||||||
val aesEn = mcCfb8(frontKey, Cipher.ENCRYPT_MODE)
|
val aesEn = mcCfb8(frontKey, Cipher.ENCRYPT_MODE)
|
||||||
val aesDe = mcCfb8(frontKey, Cipher.DECRYPT_MODE)
|
val aesDe = mcCfb8(frontKey, Cipher.DECRYPT_MODE)
|
||||||
|
|
||||||
handler.user.channel!!.pipeline().addBefore("frame", "crypto", CloudCrypto(aesDe, aesEn))
|
handler.user.channel!!.pipeline().addBefore("frame", "crypto", CloudCrypto(aesDe, aesEn))
|
||||||
|
|
||||||
generateServerHash(handler.data!!.frontId!!, frontKey, mcCryptoKey.public)
|
generateServerHash(handler.data.frontId!!, frontKey, mcCryptoKey.public)
|
||||||
}
|
}
|
||||||
|
|
||||||
val backKey = ByteArray(16).let {
|
val backKey = ByteArray(16).let {
|
||||||
@ -290,22 +287,25 @@ object LoginState : MinecraftConnectionState {
|
|||||||
it
|
it
|
||||||
}
|
}
|
||||||
|
|
||||||
val backHash = generateServerHash(handler.data!!.backServerId!!, backKey, handler.data!!.backPublicKey!!)
|
val backHash = generateServerHash(handler.data.backServerId!!, backKey, handler.data.backPublicKey!!)
|
||||||
|
|
||||||
handler.user.channel!!.setAutoRead(false)
|
handler.user.channel!!.setAutoRead(false)
|
||||||
GlobalScope.launch(Dispatchers.IO) {
|
GlobalScope.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
val profile = httpClient.get<JsonObject?>(
|
val profile = httpClient.get<JsonObject?>(
|
||||||
"https://sessionserver.mojang.com/session/minecraft/hasJoined?username=" +
|
"https://sessionserver.mojang.com/session/minecraft/hasJoined?username=" +
|
||||||
"${UrlEscapers.urlFormParameterEscaper().escape(handler.data!!.frontName!!)}&serverId=$frontHash")
|
"${
|
||||||
?: throw IllegalArgumentException("Couldn't authenticate with session servers")
|
UrlEscapers.urlFormParameterEscaper().escape(handler.data.frontName!!)
|
||||||
|
}&serverId=$frontHash"
|
||||||
|
)
|
||||||
|
?: throw IllegalArgumentException("Couldn't authenticate with session servers")
|
||||||
|
|
||||||
val sessionJoin = viaWebServer.requestSessionJoin(
|
val sessionJoin = viaWebServer.requestSessionJoin(
|
||||||
fromUndashed(profile.get("id")!!.asString),
|
fromUndashed(profile.get("id")!!.asString),
|
||||||
handler.data!!.backName!!,
|
handler.data.backName!!,
|
||||||
backHash,
|
backHash,
|
||||||
handler.address!!, // Frontend handler
|
handler.address!!, // Frontend handler
|
||||||
handler.data!!.backPublicKey!!
|
handler.data.backPublicKey!!
|
||||||
)
|
)
|
||||||
|
|
||||||
val backChan = handler.other!!
|
val backChan = handler.other!!
|
||||||
@ -313,21 +313,15 @@ object LoginState : MinecraftConnectionState {
|
|||||||
if (throwable != null) {
|
if (throwable != null) {
|
||||||
handler.disconnect("Online mode error: $throwable")
|
handler.disconnect("Online mode error: $throwable")
|
||||||
} else {
|
} else {
|
||||||
val backMsg = ByteBufAllocator.DEFAULT.buffer()
|
cryptoResponse.encryptedKey = encryptRsa(handler.data.backPublicKey!!, backKey)
|
||||||
try {
|
cryptoResponse.encryptedToken =
|
||||||
backMsg.writeByte(1) // Packet id
|
encryptRsa(handler.data.backPublicKey!!, handler.data.backToken!!)
|
||||||
Type.BYTE_ARRAY_PRIMITIVE.write(backMsg, encryptRsa(handler.data!!.backPublicKey!!, backKey))
|
forward(handler, cryptoResponse)
|
||||||
Type.BYTE_ARRAY_PRIMITIVE.write(backMsg, encryptRsa(handler.data!!.backPublicKey!!, handler.data!!.backToken!!))
|
backChan.flush()
|
||||||
backChan.writeAndFlush(backMsg.retain())
|
|
||||||
|
|
||||||
val backAesEn = mcCfb8(backKey, Cipher.ENCRYPT_MODE)
|
val backAesEn = mcCfb8(backKey, Cipher.ENCRYPT_MODE)
|
||||||
val backAesDe = mcCfb8(backKey, Cipher.DECRYPT_MODE)
|
val backAesDe = mcCfb8(backKey, Cipher.DECRYPT_MODE)
|
||||||
|
backChan.pipeline().addBefore("frame", "crypto", CloudCrypto(backAesDe, backAesEn))
|
||||||
|
|
||||||
backChan.pipeline().addBefore("frame", "crypto", CloudCrypto(backAesDe, backAesEn))
|
|
||||||
} finally {
|
|
||||||
backMsg.release()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, backChan.eventLoop())
|
}, backChan.eventLoop())
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@ -337,17 +331,12 @@ object LoginState : MinecraftConnectionState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun handleLoginStart(handler: CloudMinecraftHandler, msg: ByteBuf) {
|
fun handleLoginStart(handler: CloudMinecraftHandler, loginStart: LoginStart) {
|
||||||
handler.data!!.frontName = Type.STRING.read(msg)
|
handler.data.frontName = loginStart.username
|
||||||
handler.data!!.backName = handler.user.get(CloudData::class.java)!!.altName ?: handler.data!!.frontName
|
handler.data.backName = handler.user.get(CloudData::class.java)!!.altName ?: handler.data.frontName
|
||||||
val backMsg = ByteBufAllocator.DEFAULT.buffer()
|
|
||||||
try {
|
loginStart.username = handler.data.backName!!
|
||||||
msg.writeByte(0) // Id
|
forward(handler, loginStart)
|
||||||
Type.STRING.write(msg, handler.data!!.backName)
|
|
||||||
handler.other!!.write(msg.retain())
|
|
||||||
} finally {
|
|
||||||
backMsg.release()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun disconnect(handler: CloudMinecraftHandler, msg: String) {
|
override fun disconnect(handler: CloudMinecraftHandler, msg: String) {
|
||||||
@ -358,8 +347,8 @@ object LoginState : MinecraftConnectionState {
|
|||||||
packet.writeByte(0) // id 0 disconnect
|
packet.writeByte(0) // id 0 disconnect
|
||||||
Type.STRING.write(packet, Gson().toJson("[VIAaaS] §c$msg"))
|
Type.STRING.write(packet, Gson().toJson("[VIAaaS] §c$msg"))
|
||||||
handler.user
|
handler.user
|
||||||
.sendRawPacketFuture(packet.retain())
|
.sendRawPacketFuture(packet.retain())
|
||||||
.addListener { handler.user.channel?.close() }
|
.addListener { handler.user.channel?.close() }
|
||||||
} finally {
|
} finally {
|
||||||
packet.release()
|
packet.release()
|
||||||
}
|
}
|
||||||
@ -380,10 +369,12 @@ object StatusState : MinecraftConnectionState {
|
|||||||
val packet = ByteBufAllocator.DEFAULT.buffer()
|
val packet = ByteBufAllocator.DEFAULT.buffer()
|
||||||
try {
|
try {
|
||||||
packet.writeByte(0) // id 0 disconnect
|
packet.writeByte(0) // id 0 disconnect
|
||||||
Type.STRING.write(packet, """{"version": {"name": "VIAaaS", "protocol": -1}, "players":
|
Type.STRING.write(
|
||||||
| {"max": 0, "online": 0, "sample": []}, "description": {"text": ${Gson().toJson("§c$msg")}}}""".trimMargin())
|
packet, """{"version": {"name": "VIAaaS", "protocol": -1}, "players":
|
||||||
|
| {"max": 0, "online": 0, "sample": []}, "description": {"text": ${Gson().toJson("§c$msg")}}}""".trimMargin()
|
||||||
|
)
|
||||||
handler.user.sendRawPacketFuture(packet.retain())
|
handler.user.sendRawPacketFuture(packet.retain())
|
||||||
.addListener { handler.user.channel?.close() }
|
.addListener { handler.user.channel?.close() }
|
||||||
} finally {
|
} finally {
|
||||||
packet.release()
|
packet.release()
|
||||||
}
|
}
|
||||||
@ -422,3 +413,32 @@ fun mcCfb8(key: ByteArray, mode: Int): Cipher {
|
|||||||
it
|
it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun Channel.setAutoRead(b: Boolean) {
|
||||||
|
this.config().isAutoRead = b
|
||||||
|
if (b) this.read()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun twosComplementHexdigest(digest: ByteArray): String {
|
||||||
|
return BigInteger(digest).toString(16)
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/VelocityPowered/Velocity/blob/0dd6fe1ef2783fe1f9322af06c6fd218aa67cdb1/proxy/src/main/java/com/velocitypowered/proxy/util/EncryptionUtils.java
|
||||||
|
fun generateServerHash(serverId: String, sharedSecret: ByteArray?, key: PublicKey): String {
|
||||||
|
val digest = MessageDigest.getInstance("SHA-1")
|
||||||
|
digest.update(serverId.toByteArray(Charsets.ISO_8859_1))
|
||||||
|
digest.update(sharedSecret)
|
||||||
|
digest.update(key.encoded)
|
||||||
|
return twosComplementHexdigest(digest.digest())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun forward(handler: CloudMinecraftHandler, packet: Packet) {
|
||||||
|
val msg = ByteBufAllocator.DEFAULT.buffer()
|
||||||
|
try {
|
||||||
|
PacketRegistry.encode(packet, msg, ProtocolVersion.getProtocol(handler.data.protocolId!!))
|
||||||
|
handler.other!!.write(msg.retain())
|
||||||
|
} finally {
|
||||||
|
msg.release()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,306 @@
|
|||||||
|
package com.github.creeper123123321.viaaas
|
||||||
|
|
||||||
|
import com.google.common.collect.Range
|
||||||
|
import io.netty.buffer.ByteBuf
|
||||||
|
import us.myles.ViaVersion.api.protocol.ProtocolVersion
|
||||||
|
import us.myles.ViaVersion.api.type.Type
|
||||||
|
import us.myles.ViaVersion.packets.State
|
||||||
|
import java.security.KeyFactory
|
||||||
|
import java.security.PublicKey
|
||||||
|
import java.security.spec.X509EncodedKeySpec
|
||||||
|
import java.util.*
|
||||||
|
import java.util.function.Supplier
|
||||||
|
import kotlin.properties.Delegates
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A mutable object which represents a Minecraft packet data
|
||||||
|
*/
|
||||||
|
interface Packet {
|
||||||
|
fun decode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion)
|
||||||
|
fun encode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
object PacketRegistry {
|
||||||
|
val entries = mutableListOf<RegistryEntry>()
|
||||||
|
|
||||||
|
init {
|
||||||
|
entries.add(
|
||||||
|
RegistryEntry(Range.all(), State.HANDSHAKE, 0, true, ::HandshakePacket, HandshakePacket::class.java)
|
||||||
|
)
|
||||||
|
entries.add(
|
||||||
|
RegistryEntry(Range.all(), State.LOGIN, 0, true, ::LoginStart, LoginStart::class.java)
|
||||||
|
)
|
||||||
|
entries.add(
|
||||||
|
RegistryEntry(Range.all(), State.LOGIN, 1, true, ::CryptoResponse, CryptoResponse::class.java)
|
||||||
|
)
|
||||||
|
entries.add(
|
||||||
|
RegistryEntry(
|
||||||
|
Range.atLeast(ProtocolVersion.v1_13.version),
|
||||||
|
State.LOGIN,
|
||||||
|
2,
|
||||||
|
true,
|
||||||
|
::PluginResponse,
|
||||||
|
PluginResponse::class.java
|
||||||
|
)
|
||||||
|
)
|
||||||
|
entries.add(
|
||||||
|
RegistryEntry(Range.all(), State.LOGIN, 0, false, ::LoginDisconnect, LoginDisconnect::class.java)
|
||||||
|
)
|
||||||
|
entries.add(
|
||||||
|
RegistryEntry(Range.all(), State.LOGIN, 1, false, ::CryptoRequest, CryptoRequest::class.java)
|
||||||
|
)
|
||||||
|
entries.add(
|
||||||
|
RegistryEntry(Range.all(), State.LOGIN, 2, false, ::LoginSuccess, LoginSuccess::class.java)
|
||||||
|
)
|
||||||
|
entries.add(
|
||||||
|
RegistryEntry(Range.all(), State.LOGIN, 3, false, ::SetCompression, SetCompression::class.java)
|
||||||
|
)
|
||||||
|
entries.add(
|
||||||
|
RegistryEntry(Range.all(), State.LOGIN, 4, false, ::PluginRequest, PluginRequest::class.java)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
data class RegistryEntry(
|
||||||
|
val versionRange: Range<Int>,
|
||||||
|
val state: State,
|
||||||
|
val id: Int,
|
||||||
|
val serverBound: Boolean,
|
||||||
|
val constructor: Supplier<Packet>,
|
||||||
|
val packetClass: Class<out Packet>
|
||||||
|
)
|
||||||
|
|
||||||
|
fun getPacketConstructor(
|
||||||
|
protocolVersion: ProtocolVersion,
|
||||||
|
state: State,
|
||||||
|
id: Int,
|
||||||
|
serverBound: Boolean
|
||||||
|
): Supplier<out Packet>? {
|
||||||
|
return entries.firstOrNull {
|
||||||
|
it.serverBound == serverBound && it.state == state
|
||||||
|
&& it.versionRange.contains(protocolVersion.version) && it.id == id
|
||||||
|
}?.constructor
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getPacketId(packetClass: Class<out Packet>, protocolVersion: ProtocolVersion): Int? {
|
||||||
|
return entries.firstOrNull {
|
||||||
|
it.versionRange.contains(protocolVersion.version) && it.packetClass == packetClass
|
||||||
|
}?.id
|
||||||
|
}
|
||||||
|
|
||||||
|
fun decode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion, state: State, serverBound: Boolean): Packet {
|
||||||
|
val packetId = Type.VAR_INT.readPrimitive(byteBuf)
|
||||||
|
val packet =
|
||||||
|
getPacketConstructor(protocolVersion, state, packetId, serverBound)?.get() ?: UnknownPacket(packetId)
|
||||||
|
packet.decode(byteBuf, protocolVersion)
|
||||||
|
if (byteBuf.isReadable) throw IllegalStateException("Remaining bytes!")
|
||||||
|
return packet
|
||||||
|
}
|
||||||
|
|
||||||
|
fun encode(packet: Packet, byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
|
||||||
|
val id = if (packet is UnknownPacket) {
|
||||||
|
packet.id
|
||||||
|
} else {
|
||||||
|
getPacketId(packet.javaClass, protocolVersion)!!
|
||||||
|
}
|
||||||
|
Type.VAR_INT.writePrimitive(byteBuf, id)
|
||||||
|
packet.encode(byteBuf, protocolVersion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class UnknownPacket(val id: Int) : Packet {
|
||||||
|
lateinit var content: ByteBuf
|
||||||
|
|
||||||
|
override fun decode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
|
||||||
|
content = byteBuf.slice().clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun encode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
|
||||||
|
byteBuf.writeBytes(content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Some code based on https://github.com/VelocityPowered/Velocity/tree/dev/1.1.0/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet
|
||||||
|
|
||||||
|
class HandshakePacket : Packet {
|
||||||
|
var protocolId by Delegates.notNull<Int>()
|
||||||
|
lateinit var address: String
|
||||||
|
var port by Delegates.notNull<Int>()
|
||||||
|
lateinit var nextState: State
|
||||||
|
|
||||||
|
override fun decode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
|
||||||
|
protocolId = Type.VAR_INT.readPrimitive(byteBuf)
|
||||||
|
address = Type.STRING.read(byteBuf)
|
||||||
|
port = byteBuf.readUnsignedShort()
|
||||||
|
nextState = State.values()[Type.VAR_INT.readPrimitive(byteBuf)]
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun encode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
|
||||||
|
Type.VAR_INT.writePrimitive(byteBuf, protocolId)
|
||||||
|
Type.STRING.write(byteBuf, address)
|
||||||
|
byteBuf.writeShort(port)
|
||||||
|
byteBuf.writeByte(nextState.ordinal) // var int is too small, fits in a byte
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LoginStart : Packet {
|
||||||
|
lateinit var username: String
|
||||||
|
|
||||||
|
override fun decode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
|
||||||
|
username = Type.STRING.read(byteBuf)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun encode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
|
||||||
|
Type.STRING.write(byteBuf, username)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CryptoResponse : Packet {
|
||||||
|
lateinit var encryptedKey: ByteArray
|
||||||
|
lateinit var encryptedToken: ByteArray
|
||||||
|
|
||||||
|
override fun decode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
|
||||||
|
if (protocolVersion.version >= ProtocolVersion.v1_8.version) {
|
||||||
|
encryptedKey = Type.BYTE_ARRAY_PRIMITIVE.read(byteBuf)
|
||||||
|
encryptedToken = Type.BYTE_ARRAY_PRIMITIVE.read(byteBuf)
|
||||||
|
} else {
|
||||||
|
encryptedKey = ByteArray(byteBuf.readUnsignedShort()).also { byteBuf.readBytes(it) }
|
||||||
|
encryptedToken = ByteArray(byteBuf.readUnsignedShort()).also { byteBuf.readBytes(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun encode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
|
||||||
|
if (protocolVersion.version >= ProtocolVersion.v1_8.version) {
|
||||||
|
Type.BYTE_ARRAY_PRIMITIVE.write(byteBuf, encryptedKey)
|
||||||
|
Type.BYTE_ARRAY_PRIMITIVE.write(byteBuf, encryptedToken)
|
||||||
|
} else {
|
||||||
|
byteBuf.writeShort(encryptedKey.size)
|
||||||
|
byteBuf.writeBytes(encryptedKey)
|
||||||
|
byteBuf.writeShort(encryptedToken.size)
|
||||||
|
byteBuf.writeBytes(encryptedToken)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PluginResponse : Packet {
|
||||||
|
var id by Delegates.notNull<Int>()
|
||||||
|
var success by Delegates.notNull<Boolean>()
|
||||||
|
lateinit var data: ByteBuf
|
||||||
|
override fun decode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
|
||||||
|
id = Type.VAR_INT.readPrimitive(byteBuf)
|
||||||
|
success = byteBuf.readBoolean()
|
||||||
|
if (success) {
|
||||||
|
data = byteBuf.slice().clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun encode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
|
||||||
|
Type.VAR_INT.writePrimitive(byteBuf, id)
|
||||||
|
byteBuf.writeBoolean(success)
|
||||||
|
if (success) {
|
||||||
|
byteBuf.writeBytes(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LoginDisconnect : Packet {
|
||||||
|
lateinit var msg: String
|
||||||
|
override fun decode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
|
||||||
|
msg = Type.STRING.read(byteBuf)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun encode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
|
||||||
|
Type.STRING.write(byteBuf, msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CryptoRequest : Packet {
|
||||||
|
lateinit var serverId: String
|
||||||
|
lateinit var publicKey: PublicKey
|
||||||
|
lateinit var token: ByteArray
|
||||||
|
|
||||||
|
override fun decode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
|
||||||
|
serverId = Type.STRING.read(byteBuf)
|
||||||
|
if (protocolVersion.version >= ProtocolVersion.v1_8.version) {
|
||||||
|
publicKey = KeyFactory.getInstance("RSA")
|
||||||
|
.generatePublic(X509EncodedKeySpec(Type.BYTE_ARRAY_PRIMITIVE.read(byteBuf)))
|
||||||
|
token = Type.BYTE_ARRAY_PRIMITIVE.read(byteBuf)
|
||||||
|
} else {
|
||||||
|
publicKey = KeyFactory.getInstance("RSA")
|
||||||
|
.generatePublic(X509EncodedKeySpec(ByteArray(byteBuf.readUnsignedShort()).also { byteBuf.readBytes(it) }))
|
||||||
|
token = ByteArray(byteBuf.readUnsignedShort()).also { byteBuf.readBytes(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun encode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
|
||||||
|
Type.STRING.write(byteBuf, serverId)
|
||||||
|
if (protocolVersion.version >= ProtocolVersion.v1_8.version) {
|
||||||
|
Type.BYTE_ARRAY_PRIMITIVE.write(byteBuf, publicKey.encoded)
|
||||||
|
Type.BYTE_ARRAY_PRIMITIVE.write(byteBuf, token)
|
||||||
|
} else {
|
||||||
|
val encodedKey = publicKey.encoded
|
||||||
|
byteBuf.writeShort(encodedKey.size)
|
||||||
|
byteBuf.writeBytes(encodedKey)
|
||||||
|
byteBuf.writeShort(token.size)
|
||||||
|
byteBuf.writeBytes(token)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LoginSuccess : Packet {
|
||||||
|
lateinit var id: UUID
|
||||||
|
lateinit var username: String
|
||||||
|
|
||||||
|
override fun decode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
|
||||||
|
id = when {
|
||||||
|
protocolVersion.version >= ProtocolVersion.v1_16.version -> {
|
||||||
|
Type.UUID_INT_ARRAY.read(byteBuf)
|
||||||
|
}
|
||||||
|
protocolVersion.version >= ProtocolVersion.v1_7_6.version -> {
|
||||||
|
UUID.fromString(Type.STRING.read(byteBuf))
|
||||||
|
}
|
||||||
|
else -> fromUndashed(Type.STRING.read(byteBuf))
|
||||||
|
}
|
||||||
|
username = Type.STRING.read(byteBuf)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun encode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
|
||||||
|
when {
|
||||||
|
protocolVersion.version >= ProtocolVersion.v1_16.version -> {
|
||||||
|
Type.UUID_INT_ARRAY.write(byteBuf, id)
|
||||||
|
}
|
||||||
|
protocolVersion.version >= ProtocolVersion.v1_7_6.version -> {
|
||||||
|
Type.STRING.write(byteBuf, id.toString())
|
||||||
|
}
|
||||||
|
else -> Type.STRING.write(byteBuf, id.toString().replace("-", ""))
|
||||||
|
}
|
||||||
|
Type.STRING.write(byteBuf, username)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SetCompression : Packet {
|
||||||
|
var threshold by Delegates.notNull<Int>()
|
||||||
|
override fun decode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
|
||||||
|
threshold = Type.VAR_INT.readPrimitive(byteBuf)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun encode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
|
||||||
|
Type.VAR_INT.writePrimitive(byteBuf, threshold)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PluginRequest : Packet {
|
||||||
|
var id by Delegates.notNull<Int>()
|
||||||
|
lateinit var channel: String
|
||||||
|
lateinit var data: ByteBuf
|
||||||
|
override fun decode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
|
||||||
|
id = Type.VAR_INT.readPrimitive(byteBuf)
|
||||||
|
channel = Type.STRING.read(byteBuf)
|
||||||
|
data = byteBuf.slice().clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun encode(byteBuf: ByteBuf, protocolVersion: ProtocolVersion) {
|
||||||
|
Type.VAR_INT.writePrimitive(byteBuf, id)
|
||||||
|
Type.STRING.write(byteBuf, channel)
|
||||||
|
byteBuf.writeBytes(data)
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,5 @@
|
|||||||
package com.github.creeper123123321.viaaas
|
package com.github.creeper123123321.viaaas
|
||||||
|
|
||||||
import io.netty.channel.Channel
|
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import us.myles.ViaVersion.api.PacketWrapper
|
import us.myles.ViaVersion.api.PacketWrapper
|
||||||
import us.myles.ViaVersion.api.data.StoredObject
|
import us.myles.ViaVersion.api.data.StoredObject
|
||||||
@ -11,10 +10,6 @@ import us.myles.ViaVersion.api.protocol.SimpleProtocol
|
|||||||
import us.myles.ViaVersion.api.remapper.PacketRemapper
|
import us.myles.ViaVersion.api.remapper.PacketRemapper
|
||||||
import us.myles.ViaVersion.api.type.Type
|
import us.myles.ViaVersion.api.type.Type
|
||||||
import us.myles.ViaVersion.packets.State
|
import us.myles.ViaVersion.packets.State
|
||||||
import java.math.BigInteger
|
|
||||||
import java.security.MessageDigest
|
|
||||||
import java.security.PublicKey
|
|
||||||
import java.security.SecureRandom
|
|
||||||
|
|
||||||
class CloudPipeline(userConnection: UserConnection) : ProtocolPipeline(userConnection) {
|
class CloudPipeline(userConnection: UserConnection) : ProtocolPipeline(userConnection) {
|
||||||
override fun registerPackets() {
|
override fun registerPackets() {
|
||||||
@ -39,7 +34,8 @@ object CloudHeadProtocol : SimpleProtocol() {
|
|||||||
val addr = wrapper.read(Type.STRING) // Server Address
|
val addr = wrapper.read(Type.STRING) // Server Address
|
||||||
val receivedPort = wrapper.read(Type.UNSIGNED_SHORT)
|
val receivedPort = wrapper.read(Type.UNSIGNED_SHORT)
|
||||||
|
|
||||||
val parsed = VIAaaSAddress().parse(addr, VIAaaSConfig.hostName)
|
val parsed = VIAaaSAddress().parse(addr.substringBefore(0.toChar()), VIAaaSConfig.hostName)
|
||||||
|
if (parsed.viaSuffix == null && VIAaaSConfig.requireHostName) throw IllegalStateException("VIAaaS hostname is required")
|
||||||
val backPort = parsed.port ?: receivedPort
|
val backPort = parsed.port ?: receivedPort
|
||||||
val backAddr = parsed.realAddress
|
val backAddr = parsed.realAddress
|
||||||
val backProto = parsed.protocol ?: 47
|
val backProto = parsed.protocol ?: 47
|
||||||
@ -48,14 +44,17 @@ object CloudHeadProtocol : SimpleProtocol() {
|
|||||||
wrapper.write(Type.UNSIGNED_SHORT, backPort)
|
wrapper.write(Type.UNSIGNED_SHORT, backPort)
|
||||||
|
|
||||||
val playerAddr = wrapper.user().channel!!.pipeline()
|
val playerAddr = wrapper.user().channel!!.pipeline()
|
||||||
.get(CloudMinecraftHandler::class.java)!!.address
|
.get(CloudMinecraftHandler::class.java)!!.address
|
||||||
logger.info("Connecting $playerAddr ($playerVer) -> $backAddr:$backPort ($backProto)")
|
logger.info("Connecting $playerAddr ($playerVer) -> $backAddr:$backPort ($backProto)")
|
||||||
|
|
||||||
wrapper.user().put(CloudData(
|
wrapper.user().put(
|
||||||
|
CloudData(
|
||||||
userConnection = wrapper.user(),
|
userConnection = wrapper.user(),
|
||||||
backendVer = backProto,
|
backendVer = backProto,
|
||||||
frontOnline = parsed.online,
|
frontOnline = parsed.online,
|
||||||
altName = parsed.altUsername))
|
altName = parsed.altUsername
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
wrapper.passthrough(Type.VAR_INT) // Next state
|
wrapper.passthrough(Type.VAR_INT) // Next state
|
||||||
}
|
}
|
||||||
@ -64,28 +63,9 @@ object CloudHeadProtocol : SimpleProtocol() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Channel.setAutoRead(b: Boolean) {
|
data class CloudData(
|
||||||
this.config().isAutoRead = b
|
val userConnection: UserConnection,
|
||||||
if (b) this.read()
|
var backendVer: Int,
|
||||||
}
|
var frontOnline: Boolean,
|
||||||
|
var altName: String?
|
||||||
val secureRandom = SecureRandom.getInstanceStrong()
|
|
||||||
|
|
||||||
fun twosComplementHexdigest(digest: ByteArray): String {
|
|
||||||
return BigInteger(digest).toString(16)
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://github.com/VelocityPowered/Velocity/blob/0dd6fe1ef2783fe1f9322af06c6fd218aa67cdb1/proxy/src/main/java/com/velocitypowered/proxy/util/EncryptionUtils.java
|
|
||||||
fun generateServerHash(serverId: String, sharedSecret: ByteArray?, key: PublicKey): String {
|
|
||||||
val digest = MessageDigest.getInstance("SHA-1")
|
|
||||||
digest.update(serverId.toByteArray(Charsets.ISO_8859_1))
|
|
||||||
digest.update(sharedSecret)
|
|
||||||
digest.update(key.encoded)
|
|
||||||
return twosComplementHexdigest(digest.digest())
|
|
||||||
}
|
|
||||||
|
|
||||||
data class CloudData(val userConnection: UserConnection,
|
|
||||||
var backendVer: Int,
|
|
||||||
var frontOnline: Boolean,
|
|
||||||
var altName: String?
|
|
||||||
) : StoredObject(userConnection)
|
) : StoredObject(userConnection)
|
@ -41,6 +41,7 @@ import us.myles.ViaVersion.util.Config
|
|||||||
import java.io.File
|
import java.io.File
|
||||||
import java.net.InetAddress
|
import java.net.InetAddress
|
||||||
import java.security.KeyPairGenerator
|
import java.security.KeyPairGenerator
|
||||||
|
import java.security.SecureRandom
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.CompletableFuture
|
import java.util.concurrent.CompletableFuture
|
||||||
|
|
||||||
@ -60,10 +61,12 @@ val initFuture = CompletableFuture<Unit>()
|
|||||||
|
|
||||||
// Minecraft doesn't have forward secrecy
|
// Minecraft doesn't have forward secrecy
|
||||||
val mcCryptoKey = KeyPairGenerator.getInstance("RSA").let {
|
val mcCryptoKey = KeyPairGenerator.getInstance("RSA").let {
|
||||||
it.initialize(4096) // https://stackoverflow.com/questions/1904516/is-1024-bit-rsa-secure
|
it.initialize(VIAaaSConfig.mcRsaSize) // https://stackoverflow.com/questions/1904516/is-1024-bit-rsa-secure
|
||||||
it.genKeyPair()
|
it.genKeyPair()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val secureRandom = if (VIAaaSConfig.useStrongRandom) SecureRandom.getInstanceStrong() else SecureRandom()
|
||||||
|
|
||||||
fun eventLoopGroup(): EventLoopGroup {
|
fun eventLoopGroup(): EventLoopGroup {
|
||||||
if (VIAaaSConfig.isNativeTransportMc) {
|
if (VIAaaSConfig.isNativeTransportMc) {
|
||||||
if (Epoll.isAvailable()) return EpollEventLoopGroup()
|
if (Epoll.isAvailable()) return EpollEventLoopGroup()
|
||||||
@ -249,6 +252,10 @@ object VIAaaSConfig : Config(File("config/viaaas.yml")) {
|
|||||||
val port: Int get() = this.getInt("port", 25565)
|
val port: Int get() = this.getInt("port", 25565)
|
||||||
val bindAddress: String get() = this.getString("bind-address", "localhost")!!
|
val bindAddress: String get() = this.getString("bind-address", "localhost")!!
|
||||||
val hostName: String get() = this.getString("host-name", "viaaas.localhost")!!
|
val hostName: String get() = this.getString("host-name", "viaaas.localhost")!!
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
class VIAaaSAddress {
|
class VIAaaSAddress {
|
||||||
|
@ -8,3 +8,14 @@ bind-address: localhost
|
|||||||
host-name: viaaas.localhost
|
host-name: viaaas.localhost
|
||||||
# Use netty native transport for Minecraft when available.
|
# Use netty native transport for Minecraft when available.
|
||||||
native-transport-mc: true
|
native-transport-mc: true
|
||||||
|
# Sets the RSA key size used by client for encrypting the AES symmetric key when using online mode.
|
||||||
|
# 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 when using online mode.
|
||||||
|
# See https://wiki.archlinux.org/index.php/Rng-tools
|
||||||
|
use-strong-random: true
|
||||||
|
# Blocks connection to local addresses
|
||||||
|
block-local-address: true
|
||||||
|
# Requires virtual host to contain the value from "host-name"
|
||||||
|
# A false value could be used for transparent proxying.
|
||||||
|
require-host-name: true
|
@ -11,7 +11,8 @@
|
|||||||
<meta property="og:image" content="https://raw.githubusercontent.com/ViaVersion/ViaVersion/a13c417352298c2269aed8736a76205f0040b705/fabric/src/main/resources/assets/viaversion/textures/squarelogo.png">
|
<meta property="og:image" content="https://raw.githubusercontent.com/ViaVersion/ViaVersion/a13c417352298c2269aed8736a76205f0040b705/fabric/src/main/resources/assets/viaversion/textures/squarelogo.png">
|
||||||
<meta property="og:type" content="game">
|
<meta property="og:type" content="game">
|
||||||
<link rel="icon" href="https://raw.githubusercontent.com/ViaVersion/ViaVersion/a13c417352298c2269aed8736a76205f0040b705/fabric/src/main/resources/assets/viaversion/textures/squarelogo.png">
|
<link rel="icon" href="https://raw.githubusercontent.com/ViaVersion/ViaVersion/a13c417352298c2269aed8736a76205f0040b705/fabric/src/main/resources/assets/viaversion/textures/squarelogo.png">
|
||||||
<meta http-equiv="Content-Security-Policy" content="default-src 'self' https://cdnjs.cloudflare.com/; img-src https://*; connect-src 'self' http: https: ws: wss:">
|
<meta http-equiv="Content-Security-Policy" content="default-src 'self' https://cdnjs.cloudflare.com/; img-src https://*; connect-src 'self' http://localhost:*/ https: ws: wss:">
|
||||||
|
<!-- only accept http from localhost -->
|
||||||
<title>VIAaaS Authenticator</title>
|
<title>VIAaaS Authenticator</title>
|
||||||
<link rel="stylesheet" href="style.css">
|
<link rel="stylesheet" href="style.css">
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
|
||||||
|
@ -14,8 +14,8 @@ $(() => {
|
|||||||
var content = document.getElementById("content");
|
var content = document.getElementById("content");
|
||||||
var acounts = document.getElementById("accounts");
|
var acounts = document.getElementById("accounts");
|
||||||
|
|
||||||
$("#cors-proxy").val(localStorage.getItem("cors-proxy"));
|
|
||||||
$("#cors-proxy").on("change", () => localStorage.setItem('cors-proxy', $("#cors-proxy").val()));
|
$("#cors-proxy").on("change", () => localStorage.setItem('cors-proxy', $("#cors-proxy").val()));
|
||||||
|
$("#cors-proxy").val(localStorage.getItem("cors-proxy"));
|
||||||
$("#login_submit_mc").on("click", loginMc);
|
$("#login_submit_mc").on("click", loginMc);
|
||||||
|
|
||||||
function loginMc() {
|
function loginMc() {
|
||||||
|
Loading…
Reference in New Issue
Block a user