Decompiled source of HowtoFishIdentityAPI v1.0.0

BepInEx/plugins/HowtoFishIdentityAPI/HowtoFishIdentityAPI.dll

Decompiled 12 hours ago
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("<D>", 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<int, string> Keys = new Dictionary<int, string>();

		private static readonly HashSet<int> VerifiedClients = new HashSet<int>();

		private static readonly Dictionary<int, Challenge> Challenges = new Dictionary<int, Challenge>();

		private static bool _serializersReady;

		private static bool _clientRegistered;

		private static bool _serverRegistered;

		internal static event Action<NetworkConnection> 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<IdentityHelloBroadcast>.SetWrite((Action<Writer, IdentityHelloBroadcast>)delegate(Writer writer, IdentityHelloBroadcast message)
				{
					writer.WriteString(message.PublicKey);
				});
				GenericReader<IdentityHelloBroadcast>.SetRead((Func<Reader, IdentityHelloBroadcast>)((Reader reader) => new IdentityHelloBroadcast(reader.ReadStringAllocated())));
				GenericWriter<IdentityChallengeBroadcast>.SetWrite((Action<Writer, IdentityChallengeBroadcast>)delegate(Writer writer, IdentityChallengeBroadcast message)
				{
					writer.WriteString(message.Value);
				});
				GenericReader<IdentityChallengeBroadcast>.SetRead((Func<Reader, IdentityChallengeBroadcast>)((Reader reader) => new IdentityChallengeBroadcast(reader.ReadStringAllocated())));
				GenericWriter<IdentityProofBroadcast>.SetWrite((Action<Writer, IdentityProofBroadcast>)delegate(Writer writer, IdentityProofBroadcast message)
				{
					writer.WriteString(message.Proof);
				});
				GenericReader<IdentityProofBroadcast>.SetRead((Func<Reader, IdentityProofBroadcast>)((Reader reader) => new IdentityProofBroadcast(reader.ReadStringAllocated())));
				_serializersReady = true;
			}
			if (!_clientRegistered && (Object)(object)InstanceFinder.ClientManager != (Object)null)
			{
				InstanceFinder.ClientManager.RegisterBroadcast<IdentityChallengeBroadcast>((Action<IdentityChallengeBroadcast, Channel>)OnChallenge);
				_clientRegistered = true;
			}
			if (!_serverRegistered && (Object)(object)InstanceFinder.ServerManager != (Object)null)
			{
				InstanceFinder.ServerManager.RegisterBroadcast<IdentityHelloBroadcast>((Action<NetworkConnection, IdentityHelloBroadcast, Channel>)OnHello, true);
				InstanceFinder.ServerManager.RegisterBroadcast<IdentityProofBroadcast>((Action<NetworkConnection, IdentityProofBroadcast, Channel>)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<IdentityChallengeBroadcast>(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<IdentityProofBroadcast>(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<IdentityHelloBroadcast>(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<NetworkConnection> 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);
		}
	}
}