close #34 close #33 close #31, add option to allow local addresses and an option to require hostname

This commit is contained in:
creeper123123321 2020-12-24 16:20:22 -03:00
parent 90fe7e80ce
commit 2bdc547b66
9 changed files with 530 additions and 205 deletions

View File

@ -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

View File

@ -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()
}
} }
} }

View File

@ -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()
}
}

View File

@ -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)
}
}

View File

@ -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)

View File

@ -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 {

View File

@ -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

View File

@ -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>

View File

@ -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() {