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

Commit cef8c1f

Browse files
committed
Rename password to encryptionKey when derived key is used
1 parent bc73fc7 commit cef8c1f

File tree

3 files changed

+74
-69
lines changed

3 files changed

+74
-69
lines changed

nodecg-io-core/dashboard/crypto.ts

Lines changed: 29 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,18 @@ import {
22
PersistentData,
33
EncryptedData,
44
decryptData,
5-
deriveEncryptionSecret,
5+
deriveEncryptionKey,
66
reEncryptData,
77
} from "nodecg-io-core/extension/persistenceManager";
88
import { EventEmitter } from "events";
99
import { ObjectMap, ServiceInstance, ServiceDependency, Service } from "nodecg-io-core/extension/service";
1010
import { isLoaded } from "./authentication";
11-
import { PasswordMessage } from "nodecg-io-core/extension/messageManager";
11+
import { AuthenticationMessage } from "nodecg-io-core/extension/messageManager";
1212
import cryptoJS from "crypto-js";
1313

1414
const encryptedData = nodecg.Replicant<EncryptedData>("encryptedConfig");
1515
let services: Service<unknown, never>[] | undefined;
16-
let password: string | undefined;
16+
let encryptionKey: string | undefined;
1717

1818
/**
1919
* Layer between the actual dashboard and `PersistentData`.
@@ -47,20 +47,21 @@ class Config extends EventEmitter {
4747
}
4848
export const config = new Config();
4949

50-
// Update the decrypted copy of the data once the encrypted version changes (if a password is available).
50+
// Update the decrypted copy of the data once the encrypted version changes (if a encryption key is available).
5151
// This ensures that the decrypted data is always up-to-date.
5252
encryptedData.on("change", updateDecryptedData);
5353

5454
/**
5555
* Sets the passed password to be used by the crypto module.
56-
* Will try to decrypt encrypted data to tell whether the password is correct,
57-
* if it is wrong the internal password will be set to undefined.
56+
* Uses the password to derive a decryption secret and then tries to decrypt
57+
* the encrypted data to tell whether the password is correct.
58+
* If it is wrong the internal encryption key will be set to undefined.
5859
* Returns whether the password is correct.
5960
* @param pw the password which should be set.
6061
*/
6162
export async function setPassword(pw: string): Promise<boolean> {
6263
await Promise.all([
63-
// Ensures that the `encryptedData` has been declared because it is needed by `setPassword()`
64+
// Ensures that the `encryptedData` has been declared because it is needed to get the encrypted config.
6465
// This is especially needed when handling a re-connect as the replicant takes time to declare
6566
// and the password check is usually faster than that.
6667
NodeCG.waitForReplicants(encryptedData),
@@ -73,7 +74,7 @@ export async function setPassword(pw: string): Promise<boolean> {
7374

7475
const salt = encryptedData.value.salt ?? cryptoJS.lib.WordArray.random(128 / 8).toString(cryptoJS.enc.Hex);
7576
if (encryptedData.value.salt === undefined) {
76-
const newSecret = deriveEncryptionSecret(pw, salt);
77+
const newSecret = deriveEncryptionKey(pw, salt);
7778

7879
if (encryptedData.value.cipherText !== undefined) {
7980
const newSecretWordArray = cryptoJS.enc.Hex.parse(newSecret);
@@ -83,52 +84,55 @@ export async function setPassword(pw: string): Promise<boolean> {
8384
encryptedData.value.salt = salt;
8485
}
8586

86-
password = deriveEncryptionSecret(pw, salt);
87+
encryptionKey = deriveEncryptionKey(pw, salt);
8788

88-
// Load framework, returns false if not already loaded and password is wrong
89+
// Load framework, returns false if not already loaded and password/encryption key is wrong
8990
if ((await loadFramework()) === false) return false;
9091

9192
if (encryptedData.value) {
9293
updateDecryptedData(encryptedData.value);
93-
// Password is unset by `updateDecryptedData` if it is wrong.
94-
// This may happen if the framework was already loaded and `loadFramework` didn't check the password.
95-
if (password === undefined) {
94+
// encryption key is unset by `updateDecryptedData` if it is wrong.
95+
// This may happen if the framework was already loaded and `loadFramework` didn't check the password/encryption key.
96+
if (encryptionKey === undefined) {
9697
return false;
9798
}
9899
}
99100

100101
return true;
101102
}
102103

103-
export async function sendAuthenticatedMessage<V>(messageName: string, message: Partial<PasswordMessage>): Promise<V> {
104-
if (password === undefined) throw "No password available";
104+
export async function sendAuthenticatedMessage<V>(
105+
messageName: string,
106+
message: Partial<AuthenticationMessage>,
107+
): Promise<V> {
108+
if (encryptionKey === undefined) throw "Can't send authenticated message: crypto module not authenticated";
105109
const msgWithAuth = Object.assign({}, message);
106-
msgWithAuth.password = password;
110+
msgWithAuth.encryptionKey = encryptionKey;
107111
return await nodecg.sendMessage(messageName, msgWithAuth);
108112
}
109113

110114
/**
111-
* Returns whether a password has been set in the crypto module aka. whether it is authenticated.
115+
* Returns whether a password derived encryption key has been set in the crypto module aka. whether it is authenticated.
112116
*/
113117
export function isPasswordSet(): boolean {
114-
return password !== undefined;
118+
return encryptionKey !== undefined;
115119
}
116120

117121
/**
118-
* Decrypts the passed data using the global password variable and saves it into `ConfigData`.
119-
* Unsets the password if its wrong and also forwards `undefined` to `ConfigData` if the password is unset.
122+
* Decrypts the passed data using the global encryptionKey variable and saves it into `ConfigData`.
123+
* Unsets the encryption key if its wrong and also forwards `undefined` to `ConfigData` if the encryption key is unset.
120124
* @param data the data that should be decrypted.
121125
*/
122126
function updateDecryptedData(data: EncryptedData): void {
123127
let result: PersistentData | undefined = undefined;
124-
if (password !== undefined && data.cipherText) {
125-
const passwordWordArray = cryptoJS.enc.Hex.parse(password);
128+
if (encryptionKey !== undefined && data.cipherText) {
129+
const passwordWordArray = cryptoJS.enc.Hex.parse(encryptionKey);
126130
const res = decryptData(data.cipherText, passwordWordArray, data.iv);
127131
if (!res.failed) {
128132
result = res.result;
129133
} else {
130-
// Password is wrong
131-
password = undefined;
134+
// Secret is wrong
135+
encryptionKey = undefined;
132136
}
133137
}
134138

@@ -159,7 +163,7 @@ async function loadFramework(): Promise<boolean> {
159163
if (await isLoaded()) return true;
160164

161165
try {
162-
await nodecg.sendMessage("load", { password });
166+
await nodecg.sendMessage("load", { encryptionKey });
163167
return true;
164168
} catch {
165169
return false;

nodecg-io-core/extension/messageManager.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,25 @@ import { BundleManager } from "./bundleManager";
55
import { PersistenceManager } from "./persistenceManager";
66
import { ServiceManager } from "./serviceManager";
77

8-
export interface PasswordMessage {
9-
password: string;
8+
export interface AuthenticationMessage {
9+
encryptionKey: string;
1010
}
1111

12-
export interface UpdateInstanceConfigMessage extends PasswordMessage {
12+
export interface UpdateInstanceConfigMessage extends AuthenticationMessage {
1313
instanceName: string;
1414
config: unknown;
1515
}
1616

17-
export interface CreateServiceInstanceMessage extends PasswordMessage {
17+
export interface CreateServiceInstanceMessage extends AuthenticationMessage {
1818
serviceType: string;
1919
instanceName: string;
2020
}
2121

22-
export interface DeleteServiceInstanceMessage extends PasswordMessage {
22+
export interface DeleteServiceInstanceMessage extends AuthenticationMessage {
2323
instanceName: string;
2424
}
2525

26-
export interface SetServiceDependencyMessage extends PasswordMessage {
26+
export interface SetServiceDependencyMessage extends AuthenticationMessage {
2727
bundleName: string;
2828
instanceName: string | undefined;
2929
serviceType: string;
@@ -82,8 +82,8 @@ export class MessageManager {
8282
return success(this.persist.isLoaded());
8383
});
8484

85-
this.listen("load", async (msg: PasswordMessage) => {
86-
return this.persist.load(msg.password);
85+
this.listen("load", async (msg: AuthenticationMessage) => {
86+
return this.persist.load(msg.encryptionKey);
8787
});
8888

8989
this.listen("getServices", async () => {
@@ -113,12 +113,12 @@ export class MessageManager {
113113
});
114114
}
115115

116-
private listenWithAuth<M extends PasswordMessage, V>(
116+
private listenWithAuth<M extends AuthenticationMessage, V>(
117117
messageName: string,
118118
cb: (msg: M) => Promise<Result<V>>,
119119
): void {
120120
this.listen(messageName, async (msg: M) => {
121-
if (this.persist.checkPassword(msg.password)) {
121+
if (this.persist.checkEncryptionKey(msg.encryptionKey)) {
122122
return cb(msg);
123123
} else {
124124
return error("The password is invalid");

nodecg-io-core/extension/persistenceManager.ts

Lines changed: 35 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -33,20 +33,21 @@ export interface EncryptedData {
3333
}
3434

3535
/**
36-
* Decrypts the passed encrypted data using the passed password.
37-
* If the password is wrong, an error will be returned.
36+
* Decrypts the passed encrypted data using the passed encryption key.
37+
* If the encryption key is wrong, an error will be returned.
3838
*
3939
* @param cipherText the ciphertext that needs to be decrypted.
40-
* @param password the password for the encrypted data.
40+
* @param encryptionKey the encryption key for the encrypted data.
41+
* @param iv the initialization vector for the encrypted data.
4142
*/
4243
export function decryptData(
4344
cipherText: string,
44-
password: string | crypto.lib.WordArray,
45+
encryptionKey: string | crypto.lib.WordArray,
4546
iv: string | undefined,
4647
): Result<PersistentData> {
4748
try {
4849
const ivWordArray = iv ? crypto.enc.Hex.parse(iv) : undefined;
49-
const decryptedBytes = crypto.AES.decrypt(cipherText, password, { iv: ivWordArray });
50+
const decryptedBytes = crypto.AES.decrypt(cipherText, encryptionKey, { iv: ivWordArray });
5051
const decryptedText = decryptedBytes.toString(crypto.enc.Utf8);
5152
const data: PersistentData = JSON.parse(decryptedText);
5253
return success(data);
@@ -55,14 +56,14 @@ export function decryptData(
5556
}
5657
}
5758

58-
export function encryptData(data: PersistentData, password: string | crypto.lib.WordArray): [string, string] {
59+
export function encryptData(data: PersistentData, encryptionKey: crypto.lib.WordArray): [string, string] {
5960
const iv = crypto.lib.WordArray.random(16);
6061
const ivText = iv.toString();
61-
const encrypted = crypto.AES.encrypt(JSON.stringify(data), password, { iv });
62+
const encrypted = crypto.AES.encrypt(JSON.stringify(data), encryptionKey, { iv });
6263
return [encrypted.toString(), ivText];
6364
}
6465

65-
export function deriveEncryptionSecret(password: string, salt: string | undefined): string {
66+
export function deriveEncryptionKey(password: string, salt: string | undefined): string {
6667
if (salt === undefined) {
6768
return password;
6869
}
@@ -80,7 +81,7 @@ export function deriveEncryptionSecret(password: string, salt: string | undefine
8081
export function reEncryptData(
8182
data: EncryptedData,
8283
oldSecret: string | crypto.lib.WordArray,
83-
newSecret: string | crypto.lib.WordArray,
84+
newSecret: crypto.lib.WordArray,
8485
): Result<void> {
8586
if (data.cipherText === undefined) {
8687
return error("Cannot re-encrypt empty cipher text.");
@@ -101,7 +102,7 @@ export function reEncryptData(
101102
* Manages encrypted persistence of data that is held by the instance and bundle managers.
102103
*/
103104
export class PersistenceManager {
104-
private password: string | undefined;
105+
private encryptionKey: string | undefined;
105106
// We store the encrypted data in a replicant, because writing files in a NodeCG bundle isn't very clean
106107
// and the bundle config is read-only. It is only in encrypted form, so it is OK to be accessible in the browser.
107108
private encryptedData: ReplicantServer<EncryptedData>;
@@ -120,38 +121,38 @@ export class PersistenceManager {
120121
}
121122

122123
/**
123-
* Checks whether the passed password is correct. Only works if already loaded and a password is already set.
124-
* @param password the password which should be checked for correctness
124+
* Checks whether the passed encryption key is correct. Only works if already loaded and a encryption key is already set.
125+
* @param encryptionKey the encryption key which should be checked for correctness
125126
*/
126-
checkPassword(password: string): boolean {
127+
checkEncryptionKey(encryptionKey: string): boolean {
127128
if (this.isLoaded()) {
128-
return this.password === password;
129+
return this.encryptionKey === encryptionKey;
129130
} else {
130131
return false;
131132
}
132133
}
133134

134135
/**
135-
* Returns if the locally stored configuration has been loaded and a password has been set.
136+
* Returns if the locally stored configuration has been loaded and a encryption key has been set.
136137
*/
137138
isLoaded(): boolean {
138-
return this.password !== undefined;
139+
return this.encryptionKey !== undefined;
139140
}
140141

141142
/**
142143
* Returns whether this is the first startup aka. whether any encrypted data has been saved.
143-
* If this returns true {{@link load}} will accept any password and use it to encrypt the configuration.
144+
* If this returns true {@link load} will accept any encryption key and use it to encrypt the configuration.
144145
*/
145146
isFirstStartup(): boolean {
146147
return this.encryptedData.value.cipherText === undefined;
147148
}
148149

149150
/**
150-
* Decrypts and loads the locally stored configuration using the passed password.
151-
* @param password the password of the encrypted config.
152-
* @return success if the password was correct and loading has been successful and an error if the password is wrong.
151+
* Decrypts and loads the locally stored configuration using the passed encryption key.
152+
* @param encryptionKey the encryption key of the encrypted config.
153+
* @return success if the encryption key was correct and loading has been successful and an error if the encryption key is wrong.
153154
*/
154-
async load(password: string): Promise<Result<void>> {
155+
async load(encryptionKey: string): Promise<Result<void>> {
155156
if (this.isLoaded()) {
156157
return error("Config has already been decrypted and loaded.");
157158
}
@@ -160,19 +161,19 @@ export class PersistenceManager {
160161
// No encrypted data has been saved, probably because this is the first startup.
161162
// Therefore nothing needs to be decrypted, and we write an empty config to disk.
162163
this.nodecg.log.info("No saved configuration found, creating a empty one.");
163-
this.password = password;
164+
this.encryptionKey = encryptionKey;
164165
this.save();
165166
} else {
166167
// Decrypt config
167168
this.nodecg.log.info("Decrypting and loading saved configuration.");
168-
const passwordWordArray = crypto.enc.Hex.parse(password);
169+
const encryptionKeyArr = crypto.enc.Hex.parse(encryptionKey);
169170
const data = decryptData(
170171
this.encryptedData.value.cipherText,
171-
passwordWordArray,
172+
encryptionKeyArr,
172173
this.encryptedData.value.iv,
173174
);
174175
if (data.failed) {
175-
this.nodecg.log.error("Could not decrypt configuration: password is invalid.");
176+
this.nodecg.log.error("Could not decrypt configuration: encryption key is invalid.");
176177
return data;
177178
}
178179

@@ -183,8 +184,8 @@ export class PersistenceManager {
183184
this.saveAfterServiceInstancesLoaded(promises);
184185
}
185186

186-
// Save password, used in save() function
187-
this.password = password;
187+
// Save encryption key, used in save() function
188+
this.encryptionKey = encryptionKey;
188189

189190
// Register handlers to save when something changes
190191
this.instances.on("change", () => this.save());
@@ -258,8 +259,8 @@ export class PersistenceManager {
258259
* Encrypts and saves current state to the persistent replicant.
259260
*/
260261
save(): void {
261-
// Check if we have a password to encrypt the data with.
262-
if (this.password === undefined) {
262+
// Check if we have a encryption key to encrypt the data with.
263+
if (this.encryptionKey === undefined) {
263264
return;
264265
}
265266

@@ -270,8 +271,8 @@ export class PersistenceManager {
270271
};
271272

272273
// Encrypt and save data to persistent replicant.
273-
const passwordWordArray = crypto.enc.Hex.parse(this.password);
274-
const [cipherText, iv] = encryptData(data, passwordWordArray);
274+
const encryptionKeyArr = crypto.enc.Hex.parse(this.encryptionKey);
275+
const [cipherText, iv] = encryptData(data, encryptionKeyArr);
275276
this.encryptedData.value.cipherText = cipherText;
276277
this.encryptedData.value.iv = iv;
277278
}
@@ -353,7 +354,7 @@ export class PersistenceManager {
353354
const salt =
354355
this.encryptedData.value.salt ?? crypto.lib.WordArray.random(128 / 8).toString(crypto.enc.Hex);
355356
if (this.encryptedData.value.salt === undefined) {
356-
const newSecret = deriveEncryptionSecret(password, salt);
357+
const newSecret = deriveEncryptionKey(password, salt);
357358

358359
if (this.encryptedData.value.cipherText !== undefined) {
359360
const newSecretWordArray = crypto.enc.Hex.parse(newSecret);
@@ -363,8 +364,8 @@ export class PersistenceManager {
363364
this.encryptedData.value.salt = salt;
364365
}
365366

366-
const encryptionSecret = deriveEncryptionSecret(password, salt);
367-
const loadResult = await this.load(encryptionSecret);
367+
const encryptionKey = deriveEncryptionKey(password, salt);
368+
const loadResult = await this.load(encryptionKey);
368369

369370
if (!loadResult.failed) {
370371
this.nodecg.log.info("Automatic login successful.");

0 commit comments

Comments
 (0)