MigrateKeyStorage.php 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  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\Input\InputInterface;
  34. use Symfony\Component\Console\Output\OutputInterface;
  35. class MigrateKeyStorage extends Command {
  36. public function __construct(
  37. protected View $rootView,
  38. protected IUserManager $userManager,
  39. protected IConfig $config,
  40. protected Util $util,
  41. private ICrypto $crypto,
  42. ) {
  43. parent::__construct();
  44. }
  45. protected function configure() {
  46. parent::configure();
  47. $this
  48. ->setName('encryption:migrate-key-storage-format')
  49. ->setDescription('Migrate the format of the keystorage to a newer format');
  50. }
  51. protected function execute(InputInterface $input, OutputInterface $output): int {
  52. $root = $this->util->getKeyStorageRoot();
  53. $output->writeln("Updating key storage format");
  54. $this->updateKeys($root, $output);
  55. $output->writeln("Key storage format successfully updated");
  56. return 0;
  57. }
  58. /**
  59. * Move keys to new key storage root
  60. *
  61. * @param string $root
  62. * @param OutputInterface $output
  63. * @return bool
  64. * @throws \Exception
  65. */
  66. protected function updateKeys(string $root, OutputInterface $output): bool {
  67. $output->writeln("Start to update the keys:");
  68. $this->updateSystemKeys($root);
  69. $this->updateUsersKeys($root, $output);
  70. $this->config->deleteSystemValue('encryption.key_storage_migrated');
  71. return true;
  72. }
  73. /**
  74. * Move system key folder
  75. */
  76. protected function updateSystemKeys(string $root): void {
  77. if (!$this->rootView->is_dir($root . '/files_encryption')) {
  78. return;
  79. }
  80. $this->traverseKeys($root . '/files_encryption', null);
  81. }
  82. private function traverseKeys(string $folder, ?string $uid) {
  83. $listing = $this->rootView->getDirectoryContent($folder);
  84. foreach ($listing as $node) {
  85. if ($node['mimetype'] === 'httpd/unix-directory') {
  86. continue;
  87. }
  88. if ($node['name'] === 'fileKey' ||
  89. str_ends_with($node['name'], '.privateKey') ||
  90. str_ends_with($node['name'], '.publicKey') ||
  91. str_ends_with($node['name'], '.shareKey')) {
  92. $path = $folder . '/' . $node['name'];
  93. $content = $this->rootView->file_get_contents($path);
  94. try {
  95. $this->crypto->decrypt($content);
  96. continue;
  97. } catch (\Exception $e) {
  98. // Ignore we now update the data.
  99. }
  100. $data = [
  101. 'key' => base64_encode($content),
  102. 'uid' => $uid,
  103. ];
  104. $enc = $this->crypto->encrypt(json_encode($data));
  105. $this->rootView->file_put_contents($path, $enc);
  106. }
  107. }
  108. }
  109. private function traverseFileKeys(string $folder) {
  110. $listing = $this->rootView->getDirectoryContent($folder);
  111. foreach ($listing as $node) {
  112. if ($node['mimetype'] === 'httpd/unix-directory') {
  113. $this->traverseFileKeys($folder . '/' . $node['name']);
  114. } else {
  115. $endsWith = function ($haystack, $needle) {
  116. $length = strlen($needle);
  117. if ($length === 0) {
  118. return true;
  119. }
  120. return (substr($haystack, -$length) === $needle);
  121. };
  122. if ($node['name'] === 'fileKey' ||
  123. $endsWith($node['name'], '.privateKey') ||
  124. $endsWith($node['name'], '.publicKey') ||
  125. $endsWith($node['name'], '.shareKey')) {
  126. $path = $folder . '/' . $node['name'];
  127. $content = $this->rootView->file_get_contents($path);
  128. try {
  129. $this->crypto->decrypt($content);
  130. continue;
  131. } catch (\Exception $e) {
  132. // Ignore we now update the data.
  133. }
  134. $data = [
  135. 'key' => base64_encode($content)
  136. ];
  137. $enc = $this->crypto->encrypt(json_encode($data));
  138. $this->rootView->file_put_contents($path, $enc);
  139. }
  140. }
  141. }
  142. }
  143. /**
  144. * setup file system for the given user
  145. *
  146. * @param string $uid
  147. */
  148. protected function setupUserFS($uid) {
  149. \OC_Util::tearDownFS();
  150. \OC_Util::setupFS($uid);
  151. }
  152. /**
  153. * iterate over each user and move the keys to the new storage
  154. *
  155. * @param string $root
  156. * @param OutputInterface $output
  157. */
  158. protected function updateUsersKeys(string $root, OutputInterface $output) {
  159. $progress = new ProgressBar($output);
  160. $progress->start();
  161. foreach ($this->userManager->getBackends() as $backend) {
  162. $limit = 500;
  163. $offset = 0;
  164. do {
  165. $users = $backend->getUsers('', $limit, $offset);
  166. foreach ($users as $user) {
  167. $progress->advance();
  168. $this->setupUserFS($user);
  169. $this->updateUserKeys($root, $user);
  170. }
  171. $offset += $limit;
  172. } while (count($users) >= $limit);
  173. }
  174. $progress->finish();
  175. }
  176. /**
  177. * move user encryption folder to new root folder
  178. *
  179. * @param string $root
  180. * @param string $user
  181. * @throws \Exception
  182. */
  183. protected function updateUserKeys(string $root, string $user) {
  184. if ($this->userManager->userExists($user)) {
  185. $source = $root . '/' . $user . '/files_encryption/OC_DEFAULT_MODULE';
  186. if ($this->rootView->is_dir($source)) {
  187. $this->traverseKeys($source, $user);
  188. }
  189. $source = $root . '/' . $user . '/files_encryption/keys';
  190. if ($this->rootView->is_dir($source)) {
  191. $this->traverseFileKeys($source);
  192. }
  193. }
  194. }
  195. }