using System; using System.Net.Http; using System.Security.Cryptography; using System.Text; using UnityEngine; using System.Text.Json; using System.IO; // this entire file could be better namespace undead_universal_patch_il2cpp.Galvanic { public class GalvanicAuth { public static void PrepareKeys() { PlayerPrefs.DeleteKey("GalvanicPublicKey"); PlayerPrefs.DeleteKey("GalvanicPrivateKey"); using (ECDsa ecdsa = ECDsa.Create(ECCurve.NamedCurves.nistP256)) { byte[] publicKeyBytes = ecdsa.ExportSubjectPublicKeyInfo(); string publicKeyBase64 = Convert.ToBase64String(publicKeyBytes); byte[] privateKeyBytes = ecdsa.ExportPkcs8PrivateKey(); string privateKeyBase64 = Convert.ToBase64String(privateKeyBytes); PlayerPrefs.SetString("GalvanicPublicKey", publicKeyBase64); PlayerPrefs.SetString("GalvanicPrivateKey", privateKeyBase64); PlayerPrefs.Save(); Plugin.Log.LogInfo("Created new keypair for Galvanic authentication."); } } public static string GetPubKey() { string publicKey = PlayerPrefs.GetString("GalvanicPublicKey", null); if (string.IsNullOrEmpty(publicKey)) { Plugin.Log.LogInfo("No keypair found, generating new."); PrepareKeys(); return GetPubKey(); } else return publicKey; } private static ECDsa GetPrivKey() { string privateKey = PlayerPrefs.GetString("GalvanicPrivateKey", null); if (string.IsNullOrEmpty(privateKey)) { Plugin.Log.LogInfo("No keypair found, generating new."); PrepareKeys(); return GetPrivKey(); } byte[] privKeyBytes = Convert.FromBase64String(privateKey); ECDsa crypt = ECDsa.Create(); crypt.ImportPkcs8PrivateKey(privKeyBytes, out _); return crypt; } public static string SignPayload(string message) { using (ECDsa ecdsa = GetPrivKey()) { if (ecdsa == null) return null; byte[] payloadBytes = Encoding.UTF8.GetBytes(message); byte[] signature = ecdsa.SignData(payloadBytes, HashAlgorithmName.SHA256); return Convert.ToBase64String(signature); } } public static void Export() { File.WriteAllText("./galvanic_keys_export.txt", $"{GetPubKey()}\n{Convert.ToBase64String(GetPrivKey().ExportPkcs8PrivateKey())}"); Plugin.Log.LogWarning("Galvanic Authentication keys were exported."); } public static void Import() { try { string imported = File.ReadAllText("./galvanic_keys_export.txt").ToString(); string privkey = imported.Split("\n")[1]; string pubkey = imported.Split("\n")[0]; if (privkey == null || pubkey == null) throw new Exception("Either imported key was null"); PlayerPrefs.SetString("GalvanicPrivateKey", privkey); PlayerPrefs.SetString("GalvanicPublicKey", pubkey); PlayerPrefs.Save(); } catch (Exception err) { Plugin.Log.LogError($"Could not import Galvanic Authentication keys: {err}"); } } public static ServerInfoRes GetServerInfo() { UriBuilder nameserver = new(NameserverConfig.NewUrl.Value); nameserver.Path = "/info"; nameserver.Query = ""; using HttpClient client = new(); var response = client.GetAsync(nameserver.ToString()).Result; if (!response.IsSuccessStatusCode) return null; var str = response.Content.ReadAsStringAsync().Result; var res = JsonSerializer.Deserialize(str); GalvanicCorrosion.Name = res.name; GalvanicCorrosion.Id = res.id; GalvanicCorrosion.Motd = res.motd; GalvanicCorrosion.Patches = res.patches; return res; } } public static class GalvanicCorrosion { public static string Name { get; set; } = "Galvanic Corrosion"; public static string Id { get; set; } = "galvanic-corrosion-default"; public static string Motd { get; set; } = "A Galvanic Corrosion server."; public static string[] Patches { get; set; } = []; } public static class GalvanicWebAuth { public static string Token { get; private set; } = null; public static void TokenExpiry() { string url = NameserverConfig.NewUrl.Value; UriBuilder uri = new(url); uri.Path = "/user/checkExpired"; uri.Query = ""; HttpClient client = new(); client.DefaultRequestHeaders.Add("GalvanicAuth", Token); HttpResponseMessage res = client.GetAsync(uri.ToString()).Result; if (res.IsSuccessStatusCode) { bool expired = JsonSerializer.Deserialize(res.Content.ReadAsStringAsync().Result.ToString()); if (expired) GetToken(); } } public static void GetToken() { string url = NameserverConfig.NewUrl.Value; UriBuilder uri = new(url); uri.Path = "/user/auth"; uri.Query = ""; var info = GalvanicAuth.GetServerInfo(); Plugin.Log.LogInfo($"Sending authentication request to server ID '{info.id}'"); if (uri.Scheme == "http") Plugin.Log.LogWarning("The server is not secure! Please use HTTPS."); UserAuthPayload payload = new UserAuthPayload { timestamp = ((DateTimeOffset)DateTime.Now).ToUnixTimeSeconds(), nonce = Guid.NewGuid().ToString(), server_id = info.id, }; UserAuthRequest request = new UserAuthRequest { message = payload, signature = GalvanicAuth.SignPayload($"{JsonSerializer.Serialize(payload)}"), client_id = UniqueID.GetUniqueId(), pubkey = GalvanicAuth.GetPubKey() }; HttpClient client = new(); HttpResponseMessage res = client.PostAsync(uri.ToString(), new StringContent(JsonSerializer.Serialize(request, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping }), Encoding.UTF8, "application/json")).Result; if (!res.IsSuccessStatusCode) { Plugin.Log.LogError("The GalvanicWeb auth response was not successful."); return; } string str = res.Content.ReadAsStringAsync().Result; TokenRes token = JsonSerializer.Deserialize(str); Token = token.token; } } public static class UniqueID { public static string GetUniqueId() { if (!PlayerPrefs.HasKey("UniqueId")) SaveUniqueId(GenerateUniqueId()); return PlayerPrefs.GetString("UniqueId"); } public static void SaveUniqueId(string id) { PlayerPrefs.SetString("UniqueId", id); PlayerPrefs.Save(); } public static string GenerateUniqueId() { byte[] buffer = new byte[128]; new System.Random().NextBytes(buffer); return System.Convert.ToBase64String(buffer).Substring(0, 128); // god help me } } public class ServerInfoRes { public string name { get; set; } public string id { get; set; } public string motd { get; set; } public string[] patches { get; set; } } class TokenRes { public string token { get; set; } } class UserAuthPayload { public long timestamp { get; set; } public string nonce { get; set; } public string server_id { get; set; } } class UserAuthRequest { public string client_id { get; set; } public UserAuthPayload message { get; set; } public string signature { get; set; } public string pubkey { get; set; } } }