const { BaseCommand } = require('./base-command.js'); const { _ } = require('lib/locale.js'); const { cliUtils } = require('./cli-utils.js'); const EncryptionService = require('lib/services/EncryptionService'); const DecryptionWorker = require('lib/services/DecryptionWorker'); const MasterKey = require('lib/models/MasterKey'); const BaseItem = require('lib/models/BaseItem'); const Setting = require('lib/models/Setting.js'); class Command extends BaseCommand { usage() { return 'e2ee [path]'; } description() { return _('Manages E2EE configuration. Commands are `enable`, `disable`, `decrypt`, `status` and `target-status`.'); } options() { return [ // This is here mostly for testing - shouldn't be used ['-p, --password ', 'Use this password as master password (For security reasons, it is not recommended to use this option).'], ['-v, --verbose', 'More verbose output for the `target-status` command'], ]; } async action(args) { // change-password const options = args.options; if (args.command === 'enable') { const password = options.password ? options.password.toString() : await this.prompt(_('Enter master password:'), { type: 'string', secure: true }); if (!password) { this.stdout(_('Operation cancelled')); return; } await EncryptionService.instance().generateMasterKeyAndEnableEncryption(password); return; } if (args.command === 'disable') { await EncryptionService.instance().disableEncryption(); return; } if (args.command === 'decrypt') { this.stdout(_('Starting decryption... Please wait as it may take several minutes depending on how much there is to decrypt.')); while (true) { try { await DecryptionWorker.instance().start(); break; } catch (error) { if (error.code === 'masterKeyNotLoaded') { const masterKeyId = error.masterKeyId; const password = await this.prompt(_('Enter master password:'), { type: 'string', secure: true }); if (!password) { this.stdout(_('Operation cancelled')); return; } Setting.setObjectKey('encryption.passwordCache', masterKeyId, password); await EncryptionService.instance().loadMasterKeysFromSettings(); continue; } throw error; } } this.stdout(_('Completed decryption.')); return; } if (args.command === 'status') { this.stdout(_('Encryption is: %s', Setting.value('encryption.enabled') ? _('Enabled') : _('Disabled'))); return; } if (args.command === 'target-status') { const fs = require('fs-extra'); const pathUtils = require('lib/path-utils.js'); const fsDriver = new (require('lib/fs-driver-node.js').FsDriverNode)(); const targetPath = args.path; if (!targetPath) throw new Error('Please specify the sync target path.'); const dirPaths = function(targetPath) { let paths = []; fs.readdirSync(targetPath).forEach((path) => { paths.push(path); }); return paths; } let itemCount = 0; let resourceCount = 0; let encryptedItemCount = 0; let encryptedResourceCount = 0; let otherItemCount = 0; let encryptedPaths = []; let decryptedPaths = []; let paths = dirPaths(targetPath); for (let i = 0; i < paths.length; i++) { const path = paths[i]; const fullPath = targetPath + '/' + path; const stat = await fs.stat(fullPath); // this.stdout(fullPath); if (path === '.resource') { let resourcePaths = dirPaths(fullPath); for (let j = 0; j < resourcePaths.length; j++) { const resourcePath = resourcePaths[j]; resourceCount++; const fullResourcePath = fullPath + '/' + resourcePath; const isEncrypted = await EncryptionService.instance().fileIsEncrypted(fullResourcePath); if (isEncrypted) { encryptedResourceCount++; encryptedPaths.push(fullResourcePath); } else { decryptedPaths.push(fullResourcePath); } } } else if (stat.isDirectory()) { continue; } else { const content = await fs.readFile(fullPath, 'utf8'); const item = await BaseItem.unserialize(content); const ItemClass = BaseItem.itemClass(item); if (!ItemClass.encryptionSupported()) { otherItemCount++; continue; } itemCount++; const isEncrypted = await EncryptionService.instance().itemIsEncrypted(item); if (isEncrypted) { encryptedItemCount++; encryptedPaths.push(fullPath); } else { decryptedPaths.push(fullPath); } } } this.stdout('Encrypted items: ' + encryptedItemCount + '/' + itemCount); this.stdout('Encrypted resources: ' + encryptedResourceCount + '/' + resourceCount); this.stdout('Other items (never encrypted): ' + otherItemCount); if (options.verbose) { this.stdout(''); this.stdout('# Encrypted paths'); this.stdout(''); for (let i = 0; i < encryptedPaths.length; i++) { const path = encryptedPaths[i]; this.stdout(path); } this.stdout(''); this.stdout('# Decrypted paths'); this.stdout(''); for (let i = 0; i < decryptedPaths.length; i++) { const path = decryptedPaths[i]; this.stdout(path); } } return; } } } module.exports = Command;