58 lines
2.4 KiB
C#
58 lines
2.4 KiB
C#
using System.Security.Cryptography;
|
|
using System.Text.Json;
|
|
using App.Core.Interfaces.Licensing;
|
|
using App.Core.Services.Licensing;
|
|
|
|
namespace App.Infrastructure.Licensing;
|
|
|
|
/// <summary>
|
|
/// Stocke l'état de l'essai gratuit chiffré par DPAPI dans un fichier séparé de la base SQLite
|
|
/// (voir docs/DATABASE_SCHEMA.md §Licence) : supprimer/remplacer app.db ne doit jamais
|
|
/// réinitialiser l'essai.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Scope <see cref="DataProtectionScope.LocalMachine"/> plutôt que <c>CurrentUser</c> : la
|
|
/// protection est liée au POSTE, pas au compte Windows qui exécute l'app — cohérent avec le
|
|
/// modèle de licence "à vie, liée au poste". Contrepartie assumée : sur un poste multi-comptes,
|
|
/// n'importe quel compte Windows local peut déchiffrer ce fichier (mais pas le copier utilement
|
|
/// sur un autre poste, ce qui est le seul contournement qu'on cherche à bloquer ici).
|
|
/// </remarks>
|
|
public class DpapiTrialStateStore(string filePath) : ITrialStateStore
|
|
{
|
|
public TrialState? Load()
|
|
{
|
|
if (!File.Exists(filePath))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
try
|
|
{
|
|
var protectedBytes = File.ReadAllBytes(filePath);
|
|
var jsonBytes = ProtectedData.Unprotect(protectedBytes, optionalEntropy: null, DataProtectionScope.LocalMachine);
|
|
return JsonSerializer.Deserialize<TrialState>(jsonBytes);
|
|
}
|
|
catch (Exception ex) when (ex is CryptographicException or JsonException)
|
|
{
|
|
// Fichier corrompu ou déplacé depuis un autre poste (DPAPI ne peut pas le déchiffrer) :
|
|
// traité comme "pas d'essai enregistré" -> LicenseService en recréera un. Sans risque
|
|
// d'abus significatif : au pire ça redonne un essai, ça ne contourne pas une licence
|
|
// payante déjà activée (qui, elle, est vérifiée par signature Ed25519 indépendamment).
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public void Save(TrialState state)
|
|
{
|
|
var directory = Path.GetDirectoryName(filePath);
|
|
if (!string.IsNullOrEmpty(directory))
|
|
{
|
|
Directory.CreateDirectory(directory);
|
|
}
|
|
|
|
var jsonBytes = JsonSerializer.SerializeToUtf8Bytes(state);
|
|
var protectedBytes = ProtectedData.Protect(jsonBytes, optionalEntropy: null, DataProtectionScope.LocalMachine);
|
|
File.WriteAllBytes(filePath, protectedBytes);
|
|
}
|
|
}
|