using System; using System.Linq; using MinecraftClient.Protocol.Message; using MinecraftClient.Protocol.ProfileKey; namespace MinecraftClient.Protocol { public class PlayerInfo { public readonly Guid Uuid; public readonly string Name; // Tuple[]? Property; public int Gamemode; public int Ping; public string? DisplayName; // Entity info public Mapping.Entity? entity; // For message signature private readonly PublicKey? PublicKey; 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; Name = name; if (property != null) Property = property; Gamemode = gamemode; Ping = ping; DisplayName = displayName; lastMessageVerified = false; if (timeStamp != null && publicKey != null && signature != null) { DateTimeOffset dateTimeOffset = DateTimeOffset.FromUnixTimeMilliseconds((long)timeStamp); KeyExpiresAt = dateTimeOffset.UtcDateTime; try { PublicKey = new PublicKey(publicKey, signature); lastMessageVerified = true; } catch (System.Security.Cryptography.CryptographicException) { PublicKey = null; } } precedingSignature = null; } public PlayerInfo(string name, Guid uuid) { Name = name; Uuid = uuid; Gamemode = -1; Ping = 0; lastMessageVerified = true; precedingSignature = null; } public bool IsMessageChainLegal() { return lastMessageVerified; } public bool IsKeyExpired() { return DateTime.Now.ToUniversalTime() > 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 { DateTimeOffset timeOffset = DateTimeOffset.FromUnixTimeMilliseconds(timestamp); byte[] saltByte = BitConverter.GetBytes(salt); Array.Reverse(saltByte); 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 (lastMessageVerified == false) return false; if (PublicKey == null || IsKeyExpired() || (this.precedingSignature != null && precedingSignature == null)) { lastMessageVerified = false; return false; } if (this.precedingSignature != null && !this.precedingSignature.SequenceEqual(precedingSignature!)) { lastMessageVerified = false; 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); 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 (lastMessageVerified == false) return false; if (PublicKey == null || IsKeyExpired() || (this.precedingSignature != null && precedingSignature == null)) { lastMessageVerified = false; return false; } if (this.precedingSignature != null && !this.precedingSignature.SequenceEqual(precedingSignature!)) { lastMessageVerified = false; return false; } bool res = PublicKey.VerifyHeader(Uuid, ref bodyDigest, ref headerSignature, ref precedingSignature); lastMessageVerified = res; this.precedingSignature = headerSignature; return res; } } }