Recuperare la chiave del bytecode array dal backend
Mantenere la chiave di decrittazione del bytecode VM fuori dal client, recuperandola dal proprio backend al momento del caricamento con un key getter asincrono protetto da autenticazione, così che un bundle sottratto risulti inerte.
Il problema
vmBytecodeArrayEncoding cifra il bytecode della VM con una chiave. Se tale chiave
viene distribuita all'interno del bundle, chiunque disponga del file possiede tutto il necessario per decifrarlo. Un
vmBytecodeArrayEncodingKeyGetter consente di collocare la chiave altrove e di produrla a runtime — ma un getter sincrono
può leggere soltanto ciò che è già presente sul client (una variabile globale, un cookie, localStorage). Per recuperare la
chiave dal proprio server — così da poterla proteggere con l'autenticazione e revocarla — il getter deve essere
asincrono.
La soluzione
Attivare vmAsyncExecutor (v7.3.0+). Con l'executor asincrono il key getter può
restituire una Promise ed è quindi in grado di recuperare la chiave dal backend tramite fetch prima che la VM entri in
esecuzione. Tre opzioni operano di concerto:
vmBytecodeArrayEncoding: true— cifra il bytecode array.vmBytecodeArrayEncodingKey— la chiave utilizzata in fase di compilazione (conservata sul proprio server e non nel bundle).vmBytecodeArrayEncodingKeyGetter— un'espressione JS che restituisce la stessa chiave a runtime. ConvmAsyncExecutorattivo può restituire unaPromise; in caso contrario il getter deve restituire la chiave in modo sincrono.
Configurazione di offuscamento lato client
JavaScriptObfuscator.obfuscate(sourceCode, {
vmObfuscation: true,
vmAsyncExecutor: true,
vmBytecodeArrayEncoding: true,
vmBytecodeArrayEncodingKey: process.env.VM_KEY, // e.g. 'mySecretKey123'
vmBytecodeArrayEncodingKeyGetter:
'fetch("/api/vm-key", { credentials: "include" }).then((res) => res.text())'
});
L'espressione del getter viene incorporata testualmente e valutata nel browser. È importante tenere la chiave di
compilazione vmBytecodeArrayEncodingKey fuori dal repository del client: occorre iniettarla da una variabile d'ambiente o
da un segreto in fase di build e restituire esattamente la stessa stringa dall'endpoint illustrato di seguito.
Come operano le due chiavi
La chiave dell'utente non viene mai utilizzata da sola: su entrambi i lati viene combinata con una chiave interna controllata dall'offuscatore:
- In fase di compilazione.
vmBytecodeArrayEncodingKeyviene combinata con una chiave interna derivata dall'offuscatore e il bytecode array viene codificato con la chiave mista risultante. - A runtime. Il valore a cui si risolve il proprio
vmBytecodeArrayEncodingKeyGetter— restituito dal server — viene combinato con la stessa chiave interna, ricostruita sul client a partire da vari fattori di esecuzione, per decodificare il bytecode.
Poiché entrambi i lati combinano la chiave dell'utente con quella interna, il getter deve risolversi esattamente nella
stessa stringa passata come vmBytecodeArrayEncodingKey. Nessuno dei due elementi è sufficiente da solo: la chiave
dell'utente senza quella interna non consente di decodificare il bytecode e la chiave interna è inutile senza la prima. È
per questo motivo che fornire la propria chiave soltanto ai chiamanti autenticati rende inerte un bundle sottratto.
Lato server
L'endpoint decide quale chiave restituire in base a ciò che l'applicazione considera attendibile: una sessione valida, un
Origin o un Referer previsto, una verifica della licenza e così via. L'accorgimento consiste nel non rifiutare i
chiamanti non attendibili, restituendo invece una chiave errata. Il bytecode viene così decodificato in dati privi di
senso e il codice protetto smette di funzionare da sé: un approccio più discreto rispetto a un evidente 401, che
indicherebbe a un attaccante esattamente che cosa aggirare.
// Express example — the exact checks depend on your app
app.get('/api/vm-key', (req, res) => {
const origin = req.get('origin');
const trusted =
req.session?.user && // a valid session, and
origin === 'https://app.example.com'; // the expected production origin
res.type('text/plain').send(
// Real key for valid users; a decoy for everyone else
// (no session, or a localhost / unexpected origin).
trusted ? process.env.VM_KEY : process.env.VM_DECOY_KEY
);
});
