You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
nextcloud-server/core/Command/Encryption/MigrateKeyStorage.php

213 lines
5.2 KiB

<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
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\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class MigrateKeyStorage extends Command {
public function __construct(
protected View $rootView,
protected IUserManager $userManager,
protected IConfig $config,
protected Util $util,
private ICrypto $crypto,
) {
parent::__construct();
}
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): 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);
}
}
}
}