mirror of
https://github.com/ViaVersion/VIAaaS.git
synced 2025-02-13 01:11:20 +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
|
||||
---
|
||||
Idea: server.example.com._p25565._v1_12_2._otrue._uBACKUSERNAME.viaaas.example.com (default backend 25565 port and version
|
||||
default as auto, online-mode can be optional/required) (similar to tor to web proxies)
|
||||
How to use: server.example.com._p25565._v1_12_2._uBACKUSERNAME.viaaas.example.com (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
|
||||
|
||||
@ -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.)
|
||||
- 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
|
||||
the username you're using to connect.
|
||||
- Add web page the account you used in _u parameter.
|
||||
- Connect to mc.example.com._v1_8.viaaas._u(BACKUSERNAME).localhost
|
||||
the username A that you're using to connect to the proxy.
|
||||
- Add web page the account B you used in _u parameter.
|
||||
- Connect to mc.example.com._v1_8.viaaas._u(account B).localhost
|
||||
- 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
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
package com.github.creeper123123321.viaaas
|
||||
|
||||
import io.netty.buffer.ByteBuf
|
||||
import io.netty.buffer.Unpooled
|
||||
import io.netty.channel.Channel
|
||||
import io.netty.channel.ChannelHandlerContext
|
||||
import io.netty.channel.ChannelInitializer
|
||||
@ -25,12 +24,12 @@ object ChannelInit : ChannelInitializer<Channel>() {
|
||||
val user = UserConnection(ch)
|
||||
CloudPipeline(user)
|
||||
ch.pipeline().addLast("timeout", ReadTimeoutHandler(30, TimeUnit.SECONDS))
|
||||
// "crypto"
|
||||
.addLast("frame", FrameCodec())
|
||||
// "compress" / dummy "decompress"
|
||||
.addLast("flow-handler", FlowControlHandler())
|
||||
.addLast("via-codec", CloudViaCodec(user))
|
||||
.addLast("handler", CloudMinecraftHandler(user, null, frontEnd = true))
|
||||
// "crypto"
|
||||
.addLast("frame", FrameCodec())
|
||||
// "compress" / dummy "decompress"
|
||||
.addLast("flow-handler", FlowControlHandler())
|
||||
.addLast("via-codec", CloudViaCodec(user))
|
||||
.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>() {
|
||||
override fun initChannel(ch: Channel) {
|
||||
ch.pipeline().addLast("timeout", ReadTimeoutHandler(30, TimeUnit.SECONDS))
|
||||
// "crypto"
|
||||
.addLast("frame", FrameCodec())
|
||||
// compress
|
||||
.addLast("handler", CloudMinecraftHandler(user, null, frontEnd = false))
|
||||
// "crypto"
|
||||
.addLast("frame", FrameCodec())
|
||||
// compress
|
||||
.addLast("handler", CloudMinecraftHandler(user, null, frontEnd = false))
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
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()
|
||||
|
||||
@Throws(Exception::class)
|
||||
override fun encode(ctx: ChannelHandlerContext, input: ByteBuf, out: MutableList<Any>) {
|
||||
val frameLength = input.readableBytes()
|
||||
val outBuf = ctx.alloc().heapBuffer()
|
||||
val outBuf = ctx.alloc().buffer()
|
||||
try {
|
||||
if (frameLength < threshold) {
|
||||
outBuf.writeByte(0)
|
||||
@ -77,15 +77,12 @@ class CloudCompressionCodec(val threshold: Int) : MessageToMessageCodec<ByteBuf,
|
||||
return
|
||||
}
|
||||
Type.VAR_INT.writePrimitive(outBuf, frameLength)
|
||||
val inBytes = ByteArray(frameLength)
|
||||
input.readBytes(inBytes)
|
||||
deflater.setInput(inBytes, 0, frameLength)
|
||||
deflater.setInput(input.nioBuffer())
|
||||
deflater.finish()
|
||||
while (!deflater.finished()) {
|
||||
outBuf.ensureWritable(8192)
|
||||
val wIndex = outBuf.writerIndex()
|
||||
outBuf.writerIndex(wIndex + deflater.deflate(outBuf.array(),
|
||||
outBuf.arrayOffset() + wIndex, outBuf.writableBytes()))
|
||||
outBuf.writerIndex(wIndex + deflater.deflate(outBuf.nioBuffer(wIndex, outBuf.writableBytes())))
|
||||
}
|
||||
out.add(outBuf.retain())
|
||||
} 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")
|
||||
}
|
||||
|
||||
val temp = ByteArray(input.readableBytes())
|
||||
input.readBytes(temp)
|
||||
inflater.setInput(temp)
|
||||
val output = ByteArray(outLength)
|
||||
inflater.inflate(output)
|
||||
out.add(Unpooled.wrappedBuffer(output))
|
||||
inflater.reset()
|
||||
inflater.setInput(input.nioBuffer())
|
||||
val output = ctx.alloc().buffer(outLength, outLength)
|
||||
try {
|
||||
output.writerIndex(output.writerIndex() + inflater.inflate(
|
||||
output.nioBuffer(output.writerIndex(), output.writableBytes())))
|
||||
out.add(output.retain())
|
||||
} finally {
|
||||
inflater.reset()
|
||||
output.release()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -19,15 +19,17 @@ import kotlinx.coroutines.launch
|
||||
import org.slf4j.LoggerFactory
|
||||
import us.myles.ViaVersion.api.data.StoredObject
|
||||
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.exception.CancelCodecException
|
||||
import us.myles.ViaVersion.packets.State
|
||||
import java.math.BigInteger
|
||||
import java.net.InetAddress
|
||||
import java.net.InetSocketAddress
|
||||
import java.net.SocketAddress
|
||||
import java.security.KeyFactory
|
||||
import java.security.MessageDigest
|
||||
import java.security.PrivateKey
|
||||
import java.security.PublicKey
|
||||
import java.security.spec.X509EncodedKeySpec
|
||||
import java.util.*
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.spec.IvParameterSpec
|
||||
@ -37,27 +39,30 @@ import javax.naming.directory.InitialDirContext
|
||||
|
||||
val chLogger = LoggerFactory.getLogger("VIAaaS MC Handler")
|
||||
|
||||
class HandlerData(userConnection: UserConnection,
|
||||
var state: MinecraftConnectionState,
|
||||
var protocolId: Int? = null,
|
||||
var frontName: String? = null,
|
||||
var backName: String? = null,
|
||||
var backServerId: String? = null,
|
||||
var backPublicKey: PublicKey? = null,
|
||||
var backToken: ByteArray? = null,
|
||||
var frontToken: ByteArray? = null,
|
||||
var frontId: String? = null
|
||||
class HandlerData(
|
||||
userConnection: UserConnection,
|
||||
var state: MinecraftConnectionState,
|
||||
var protocolId: Int? = null,
|
||||
var frontName: String? = null,
|
||||
var backName: String? = null,
|
||||
var backServerId: String? = null,
|
||||
var backPublicKey: PublicKey? = null,
|
||||
var backToken: ByteArray? = null,
|
||||
var frontToken: ByteArray? = null,
|
||||
var frontId: String? = null
|
||||
) : StoredObject(userConnection)
|
||||
|
||||
class CloudMinecraftHandler(val user: UserConnection,
|
||||
var other: Channel?,
|
||||
val frontEnd: Boolean) : SimpleChannelInboundHandler<ByteBuf>() {
|
||||
val data get() = user.get(HandlerData::class.java)
|
||||
class CloudMinecraftHandler(
|
||||
val user: UserConnection,
|
||||
var other: Channel?,
|
||||
val frontEnd: Boolean
|
||||
) : SimpleChannelInboundHandler<ByteBuf>() {
|
||||
val data get() = user.get(HandlerData::class.java)!!
|
||||
var address: SocketAddress? = null
|
||||
|
||||
override fun channelRead0(ctx: ChannelHandlerContext, msg: ByteBuf) {
|
||||
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!!!")
|
||||
//other?.write(msg.retain())
|
||||
}
|
||||
@ -65,14 +70,14 @@ class CloudMinecraftHandler(val user: UserConnection,
|
||||
|
||||
override fun channelActive(ctx: ChannelHandlerContext) {
|
||||
address = ctx.channel().remoteAddress()
|
||||
if (data == null) {
|
||||
if (user.get(HandlerData::class.java) == null) {
|
||||
user.put(HandlerData(user, HandshakeState()))
|
||||
}
|
||||
}
|
||||
|
||||
override fun channelInactive(ctx: ChannelHandlerContext) {
|
||||
other?.close()
|
||||
data!!.state.onInactivated(this)
|
||||
data.state.onInactivated(this)
|
||||
}
|
||||
|
||||
override fun channelReadComplete(ctx: ChannelHandlerContext?) {
|
||||
@ -90,13 +95,15 @@ class CloudMinecraftHandler(val user: UserConnection,
|
||||
}
|
||||
|
||||
fun disconnect(s: String) {
|
||||
data!!.state.disconnect(this, s)
|
||||
data.state.disconnect(this, s)
|
||||
}
|
||||
}
|
||||
|
||||
interface MinecraftConnectionState {
|
||||
fun handleMessage(handler: CloudMinecraftHandler, ctx: ChannelHandlerContext,
|
||||
msg: ByteBuf)
|
||||
fun handleMessage(
|
||||
handler: CloudMinecraftHandler, ctx: ChannelHandlerContext,
|
||||
msg: ByteBuf
|
||||
)
|
||||
|
||||
fun disconnect(handler: CloudMinecraftHandler, msg: String) {
|
||||
chLogger.info("Disconnected ${handler.address}: $msg")
|
||||
@ -110,14 +117,17 @@ interface MinecraftConnectionState {
|
||||
|
||||
class HandshakeState : MinecraftConnectionState {
|
||||
override fun handleMessage(handler: CloudMinecraftHandler, ctx: ChannelHandlerContext, msg: ByteBuf) {
|
||||
if (!handler.frontEnd || Type.VAR_INT.readPrimitive(msg) != 0) throw IllegalArgumentException("Invalid packet ID!")
|
||||
handler.data!!.protocolId = Type.VAR_INT.readPrimitive(msg)
|
||||
val backAddr = Type.STRING.read(msg)
|
||||
val backPort = Type.UNSIGNED_SHORT.read(msg)
|
||||
val nextAddr = Type.VAR_INT.readPrimitive(msg)
|
||||
when (nextAddr) {
|
||||
1 -> handler.data!!.state = StatusState
|
||||
2 -> handler.data!!.state = LoginState
|
||||
val packet = PacketRegistry.decode(
|
||||
msg,
|
||||
ProtocolVersion.getProtocol(handler.user.protocolInfo!!.serverProtocolVersion),
|
||||
State.HANDSHAKE,
|
||||
handler.frontEnd
|
||||
)
|
||||
if (packet !is HandshakePacket) throw IllegalArgumentException("Invalid packet!")
|
||||
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")
|
||||
}
|
||||
|
||||
@ -125,13 +135,13 @@ class HandshakeState : MinecraftConnectionState {
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
val frontHandler = handler.user.channel!!.pipeline().get(CloudMinecraftHandler::class.java)
|
||||
try {
|
||||
var srvResolvedAddr = backAddr
|
||||
var srvResolvedPort = backPort
|
||||
var srvResolvedAddr = packet.address
|
||||
var srvResolvedPort = packet.port
|
||||
if (srvResolvedPort == 25565) {
|
||||
try {
|
||||
// https://github.com/GeyserMC/Geyser/blob/99e72f35b308542cf0dbfb5b58816503c3d6a129/connector/src/main/java/org/geysermc/connector/GeyserConnector.java
|
||||
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) {
|
||||
val record = (attr.get(0) as String).split(" ")
|
||||
srvResolvedAddr = record[3]
|
||||
@ -142,16 +152,17 @@ class HandshakeState : MinecraftConnectionState {
|
||||
}
|
||||
val socketAddr = InetSocketAddress(InetAddress.getByName(srvResolvedAddr), srvResolvedPort)
|
||||
val addrInfo = socketAddr.address
|
||||
if (addrInfo.isSiteLocalAddress
|
||||
|| addrInfo.isLoopbackAddress
|
||||
|| addrInfo.isLinkLocalAddress
|
||||
|| addrInfo.isAnyLocalAddress) throw SecurityException("Local addresses aren't allowed")
|
||||
if (VIAaaSConfig.blockLocalAddress && (addrInfo.isSiteLocalAddress
|
||||
|| addrInfo.isLoopbackAddress
|
||||
|| addrInfo.isLinkLocalAddress
|
||||
|| addrInfo.isAnyLocalAddress)
|
||||
) throw SecurityException("Local addresses aren't allowed")
|
||||
|
||||
val bootstrap = Bootstrap().handler(BackendInit(handler.user))
|
||||
.channelFactory(channelSocketFactory())
|
||||
.group(handler.user.channel!!.eventLoop())
|
||||
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 15_000) // Half of mc timeout
|
||||
.connect(socketAddr)
|
||||
.channelFactory(channelSocketFactory())
|
||||
.group(handler.user.channel!!.eventLoop())
|
||||
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 15_000) // Half of mc timeout
|
||||
.connect(socketAddr)
|
||||
|
||||
bootstrap.addListener {
|
||||
if (it.isSuccess) {
|
||||
@ -161,23 +172,15 @@ class HandshakeState : MinecraftConnectionState {
|
||||
backChan.pipeline().get(CloudMinecraftHandler::class.java).other = handler.user.channel
|
||||
frontHandler.other = backChan
|
||||
|
||||
val backHandshake = ByteBufAllocator.DEFAULT.buffer()
|
||||
try {
|
||||
backHandshake.writeByte(0) // Packet 0 handshake
|
||||
Type.VAR_INT.writePrimitive(backHandshake, handler.data!!.protocolId!!)
|
||||
Type.STRING.write(backHandshake, srvResolvedAddr) // Server Address
|
||||
backHandshake.writeShort(srvResolvedPort)
|
||||
Type.VAR_INT.writePrimitive(backHandshake, nextAddr)
|
||||
backChan.writeAndFlush(backHandshake.retain())
|
||||
} finally {
|
||||
backHandshake.release()
|
||||
}
|
||||
packet.address = srvResolvedAddr
|
||||
packet.port = srvResolvedPort
|
||||
forward(handler, packet)
|
||||
backChan.flush()
|
||||
|
||||
handler.user.channel!!.setAutoRead(true)
|
||||
} else {
|
||||
handler.user.channel!!.eventLoop().submit {
|
||||
frontHandler.disconnect("Couldn't connect: " + it.cause().toString())
|
||||
}
|
||||
// We're in the event loop
|
||||
frontHandler.disconnect("Couldn't connect: " + it.cause().toString())
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
@ -199,50 +202,49 @@ class HandshakeState : MinecraftConnectionState {
|
||||
|
||||
object LoginState : MinecraftConnectionState {
|
||||
override fun handleMessage(handler: CloudMinecraftHandler, ctx: ChannelHandlerContext, msg: ByteBuf) {
|
||||
msg.markReaderIndex()
|
||||
val id = Type.VAR_INT.readPrimitive(msg)
|
||||
when {
|
||||
handler.frontEnd && id == 0 -> handleLoginStart(handler, msg)
|
||||
handler.frontEnd && id == 1 -> handleCryptoResponse(handler, msg)
|
||||
handler.frontEnd && id == 2 -> forward(handler, msg) // Plugin response
|
||||
!handler.frontEnd && id == 0 -> forward(handler, msg) // Disconnect
|
||||
!handler.frontEnd && id == 1 -> handleCryptoRequest(handler, msg)
|
||||
!handler.frontEnd && id == 2 -> handleLoginSuccess(handler, msg)
|
||||
!handler.frontEnd && id == 3 -> handleCompression(handler, msg)
|
||||
!handler.frontEnd && id == 4 -> forward(handler, msg) // Plugin request
|
||||
else -> throw IllegalArgumentException("Invalid packet ID")
|
||||
val packet = PacketRegistry.decode(
|
||||
msg,
|
||||
ProtocolVersion.getProtocol(handler.user.protocolInfo!!.serverProtocolVersion),
|
||||
State.LOGIN,
|
||||
handler.frontEnd
|
||||
)
|
||||
|
||||
when (packet) {
|
||||
is LoginStart -> handleLoginStart(handler, packet)
|
||||
is CryptoResponse -> handleCryptoResponse(handler, packet)
|
||||
is PluginResponse -> forward(handler, packet)
|
||||
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) {
|
||||
msg.resetReaderIndex()
|
||||
handler.other!!.write(msg.retain())
|
||||
private fun handleLoginSuccess(handler: CloudMinecraftHandler, loginSuccess: LoginSuccess) {
|
||||
handler.data.state = PlayState
|
||||
forward(handler, loginSuccess)
|
||||
}
|
||||
|
||||
private fun handleLoginSuccess(handler: CloudMinecraftHandler, msg: ByteBuf) {
|
||||
handler.data!!.state = PlayState
|
||||
forward(handler, msg)
|
||||
}
|
||||
|
||||
private fun handleCompression(handler: CloudMinecraftHandler, msg: ByteBuf) {
|
||||
private fun handleCompression(handler: CloudMinecraftHandler, setCompression: SetCompression) {
|
||||
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()
|
||||
backPipe.addAfter("frame", "compress", CloudCompressionCodec(threshold))
|
||||
|
||||
forward(handler, msg)
|
||||
forward(handler, setCompression)
|
||||
|
||||
pipe.addAfter("frame", "compress", CloudCompressionCodec(threshold))
|
||||
pipe.addAfter("frame", "decompress", EmptyChannelHandler()) // ViaRewind compat workaround
|
||||
}
|
||||
|
||||
fun handleCryptoRequest(handler: CloudMinecraftHandler, msg: ByteBuf) {
|
||||
val data = handler.data!!
|
||||
data.backServerId = Type.STRING.read(msg)
|
||||
data.backPublicKey = KeyFactory.getInstance("RSA")
|
||||
.generatePublic(X509EncodedKeySpec(Type.BYTE_ARRAY_PRIMITIVE.read(msg)))
|
||||
data.backToken = Type.BYTE_ARRAY_PRIMITIVE.read(msg)
|
||||
fun handleCryptoRequest(handler: CloudMinecraftHandler, cryptoRequest: CryptoRequest) {
|
||||
val data = handler.data
|
||||
data.backServerId = cryptoRequest.serverId
|
||||
data.backPublicKey = cryptoRequest.publicKey
|
||||
data.backToken = cryptoRequest.token
|
||||
|
||||
val id = "VIAaaS" + ByteArray(10).let {
|
||||
secureRandom.nextBytes(it)
|
||||
@ -257,32 +259,27 @@ object LoginState : MinecraftConnectionState {
|
||||
data.frontToken = token
|
||||
data.frontId = id
|
||||
|
||||
val backMsg = ByteBufAllocator.DEFAULT.buffer()
|
||||
try {
|
||||
backMsg.writeByte(1) // Packet id
|
||||
Type.STRING.write(backMsg, id)
|
||||
Type.BYTE_ARRAY_PRIMITIVE.write(backMsg, mcCryptoKey.public.encoded)
|
||||
Type.BYTE_ARRAY_PRIMITIVE.write(backMsg, token)
|
||||
handler.other!!.write(backMsg.retain())
|
||||
} finally {
|
||||
backMsg.release()
|
||||
}
|
||||
cryptoRequest.serverId = id
|
||||
cryptoRequest.publicKey = mcCryptoKey.public
|
||||
cryptoRequest.token = token
|
||||
|
||||
forward(handler, cryptoRequest)
|
||||
}
|
||||
|
||||
fun handleCryptoResponse(handler: CloudMinecraftHandler, msg: ByteBuf) {
|
||||
fun handleCryptoResponse(handler: CloudMinecraftHandler, cryptoResponse: CryptoResponse) {
|
||||
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?
|
||||
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 aesDe = mcCfb8(frontKey, Cipher.DECRYPT_MODE)
|
||||
|
||||
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 {
|
||||
@ -290,22 +287,25 @@ object LoginState : MinecraftConnectionState {
|
||||
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)
|
||||
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")
|
||||
"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 sessionJoin = viaWebServer.requestSessionJoin(
|
||||
fromUndashed(profile.get("id")!!.asString),
|
||||
handler.data!!.backName!!,
|
||||
backHash,
|
||||
handler.address!!, // Frontend handler
|
||||
handler.data!!.backPublicKey!!
|
||||
fromUndashed(profile.get("id")!!.asString),
|
||||
handler.data.backName!!,
|
||||
backHash,
|
||||
handler.address!!, // Frontend handler
|
||||
handler.data.backPublicKey!!
|
||||
)
|
||||
|
||||
val backChan = handler.other!!
|
||||
@ -313,21 +313,15 @@ object LoginState : MinecraftConnectionState {
|
||||
if (throwable != null) {
|
||||
handler.disconnect("Online mode error: $throwable")
|
||||
} else {
|
||||
val backMsg = ByteBufAllocator.DEFAULT.buffer()
|
||||
try {
|
||||
backMsg.writeByte(1) // Packet id
|
||||
Type.BYTE_ARRAY_PRIMITIVE.write(backMsg, encryptRsa(handler.data!!.backPublicKey!!, backKey))
|
||||
Type.BYTE_ARRAY_PRIMITIVE.write(backMsg, encryptRsa(handler.data!!.backPublicKey!!, handler.data!!.backToken!!))
|
||||
backChan.writeAndFlush(backMsg.retain())
|
||||
cryptoResponse.encryptedKey = encryptRsa(handler.data.backPublicKey!!, backKey)
|
||||
cryptoResponse.encryptedToken =
|
||||
encryptRsa(handler.data.backPublicKey!!, handler.data.backToken!!)
|
||||
forward(handler, cryptoResponse)
|
||||
backChan.flush()
|
||||
|
||||
val backAesEn = mcCfb8(backKey, Cipher.ENCRYPT_MODE)
|
||||
val backAesDe = mcCfb8(backKey, Cipher.DECRYPT_MODE)
|
||||
|
||||
|
||||
backChan.pipeline().addBefore("frame", "crypto", CloudCrypto(backAesDe, backAesEn))
|
||||
} finally {
|
||||
backMsg.release()
|
||||
}
|
||||
val backAesEn = mcCfb8(backKey, Cipher.ENCRYPT_MODE)
|
||||
val backAesDe = mcCfb8(backKey, Cipher.DECRYPT_MODE)
|
||||
backChan.pipeline().addBefore("frame", "crypto", CloudCrypto(backAesDe, backAesEn))
|
||||
}
|
||||
}, backChan.eventLoop())
|
||||
} catch (e: Exception) {
|
||||
@ -337,17 +331,12 @@ object LoginState : MinecraftConnectionState {
|
||||
}
|
||||
}
|
||||
|
||||
fun handleLoginStart(handler: CloudMinecraftHandler, msg: ByteBuf) {
|
||||
handler.data!!.frontName = Type.STRING.read(msg)
|
||||
handler.data!!.backName = handler.user.get(CloudData::class.java)!!.altName ?: handler.data!!.frontName
|
||||
val backMsg = ByteBufAllocator.DEFAULT.buffer()
|
||||
try {
|
||||
msg.writeByte(0) // Id
|
||||
Type.STRING.write(msg, handler.data!!.backName)
|
||||
handler.other!!.write(msg.retain())
|
||||
} finally {
|
||||
backMsg.release()
|
||||
}
|
||||
fun handleLoginStart(handler: CloudMinecraftHandler, loginStart: LoginStart) {
|
||||
handler.data.frontName = loginStart.username
|
||||
handler.data.backName = handler.user.get(CloudData::class.java)!!.altName ?: handler.data.frontName
|
||||
|
||||
loginStart.username = handler.data.backName!!
|
||||
forward(handler, loginStart)
|
||||
}
|
||||
|
||||
override fun disconnect(handler: CloudMinecraftHandler, msg: String) {
|
||||
@ -358,8 +347,8 @@ object LoginState : MinecraftConnectionState {
|
||||
packet.writeByte(0) // id 0 disconnect
|
||||
Type.STRING.write(packet, Gson().toJson("[VIAaaS] §c$msg"))
|
||||
handler.user
|
||||
.sendRawPacketFuture(packet.retain())
|
||||
.addListener { handler.user.channel?.close() }
|
||||
.sendRawPacketFuture(packet.retain())
|
||||
.addListener { handler.user.channel?.close() }
|
||||
} finally {
|
||||
packet.release()
|
||||
}
|
||||
@ -380,10 +369,12 @@ object StatusState : MinecraftConnectionState {
|
||||
val packet = ByteBufAllocator.DEFAULT.buffer()
|
||||
try {
|
||||
packet.writeByte(0) // id 0 disconnect
|
||||
Type.STRING.write(packet, """{"version": {"name": "VIAaaS", "protocol": -1}, "players":
|
||||
| {"max": 0, "online": 0, "sample": []}, "description": {"text": ${Gson().toJson("§c$msg")}}}""".trimMargin())
|
||||
Type.STRING.write(
|
||||
packet, """{"version": {"name": "VIAaaS", "protocol": -1}, "players":
|
||||
| {"max": 0, "online": 0, "sample": []}, "description": {"text": ${Gson().toJson("§c$msg")}}}""".trimMargin()
|
||||
)
|
||||
handler.user.sendRawPacketFuture(packet.retain())
|
||||
.addListener { handler.user.channel?.close() }
|
||||
.addListener { handler.user.channel?.close() }
|
||||
} finally {
|
||||
packet.release()
|
||||
}
|
||||
@ -422,3 +413,32 @@ fun mcCfb8(key: ByteArray, mode: Int): Cipher {
|
||||
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
|
||||
|
||||
import io.netty.channel.Channel
|
||||
import org.slf4j.LoggerFactory
|
||||
import us.myles.ViaVersion.api.PacketWrapper
|
||||
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.type.Type
|
||||
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) {
|
||||
override fun registerPackets() {
|
||||
@ -39,7 +34,8 @@ object CloudHeadProtocol : SimpleProtocol() {
|
||||
val addr = wrapper.read(Type.STRING) // Server Address
|
||||
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 backAddr = parsed.realAddress
|
||||
val backProto = parsed.protocol ?: 47
|
||||
@ -48,14 +44,17 @@ object CloudHeadProtocol : SimpleProtocol() {
|
||||
wrapper.write(Type.UNSIGNED_SHORT, backPort)
|
||||
|
||||
val playerAddr = wrapper.user().channel!!.pipeline()
|
||||
.get(CloudMinecraftHandler::class.java)!!.address
|
||||
.get(CloudMinecraftHandler::class.java)!!.address
|
||||
logger.info("Connecting $playerAddr ($playerVer) -> $backAddr:$backPort ($backProto)")
|
||||
|
||||
wrapper.user().put(CloudData(
|
||||
wrapper.user().put(
|
||||
CloudData(
|
||||
userConnection = wrapper.user(),
|
||||
backendVer = backProto,
|
||||
frontOnline = parsed.online,
|
||||
altName = parsed.altUsername))
|
||||
altName = parsed.altUsername
|
||||
)
|
||||
)
|
||||
|
||||
wrapper.passthrough(Type.VAR_INT) // Next state
|
||||
}
|
||||
@ -64,28 +63,9 @@ object CloudHeadProtocol : SimpleProtocol() {
|
||||
}
|
||||
}
|
||||
|
||||
fun Channel.setAutoRead(b: Boolean) {
|
||||
this.config().isAutoRead = b
|
||||
if (b) this.read()
|
||||
}
|
||||
|
||||
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?
|
||||
data class CloudData(
|
||||
val userConnection: UserConnection,
|
||||
var backendVer: Int,
|
||||
var frontOnline: Boolean,
|
||||
var altName: String?
|
||||
) : StoredObject(userConnection)
|
@ -41,6 +41,7 @@ import us.myles.ViaVersion.util.Config
|
||||
import java.io.File
|
||||
import java.net.InetAddress
|
||||
import java.security.KeyPairGenerator
|
||||
import java.security.SecureRandom
|
||||
import java.util.*
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
@ -60,10 +61,12 @@ val initFuture = CompletableFuture<Unit>()
|
||||
|
||||
// Minecraft doesn't have forward secrecy
|
||||
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()
|
||||
}
|
||||
|
||||
val secureRandom = if (VIAaaSConfig.useStrongRandom) SecureRandom.getInstanceStrong() else SecureRandom()
|
||||
|
||||
fun eventLoopGroup(): EventLoopGroup {
|
||||
if (VIAaaSConfig.isNativeTransportMc) {
|
||||
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 bindAddress: String get() = this.getString("bind-address", "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 {
|
||||
|
@ -7,4 +7,15 @@ bind-address: localhost
|
||||
# Host name of this instance, that will be used in the virtual host
|
||||
host-name: viaaas.localhost
|
||||
# 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:type" content="game">
|
||||
<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>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<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 acounts = document.getElementById("accounts");
|
||||
|
||||
$("#cors-proxy").val(localStorage.getItem("cors-proxy"));
|
||||
$("#cors-proxy").on("change", () => localStorage.setItem('cors-proxy', $("#cors-proxy").val()));
|
||||
$("#cors-proxy").val(localStorage.getItem("cors-proxy"));
|
||||
$("#login_submit_mc").on("click", loginMc);
|
||||
|
||||
function loginMc() {
|
||||
|
Loading…
Reference in New Issue
Block a user