diff --git a/MinecraftClient/McClient.cs b/MinecraftClient/McClient.cs index 4309c1ba..cb779ccf 100644 --- a/MinecraftClient/McClient.cs +++ b/MinecraftClient/McClient.cs @@ -14,6 +14,7 @@ using MinecraftClient.Mapping; using MinecraftClient.Inventory; using MinecraftClient.Logger; using MinecraftClient.Protocol.Keys; +using MinecraftClient.Protocol.Message; namespace MinecraftClient { @@ -68,7 +69,8 @@ namespace MinecraftClient private int port; private int protocolversion; private string username; - private string uuid; + private Guid uuid; + private string uuidStr; private string sessionid; private PlayerKeyPair? playerKeyPair; private DateTime lastKeepAlive; @@ -104,7 +106,8 @@ namespace MinecraftClient public int GetServerPort() { return port; } public string GetServerHost() { return host; } public string GetUsername() { return username; } - public string GetUserUUID() { return uuid; } + public Guid GetUserUuid() { return uuid; } + public string GetUserUuidStr() { return uuidStr; } public string GetSessionID() { return sessionid; } public Location GetCurrentLocation() { return location; } public float GetYaw() { return playerYaw; } @@ -180,7 +183,8 @@ namespace MinecraftClient bool retry = false; this.sessionid = sessionID; - this.uuid = uuid; + this.uuid = new Guid(uuid); + this.uuidStr = uuid; this.username = user; this.host = server_ip; this.port = port; @@ -2093,20 +2097,18 @@ namespace MinecraftClient List links = new(); string messageText; - if (message.isJson) + if (message.isSignedChat) { - if (message.isSignedChat) - { - if (!Settings.ShowIllegalSignedChat && !message.isSystemChat && !(bool)message.isSignatureLegal!) - return; - messageText = ChatParser.ParseSignedChat(message, links); - } - else - messageText = ChatParser.ParseText(message.content, links); + if (!Settings.ShowIllegalSignedChat && !message.isSystemChat && !(bool)message.isSignatureLegal!) + return; + messageText = ChatParser.ParseSignedChat(message, links); } else { - messageText = message.content; + if (message.isJson) + messageText = ChatParser.ParseText(message.content, links); + else + messageText = message.content; } Log.Chat(messageText); @@ -2243,15 +2245,16 @@ namespace MinecraftClient if (player.Name == username) { // 1.19+ offline server is possible to return different uuid - this.uuid = player.UUID.ToString().Replace("-", string.Empty); + this.uuid = player.Uuid; + this.uuidStr = player.Uuid.ToString().Replace("-", string.Empty); } lock (onlinePlayers) { - onlinePlayers[player.UUID] = player; + onlinePlayers[player.Uuid] = player; } - DispatchBotEvent(bot => bot.OnPlayerJoin(player.UUID, player.Name)); + DispatchBotEvent(bot => bot.OnPlayerJoin(player.Uuid, player.Name)); } /// diff --git a/MinecraftClient/Protocol/Handlers/DataTypes.cs b/MinecraftClient/Protocol/Handlers/DataTypes.cs index 624924ab..6907484e 100644 --- a/MinecraftClient/Protocol/Handlers/DataTypes.cs +++ b/MinecraftClient/Protocol/Handlers/DataTypes.cs @@ -1103,9 +1103,51 @@ namespace MinecraftClient.Protocol.Handlers /// /// Byte array /// String representation - public string ByteArrayToString(byte[] bytes) + public string ByteArrayToString(byte[]? bytes) { - return BitConverter.ToString(bytes).Replace("-", " "); + if (bytes == null) + return "null"; + else + return BitConverter.ToString(bytes).Replace("-", " "); + } + + /// + /// Write LastSeenMessageList + /// + /// Message.LastSeenMessageList + /// Message.LastSeenMessageList Packet Data + public byte[] GetLastSeenMessageList(Message.LastSeenMessageList msgList) + { + List fields = new List(); + fields.AddRange(GetVarInt(msgList.entries.Length)); + foreach (Message.LastSeenMessageList.Entry entry in msgList.entries) + { + fields.AddRange(entry.profileId.ToBigEndianBytes()); + fields.AddRange(GetVarInt(entry.lastSignature.Length)); + fields.AddRange(entry.lastSignature); + } + return fields.ToArray(); + } + + /// + /// Write LastSeenMessageList.Acknowledgment + /// + /// Acknowledgment + /// Acknowledgment Packet Data + public byte[] GetAcknowledgment(Message.LastSeenMessageList.Acknowledgment ack) + { + List fields = new List(); + fields.AddRange(GetLastSeenMessageList(ack.lastSeen)); + if (ack.lastReceived == null) + fields.AddRange(GetBool(false)); + else + { + fields.AddRange(GetBool(true)); + fields.AddRange(ack.lastReceived.profileId.ToBigEndianBytes()); + fields.AddRange(GetVarInt(ack.lastReceived.lastSignature.Length)); + fields.AddRange(ack.lastReceived.lastSignature); + } + return fields.ToArray(); } } } diff --git a/MinecraftClient/Protocol/Handlers/PacketType18Handler.cs b/MinecraftClient/Protocol/Handlers/PacketType18Handler.cs index b086e747..4dff45a9 100644 --- a/MinecraftClient/Protocol/Handlers/PacketType18Handler.cs +++ b/MinecraftClient/Protocol/Handlers/PacketType18Handler.cs @@ -53,6 +53,7 @@ namespace MinecraftClient.Protocol.Handlers PacketTypePalette p; if (protocol > Protocol18Handler.MC_1_19_2_Version) throw new NotImplementedException(Translations.Get("exception.palette.packet")); + if (protocol <= Protocol18Handler.MC_1_8_Version) p = new PacketPalette17(); else if (protocol <= Protocol18Handler.MC_1_11_2_Version) diff --git a/MinecraftClient/Protocol/Handlers/Protocol16.cs b/MinecraftClient/Protocol/Handlers/Protocol16.cs index f691a2be..957e5d9e 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol16.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol16.cs @@ -10,6 +10,7 @@ using System.Security.Cryptography; using MinecraftClient.Mapping; using MinecraftClient.Inventory; using MinecraftClient.Protocol.Keys; +using MinecraftClient.Protocol.Message; namespace MinecraftClient.Protocol.Handlers { @@ -559,7 +560,7 @@ namespace MinecraftClient.Protocol.Handlers public bool Login(PlayerKeyPair playerKeyPair) { - if (Handshake(handler.GetUserUUID(), handler.GetUsername(), handler.GetSessionID(), handler.GetServerHost(), handler.GetServerPort())) + if (Handshake(handler.GetUserUuidStr(), handler.GetUsername(), handler.GetSessionID(), handler.GetServerHost(), handler.GetServerPort())) { Send(new byte[] { 0xCD, 0 }); try diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs index ca42bad5..64bdec30 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -19,6 +19,7 @@ using MinecraftClient.Logger; using System.Threading.Tasks; using MinecraftClient.Protocol.Keys; using System.Text.RegularExpressions; +using MinecraftClient.Protocol.Message; namespace MinecraftClient.Protocol.Handlers { @@ -66,6 +67,10 @@ namespace MinecraftClient.Protocol.Handlers private int protocolversion; private int currentDimension; + private int pendingAcknowledgments = 0; + private LastSeenMessagesCollector lastSeenMessagesCollector = new(5); + private LastSeenMessageList.Entry? lastReceivedMessage = null; + Protocol18Forge pForge; Protocol18Terrain pTerrain; IMinecraftComHandler handler; @@ -163,6 +168,36 @@ namespace MinecraftClient.Protocol.Handlers else itemPalette = new ItemPalette1161(); } else itemPalette = new ItemPalette115(); + + // MessageType + // You can find it in https://wiki.vg/Protocol#Player_Chat_Message or /net/minecraft/network/message/MessageType.java + if (protocolversion >= MC_1_19_2_Version) + { + ChatParser.ChatId2Type = new() + { + { 0, ChatParser.MessageType.CHAT }, + { 1, ChatParser.MessageType.SAY_COMMAND }, + { 2, ChatParser.MessageType.MSG_COMMAND_INCOMING }, + { 3, ChatParser.MessageType.MSG_COMMAND_OUTGOING }, + { 4, ChatParser.MessageType.TEAM_MSG_COMMAND_INCOMING }, + { 5, ChatParser.MessageType.TEAM_MSG_COMMAND_OUTGOING }, + { 6, ChatParser.MessageType.EMOTE_COMMAND }, + }; + } + else if (protocolversion >= MC_1_19_Version) + { + ChatParser.ChatId2Type = new() + { + { 0, ChatParser.MessageType.CHAT }, + { 1, ChatParser.MessageType.RAW_MSG }, + { 2, ChatParser.MessageType.RAW_MSG }, + { 3, ChatParser.MessageType.SAY_COMMAND }, + { 4, ChatParser.MessageType.MSG_COMMAND_INCOMING }, + { 5, ChatParser.MessageType.TEAM_MSG_COMMAND_INCOMING }, + { 6, ChatParser.MessageType.EMOTE_COMMAND }, + { 7, ChatParser.MessageType.RAW_MSG }, + }; + } } /// @@ -439,14 +474,104 @@ namespace MinecraftClient.Protocol.Handlers byte[] messageSignature = dataTypes.ReadNextByteArray(packetData); - PlayerInfo? player = handler.GetPlayerInfo(senderUUID); - bool verifyResult = player == null ? false : player.VerifyMessage(signedChat, senderUUID, timestamp, salt, ref messageSignature); + bool verifyResult; + if (senderUUID.ToString().Replace("-", String.Empty) == handler.GetUserUuidStr()) + verifyResult = true; + else + { + PlayerInfo? player = handler.GetPlayerInfo(senderUUID); + verifyResult = player == null ? false : player.VerifyMessage(signedChat, timestamp, salt, ref messageSignature); + } - handler.OnTextReceived(new(signedChat, true, messageType, senderUUID, unsignedChatContent, senderDisplayName, senderTeamName, timestamp, verifyResult)); + handler.OnTextReceived(new(signedChat, true, messageType, senderUUID, unsignedChatContent, senderDisplayName, senderTeamName, timestamp, messageSignature, verifyResult)); } else // 1.19.1 + { - // Todo + byte[]? precedingSignature = dataTypes.ReadNextBool(packetData) ? dataTypes.ReadNextByteArray(packetData) : null; + Guid senderUUID = dataTypes.ReadNextUUID(packetData); + byte[] headerSignature = dataTypes.ReadNextByteArray(packetData); + + string signedChat = dataTypes.ReadNextString(packetData); + string? decorated = dataTypes.ReadNextBool(packetData) ? dataTypes.ReadNextString(packetData) : null; + + long timestamp = dataTypes.ReadNextLong(packetData); + long salt = dataTypes.ReadNextLong(packetData); + + int lastSeenMessageListLen = dataTypes.ReadNextVarInt(packetData); + LastSeenMessageList.Entry[] lastSeenMessageList = new LastSeenMessageList.Entry[lastSeenMessageListLen]; + for (int i = 0; i < lastSeenMessageListLen; ++i) + { + Guid user = dataTypes.ReadNextUUID(packetData); + byte[] lastSignature = dataTypes.ReadNextByteArray(packetData); + lastSeenMessageList[i] = new(user, lastSignature); + } + LastSeenMessageList lastSeenMessages = new(lastSeenMessageList); + + string? unsignedChatContent = dataTypes.ReadNextBool(packetData) ? dataTypes.ReadNextString(packetData) : null; + + int filterEnum = dataTypes.ReadNextVarInt(packetData); + if (filterEnum == 2) // PARTIALLY_FILTERED + dataTypes.ReadNextULongArray(packetData); + + int chatTypeId = dataTypes.ReadNextVarInt(packetData); + string chatName = dataTypes.ReadNextString(packetData); + string? targetName = dataTypes.ReadNextBool(packetData) ? dataTypes.ReadNextString(packetData) : null; + + bool verifyResult; + if (senderUUID.ToString().Replace("-", String.Empty) == handler.GetUserUuidStr()) + verifyResult = true; + else + { + PlayerInfo? player = handler.GetPlayerInfo(senderUUID); + if (player == null) + verifyResult = false; + else + { + bool lastVerifyResult = player.IsMessageChainLegal(); + verifyResult = player.VerifyMessage(signedChat, timestamp, salt, ref headerSignature, ref precedingSignature, lastSeenMessages); + if (lastVerifyResult && !verifyResult) + log.Warn("Player " + player.DisplayName + " message chains broken!"); + } + } + + Dictionary chatInfo = Json.ParseJson(chatName).Properties; + string senderDisplayName = (chatInfo.ContainsKey("insertion") ? chatInfo["insertion"] : chatInfo["text"]).StringValue; + string? senderTeamName = null; + ChatParser.MessageType messageTypeEnum = ChatParser.ChatId2Type![chatTypeId]; + if (targetName != null && + (messageTypeEnum == ChatParser.MessageType.TEAM_MSG_COMMAND_INCOMING || messageTypeEnum == ChatParser.MessageType.TEAM_MSG_COMMAND_OUTGOING)) + senderTeamName = Json.ParseJson(targetName).Properties["with"].DataArray[0].Properties["text"].StringValue; + + ChatMessage chat = new(signedChat, false, chatTypeId, senderUUID, unsignedChatContent, senderDisplayName, senderTeamName, timestamp, headerSignature, verifyResult); + if (!chat.lacksSender()) + this.acknowledge(chat); + handler.OnTextReceived(chat); + } + break; + case PacketTypesIn.MessageHeader: + if (protocolversion >= MC_1_19_2_Version) + { + byte[]? precedingSignature = dataTypes.ReadNextBool(packetData) ? dataTypes.ReadNextByteArray(packetData) : null; + Guid senderUUID = dataTypes.ReadNextUUID(packetData); + byte[] headerSignature = dataTypes.ReadNextByteArray(packetData); + byte[] bodyDigest = dataTypes.ReadNextByteArray(packetData); + + bool verifyResult; + if (senderUUID.ToString().Replace("-", String.Empty) == handler.GetUserUuidStr()) + verifyResult = true; + else + { + PlayerInfo? player = handler.GetPlayerInfo(senderUUID); + if (player == null) + verifyResult = false; + else + { + bool lastVerifyResult = player.IsMessageChainLegal(); + verifyResult = player.VerifyMessageHead(ref precedingSignature, ref headerSignature, ref bodyDigest); + if (lastVerifyResult && !verifyResult) + log.Warn("Player " + player.DisplayName + " message chains broken!"); + } + } } break; case PacketTypesIn.Respawn: @@ -826,6 +951,8 @@ namespace MinecraftClient.Protocol.Handlers //handler.OnTextReceived(message, true); } + break; + case PacketTypesIn.ChatSuggestions: break; case PacketTypesIn.MapChunkBulk: if (protocolversion < MC_1_9_Version && handler.GetTerrainEnabled()) @@ -1546,7 +1673,7 @@ namespace MinecraftClient.Protocol.Handlers } if (protocolversion >= MC_1_19_2_Version) { - string uuid = handler.GetUserUUID(); + string uuid = handler.GetUserUuidStr(); if (uuid == "0") fullLoginPacket.AddRange(dataTypes.GetBool(false)); // Has UUID else @@ -1572,7 +1699,7 @@ namespace MinecraftClient.Protocol.Handlers string serverID = dataTypes.ReadNextString(packetData); byte[] serverPublicKey = dataTypes.ReadNextByteArray(packetData); byte[] token = dataTypes.ReadNextByteArray(packetData); - return StartEncryption(handler.GetUserUUID(), handler.GetSessionID(), token, serverID, serverPublicKey, playerKeyPair); + return StartEncryption(handler.GetUserUuidStr(), handler.GetSessionID(), token, serverID, serverPublicKey, playerKeyPair); } else if (packetID == 0x02) //Login successful { @@ -1854,15 +1981,57 @@ namespace MinecraftClient.Protocol.Handlers return protocolversion; } + /// + /// Send MessageAcknowledgment packet + /// + /// Message acknowledgment + /// True if properly sent + public bool SendMessageAcknowledgment(LastSeenMessageList.Acknowledgment acknowledgment) + { + try + { + byte[] fields = dataTypes.GetAcknowledgment(acknowledgment); + + SendPacket(PacketTypesOut.MessageAcknowledgment, fields); + + return true; + } + catch (SocketException) { return false; } + catch (System.IO.IOException) { return false; } + catch (ObjectDisposedException) { return false; } + } + + public LastSeenMessageList.Acknowledgment consumeAcknowledgment() + { + this.pendingAcknowledgments = 0; + return new LastSeenMessageList.Acknowledgment(this.lastSeenMessagesCollector.GetLastSeenMessages(), this.lastReceivedMessage); + } + + public void acknowledge(ChatMessage message) + { + LastSeenMessageList.Entry? entry = message.toLastSeenMessageEntry(); + + if (entry != null) + { + lastSeenMessagesCollector.Add(entry); + lastReceivedMessage = null; + + if (pendingAcknowledgments++ > 64) + SendMessageAcknowledgment(this.consumeAcknowledgment()); + } + } /// /// The signable argument names and their values from command /// Signature will used in Vanilla's say, me, msg, teammsg, ban, banip, and kick commands. /// https://gist.github.com/kennytv/ed783dd244ca0321bbd882c347892874#signed-command-arguments + /// You can find all the commands that need to be signed by searching for "MessageArgumentType.getSignedMessage" in the source code. + /// Don't forget to handle the redirected commands, e.g. /tm, /w + /// /// /// Command /// List< Argument Name, Argument Value > - private List> collectCommandArguments(string command) + private static List> CollectCommandArguments(string command) { List> needSigned = new(); @@ -1874,21 +2043,25 @@ namespace MinecraftClient.Protocol.Handlers { /* /me /say - /teammsg */ + /teammsg + /tm */ if (argStage1[0] == "me") needSigned.Add(new("action", argStage1[1])); - else if (argStage1[0] == "say" || argStage1[0] == "teammsg") + else if (argStage1[0] == "say" || argStage1[0] == "teammsg" || argStage1[0] == "tm") needSigned.Add(new("message", argStage1[1])); - else if (argStage1[0] == "msg" || argStage1[0] == "ban" || argStage1[0] == "ban-ip" || argStage1[0] == "kick") + else if (argStage1[0] == "msg" || argStage1[0] == "tell" || argStage1[0] == "w" || + argStage1[0] == "ban" || argStage1[0] == "ban-ip" || argStage1[0] == "kick") { /* /msg + /tell + /w /ban [] /ban-ip [] /kick [] */ string[] argStage2 = argStage1[1].Split(' ', 2, StringSplitOptions.None); if (argStage2.Length == 2) { - if (argStage1[0] == "msg") + if (argStage1[0] == "msg" || argStage1[0] == "tell" || argStage1[0] == "w") needSigned.Add(new("message", argStage2[1])); else if (argStage1[0] == "ban" || argStage1[0] == "ban-ip" || argStage1[0] == "kick") needSigned.Add(new("reason", argStage2[1])); @@ -1917,6 +2090,8 @@ namespace MinecraftClient.Protocol.Handlers try { + LastSeenMessageList.Acknowledgment? acknowledgment = (protocolversion >= MC_1_19_2_Version) ? this.consumeAcknowledgment() : null; + List fields = new(); // Command: String @@ -1926,9 +2101,7 @@ namespace MinecraftClient.Protocol.Handlers DateTimeOffset timeNow = DateTimeOffset.UtcNow; fields.AddRange(dataTypes.GetLong(timeNow.ToUnixTimeMilliseconds())); - List> needSigned = collectCommandArguments(command); // List< Argument Name, Argument Value > - // foreach (var msg in needSigned) - // log.Info("<" + msg.Item1 + ">: " + msg.Item2); + List> needSigned = CollectCommandArguments(command); // List< Argument Name, Argument Value > if (needSigned.Count == 0 || playerKeyPair == null || !Settings.SignMessageInCommand) { fields.AddRange(dataTypes.GetLong(0)); // Salt: Long @@ -1936,14 +2109,16 @@ namespace MinecraftClient.Protocol.Handlers } else { - string uuid = handler.GetUserUUID()!; + Guid uuid = handler.GetUserUuid(); byte[] salt = GenerateSalt(); fields.AddRange(salt); // Salt: Long fields.AddRange(dataTypes.GetVarInt(needSigned.Count)); // Signature Length: VarInt foreach (var argument in needSigned) { fields.AddRange(dataTypes.GetString(argument.Item1)); // Argument name: String - byte[] sign = playerKeyPair.PrivateKey.SignMessage(argument.Item2, uuid, timeNow, ref salt); + byte[] sign = (protocolversion >= MC_1_19_2_Version) ? + playerKeyPair.PrivateKey.SignMessage(argument.Item2, uuid, timeNow, ref salt, acknowledgment!.lastSeen) : + playerKeyPair.PrivateKey.SignMessage(argument.Item2, uuid, timeNow, ref salt); fields.AddRange(dataTypes.GetVarInt(sign.Length)); // Signature length: VarInt fields.AddRange(sign); // Signature: Byte Array } @@ -1952,6 +2127,12 @@ namespace MinecraftClient.Protocol.Handlers // Signed Preview: Boolean fields.AddRange(dataTypes.GetBool(false)); + if (protocolversion >= MC_1_19_2_Version) + { + // Message Acknowledgment + fields.AddRange(dataTypes.GetAcknowledgment(acknowledgment!)); + } + SendPacket(PacketTypesOut.ChatCommand, fields); return true; } @@ -1971,19 +2152,14 @@ namespace MinecraftClient.Protocol.Handlers if (String.IsNullOrEmpty(message)) return true; - if (protocolversion >= MC_1_19_2_Version) - { - // Todo - log.Warn("Not implement"); - return false; - } - // Process Chat Command - 1.19 and above if (protocolversion >= MC_1_19_Version && message.StartsWith('/')) return SendChatCommand(message[1..], playerKeyPair); try { + LastSeenMessageList.Acknowledgment? acknowledgment = (protocolversion >= MC_1_19_2_Version) ? this.consumeAcknowledgment() : null; + List fields = new(); // Message: String (up to 256 chars) @@ -2007,8 +2183,10 @@ namespace MinecraftClient.Protocol.Handlers fields.AddRange(salt); // Signature Length & Signature: (VarInt) and Byte Array - string uuid = handler.GetUserUUID()!; - byte[] sign = playerKeyPair.PrivateKey.SignMessage(message, uuid, timeNow, ref salt); + Guid uuid = handler.GetUserUuid(); + byte[] sign = (protocolversion >= MC_1_19_2_Version) ? + playerKeyPair.PrivateKey.SignMessage(message, uuid, timeNow, ref salt, acknowledgment!.lastSeen) : + playerKeyPair.PrivateKey.SignMessage(message, uuid, timeNow, ref salt); fields.AddRange(dataTypes.GetVarInt(sign.Length)); fields.AddRange(sign); } @@ -2016,8 +2194,11 @@ namespace MinecraftClient.Protocol.Handlers // Signed Preview: Boolean fields.AddRange(dataTypes.GetBool(false)); - fields.AddRange(dataTypes.GetVarInt(0)); - fields.AddRange(dataTypes.GetBool(false)); + if (protocolversion >= MC_1_19_2_Version) + { + // Message Acknowledgment + fields.AddRange(dataTypes.GetAcknowledgment(acknowledgment!)); + } } SendPacket(PacketTypesOut.ChatMessage, fields); return true; @@ -2098,9 +2279,12 @@ namespace MinecraftClient.Protocol.Handlers List fields = new List(); fields.AddRange(dataTypes.GetString(language)); fields.Add(viewDistance); - fields.AddRange(protocolversion >= MC_1_9_Version - ? dataTypes.GetVarInt(chatMode) - : new byte[] { chatMode }); + + if (protocolversion >= MC_1_9_Version) + fields.AddRange(dataTypes.GetVarInt(chatMode)); + else + fields.AddRange(new byte[] { chatMode }); + fields.Add(chatColors ? (byte)1 : (byte)0); if (protocolversion < MC_1_8_Version) { diff --git a/MinecraftClient/Protocol/IMinecraftComHandler.cs b/MinecraftClient/Protocol/IMinecraftComHandler.cs index 96b4e4b7..4e0de511 100644 --- a/MinecraftClient/Protocol/IMinecraftComHandler.cs +++ b/MinecraftClient/Protocol/IMinecraftComHandler.cs @@ -5,6 +5,7 @@ using System.Text; using MinecraftClient.Mapping; using MinecraftClient.Inventory; using MinecraftClient.Logger; +using MinecraftClient.Protocol.Message; namespace MinecraftClient.Protocol { @@ -22,7 +23,8 @@ namespace MinecraftClient.Protocol int GetServerPort(); string GetServerHost(); string GetUsername(); - string GetUserUUID(); + Guid GetUserUuid(); + string GetUserUuidStr(); string GetSessionID(); string[] GetOnlinePlayers(); Dictionary GetOnlinePlayersWithUUID(); diff --git a/MinecraftClient/Protocol/ChatMessage.cs b/MinecraftClient/Protocol/Message/ChatMessage.cs similarity index 69% rename from MinecraftClient/Protocol/ChatMessage.cs rename to MinecraftClient/Protocol/Message/ChatMessage.cs index 1a4b961a..28d4c650 100644 --- a/MinecraftClient/Protocol/ChatMessage.cs +++ b/MinecraftClient/Protocol/Message/ChatMessage.cs @@ -4,7 +4,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace MinecraftClient.Protocol +namespace MinecraftClient.Protocol.Message { public class ChatMessage { @@ -17,7 +17,7 @@ namespace MinecraftClient.Protocol // 0: chat (chat box), 1: system message (chat box), 2: game info (above hotbar), 3: say command, // 4: msg command, 5: team msg command, 6: emote command, 7: tellraw command - public readonly int chatType; + public readonly int chatTypeId; public readonly Guid senderUUID; @@ -31,31 +31,44 @@ namespace MinecraftClient.Protocol public readonly DateTime? timestamp; + public readonly byte[]? signature; + public readonly bool? isSignatureLegal; - public ChatMessage(string content, bool isJson, int chatType, Guid senderUUID, string? unsignedContent, string displayName, string? teamName, long timestamp, bool isSignatureLegal) + public ChatMessage(string content, bool isJson, int chatType, Guid senderUUID, string? unsignedContent, string displayName, string? teamName, long timestamp, byte[] signature, bool isSignatureLegal) { - this.isSignedChat = true; - this.isSystemChat = false; + isSignedChat = true; + isSystemChat = false; this.content = content; this.isJson = isJson; - this.chatType = chatType; + this.chatTypeId = chatType; this.senderUUID = senderUUID; this.unsignedContent = unsignedContent; this.displayName = displayName; this.teamName = teamName; - this.timestamp = DateTimeOffset.FromUnixTimeMilliseconds(timestamp).DateTime; + this.timestamp = DateTimeOffset.FromUnixTimeMilliseconds(timestamp).DateTime; + this.signature = signature; this.isSignatureLegal = isSignatureLegal; } public ChatMessage(string content, bool isJson, int chatType, Guid senderUUID, bool isSystemChat = false) { - this.isSignedChat = isSystemChat; + isSignedChat = isSystemChat; this.isSystemChat = isSystemChat; this.content = content; this.isJson = isJson; - this.chatType = chatType; + this.chatTypeId = chatType; this.senderUUID = senderUUID; } + + public LastSeenMessageList.Entry? toLastSeenMessageEntry() + { + return signature != null ? new LastSeenMessageList.Entry(senderUUID, signature) : null; + } + + public bool lacksSender() + { + return this.senderUUID == Guid.Empty; + } } } diff --git a/MinecraftClient/Protocol/ChatParser.cs b/MinecraftClient/Protocol/Message/ChatParser.cs similarity index 82% rename from MinecraftClient/Protocol/ChatParser.cs rename to MinecraftClient/Protocol/Message/ChatParser.cs index 57cee341..6fd05251 100644 --- a/MinecraftClient/Protocol/ChatParser.cs +++ b/MinecraftClient/Protocol/Message/ChatParser.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; +using MinecraftClient.Protocol.Message; namespace MinecraftClient.Protocol { @@ -12,6 +13,20 @@ namespace MinecraftClient.Protocol static class ChatParser { + public enum MessageType + { + CHAT, + SAY_COMMAND, + MSG_COMMAND_INCOMING, + MSG_COMMAND_OUTGOING, + TEAM_MSG_COMMAND_INCOMING, + TEAM_MSG_COMMAND_OUTGOING, + EMOTE_COMMAND, + RAW_MSG + }; + + public static Dictionary? ChatId2Type; + /// /// The main function to convert text from MC 1.6+ JSON to MC 1.5.2 formatted text /// @@ -31,56 +46,64 @@ namespace MinecraftClient.Protocol /// Returns the translated text public static string ParseSignedChat(ChatMessage message, List? links = null) { - string content; - if (Settings.ShowModifiedChat && message.unsignedContent != null) - content = ChatParser.ParseText(message.unsignedContent, links); - else - content = ChatParser.ParseText(message.content, links); + string chatContent = Settings.ShowModifiedChat && message.unsignedContent != null ? message.unsignedContent : message.content; + string content = message.isJson ? ParseText(chatContent, links) : chatContent; string sender = message.displayName!; + string text; List usingData = new(); - switch (message.chatType) + + MessageType chatType; + if (message.isSystemChat) + chatType = MessageType.RAW_MSG; + else if (!ChatId2Type!.TryGetValue(message.chatTypeId, out chatType)) + chatType = MessageType.CHAT; + switch (chatType) { - case 0: // chat (chat box) + case MessageType.CHAT: usingData.Add(sender); usingData.Add(content); text = TranslateString("chat.type.text", usingData); break; - case 1: // system message (chat box) - text = content; - break; - case 2: // game info (above hotbar) - text = content; - break; - case 3: // say command + case MessageType.SAY_COMMAND: usingData.Add(sender); usingData.Add(content); text = TranslateString("chat.type.announcement", usingData); break; - case 4: // msg command + case MessageType.MSG_COMMAND_INCOMING: usingData.Add(sender); usingData.Add(content); text = TranslateString("commands.message.display.incoming", usingData); break; - case 5: // team msg command (/teammsg) + case MessageType.MSG_COMMAND_OUTGOING: + usingData.Add(sender); + usingData.Add(content); + text = TranslateString("commands.message.display.outgoing", usingData); + break; + case MessageType.TEAM_MSG_COMMAND_INCOMING: usingData.Add(message.teamName!); usingData.Add(sender); usingData.Add(content); text = TranslateString("chat.type.team.text", usingData); break; - case 6: // emote command (/me) + case MessageType.TEAM_MSG_COMMAND_OUTGOING: + usingData.Add(message.teamName!); + usingData.Add(sender); + usingData.Add(content); + text = TranslateString("chat.type.team.sent", usingData); + break; + case MessageType.EMOTE_COMMAND: usingData.Add(sender); usingData.Add(content); text = TranslateString("chat.type.emote", usingData); break; - case 7: // tellraw command + case MessageType.RAW_MSG: text = content; break; default: - text = $"{sender}: {content}"; - break; + goto case MessageType.CHAT; } - string color = String.Empty; + string color = string.Empty; if (message.isSystemChat) { if (Settings.MarkSystemMessage) @@ -121,21 +144,21 @@ namespace MinecraftClient.Protocol { /* MC 1.7+ Name MC 1.6 Name Classic tag */ case "black": /* Blank if same */ return "§0"; - case "dark_blue": return "§1"; - case "dark_green": return "§2"; - case "dark_aqua": case "dark_cyan": return "§3"; - case "dark_red": return "§4"; - case "dark_purple": case "dark_magenta": return "§5"; - case "gold": case "dark_yellow": return "§6"; - case "gray": return "§7"; - case "dark_gray": return "§8"; - case "blue": return "§9"; - case "green": return "§a"; - case "aqua": case "cyan": return "§b"; - case "red": return "§c"; - case "light_purple": case "magenta": return "§d"; - case "yellow": return "§e"; - case "white": return "§f"; + case "dark_blue": return "§1"; + case "dark_green": return "§2"; + case "dark_aqua": case "dark_cyan": return "§3"; + case "dark_red": return "§4"; + case "dark_purple": case "dark_magenta": return "§5"; + case "gold": case "dark_yellow": return "§6"; + case "gray": return "§7"; + case "dark_gray": return "§8"; + case "blue": return "§9"; + case "green": return "§a"; + case "aqua": case "cyan": return "§b"; + case "red": return "§c"; + case "light_purple": case "magenta": return "§d"; + case "yellow": return "§e"; + case "white": return "§f"; default: return ""; } } @@ -172,13 +195,13 @@ namespace MinecraftClient.Protocol TranslationRules["commands.message.display.outgoing"] = "§7You whisper to %s: %s"; //Language file in a subfolder, depending on the language setting - if (!System.IO.Directory.Exists("lang")) - System.IO.Directory.CreateDirectory("lang"); + if (!Directory.Exists("lang")) + Directory.CreateDirectory("lang"); string Language_File = "lang" + Path.DirectorySeparatorChar + Settings.Language + ".lang"; //File not found? Try downloading language file from Mojang's servers? - if (!System.IO.File.Exists(Language_File)) + if (!File.Exists(Language_File)) { ConsoleIO.WriteLineFormatted(Translations.Get("chat.download", Settings.Language)); try @@ -197,7 +220,7 @@ namespace MinecraftClient.Protocol stringBuilder.Append(entry.Key + "=" + entry.Value.StringValue + Environment.NewLine); } - System.IO.File.WriteAllText(Language_File, stringBuilder.ToString()); + File.WriteAllText(Language_File, stringBuilder.ToString()); ConsoleIO.WriteLineFormatted(Translations.Get("chat.done", Language_File)); } catch @@ -207,17 +230,17 @@ namespace MinecraftClient.Protocol } //Download Failed? Defaulting to en_GB.lang if the game is installed - if (!System.IO.File.Exists(Language_File) //Try en_GB.lang - && System.IO.File.Exists(Settings.TranslationsFile_FromMCDir)) + if (!File.Exists(Language_File) //Try en_GB.lang + && File.Exists(Settings.TranslationsFile_FromMCDir)) { Language_File = Settings.TranslationsFile_FromMCDir; Translations.WriteLineFormatted("chat.from_dir"); } //Load the external dictionnary of translation rules or display an error message - if (System.IO.File.Exists(Language_File)) + if (File.Exists(Language_File)) { - string[] translations = System.IO.File.ReadAllLines(Language_File); + string[] translations = File.ReadAllLines(Language_File); foreach (string line in translations) { if (line.Length > 0) @@ -289,7 +312,7 @@ namespace MinecraftClient.Protocol } return result.ToString(); } - else return "[" + rulename + "] " + String.Join(" ", using_data); + else return "[" + rulename + "] " + string.Join(" ", using_data); } /// @@ -315,11 +338,11 @@ namespace MinecraftClient.Protocol if (clickEvent.Properties.ContainsKey("action") && clickEvent.Properties.ContainsKey("value") && clickEvent.Properties["action"].StringValue == "open_url" - && !String.IsNullOrEmpty(clickEvent.Properties["value"].StringValue)) + && !string.IsNullOrEmpty(clickEvent.Properties["value"].StringValue)) { links.Add(clickEvent.Properties["value"].StringValue); } - } + } if (data.Properties.ContainsKey("extra")) { Json.JSONData[] extras = data.Properties["extra"].DataArray.ToArray(); @@ -372,7 +395,7 @@ namespace MinecraftClient.Protocol System.Net.HttpWebRequest myRequest = (System.Net.HttpWebRequest)System.Net.WebRequest.Create(url); myRequest.Method = "GET"; System.Net.WebResponse myResponse = myRequest.GetResponse(); - System.IO.StreamReader sr = new System.IO.StreamReader(myResponse.GetResponseStream(), System.Text.Encoding.UTF8); + StreamReader sr = new StreamReader(myResponse.GetResponseStream(), Encoding.UTF8); string result = sr.ReadToEnd(); sr.Close(); myResponse.Close(); diff --git a/MinecraftClient/Protocol/Message/LastSeenMessageList.cs b/MinecraftClient/Protocol/Message/LastSeenMessageList.cs new file mode 100644 index 00000000..262b1b55 --- /dev/null +++ b/MinecraftClient/Protocol/Message/LastSeenMessageList.cs @@ -0,0 +1,117 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MinecraftClient.Protocol.Message +{ + /// + /// A list of messages a client has seen. + /// + public class LastSeenMessageList + { + public static readonly LastSeenMessageList EMPTY = new(new Entry[0]); + public static readonly int MAX_ENTRIES = 5; + + public Entry[] entries; + + public LastSeenMessageList(Entry[] list) + { + entries = list; + } + + public void WriteForSign(List data) + { + foreach (Entry entry in entries) + { + data.Add(70); + data.AddRange(entry.profileId.ToBigEndianBytes()); + data.AddRange(entry.lastSignature); + } + } + + /// + /// A pair of a player's UUID and the signature of the last message they saw, used as an entry of LastSeenMessageList. + /// + public class Entry + { + public Guid profileId; + public byte[] lastSignature; + + public Entry(Guid profileId, byte[] lastSignature) + { + this.profileId = profileId; + this.lastSignature = lastSignature; + } + } + + /// + /// A record of messages acknowledged by a client. + /// This holds the messages the client has recently seen, as well as the last message they received, if any. + /// + public class Acknowledgment + { + public LastSeenMessageList lastSeen; + public Entry? lastReceived; + + public Acknowledgment(LastSeenMessageList lastSeenMessageList, Entry? lastReceivedMessage) + { + lastSeen = lastSeenMessageList; + lastReceived = lastReceivedMessage; + } + } + } + + /// + /// Collects the message that are last seen by a client. + /// The message, along with the "last received" message, forms an "acknowledgment" of received messages. + /// They are sent to the server when the client has enough messages received or when they send a message. + /// The maximum amount of message entries are specified in the constructor.The vanilla clients collect 5 entries. + /// Calling add adds the message to the beginning of the entries list, and evicts the oldest message. + /// If there are entries with the same sender profile ID, the older entry will be replaced with null instead of filling the hole. + /// + public class LastSeenMessagesCollector + { + private readonly LastSeenMessageList.Entry[] entries; + private int size = 0; + private LastSeenMessageList lastSeenMessages; + + public LastSeenMessagesCollector(int size) + { + lastSeenMessages = LastSeenMessageList.EMPTY; + entries = new LastSeenMessageList.Entry[size]; + } + + public void Add(LastSeenMessageList.Entry entry) + { + LastSeenMessageList.Entry? lastEntry = entry; + + for (int i = 0; i < size; ++i) + { + LastSeenMessageList.Entry curEntry = entries[i]; + entries[i] = lastEntry; + lastEntry = curEntry; + if (curEntry.profileId == entry.profileId) + { + lastEntry = null; + break; + } + } + + if (lastEntry != null && size < entries.Length) + entries[size++] = lastEntry; + + LastSeenMessageList.Entry[] msgList = new LastSeenMessageList.Entry[size]; + for (int i = 0; i < size; ++i) + msgList[i] = entries[i]; + lastSeenMessages = new LastSeenMessageList(msgList); + } + + public LastSeenMessageList GetLastSeenMessages() + { + return lastSeenMessages; + } + + } +} diff --git a/MinecraftClient/Protocol/PlayerInfo.cs b/MinecraftClient/Protocol/PlayerInfo.cs index f810b763..46c50bf6 100644 --- a/MinecraftClient/Protocol/PlayerInfo.cs +++ b/MinecraftClient/Protocol/PlayerInfo.cs @@ -4,12 +4,13 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using MinecraftClient.Protocol.Keys; +using MinecraftClient.Protocol.Message; namespace MinecraftClient.Protocol { public class PlayerInfo { - public readonly Guid UUID; + public readonly Guid Uuid; public readonly string Name; @@ -26,9 +27,13 @@ namespace MinecraftClient.Protocol private readonly DateTime? KeyExpiresAt; + private bool lastMessageVerified; + + private byte[]? precedingSignature; + public PlayerInfo(Guid uuid, string name, Tuple[]? property, int gamemode, int ping, string? displayName, long? timeStamp, byte[]? publicKey, byte[]? signature) { - UUID = uuid; + Uuid = uuid; Name = name; if (property != null) Property = property; @@ -48,36 +53,107 @@ namespace MinecraftClient.Protocol PublicKey = null; } } + lastMessageVerified = true; + precedingSignature = null; } public PlayerInfo(string name, Guid uuid) { Name = name; - UUID = uuid; + Uuid = uuid; Gamemode = -1; Ping = 0; + lastMessageVerified = true; + precedingSignature = null; } - public bool IsKeyVaild() + public bool IsMessageChainLegal() { - return PublicKey != null && DateTime.Now.ToUniversalTime() > this.KeyExpiresAt; + return this.lastMessageVerified; } - public bool VerifyMessage(string message, Guid uuid, long timestamp, long salt, ref byte[] signature) + public bool IsKeyExpired() { - if (PublicKey == null) + return DateTime.Now.ToUniversalTime() > this.KeyExpiresAt; + } + + /// + /// Verify message - 1.19 + /// + /// Message content + /// Timestamp + /// Salt + /// Message signature + /// Is this message vaild + public bool VerifyMessage(string message, long timestamp, long salt, ref byte[] signature) + { + if (PublicKey == null || IsKeyExpired()) return false; else { - string uuidString = uuid.ToString().Replace("-", string.Empty); - DateTimeOffset timeOffset = DateTimeOffset.FromUnixTimeMilliseconds(timestamp); byte[] saltByte = BitConverter.GetBytes(salt); Array.Reverse(saltByte); - return PublicKey.VerifyMessage(message, uuidString, timeOffset, ref saltByte, ref signature); + return PublicKey.VerifyMessage(message, Uuid, timeOffset, ref saltByte, ref signature); } } + + /// + /// Verify message - 1.19.1 and above + /// + /// Message content + /// Timestamp + /// Salt + /// Message signature + /// Preceding message signature + /// LastSeenMessages + /// Is this message chain vaild + public bool VerifyMessage(string message, long timestamp, long salt, ref byte[] signature, ref byte[]? precedingSignature, LastSeenMessageList lastSeenMessages) + { + if (this.lastMessageVerified == false) + return false; + if (PublicKey == null || IsKeyExpired() || (this.precedingSignature != null && precedingSignature == null)) + return false; + if (this.precedingSignature != null && !this.precedingSignature.SequenceEqual(precedingSignature!)) + return false; + + DateTimeOffset timeOffset = DateTimeOffset.FromUnixTimeMilliseconds(timestamp); + + byte[] saltByte = BitConverter.GetBytes(salt); + Array.Reverse(saltByte); + + bool res = PublicKey.VerifyMessage(message, Uuid, timeOffset, ref saltByte, ref signature, ref precedingSignature, lastSeenMessages); + + this.lastMessageVerified = res; + this.precedingSignature = signature; + + return res; + } + + /// + /// Verify message head - 1.19.1 and above + /// + /// Preceding message signature + /// Message signature + /// Message body hash + /// Is this message chain vaild + public bool VerifyMessageHead(ref byte[]? precedingSignature, ref byte[] headerSignature, ref byte[] bodyDigest) + { + if (this.lastMessageVerified == false) + return false; + if (PublicKey == null || IsKeyExpired() || (this.precedingSignature != null && precedingSignature == null)) + return false; + if (this.precedingSignature != null && !this.precedingSignature.SequenceEqual(precedingSignature!)) + return false; + + bool res = PublicKey.VerifyHeader(ref bodyDigest, ref headerSignature); + + this.lastMessageVerified = res; + this.precedingSignature = headerSignature; + + return res; + } } } diff --git a/MinecraftClient/Protocol/ProfileKey/KeyUtils.cs b/MinecraftClient/Protocol/ProfileKey/KeyUtils.cs index 95c8fd57..775947f1 100644 --- a/MinecraftClient/Protocol/ProfileKey/KeyUtils.cs +++ b/MinecraftClient/Protocol/ProfileKey/KeyUtils.cs @@ -1,14 +1,19 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; +using MinecraftClient.Protocol.Handlers; +using MinecraftClient.Protocol.Message; namespace MinecraftClient.Protocol.Keys { static class KeyUtils { - private static string certificates = "https://api.minecraftservices.com/player/certificates"; + private static readonly SHA256 sha256Hash = SHA256.Create(); + + private static readonly string certificates = "https://api.minecraftservices.com/player/certificates"; public static PlayerKeyPair? GetKeys(string accessToken) { @@ -67,19 +72,18 @@ namespace MinecraftClient.Protocol.Keys return Convert.FromBase64String(key); } - public static byte[] GetSignatureData(string message, string uuid, DateTimeOffset timestamp, ref byte[] salt) + public static byte[] ComputeHash(byte[] data) + { + return sha256Hash.ComputeHash(data); + } + + public static byte[] GetSignatureData(string message, Guid uuid, DateTimeOffset timestamp, ref byte[] salt) { List data = new(); data.AddRange(salt); - byte[] UUIDLeastSignificantBits = BitConverter.GetBytes(Convert.ToInt64(uuid[..16], 16)); - Array.Reverse(UUIDLeastSignificantBits); - data.AddRange(UUIDLeastSignificantBits); - - byte[] UUIDMostSignificantBits = BitConverter.GetBytes(Convert.ToInt64(uuid.Substring(16, 16), 16)); - Array.Reverse(UUIDMostSignificantBits); - data.AddRange(UUIDMostSignificantBits); + data.AddRange(uuid.ToBigEndianBytes()); byte[] timestampByte = BitConverter.GetBytes(timestamp.ToUnixTimeSeconds()); Array.Reverse(timestampByte); @@ -90,6 +94,39 @@ namespace MinecraftClient.Protocol.Keys return data.ToArray(); } + public static byte[] GetSignatureData(string message, DateTimeOffset timestamp, ref byte[] salt, LastSeenMessageList lastSeenMessages) + { + List data = new(); + + data.AddRange(salt); + + byte[] timestampByte = BitConverter.GetBytes(timestamp.ToUnixTimeSeconds()); + Array.Reverse(timestampByte); + data.AddRange(timestampByte); + + data.AddRange(Encoding.UTF8.GetBytes(message)); + + data.Add(70); + + lastSeenMessages.WriteForSign(data); + + return data.ToArray(); + } + + public static byte[] GetSignatureData(byte[]? precedingSignature, Guid sender, byte[] bodySign) + { + List data = new(); + + if (precedingSignature != null) + data.AddRange(precedingSignature); + + data.AddRange(sender.ToBigEndianBytes()); + + data.AddRange(bodySign); + + return data.ToArray(); + } + // https://github.com/mono/mono/blob/master/mcs/class/System.Json/System.Json/JsonValue.cs public static string EscapeString(string src) { diff --git a/MinecraftClient/Protocol/ProfileKey/PrivateKey.cs b/MinecraftClient/Protocol/ProfileKey/PrivateKey.cs index b02be93f..0683ef46 100644 --- a/MinecraftClient/Protocol/ProfileKey/PrivateKey.cs +++ b/MinecraftClient/Protocol/ProfileKey/PrivateKey.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; +using MinecraftClient.Protocol.Message; namespace MinecraftClient.Protocol.Keys { @@ -13,6 +14,8 @@ namespace MinecraftClient.Protocol.Keys private readonly RSA rsa; + private byte[]? precedingSignature = null; + public PrivateKey(string pemKey) { this.Key = KeyUtils.DecodePemKey(pemKey, "-----BEGIN RSA PRIVATE KEY-----", "-----END RSA PRIVATE KEY-----"); @@ -26,7 +29,15 @@ namespace MinecraftClient.Protocol.Keys return rsa.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); } - public byte[] SignMessage(string message, string uuid, DateTimeOffset timestamp, ref byte[] salt) + /// + /// Sign message - 1.19 + /// + /// Message content + /// Sender uuid + /// Timestamp + /// Salt + /// Signature data + public byte[] SignMessage(string message, Guid uuid, DateTimeOffset timestamp, ref byte[] salt) { string messageJson = "{\"text\":\"" + KeyUtils.EscapeString(message) + "\"}"; @@ -35,5 +46,27 @@ namespace MinecraftClient.Protocol.Keys return SignData(data); } + /// + /// Sign message - 1.19.1 and above + /// + /// Message content + /// Sender uuid + /// Timestamp + /// Salt + /// LastSeenMessageList + /// Signature data + public byte[] SignMessage(string message, Guid uuid, DateTimeOffset timestamp, ref byte[] salt, LastSeenMessageList lastSeenMessages) + { + byte[] bodySignData = KeyUtils.GetSignatureData(message, timestamp, ref salt, lastSeenMessages); + byte[] bodyDigest = KeyUtils.ComputeHash(bodySignData); + + byte[] msgSignData = KeyUtils.GetSignatureData(precedingSignature, uuid, bodyDigest); + byte[] msgSign = SignData(msgSignData); + + this.precedingSignature = msgSign; + + return msgSign; + } + } } diff --git a/MinecraftClient/Protocol/ProfileKey/PublicKey.cs b/MinecraftClient/Protocol/ProfileKey/PublicKey.cs index f0a2cdd8..6bec8ae9 100644 --- a/MinecraftClient/Protocol/ProfileKey/PublicKey.cs +++ b/MinecraftClient/Protocol/ProfileKey/PublicKey.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; +using MinecraftClient.Protocol.Message; namespace MinecraftClient.Protocol.Keys { @@ -44,12 +45,53 @@ namespace MinecraftClient.Protocol.Keys return rsa.VerifyData(data, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); } - public bool VerifyMessage(string message, string uuid, DateTimeOffset timestamp, ref byte[] salt, ref byte[] signature) + /// + /// Verify message - 1.19 + /// + /// Message content + /// Sender uuid + /// Timestamp + /// Salt + /// Message signature + /// Is this message vaild + public bool VerifyMessage(string message, Guid uuid, DateTimeOffset timestamp, ref byte[] salt, ref byte[] signature) { byte[] data = KeyUtils.GetSignatureData(message, uuid, timestamp, ref salt); return VerifyData(data, signature); } + /// + /// Verify message - 1.19.1 and above + /// + /// Message content + /// Sender uuid + /// Timestamp + /// Salt + /// Message signature + /// Preceding message signature + /// LastSeenMessages + /// Is this message vaild + public bool VerifyMessage(string message, Guid uuid, DateTimeOffset timestamp, ref byte[] salt, ref byte[] signature, ref byte[]? precedingSignature, LastSeenMessageList lastSeenMessages) + { + byte[] bodySignData = KeyUtils.GetSignatureData(message, timestamp, ref salt, lastSeenMessages); + byte[] bodyDigest = KeyUtils.ComputeHash(bodySignData); + + byte[] msgSignData = KeyUtils.GetSignatureData(precedingSignature, uuid, bodyDigest); + + return VerifyData(msgSignData, signature); + } + + /// + /// Verify message head - 1.19.1 and above + /// + /// Message body hash + /// Message signature + /// Is this message header vaild + public bool VerifyHeader(ref byte[] bodyDigest, ref byte[] signature) + { + return VerifyData(bodyDigest, signature); + } + } } diff --git a/MinecraftClient/Scripting/ChatBot.cs b/MinecraftClient/Scripting/ChatBot.cs index 650bcf8c..4fc59e1e 100644 --- a/MinecraftClient/Scripting/ChatBot.cs +++ b/MinecraftClient/Scripting/ChatBot.cs @@ -1110,7 +1110,7 @@ namespace MinecraftClient /// UserUUID of the current account protected string GetUserUUID() { - return Handler.GetUserUUID(); + return Handler.GetUserUuidStr(); } ///