Skip to content
This repository was archived by the owner on Apr 13, 2025. It is now read-only.

Commit f3ee487

Browse files
committed
Remove salt generation and config migration redundancy and add config version explainations
1 parent b504cc5 commit f3ee487

File tree

2 files changed

+60
-46
lines changed

2 files changed

+60
-46
lines changed

nodecg-io-core/dashboard/crypto.ts

Lines changed: 3 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
decryptData,
55
deriveEncryptionKey,
66
reEncryptData,
7+
ensureEncryptionSaltIsSet,
78
} from "nodecg-io-core/extension/persistenceManager";
89
import { EventEmitter } from "events";
910
import { ObjectMap, ServiceInstance, ServiceDependency, Service } from "nodecg-io-core/extension/service";
@@ -72,29 +73,8 @@ export async function setPassword(pw: string): Promise<boolean> {
7273
encryptedData.value = {};
7374
}
7475

75-
const salt = encryptedData.value.salt ?? cryptoJS.lib.WordArray.random(128 / 8).toString();
76-
// Check if no salt is present, which is the case for the nodecg-io <=0.2 configs
77-
// where crypto-js derived the encryption key and managed the salt.
78-
if (encryptedData.value.salt === undefined) {
79-
// Salt is unset when nodecg-io is first started.
80-
81-
if (encryptedData.value.cipherText !== undefined) {
82-
// Salt is unset but we have some encrypted data.
83-
// This means that this is a old config, that we need to migrate to the new format.
84-
85-
// Re-encrypt the configuration using our own derived key instead of the password.
86-
const newEncryptionKey = deriveEncryptionKey(pw, salt);
87-
const newEncryptionKeyArr = cryptoJS.enc.Hex.parse(newEncryptionKey);
88-
const res = reEncryptData(encryptedData.value, pw, newEncryptionKeyArr);
89-
if (res.failed) {
90-
throw new Error(`Failed to migrate config: ${res.errorMessage}`);
91-
}
92-
}
93-
94-
encryptedData.value.salt = salt;
95-
}
96-
97-
encryptionKey = deriveEncryptionKey(pw, salt);
76+
ensureEncryptionSaltIsSet(encryptedData.value, pw);
77+
encryptionKey = deriveEncryptionKey(pw, encryptedData.value.salt ?? "");
9878

9979
// Load framework, returns false if not already loaded and password/encryption key is wrong
10080
if ((await loadFramework()) === false) return false;

nodecg-io-core/extension/persistenceManager.ts

Lines changed: 57 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,20 @@ export interface PersistentData {
2222

2323
/**
2424
* Models all the data that needs to be persistent in an encrypted manner.
25+
*
26+
* For nodecg-io <= 0.2 configurations only the ciphertext value may be set
27+
* containing the encrypted data, iv and salt in the crypto.js format.
28+
* Salt and iv are managed by crypto.js and all AES defaults with a password are used (PBKDF1 using 1 MD5 iteration).
29+
* All this happens in the nodecg-io-core extension and the password is sent using NodeCG Messages.
30+
*
31+
* For nodecg-io >= 0.3 this was changed. PBKDF2 using SHA256 is directly run inside the browser when logging in.
32+
* Only the derived AES encryption key is sent to the extension using NodeCG messages.
33+
* That way analyzed network traffic and malicious bundles that listen for the same NodeCG message only allow getting
34+
* the encryption key and not the plain text password that may be used somewhere else.
35+
*
36+
* Still with this security upgrade you should only use trusted bundles with your NodeCG installation
37+
* and use https if your using the dashboard over a untrusted network.
38+
*
2539
*/
2640
export interface EncryptedData {
2741
/**
@@ -147,6 +161,47 @@ export function reEncryptData(
147161
return emptySuccess();
148162
}
149163

164+
/**
165+
* Ensures that the passed encrypted data has the salt attribute set.
166+
* The salt attribute is not set when either this is the first start of nodecg-io
167+
* or if this is a old config from nodecg-io <= 0.2.
168+
*
169+
* If this is a new configuration a new salt will be generated and set inside the EncryptedData object.
170+
* If this is a old configuration from nodecg-io <= 0.2 it will be migrated to the new format as well.
171+
*
172+
* @param data the encrypted data where the salt should be ensured to be available
173+
* @param password the password of the encrypted data. Used if this config needs to be migrated
174+
*/
175+
export function ensureEncryptionSaltIsSet(data: EncryptedData, password: string): void {
176+
if (data.salt !== undefined) {
177+
// We already have a salt, so we have the new (nodecg-io >=0.3) format too.
178+
// We don't need to do anything then.
179+
return;
180+
}
181+
182+
// No salt is present, which is the case for the nodecg-io <=0.2 configs
183+
// where crypto-js derived the encryption key and managed the salt
184+
// or when nodecg-io is first started.
185+
186+
// Generate a random salt.
187+
const salt = crypto.lib.WordArray.random(128 / 8).toString();
188+
189+
if (data.cipherText !== undefined) {
190+
// Salt is unset but we have some encrypted data.
191+
// This means that this is a old config (nodecg-io <=0.2), that we need to migrate to the new format.
192+
193+
// Re-encrypt the configuration using our own derived key instead of the password.
194+
const newEncryptionKey = deriveEncryptionKey(password, salt);
195+
const newEncryptionKeyArr = crypto.enc.Hex.parse(newEncryptionKey);
196+
const res = reEncryptData(data, password, newEncryptionKeyArr);
197+
if (res.failed) {
198+
throw new Error(`Failed to migrate config: ${res.errorMessage}`);
199+
}
200+
}
201+
202+
data.salt = salt;
203+
}
204+
150205
/**
151206
* Manages encrypted persistence of data that is held by the instance and bundle managers.
152207
*/
@@ -400,29 +455,8 @@ export class PersistenceManager {
400455
try {
401456
this.nodecg.log.info("Attempting to automatically login...");
402457

403-
const salt = this.encryptedData.value.salt ?? crypto.lib.WordArray.random(128 / 8).toString();
404-
// Check if no salt is present, which is the case for the nodecg-io <=0.2 configs
405-
// where crypto-js derived the encryption key and managed the salt.
406-
if (this.encryptedData.value.salt === undefined) {
407-
// Salt is unset when nodecg-io is first started.
408-
409-
if (this.encryptedData.value.cipherText !== undefined) {
410-
// Salt is unset but we have some encrypted data.
411-
// This means that this is a old config, that we need to migrate to the new format.
412-
413-
// Re-encrypt the configuration using our own derived key instead of the password.
414-
const newEncryptionKey = deriveEncryptionKey(password, salt);
415-
const newEncryptionKeyArr = crypto.enc.Hex.parse(newEncryptionKey);
416-
const res = reEncryptData(this.encryptedData.value, password, newEncryptionKeyArr);
417-
if (res.failed) {
418-
throw new Error(`Failed to migrate config: ${res.errorMessage}`);
419-
}
420-
}
421-
422-
this.encryptedData.value.salt = salt;
423-
}
424-
425-
const encryptionKey = deriveEncryptionKey(password, salt);
458+
ensureEncryptionSaltIsSet(this.encryptedData.value, password);
459+
const encryptionKey = deriveEncryptionKey(password, this.encryptedData.value.salt ?? "");
426460
const loadResult = await this.load(encryptionKey);
427461

428462
if (!loadResult.failed) {

0 commit comments

Comments
 (0)