setName('encryption:migrate-key-storage-format') ->setDescription('Migrate the format of the keystorage to a newer format'); } protected function execute(InputInterface $input, OutputInterface $output): int { $root = $this->util->getKeyStorageRoot(); $output->writeln('Updating key storage format'); $this->updateKeys($root, $output); $output->writeln('Key storage format successfully updated'); return 0; } /** * Move keys to new key storage root * * @param string $root * @param OutputInterface $output * @return bool * @throws \Exception */ protected function updateKeys(string $root, OutputInterface $output): bool { $output->writeln('Start to update the keys:'); $this->updateSystemKeys($root); $this->updateUsersKeys($root, $output); $this->config->deleteSystemValue('encryption.key_storage_migrated'); return true; } /** * Move system key folder */ protected function updateSystemKeys(string $root): void { if (!$this->rootView->is_dir($root . '/files_encryption')) { return; } $this->traverseKeys($root . '/files_encryption', null); } private function traverseKeys(string $folder, ?string $uid) { $listing = $this->rootView->getDirectoryContent($folder); foreach ($listing as $node) { if ($node['mimetype'] === 'httpd/unix-directory') { continue; } if ($node['name'] === 'fileKey' || str_ends_with($node['name'], '.privateKey') || str_ends_with($node['name'], '.publicKey') || str_ends_with($node['name'], '.shareKey')) { $path = $folder . '/' . $node['name']; $content = $this->rootView->file_get_contents($path); try { $this->crypto->decrypt($content); continue; } catch (\Exception $e) { // Ignore we now update the data. } $data = [ 'key' => base64_encode($content), 'uid' => $uid, ]; $enc = $this->crypto->encrypt(json_encode($data)); $this->rootView->file_put_contents($path, $enc); } } } private function traverseFileKeys(string $folder) { $listing = $this->rootView->getDirectoryContent($folder); foreach ($listing as $node) { if ($node['mimetype'] === 'httpd/unix-directory') { $this->traverseFileKeys($folder . '/' . $node['name']); } else { $endsWith = function ($haystack, $needle) { $length = strlen($needle); if ($length === 0) { return true; } return (substr($haystack, -$length) === $needle); }; if ($node['name'] === 'fileKey' || $endsWith($node['name'], '.privateKey') || $endsWith($node['name'], '.publicKey') || $endsWith($node['name'], '.shareKey')) { $path = $folder . '/' . $node['name']; $content = $this->rootView->file_get_contents($path); try { $this->crypto->decrypt($content); continue; } catch (\Exception $e) { // Ignore we now update the data. } $data = [ 'key' => base64_encode($content) ]; $enc = $this->crypto->encrypt(json_encode($data)); $this->rootView->file_put_contents($path, $enc); } } } } /** * setup file system for the given user * * @param string $uid */ protected function setupUserFS($uid) { \OC_Util::tearDownFS(); \OC_Util::setupFS($uid); } /** * iterate over each user and move the keys to the new storage * * @param string $root * @param OutputInterface $output */ protected function updateUsersKeys(string $root, OutputInterface $output) { $progress = new ProgressBar($output); $progress->start(); foreach ($this->userManager->getBackends() as $backend) { $limit = 500; $offset = 0; do { $users = $backend->getUsers('', $limit, $offset); foreach ($users as $user) { $progress->advance(); $this->setupUserFS($user); $this->updateUserKeys($root, $user); } $offset += $limit; } while (count($users) >= $limit); } $progress->finish(); } /** * move user encryption folder to new root folder * * @param string $root * @param string $user * @throws \Exception */ protected function updateUserKeys(string $root, string $user) { if ($this->userManager->userExists($user)) { $source = $root . '/' . $user . '/files_encryption/OC_DEFAULT_MODULE'; if ($this->rootView->is_dir($source)) { $this->traverseKeys($source, $user); } $source = $root . '/' . $user . '/files_encryption/keys'; if ($this->rootView->is_dir($source)) { $this->traverseFileKeys($source); } } } }