EncryptAll.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474
  1. <?php
  2. /**
  3. * @copyright Copyright (c) 2016, ownCloud, Inc.
  4. *
  5. * @author Bjoern Schiessle <bjoern@schiessle.org>
  6. * @author Björn Schießle <bjoern@schiessle.org>
  7. * @author Christoph Wurst <christoph@winzerhof-wurst.at>
  8. * @author Kenneth Newwood <kenneth@newwood.name>
  9. * @author Morris Jobke <hey@morrisjobke.de>
  10. * @author Roeland Jago Douma <roeland@famdouma.nl>
  11. * @author Thomas Müller <thomas.mueller@tmit.eu>
  12. *
  13. * @license AGPL-3.0
  14. *
  15. * This code is free software: you can redistribute it and/or modify
  16. * it under the terms of the GNU Affero General Public License, version 3,
  17. * as published by the Free Software Foundation.
  18. *
  19. * This program is distributed in the hope that it will be useful,
  20. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  21. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  22. * GNU Affero General Public License for more details.
  23. *
  24. * You should have received a copy of the GNU Affero General Public License, version 3,
  25. * along with this program. If not, see <http://www.gnu.org/licenses/>
  26. *
  27. */
  28. namespace OCA\Encryption\Crypto;
  29. use OC\Encryption\Exceptions\DecryptionFailedException;
  30. use OC\Files\View;
  31. use OCA\Encryption\KeyManager;
  32. use OCA\Encryption\Users\Setup;
  33. use OCA\Encryption\Util;
  34. use OCP\IConfig;
  35. use OCP\IL10N;
  36. use OCP\IUser;
  37. use OCP\IUserManager;
  38. use OCP\L10N\IFactory;
  39. use OCP\Mail\Headers\AutoSubmitted;
  40. use OCP\Mail\IMailer;
  41. use OCP\Security\ISecureRandom;
  42. use Symfony\Component\Console\Helper\ProgressBar;
  43. use Symfony\Component\Console\Helper\QuestionHelper;
  44. use Symfony\Component\Console\Helper\Table;
  45. use Symfony\Component\Console\Input\InputInterface;
  46. use Symfony\Component\Console\Output\OutputInterface;
  47. use Symfony\Component\Console\Question\ConfirmationQuestion;
  48. class EncryptAll {
  49. /** @var Setup */
  50. protected $userSetup;
  51. /** @var IUserManager */
  52. protected $userManager;
  53. /** @var View */
  54. protected $rootView;
  55. /** @var KeyManager */
  56. protected $keyManager;
  57. /** @var Util */
  58. protected $util;
  59. /** @var array */
  60. protected $userPasswords;
  61. /** @var IConfig */
  62. protected $config;
  63. /** @var IMailer */
  64. protected $mailer;
  65. /** @var IL10N */
  66. protected $l;
  67. /** @var IFactory */
  68. protected $l10nFactory;
  69. /** @var QuestionHelper */
  70. protected $questionHelper;
  71. /** @var OutputInterface */
  72. protected $output;
  73. /** @var InputInterface */
  74. protected $input;
  75. /** @var ISecureRandom */
  76. protected $secureRandom;
  77. public function __construct(
  78. Setup $userSetup,
  79. IUserManager $userManager,
  80. View $rootView,
  81. KeyManager $keyManager,
  82. Util $util,
  83. IConfig $config,
  84. IMailer $mailer,
  85. IL10N $l,
  86. IFactory $l10nFactory,
  87. QuestionHelper $questionHelper,
  88. ISecureRandom $secureRandom
  89. ) {
  90. $this->userSetup = $userSetup;
  91. $this->userManager = $userManager;
  92. $this->rootView = $rootView;
  93. $this->keyManager = $keyManager;
  94. $this->util = $util;
  95. $this->config = $config;
  96. $this->mailer = $mailer;
  97. $this->l = $l;
  98. $this->l10nFactory = $l10nFactory;
  99. $this->questionHelper = $questionHelper;
  100. $this->secureRandom = $secureRandom;
  101. // store one time passwords for the users
  102. $this->userPasswords = [];
  103. }
  104. /**
  105. * start to encrypt all files
  106. *
  107. * @param InputInterface $input
  108. * @param OutputInterface $output
  109. */
  110. public function encryptAll(InputInterface $input, OutputInterface $output) {
  111. $this->input = $input;
  112. $this->output = $output;
  113. $headline = 'Encrypt all files with the ' . Encryption::DISPLAY_NAME;
  114. $this->output->writeln("\n");
  115. $this->output->writeln($headline);
  116. $this->output->writeln(str_pad('', strlen($headline), '='));
  117. $this->output->writeln("\n");
  118. if ($this->util->isMasterKeyEnabled()) {
  119. $this->output->writeln('Use master key to encrypt all files.');
  120. $this->keyManager->validateMasterKey();
  121. } else {
  122. //create private/public keys for each user and store the private key password
  123. $this->output->writeln('Create key-pair for every user');
  124. $this->output->writeln('------------------------------');
  125. $this->output->writeln('');
  126. $this->output->writeln('This module will encrypt all files in the users files folder initially.');
  127. $this->output->writeln('Already existing versions and files in the trash bin will not be encrypted.');
  128. $this->output->writeln('');
  129. $this->createKeyPairs();
  130. }
  131. // output generated encryption key passwords
  132. if ($this->util->isMasterKeyEnabled() === false) {
  133. //send-out or display password list and write it to a file
  134. $this->output->writeln("\n");
  135. $this->output->writeln('Generated encryption key passwords');
  136. $this->output->writeln('----------------------------------');
  137. $this->output->writeln('');
  138. $this->outputPasswords();
  139. }
  140. //setup users file system and encrypt all files one by one (take should encrypt setting of storage into account)
  141. $this->output->writeln("\n");
  142. $this->output->writeln('Start to encrypt users files');
  143. $this->output->writeln('----------------------------');
  144. $this->output->writeln('');
  145. $this->encryptAllUsersFiles();
  146. $this->output->writeln("\n");
  147. }
  148. /**
  149. * create key-pair for every user
  150. */
  151. protected function createKeyPairs() {
  152. $this->output->writeln("\n");
  153. $progress = new ProgressBar($this->output);
  154. $progress->setFormat(" %message% \n [%bar%]");
  155. $progress->start();
  156. foreach ($this->userManager->getBackends() as $backend) {
  157. $limit = 500;
  158. $offset = 0;
  159. do {
  160. $users = $backend->getUsers('', $limit, $offset);
  161. foreach ($users as $user) {
  162. if ($this->keyManager->userHasKeys($user) === false) {
  163. $progress->setMessage('Create key-pair for ' . $user);
  164. $progress->advance();
  165. $this->setupUserFS($user);
  166. $password = $this->generateOneTimePassword($user);
  167. $this->userSetup->setupUser($user, $password);
  168. } else {
  169. // users which already have a key-pair will be stored with a
  170. // empty password and filtered out later
  171. $this->userPasswords[$user] = '';
  172. }
  173. }
  174. $offset += $limit;
  175. } while (count($users) >= $limit);
  176. }
  177. $progress->setMessage('Key-pair created for all users');
  178. $progress->finish();
  179. }
  180. /**
  181. * iterate over all user and encrypt their files
  182. */
  183. protected function encryptAllUsersFiles() {
  184. $this->output->writeln("\n");
  185. $progress = new ProgressBar($this->output);
  186. $progress->setFormat(" %message% \n [%bar%]");
  187. $progress->start();
  188. $numberOfUsers = count($this->userPasswords);
  189. $userNo = 1;
  190. if ($this->util->isMasterKeyEnabled()) {
  191. $this->encryptAllUserFilesWithMasterKey($progress);
  192. } else {
  193. foreach ($this->userPasswords as $uid => $password) {
  194. $userCount = "$uid ($userNo of $numberOfUsers)";
  195. $this->encryptUsersFiles($uid, $progress, $userCount);
  196. $userNo++;
  197. }
  198. }
  199. $progress->setMessage("all files encrypted");
  200. $progress->finish();
  201. }
  202. /**
  203. * encrypt all user files with the master key
  204. *
  205. * @param ProgressBar $progress
  206. */
  207. protected function encryptAllUserFilesWithMasterKey(ProgressBar $progress) {
  208. $userNo = 1;
  209. foreach ($this->userManager->getBackends() as $backend) {
  210. $limit = 500;
  211. $offset = 0;
  212. do {
  213. $users = $backend->getUsers('', $limit, $offset);
  214. foreach ($users as $user) {
  215. $userCount = "$user ($userNo)";
  216. $this->encryptUsersFiles($user, $progress, $userCount);
  217. $userNo++;
  218. }
  219. $offset += $limit;
  220. } while (count($users) >= $limit);
  221. }
  222. }
  223. /**
  224. * encrypt files from the given user
  225. *
  226. * @param string $uid
  227. * @param ProgressBar $progress
  228. * @param string $userCount
  229. */
  230. protected function encryptUsersFiles($uid, ProgressBar $progress, $userCount) {
  231. $this->setupUserFS($uid);
  232. $directories = [];
  233. $directories[] = '/' . $uid . '/files';
  234. while ($root = array_pop($directories)) {
  235. $content = $this->rootView->getDirectoryContent($root);
  236. foreach ($content as $file) {
  237. $path = $root . '/' . $file['name'];
  238. if ($this->rootView->is_dir($path)) {
  239. $directories[] = $path;
  240. continue;
  241. } else {
  242. $progress->setMessage("encrypt files for user $userCount: $path");
  243. $progress->advance();
  244. if ($this->encryptFile($path) === false) {
  245. $progress->setMessage("encrypt files for user $userCount: $path (already encrypted)");
  246. $progress->advance();
  247. }
  248. }
  249. }
  250. }
  251. }
  252. /**
  253. * encrypt file
  254. *
  255. * @param string $path
  256. * @return bool
  257. */
  258. protected function encryptFile($path) {
  259. // skip already encrypted files
  260. $fileInfo = $this->rootView->getFileInfo($path);
  261. if ($fileInfo !== false && $fileInfo->isEncrypted()) {
  262. return true;
  263. }
  264. $source = $path;
  265. $target = $path . '.encrypted.' . time();
  266. try {
  267. $this->rootView->copy($source, $target);
  268. $this->rootView->rename($target, $source);
  269. } catch (DecryptionFailedException $e) {
  270. if ($this->rootView->file_exists($target)) {
  271. $this->rootView->unlink($target);
  272. }
  273. return false;
  274. }
  275. return true;
  276. }
  277. /**
  278. * output one-time encryption passwords
  279. */
  280. protected function outputPasswords() {
  281. $table = new Table($this->output);
  282. $table->setHeaders(['Username', 'Private key password']);
  283. //create rows
  284. $newPasswords = [];
  285. $unchangedPasswords = [];
  286. foreach ($this->userPasswords as $uid => $password) {
  287. if (empty($password)) {
  288. $unchangedPasswords[] = $uid;
  289. } else {
  290. $newPasswords[] = [$uid, $password];
  291. }
  292. }
  293. if (empty($newPasswords)) {
  294. $this->output->writeln("\nAll users already had a key-pair, no further action needed.\n");
  295. return;
  296. }
  297. $table->setRows($newPasswords);
  298. $table->render();
  299. if (!empty($unchangedPasswords)) {
  300. $this->output->writeln("\nThe following users already had a key-pair which was reused without setting a new password:\n");
  301. foreach ($unchangedPasswords as $uid) {
  302. $this->output->writeln(" $uid");
  303. }
  304. }
  305. $this->writePasswordsToFile($newPasswords);
  306. $this->output->writeln('');
  307. $question = new ConfirmationQuestion('Do you want to send the passwords directly to the users by mail? (y/n) ', false);
  308. if ($this->questionHelper->ask($this->input, $this->output, $question)) {
  309. $this->sendPasswordsByMail();
  310. }
  311. }
  312. /**
  313. * write one-time encryption passwords to a csv file
  314. *
  315. * @param array $passwords
  316. */
  317. protected function writePasswordsToFile(array $passwords) {
  318. $fp = $this->rootView->fopen('oneTimeEncryptionPasswords.csv', 'w');
  319. foreach ($passwords as $pwd) {
  320. fputcsv($fp, $pwd);
  321. }
  322. fclose($fp);
  323. $this->output->writeln("\n");
  324. $this->output->writeln('A list of all newly created passwords was written to data/oneTimeEncryptionPasswords.csv');
  325. $this->output->writeln('');
  326. $this->output->writeln('Each of these users need to login to the web interface, go to the');
  327. $this->output->writeln('personal settings section "basic encryption module" and');
  328. $this->output->writeln('update the private key password to match the login password again by');
  329. $this->output->writeln('entering the one-time password into the "old log-in password" field');
  330. $this->output->writeln('and their current login password');
  331. }
  332. /**
  333. * setup user file system
  334. *
  335. * @param string $uid
  336. */
  337. protected function setupUserFS($uid) {
  338. \OC_Util::tearDownFS();
  339. \OC_Util::setupFS($uid);
  340. }
  341. /**
  342. * generate one time password for the user and store it in a array
  343. *
  344. * @param string $uid
  345. * @return string password
  346. */
  347. protected function generateOneTimePassword($uid) {
  348. $password = $this->secureRandom->generate(16, ISecureRandom::CHAR_HUMAN_READABLE);
  349. $this->userPasswords[$uid] = $password;
  350. return $password;
  351. }
  352. /**
  353. * send encryption key passwords to the users by mail
  354. */
  355. protected function sendPasswordsByMail() {
  356. $noMail = [];
  357. $this->output->writeln('');
  358. $progress = new ProgressBar($this->output, count($this->userPasswords));
  359. $progress->start();
  360. foreach ($this->userPasswords as $uid => $password) {
  361. $progress->advance();
  362. if (!empty($password)) {
  363. $recipient = $this->userManager->get($uid);
  364. if (!$recipient instanceof IUser) {
  365. continue;
  366. }
  367. $recipientDisplayName = $recipient->getDisplayName();
  368. $to = $recipient->getEMailAddress();
  369. if ($to === '' || $to === null) {
  370. $noMail[] = $uid;
  371. continue;
  372. }
  373. $l = $this->l10nFactory->get('encryption', $this->l10nFactory->getUserLanguage($recipient));
  374. $template = $this->mailer->createEMailTemplate('encryption.encryptAllPassword', [
  375. 'user' => $recipient->getUID(),
  376. 'password' => $password,
  377. ]);
  378. $template->setSubject($l->t('one-time password for server-side-encryption'));
  379. // 'Hey there,<br><br>The administration enabled server-side-encryption. Your files were encrypted using the password <strong>%s</strong>.<br><br>
  380. // Please login to the web interface, go to the section "Basic encryption module" of your personal settings and update your encryption password by entering this password into the "Old log-in password" field and your current login-password.<br><br>'
  381. $template->addHeader();
  382. $template->addHeading($l->t('Encryption password'));
  383. $template->addBodyText(
  384. $l->t('The administration enabled server-side-encryption. Your files were encrypted using the password <strong>%s</strong>.', [htmlspecialchars($password)]),
  385. $l->t('The administration enabled server-side-encryption. Your files were encrypted using the password "%s".', $password)
  386. );
  387. $template->addBodyText(
  388. $l->t('Please login to the web interface, go to the "Security" section of your personal settings and update your encryption password by entering this password into the "Old login password" field and your current login password.')
  389. );
  390. $template->addFooter();
  391. // send it out now
  392. try {
  393. $message = $this->mailer->createMessage();
  394. $message->setTo([$to => $recipientDisplayName]);
  395. $message->useTemplate($template);
  396. $message->setAutoSubmitted(AutoSubmitted::VALUE_AUTO_GENERATED);
  397. $this->mailer->send($message);
  398. } catch (\Exception $e) {
  399. $noMail[] = $uid;
  400. }
  401. }
  402. }
  403. $progress->finish();
  404. if (empty($noMail)) {
  405. $this->output->writeln("\n\nPassword successfully send to all users");
  406. } else {
  407. $table = new Table($this->output);
  408. $table->setHeaders(['Username', 'Private key password']);
  409. $this->output->writeln("\n\nCould not send password to following users:\n");
  410. $rows = [];
  411. foreach ($noMail as $uid) {
  412. $rows[] = [$uid, $this->userPasswords[$uid]];
  413. }
  414. $table->setRows($rows);
  415. $table->render();
  416. }
  417. }
  418. }