using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security.Cryptography; using System.Text; using BepInEx; using FishNet; using FishNet.Broadcast; using FishNet.Connection; using FishNet.Managing.Server; using FishNet.Object; using FishNet.Serializing; using FishNet.Transporting; using HarmonyLib; using HowtoFishIdentityAPI.Runtime; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyVersion("0.0.0.0")] namespace HowtoFishIdentityAPI { [BepInPlugin("IceBoxStudio.HowToFish.IdentityAPI", "HowtoFishIdentityAPI", "1.0.0")] public sealed class HowtoFishIdentityPlugin : BaseUnityPlugin { private void Awake() { //IL_0005: Unknown result type (might be due to invalid IL or missing references) new Harmony("IceBoxStudio.HowToFish.IdentityAPI").PatchAll(Assembly.GetExecutingAssembly()); ((BaseUnityPlugin)this).Logger.LogInfo((object)"HowtoFishIdentityAPI 1.0.0 initialized."); } } public static class PluginInfo { public const string PLUGIN_GUID = "IceBoxStudio.HowToFish.IdentityAPI"; public const string PLUGIN_NAME = "HowtoFishIdentityAPI"; public const string PLUGIN_VERSION = "1.0.0"; } } namespace HowtoFishIdentityAPI.Runtime { internal static class ClientIdentity { private static string _privateKey; private static string _publicKey; internal static string PublicKey { get { Load(); return _publicKey ?? string.Empty; } } internal static bool Sign(string challenge, out string proof) { proof = string.Empty; Load(); if (string.IsNullOrEmpty(_privateKey) || !TryBytes(challenge, 32, out var bytes)) { return false; } try { using RSACryptoServiceProvider rSACryptoServiceProvider = new RSACryptoServiceProvider(); rSACryptoServiceProvider.FromXmlString(_privateKey); proof = Convert.ToBase64String(rSACryptoServiceProvider.SignData(bytes, "SHA256")); return true; } catch (Exception) { return false; } } internal static bool Verify(string publicKey, string challenge, string proof) { if (!TryPublicKey(publicKey, out var xml) || !TryBytes(challenge, 32, out var bytes) || !TryBytes(proof, 512, out var bytes2)) { return false; } try { using RSACryptoServiceProvider rSACryptoServiceProvider = new RSACryptoServiceProvider(); rSACryptoServiceProvider.FromXmlString(xml); return rSACryptoServiceProvider.VerifyData(bytes, "SHA256", bytes2); } catch (Exception) { return false; } } internal static bool TryPublicKey(string key, out string xml) { xml = string.Empty; if (!TryBytes(key, 2048, out var bytes)) { return false; } try { string text = Encoding.UTF8.GetString(bytes); if (text.IndexOf("", StringComparison.OrdinalIgnoreCase) >= 0) { return false; } using RSACryptoServiceProvider rSACryptoServiceProvider = new RSACryptoServiceProvider(); rSACryptoServiceProvider.FromXmlString(text); xml = rSACryptoServiceProvider.ToXmlString(includePrivateParameters: false); return true; } catch (Exception) { return false; } } private static void Load() { if (_privateKey != null) { return; } try { string text = Path.Combine(Application.persistentDataPath, "Saves"); string text2 = Path.Combine(text, "HowtoFishIdentityAPI.identity"); string text3 = Path.Combine(text, "PersonalMoney.identity"); if (!File.Exists(text2) && File.Exists(text3)) { Directory.CreateDirectory(text); File.Copy(text3, text2); } if (File.Exists(text2)) { using (RSACryptoServiceProvider rSACryptoServiceProvider = new RSACryptoServiceProvider()) { rSACryptoServiceProvider.FromXmlString(File.ReadAllText(text2)); _privateKey = rSACryptoServiceProvider.ToXmlString(includePrivateParameters: true); _publicKey = Convert.ToBase64String(Encoding.UTF8.GetBytes(rSACryptoServiceProvider.ToXmlString(includePrivateParameters: false))); return; } } using (RSACryptoServiceProvider rSACryptoServiceProvider2 = new RSACryptoServiceProvider(2048)) { _privateKey = rSACryptoServiceProvider2.ToXmlString(includePrivateParameters: true); _publicKey = Convert.ToBase64String(Encoding.UTF8.GetBytes(rSACryptoServiceProvider2.ToXmlString(includePrivateParameters: false))); } Directory.CreateDirectory(text); File.WriteAllText(text2, _privateKey); } catch (Exception) { _privateKey = string.Empty; _publicKey = string.Empty; } } private static bool TryBytes(string value, int maxLength, out byte[] bytes) { bytes = null; if (string.IsNullOrEmpty(value) || value.Length > (maxLength + 2) / 3 * 4) { return false; } try { bytes = Convert.FromBase64String(value); return bytes.Length != 0 && bytes.Length <= maxLength; } catch (FormatException) { return false; } } } public struct IdentityHelloBroadcast : IBroadcast { public string PublicKey; public IdentityHelloBroadcast(string publicKey) { PublicKey = publicKey; } } public struct IdentityChallengeBroadcast : IBroadcast { public string Value; public IdentityChallengeBroadcast(string value) { Value = value; } } public struct IdentityProofBroadcast : IBroadcast { public string Proof; public IdentityProofBroadcast(string proof) { Proof = proof; } } internal sealed class Challenge { internal string PublicKey; internal string Value; internal DateTime Expires; } internal static class IdentityRuntime { private static readonly Dictionary Keys = new Dictionary(); private static readonly HashSet VerifiedClients = new HashSet(); private static readonly Dictionary Challenges = new Dictionary(); private static bool _serializersReady; private static bool _clientRegistered; private static bool _serverRegistered; internal static event Action Verified; internal static bool IsVerified(NetworkConnection connection) { if (connection != (NetworkConnection)null) { return VerifiedClients.Contains(connection.ClientId); } return false; } internal static bool TryGetKey(Player player, out string key) { key = null; if ((Object)(object)player == (Object)null || ((NetworkBehaviour)player).Owner == (NetworkConnection)null || !IsVerified(((NetworkBehaviour)player).Owner)) { return false; } string text = SteamKey(player); if (text != null) { key = text; return true; } return Keys.TryGetValue(((NetworkBehaviour)player).Owner.ClientId, out key); } internal static bool TryGetKey(NetworkConnection connection, out string key) { key = null; if (connection != (NetworkConnection)null && IsVerified(connection)) { return Keys.TryGetValue(connection.ClientId, out key); } return false; } internal static bool IsSteam(Player player) { return SteamKey(player) != null; } internal static bool IsSteam(NetworkConnection connection) { if (TryGetKey(connection, out var key)) { return key.StartsWith("steam:", StringComparison.Ordinal); } return false; } internal static void PlayerStarted(Player player) { if ((Object)(object)player == (Object)null || ((NetworkBehaviour)player).Owner == (NetworkConnection)null) { return; } EnsureNetwork(); if (InstanceFinder.IsServerStarted) { if (((NetworkBehaviour)player).Owner.IsLocalClient) { if (IsSteam(player)) { Mark(((NetworkBehaviour)player).Owner, SteamKey(player)); } else { Hello(((NetworkBehaviour)player).Owner, ClientIdentity.PublicKey); } } } else if (((NetworkBehaviour)player).Owner.IsLocalClient) { SendHello(player); } } internal static void PlayerStopped(Player player) { if (!((Object)(object)player == (Object)null) && !(((NetworkBehaviour)player).Owner == (NetworkConnection)null)) { VerifiedClients.Remove(((NetworkBehaviour)player).Owner.ClientId); Keys.Remove(((NetworkBehaviour)player).Owner.ClientId); Challenges.Remove(((NetworkBehaviour)player).Owner.ClientId); } } internal static void Stop() { VerifiedClients.Clear(); Keys.Clear(); Challenges.Clear(); _clientRegistered = false; _serverRegistered = false; } private static void EnsureNetwork() { if (!_serializersReady) { GenericWriter.SetWrite((Action)delegate(Writer writer, IdentityHelloBroadcast message) { writer.WriteString(message.PublicKey); }); GenericReader.SetRead((Func)((Reader reader) => new IdentityHelloBroadcast(reader.ReadStringAllocated()))); GenericWriter.SetWrite((Action)delegate(Writer writer, IdentityChallengeBroadcast message) { writer.WriteString(message.Value); }); GenericReader.SetRead((Func)((Reader reader) => new IdentityChallengeBroadcast(reader.ReadStringAllocated()))); GenericWriter.SetWrite((Action)delegate(Writer writer, IdentityProofBroadcast message) { writer.WriteString(message.Proof); }); GenericReader.SetRead((Func)((Reader reader) => new IdentityProofBroadcast(reader.ReadStringAllocated()))); _serializersReady = true; } if (!_clientRegistered && (Object)(object)InstanceFinder.ClientManager != (Object)null) { InstanceFinder.ClientManager.RegisterBroadcast((Action)OnChallenge); _clientRegistered = true; } if (!_serverRegistered && (Object)(object)InstanceFinder.ServerManager != (Object)null) { InstanceFinder.ServerManager.RegisterBroadcast((Action)OnHello, true); InstanceFinder.ServerManager.RegisterBroadcast((Action)OnProof, true); _serverRegistered = true; } } private static void OnHello(NetworkConnection connection, IdentityHelloBroadcast message, Channel channel) { Hello(connection, message.PublicKey); } private static void OnProof(NetworkConnection connection, IdentityProofBroadcast message, Channel channel) { Proof(connection, message.Proof); } internal static void Hello(NetworkConnection connection, string publicKey) { Player val = FindPlayer(connection); if (connection == (NetworkConnection)null || (Object)(object)val == (Object)null) { return; } string text = SteamKey(val); if (text != null) { Mark(connection, text); } else { if (!ClientIdentity.TryPublicKey(publicKey, out var _)) { return; } if (connection.IsLocalClient) { Mark(connection, MakeKey(publicKey)); return; } byte[] array = new byte[32]; using (RandomNumberGenerator randomNumberGenerator = RandomNumberGenerator.Create()) { randomNumberGenerator.GetBytes(array); } Challenge challenge = new Challenge { PublicKey = publicKey, Value = Convert.ToBase64String(array), Expires = DateTime.UtcNow.AddSeconds(20.0) }; Challenges[connection.ClientId] = challenge; EnsureNetwork(); ServerManager serverManager = InstanceFinder.ServerManager; if (serverManager != null) { serverManager.Broadcast(connection, new IdentityChallengeBroadcast(challenge.Value), true, (Channel)0); } } } internal static void Proof(NetworkConnection connection, string proof) { if (!(connection == (NetworkConnection)null) && Challenges.TryGetValue(connection.ClientId, out var value)) { Challenges.Remove(connection.ClientId); if (!(value.Expires < DateTime.UtcNow) && ClientIdentity.Verify(value.PublicKey, value.Value, proof)) { Mark(connection, MakeKey(value.PublicKey)); } } } private static void OnChallenge(IdentityChallengeBroadcast message, Channel channel) { if (ClientIdentity.Sign(message.Value, out var proof) && !((Object)(object)InstanceFinder.ClientManager == (Object)null)) { InstanceFinder.ClientManager.Broadcast(new IdentityProofBroadcast(proof), (Channel)0); } } private static void SendHello(Player player) { if (!((Object)(object)player == (Object)null) && !((Object)(object)InstanceFinder.ClientManager == (Object)null)) { string text = (ConnectionManager.IsUsingSteam ? "steam" : ClientIdentity.PublicKey); if (!string.IsNullOrEmpty(text)) { InstanceFinder.ClientManager.Broadcast(new IdentityHelloBroadcast(text), (Channel)0); } } } private static void Mark(NetworkConnection connection, string key) { if (!(connection == (NetworkConnection)null) && !string.IsNullOrEmpty(key)) { Keys[connection.ClientId] = key; VerifiedClients.Add(connection.ClientId); IdentityRuntime.Verified?.Invoke(connection); } } private static string MakeKey(string publicKey) { if (!ClientIdentity.TryPublicKey(publicKey, out var xml)) { return null; } using SHA256 sHA = SHA256.Create(); byte[] array = sHA.ComputeHash(Encoding.UTF8.GetBytes(xml)); return "key:" + BitConverter.ToString(array).Replace("-", string.Empty).ToLowerInvariant(); } private static string SteamKey(Player player) { if ((Object)(object)player == (Object)null || !ConnectionManager.IsUsingSteam) { return null; } if (player.SteamID != 0L) { return "steam:" + player.SteamID; } if (((NetworkBehaviour)player).Owner != (NetworkConnection)null && ulong.TryParse(((NetworkBehaviour)player).Owner.GetAddress(), out var result) && result != 0L) { return "steam:" + result; } return null; } private static Player FindPlayer(NetworkConnection connection) { if (connection == (NetworkConnection)null) { return null; } foreach (Player player in PlayerManager.Players) { if ((Object)(object)player != (Object)null && ((NetworkBehaviour)player).Owner != (NetworkConnection)null && ((NetworkBehaviour)player).Owner.ClientId == connection.ClientId) { return player; } } return null; } } } namespace HowtoFishIdentityAPI.Patches { [HarmonyPatch(typeof(Player), "OnStartClient")] internal static class PlayerStartPatch { private static void Postfix(Player __instance) { IdentityRuntime.PlayerStarted(__instance); } } [HarmonyPatch(typeof(Player), "OnStopClient")] internal static class PlayerStopPatch { private static void Postfix(Player __instance) { IdentityRuntime.PlayerStopped(__instance); } } [HarmonyPatch(typeof(Server), "OnStopServer")] internal static class ServerStopPatch { private static void Postfix() { IdentityRuntime.Stop(); } } } namespace HowtoFishIdentityAPI.Api { public static class IdentityApi { public static event Action Verified { add { IdentityRuntime.Verified += value; } remove { IdentityRuntime.Verified -= value; } } public static bool IsVerified(NetworkConnection connection) { return IdentityRuntime.IsVerified(connection); } public static bool IsVerified(Player player) { if ((Object)(object)player != (Object)null) { return IsVerified(((NetworkBehaviour)player).Owner); } return false; } public static bool TryGetKey(NetworkConnection connection, out string key) { return IdentityRuntime.TryGetKey(connection, out key); } public static bool TryGetKey(Player player, out string key) { return IdentityRuntime.TryGetKey(player, out key); } public static bool IsSteam(Player player) { return IdentityRuntime.IsSteam(player); } public static bool IsSteam(NetworkConnection connection) { return IdentityRuntime.IsSteam(connection); } } }