feat: Aggiunto sistema crittografia credenziali portabile e migrazione

Sostituita Windows ProtectedData con AES-256-GCM per compatibilità multi-macchina.
Aggiunta interfaccia migrazione guidata per credenziali legacy e gestione errori completa.

- Nuovo: Servizio crittografia AES con derivazione chiavi PBKDF2
- Nuovo: Interfaccia Blazor migrazione con rilevamento credenziali
- Nuovo: Documentazione utente per risoluzione problemi
- Fix: Errori compilazione e problemi binding componenti
- Miglioramento: Credenziali portabili funzionano su qualsiasi macchina dopo migrazione una-tantum

Completamente retrocompatibile - credenziali
This commit is contained in:
Alessio Dal Santo
2025-06-17 12:24:09 +02:00
parent c22b4a2613
commit 562784e097
8 changed files with 597 additions and 31 deletions
@@ -666,8 +666,7 @@ public class CredentialService : ICredentialService
return await query.Select(c => c.Name).ToListAsync();
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore nel recuperare i nomi delle credenziali");
{ _logger.LogError(ex, "Errore nel recuperare i nomi delle credenziali");
throw;
}
}
@@ -677,7 +676,8 @@ public class CredentialService : ICredentialService
#region Private Mapping Methods
private DatabaseCredential MapToDatabaseCredential(CredentialEntity entity)
{ var credential = new DatabaseCredential
{
var credential = new DatabaseCredential
{
Name = entity.Name,
DatabaseType = Enum.Parse<DatabaseType>(entity.DatabaseType!),
@@ -685,7 +685,7 @@ public class CredentialService : ICredentialService
Port = entity.Port ?? 0,
DatabaseName = entity.DatabaseName ?? string.Empty,
Username = entity.Username ?? string.Empty,
Password = _encryptionService.Decrypt(entity.EncryptedPassword!),
Password = DecryptSafely(entity.EncryptedPassword, entity.Name, "password"),
ConnectionString = entity.ConnectionString,
CommandTimeout = entity.CommandTimeout,
IgnoreSslErrors = entity.IgnoreSslErrors
@@ -715,15 +715,14 @@ public class CredentialService : ICredentialService
? serviceType
: RestServiceType.Generic,
BaseUrl = entity.Host ?? string.Empty,
Username = entity.Username,
Password = !string.IsNullOrEmpty(entity.EncryptedPassword)
? _encryptionService.Decrypt(entity.EncryptedPassword)
Username = entity.Username, Password = !string.IsNullOrEmpty(entity.EncryptedPassword)
? DecryptSafely(entity.EncryptedPassword, entity.Name, "password")
: null,
ApiKey = !string.IsNullOrEmpty(entity.EncryptedApiKey)
? _encryptionService.Decrypt(entity.EncryptedApiKey)
? DecryptSafely(entity.EncryptedApiKey, entity.Name, "API key")
: null,
AuthToken = !string.IsNullOrEmpty(entity.EncryptedAuthToken)
? _encryptionService.Decrypt(entity.EncryptedAuthToken)
? DecryptSafely(entity.EncryptedAuthToken, entity.Name, "auth token")
: null,
TimeoutSeconds = entity.TimeoutSeconds,
IgnoreSslErrors = entity.IgnoreSslErrors
@@ -919,4 +918,47 @@ public class CredentialService : ICredentialService
}
#endregion
#region Private Utility Methods /// <summary>
/// Decrittografa in modo sicuro gestendo i fallimenti dovuti a migrazione tra macchine
/// </summary>
/// <param name="encryptedValue">Valore crittografato</param>
/// <param name="credentialName">Nome della credenziale per logging</param>
/// <param name="fieldName">Nome del campo per logging</param>
/// <returns>Valore decrittografato o stringa speciale per indicare che serve re-inserimento</returns>
private string DecryptSafely(string? encryptedValue, string credentialName, string fieldName)
{
if (string.IsNullOrEmpty(encryptedValue))
return string.Empty;
try
{
return _encryptionService.Decrypt(encryptedValue);
}
catch (InvalidOperationException ex) when (ex.Message.Contains("Impossibile decrittografare"))
{
_logger.LogWarning("Impossibile decrittografare {FieldName} per la credenziale {CredentialName}. Probabile migrazione tra macchine diverse.",
fieldName, credentialName);
return "*** CREDENZIALI NON DISPONIBILI - REINSERIRE ***";
}
catch (Exception ex)
{
_logger.LogWarning("Problema nella decrittografia di {FieldName} per la credenziale {CredentialName}: {Message}",
fieldName, credentialName, ex.Message);
return "*** ERRORE DECRITTOGRAFIA ***";
}
}
/// <summary>
/// Verifica se una credenziale ha bisogno di essere re-inserita
/// </summary>
/// <param name="credentialValue">Valore della credenziale</param>
/// <returns>True se la credenziale deve essere re-inserita</returns>
private bool NeedsReentry(string credentialValue)
{
return credentialValue.Contains("*** CREDENZIALI NON DISPONIBILI") ||
credentialValue.Contains("*** ERRORE DECRITTOGRAFIA ***");
}
#endregion
}