MigrateKeyStorage.php 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * @copyright Copyright (c) 2020, Roeland Jago Douma <roeland@famdouma.nl>
  5. *
  6. * @author Roeland Jago Douma <roeland@famdouma.nl>
  7. *
  8. * @license GNU AGPL version 3 or any later version
  9. *
  10. * This program is free software: you can redistribute it and/or modify
  11. * it under the terms of the GNU Affero General Public License as
  12. * published by the Free Software Foundation, either version 3 of the
  13. * License, or (at your option) any later version.
  14. *
  15. * This program is distributed in the hope that it will be useful,
  16. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. * GNU Affero General Public License for more details.
  19. *
  20. * You should have received a copy of the GNU Affero General Public License
  21. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  22. *
  23. */
  24. namespace OC\Core\Command\Encryption;
  25. use OC\Encryption\Keys\Storage;
  26. use OC\Encryption\Util;
  27. use OC\Files\View;
  28. use OCP\IConfig;
  29. use OCP\IUserManager;
  30. use OCP\Security\ICrypto;
  31. use Symfony\Component\Console\Command\Command;
  32. use Symfony\Component\Console\Helper\ProgressBar;
  33. use Symfony\Component\Console\Helper\QuestionHelper;
  34. use Symfony\Component\Console\Input\InputInterface;
  35. use Symfony\Component\Console\Output\OutputInterface;
  36. class MigrateKeyStorage extends Command {
  37. protected View $rootView;
  38. protected IUserManager $userManager;
  39. protected IConfig $config;
  40. protected Util $util;
  41. protected QuestionHelper $questionHelper;
  42. private ICrypto $crypto;
  43. public function __construct(View $view, IUserManager $userManager, IConfig $config, Util $util, ICrypto $crypto) {
  44. parent::__construct();
  45. $this->rootView = $view;
  46. $this->userManager = $userManager;
  47. $this->config = $config;
  48. $this->util = $util;
  49. $this->crypto = $crypto;
  50. }
  51. protected function configure() {
  52. parent::configure();
  53. $this
  54. ->setName('encryption:migrate-key-storage-format')
  55. ->setDescription('Migrate the format of the keystorage to a newer format');
  56. }
  57. protected function execute(InputInterface $input, OutputInterface $output): int {
  58. $root = $this->util->getKeyStorageRoot();
  59. $output->writeln("Updating key storage format");
  60. $this->updateKeys($root, $output);
  61. $output->writeln("Key storage format successfully updated");
  62. return 0;
  63. }
  64. /**
  65. * Move keys to new key storage root
  66. *
  67. * @param string $root
  68. * @param OutputInterface $output
  69. * @return bool
  70. * @throws \Exception
  71. */
  72. protected function updateKeys(string $root, OutputInterface $output): bool {
  73. $output->writeln("Start to update the keys:");
  74. $this->updateSystemKeys($root);
  75. $this->updateUsersKeys($root, $output);
  76. $this->config->deleteSystemValue('encryption.key_storage_migrated');
  77. return true;
  78. }
  79. /**
  80. * Move system key folder
  81. */
  82. protected function updateSystemKeys(string $root): void {
  83. if (!$this->rootView->is_dir($root . '/files_encryption')) {
  84. return;
  85. }
  86. $this->traverseKeys($root . '/files_encryption', null);
  87. }
  88. private function traverseKeys(string $folder, ?string $uid) {
  89. $listing = $this->rootView->getDirectoryContent($folder);
  90. foreach ($listing as $node) {
  91. if ($node['mimetype'] === 'httpd/unix-directory') {
  92. continue;
  93. }
  94. if ($node['name'] === 'fileKey' ||
  95. str_ends_with($node['name'], '.privateKey') ||
  96. str_ends_with($node['name'], '.publicKey') ||
  97. str_ends_with($node['name'], '.shareKey')) {
  98. $path = $folder . '/' . $node['name'];
  99. $content = $this->rootView->file_get_contents($path);
  100. try {
  101. $this->crypto->decrypt($content);
  102. continue;
  103. } catch (\Exception $e) {
  104. // Ignore we now update the data.
  105. }
  106. $data = [
  107. 'key' => base64_encode($content),
  108. 'uid' => $uid,
  109. ];
  110. $enc = $this->crypto->encrypt(json_encode($data));
  111. $this->rootView->file_put_contents($path, $enc);
  112. }
  113. }
  114. }
  115. private function traverseFileKeys(string $folder) {
  116. $listing = $this->rootView->getDirectoryContent($folder);
  117. foreach ($listing as $node) {
  118. if ($node['mimetype'] === 'httpd/unix-directory') {
  119. $this->traverseFileKeys($folder . '/' . $node['name']);
  120. } else {
  121. $endsWith = function ($haystack, $needle) {
  122. $length = strlen($needle);
  123. if ($length === 0) {
  124. return true;
  125. }
  126. return (substr($haystack, -$length) === $needle);
  127. };
  128. if ($node['name'] === 'fileKey' ||
  129. $endsWith($node['name'], '.privateKey') ||
  130. $endsWith($node['name'], '.publicKey') ||
  131. $endsWith($node['name'], '.shareKey')) {
  132. $path = $folder . '/' . $node['name'];
  133. $content = $this->rootView->file_get_contents($path);
  134. try {
  135. $this->crypto->decrypt($content);
  136. continue;
  137. } catch (\Exception $e) {
  138. // Ignore we now update the data.
  139. }
  140. $data = [
  141. 'key' => base64_encode($content)
  142. ];
  143. $enc = $this->crypto->encrypt(json_encode($data));
  144. $this->rootView->file_put_contents($path, $enc);
  145. }
  146. }
  147. }
  148. }
  149. /**
  150. * setup file system for the given user
  151. *
  152. * @param string $uid
  153. */
  154. protected function setupUserFS($uid) {
  155. \OC_Util::tearDownFS();
  156. \OC_Util::setupFS($uid);
  157. }
  158. /**
  159. * iterate over each user and move the keys to the new storage
  160. *
  161. * @param string $root
  162. * @param OutputInterface $output
  163. */
  164. protected function updateUsersKeys(string $root, OutputInterface $output) {
  165. $progress = new ProgressBar($output);
  166. $progress->start();
  167. foreach ($this->userManager->getBackends() as $backend) {
  168. $limit = 500;
  169. $offset = 0;
  170. do {
  171. $users = $backend->getUsers('', $limit, $offset);
  172. foreach ($users as $user) {
  173. $progress->advance();
  174. $this->setupUserFS($user);
  175. $this->updateUserKeys($root, $user);
  176. }
  177. $offset += $limit;
  178. } while (count($users) >= $limit);
  179. }
  180. $progress->finish();
  181. }
  182. /**
  183. * move user encryption folder to new root folder
  184. *
  185. * @param string $root
  186. * @param string $user
  187. * @throws \Exception
  188. */
  189. protected function updateUserKeys(string $root, string $user) {
  190. if ($this->userManager->userExists($user)) {
  191. $source = $root . '/' . $user . '/files_encryption/OC_DEFAULT_MODULE';
  192. if ($this->rootView->is_dir($source)) {
  193. $this->traverseKeys($source, $user);
  194. }
  195. $source = $root . '/' . $user . '/files_encryption/keys';
  196. if ($this->rootView->is_dir($source)) {
  197. $this->traverseFileKeys($source);
  198. }
  199. }
  200. }
  201. }