From 80027e37545181aad7d0c201cdeca1ef3052330a Mon Sep 17 00:00:00 2001 From: Bixilon Date: Sun, 16 Oct 2022 18:04:58 +0200 Subject: [PATCH] network: 1.19.1-pre4 --- .../types/microsoft/MicrosoftAccount.kt | 10 +-- .../data/chat/message/PlayerChatMessage.kt | 4 +- .../data/chat/message/SimpleChatMessage.kt | 4 +- .../data/chat/signature/MessageChain.kt | 65 +++++++++++++++++++ .../data/chat/signature/SignedMessage.kt | 2 + .../DefaultMessageTypes.kt} | 13 ++-- .../minosoft/data/chat/type/MessageType.kt | 23 +++++++ .../event/events/ChatMessageReceiveEvent.kt | 8 +-- .../minosoft/protocol/PlayerPublicKey.kt | 22 +++++-- .../network/connection/play/ConnectionUtil.kt | 21 +++--- .../network/connection/play/PlayConnection.kt | 9 ++- .../packets/c2s/login/EncryptionC2SP.kt | 8 ++- .../packets/c2s/play/chat/CommandC2SP.kt | 6 +- .../c2s/play/chat/SignedChatMessageC2SP.kt | 2 + .../packets/s2c/login/EncryptionS2CP.kt | 3 +- .../packets/s2c/play/chat/ChatMessageS2CP.kt | 6 +- .../packets/s2c/play/chat/HideMessageS2CP.kt | 29 +++++++++ .../s2c/play/chat/MessageHeaderS2CP.kt | 31 +++++++++ .../s2c/play/chat/SignedChatMessageS2CP.kt | 11 +--- .../packets/s2c/play/tab/TabListS2CP.kt | 5 ++ .../protocol/protocol/PlayInByteBuffer.kt | 50 +++++++++----- .../protocol/protocol/PlayOutByteBuffer.kt | 1 - .../encryption/EncryptionSignatureData.kt | 19 ++++++ .../protocol/encryption/SignatureData.kt | 3 +- .../account/minecraft/MinecraftPrivateKey.kt | 25 ++++--- .../account/minecraft/key/MinecraftKeyPair.kt | 36 +++++++++- 26 files changed, 324 insertions(+), 92 deletions(-) create mode 100644 src/main/java/de/bixilon/minosoft/data/chat/signature/MessageChain.kt rename src/main/java/de/bixilon/minosoft/data/chat/{ChatMessageTypes.kt => type/DefaultMessageTypes.kt} (67%) create mode 100644 src/main/java/de/bixilon/minosoft/data/chat/type/MessageType.kt create mode 100644 src/main/java/de/bixilon/minosoft/protocol/packets/s2c/play/chat/HideMessageS2CP.kt create mode 100644 src/main/java/de/bixilon/minosoft/protocol/packets/s2c/play/chat/MessageHeaderS2CP.kt create mode 100644 src/main/java/de/bixilon/minosoft/protocol/protocol/encryption/EncryptionSignatureData.kt diff --git a/src/main/java/de/bixilon/minosoft/data/accounts/types/microsoft/MicrosoftAccount.kt b/src/main/java/de/bixilon/minosoft/data/accounts/types/microsoft/MicrosoftAccount.kt index 9bf83525a..5f99c4fff 100644 --- a/src/main/java/de/bixilon/minosoft/data/accounts/types/microsoft/MicrosoftAccount.kt +++ b/src/main/java/de/bixilon/minosoft/data/accounts/types/microsoft/MicrosoftAccount.kt @@ -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() diff --git a/src/main/java/de/bixilon/minosoft/data/chat/message/PlayerChatMessage.kt b/src/main/java/de/bixilon/minosoft/data/chat/message/PlayerChatMessage.kt index eb446efd0..c43ad2ff4 100644 --- a/src/main/java/de/bixilon/minosoft/data/chat/message/PlayerChatMessage.kt +++ b/src/main/java/de/bixilon/minosoft/data/chat/message/PlayerChatMessage.kt @@ -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) diff --git a/src/main/java/de/bixilon/minosoft/data/chat/message/SimpleChatMessage.kt b/src/main/java/de/bixilon/minosoft/data/chat/message/SimpleChatMessage.kt index eaa6c9ffc..00ecd1691 100644 --- a/src/main/java/de/bixilon/minosoft/data/chat/message/SimpleChatMessage.kt +++ b/src/main/java/de/bixilon/minosoft/data/chat/message/SimpleChatMessage.kt @@ -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 diff --git a/src/main/java/de/bixilon/minosoft/data/chat/signature/MessageChain.kt b/src/main/java/de/bixilon/minosoft/data/chat/signature/MessageChain.kt new file mode 100644 index 000000000..c69462ee6 --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/data/chat/signature/MessageChain.kt @@ -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 . + * + * 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() + } +} diff --git a/src/main/java/de/bixilon/minosoft/data/chat/signature/SignedMessage.kt b/src/main/java/de/bixilon/minosoft/data/chat/signature/SignedMessage.kt index 8a7a6f893..5d6dc05f9 100644 --- a/src/main/java/de/bixilon/minosoft/data/chat/signature/SignedMessage.kt +++ b/src/main/java/de/bixilon/minosoft/data/chat/signature/SignedMessage.kt @@ -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, ) diff --git a/src/main/java/de/bixilon/minosoft/data/chat/ChatMessageTypes.kt b/src/main/java/de/bixilon/minosoft/data/chat/type/DefaultMessageTypes.kt similarity index 67% rename from src/main/java/de/bixilon/minosoft/data/chat/ChatMessageTypes.kt rename to src/main/java/de/bixilon/minosoft/data/chat/type/DefaultMessageTypes.kt index 8977005f0..0aba1134e 100644 --- a/src/main/java/de/bixilon/minosoft/data/chat/ChatMessageTypes.kt +++ b/src/main/java/de/bixilon/minosoft/data/chat/type/DefaultMessageTypes.kt @@ -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 { - override val VALUES: Array = values() - override val NAME_MAP: Map = EnumUtil.getEnumValues(VALUES) + companion object : ValuesEnum { + override val VALUES: Array = values() + override val NAME_MAP: Map = EnumUtil.getEnumValues(VALUES) } } diff --git a/src/main/java/de/bixilon/minosoft/data/chat/type/MessageType.kt b/src/main/java/de/bixilon/minosoft/data/chat/type/MessageType.kt new file mode 100644 index 000000000..c0e5d99cb --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/data/chat/type/MessageType.kt @@ -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 . + * + * 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?, +) diff --git a/src/main/java/de/bixilon/minosoft/modding/event/events/ChatMessageReceiveEvent.kt b/src/main/java/de/bixilon/minosoft/modding/event/events/ChatMessageReceiveEvent.kt index a54f26a62..374715959 100644 --- a/src/main/java/de/bixilon/minosoft/modding/event/events/ChatMessageReceiveEvent.kt +++ b/src/main/java/de/bixilon/minosoft/modding/event/events/ChatMessageReceiveEvent.kt @@ -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) } diff --git a/src/main/java/de/bixilon/minosoft/protocol/PlayerPublicKey.kt b/src/main/java/de/bixilon/minosoft/protocol/PlayerPublicKey.kt index 9b25455b0..a1a97cdd8 100644 --- a/src/main/java/de/bixilon/minosoft/protocol/PlayerPublicKey.kt +++ b/src/main/java/de/bixilon/minosoft/protocol/PlayerPublicKey.kt @@ -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() + } + } } diff --git a/src/main/java/de/bixilon/minosoft/protocol/network/connection/play/ConnectionUtil.kt b/src/main/java/de/bixilon/minosoft/protocol/network/connection/play/ConnectionUtil.kt index 2e334c3fa..a87de5946 100644 --- a/src/main/java/de/bixilon/minosoft/protocol/network/connection/play/ConnectionUtil.kt +++ b/src/main/java/de/bixilon/minosoft/protocol/network/connection/play/ConnectionUtil.kt @@ -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") diff --git a/src/main/java/de/bixilon/minosoft/protocol/network/connection/play/PlayConnection.kt b/src/main/java/de/bixilon/minosoft/protocol/network/connection/play/PlayConnection.kt index 585a1ed16..0aa11578d 100644 --- a/src/main/java/de/bixilon/minosoft/protocol/network/connection/play/PlayConnection.kt +++ b/src/main/java/de/bixilon/minosoft/protocol/network/connection/play/PlayConnection.kt @@ -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, ) } } diff --git a/src/main/java/de/bixilon/minosoft/protocol/packets/c2s/login/EncryptionC2SP.kt b/src/main/java/de/bixilon/minosoft/protocol/packets/c2s/login/EncryptionC2SP.kt index aefc41538..1c0b3877b 100644 --- a/src/main/java/de/bixilon/minosoft/protocol/packets/c2s/login/EncryptionC2SP.kt +++ b/src/main/java/de/bixilon/minosoft/protocol/packets/c2s/login/EncryptionC2SP.kt @@ -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) } } } diff --git a/src/main/java/de/bixilon/minosoft/protocol/packets/c2s/play/chat/CommandC2SP.kt b/src/main/java/de/bixilon/minosoft/protocol/packets/c2s/play/chat/CommandC2SP.kt index 007395e44..a700bb2ef 100644 --- a/src/main/java/de/bixilon/minosoft/protocol/packets/c2s/play/chat/CommandC2SP.kt +++ b/src/main/java/de/bixilon/minosoft/protocol/packets/c2s/play/chat/CommandC2SP.kt @@ -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, ) } diff --git a/src/main/java/de/bixilon/minosoft/protocol/packets/c2s/play/chat/SignedChatMessageC2SP.kt b/src/main/java/de/bixilon/minosoft/protocol/packets/c2s/play/chat/SignedChatMessageC2SP.kt index c20fb98a8..05f862b69 100644 --- a/src/main/java/de/bixilon/minosoft/protocol/packets/c2s/play/chat/SignedChatMessageC2SP.kt +++ b/src/main/java/de/bixilon/minosoft/protocol/packets/c2s/play/chat/SignedChatMessageC2SP.kt @@ -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) diff --git a/src/main/java/de/bixilon/minosoft/protocol/packets/s2c/login/EncryptionS2CP.kt b/src/main/java/de/bixilon/minosoft/protocol/packets/s2c/login/EncryptionS2CP.kt index 8653a6cff..5ec443817 100644 --- a/src/main/java/de/bixilon/minosoft/protocol/packets/s2c/login/EncryptionS2CP.kt +++ b/src/main/java/de/bixilon/minosoft/protocol/packets/s2c/login/EncryptionS2CP.kt @@ -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))) } diff --git a/src/main/java/de/bixilon/minosoft/protocol/packets/s2c/play/chat/ChatMessageS2CP.kt b/src/main/java/de/bixilon/minosoft/protocol/packets/s2c/play/chat/ChatMessageS2CP.kt index 50d0d3f46..24f6524c0 100644 --- a/src/main/java/de/bixilon/minosoft/protocol/packets/s2c/play/chat/ChatMessageS2CP.kt +++ b/src/main/java/de/bixilon/minosoft/protocol/packets/s2c/play/chat/ChatMessageS2CP.kt @@ -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() } diff --git a/src/main/java/de/bixilon/minosoft/protocol/packets/s2c/play/chat/HideMessageS2CP.kt b/src/main/java/de/bixilon/minosoft/protocol/packets/s2c/play/chat/HideMessageS2CP.kt new file mode 100644 index 000000000..556a8596c --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/protocol/packets/s2c/play/chat/HideMessageS2CP.kt @@ -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 . + * + * 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)" } + } +} diff --git a/src/main/java/de/bixilon/minosoft/protocol/packets/s2c/play/chat/MessageHeaderS2CP.kt b/src/main/java/de/bixilon/minosoft/protocol/packets/s2c/play/chat/MessageHeaderS2CP.kt new file mode 100644 index 000000000..626f5b117 --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/protocol/packets/s2c/play/chat/MessageHeaderS2CP.kt @@ -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 . + * + * 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)" } + } +} diff --git a/src/main/java/de/bixilon/minosoft/protocol/packets/s2c/play/chat/SignedChatMessageS2CP.kt b/src/main/java/de/bixilon/minosoft/protocol/packets/s2c/play/chat/SignedChatMessageS2CP.kt index 13b96b295..616364777 100644 --- a/src/main/java/de/bixilon/minosoft/protocol/packets/s2c/play/chat/SignedChatMessageS2CP.kt +++ b/src/main/java/de/bixilon/minosoft/protocol/packets/s2c/play/chat/SignedChatMessageS2CP.kt @@ -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)" } } } diff --git a/src/main/java/de/bixilon/minosoft/protocol/packets/s2c/play/tab/TabListS2CP.kt b/src/main/java/de/bixilon/minosoft/protocol/packets/s2c/play/tab/TabListS2CP.kt index a214d5de6..a9402e805 100644 --- a/src/main/java/de/bixilon/minosoft/protocol/packets/s2c/play/tab/TabListS2CP.kt +++ b/src/main/java/de/bixilon/minosoft/protocol/packets/s2c/play/tab/TabListS2CP.kt @@ -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 } diff --git a/src/main/java/de/bixilon/minosoft/protocol/protocol/PlayInByteBuffer.kt b/src/main/java/de/bixilon/minosoft/protocol/protocol/PlayInByteBuffer.kt index 0bf6f9f12..634d99f24 100644 --- a/src/main/java/de/bixilon/minosoft/protocol/protocol/PlayInByteBuffer.kt +++ b/src/main/java/de/bixilon/minosoft/protocol/protocol/PlayInByteBuffer.kt @@ -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()) } } diff --git a/src/main/java/de/bixilon/minosoft/protocol/protocol/PlayOutByteBuffer.kt b/src/main/java/de/bixilon/minosoft/protocol/protocol/PlayOutByteBuffer.kt index 64c34fcaf..fb8877923 100644 --- a/src/main/java/de/bixilon/minosoft/protocol/protocol/PlayOutByteBuffer.kt +++ b/src/main/java/de/bixilon/minosoft/protocol/protocol/PlayOutByteBuffer.kt @@ -87,7 +87,6 @@ class PlayOutByteBuffer(val connection: PlayConnection) : OutByteBuffer() { } fun writeSignatureData(signature: SignatureData) { - writeLong(signature.salt) writeByteArray(signature.signature) } diff --git a/src/main/java/de/bixilon/minosoft/protocol/protocol/encryption/EncryptionSignatureData.kt b/src/main/java/de/bixilon/minosoft/protocol/protocol/encryption/EncryptionSignatureData.kt new file mode 100644 index 000000000..1378cbb48 --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/protocol/protocol/encryption/EncryptionSignatureData.kt @@ -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 . + * + * 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, +) diff --git a/src/main/java/de/bixilon/minosoft/protocol/protocol/encryption/SignatureData.kt b/src/main/java/de/bixilon/minosoft/protocol/protocol/encryption/SignatureData.kt index 2c596175a..a8fa96582 100644 --- a/src/main/java/de/bixilon/minosoft/protocol/protocol/encryption/SignatureData.kt +++ b/src/main/java/de/bixilon/minosoft/protocol/protocol/encryption/SignatureData.kt @@ -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()) } } diff --git a/src/main/java/de/bixilon/minosoft/util/account/minecraft/MinecraftPrivateKey.kt b/src/main/java/de/bixilon/minosoft/util/account/minecraft/MinecraftPrivateKey.kt index b4ccdcab9..7ba42cefb 100644 --- a/src/main/java/de/bixilon/minosoft/util/account/minecraft/MinecraftPrivateKey.kt +++ b/src/main/java/de/bixilon/minosoft/util/account/minecraft/MinecraftPrivateKey.kt @@ -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 } } diff --git a/src/main/java/de/bixilon/minosoft/util/account/minecraft/key/MinecraftKeyPair.kt b/src/main/java/de/bixilon/minosoft/util/account/minecraft/key/MinecraftKeyPair.kt index ae1e0d314..e4bb41928 100644 --- a/src/main/java/de/bixilon/minosoft/util/account/minecraft/key/MinecraftKeyPair.kt +++ b/src/main/java/de/bixilon/minosoft/util/account/minecraft/key/MinecraftKeyPair.kt @@ -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() + } + } + + + } +}