using FishNet.Connection; using FishNet.Example.Authenticating; using FishNet.Managing; using FishNet.Transporting; using System; using System.Security.Cryptography; using System.Text; using UnityEngine; namespace FishNet.Authenticating { /// /// This authenticator is an example of how to let host bypass the authentication process. /// When checking to authenticate on the client side call AuthenticateAsHost, and if returned true skip normal authentication. /// public abstract class HostAuthenticator : Authenticator { #region Serialized. /// /// True to enable use of AuthenticateAsHost. /// [Tooltip("True to enable use of AuthenticateAsHost.")] [SerializeField] private bool _allowHostAuthentication; /// /// Sets if to allow host authentication. /// /// public void SetAllowHostAuthentication(bool value) => _allowHostAuthentication = value; /// /// Returns if AllowHostAuthentication is enabled. /// /// public bool GetAllowHostAuthentication() => _allowHostAuthentication; #endregion #region Private. /// /// A random hash which only exist if the server is started. /// private static string _hostHash = string.Empty; #endregion /// /// Initializes this script for use. /// /// public override void InitializeOnce(NetworkManager networkManager) { base.InitializeOnce(networkManager); //Listen for connection state of local server to set hash. base.NetworkManager.ServerManager.OnServerConnectionState += ServerManager_OnServerConnectionState; //Listen for broadcast from client. Be sure to set requireAuthentication to false. base.NetworkManager.ServerManager.RegisterBroadcast(OnHostPasswordBroadcast, false); } /// /// Called after the local server connection state changes. /// private void ServerManager_OnServerConnectionState(ServerConnectionStateArgs obj) { int length = (obj.ConnectionState == LocalConnectionState.Started) ? 25 : 0; SetHostHash(length); } /// /// Received on server when a client sends the password broadcast message. /// /// Connection sending broadcast. /// private void OnHostPasswordBroadcast(NetworkConnection conn, HostPasswordBroadcast hpb, Channel channel) { //Not accepting host authentications. This could be an attack. if (!_allowHostAuthentication) { conn.Disconnect(true); return; } /* If client is already authenticated this could be an attack. Connections * are removed when a client disconnects so there is no reason they should * already be considered authenticated. */ if (conn.IsAuthenticated) { conn.Disconnect(true); return; } bool correctPassword = (hpb.Password == _hostHash); OnHostAuthenticationResult(conn, correctPassword); } /// /// Called after handling a host authentication result. /// /// Connection authenticating. /// True if authentication passed. protected abstract void OnHostAuthenticationResult(NetworkConnection conn, bool authenticated); /// /// Sets a host hash of length. /// /// https://stackoverflow.com/questions/32932679/using-rngcryptoserviceprovider-to-generate-random-string private void SetHostHash(int length) { if (length <= 0) { _hostHash = string.Empty; } else { const string charPool = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!@#$%^&*()"; StringBuilder result = new(); using (RNGCryptoServiceProvider rng = new()) { byte[] uintBuffer = new byte[sizeof(uint)]; while (length-- > 0) { rng.GetBytes(uintBuffer); uint num = BitConverter.ToUInt32(uintBuffer, 0); result.Append(charPool[(int)(num % (uint)charPool.Length)]); } } _hostHash = result.ToString(); } } /// /// Returns true if authentication was sent as host. /// /// protected bool AuthenticateAsHost() { if (!_allowHostAuthentication) return false; if (_hostHash == string.Empty) return false; HostPasswordBroadcast hpb = new() { Password = _hostHash, }; base.NetworkManager.ClientManager.Broadcast(hpb); return true; } } }