diff --git a/src/backups.js b/src/backups.js index 0e8eaddfe..e4964b69e 100644 --- a/src/backups.js +++ b/src/backups.js @@ -260,20 +260,23 @@ function decryptFilePath(filePath, backupConfig) { } class EncryptStream extends TransformStream { - constructor(key) { + constructor(encryption) { super(); this._ivPushed = false; this._iv = crypto.randomBytes(16); - this._cipher = crypto.createCipheriv('aes-256-cbc', key, this._iv); + this._cipher = crypto.createCipheriv('aes-256-cbc', Buffer.from(encryption.dataKey, 'hex'), this._iv); + this._hmac = crypto.createHmac('sha256', Buffer.from(encryption.dataHmacKey, 'hex')); } _transform(chunk, ignoredEncoding, callback) { if (!this._ivPushed) { this.push(this._iv); + this._hmac.update(this._iv); this._ivPushed = true; } try { const crypt = this._cipher.update(chunk); + this._hmac.update(crypt); callback(null, crypt); } catch (error) { callback(error); @@ -283,7 +286,9 @@ class EncryptStream extends TransformStream { _flush(callback) { try { const crypt = this._cipher.final(); - callback(null, crypt); + this.push(crypt); + this._hmac.update(crypt); + callback(null, this._hmac.digest()); } catch (error) { callback(error); } @@ -291,25 +296,34 @@ class EncryptStream extends TransformStream { } class DecryptStream extends TransformStream { - constructor(key) { + constructor(encryption) { super(); - this._key = key; + this._key = Buffer.from(encryption.dataKey, 'hex'); this._iv = Buffer.alloc(0); this._decipher = null; + this._hmac = crypto.createHmac('sha256', Buffer.from(encryption.dataHmacKey, 'hex')); + this._buffer = Buffer.alloc(0); } _transform(chunk, ignoredEncoding, callback) { + const needed = 16 - this._iv.length; + if (this._iv.length !== 16) { // not gotten IV yet - const needed = 16 - this._iv.length; this._iv = Buffer.concat([this._iv, chunk.slice(0, needed)]); if (this._iv.length !== 16) return callback(); this._decipher = crypto.createDecipheriv('aes-256-cbc', this._key, this._iv); - chunk = chunk.slice(needed); + this._hmac.update(this._iv); } + this._buffer = Buffer.concat([ this._buffer, chunk.slice(needed) ]); + if (this._buffer.length < 32) return callback(); + try { - const plainText = this._decipher.update(chunk); + const cipherText = this._buffer.slice(0, -32); + this._hmac.update(cipherText); + const plainText = this._decipher.update(cipherText); + this._buffer = this._buffer.slice(-32); callback(null, plainText); } catch (error) { callback(error); @@ -317,7 +331,11 @@ class DecryptStream extends TransformStream { } _flush (callback) { + if (this._buffer.length !== 32) return callback(new BoxError(BoxError.CRYPTO_ERROR, 'Invalid password or tampered file (not enough data)')); + try { + if (!this._hmac.digest().equals(this._buffer)) return callback(new BoxError(BoxError.CRYPTO_ERROR, 'Invalid password or tampered file (mac mismatch)')); + const plainText = this._decipher.final(); callback(null, plainText); } catch (error) { @@ -339,7 +357,7 @@ function createReadStream(sourceFile, backupConfig) { }); if (backupConfig.encryption) { - let encryptStream = new EncryptStream(Buffer.from(backupConfig.encryption.dataKey, 'hex')); + let encryptStream = new EncryptStream(backupConfig.encryption); encryptStream.on('error', function (error) { debug('createReadStream: encrypt stream error.', error); @@ -365,7 +383,7 @@ function createWriteStream(destFile, backupConfig) { }); if (backupConfig.encryption) { - let decrypt = new DecryptStream(Buffer.from(backupConfig.encryption.dataKey, 'hex')); + let decrypt = new DecryptStream(backupConfig.encryption); decrypt.on('error', function (error) { debug('createWriteStream: decrypt stream error.', error); ps.emit('error', new BoxError(BoxError.CRYPTO_ERROR, error.message)); @@ -415,7 +433,7 @@ function tarPack(dataLayout, backupConfig, callback) { }); if (backupConfig.encryption) { - const encryptStream = new EncryptStream(Buffer.from(backupConfig.encryption.dataKey, 'hex')); + const encryptStream = new EncryptStream(backupConfig.encryption); encryptStream.on('error', function (error) { debug('tarPack: encrypt stream error.', error); ps.emit('error', new BoxError(BoxError.EXTERNAL_ERROR, error.message)); @@ -626,7 +644,7 @@ function tarExtract(inStream, dataLayout, backupConfig, callback) { }); if (backupConfig.encryption) { - let decrypt = new DecryptStream(Buffer.from(backupConfig.encryption.dataKey, 'hex')); + let decrypt = new DecryptStream(backupConfig.encryption); decrypt.on('error', function (error) { debug('tarExtract: decrypt stream error.', error); emitError(new BoxError(BoxError.EXTERNAL_ERROR, `Failed to decrypt: ${error.message}`));