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);
+}