From 2c8b15b02e1603383c256bb06c1ff2c08a792040 Mon Sep 17 00:00:00 2001 From: oldkingOK Date: Sat, 13 Jan 2024 03:00:02 +0800 Subject: [PATCH] Fix bug: Yggdrasil client can't send message upper 22w17a When try to send message in yggdrasil-auth server with `enforce-secure-profile=true` and `online-mode=true` enabled, will fail with message in red: Chat disabled due to missing profile public key. Please try reconnecting. So yggdrasil-auth-client also has to encrypt chat messages like Microsoft-auth-client to send out messages. --- MinecraftClient/Program.cs | 4 +- .../Protocol/ProfileKey/KeyUtils.cs | 55 +++++++++++++++---- 2 files changed, 46 insertions(+), 13 deletions(-) diff --git a/MinecraftClient/Program.cs b/MinecraftClient/Program.cs index f0d8f0b8..29ea3df5 100644 --- a/MinecraftClient/Program.cs +++ b/MinecraftClient/Program.cs @@ -557,7 +557,7 @@ namespace MinecraftClient } } - if (Config.Main.General.AccountType == LoginType.microsoft + if ((Config.Main.General.AccountType == LoginType.microsoft || Config.Main.General.AccountType == LoginType.yggdrasil) && (InternalConfig.Account.Password != "-" || Config.Main.General.Method == LoginMethod.browser) && Config.Signature.LoginWithSecureProfile && protocolversion >= 759 /* 1.19 and above */) @@ -582,7 +582,7 @@ namespace MinecraftClient if (playerKeyPair == null || playerKeyPair.NeedRefresh()) { ConsoleIO.WriteLineFormatted(Translations.mcc_fetching_key, acceptnewlines: true); - playerKeyPair = KeyUtils.GetNewProfileKeys(session.ID); + playerKeyPair = KeyUtils.GetNewProfileKeys(session.ID, Config.Main.General.AccountType == LoginType.yggdrasil); if (Config.Main.Advanced.ProfileKeyCache != CacheType.none && playerKeyPair != null) { KeysCache.Store(loginLower, playerKeyPair); diff --git a/MinecraftClient/Protocol/ProfileKey/KeyUtils.cs b/MinecraftClient/Protocol/ProfileKey/KeyUtils.cs index 4a8d5c66..a4bc6f2b 100644 --- a/MinecraftClient/Protocol/ProfileKey/KeyUtils.cs +++ b/MinecraftClient/Protocol/ProfileKey/KeyUtils.cs @@ -5,6 +5,7 @@ using System.Text; using MinecraftClient.Protocol.Handlers; using MinecraftClient.Protocol.Message; using static MinecraftClient.Protocol.Message.LastSeenMessageList; +using Newtonsoft.Json.Linq; namespace MinecraftClient.Protocol.ProfileKey { @@ -14,27 +15,34 @@ namespace MinecraftClient.Protocol.ProfileKey private static readonly string certificates = "https://api.minecraftservices.com/player/certificates"; - public static PlayerKeyPair? GetNewProfileKeys(string accessToken) + public static PlayerKeyPair? GetNewProfileKeys(string accessToken, bool isYggdrasil) { ProxiedWebRequest.Response? response = null; try { - var request = new ProxiedWebRequest(certificates) - { - Accept = "application/json" - }; - request.Headers.Add("Authorization", string.Format("Bearer {0}", accessToken)); + if (!isYggdrasil) { + var request = new ProxiedWebRequest(certificates) + { + Accept = "application/json" + }; + request.Headers.Add("Authorization", string.Format("Bearer {0}", accessToken)); - response = request.Post("application/json", ""); + response = request.Post("application/json", ""); - if (Settings.Config.Logging.DebugMessages) - { - ConsoleIO.WriteLine(response.Body.ToString()); + if (Settings.Config.Logging.DebugMessages) + { + ConsoleIO.WriteLine(response.Body.ToString()); + } } - string jsonString = response.Body; + // see https://github.com/yushijinhun/authlib-injector/blob/da910956eaa30d2f6c2c457222d188aeb53b0d1f/src/main/java/moe/yushi/authlibinjector/httpd/ProfileKeyFilter.java#L49 + // POST to "https://api.minecraftservices.com/player/certificates" with authlib-injector will get a dummy response + string jsonString = isYggdrasil ? MakeDummyResponse() : response!.Body; Json.JSONData json = Json.ParseJson(jsonString); + Console.WriteLine("Got public key:" + json.Properties["keyPair"].Properties["publicKey"].StringValue); + Console.WriteLine("Got private key:" + json.Properties["keyPair"].Properties["privateKey"].StringValue); + // Error here PublicKey publicKey = new(pemKey: json.Properties["keyPair"].Properties["publicKey"].StringValue, sig: json.Properties["publicKeySignature"].StringValue, sigV2: json.Properties["publicKeySignatureV2"].StringValue); @@ -230,5 +238,30 @@ namespace MinecraftClient.Protocol.ProfileKey sb.Append(src, start, src.Length - start); return sb.ToString(); } + + public static string MakeDummyResponse() + { + RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(2048); + var mimePublicKey = Convert.ToBase64String(rsa.ExportSubjectPublicKeyInfo()); + var mimePrivateKey = Convert.ToBase64String(rsa.ExportPkcs8PrivateKey()); + string publicKeyPEM = $"-----BEGIN RSA PUBLIC KEY-----\n{mimePublicKey}\n-----END RSA PUBLIC KEY-----\n"; + string privateKeyPEM = $"-----BEGIN RSA PRIVATE KEY-----\n{mimePrivateKey}\n-----END RSA PRIVATE KEY-----\n"; + DateTime now = DateTime.UtcNow; + DateTime expiresAt = now.AddHours(48); + DateTime refreshedAfter = now.AddHours(36); + JObject response = new JObject(); + JObject keyPairObj = new JObject + { + { "privateKey", privateKeyPEM }, + { "publicKey", publicKeyPEM } + }; + response.Add("keyPair", keyPairObj); + response.Add("publicKeySignature", "AA=="); + response.Add("publicKeySignatureV2", "AA=="); + string format = "yyyy-MM-ddTHH:mm:ss.ffffffZ"; + response.Add("expiresAt", expiresAt.ToString(format)); + response.Add("refreshedAfter", refreshedAfter.ToString(format)); + return response.ToString(); + } } }