using NetMQ; using Phantom.Common.Data; using Phantom.Utils.Cryptography; using Phantom.Utils.IO; using Phantom.Utils.Logging; using Serilog; namespace Phantom.Controller; abstract class ConnectionKeyFiles { private const string CommonKeyFileExtension = ".key"; private const string SecretKeyFileExtension = ".secret"; private readonly ILogger logger; private readonly string commonKeyFileName; private readonly string secretKeyFileName; private ConnectionKeyFiles(ILogger logger, string name) { this.logger = logger; this.commonKeyFileName = name + CommonKeyFileExtension; this.secretKeyFileName = name + SecretKeyFileExtension; } public async Task<ConnectionKeyData?> CreateOrLoad(string folderPath) { string commonKeyFilePath = Path.Combine(folderPath, commonKeyFileName); string secretKeyFilePath = Path.Combine(folderPath, secretKeyFileName); bool commonKeyFileExists = File.Exists(commonKeyFilePath); bool secretKeyFileExists = File.Exists(secretKeyFilePath); if (commonKeyFileExists && secretKeyFileExists) { try { return await ReadKeyFiles(commonKeyFilePath, secretKeyFilePath); } catch (IOException e) { logger.Fatal("Error reading connection key files."); logger.Fatal(e.Message); return null; } catch (Exception) { logger.Fatal("Connection key files contain invalid data."); return null; } } if (commonKeyFileExists || secretKeyFileExists) { string existingKeyFilePath = commonKeyFileExists ? commonKeyFilePath : secretKeyFilePath; string missingKeyFileName = commonKeyFileExists ? secretKeyFileName : commonKeyFileName; logger.Fatal("The connection key file {ExistingKeyFilePath} exists but {MissingKeyFileName} does not. Please delete it to regenerate both files.", existingKeyFilePath, missingKeyFileName); return null; } logger.Information("Creating connection key files in: {FolderPath}", folderPath); try { return await GenerateKeyFiles(commonKeyFilePath, secretKeyFilePath); } catch (Exception e) { logger.Fatal("Error creating connection key files."); logger.Fatal(e.Message); return null; } } private async Task<ConnectionKeyData?> ReadKeyFiles(string commonKeyFilePath, string secretKeyFilePath) { byte[] commonKeyBytes = await ReadKeyFile(commonKeyFilePath); byte[] secretKeyBytes = await ReadKeyFile(secretKeyFilePath); var (publicKey, authToken) = ConnectionCommonKey.FromBytes(commonKeyBytes); var certificate = new NetMQCertificate(secretKeyBytes, publicKey); logger.Information("Loaded connection key files."); LogCommonKey(commonKeyFilePath, TokenGenerator.EncodeBytes(commonKeyBytes)); return new ConnectionKeyData(certificate, authToken); } private static Task<byte[]> ReadKeyFile(string filePath) { Files.RequireMaximumFileSize(filePath, 64); return File.ReadAllBytesAsync(filePath); } private async Task<ConnectionKeyData> GenerateKeyFiles(string commonKeyFilePath, string secretKeyFilePath) { var certificate = new NetMQCertificate(); var authToken = AuthToken.Generate(); var commonKey = new ConnectionCommonKey(certificate.PublicKey, authToken).ToBytes(); await Files.WriteBytesAsync(secretKeyFilePath, certificate.SecretKey, FileMode.Create, Chmod.URW_GR); await Files.WriteBytesAsync(commonKeyFilePath, commonKey, FileMode.Create, Chmod.URW_GR); logger.Information("Created new connection key files."); LogCommonKey(commonKeyFilePath, TokenGenerator.EncodeBytes(commonKey)); return new ConnectionKeyData(certificate, authToken); } protected abstract void LogCommonKey(string commonKeyFilePath, string commonKeyEncoded); internal sealed class Agent : ConnectionKeyFiles { public Agent() : base(PhantomLogger.Create<ConnectionKeyFiles, Agent>(), "agent") {} protected override void LogCommonKey(string commonKeyFilePath, string commonKeyEncoded) { logger.Information("Agent key file: {AgentKeyFilePath}", commonKeyFilePath); logger.Information("Agent key: {AgentKey}", commonKeyEncoded); } } internal sealed class Web : ConnectionKeyFiles { public Web() : base(PhantomLogger.Create<ConnectionKeyFiles, Web>(), "web") {} protected override void LogCommonKey(string commonKeyFilePath, string commonKeyEncoded) { logger.Information("Web key file: {WebKeyFilePath}", commonKeyFilePath); logger.Information("Web key: {WebKey}", commonKeyEncoded); } } }