using System;
using System.Security.Cryptography;
using System.Text;
namespace GameCult.Networking
{
///
/// Carries the server-side networking configuration and server-only signing secret.
///
public sealed class ServerSecurityOptions
{
///
/// Environment variable name for the LiteNetLib connection key.
///
public const string ConnectionKeyEnvironmentVariable = "GAMECULT_CONNECTION_KEY";
///
/// Environment variable name for the session-token signing secret.
///
public const string SessionSigningSecretEnvironmentVariable = "GAMECULT_SESSION_SIGNING_SECRET";
private readonly byte[] _encryptionKey;
private readonly byte[] _sessionSigningKey;
///
/// Shared LiteNetLib connection key and encryption seed.
///
public string ConnectionKey { get; }
///
/// Gets a value indicating whether the options were created for explicit development use.
///
public bool IsDevelopment { get; }
///
/// Initializes a new server security configuration.
///
public ServerSecurityOptions(string connectionKey, string sessionSigningSecret, bool isDevelopment = false)
{
if (string.IsNullOrWhiteSpace(connectionKey))
throw new ArgumentException("Connection key must be provided.", nameof(connectionKey));
if (string.IsNullOrWhiteSpace(sessionSigningSecret))
throw new ArgumentException("Session signing secret must be provided.", nameof(sessionSigningSecret));
ConnectionKey = connectionKey;
IsDevelopment = isDevelopment;
_encryptionKey = ComputeSha256(Encoding.UTF8.GetBytes(connectionKey));
_sessionSigningKey = ComputeSha256(Encoding.UTF8.GetBytes(sessionSigningSecret));
}
///
/// Creates a strict production-style configuration from environment variables.
///
///
/// When true, returns if both environment variables are absent.
///
public static ServerSecurityOptions FromEnvironment(bool allowDevelopmentDefaults = false)
{
var connectionKey = Environment.GetEnvironmentVariable(ConnectionKeyEnvironmentVariable);
var sessionSigningSecret = Environment.GetEnvironmentVariable(SessionSigningSecretEnvironmentVariable);
var missingConnectionKey = string.IsNullOrWhiteSpace(connectionKey);
var missingSessionSigningSecret = string.IsNullOrWhiteSpace(sessionSigningSecret);
if (missingConnectionKey && missingSessionSigningSecret)
{
if (allowDevelopmentDefaults)
return Development();
throw new InvalidOperationException(
"Server security configuration is not configured. Set " +
$"{ConnectionKeyEnvironmentVariable} and {SessionSigningSecretEnvironmentVariable}, " +
"or explicitly use ServerSecurityOptions.Development() for local development.");
}
if (missingConnectionKey || missingSessionSigningSecret)
{
throw new InvalidOperationException(
"Server security configuration is partially configured. Missing: " +
string.Join(", ", GetMissingVariables(missingConnectionKey, missingSessionSigningSecret)) + ".");
}
return new ServerSecurityOptions(connectionKey!, sessionSigningSecret!);
}
///
/// Creates a deterministic development-only configuration.
///
public static ServerSecurityOptions Development()
{
return new ServerSecurityOptions(
"gamecult-dev-connection-key",
"gamecult-dev-session-signing-secret",
true);
}
internal byte[] GetEncryptionKey()
{
return _encryptionKey;
}
internal byte[] GetSessionSigningKey()
{
return _sessionSigningKey;
}
internal ClientSecurityOptions ToClientOptions()
{
return new ClientSecurityOptions(ConnectionKey);
}
private static byte[] ComputeSha256(byte[] data)
{
using var sha256 = SHA256.Create();
return sha256.ComputeHash(data);
}
private static string[] GetMissingVariables(bool missingConnectionKey, bool missingSessionSigningSecret)
{
if (missingConnectionKey && missingSessionSigningSecret)
return [ConnectionKeyEnvironmentVariable, SessionSigningSecretEnvironmentVariable];
if (missingConnectionKey)
return [ConnectionKeyEnvironmentVariable];
return [SessionSigningSecretEnvironmentVariable];
}
}
}