mirror of
https://gitlab.bixilon.de/bixilon/minosoft.git
synced 2025-09-15 02:15:34 -04:00
network: 1.19.1-pre4
This commit is contained in:
parent
1fc08c0662
commit
80027e3754
@ -15,7 +15,7 @@ package de.bixilon.minosoft.data.accounts.types.microsoft
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import de.bixilon.kutil.latch.CountUpAndDownLatch
|
||||
import de.bixilon.kutil.time.TimeUtil
|
||||
import de.bixilon.kutil.time.TimeUtil.millis
|
||||
import de.bixilon.minosoft.config.profile.profiles.account.AccountProfileManager
|
||||
import de.bixilon.minosoft.data.accounts.Account
|
||||
import de.bixilon.minosoft.data.accounts.AccountStates
|
||||
@ -80,7 +80,7 @@ class MicrosoftAccount(
|
||||
// already checking
|
||||
return
|
||||
}
|
||||
if (minecraft.expires >= TimeUtil.millis / 1000) {
|
||||
if (minecraft.expires >= millis() / 1000) {
|
||||
return check(latch, "null")
|
||||
}
|
||||
if (state == AccountStates.WORKING) {
|
||||
@ -99,7 +99,7 @@ class MicrosoftAccount(
|
||||
|
||||
private fun refreshMinecraftToken(latch: CountUpAndDownLatch?) {
|
||||
state = AccountStates.REFRESHING
|
||||
val time = TimeUtil.millis / 1000
|
||||
val time = millis() / 1000
|
||||
if (time >= msa.expires) {
|
||||
// token expired
|
||||
refreshMicrosoftToken(latch)
|
||||
@ -123,7 +123,7 @@ class MicrosoftAccount(
|
||||
|
||||
private fun checkMinecraftToken(latch: CountUpAndDownLatch?) {
|
||||
state = AccountStates.CHECKING
|
||||
val time = TimeUtil.millis / 1000
|
||||
val time = millis() / 1000
|
||||
if (time >= minecraft.expires) {
|
||||
// token expired
|
||||
refreshMinecraftToken(latch)
|
||||
@ -143,7 +143,7 @@ class MicrosoftAccount(
|
||||
|
||||
override fun fetchKey(latch: CountUpAndDownLatch?): MinecraftPrivateKey {
|
||||
var key = key
|
||||
if (key == null || key.isExpired()) {
|
||||
if (key == null || key.isExpired() || key.signatureV2 == null) {
|
||||
key = AccountUtil.fetchPrivateKey(minecraft)
|
||||
this.key = key
|
||||
save()
|
||||
|
@ -13,12 +13,12 @@
|
||||
|
||||
package de.bixilon.minosoft.data.chat.message
|
||||
|
||||
import de.bixilon.minosoft.data.chat.ChatMessageTypes
|
||||
import de.bixilon.minosoft.data.chat.type.DefaultMessageTypes
|
||||
import de.bixilon.minosoft.data.text.ChatComponent
|
||||
import java.util.*
|
||||
|
||||
open class PlayerChatMessage(
|
||||
text: ChatComponent,
|
||||
type: ChatMessageTypes,
|
||||
type: DefaultMessageTypes,
|
||||
val sender: UUID,
|
||||
) : SimpleChatMessage(text, type)
|
||||
|
@ -13,10 +13,10 @@
|
||||
|
||||
package de.bixilon.minosoft.data.chat.message
|
||||
|
||||
import de.bixilon.minosoft.data.chat.ChatMessageTypes
|
||||
import de.bixilon.minosoft.data.chat.type.DefaultMessageTypes
|
||||
import de.bixilon.minosoft.data.text.ChatComponent
|
||||
|
||||
open class SimpleChatMessage(
|
||||
override val text: ChatComponent,
|
||||
val type: ChatMessageTypes,
|
||||
val type: DefaultMessageTypes,
|
||||
) : ChatMessage
|
||||
|
@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Minosoft
|
||||
* Copyright (C) 2020-2022 Moritz Zwerger and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
|
||||
*/
|
||||
|
||||
package de.bixilon.minosoft.data.chat.signature
|
||||
|
||||
import com.fasterxml.jackson.core.io.JsonStringEncoder
|
||||
import com.google.common.hash.Hashing
|
||||
import com.google.common.primitives.Longs
|
||||
import de.bixilon.minosoft.data.registries.versions.Version
|
||||
import de.bixilon.minosoft.data.text.ChatComponent
|
||||
import de.bixilon.minosoft.protocol.ProtocolUtil.encodeNetwork
|
||||
import de.bixilon.minosoft.protocol.protocol.OutByteBuffer
|
||||
import de.bixilon.minosoft.protocol.protocol.ProtocolVersions
|
||||
import de.bixilon.minosoft.protocol.protocol.encryption.CryptManager
|
||||
import java.security.PrivateKey
|
||||
import java.time.Instant
|
||||
import java.util.*
|
||||
|
||||
class MessageChain {
|
||||
private var previous: ByteArray? = null
|
||||
|
||||
fun signMessage(version: Version, privateKey: PrivateKey, message: String, preview: ChatComponent?, salt: Long, sender: UUID, time: Instant): ByteArray {
|
||||
val signature = CryptManager.createSignature(version)
|
||||
|
||||
signature.initSign(privateKey)
|
||||
|
||||
if (version < ProtocolVersions.V_1_19_1_PRE4) {
|
||||
signature.update(Longs.toByteArray(salt))
|
||||
signature.update(Longs.toByteArray(sender.mostSignificantBits))
|
||||
signature.update(Longs.toByteArray(sender.leastSignificantBits))
|
||||
signature.update(Longs.toByteArray(time.epochSecond))
|
||||
signature.update(message.getSignatureBytes())
|
||||
} else {
|
||||
val buffer = OutByteBuffer()
|
||||
buffer.writeLong(salt)
|
||||
buffer.writeLong(time.epochSecond)
|
||||
buffer.writeBareByteArray(message.getSignatureBytes())
|
||||
// TODo: lastseen
|
||||
val hash = Hashing.sha256().hashBytes(buffer.toArray()).asBytes()
|
||||
previous?.let { signature.update(it) }
|
||||
signature.update(Longs.toByteArray(sender.mostSignificantBits))
|
||||
signature.update(Longs.toByteArray(sender.leastSignificantBits))
|
||||
signature.update(hash)
|
||||
}
|
||||
|
||||
val singed = signature.sign()
|
||||
this.previous = singed
|
||||
|
||||
return singed
|
||||
}
|
||||
|
||||
private fun String.getSignatureBytes(): ByteArray {
|
||||
return """{"text":"${String(JsonStringEncoder.getInstance().quoteAsString(this))}"}""".encodeNetwork()
|
||||
}
|
||||
}
|
@ -13,6 +13,7 @@
|
||||
|
||||
package de.bixilon.minosoft.data.chat.signature
|
||||
|
||||
import de.bixilon.minosoft.data.chat.type.MessageType
|
||||
import de.bixilon.minosoft.data.text.ChatComponent
|
||||
|
||||
data class SignedMessage(
|
||||
@ -20,4 +21,5 @@ data class SignedMessage(
|
||||
val signature: ByteArray,
|
||||
val body: MessageBody,
|
||||
val unsigned: ChatComponent? = null,
|
||||
val type: MessageType? = null,
|
||||
)
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Minosoft
|
||||
* Copyright (C) 2020-2022 Moritz Zwerger
|
||||
* Copyright (C) 2020-2022 Moritz Zwerger and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
@ -11,12 +11,13 @@
|
||||
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
|
||||
*/
|
||||
|
||||
package de.bixilon.minosoft.data.chat
|
||||
package de.bixilon.minosoft.data.chat.type
|
||||
|
||||
import de.bixilon.kutil.enums.EnumUtil
|
||||
import de.bixilon.kutil.enums.ValuesEnum
|
||||
import de.bixilon.minosoft.data.chat.ChatTextPositions
|
||||
|
||||
enum class ChatMessageTypes(val position: ChatTextPositions = ChatTextPositions.CHAT) {
|
||||
enum class DefaultMessageTypes(val position: ChatTextPositions = ChatTextPositions.CHAT) {
|
||||
CHAT_MESSAGE,
|
||||
SYSTEM_MESSAGE,
|
||||
GAME_MESSAGE,
|
||||
@ -28,8 +29,8 @@ enum class ChatMessageTypes(val position: ChatTextPositions = ChatTextPositions.
|
||||
COMMAND_TELLRAW,
|
||||
;
|
||||
|
||||
companion object : ValuesEnum<ChatMessageTypes> {
|
||||
override val VALUES: Array<ChatMessageTypes> = values()
|
||||
override val NAME_MAP: Map<String, ChatMessageTypes> = EnumUtil.getEnumValues(VALUES)
|
||||
companion object : ValuesEnum<DefaultMessageTypes> {
|
||||
override val VALUES: Array<DefaultMessageTypes> = values()
|
||||
override val NAME_MAP: Map<String, DefaultMessageTypes> = EnumUtil.getEnumValues(VALUES)
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Minosoft
|
||||
* Copyright (C) 2020-2022 Moritz Zwerger and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
|
||||
*/
|
||||
|
||||
package de.bixilon.minosoft.data.chat.type
|
||||
|
||||
import de.bixilon.minosoft.data.text.ChatComponent
|
||||
|
||||
@Deprecated("Refactor")
|
||||
class MessageType(
|
||||
val id: Int,
|
||||
val text: ChatComponent,
|
||||
val targetName: ChatComponent?,
|
||||
)
|
@ -12,7 +12,7 @@
|
||||
*/
|
||||
package de.bixilon.minosoft.modding.event.events
|
||||
|
||||
import de.bixilon.minosoft.data.chat.ChatMessageTypes
|
||||
import de.bixilon.minosoft.data.chat.type.DefaultMessageTypes
|
||||
import de.bixilon.minosoft.data.text.ChatComponent
|
||||
import de.bixilon.minosoft.modding.event.EventInitiators
|
||||
import de.bixilon.minosoft.modding.event.events.connection.play.PlayConnectionEvent
|
||||
@ -26,14 +26,14 @@ class ChatMessageReceiveEvent(
|
||||
connection: PlayConnection,
|
||||
initiator: EventInitiators,
|
||||
val message: ChatComponent,
|
||||
val type: ChatMessageTypes,
|
||||
val type: DefaultMessageTypes,
|
||||
val sender: UUID?,
|
||||
) : PlayConnectionEvent(connection, initiator), CancelableEvent {
|
||||
|
||||
|
||||
constructor(connection: PlayConnection, packet: ChatMessageS2CP) : this(connection, EventInitiators.SERVER, packet.message, packet.type, packet.sender)
|
||||
|
||||
constructor(connection: PlayConnection, packet: HotbarTextS2CP) : this(connection, EventInitiators.SERVER, packet.text, ChatMessageTypes.GAME_MESSAGE, null)
|
||||
constructor(connection: PlayConnection, packet: HotbarTextS2CP) : this(connection, EventInitiators.SERVER, packet.text, DefaultMessageTypes.GAME_MESSAGE, null)
|
||||
|
||||
constructor(connection: PlayConnection, packet: SignedChatMessageS2CP) : this(connection, EventInitiators.SERVER, packet.message, packet.type, packet.sender.uuid)
|
||||
constructor(connection: PlayConnection, packet: SignedChatMessageS2CP) : this(connection, EventInitiators.SERVER, packet.message.body.text, DefaultMessageTypes.CHAT_MESSAGE, packet.message.header.sender)
|
||||
}
|
||||
|
@ -17,12 +17,16 @@ import de.bixilon.kutil.base64.Base64Util.fromBase64
|
||||
import de.bixilon.kutil.base64.Base64Util.toBase64
|
||||
import de.bixilon.kutil.json.JsonObject
|
||||
import de.bixilon.kutil.primitive.LongUtil.toLong
|
||||
import de.bixilon.minosoft.protocol.protocol.ProtocolVersions
|
||||
import de.bixilon.minosoft.protocol.protocol.encryption.CryptManager
|
||||
import de.bixilon.minosoft.protocol.protocol.encryption.CryptManager.encodeNetwork
|
||||
import de.bixilon.minosoft.util.account.minecraft.key.MinecraftKeyPair
|
||||
import de.bixilon.minosoft.util.yggdrasil.YggdrasilException
|
||||
import de.bixilon.minosoft.util.yggdrasil.YggdrasilUtil
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.security.PublicKey
|
||||
import java.time.Instant
|
||||
import java.util.*
|
||||
|
||||
class PlayerPublicKey(
|
||||
val expiresAt: Instant,
|
||||
@ -30,10 +34,6 @@ class PlayerPublicKey(
|
||||
val signature: ByteArray,
|
||||
) {
|
||||
|
||||
init {
|
||||
check(isSignatureCorrect()) { "Yggdrasil signature invalid" }
|
||||
}
|
||||
|
||||
constructor(nbt: JsonObject) : this(Instant.ofEpochMilli(nbt["expires_at"].toLong()), CryptManager.getPlayerPublicKey(nbt["key"].toString()), nbt["signature"].toString().fromBase64())
|
||||
|
||||
fun toNbt(): JsonObject {
|
||||
@ -44,9 +44,21 @@ class PlayerPublicKey(
|
||||
)
|
||||
}
|
||||
|
||||
private fun isSignatureCorrect(): Boolean {
|
||||
fun isLegacySignatureCorrect(): Boolean {
|
||||
val bytes = (expiresAt.toEpochMilli().toString() + publicKey.encodeNetwork()).toByteArray(StandardCharsets.US_ASCII)
|
||||
|
||||
return YggdrasilUtil.verify(bytes, signature)
|
||||
}
|
||||
|
||||
fun isSignatureCorrect(uuid: UUID): Boolean {
|
||||
return MinecraftKeyPair.isSignatureCorrect(uuid, expiresAt, publicKey, signature)
|
||||
}
|
||||
|
||||
fun requireSignature(versionId: Int, uuid: UUID) {
|
||||
if (versionId < ProtocolVersions.V_1_19_1_PRE4) {
|
||||
if (!isLegacySignatureCorrect()) throw YggdrasilException()
|
||||
} else {
|
||||
if (!isSignatureCorrect(uuid)) throw YggdrasilException()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -13,11 +13,10 @@
|
||||
|
||||
package de.bixilon.minosoft.protocol.network.connection.play
|
||||
|
||||
import com.fasterxml.jackson.core.io.JsonStringEncoder
|
||||
import com.google.common.primitives.Longs
|
||||
import de.bixilon.kotlinglm.vec3.Vec3d
|
||||
import de.bixilon.kutil.string.WhitespaceUtil.trimWhitespaces
|
||||
import de.bixilon.minosoft.commands.stack.CommandStack
|
||||
import de.bixilon.minosoft.data.chat.signature.MessageChain
|
||||
import de.bixilon.minosoft.data.text.BaseComponent
|
||||
import de.bixilon.minosoft.data.text.ChatComponent
|
||||
import de.bixilon.minosoft.data.text.formatting.color.ChatColors
|
||||
@ -30,17 +29,18 @@ import de.bixilon.minosoft.protocol.ProtocolUtil.encodeNetwork
|
||||
import de.bixilon.minosoft.protocol.packets.c2s.play.chat.ChatMessageC2SP
|
||||
import de.bixilon.minosoft.protocol.packets.c2s.play.chat.SignedChatMessageC2SP
|
||||
import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition
|
||||
import de.bixilon.minosoft.protocol.protocol.encryption.CryptManager
|
||||
import de.bixilon.minosoft.protocol.protocol.encryption.SignatureData
|
||||
import de.bixilon.minosoft.util.logging.Log
|
||||
import de.bixilon.minosoft.util.logging.LogLevels
|
||||
import de.bixilon.minosoft.util.logging.LogMessageType
|
||||
import java.security.PrivateKey
|
||||
import java.security.SecureRandom
|
||||
import java.time.Instant
|
||||
|
||||
class ConnectionUtil(
|
||||
private val connection: PlayConnection,
|
||||
) {
|
||||
private val chain = MessageChain()
|
||||
private val random = SecureRandom()
|
||||
|
||||
fun sendDebugMessage(message: Any) {
|
||||
@ -75,22 +75,17 @@ class ConnectionUtil(
|
||||
if (privateKey == null || !connection.version.requiresSignedChat) {
|
||||
return connection.sendPacket(ChatMessageC2SP(message))
|
||||
}
|
||||
sendSignedMessage(privateKey, message)
|
||||
}
|
||||
|
||||
val signature = CryptManager.createSignature(connection.version)
|
||||
|
||||
fun sendSignedMessage(privateKey: PrivateKey = connection.player.privateKey?.private!!, message: String) {
|
||||
val salt = random.nextLong()
|
||||
val time = Instant.now()
|
||||
val uuid = connection.player.uuid
|
||||
|
||||
signature.initSign(privateKey)
|
||||
val signature = chain.signMessage(connection.version, privateKey, message, null, salt, uuid, time)
|
||||
|
||||
signature.update(Longs.toByteArray(salt))
|
||||
signature.update(Longs.toByteArray(uuid.mostSignificantBits))
|
||||
signature.update(Longs.toByteArray(uuid.leastSignificantBits))
|
||||
signature.update(Longs.toByteArray(time.epochSecond))
|
||||
signature.update("""{"text":"${String(JsonStringEncoder.getInstance().quoteAsString(message))}"}""".encodeNetwork())
|
||||
|
||||
connection.sendPacket(SignedChatMessageC2SP(message.encodeNetwork(), time = time, signature = SignatureData(salt, signature.sign()), false))
|
||||
connection.sendPacket(SignedChatMessageC2SP(message.encodeNetwork(), time = time, salt = salt, signature = SignatureData(signature), false))
|
||||
}
|
||||
|
||||
@Deprecated("message will re removed as soon as brigadier is fully implemented")
|
||||
|
@ -56,7 +56,6 @@ import de.bixilon.minosoft.protocol.network.connection.play.tick.ConnectionTicke
|
||||
import de.bixilon.minosoft.protocol.packets.c2s.handshaking.HandshakeC2SP
|
||||
import de.bixilon.minosoft.protocol.packets.c2s.login.StartC2SP
|
||||
import de.bixilon.minosoft.protocol.protocol.ProtocolStates
|
||||
import de.bixilon.minosoft.protocol.protocol.encryption.CryptManager
|
||||
import de.bixilon.minosoft.terminal.RunConfiguration
|
||||
import de.bixilon.minosoft.terminal.cli.CLI
|
||||
import de.bixilon.minosoft.util.ServerAddress
|
||||
@ -189,12 +188,12 @@ class PlayConnection(
|
||||
if (version.requiresSignedChat) {
|
||||
taskWorker += WorkerTask(optional = true) {
|
||||
val minecraftKey = account.fetchKey(latch) ?: return@WorkerTask
|
||||
minecraftKey.requireSignature()
|
||||
minecraftKey.requireSignature(account.uuid)
|
||||
privateKey = PlayerPrivateKey(
|
||||
expiresAt = minecraftKey.expiresAt,
|
||||
signature = minecraftKey.signatureBytes,
|
||||
private = CryptManager.getPlayerPrivateKey(minecraftKey.pair.private),
|
||||
public = CryptManager.getPlayerPublicKey(minecraftKey.pair.public),
|
||||
signature = minecraftKey.getSignature(version.versionId),
|
||||
private = minecraftKey.pair.private,
|
||||
public = minecraftKey.pair.public,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ import de.bixilon.minosoft.protocol.packets.factory.LoadPacket
|
||||
import de.bixilon.minosoft.protocol.protocol.PlayOutByteBuffer
|
||||
import de.bixilon.minosoft.protocol.protocol.ProtocolStates
|
||||
import de.bixilon.minosoft.protocol.protocol.ProtocolVersions
|
||||
import de.bixilon.minosoft.protocol.protocol.encryption.SignatureData
|
||||
import de.bixilon.minosoft.protocol.protocol.encryption.EncryptionSignatureData
|
||||
import de.bixilon.minosoft.util.logging.Log
|
||||
import de.bixilon.minosoft.util.logging.LogLevels
|
||||
import de.bixilon.minosoft.util.logging.LogMessageType
|
||||
@ -31,7 +31,7 @@ class EncryptionC2SP private constructor(
|
||||
) : PlayC2SPacket {
|
||||
|
||||
constructor(secret: ByteArray, nonce: ByteArray) : this(secret, nonce as Any)
|
||||
constructor(secret: ByteArray, nonce: SignatureData) : this(secret, nonce as Any)
|
||||
constructor(secret: ByteArray, nonce: EncryptionSignatureData) : this(secret, nonce as Any)
|
||||
|
||||
override fun write(buffer: PlayOutByteBuffer) {
|
||||
buffer.writeByteArray(secret)
|
||||
@ -43,7 +43,9 @@ class EncryptionC2SP private constructor(
|
||||
buffer.writeByteArray(nonce)
|
||||
} else {
|
||||
buffer.writeBoolean(false)
|
||||
buffer.writeSignatureData(nonce as SignatureData)
|
||||
val nonce = nonce as EncryptionSignatureData
|
||||
buffer.writeLong(nonce.salt)
|
||||
buffer.writeSignatureData(nonce.signatureData)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ import java.time.Instant
|
||||
class CommandC2SP(
|
||||
val command: String,
|
||||
val time: Instant = Instant.now(),
|
||||
val salt: Long,
|
||||
val signature: CommandArgumentSignature,
|
||||
val signedPreview: Boolean,
|
||||
) : PlayC2SPacket {
|
||||
@ -32,7 +33,7 @@ class CommandC2SP(
|
||||
override fun write(buffer: PlayOutByteBuffer) {
|
||||
buffer.writeString(command)
|
||||
buffer.writeInstant(time)
|
||||
buffer.writeLong(signature.salt)
|
||||
buffer.writeLong(salt)
|
||||
buffer.writeVarInt(signature.signatures.size)
|
||||
for ((argument, signature) in signature.signatures) {
|
||||
buffer.writeString(argument)
|
||||
@ -44,12 +45,11 @@ class CommandC2SP(
|
||||
}
|
||||
|
||||
override fun log(reducedLog: Boolean) {
|
||||
Log.log(LogMessageType.NETWORK_PACKETS_OUT, LogLevels.VERBOSE) { "Chat message (message=$command, time=$time, signature=$signature)" }
|
||||
Log.log(LogMessageType.NETWORK_PACKETS_OUT, LogLevels.VERBOSE) { "Command (message=$command, time=$time, signature=$signature)" }
|
||||
}
|
||||
|
||||
|
||||
data class CommandArgumentSignature(
|
||||
val salt: Long,
|
||||
val signatures: Map<String, ByteArray>,
|
||||
)
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ import java.time.Instant
|
||||
class SignedChatMessageC2SP(
|
||||
val message: ByteArray,
|
||||
val time: Instant = Instant.now(),
|
||||
val salt: Long,
|
||||
val signature: SignatureData? = null,
|
||||
val previewed: Boolean = false,
|
||||
) : PlayC2SPacket {
|
||||
@ -38,6 +39,7 @@ class SignedChatMessageC2SP(
|
||||
if (buffer.versionId >= ProtocolVersions.V_22W18A) {
|
||||
buffer.writeInstant(time)
|
||||
}
|
||||
buffer.writeLong(salt)
|
||||
buffer.writeSignatureData(signature ?: SignatureData.EMPTY)
|
||||
if (buffer.versionId >= ProtocolVersions.V_22W19A) {
|
||||
buffer.writeBoolean(previewed)
|
||||
|
@ -23,6 +23,7 @@ import de.bixilon.minosoft.protocol.packets.s2c.PlayS2CPacket
|
||||
import de.bixilon.minosoft.protocol.protocol.PlayInByteBuffer
|
||||
import de.bixilon.minosoft.protocol.protocol.ProtocolStates
|
||||
import de.bixilon.minosoft.protocol.protocol.encryption.CryptManager
|
||||
import de.bixilon.minosoft.protocol.protocol.encryption.EncryptionSignatureData
|
||||
import de.bixilon.minosoft.protocol.protocol.encryption.SignatureData
|
||||
import de.bixilon.minosoft.util.logging.Log
|
||||
import de.bixilon.minosoft.util.logging.LogLevels
|
||||
@ -57,7 +58,7 @@ class EncryptionS2CP(buffer: PlayInByteBuffer) : PlayS2CPacket {
|
||||
signature.update(Longs.toByteArray(salt))
|
||||
val signed = signature.sign()
|
||||
|
||||
connection.sendPacket(EncryptionC2SP(encryptedSecretKey, SignatureData(salt, signed)))
|
||||
connection.sendPacket(EncryptionC2SP(encryptedSecretKey, EncryptionSignatureData(salt, SignatureData(signed))))
|
||||
} else {
|
||||
connection.sendPacket(EncryptionC2SP(encryptedSecretKey, CryptManager.encryptData(publicKey, nonce)))
|
||||
}
|
||||
|
@ -12,7 +12,7 @@
|
||||
*/
|
||||
package de.bixilon.minosoft.protocol.packets.s2c.play.chat
|
||||
|
||||
import de.bixilon.minosoft.data.chat.ChatMessageTypes
|
||||
import de.bixilon.minosoft.data.chat.type.DefaultMessageTypes
|
||||
import de.bixilon.minosoft.data.text.ChatComponent
|
||||
import de.bixilon.minosoft.modding.event.events.ChatMessageReceiveEvent
|
||||
import de.bixilon.minosoft.protocol.network.connection.play.PlayConnection
|
||||
@ -29,7 +29,7 @@ import java.util.*
|
||||
@LoadPacket(threadSafe = false)
|
||||
class ChatMessageS2CP(buffer: PlayInByteBuffer) : PlayS2CPacket {
|
||||
val message: ChatComponent = buffer.readChatComponent()
|
||||
var type: ChatMessageTypes = ChatMessageTypes.CHAT_MESSAGE
|
||||
var type: DefaultMessageTypes = DefaultMessageTypes.CHAT_MESSAGE
|
||||
private set
|
||||
var sender: UUID? = null
|
||||
private set
|
||||
@ -42,7 +42,7 @@ class ChatMessageS2CP(buffer: PlayInByteBuffer) : PlayS2CPacket {
|
||||
if (buffer.versionId >= ProtocolVersions.V_1_19_1_PRE2) {
|
||||
overlay = buffer.readBoolean()
|
||||
} else {
|
||||
type = ChatMessageTypes[buffer.readVarInt()]
|
||||
type = DefaultMessageTypes[buffer.readVarInt()]
|
||||
if (buffer.versionId >= ProtocolVersions.V_20W21A && buffer.versionId < ProtocolVersions.V_22W17A) {
|
||||
sender = buffer.readUUID()
|
||||
}
|
||||
|
@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Minosoft
|
||||
* Copyright (C) 2020-2022 Moritz Zwerger
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
|
||||
*/
|
||||
package de.bixilon.minosoft.protocol.packets.s2c.play.chat
|
||||
|
||||
import de.bixilon.minosoft.protocol.packets.factory.LoadPacket
|
||||
import de.bixilon.minosoft.protocol.packets.s2c.PlayS2CPacket
|
||||
import de.bixilon.minosoft.protocol.protocol.PlayInByteBuffer
|
||||
import de.bixilon.minosoft.util.logging.Log
|
||||
import de.bixilon.minosoft.util.logging.LogLevels
|
||||
import de.bixilon.minosoft.util.logging.LogMessageType
|
||||
|
||||
@LoadPacket(threadSafe = false)
|
||||
class HideMessageS2CP(buffer: PlayInByteBuffer) : PlayS2CPacket {
|
||||
val signature = buffer.readByteArray()
|
||||
|
||||
override fun log(reducedLog: Boolean) {
|
||||
Log.log(LogMessageType.NETWORK_PACKETS_IN, level = LogLevels.VERBOSE) { "Hide message (signature=$signature)" }
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Minosoft
|
||||
* Copyright (C) 2020-2022 Moritz Zwerger
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
|
||||
*/
|
||||
package de.bixilon.minosoft.protocol.packets.s2c.play.chat
|
||||
|
||||
import de.bixilon.minosoft.protocol.packets.factory.LoadPacket
|
||||
import de.bixilon.minosoft.protocol.packets.s2c.PlayS2CPacket
|
||||
import de.bixilon.minosoft.protocol.protocol.PlayInByteBuffer
|
||||
import de.bixilon.minosoft.util.logging.Log
|
||||
import de.bixilon.minosoft.util.logging.LogLevels
|
||||
import de.bixilon.minosoft.util.logging.LogMessageType
|
||||
|
||||
@LoadPacket(threadSafe = false)
|
||||
class MessageHeaderS2CP(buffer: PlayInByteBuffer) : PlayS2CPacket {
|
||||
val header = buffer.readMessageHeader()
|
||||
val signature = buffer.readByteArray()
|
||||
val bodyDigest = buffer.readByteArray()
|
||||
|
||||
override fun log(reducedLog: Boolean) {
|
||||
Log.log(LogMessageType.NETWORK_PACKETS_IN, level = LogLevels.VERBOSE) { "Message header (header=$header, signature=$signature, bodyDigest=$bodyDigest)" }
|
||||
}
|
||||
}
|
@ -12,25 +12,18 @@
|
||||
*/
|
||||
package de.bixilon.minosoft.protocol.packets.s2c.play.chat
|
||||
|
||||
import de.bixilon.minosoft.data.chat.ChatMessageTypes
|
||||
import de.bixilon.minosoft.modding.event.events.ChatMessageReceiveEvent
|
||||
import de.bixilon.minosoft.protocol.network.connection.play.PlayConnection
|
||||
import de.bixilon.minosoft.protocol.packets.factory.LoadPacket
|
||||
import de.bixilon.minosoft.protocol.packets.s2c.PlayS2CPacket
|
||||
import de.bixilon.minosoft.protocol.protocol.PlayInByteBuffer
|
||||
import de.bixilon.minosoft.protocol.protocol.ProtocolVersions
|
||||
import de.bixilon.minosoft.util.logging.Log
|
||||
import de.bixilon.minosoft.util.logging.LogLevels
|
||||
import de.bixilon.minosoft.util.logging.LogMessageType
|
||||
|
||||
@LoadPacket(threadSafe = false)
|
||||
class SignedChatMessageS2CP(buffer: PlayInByteBuffer) : PlayS2CPacket {
|
||||
val message = buffer.readChatComponent()
|
||||
val unsignedContent = if (buffer.versionId >= ProtocolVersions.V_22W19A) buffer.readOptional { readChatComponent() } else null
|
||||
var type = ChatMessageTypes[buffer.readVarInt()]
|
||||
val sender = buffer.readChatMessageSender()
|
||||
val sendingTime = buffer.readInstant()
|
||||
val signatureData = buffer.readSignatureData()
|
||||
val message = buffer.readSignedMessage()
|
||||
|
||||
override fun handle(connection: PlayConnection) {
|
||||
val event = ChatMessageReceiveEvent(connection, this)
|
||||
@ -40,6 +33,6 @@ class SignedChatMessageS2CP(buffer: PlayInByteBuffer) : PlayS2CPacket {
|
||||
}
|
||||
|
||||
override fun log(reducedLog: Boolean) {
|
||||
Log.log(LogMessageType.NETWORK_PACKETS_IN, level = LogLevels.VERBOSE) { "Chat message (message=\"$message\", type=$type, sender=$sender, sendingTime=$sendingTime, signateDate=$signatureData)" }
|
||||
Log.log(LogMessageType.NETWORK_PACKETS_IN, level = LogLevels.VERBOSE) { "Chat message (message=$message)" }
|
||||
}
|
||||
}
|
||||
|
@ -53,6 +53,7 @@ class TabListS2CP(buffer: PlayInByteBuffer) : PlayS2CPacket {
|
||||
null
|
||||
}
|
||||
val publicKey = if (buffer.versionId >= ProtocolVersions.V_22W18A) buffer.readOptional { buffer.readPlayerPublicKey() } else null
|
||||
publicKey?.requireSignature(buffer.versionId, uuid)
|
||||
data = TabListItemData(
|
||||
name = name,
|
||||
properties = properties,
|
||||
@ -62,12 +63,15 @@ class TabListS2CP(buffer: PlayInByteBuffer) : PlayS2CPacket {
|
||||
publicKey = publicKey,
|
||||
)
|
||||
}
|
||||
|
||||
TabListItemActions.UPDATE_GAMEMODE -> {
|
||||
data = TabListItemData(gamemode = Gamemodes[buffer.readVarInt()])
|
||||
}
|
||||
|
||||
TabListItemActions.UPDATE_LATENCY -> {
|
||||
data = TabListItemData(ping = buffer.readVarInt())
|
||||
}
|
||||
|
||||
TabListItemActions.UPDATE_DISPLAY_NAME -> {
|
||||
val hasDisplayName = buffer.readBoolean()
|
||||
val displayName = if (hasDisplayName) {
|
||||
@ -80,6 +84,7 @@ class TabListS2CP(buffer: PlayInByteBuffer) : PlayS2CPacket {
|
||||
displayName = displayName,
|
||||
)
|
||||
}
|
||||
|
||||
TabListItemActions.REMOVE_PLAYER -> {
|
||||
data = null
|
||||
}
|
||||
|
@ -26,6 +26,8 @@ import de.bixilon.minosoft.data.chat.signature.LastSeenMessage
|
||||
import de.bixilon.minosoft.data.chat.signature.MessageBody
|
||||
import de.bixilon.minosoft.data.chat.signature.MessageHeader
|
||||
import de.bixilon.minosoft.data.chat.signature.SignedMessage
|
||||
import de.bixilon.minosoft.data.chat.type.DefaultMessageTypes
|
||||
import de.bixilon.minosoft.data.chat.type.MessageType
|
||||
import de.bixilon.minosoft.data.container.ItemStackUtil
|
||||
import de.bixilon.minosoft.data.container.stack.ItemStack
|
||||
import de.bixilon.minosoft.data.entities.entities.player.properties.PlayerProperties
|
||||
@ -282,21 +284,6 @@ class PlayInByteBuffer : InByteBuffer {
|
||||
}
|
||||
}
|
||||
|
||||
fun readChatMessageSender(): ChatMessageSender {
|
||||
return ChatMessageSender(readUUID(), readChatComponent(), if (versionId >= ProtocolVersions.V_22W18A) readOptional { readChatComponent() } else null)
|
||||
}
|
||||
|
||||
fun readSignatureData(): SignatureData {
|
||||
return SignatureData(readLong(), readByteArray())
|
||||
}
|
||||
|
||||
fun readPlayerPublicKey(): PlayerPublicKey? {
|
||||
if (versionId <= ProtocolVersions.V_22W18A) { // ToDo: find version
|
||||
return readNBT()?.let { PlayerPublicKey(it.asJsonObject()) }
|
||||
}
|
||||
return PlayerPublicKey(readInstant(), CryptManager.getPlayerPublicKey(readByteArray()), readByteArray())
|
||||
}
|
||||
|
||||
fun readInstant(): Instant {
|
||||
val time = readLong()
|
||||
if (versionId >= ProtocolVersions.V_22W19A) {
|
||||
@ -360,6 +347,22 @@ class PlayInByteBuffer : InByteBuffer {
|
||||
return registry[readVarInt()]
|
||||
}
|
||||
|
||||
|
||||
fun readChatMessageSender(): ChatMessageSender {
|
||||
return ChatMessageSender(readUUID(), readChatComponent(), if (versionId >= ProtocolVersions.V_22W18A) readOptional { readChatComponent() } else null)
|
||||
}
|
||||
|
||||
fun readSignatureData(): SignatureData {
|
||||
return SignatureData(readByteArray())
|
||||
}
|
||||
|
||||
fun readPlayerPublicKey(): PlayerPublicKey? {
|
||||
if (versionId <= ProtocolVersions.V_22W18A) { // ToDo: find version
|
||||
return readNBT()?.let { PlayerPublicKey(it.asJsonObject()) }
|
||||
}
|
||||
return PlayerPublicKey(readInstant(), CryptManager.getPlayerPublicKey(readByteArray()), readByteArray())
|
||||
}
|
||||
|
||||
fun readMessageHeader(): MessageHeader {
|
||||
return MessageHeader(readOptional { readByteArray() }, readUUID())
|
||||
}
|
||||
@ -377,7 +380,22 @@ class PlayInByteBuffer : InByteBuffer {
|
||||
)
|
||||
}
|
||||
|
||||
fun readMessageType(): MessageType {
|
||||
return MessageType(readVarInt(), readChatComponent(), readOptional { readChatComponent() })
|
||||
}
|
||||
|
||||
fun readSignedMessage(): SignedMessage {
|
||||
return SignedMessage(readMessageHeader(), readByteArray(), readMessageBody(), readOptional { readChatComponent() })
|
||||
if (versionId < ProtocolVersions.V_1_19_1_PRE4) {
|
||||
val message = readChatComponent()
|
||||
val unsignedContent = if (versionId >= ProtocolVersions.V_22W19A) readOptional { readChatComponent() } else null
|
||||
var type = DefaultMessageTypes[readVarInt()]
|
||||
val sender = readChatMessageSender()
|
||||
val sendingTime = readInstant()
|
||||
val salt = readLong()
|
||||
val signatureData = readSignatureData()
|
||||
|
||||
TODO("return message, refactor")
|
||||
}
|
||||
return SignedMessage(readMessageHeader(), readByteArray(), readMessageBody(), readOptional { readChatComponent() }, readMessageType())
|
||||
}
|
||||
}
|
||||
|
@ -87,7 +87,6 @@ class PlayOutByteBuffer(val connection: PlayConnection) : OutByteBuffer() {
|
||||
}
|
||||
|
||||
fun writeSignatureData(signature: SignatureData) {
|
||||
writeLong(signature.salt)
|
||||
writeByteArray(signature.signature)
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Minosoft
|
||||
* Copyright (C) 2020-2022 Moritz Zwerger and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
|
||||
*/
|
||||
|
||||
package de.bixilon.minosoft.protocol.protocol.encryption
|
||||
|
||||
class EncryptionSignatureData(
|
||||
val salt: Long,
|
||||
val signatureData: SignatureData,
|
||||
)
|
@ -14,10 +14,9 @@
|
||||
package de.bixilon.minosoft.protocol.protocol.encryption
|
||||
|
||||
data class SignatureData(
|
||||
val salt: Long,
|
||||
val signature: ByteArray,
|
||||
) {
|
||||
companion object {
|
||||
val EMPTY = SignatureData(0L, byteArrayOf())
|
||||
val EMPTY = SignatureData(byteArrayOf())
|
||||
}
|
||||
}
|
||||
|
@ -16,32 +16,39 @@ package de.bixilon.minosoft.util.account.minecraft
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import de.bixilon.kutil.base64.Base64Util.fromBase64
|
||||
import de.bixilon.minosoft.protocol.protocol.ProtocolVersions
|
||||
import de.bixilon.minosoft.util.account.minecraft.key.MinecraftKeyPair
|
||||
import de.bixilon.minosoft.util.yggdrasil.YggdrasilUtil
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.time.Instant
|
||||
import java.util.*
|
||||
|
||||
data class MinecraftPrivateKey(
|
||||
@JsonProperty("keyPair") val pair: MinecraftKeyPair,
|
||||
@JsonProperty("publicKeySignature") val signature: String,
|
||||
@JsonProperty("publicKeySignature") val signature: String?,
|
||||
@JsonProperty("publicKeySignatureV2") val signatureV2: String?,
|
||||
@JsonProperty("expiresAt") val expiresAt: Instant,
|
||||
@JsonProperty("refreshedAfter") val refreshedAfter: Instant,
|
||||
) {
|
||||
@get:JsonIgnore val signatureBytes: ByteArray by lazy { signature.fromBase64() }
|
||||
@get:JsonIgnore val signatureBytes: ByteArray? by lazy { signature?.fromBase64() }
|
||||
@get:JsonIgnore val signatureBytesV2: ByteArray by lazy { signatureV2!!.fromBase64() }
|
||||
|
||||
@JsonIgnore
|
||||
fun isExpired(): Boolean {
|
||||
val now = Instant.now()
|
||||
return now.isAfter(expiresAt) || now.isAfter(refreshedAfter)
|
||||
}
|
||||
|
||||
private val getSignedBytes: ByteArray
|
||||
get() = (expiresAt.toEpochMilli().toString() + pair.public).toByteArray(StandardCharsets.US_ASCII)
|
||||
|
||||
fun isSignatureCorrect(): Boolean {
|
||||
return YggdrasilUtil.verify(getSignedBytes, signatureBytes)
|
||||
@JsonIgnore
|
||||
fun requireSignature(uuid: UUID) {
|
||||
MinecraftKeyPair.requireSignature(uuid, expiresAt, pair.public, signatureBytesV2)
|
||||
signatureBytes?.let { YggdrasilUtil.requireSignature((expiresAt.toEpochMilli().toString() + pair.publicString).toByteArray(StandardCharsets.US_ASCII), it) }
|
||||
}
|
||||
|
||||
fun requireSignature() {
|
||||
YggdrasilUtil.requireSignature(getSignedBytes, signatureBytes)
|
||||
fun getSignature(versionId: Int): ByteArray {
|
||||
if (versionId < ProtocolVersions.V_1_19_1_PRE4) {
|
||||
return signatureBytes ?: throw IllegalStateException("v1 signature required")
|
||||
}
|
||||
return signatureBytesV2
|
||||
}
|
||||
}
|
||||
|
@ -13,9 +13,39 @@
|
||||
|
||||
package de.bixilon.minosoft.util.account.minecraft.key
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import de.bixilon.minosoft.protocol.protocol.OutByteBuffer
|
||||
import de.bixilon.minosoft.protocol.protocol.encryption.CryptManager
|
||||
import de.bixilon.minosoft.util.yggdrasil.YggdrasilException
|
||||
import de.bixilon.minosoft.util.yggdrasil.YggdrasilUtil
|
||||
import java.security.PublicKey
|
||||
import java.time.Instant
|
||||
import java.util.*
|
||||
|
||||
data class MinecraftKeyPair(
|
||||
@JsonProperty("privateKey") val private: String,
|
||||
@JsonProperty("publicKey") val public: String,
|
||||
)
|
||||
@JsonProperty("privateKey") val privateString: String,
|
||||
@JsonProperty("publicKey") val publicString: String,
|
||||
) {
|
||||
@get:JsonIgnore val private by lazy { CryptManager.getPlayerPrivateKey(privateString) }
|
||||
@get:JsonIgnore val public by lazy { CryptManager.getPlayerPublicKey(publicString) }
|
||||
|
||||
companion object {
|
||||
|
||||
fun isSignatureCorrect(uuid: UUID, expiresAt: Instant, publicKey: PublicKey, signature: ByteArray): Boolean {
|
||||
val signed = OutByteBuffer()
|
||||
signed.writeUUID(uuid)
|
||||
signed.writeLong(expiresAt.toEpochMilli())
|
||||
signed.writeBareByteArray(publicKey.encoded)
|
||||
return YggdrasilUtil.verify(signed.toArray(), signature)
|
||||
}
|
||||
|
||||
fun requireSignature(uuid: UUID, expiresAt: Instant, publicKey: PublicKey, signature: ByteArray) {
|
||||
if (!isSignatureCorrect(uuid, expiresAt, publicKey, signature)) {
|
||||
throw YggdrasilException()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user