diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/Tools/ResourceServer.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/Tools/ResourceServer.cs index bfea1b8..9f2e319 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/Tools/ResourceServer.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/Tools/ResourceServer.cs @@ -125,6 +125,14 @@ namespace WelsonJS.Launcher.Tools return; } + // Serve TFA request + const string tfaPrefix = "tfa/"; + if (path.StartsWith(tfaPrefix, StringComparison.OrdinalIgnoreCase)) + { + ServeTfaRequest(context, path.Substring(tfaPrefix.Length - 1)); + return; + } + // Serve a resource ServeResource(context, GetResource(_resourceName), "text/html"); } @@ -251,6 +259,24 @@ namespace WelsonJS.Launcher.Tools } } + private void ServeTfaRequest(HttpListenerContext context, string endpoint) + { + Tfa _tfa = new Tfa(); + + if (endpoint.Equals("/pubkey")) + { + ServeResource(context, _tfa.GetPubKey(), "text/plain", 200); + return; + } + + ServeResource(context); + } + + private void ServeResource(HttpListenerContext context) + { + ServeResource(context, "Not Found", "application/xml", 404); + } + private void ServeResource(HttpListenerContext context, byte[] data, string mimeType = "text/html", int statusCode = 200) { string xmlHeader = ""; diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/Tools/Tfa.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/Tools/Tfa.cs new file mode 100644 index 0000000..7621176 --- /dev/null +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/Tools/Tfa.cs @@ -0,0 +1,65 @@ +using System; +using System.Linq; +using System.Security.Cryptography; +using System.Collections.Generic; + +namespace WelsonJS.Launcher.Tools +{ + public class Tfa + { + private const string Base32Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; + + public int GetOtp(string key) + { + byte[] binaryKey = DecodeBase32(key.Replace(" ", "")); + long timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds() / 30; + byte[] timestampBytes = BitConverter.GetBytes(timestamp); + Array.Reverse(timestampBytes); // Ensure big-endian order + + using (var hmac = new HMACSHA1(binaryKey)) + { + byte[] hash = hmac.ComputeHash(timestampBytes); + int offset = hash[hash.Length - 1] & 0xF; + + int otp = ((hash[offset] & 0x7F) << 24) | + ((hash[offset + 1] & 0xFF) << 16) | + ((hash[offset + 2] & 0xFF) << 8) | + (hash[offset + 3] & 0xFF); + + return otp % 1000000; // Ensure 6-digit OTP + } + } + + public string GetPubKey() + { + var rand = new Random(); + var key = new char[16]; + for (int i = 0; i < 16; i++) + { + key[i] = Base32Chars[rand.Next(Base32Chars.Length)]; + } + return string.Join(" ", Enumerable.Range(0, 4).Select(i => new string(key, i * 4, 4))); + } + + private static byte[] DecodeBase32(string key) + { + int buffer = 0, bitsLeft = 0; + var binaryKey = new List(); + + foreach (char c in key) + { + int value = Base32Chars.IndexOf(c); + if (value < 0) continue; // Ignore invalid characters + + buffer = (buffer << 5) + value; + bitsLeft += 5; + if (bitsLeft >= 8) + { + bitsLeft -= 8; + binaryKey.Add((byte)((buffer >> bitsLeft) & 0xFF)); + } + } + return binaryKey.ToArray(); + } + } +} \ No newline at end of file diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/WelsonJS.Launcher.csproj b/WelsonJS.Toolkit/WelsonJS.Launcher/WelsonJS.Launcher.csproj index d037bbf..3525d39 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/WelsonJS.Launcher.csproj +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/WelsonJS.Launcher.csproj @@ -101,6 +101,7 @@ GlobalSettingsForm.cs + EnvForm.cs