From 91dbe9ae1112f2edaa8a74075dc979624ca4a196 Mon Sep 17 00:00:00 2001 From: Alessio Dal Santo Date: Mon, 30 Mar 2026 16:42:43 +0200 Subject: [PATCH] [Feature] Aggiunta protezione machine-binding tramite MachineGuard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Nuovo progetto MachineGuard: libreria che verifica se la macchina corrente è autorizzata all'esecuzione tramite DPAPI (Data Protection API di Windows) - Nuovo progetto MachineGuardSetup: tool di configurazione da eseguire come Amministratore per registrare la macchina autorizzata - Data_Coupler.sln: aggiunti entrambi i nuovi progetti alla soluzione - Data_Coupler.csproj: aggiunto riferimento al progetto MachineGuard - Program.cs: integrazione MachineGuard all'avvio dell'applicazione; se la macchina non è autorizzata l'app viene arrestata immediatamente con log critico e scrittura nel Windows Event Log --- Data_Coupler.sln | 28 +++ Data_Coupler/Data_Coupler.csproj | 1 + Data_Coupler/Program.cs | 36 ++++ MachineGuard/DpapiMachineGuard.cs | 85 +++++++++ MachineGuard/IMachineGuard.cs | 14 ++ MachineGuard/MachineGuard.csproj | 18 ++ MachineGuard/MachineGuardExtensions.cs | 75 ++++++++ MachineGuard/MachineGuardOptions.cs | 25 +++ MachineGuard/MachineGuardSetupHelper.cs | 73 ++++++++ MachineGuard/MachineGuardToken.cs | 14 ++ MachineGuard/NullMachineGuard.cs | 13 ++ MachineGuardSetup/MachineGuardSetup.csproj | 20 +++ MachineGuardSetup/Program.cs | 198 +++++++++++++++++++++ 13 files changed, 600 insertions(+) create mode 100644 MachineGuard/DpapiMachineGuard.cs create mode 100644 MachineGuard/IMachineGuard.cs create mode 100644 MachineGuard/MachineGuard.csproj create mode 100644 MachineGuard/MachineGuardExtensions.cs create mode 100644 MachineGuard/MachineGuardOptions.cs create mode 100644 MachineGuard/MachineGuardSetupHelper.cs create mode 100644 MachineGuard/MachineGuardToken.cs create mode 100644 MachineGuard/NullMachineGuard.cs create mode 100644 MachineGuardSetup/MachineGuardSetup.csproj create mode 100644 MachineGuardSetup/Program.cs diff --git a/Data_Coupler.sln b/Data_Coupler.sln index 449531a..d9bf0b1 100644 --- a/Data_Coupler.sln +++ b/Data_Coupler.sln @@ -11,6 +11,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CredentialManager", "Creden EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Components", "Components\Components.csproj", "{B5114CAC-3E03-4150-B93C-652882F66CB7}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MachineGuard", "MachineGuard\MachineGuard.csproj", "{AFF3AD52-0356-4879-A0C8-67819611445A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MachineGuardSetup", "MachineGuardSetup\MachineGuardSetup.csproj", "{EACF8FA5-EF21-4D7E-8CA3-347C74C4CD0D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -69,6 +73,30 @@ Global {B5114CAC-3E03-4150-B93C-652882F66CB7}.Release|x64.Build.0 = Release|Any CPU {B5114CAC-3E03-4150-B93C-652882F66CB7}.Release|x86.ActiveCfg = Release|Any CPU {B5114CAC-3E03-4150-B93C-652882F66CB7}.Release|x86.Build.0 = Release|Any CPU + {AFF3AD52-0356-4879-A0C8-67819611445A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AFF3AD52-0356-4879-A0C8-67819611445A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AFF3AD52-0356-4879-A0C8-67819611445A}.Debug|x64.ActiveCfg = Debug|Any CPU + {AFF3AD52-0356-4879-A0C8-67819611445A}.Debug|x64.Build.0 = Debug|Any CPU + {AFF3AD52-0356-4879-A0C8-67819611445A}.Debug|x86.ActiveCfg = Debug|Any CPU + {AFF3AD52-0356-4879-A0C8-67819611445A}.Debug|x86.Build.0 = Debug|Any CPU + {AFF3AD52-0356-4879-A0C8-67819611445A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AFF3AD52-0356-4879-A0C8-67819611445A}.Release|Any CPU.Build.0 = Release|Any CPU + {AFF3AD52-0356-4879-A0C8-67819611445A}.Release|x64.ActiveCfg = Release|Any CPU + {AFF3AD52-0356-4879-A0C8-67819611445A}.Release|x64.Build.0 = Release|Any CPU + {AFF3AD52-0356-4879-A0C8-67819611445A}.Release|x86.ActiveCfg = Release|Any CPU + {AFF3AD52-0356-4879-A0C8-67819611445A}.Release|x86.Build.0 = Release|Any CPU + {EACF8FA5-EF21-4D7E-8CA3-347C74C4CD0D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EACF8FA5-EF21-4D7E-8CA3-347C74C4CD0D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EACF8FA5-EF21-4D7E-8CA3-347C74C4CD0D}.Debug|x64.ActiveCfg = Debug|Any CPU + {EACF8FA5-EF21-4D7E-8CA3-347C74C4CD0D}.Debug|x64.Build.0 = Debug|Any CPU + {EACF8FA5-EF21-4D7E-8CA3-347C74C4CD0D}.Debug|x86.ActiveCfg = Debug|Any CPU + {EACF8FA5-EF21-4D7E-8CA3-347C74C4CD0D}.Debug|x86.Build.0 = Debug|Any CPU + {EACF8FA5-EF21-4D7E-8CA3-347C74C4CD0D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EACF8FA5-EF21-4D7E-8CA3-347C74C4CD0D}.Release|Any CPU.Build.0 = Release|Any CPU + {EACF8FA5-EF21-4D7E-8CA3-347C74C4CD0D}.Release|x64.ActiveCfg = Release|Any CPU + {EACF8FA5-EF21-4D7E-8CA3-347C74C4CD0D}.Release|x64.Build.0 = Release|Any CPU + {EACF8FA5-EF21-4D7E-8CA3-347C74C4CD0D}.Release|x86.ActiveCfg = Release|Any CPU + {EACF8FA5-EF21-4D7E-8CA3-347C74C4CD0D}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Data_Coupler/Data_Coupler.csproj b/Data_Coupler/Data_Coupler.csproj index afd4219..86079ce 100644 --- a/Data_Coupler/Data_Coupler.csproj +++ b/Data_Coupler/Data_Coupler.csproj @@ -20,6 +20,7 @@ + diff --git a/Data_Coupler/Program.cs b/Data_Coupler/Program.cs index 74afb3b..33fe871 100644 --- a/Data_Coupler/Program.cs +++ b/Data_Coupler/Program.cs @@ -10,6 +10,7 @@ using CredentialManager; using Data_Coupler.Services; using Data_Coupler.BackgroundServices; using CredentialManager.Services; +using MachineGuard; using System; using System.Threading.Tasks; @@ -130,6 +131,9 @@ builder.Services.AddScoped(); +// Register MachineGuard — protezione machine-binding tramite DPAPI +builder.Services.AddMachineGuard(builder.Configuration); + // Configurazione URL e timeout per servizio Windows var urls = builder.Configuration.GetValue("Urls") ?? "http://*:7550"; builder.WebHost.UseUrls(urls); @@ -143,6 +147,38 @@ builder.WebHost.ConfigureKestrel(serverOptions => var app = builder.Build(); +#region MachineGuard — verifica autorizzazione macchina +// Questa verifica deve avvenire PRIMA di qualsiasi altra inizializzazione. +// Se la macchina non è autorizzata, l'applicazione viene arrestata immediatamente. +{ + var machineGuard = app.Services.GetRequiredService(); + if (!machineGuard.Verify()) + { + var critLogger = app.Services.GetRequiredService>(); + critLogger.LogCritical( + "MachineGuard: questa macchina NON è autorizzata a eseguire Data Coupler. " + + "Eseguire MachineGuardSetup.exe come Amministratore per configurare questa macchina. " + + "Applicazione arrestata."); + + if (OperatingSystem.IsWindows()) + { + try + { + using var eventLog = new System.Diagnostics.EventLog("Application"); + eventLog.Source = "DataCouplerService"; + eventLog.WriteEntry( + "MachineGuard: macchina non autorizzata. " + + "Eseguire MachineGuardSetup.exe come Amministratore. Applicazione arrestata.", + System.Diagnostics.EventLogEntryType.Error); + } + catch { /* Ignora errori di scrittura EventLog */ } + } + + Environment.Exit(1); + } +} +#endregion + // Initialize database con timeout e retry using (var scope = app.Services.CreateScope()) { diff --git a/MachineGuard/DpapiMachineGuard.cs b/MachineGuard/DpapiMachineGuard.cs new file mode 100644 index 0000000..eff8ecc --- /dev/null +++ b/MachineGuard/DpapiMachineGuard.cs @@ -0,0 +1,85 @@ +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using System.Runtime.Versioning; +using System.Security.Cryptography; +using System.Text; + +namespace MachineGuard; + +/// +/// Implementazione Windows-only della protezione machine-binding tramite DPAPI. +/// Utilizza con scope : +/// il dato cifrato è legato fisicamente alla macchina e non può essere decifrato +/// su un'altra macchina, anche con gli stessi account o credenziali. +/// +[SupportedOSPlatform("windows")] +internal sealed class DpapiMachineGuard : IMachineGuard +{ + private readonly MachineGuardOptions _options; + private readonly ILogger _logger; + + public DpapiMachineGuard(IOptions options, ILogger logger) + { + _options = options.Value; + _logger = logger; + } + + /// + public bool Verify() + { + var secretPath = ResolveSecretFilePath(); + + if (!File.Exists(secretPath)) + { + _logger.LogError( + "MachineGuard: file secret non trovato in '{Path}'. " + + "Eseguire MachineGuardSetup.exe su questa macchina per inizializzare l'autorizzazione.", + secretPath); + return false; + } + + try + { + var encryptedBytes = File.ReadAllBytes(secretPath); + var decryptedBytes = ProtectedData.Unprotect(encryptedBytes, null, DataProtectionScope.LocalMachine); + var decryptedToken = Encoding.UTF8.GetString(decryptedBytes); + + if (!string.Equals(decryptedToken, MachineGuardToken.ExpectedToken, StringComparison.Ordinal)) + { + _logger.LogError( + "MachineGuard: il token decifrato non corrisponde al token atteso. " + + "Questa macchina non è autorizzata a eseguire questa applicazione."); + return false; + } + + _logger.LogInformation("MachineGuard: autorizzazione macchina verificata con successo."); + return true; + } + catch (CryptographicException ex) + { + _logger.LogError(ex, + "MachineGuard: decifrazione fallita. " + + "Il file secret potrebbe provenire da un'altra macchina o essere corrotto. " + + "Percorso: '{Path}'", secretPath); + return false; + } + catch (Exception ex) + { + _logger.LogError(ex, + "MachineGuard: errore imprevisto durante la verifica. Percorso: '{Path}'", secretPath); + return false; + } + } + + private string ResolveSecretFilePath() + { + if (!string.IsNullOrWhiteSpace(_options.SecretFilePath)) + return _options.SecretFilePath; + + var appData = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData); + if (string.IsNullOrEmpty(appData)) + appData = @"C:\ProgramData"; + + return Path.Combine(appData, "DataCoupler", "machine.guard"); + } +} diff --git a/MachineGuard/IMachineGuard.cs b/MachineGuard/IMachineGuard.cs new file mode 100644 index 0000000..17a0718 --- /dev/null +++ b/MachineGuard/IMachineGuard.cs @@ -0,0 +1,14 @@ +namespace MachineGuard; + +/// +/// Interfaccia per il meccanismo di protezione machine-binding tramite DPAPI. +/// +public interface IMachineGuard +{ + /// + /// Verifica che l'applicazione sia in esecuzione su una macchina autorizzata. + /// Restituisce true se l'autorizzazione è confermata, false altrimenti. + /// + bool Verify(); +} + diff --git a/MachineGuard/MachineGuard.csproj b/MachineGuard/MachineGuard.csproj new file mode 100644 index 0000000..afb2609 --- /dev/null +++ b/MachineGuard/MachineGuard.csproj @@ -0,0 +1,18 @@ + + + + net9.0 + enable + enable + + + + + + + + + + + + diff --git a/MachineGuard/MachineGuardExtensions.cs b/MachineGuard/MachineGuardExtensions.cs new file mode 100644 index 0000000..4c04533 --- /dev/null +++ b/MachineGuard/MachineGuardExtensions.cs @@ -0,0 +1,75 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace MachineGuard; + +/// +/// Metodi di estensione per la registrazione di MachineGuard nel container DI. +/// +public static class MachineGuardExtensions +{ + /// + /// Registra il servizio nel container DI. + /// + /// Se MachineGuard:Enabled è false in appsettings, viene registrato + /// un guard no-op che approva sempre la verifica (utile per sviluppo/CI). + /// Su piattaforme non-Windows, DPAPI non è disponibile e il guard viene disabilitato automaticamente. + /// + /// + public static IServiceCollection AddMachineGuard( + this IServiceCollection services, + IConfiguration configuration) + { + services.Configure( + configuration.GetSection(MachineGuardOptions.SectionName)); + + services.AddSingleton(sp => + { + var loggerFactory = sp.GetRequiredService(); + var logger = loggerFactory.CreateLogger("MachineGuard.Startup"); + +#if DEBUG + // In build Debug la protezione è sempre disabilitata — nessuna configurazione richiesta. + logger.LogInformation( + "MachineGuard: build DEBUG — protezione machine-binding disabilitata automaticamente."); + return new NullMachineGuard(); +#else + // In build Release la protezione è sempre attiva. + // Può essere disabilitata esplicitamente via MachineGuard:Enabled = false + // (utile per ambienti Linux/Docker o casi eccezionali). + var options = sp.GetRequiredService>(); + + if (!options.Value.Enabled) + { + logger.LogWarning( + "MachineGuard: protezione machine-binding DISABILITATA via configurazione. " + + "Impostare MachineGuard:Enabled = true in produzione."); + return new NullMachineGuard(); + } + + if (!OperatingSystem.IsWindows()) + { + logger.LogWarning( + "MachineGuard: DPAPI non è disponibile su piattaforme non-Windows. " + + "La protezione machine-binding è bypassata automaticamente."); + return new NullMachineGuard(); + } + + return CreateWindowsGuard(sp, options); +#endif + }); + + return services; + } + + [System.Runtime.Versioning.SupportedOSPlatform("windows")] + private static IMachineGuard CreateWindowsGuard( + IServiceProvider sp, + IOptions options) + { + var logger = sp.GetRequiredService>(); + return new DpapiMachineGuard(options, logger); + } +} diff --git a/MachineGuard/MachineGuardOptions.cs b/MachineGuard/MachineGuardOptions.cs new file mode 100644 index 0000000..c394990 --- /dev/null +++ b/MachineGuard/MachineGuardOptions.cs @@ -0,0 +1,25 @@ +namespace MachineGuard; + +/// +/// Opzioni di configurazione per MachineGuard. +/// Configurabili tramite appsettings.json nella sezione "MachineGuard". +/// +public sealed class MachineGuardOptions +{ + /// Nome della sezione in appsettings.json. + public const string SectionName = "MachineGuard"; + + /// + /// Imposta a false per disabilitare completamente la protezione machine-binding. + /// Utile in ambienti di sviluppo o CI. Default: true. + /// + public bool Enabled { get; set; } = true; + + /// + /// Percorso del file secret cifrato. + /// Se vuoto, viene usato il percorso predefinito: + /// Windows: %ProgramData%\DataCoupler\machine.guard + /// Linux: /etc/datacoupler/machine.guard + /// + public string? SecretFilePath { get; set; } +} diff --git a/MachineGuard/MachineGuardSetupHelper.cs b/MachineGuard/MachineGuardSetupHelper.cs new file mode 100644 index 0000000..e8561b7 --- /dev/null +++ b/MachineGuard/MachineGuardSetupHelper.cs @@ -0,0 +1,73 @@ +using System.Runtime.Versioning; +using System.Security.Cryptography; +using System.Text; + +namespace MachineGuard; + +/// +/// Helper pubblico per la scrittura e la verifica del file secret di MachineGuard. +/// Usato da MachineGuardSetup e da eventuali script di deployment. +/// +public static class MachineGuardSetupHelper +{ + /// + /// Cifra il token interno con DPAPI (LocalMachine scope) e lo scrive nel percorso specificato. + /// Crea la directory di destinazione se non esiste. + /// + /// + /// Percorso completo del file in cui salvare il secret cifrato. + /// Usare per il percorso di default. + /// + [SupportedOSPlatform("windows")] + public static void WriteSecret(string secretFilePath) + { + ArgumentException.ThrowIfNullOrWhiteSpace(secretFilePath); + + var tokenBytes = Encoding.UTF8.GetBytes(MachineGuardToken.ExpectedToken); + var encryptedBytes = ProtectedData.Protect(tokenBytes, null, DataProtectionScope.LocalMachine); + + var directory = Path.GetDirectoryName(secretFilePath); + if (!string.IsNullOrEmpty(directory)) + Directory.CreateDirectory(directory); + + File.WriteAllBytes(secretFilePath, encryptedBytes); + } + + /// + /// Verifica che il file secret nel percorso specificato decifrabile con successo prima del deployment. + /// + [SupportedOSPlatform("windows")] + public static bool VerifySecret(string secretFilePath) + { + if (!File.Exists(secretFilePath)) + return false; + + try + { + var encryptedBytes = File.ReadAllBytes(secretFilePath); + var decryptedBytes = ProtectedData.Unprotect(encryptedBytes, null, DataProtectionScope.LocalMachine); + var decryptedToken = Encoding.UTF8.GetString(decryptedBytes); + return string.Equals(decryptedToken, MachineGuardToken.ExpectedToken, StringComparison.Ordinal); + } + catch + { + return false; + } + } + + /// + /// Restituisce il percorso predefinito del file secret in base al sistema operativo corrente. + /// + public static string GetDefaultSecretFilePath() + { + if (OperatingSystem.IsWindows()) + { + var appData = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData); + if (string.IsNullOrEmpty(appData)) + appData = @"C:\ProgramData"; + return Path.Combine(appData, "DataCoupler", "machine.guard"); + } + + return "/etc/datacoupler/machine.guard"; + } +} diff --git a/MachineGuard/MachineGuardToken.cs b/MachineGuard/MachineGuardToken.cs new file mode 100644 index 0000000..f4ecd55 --- /dev/null +++ b/MachineGuard/MachineGuardToken.cs @@ -0,0 +1,14 @@ +namespace MachineGuard; + +/// +/// Contiene il token di machine-binding cablato nel codice. +/// Modificare questo valore per ogni distribuzione autorizzata, +/// quindi eseguire MachineGuardSetup per scrivere il secret su ogni macchina target. +/// +internal static class MachineGuardToken +{ + /// + /// Token atteso. Deve corrispondere esattamente al valore firmato durante il setup. + /// + internal const string ExpectedToken = "DC-F47AC10B-58CC-4372-A567-0E02B2C3D479"; +} diff --git a/MachineGuard/NullMachineGuard.cs b/MachineGuard/NullMachineGuard.cs new file mode 100644 index 0000000..676b479 --- /dev/null +++ b/MachineGuard/NullMachineGuard.cs @@ -0,0 +1,13 @@ +namespace MachineGuard; + +/// +/// Implementazione no-op di . +/// Usata quando la protezione è disabilitata via configurazione +/// o quando il sistema operativo non supporta DPAPI (es. Linux, macOS). +/// Restituisce sempre true senza eseguire alcuna verifica. +/// +internal sealed class NullMachineGuard : IMachineGuard +{ + /// + public bool Verify() => true; +} diff --git a/MachineGuardSetup/MachineGuardSetup.csproj b/MachineGuardSetup/MachineGuardSetup.csproj new file mode 100644 index 0000000..76494d8 --- /dev/null +++ b/MachineGuardSetup/MachineGuardSetup.csproj @@ -0,0 +1,20 @@ + + + + Exe + net9.0 + enable + enable + MachineGuardSetup + MachineGuardSetup + + true + true + win-x64 + + + + + + + diff --git a/MachineGuardSetup/Program.cs b/MachineGuardSetup/Program.cs new file mode 100644 index 0000000..87f9cac --- /dev/null +++ b/MachineGuardSetup/Program.cs @@ -0,0 +1,198 @@ +using MachineGuard; + +// ============================================================ +// MachineGuardSetup — Strumento di configurazione machine-binding +// Utilizzo: eseguire come Amministratore su ogni server autorizzato +// Indipendente da Data Coupler — nessuna dipendenza dall'applicazione +// ============================================================ + +Console.OutputEncoding = System.Text.Encoding.UTF8; + +PrintBanner(); + +if (!OperatingSystem.IsWindows()) +{ + PrintError("Questo strumento richiede Windows (DPAPI è un'API esclusiva di Windows)."); + return 1; +} + +if (!IsRunningAsAdministrator()) +{ + PrintWarning("Attenzione: l'applicazione non è in esecuzione come Amministratore."); + PrintWarning("La scrittura in C:\\ProgramData potrebbe fallire senza privilegi elevati."); + Console.WriteLine(); +} + +return RunSetupWindows(); + +// ───────────────────────────────────────────────────────────── +// Entry point per Windows (isolato per soddisfare l'analizzatore) +// ───────────────────────────────────────────────────────────── +[System.Runtime.Versioning.SupportedOSPlatform("windows")] +static int RunSetupWindows() +{ + var defaultPath = MachineGuardSetupHelper.GetDefaultSecretFilePath(); + + Console.WriteLine("╔══════════════════════════════════════════════════════════╗"); + Console.WriteLine("║ CONFIGURAZIONE MACHINE-BINDING ║"); + Console.WriteLine("╚══════════════════════════════════════════════════════════╝"); + Console.WriteLine(); + Console.WriteLine($" Percorso predefinito secret: {defaultPath}"); + Console.WriteLine(); + + // Chiedi conferma o percorso personalizzato + Console.Write(" Usare il percorso predefinito? [S/n]: "); + var input = Console.ReadLine()?.Trim().ToUpperInvariant(); + Console.WriteLine(); + + string targetPath; + if (string.IsNullOrEmpty(input) || input == "S" || input == "Y") + { + targetPath = defaultPath; + } + else + { + Console.Write(" Inserire il percorso completo del file secret: "); + var customPath = Console.ReadLine()?.Trim(); + if (string.IsNullOrWhiteSpace(customPath)) + { + PrintError("Percorso non valido. Operazione annullata."); + return 1; + } + targetPath = customPath; + } + + // Verifica se esiste già un secret + if (File.Exists(targetPath)) + { + Console.WriteLine($" ⚠ Il file secret esiste già: {targetPath}"); + Console.Write(" Sovrascrivere? [s/N]: "); + var overwrite = Console.ReadLine()?.Trim().ToUpperInvariant(); + Console.WriteLine(); + + if (overwrite != "S" && overwrite != "Y") + { + Console.ForegroundColor = ConsoleColor.Yellow; + Console.WriteLine(" Operazione annullata dall'utente."); + Console.ResetColor(); + return 0; + } + + // Verifica se il secret attuale è già valido + Console.WriteLine(" Verifica del secret esistente..."); + if (MachineGuardSetupHelper.VerifySecret(targetPath)) + { + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine(" ✓ Il secret esistente è GIÀ valido per questa macchina."); + Console.ResetColor(); + Console.Write(" Continuare comunque e riscrivere? [s/N]: "); + var rewrite = Console.ReadLine()?.Trim().ToUpperInvariant(); + Console.WriteLine(); + if (rewrite != "S" && rewrite != "Y") + { + Console.WriteLine(" Operazione annullata. Il secret esistente rimane invariato."); + return 0; + } + } + } + + // Scrittura del secret + Console.WriteLine($" Scrittura del secret cifrato con DPAPI (LocalMachine) in:"); + Console.WriteLine($" {targetPath}"); + Console.WriteLine(); + + try + { + MachineGuardSetupHelper.WriteSecret(targetPath); + } + catch (UnauthorizedAccessException ex) + { + PrintError($"Accesso negato: {ex.Message}"); + PrintError("Riprovare eseguendo il programma come Amministratore (tasto destro → Esegui come amministratore)."); + return 1; + } + catch (Exception ex) + { + PrintError($"Errore durante la scrittura del secret: {ex.Message}"); + return 1; + } + + // Verifica post-scrittura + Console.WriteLine(" Verifica del secret appena scritto..."); + if (MachineGuardSetupHelper.VerifySecret(targetPath)) + { + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine(" ✓ Secret scritto e verificato con successo!"); + Console.ResetColor(); + Console.WriteLine(); + Console.WriteLine(" Questa macchina è ora autorizzata a eseguire Data Coupler."); + Console.WriteLine(); + PrintFileInfo(targetPath); + } + else + { + PrintError("Verifica post-scrittura fallita. Il file potrebbe essere corrotto."); + return 1; + } + + Console.WriteLine(); + Console.WriteLine(" Premere un tasto per uscire..."); + if (!Console.IsInputRedirected) + Console.ReadKey(intercept: true); + return 0; +} + +// ───────────────────────────────────────────────────────────── +// Funzioni di utilità +// ───────────────────────────────────────────────────────────── + +static void PrintBanner() +{ + Console.ForegroundColor = ConsoleColor.Cyan; + Console.WriteLine(); + Console.WriteLine(" ██████╗ █████╗ ████████╗ █████╗ ██████╗ ██████╗ ██╗ ██╗██████╗ ██╗ ███████╗██████╗ "); + Console.WriteLine(" ██╔══██╗██╔══██╗╚══██╔══╝██╔══██╗ ██╔════╝██╔═══██╗██║ ██║██╔══██╗██║ ██╔════╝██╔══██╗"); + Console.WriteLine(" ██║ ██║███████║ ██║ ███████║ ██║ ██║ ██║██║ ██║██████╔╝██║ █████╗ ██████╔╝"); + Console.WriteLine(" ██║ ██║██╔══██║ ██║ ██╔══██║ ██║ ██║ ██║██║ ██║██╔═══╝ ██║ ██╔══╝ ██╔══██╗"); + Console.WriteLine(" ██████╔╝██║ ██║ ██║ ██║ ██║ ╚██████╗╚██████╔╝╚██████╔╝██║ ███████╗███████╗██║ ██║"); + Console.WriteLine(" ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚══════╝╚══════╝╚═╝ ╚═╝"); + Console.ResetColor(); + Console.WriteLine(); + Console.ForegroundColor = ConsoleColor.White; + Console.WriteLine(" MachineGuardSetup — Configurazione protezione machine-binding per Data Coupler"); + Console.WriteLine(" Versione: 1.0 | Tecnologia: DPAPI (DataProtectionScope.LocalMachine)"); + Console.ResetColor(); + Console.WriteLine(new string('─', 70)); + Console.WriteLine(); +} + +static void PrintError(string message) +{ + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine($" ✗ ERRORE: {message}"); + Console.ResetColor(); +} + +static void PrintWarning(string message) +{ + Console.ForegroundColor = ConsoleColor.Yellow; + Console.WriteLine($" ⚠ {message}"); + Console.ResetColor(); +} + +static void PrintFileInfo(string path) +{ + var fi = new FileInfo(path); + Console.WriteLine(" Dettagli file:"); + Console.WriteLine($" Percorso : {fi.FullName}"); + Console.WriteLine($" Dimensione: {fi.Length} byte (cifrati con DPAPI)"); + Console.WriteLine($" Creato : {fi.CreationTime:dd/MM/yyyy HH:mm:ss}"); +} + +static bool IsRunningAsAdministrator() +{ + if (!OperatingSystem.IsWindows()) return false; + using var identity = System.Security.Principal.WindowsIdentity.GetCurrent(); + var principal = new System.Security.Principal.WindowsPrincipal(identity); + return principal.IsInRole(System.Security.Principal.WindowsBuiltInRole.Administrator); +}