2 Commits

Author SHA1 Message Date
Alessio Dal Santo 91dbe9ae11 [Feature] Aggiunta protezione machine-binding tramite MachineGuard
- 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
2026-03-30 16:42:43 +02:00
Alessio Dal Santo e43b7dc869 [Fix] Correzione controllo sicurezza query SQL: uso regex con word boundary per evitare falsi positivi su nomi colonna (es. UpdateDate, CreateDate) e gestione corretta dei prefissi sp_/xp_ 2026-03-30 16:40:39 +02:00
14 changed files with 623 additions and 6 deletions
+28
View File
@@ -11,6 +11,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CredentialManager", "Creden
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Components", "Components\Components.csproj", "{B5114CAC-3E03-4150-B93C-652882F66CB7}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Components", "Components\Components.csproj", "{B5114CAC-3E03-4150-B93C-652882F66CB7}"
EndProject 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 Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU 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|x64.Build.0 = Release|Any CPU
{B5114CAC-3E03-4150-B93C-652882F66CB7}.Release|x86.ActiveCfg = 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 {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 EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
+1
View File
@@ -20,6 +20,7 @@
<ProjectReference Include="..\DataConnection\DataConnection.csproj" /> <ProjectReference Include="..\DataConnection\DataConnection.csproj" />
<ProjectReference Include="..\CredentialManager\CredentialManager.csproj" /> <ProjectReference Include="..\CredentialManager\CredentialManager.csproj" />
<ProjectReference Include="..\Components\Components.csproj" /> <ProjectReference Include="..\Components\Components.csproj" />
<ProjectReference Include="..\MachineGuard\MachineGuard.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
+23 -6
View File
@@ -2894,25 +2894,42 @@ public partial class DataCoupler : ComponentBase
if (!trimmedQuery.StartsWith("SELECT", StringComparison.OrdinalIgnoreCase)) if (!trimmedQuery.StartsWith("SELECT", StringComparison.OrdinalIgnoreCase))
return false; return false;
// Lista di parole chiave vietate per sicurezza // Parole chiave complete: devono essere token SQL isolati (\bKEYWORD\b)
var forbiddenKeywords = new[] // Evita falsi positivi su nomi di colonne come UpdateDate, CreateDate, DeletedAt, ecc.
var forbiddenFullKeywords = new[]
{ {
"INSERT", "UPDATE", "DELETE", "DROP", "CREATE", "ALTER", "TRUNCATE", "INSERT", "UPDATE", "DELETE", "DROP", "CREATE", "ALTER", "TRUNCATE",
"EXEC", "EXECUTE", "sp_", "xp_", "BULK", "OPENROWSET", "OPENDATASOURCE" "EXEC", "EXECUTE", "BULK", "OPENROWSET", "OPENDATASOURCE"
}; };
// Prefissi pericolosi: devono iniziare la parola (sp_anything, xp_anything)
// Non si usa \bprefix\b perché _ è un word-char e mancherebbe sp_executesql, xp_cmdshell, ecc.
var forbiddenPrefixes = new[] { "SP_", "XP_" };
var upperQuery = trimmedQuery.ToUpperInvariant(); var upperQuery = trimmedQuery.ToUpperInvariant();
// Verifica che non contenga parole chiave vietate // Verifica parole chiave complete con word boundary
foreach (var keyword in forbiddenKeywords) foreach (var keyword in forbiddenFullKeywords)
{ {
if (upperQuery.Contains(keyword)) var pattern = $@"\b{System.Text.RegularExpressions.Regex.Escape(keyword)}\b";
if (System.Text.RegularExpressions.Regex.IsMatch(upperQuery, pattern))
{ {
Logger.LogWarning("Query rifiutata: contiene parola chiave vietata '{Keyword}'", keyword); Logger.LogWarning("Query rifiutata: contiene parola chiave vietata '{Keyword}'", keyword);
return false; return false;
} }
} }
// Verifica prefissi stored procedure: \bSP_ cattura sp_anything, xp_anything
foreach (var prefix in forbiddenPrefixes)
{
var pattern = $@"\b{System.Text.RegularExpressions.Regex.Escape(prefix)}";
if (System.Text.RegularExpressions.Regex.IsMatch(upperQuery, pattern))
{
Logger.LogWarning("Query rifiutata: contiene prefisso stored procedure vietato '{Prefix}'", prefix);
return false;
}
}
return true; return true;
} }
+36
View File
@@ -10,6 +10,7 @@ using CredentialManager;
using Data_Coupler.Services; using Data_Coupler.Services;
using Data_Coupler.BackgroundServices; using Data_Coupler.BackgroundServices;
using CredentialManager.Services; using CredentialManager.Services;
using MachineGuard;
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -130,6 +131,9 @@ builder.Services.AddScoped<Data_Coupler.Services.IDeletionSyncService, Data_Coup
// Register Background Services (solo uno per evitare duplicazioni) // Register Background Services (solo uno per evitare duplicazioni)
builder.Services.AddHostedService<Data_Coupler.BackgroundServices.ScheduledJobService>(); builder.Services.AddHostedService<Data_Coupler.BackgroundServices.ScheduledJobService>();
// Register MachineGuard — protezione machine-binding tramite DPAPI
builder.Services.AddMachineGuard(builder.Configuration);
// Configurazione URL e timeout per servizio Windows // Configurazione URL e timeout per servizio Windows
var urls = builder.Configuration.GetValue<string>("Urls") ?? "http://*:7550"; var urls = builder.Configuration.GetValue<string>("Urls") ?? "http://*:7550";
builder.WebHost.UseUrls(urls); builder.WebHost.UseUrls(urls);
@@ -143,6 +147,38 @@ builder.WebHost.ConfigureKestrel(serverOptions =>
var app = builder.Build(); 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<IMachineGuard>();
if (!machineGuard.Verify())
{
var critLogger = app.Services.GetRequiredService<ILogger<Program>>();
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 // Initialize database con timeout e retry
using (var scope = app.Services.CreateScope()) using (var scope = app.Services.CreateScope())
{ {
+85
View File
@@ -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;
/// <summary>
/// Implementazione Windows-only della protezione machine-binding tramite DPAPI.
/// Utilizza <see cref="ProtectedData"/> con scope <see cref="DataProtectionScope.LocalMachine"/>:
/// il dato cifrato è legato fisicamente alla macchina e non può essere decifrato
/// su un'altra macchina, anche con gli stessi account o credenziali.
/// </summary>
[SupportedOSPlatform("windows")]
internal sealed class DpapiMachineGuard : IMachineGuard
{
private readonly MachineGuardOptions _options;
private readonly ILogger<DpapiMachineGuard> _logger;
public DpapiMachineGuard(IOptions<MachineGuardOptions> options, ILogger<DpapiMachineGuard> logger)
{
_options = options.Value;
_logger = logger;
}
/// <inheritdoc/>
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");
}
}
+14
View File
@@ -0,0 +1,14 @@
namespace MachineGuard;
/// <summary>
/// Interfaccia per il meccanismo di protezione machine-binding tramite DPAPI.
/// </summary>
public interface IMachineGuard
{
/// <summary>
/// Verifica che l'applicazione sia in esecuzione su una macchina autorizzata.
/// Restituisce <c>true</c> se l'autorizzazione è confermata, <c>false</c> altrimenti.
/// </summary>
bool Verify();
}
+18
View File
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="9.0.0" />
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="9.0.0" />
</ItemGroup>
</Project>
+75
View File
@@ -0,0 +1,75 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace MachineGuard;
/// <summary>
/// Metodi di estensione per la registrazione di MachineGuard nel container DI.
/// </summary>
public static class MachineGuardExtensions
{
/// <summary>
/// Registra il servizio <see cref="IMachineGuard"/> nel container DI.
/// <para>
/// Se <c>MachineGuard:Enabled</c> è <c>false</c> 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.
/// </para>
/// </summary>
public static IServiceCollection AddMachineGuard(
this IServiceCollection services,
IConfiguration configuration)
{
services.Configure<MachineGuardOptions>(
configuration.GetSection(MachineGuardOptions.SectionName));
services.AddSingleton<IMachineGuard>(sp =>
{
var loggerFactory = sp.GetRequiredService<ILoggerFactory>();
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<IOptions<MachineGuardOptions>>();
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<MachineGuardOptions> options)
{
var logger = sp.GetRequiredService<ILogger<DpapiMachineGuard>>();
return new DpapiMachineGuard(options, logger);
}
}
+25
View File
@@ -0,0 +1,25 @@
namespace MachineGuard;
/// <summary>
/// Opzioni di configurazione per MachineGuard.
/// Configurabili tramite appsettings.json nella sezione "MachineGuard".
/// </summary>
public sealed class MachineGuardOptions
{
/// <summary>Nome della sezione in appsettings.json.</summary>
public const string SectionName = "MachineGuard";
/// <summary>
/// Imposta a <c>false</c> per disabilitare completamente la protezione machine-binding.
/// Utile in ambienti di sviluppo o CI. Default: <c>true</c>.
/// </summary>
public bool Enabled { get; set; } = true;
/// <summary>
/// Percorso del file secret cifrato.
/// Se vuoto, viene usato il percorso predefinito:
/// Windows: %ProgramData%\DataCoupler\machine.guard
/// Linux: /etc/datacoupler/machine.guard
/// </summary>
public string? SecretFilePath { get; set; }
}
+73
View File
@@ -0,0 +1,73 @@
using System.Runtime.Versioning;
using System.Security.Cryptography;
using System.Text;
namespace MachineGuard;
/// <summary>
/// Helper pubblico per la scrittura e la verifica del file secret di MachineGuard.
/// Usato da MachineGuardSetup e da eventuali script di deployment.
/// </summary>
public static class MachineGuardSetupHelper
{
/// <summary>
/// Cifra il token interno con DPAPI (LocalMachine scope) e lo scrive nel percorso specificato.
/// Crea la directory di destinazione se non esiste.
/// </summary>
/// <param name="secretFilePath">
/// Percorso completo del file in cui salvare il secret cifrato.
/// Usare <see cref="GetDefaultSecretFilePath"/> per il percorso di default.
/// </param>
[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);
}
/// <summary>
/// Verifica che il file secret nel percorso specificato decifrabile con successo prima del deployment.
/// </summary>
[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;
}
}
/// <summary>
/// Restituisce il percorso predefinito del file secret in base al sistema operativo corrente.
/// </summary>
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";
}
}
+14
View File
@@ -0,0 +1,14 @@
namespace MachineGuard;
/// <summary>
/// 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.
/// </summary>
internal static class MachineGuardToken
{
/// <summary>
/// Token atteso. Deve corrispondere esattamente al valore firmato durante il setup.
/// </summary>
internal const string ExpectedToken = "DC-F47AC10B-58CC-4372-A567-0E02B2C3D479";
}
+13
View File
@@ -0,0 +1,13 @@
namespace MachineGuard;
/// <summary>
/// Implementazione no-op di <see cref="IMachineGuard"/>.
/// Usata quando la protezione è disabilitata via configurazione
/// o quando il sistema operativo non supporta DPAPI (es. Linux, macOS).
/// Restituisce sempre <c>true</c> senza eseguire alcuna verifica.
/// </summary>
internal sealed class NullMachineGuard : IMachineGuard
{
/// <inheritdoc/>
public bool Verify() => true;
}
@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AssemblyName>MachineGuardSetup</AssemblyName>
<RootNamespace>MachineGuardSetup</RootNamespace>
<!-- Standalone: publish as single self-contained exe for easy deployment -->
<PublishSingleFile>true</PublishSingleFile>
<SelfContained>true</SelfContained>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\MachineGuard\MachineGuard.csproj" />
</ItemGroup>
</Project>
+198
View File
@@ -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);
}