Cifratura del codice legata alla licenza
Fai dipendere una funzionalità protetta, a livello crittografico, da una licenza valida conservando la password della cifratura del codice nella licenza stessa.
Un controllo booleano della licenza, if (!IsLicensed) return;, è facile da eliminare con una patch.
Questo esempio mostra uno schema più robusto: il metodo sensibile è cifrato con la
cifratura del codice di Babel Obfuscator, e la password che serve
alla Babel Virtual Machine (BVM) per eseguirlo non è affatto nel file binario. Si trova, a sua volta
cifrata, in una licenza Babel firmata e vincolata al computer. Rimuovere il controllo della licenza
non basta: senza una licenza valida non c’è password, e senza la password il metodo cifrato non può
essere ricostruito né eseguito.
Esempio di codice
git clone https://github.com/babelfornet/license-bound-encryption-console-example.gitIl progetto è un’applicazione console .NET multipiattaforma in due parti: SecureApp,
l’applicazione con licenza il cui metodo sensibile è protetto dalla cifratura del codice, e
LicenseGenerator, uno strumento del produttore che emette licenze firmate. La versione del pacchetto
Babel è centralizzata in Directory.Build.props, così l’esempio può usare la versione presente nel tuo feed.
Il metodo protetto
La proprietà intellettuale da proteggere è un solo metodo. L’attributo [Obfuscation] indica a
Babel di cifrarne il corpo e di conservarlo all’interno dell’assembly (internal=true) con il nome
di origine core, usando una password definita in fase di build:
[Obfuscation(Feature = "msil encryption:internal=true;source=core;password=C0re-Pr0tect!on-K3y", Exclude = false)]
public static long Compute(int a, int b)
{
long acc = 0;
for (int i = 1; i <= b; i++)
acc += (long)a * i;
return acc + 42;
}La cifratura del codice è abilitata solo nella build Release, limitata alla classe sensibile tramite
una proprietà MSBuild in SecureApp.csproj:
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<MsilEncryption Condition="'$(MsilEncryption)' == ''">SecureApp\.Secret::.*</MsilEncryption>
<BabelWarningsAsErrors>W00000</BabelWarningsAsErrors>
</PropertyGroup>La cifratura del codice viene eseguita solo nella configurazione Release. La riga BabelWarningsAsErrors
trasforma in errore l’avviso W00000 della modalità demo, così un assembly non protetto non può mai
essere distribuito per sbaglio.
Dove si trova la password
In fase di build il metodo viene cifrato con la password C0re-Pr0tect!on-K3y. In fase di esecuzione
la BVM chiede quella password all’applicazione attraverso un hook get password prima di poter eseguire
il metodo. Invece di restituire un valore scritto nel codice, l’hook convalida la licenza e legge la
password da lì:
[Obfuscation(Feature = "msil encryption get password")]
internal static string GetSourcePassword(string source)
{
var license = Validate();
// The code password is carried by the license as an encrypted field.
var field = license.Fields.FirstOrDefault(f => f.Name == source)
?? throw new InvalidOperationException($"License does not grant source '{source}'.");
// Decrypt the field with the vendor secret to recover the real code password.
return field.Value.Decrypt(Secrets.FieldSecret);
}Validate() usa la chiave pubblica RSA incorporata per verificare la firma della licenza e
applica tutte le restrizioni, compreso il vincolo al computer:
public static ILicense Validate()
{
var manager = new StringLicenseManager
{
SignatureProvider = RSASignature.FromKeys(PublicKey)
};
return manager.Validate(File.ReadAllText(LicenseFilePath), typeof(Program));
}Emettere la licenza
LicenseGenerator, lo strumento del produttore, firma una licenza vincolata all’assembly SecureApp e
al computer corrente, che contiene la password del codice in un campo cifrato. La password quindi
non compare mai in chiaro, né nel file binario né nel file di licenza:
string encryptedPassword = Secrets.CodePassword.Encrypt(Secrets.FieldSecret);
var license = new StringLicense()
.ForAssembly(assemblyFullName)
.WithUniqueId("SECURE-")
.WithHardwareKey(HardwareId.Create().ToMachineKey())
.WithField(Secrets.Source, encryptedPassword)
.LicensedTo("Demo Customer", "demo@example.com", "ACME Corp");
string serial = license.SignWith(signature).ToReadableString("ASCII");LicenseGenerator e la chiave privata RSA (keys.pem) hanno solo scopo dimostrativo. In
produzione la licenza è emessa dal produttore, oppure dal
Babel Licensing Service, e la chiave privata è conservata
offline. Con l’applicazione viene distribuita solo la chiave pubblica.
Eseguire l’esempio
# Build the app in Release — this is when Babel encrypts Secret.Compute.
dotnet build src/SecureApp/SecureApp.csproj -c Release
# Generate a signed, machine-locked license.
dotnet run --project src/LicenseGenerator
# Deploy the license next to the app and run it.
cp SecureApp.lic src/SecureApp/bin/Release/net8.0/
dotnet src/SecureApp/bin/Release/net8.0/SecureApp.dllCon una licenza valida il metodo protetto viene eseguito:
License OK : SECURE-8CCC8
Licensed to : Demo Customer
Bound to machine: ADSQH-89GGJ-J7CJY-3FQFK-DF48H
Protected computation Secret.Compute(7, 5) = 147
The encrypted method ran: the license unlocked the code.Se rimuovi o manometti SecureApp.lic, oppure esegui l’applicazione su un altro computer, il risultato
non è soltanto un messaggio di errore più cortese, perché il codice cifrato non può materialmente essere eseguito:
License INVALID : Invalid license signature
Protected computation blocked: Invalid license signature
Without a valid license the encrypted code cannot be decrypted or executed.Perché è più robusto
Poiché la password proviene da una licenza firmata, un attaccante che elimina dall’assembly il codice
di convalida non ottiene nulla: la BVM continua a non avere la password per l’origine core, quindi
Secret.Compute resta un blob cifrato e opaco. La firma impedisce di falsificare una licenza,
e il vincolo al computer impedisce di riutilizzarne una legittima su un altro computer. La stessa tecnica
è alla base dell’esempio Licenze basate sulle funzionalità e
dei Modelli di licenza ospitati dal produttore, in cui il campo cifrato
è fornito dal Babel Licensing Service anziché da un generatore locale.