package space.blokk.net import io.netty.buffer.Unpooled import space.blokk.BlokkServer import space.blokk.GameMode import space.blokk.chat.TextComponent import space.blokk.net.MinecraftDataTypes.writeString import space.blokk.net.protocols.login.EncryptionRequestPacket import space.blokk.net.protocols.login.EncryptionResponsePacket import space.blokk.net.protocols.login.LoginStartPacket import space.blokk.net.protocols.login.LoginSuccessPacket import space.blokk.net.protocols.play.JoinGamePacket import space.blokk.net.protocols.play.PlayProtocol import space.blokk.net.protocols.play.PlayerAbilitiesPacket import space.blokk.net.protocols.play.ServerDifficultyPacket import space.blokk.utils.AuthenticationHelper import space.blokk.utils.EncryptionUtils import space.blokk.worlds.WorldDifficulty import space.blokk.worlds.WorldDimension import space.blokk.worlds.WorldType import java.security.MessageDigest import java.util.* import javax.crypto.Cipher import javax.crypto.spec.SecretKeySpec class JoinProcedure(val session: BlokkSession) { suspend fun start(packet: LoginStartPacket) { if (session.loginState !is BlokkSession.LoginState.NotStarted) throw IllegalStateException("loginState is not NotStarted") if (BlokkServer.instance.config.authenticateAndEncrypt) { val verifyToken = EncryptionUtils.generateVerifyToken() session.loginState = BlokkSession.LoginState.WaitingForVerification(packet.username, verifyToken) session.send(EncryptionRequestPacket(BlokkServer.instance.x509EncodedPublicKey, verifyToken)) } else { session.send( LoginSuccessPacket( UUID.nameUUIDFromBytes("OfflinePlayer:${packet.username}".toByteArray()), packet.username ) ) afterLogin() } } suspend fun onEncryptionResponse(packet: EncryptionResponsePacket) { var loginState = session.loginState if (loginState !is BlokkSession.LoginState.WaitingForVerification) throw IllegalStateException("loginState is not WaitingForVerification") val cipher = Cipher.getInstance("RSA") cipher.init(Cipher.DECRYPT_MODE, BlokkServer.instance.keyPair.private) // Decrypt verify token val verifyToken: ByteArray = cipher.doFinal(packet.verifyToken) if (!verifyToken.contentEquals((session.loginState as BlokkSession.LoginState.WaitingForVerification).verifyToken)) { session.disconnect(TextComponent of "Encryption failed.") return } // Decrypt shared secret val sharedSecretKey = SecretKeySpec(cipher.doFinal(packet.sharedSecret), "AES") loginState = BlokkSession.LoginState.Encrypted(loginState.username) session.loginState = loginState session.enableEncryption(sharedSecretKey) val hash = MessageDigest.getInstance("SHA-1") hash.update(sharedSecretKey.encoded) hash.update(BlokkServer.instance.x509EncodedPublicKey) val hashString = EncryptionUtils.toMinecraftStyleSha1String(hash.digest()) val result = AuthenticationHelper.authenticate(hashString, loginState.username) loginState = BlokkSession.LoginState.Success(result.username, result.uuid) session.loginState = loginState session.send(LoginSuccessPacket(result.uuid, result.username)) afterLogin() } private suspend fun afterLogin() { session.currentProtocol = PlayProtocol // TODO: Use real data session.send( JoinGamePacket( 0, GameMode.CREATIVE, false, WorldDimension.OVERWORLD, 12, WorldType.DEFAULT, 32, reducedDebugInfo = false, respawnScreenEnabled = true ) ) session.sendPluginMessage( "minecraft:brand", Unpooled.buffer().writeString("Blokk ${BlokkServer.VERSION_WITH_V}") ) // TODO: Use real data session.send( PlayerAbilitiesPacket( invulnerable = false, flying = false, flyingAllowed = true, instantlyBreakBlocks = true, flyingSpeed = 0.2f, fieldOfView = 0.2f ) ) joinWorld() } private suspend fun joinWorld() { session.send( ServerDifficultyPacket( WorldDifficulty.NORMAL, true ) ) } }