From e388d5b47e5eb0191e685a77a8408279e43e78ca Mon Sep 17 00:00:00 2001 From: Linwenxuan <116782992+Linwenxuan05@users.noreply.github.com> Date: Fri, 26 Jan 2024 10:50:43 +0800 Subject: [PATCH] [Core] NewDeviceVerify stage 1 --- Lagrange.Core/Common/BotKeystore.cs | 1 + .../Event/EventArg/BotNewDeviceVerifyEvent.cs | 15 +++++ Lagrange.Core/Event/EventInvoker.Events.cs | 4 +- Lagrange.Core/Event/EventInvoker.cs | 1 + .../Logic/Implementation/WtExchangeLogic.cs | 20 ++++++ .../Event/Login/NewDeviceLoginEvent.cs | 17 ++++++ .../Plain/Universal/SsoNTLoginError.cs | 2 + .../Service/Login/NewDeviceLoginService.cs | 61 +++++++++++++++++++ .../Service/Login/PasswordLoginService.cs | 1 + 9 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 Lagrange.Core/Event/EventArg/BotNewDeviceVerifyEvent.cs create mode 100644 Lagrange.Core/Internal/Event/Login/NewDeviceLoginEvent.cs create mode 100644 Lagrange.Core/Internal/Service/Login/NewDeviceLoginService.cs diff --git a/Lagrange.Core/Common/BotKeystore.cs b/Lagrange.Core/Common/BotKeystore.cs index 4d95e5c9b..97120e16c 100644 --- a/Lagrange.Core/Common/BotKeystore.cs +++ b/Lagrange.Core/Common/BotKeystore.cs @@ -81,6 +81,7 @@ public class WtLoginSession internal byte[]? UnusualSign { get; set; } internal string? UnusualCookies { get; set; } internal string? CaptchaUrl { get; set; } + internal string? NewDeviceVerifyUrl { get; set; } internal (string, string, string)? Captcha { get; set; } public byte[]? TempPassword { get; set; } diff --git a/Lagrange.Core/Event/EventArg/BotNewDeviceVerifyEvent.cs b/Lagrange.Core/Event/EventArg/BotNewDeviceVerifyEvent.cs new file mode 100644 index 000000000..d5dda0068 --- /dev/null +++ b/Lagrange.Core/Event/EventArg/BotNewDeviceVerifyEvent.cs @@ -0,0 +1,15 @@ +namespace Lagrange.Core.Event.EventArg; + +public class BotNewDeviceVerifyEvent : EventBase +{ + public string Url { get; } + + public byte[] QrCode { get; } + + public BotNewDeviceVerifyEvent(string url, byte[] qrCode) + { + Url = url; + EventMessage = $"[{nameof(BotNewDeviceVerifyEvent)}]: Url: {url}"; + QrCode = qrCode; + } +} \ No newline at end of file diff --git a/Lagrange.Core/Event/EventInvoker.Events.cs b/Lagrange.Core/Event/EventInvoker.Events.cs index 491ba28a4..70fe09951 100644 --- a/Lagrange.Core/Event/EventInvoker.Events.cs +++ b/Lagrange.Core/Event/EventInvoker.Events.cs @@ -10,7 +10,9 @@ public partial class EventInvoker public event LagrangeEvent? OnBotLogEvent; - public event LagrangeEvent? OnBotCaptchaEvent; + public event LagrangeEvent? OnBotCaptchaEvent; + + public event LagrangeEvent? OnBotNewDeviceVerify; public event LagrangeEvent? OnGroupInvitationReceived; diff --git a/Lagrange.Core/Event/EventInvoker.cs b/Lagrange.Core/Event/EventInvoker.cs index 1c93651f6..d974c8758 100644 --- a/Lagrange.Core/Event/EventInvoker.cs +++ b/Lagrange.Core/Event/EventInvoker.cs @@ -19,6 +19,7 @@ internal EventInvoker(BotContext context) RegisterEvent((BotOfflineEvent e) => OnBotOfflineEvent?.Invoke(context, e)); RegisterEvent((BotLogEvent e) => OnBotLogEvent?.Invoke(context, e)); RegisterEvent((BotCaptchaEvent e) => OnBotCaptchaEvent?.Invoke(context, e)); + RegisterEvent((BotNewDeviceVerifyEvent e) => OnBotNewDeviceVerify?.Invoke(context, e)); RegisterEvent((GroupInvitationEvent e) => OnGroupInvitationReceived?.Invoke(context, e)); RegisterEvent((FriendMessageEvent e) => OnFriendMessageReceived?.Invoke(context, e)); RegisterEvent((GroupMessageEvent e) => OnGroupMessageReceived?.Invoke(context, e)); diff --git a/Lagrange.Core/Internal/Context/Logic/Implementation/WtExchangeLogic.cs b/Lagrange.Core/Internal/Context/Logic/Implementation/WtExchangeLogic.cs index 60af183c8..3e3855fcd 100644 --- a/Lagrange.Core/Internal/Context/Logic/Implementation/WtExchangeLogic.cs +++ b/Lagrange.Core/Internal/Context/Logic/Implementation/WtExchangeLogic.cs @@ -187,6 +187,7 @@ public async Task LoginByPassword() case LoginCommon.Error.CaptchaVerify: { Collection.Log.LogInfo(Tag, "Login Success, but captcha is required, please follow the link from event"); + if (Collection.Keystore.Session.CaptchaUrl != null) { var captchaEvent = new BotCaptchaEvent(Collection.Keystore.Session.CaptchaUrl); @@ -203,6 +204,17 @@ public async Task LoginByPassword() Collection.Log.LogInfo(Tag, "Captcha Url is null, please try again later"); return false; } + case LoginCommon.Error.NewDeviceVerify: + { + Collection.Log.LogInfo(Tag, $"NewDeviceVerify Url: {Collection.Keystore.Session.NewDeviceVerifyUrl}"); + + var newDeviceEvent = new BotNewDeviceVerifyEvent("", Array.Empty()); + Collection.Invoker.PostEvent(newDeviceEvent); + + bool result = await _transEmpTask.Task; + if (result) await BotOnline(); + return result; + } default: { Collection.Log.LogWarning(Tag, @event is { Message: not null, Tag: not null } @@ -359,5 +371,13 @@ private async Task DoUnusualEasyLogin() return result.Count != 0 && ((UnusualEasyLoginEvent)result[0]).Success; } + private async Task DoNewDeviceLogin() + { + Collection.Log.LogInfo(Tag, "Trying to Login by EasyLogin..."); + var unusualEvent = NewDeviceLoginEvent.Create(); + var result = await Collection.Business.SendEvent(unusualEvent); + return result.Count != 0 && ((NewDeviceLoginEvent)result[0]).Success; + } + public bool SubmitCaptcha(string ticket, string randStr) => _captchaTask?.TrySetResult((ticket, randStr)) ?? false; } \ No newline at end of file diff --git a/Lagrange.Core/Internal/Event/Login/NewDeviceLoginEvent.cs b/Lagrange.Core/Internal/Event/Login/NewDeviceLoginEvent.cs new file mode 100644 index 000000000..0ba1f9eb4 --- /dev/null +++ b/Lagrange.Core/Internal/Event/Login/NewDeviceLoginEvent.cs @@ -0,0 +1,17 @@ +namespace Lagrange.Core.Internal.Event.Login; + +internal class NewDeviceLoginEvent : ProtocolEvent +{ + public bool Success { get; set; } + + private NewDeviceLoginEvent() : base(true) { } + + private NewDeviceLoginEvent(int result) : base(result) + { + Success = result == 0; + } + + public static NewDeviceLoginEvent Create() => new(); + + public static NewDeviceLoginEvent Result(int result) => new(result); +} \ No newline at end of file diff --git a/Lagrange.Core/Internal/Packets/Login/NTLogin/Plain/Universal/SsoNTLoginError.cs b/Lagrange.Core/Internal/Packets/Login/NTLogin/Plain/Universal/SsoNTLoginError.cs index d9c88fe90..dfcd67ca9 100644 --- a/Lagrange.Core/Internal/Packets/Login/NTLogin/Plain/Universal/SsoNTLoginError.cs +++ b/Lagrange.Core/Internal/Packets/Login/NTLogin/Plain/Universal/SsoNTLoginError.cs @@ -13,4 +13,6 @@ internal class SsoNTLoginError [ProtoMember(2)] public string Tag { get; set; } [ProtoMember(3)] public string Message { get; set; } + + [ProtoMember(4)] public string? NewDeviceVerifyUrl { get; set; } } \ No newline at end of file diff --git a/Lagrange.Core/Internal/Service/Login/NewDeviceLoginService.cs b/Lagrange.Core/Internal/Service/Login/NewDeviceLoginService.cs new file mode 100644 index 000000000..1260260ef --- /dev/null +++ b/Lagrange.Core/Internal/Service/Login/NewDeviceLoginService.cs @@ -0,0 +1,61 @@ +using Lagrange.Core.Common; +using Lagrange.Core.Internal.Event; +using Lagrange.Core.Internal.Event.Login; +using Lagrange.Core.Internal.Packets.Login.NTLogin; +using Lagrange.Core.Internal.Packets.Login.NTLogin.Plain; +using Lagrange.Core.Internal.Packets.Login.NTLogin.Plain.Body; +using Lagrange.Core.Utility.Binary; +using Lagrange.Core.Utility.Crypto; +using ProtoBuf; + +namespace Lagrange.Core.Internal.Service.Login; + +[Service("trpc.login.ecdh.EcdhService.SsoNTLoginPasswordLoginNewDevice")] +internal class NewDeviceLoginService : BaseService +{ + protected override bool Build(NewDeviceLoginEvent input, BotKeystore keystore, BotAppInfo appInfo, BotDeviceInfo device, + out BinaryPacket output, out List? extraPackets) + { + if (keystore.Session.TempPassword == null) throw new InvalidOperationException("TempPassword is null"); + + output = SsoNTLoginCommon.BuildNTLoginPacket(keystore, appInfo, device, keystore.Session.TempPassword); + extraPackets = null; + return true; + } + + protected override bool Parse(byte[] input, BotKeystore keystore, BotAppInfo appInfo, BotDeviceInfo device, + out NewDeviceLoginEvent output, out List? extraEvents) + { + if (keystore.Session.ExchangeKey == null) throw new InvalidOperationException("ExchangeKey is null"); + + var encrypted = Serializer.Deserialize(input.AsSpan()); + + if (encrypted.GcmCalc != null) + { + var decrypted = new AesGcmImpl().Decrypt(encrypted.GcmCalc, keystore.Session.ExchangeKey); + var response = Serializer.Deserialize>(decrypted.AsSpan()); + var body = response.Body; + + if (response.Header?.Error != null || body is not { Credentials: not null }) + { + output = NewDeviceLoginEvent.Result((int)(response.Header?.Error?.ErrorCode ?? 1)); + } + else + { + keystore.Session.Tgt = body.Credentials.Tgt; + keystore.Session.D2 = body.Credentials.D2; + keystore.Session.D2Key = body.Credentials.D2Key; + keystore.Session.TempPassword = body.Credentials.TempPassword; + + output = NewDeviceLoginEvent.Result(0); + } + } + else + { + output = NewDeviceLoginEvent.Result(1); + } + + extraEvents = null; + return true; + } +} \ No newline at end of file diff --git a/Lagrange.Core/Internal/Service/Login/PasswordLoginService.cs b/Lagrange.Core/Internal/Service/Login/PasswordLoginService.cs index 5e80158bf..0aa9a01bb 100644 --- a/Lagrange.Core/Internal/Service/Login/PasswordLoginService.cs +++ b/Lagrange.Core/Internal/Service/Login/PasswordLoginService.cs @@ -57,6 +57,7 @@ protected override bool Parse(byte[] input, BotKeystore keystore, BotAppInfo app keystore.Session.UnusualSign = body?.Unusual?.Sig; keystore.Session.UnusualCookies = response.Header?.Cookie?.Cookie; keystore.Session.CaptchaUrl = body?.Captcha?.Url; + keystore.Session.NewDeviceVerifyUrl = response.Header?.Error?.NewDeviceVerifyUrl; string? tag = response.Header?.Error?.Tag; string? message = response.Header?.Error?.Message;