@@ -22,6 +22,20 @@ export interface PersistentData {
22
22
23
23
/**
24
24
* 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
+ *
25
39
*/
26
40
export interface EncryptedData {
27
41
/**
@@ -147,6 +161,47 @@ export function reEncryptData(
147
161
return emptySuccess ( ) ;
148
162
}
149
163
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
+
150
205
/**
151
206
* Manages encrypted persistence of data that is held by the instance and bundle managers.
152
207
*/
@@ -400,29 +455,8 @@ export class PersistenceManager {
400
455
try {
401
456
this . nodecg . log . info ( "Attempting to automatically login..." ) ;
402
457
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 ?? "" ) ;
426
460
const loadResult = await this . load ( encryptionKey ) ;
427
461
428
462
if ( ! loadResult . failed ) {
0 commit comments