diff options
author | Roeland Jago Douma <roeland@famdouma.nl> | 2020-06-22 15:35:52 +0200 |
---|---|---|
committer | Roeland Jago Douma <roeland@famdouma.nl> | 2020-08-20 15:42:43 +0200 |
commit | 5340ab3a75d58651e3cc65688d94444b38570cfc (patch) | |
tree | 426f4cd4a944319ed1254a463adfbb86dba6f950 /core/Command | |
parent | 886466d5109de6ed399e2da3dcf87eea66d531ce (diff) | |
download | nextcloud-server-5340ab3a75d58651e3cc65688d94444b38570cfc.tar.gz nextcloud-server-5340ab3a75d58651e3cc65688d94444b38570cfc.zip |
New SSE key format
* Encrypt the keys with the instance secret
* Store them as json (so we can add other things if needed)
Signed-off-by: Roeland Jago Douma <roeland@famdouma.nl>
Diffstat (limited to 'core/Command')
-rw-r--r-- | core/Command/Encryption/MigrateKeyStorage.php | 261 |
1 files changed, 261 insertions, 0 deletions
diff --git a/core/Command/Encryption/MigrateKeyStorage.php b/core/Command/Encryption/MigrateKeyStorage.php new file mode 100644 index 00000000000..099eba1e822 --- /dev/null +++ b/core/Command/Encryption/MigrateKeyStorage.php @@ -0,0 +1,261 @@ +<?php + +declare(strict_types=1); +/** + * @copyright Copyright (c) 2020, Roeland Jago Douma <roeland@famdouma.nl> + * + * @author Roeland Jago Douma <roeland@famdouma.nl> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + + +namespace OC\Core\Command\Encryption; + +use OC\Encryption\Keys\Storage; +use OC\Encryption\Util; +use OC\Files\View; +use OCP\IConfig; +use OCP\IUserManager; +use OCP\Security\ICrypto; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Helper\ProgressBar; +use Symfony\Component\Console\Helper\QuestionHelper; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +class MigrateKeyStorage extends Command { + + /** @var View */ + protected $rootView; + + /** @var IUserManager */ + protected $userManager; + + /** @var IConfig */ + protected $config; + + /** @var Util */ + protected $util; + + /** @var QuestionHelper */ + protected $questionHelper; + /** + * @var ICrypto + */ + private $crypto; + + public function __construct(View $view, IUserManager $userManager, IConfig $config, Util $util, ICrypto $crypto) { + parent::__construct(); + $this->rootView = $view; + $this->userManager = $userManager; + $this->config = $config; + $this->util = $util; + $this->crypto = $crypto; + } + + protected function configure() { + parent::configure(); + $this + ->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) { + $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 + * + * @param string $root + */ + protected function updateSystemKeys($root) { + 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') { + //ignore + } 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), + '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); + } + } + } +} |