cloudron 5.4.1 → 5.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/actions.js +1 -1
- package/src/backup-tools.js +52 -26
package/package.json
CHANGED
package/src/actions.js
CHANGED
|
@@ -346,7 +346,7 @@ async function authenticate(adminFqdn, username, password, options) {
|
|
|
346
346
|
|
|
347
347
|
const request = superagent.post(`https://${adminFqdn}/api/v1/auth/login`)
|
|
348
348
|
.timeout(60000)
|
|
349
|
-
.send({ username, password, totpToken })
|
|
349
|
+
.send({ username, password, totpToken, type: 'cid-cli' })
|
|
350
350
|
.ok(() => true)
|
|
351
351
|
.set('User-Agent', 'cloudron-cli');
|
|
352
352
|
if (!rejectUnauthorized) request.disableTLSCerts();
|
package/src/backup-tools.js
CHANGED
|
@@ -16,6 +16,7 @@ const assert = require('assert'),
|
|
|
16
16
|
debug = require('debug')('cloudron-backup'),
|
|
17
17
|
fs = require('fs'),
|
|
18
18
|
path = require('path'),
|
|
19
|
+
readlinePromises = require('readline/promises'),
|
|
19
20
|
safe = require('safetydance'),
|
|
20
21
|
TransformStream = require('stream').Transform;
|
|
21
22
|
|
|
@@ -25,7 +26,7 @@ function encryptFilePath(filePath, encryption) {
|
|
|
25
26
|
|
|
26
27
|
const encryptedParts = filePath.split('/').map(function (part) {
|
|
27
28
|
let hmac = crypto.createHmac('sha256', Buffer.from(encryption.filenameHmacKey, 'hex'));
|
|
28
|
-
const iv = hmac.update(part).digest().
|
|
29
|
+
const iv = hmac.update(part).digest().subarray(0, 16); // iv has to be deterministic, for our sync (copy) logic to work
|
|
29
30
|
const cipher = crypto.createCipheriv('aes-256-cbc', Buffer.from(encryption.filenameKey, 'hex'), iv);
|
|
30
31
|
let crypt = cipher.update(part);
|
|
31
32
|
crypt = Buffer.concat([ iv, crypt, cipher.final() ]);
|
|
@@ -49,22 +50,22 @@ function decryptFilePath(filePath, encryption) {
|
|
|
49
50
|
|
|
50
51
|
try {
|
|
51
52
|
const buffer = Buffer.from(part, 'base64');
|
|
52
|
-
const iv = buffer.
|
|
53
|
+
const iv = buffer.subarray(0, 16);
|
|
53
54
|
|
|
54
55
|
const decrypt = crypto.createDecipheriv('aes-256-cbc', Buffer.from(encryption.filenameKey, 'hex'), iv);
|
|
55
|
-
const plainText = decrypt.update(buffer.
|
|
56
|
+
const plainText = decrypt.update(buffer.subarray(16));
|
|
56
57
|
const plainTextString = Buffer.concat([ plainText, decrypt.final() ]).toString('utf8');
|
|
57
58
|
const hmac = crypto.createHmac('sha256', Buffer.from(encryption.filenameHmacKey, 'hex'));
|
|
58
|
-
if (!hmac.update(plainTextString).digest().
|
|
59
|
+
if (!hmac.update(plainTextString).digest().subarray(0, 16).equals(iv)) return { error: new Error(`mac error decrypting part ${part} of path ${filePath}`) };
|
|
59
60
|
|
|
60
61
|
decryptedParts.push(plainTextString);
|
|
61
62
|
} catch (error) {
|
|
62
63
|
debug(`Error decrypting file ${filePath} part ${part}:`, error);
|
|
63
|
-
return
|
|
64
|
+
return { error };
|
|
64
65
|
}
|
|
65
66
|
}
|
|
66
67
|
|
|
67
|
-
return { decryptedFilePath: decryptedParts.join('/') };
|
|
68
|
+
return { error: null, decryptedFilePath: decryptedParts.join('/') };
|
|
68
69
|
}
|
|
69
70
|
|
|
70
71
|
class EncryptStream extends TransformStream {
|
|
@@ -131,27 +132,27 @@ class DecryptStream extends TransformStream {
|
|
|
131
132
|
|
|
132
133
|
debug('got chunk', chunk.length);
|
|
133
134
|
if (this._header.length !== 20) { // not gotten IV yet
|
|
134
|
-
this._header = Buffer.concat([this._header, chunk.
|
|
135
|
+
this._header = Buffer.concat([this._header, chunk.subarray(0, needed)]);
|
|
135
136
|
if (this._header.length !== 20) return callback();
|
|
136
137
|
|
|
137
|
-
if (!this._header.
|
|
138
|
+
if (!this._header.subarray(0, 4).equals(new Buffer.from('CBV2'))) return callback(new Error('Invalid magic in header'));
|
|
138
139
|
|
|
139
|
-
const iv = this._header.
|
|
140
|
+
const iv = this._header.subarray(4);
|
|
140
141
|
this._decipher = crypto.createDecipheriv('aes-256-cbc', this._key, iv);
|
|
141
142
|
this._hmac.update(this._header);
|
|
142
143
|
}
|
|
143
144
|
|
|
144
145
|
debug('needed is', needed);
|
|
145
|
-
this._buffer = Buffer.concat([ this._buffer, chunk.
|
|
146
|
+
this._buffer = Buffer.concat([ this._buffer, chunk.subarray(needed) ]);
|
|
146
147
|
debug('buffer is ', this._buffer.length);
|
|
147
148
|
if (this._buffer.length < 32) return callback();
|
|
148
149
|
|
|
149
150
|
try {
|
|
150
|
-
const cipherText = this._buffer.
|
|
151
|
+
const cipherText = this._buffer.subarray(0, -32);
|
|
151
152
|
debug('Got:', cipherText.toString('hex'));
|
|
152
153
|
this._hmac.update(cipherText);
|
|
153
154
|
const plainText = this._decipher.update(cipherText);
|
|
154
|
-
this._buffer = this._buffer.
|
|
155
|
+
this._buffer = this._buffer.subarray(-32);
|
|
155
156
|
callback(null, plainText);
|
|
156
157
|
} catch (error) {
|
|
157
158
|
callback(error);
|
|
@@ -185,15 +186,20 @@ function exit(msgOrError) {
|
|
|
185
186
|
function aesKeysFromPassword(password) {
|
|
186
187
|
const derived = crypto.scryptSync(password, Buffer.from('CLOUDRONSCRYPTSALT', 'utf8'), 128);
|
|
187
188
|
return {
|
|
188
|
-
dataKey: derived.
|
|
189
|
-
dataHmacKey: derived.
|
|
190
|
-
filenameKey: derived.
|
|
191
|
-
filenameHmacKey: derived.
|
|
189
|
+
dataKey: derived.subarray(0, 32).toString('hex'),
|
|
190
|
+
dataHmacKey: derived.subarray(32, 64).toString('hex'),
|
|
191
|
+
filenameKey: derived.subarray(64, 96).toString('hex'),
|
|
192
|
+
filenameHmacKey: derived.subarray(96).toString('hex')
|
|
192
193
|
};
|
|
193
194
|
}
|
|
194
195
|
|
|
195
|
-
function encrypt(input, options) {
|
|
196
|
-
if (!options.password)
|
|
196
|
+
async function encrypt(input, options) {
|
|
197
|
+
if (!options.password) {
|
|
198
|
+
const rl = readlinePromises.createInterface({ input: process.stdin, output: process.stdout });
|
|
199
|
+
options.password = rl.question('Enter encryption password: ');
|
|
200
|
+
rl.close();
|
|
201
|
+
if (!options.password) return exit('--password is needed');
|
|
202
|
+
}
|
|
197
203
|
|
|
198
204
|
const encryption = aesKeysFromPassword(options.password);
|
|
199
205
|
|
|
@@ -207,16 +213,26 @@ function encrypt(input, options) {
|
|
|
207
213
|
inStream.pipe(encryptStream).pipe(outStream);
|
|
208
214
|
}
|
|
209
215
|
|
|
210
|
-
function encryptFilename(filePath, options) {
|
|
211
|
-
if (!options.password)
|
|
216
|
+
async function encryptFilename(filePath, options) {
|
|
217
|
+
if (!options.password) {
|
|
218
|
+
const rl = readlinePromises.createInterface({ input: process.stdin, output: process.stdout });
|
|
219
|
+
options.password = await rl.question('Enter encryption password: ');
|
|
220
|
+
rl.close();
|
|
221
|
+
if (!options.password) return exit('--password is needed');
|
|
222
|
+
}
|
|
212
223
|
|
|
213
224
|
const encryption = aesKeysFromPassword(options.password);
|
|
214
225
|
|
|
215
226
|
console.log(encryptFilePath(filePath, encryption));
|
|
216
227
|
}
|
|
217
228
|
|
|
218
|
-
function decrypt(input, options) {
|
|
219
|
-
if (!options.password)
|
|
229
|
+
async function decrypt(input, options) {
|
|
230
|
+
if (!options.password) {
|
|
231
|
+
const rl = readlinePromises.createInterface({ input: process.stdin, output: process.stdout });
|
|
232
|
+
options.password = await rl.question('Enter encryption password: ');
|
|
233
|
+
rl.close();
|
|
234
|
+
if (!options.password) return exit('--password is needed');
|
|
235
|
+
}
|
|
220
236
|
|
|
221
237
|
const fd = safe.fs.openSync(input, 'r');
|
|
222
238
|
if (!fd) return exit(safe.error);
|
|
@@ -237,8 +253,13 @@ function decrypt(input, options) {
|
|
|
237
253
|
inStream.pipe(decryptStream).pipe(outStream);
|
|
238
254
|
}
|
|
239
255
|
|
|
240
|
-
function decryptDir(inDir, outDir, options) {
|
|
241
|
-
if (!options.password)
|
|
256
|
+
async function decryptDir(inDir, outDir, options) {
|
|
257
|
+
if (!options.password) {
|
|
258
|
+
const rl = readlinePromises.createInterface({ input: process.stdin, output: process.stdout });
|
|
259
|
+
options.password = await rl.question('Enter encryption password: ');
|
|
260
|
+
rl.close();
|
|
261
|
+
if (!options.password) return exit('--password is needed');
|
|
262
|
+
}
|
|
242
263
|
|
|
243
264
|
const encryption = aesKeysFromPassword(options.password);
|
|
244
265
|
|
|
@@ -274,8 +295,13 @@ function decryptDir(inDir, outDir, options) {
|
|
|
274
295
|
}, exit);
|
|
275
296
|
}
|
|
276
297
|
|
|
277
|
-
function decryptFilename(filePath, options) {
|
|
278
|
-
if (!options.password)
|
|
298
|
+
async function decryptFilename(filePath, options) {
|
|
299
|
+
if (!options.password) {
|
|
300
|
+
const rl = readlinePromises.createInterface({ input: process.stdin, output: process.stdout });
|
|
301
|
+
options.password = await rl.question('Enter encryption password: ');
|
|
302
|
+
rl.close();
|
|
303
|
+
if (!options.password) return exit('--password is needed');
|
|
304
|
+
}
|
|
279
305
|
|
|
280
306
|
const encryption = aesKeysFromPassword(options.password);
|
|
281
307
|
|