aboutsummaryrefslogtreecommitdiffstats
path: root/apps/encryption
diff options
context:
space:
mode:
authorCôme Chilliet <91878298+come-nc@users.noreply.github.com>2023-05-15 10:20:34 +0200
committerGitHub <noreply@github.com>2023-05-15 10:20:34 +0200
commit7fd453eb10d98a9ec1862f1506f6c1b82f38b54d (patch)
tree66ad0e9738d3f9d9a919087391200ed4f5a92aaa /apps/encryption
parent25f3678e2428549f72c43a9bc72c0a5741f7c210 (diff)
parent49108880d2ada949caab6d64ec1eb0c81c1b67d6 (diff)
downloadnextcloud-server-7fd453eb10d98a9ec1862f1506f6c1b82f38b54d.tar.gz
nextcloud-server-7fd453eb10d98a9ec1862f1506f6c1b82f38b54d.zip
Merge pull request #38080 from nextcloud/enh/add-occ-command-for-legacy-filekey
Add an occ command to scan files for legacy file key in use and get rid of those
Diffstat (limited to 'apps/encryption')
-rw-r--r--apps/encryption/appinfo/info.xml1
-rw-r--r--apps/encryption/composer/composer/autoload_classmap.php1
-rw-r--r--apps/encryption/composer/composer/autoload_static.php1
-rw-r--r--apps/encryption/composer/composer/installed.php4
-rw-r--r--apps/encryption/lib/Command/DropLegacyFileKey.php167
-rw-r--r--apps/encryption/lib/Command/ScanLegacyFormat.php3
-rw-r--r--apps/encryption/lib/Crypto/Encryption.php7
7 files changed, 179 insertions, 5 deletions
diff --git a/apps/encryption/appinfo/info.xml b/apps/encryption/appinfo/info.xml
index e77261c4712..de7ba7f0783 100644
--- a/apps/encryption/appinfo/info.xml
+++ b/apps/encryption/appinfo/info.xml
@@ -42,6 +42,7 @@ Please read the documentation to know all implications before you decide to enab
<command>OCA\Encryption\Command\ScanLegacyFormat</command>
<command>OCA\Encryption\Command\FixEncryptedVersion</command>
<command>OCA\Encryption\Command\FixKeyLocation</command>
+ <command>OCA\Encryption\Command\DropLegacyFileKey</command>
</commands>
<settings>
diff --git a/apps/encryption/composer/composer/autoload_classmap.php b/apps/encryption/composer/composer/autoload_classmap.php
index 9f9ab4e406f..059296338b4 100644
--- a/apps/encryption/composer/composer/autoload_classmap.php
+++ b/apps/encryption/composer/composer/autoload_classmap.php
@@ -9,6 +9,7 @@ return array(
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
'OCA\\Encryption\\AppInfo\\Application' => $baseDir . '/../lib/AppInfo/Application.php',
'OCA\\Encryption\\Command\\DisableMasterKey' => $baseDir . '/../lib/Command/DisableMasterKey.php',
+ 'OCA\\Encryption\\Command\\DropLegacyFileKey' => $baseDir . '/../lib/Command/DropLegacyFileKey.php',
'OCA\\Encryption\\Command\\EnableMasterKey' => $baseDir . '/../lib/Command/EnableMasterKey.php',
'OCA\\Encryption\\Command\\FixEncryptedVersion' => $baseDir . '/../lib/Command/FixEncryptedVersion.php',
'OCA\\Encryption\\Command\\FixKeyLocation' => $baseDir . '/../lib/Command/FixKeyLocation.php',
diff --git a/apps/encryption/composer/composer/autoload_static.php b/apps/encryption/composer/composer/autoload_static.php
index 8f50f064997..6c458eabddd 100644
--- a/apps/encryption/composer/composer/autoload_static.php
+++ b/apps/encryption/composer/composer/autoload_static.php
@@ -24,6 +24,7 @@ class ComposerStaticInitEncryption
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
'OCA\\Encryption\\AppInfo\\Application' => __DIR__ . '/..' . '/../lib/AppInfo/Application.php',
'OCA\\Encryption\\Command\\DisableMasterKey' => __DIR__ . '/..' . '/../lib/Command/DisableMasterKey.php',
+ 'OCA\\Encryption\\Command\\DropLegacyFileKey' => __DIR__ . '/..' . '/../lib/Command/DropLegacyFileKey.php',
'OCA\\Encryption\\Command\\EnableMasterKey' => __DIR__ . '/..' . '/../lib/Command/EnableMasterKey.php',
'OCA\\Encryption\\Command\\FixEncryptedVersion' => __DIR__ . '/..' . '/../lib/Command/FixEncryptedVersion.php',
'OCA\\Encryption\\Command\\FixKeyLocation' => __DIR__ . '/..' . '/../lib/Command/FixKeyLocation.php',
diff --git a/apps/encryption/composer/composer/installed.php b/apps/encryption/composer/composer/installed.php
index 1426826287d..46be34510db 100644
--- a/apps/encryption/composer/composer/installed.php
+++ b/apps/encryption/composer/composer/installed.php
@@ -3,7 +3,7 @@
'name' => '__root__',
'pretty_version' => 'dev-master',
'version' => 'dev-master',
- 'reference' => 'dd3d689e04a5e1d558da937ca72980e0e2c7c404',
+ 'reference' => 'ffa8d21f37c8ccf968974b6aeb828e3e84287b94',
'type' => 'library',
'install_path' => __DIR__ . '/../',
'aliases' => array(),
@@ -13,7 +13,7 @@
'__root__' => array(
'pretty_version' => 'dev-master',
'version' => 'dev-master',
- 'reference' => 'dd3d689e04a5e1d558da937ca72980e0e2c7c404',
+ 'reference' => 'ffa8d21f37c8ccf968974b6aeb828e3e84287b94',
'type' => 'library',
'install_path' => __DIR__ . '/../',
'aliases' => array(),
diff --git a/apps/encryption/lib/Command/DropLegacyFileKey.php b/apps/encryption/lib/Command/DropLegacyFileKey.php
new file mode 100644
index 00000000000..f0a5f36f30f
--- /dev/null
+++ b/apps/encryption/lib/Command/DropLegacyFileKey.php
@@ -0,0 +1,167 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 2023, Côme Chilliet <come.chilliet@nextcloud.com>
+ *
+ * @author Côme Chilliet <come.chilliet@nextcloud.com>
+ *
+ * @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 OCA\Encryption\Command;
+
+use OC\Encryption\Exceptions\DecryptionFailedException;
+use OC\Files\FileInfo;
+use OC\Files\View;
+use OCA\Encryption\KeyManager;
+use OCP\Encryption\Exceptions\GenericEncryptionException;
+use OCP\IUserManager;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class DropLegacyFileKey extends Command {
+ private View $rootView;
+
+ public function __construct(
+ private IUserManager $userManager,
+ private KeyManager $keyManager,
+ ) {
+ parent::__construct();
+
+ $this->rootView = new View();
+ }
+
+ protected function configure(): void {
+ $this
+ ->setName('encryption:drop-legacy-filekey')
+ ->setDescription('Scan the files for the legacy filekey format using RC4 and get rid of it (if master key is enabled)');
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output): int {
+ $result = true;
+
+ $output->writeln('<info>Scanning all files for legacy filekey</info>');
+
+ foreach ($this->userManager->getBackends() as $backend) {
+ $limit = 500;
+ $offset = 0;
+ do {
+ $users = $backend->getUsers('', $limit, $offset);
+ foreach ($users as $user) {
+ $output->writeln('Scanning all files for ' . $user);
+ $this->setupUserFS($user);
+ $result = $result && $this->scanFolder($output, '/' . $user);
+ }
+ $offset += $limit;
+ } while (count($users) >= $limit);
+ }
+
+ if ($result) {
+ $output->writeln('All scanned files are properly encrypted.');
+ return 0;
+ }
+
+ return 1;
+ }
+
+ private function scanFolder(OutputInterface $output, string $folder): bool {
+ $clean = true;
+
+ foreach ($this->rootView->getDirectoryContent($folder) as $item) {
+ $path = $folder . '/' . $item['name'];
+ if ($this->rootView->is_dir($path)) {
+ if ($this->scanFolder($output, $path) === false) {
+ $clean = false;
+ }
+ } else {
+ if (!$item->isEncrypted()) {
+ // ignore
+ continue;
+ }
+
+ $stats = $this->rootView->stat($path);
+ if (!isset($stats['hasHeader']) || $stats['hasHeader'] === false) {
+ $clean = false;
+ $output->writeln('<error>' . $path . ' does not have a proper header</error>');
+ } else {
+ try {
+ $legacyFileKey = $this->keyManager->getFileKey($path, null, true);
+ if ($legacyFileKey === '') {
+ $output->writeln('Got an empty legacy filekey for ' . $path . ', continuing', OutputInterface::VERBOSITY_VERBOSE);
+ continue;
+ }
+ } catch (GenericEncryptionException $e) {
+ $output->writeln('Got a decryption error for legacy filekey for ' . $path . ', continuing', OutputInterface::VERBOSITY_VERBOSE);
+ continue;
+ }
+ /* If that did not throw and filekey is not empty, a legacy filekey is used */
+ $clean = false;
+ $output->writeln($path . ' is using a legacy filekey, migrating');
+ $this->migrateSinglefile($path, $item, $output);
+ }
+ }
+ }
+
+ return $clean;
+ }
+
+ private function migrateSinglefile(string $path, FileInfo $fileInfo, OutputInterface $output): void {
+ $source = $path;
+ $target = $path . '.reencrypted.' . time();
+
+ try {
+ $this->rootView->copy($source, $target);
+ $copyResource = $this->rootView->fopen($target, 'r');
+ $sourceResource = $this->rootView->fopen($source, 'w');
+ if ($copyResource === false || $sourceResource === false) {
+ throw new DecryptionFailedException('Failed to open '.$source.' or '.$target);
+ }
+ if (stream_copy_to_stream($copyResource, $sourceResource) === false) {
+ $output->writeln('<error>Failed to copy '.$target.' data into '.$source.'</error>');
+ $output->writeln('<error>Leaving both files in there to avoid data loss</error>');
+ return;
+ }
+ $this->rootView->touch($source, $fileInfo->getMTime());
+ $this->rootView->unlink($target);
+ $output->writeln('<info>Migrated ' . $source . '</info>', OutputInterface::VERBOSITY_VERBOSE);
+ } catch (DecryptionFailedException $e) {
+ if ($this->rootView->file_exists($target)) {
+ $this->rootView->unlink($target);
+ }
+ $output->writeln('<error>Failed to migrate ' . $path . '</error>');
+ $output->writeln('<error>' . $e . '</error>', OutputInterface::VERBOSITY_VERBOSE);
+ } finally {
+ if (is_resource($copyResource)) {
+ fclose($copyResource);
+ }
+ if (is_resource($sourceResource)) {
+ fclose($sourceResource);
+ }
+ }
+ }
+
+ /**
+ * setup user file system
+ */
+ protected function setupUserFS(string $uid): void {
+ \OC_Util::tearDownFS();
+ \OC_Util::setupFS($uid);
+ }
+}
diff --git a/apps/encryption/lib/Command/ScanLegacyFormat.php b/apps/encryption/lib/Command/ScanLegacyFormat.php
index dc6d43ee5b8..85a99a17845 100644
--- a/apps/encryption/lib/Command/ScanLegacyFormat.php
+++ b/apps/encryption/lib/Command/ScanLegacyFormat.php
@@ -36,7 +36,6 @@ use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class ScanLegacyFormat extends Command {
-
/** @var Util */
protected $util;
@@ -89,7 +88,7 @@ class ScanLegacyFormat extends Command {
foreach ($users as $user) {
$output->writeln('Scanning all files for ' . $user);
$this->setupUserFS($user);
- $result &= $this->scanFolder($output, '/' . $user);
+ $result = $result && $this->scanFolder($output, '/' . $user);
}
$offset += $limit;
} while (count($users) >= $limit);
diff --git a/apps/encryption/lib/Crypto/Encryption.php b/apps/encryption/lib/Crypto/Encryption.php
index 465856fd28d..0bcaa167907 100644
--- a/apps/encryption/lib/Crypto/Encryption.php
+++ b/apps/encryption/lib/Crypto/Encryption.php
@@ -309,7 +309,12 @@ class Encryption implements IEncryptionModule {
$publicKeys = $this->keyManager->addSystemKeys($this->accessList, $publicKeys, $this->getOwner($path));
$shareKeys = $this->crypt->multiKeyEncrypt($this->fileKey, $publicKeys);
- $this->keyManager->deleteLegacyFileKey($this->path);
+ if (!$this->keyManager->deleteLegacyFileKey($this->path)) {
+ $this->logger->warning(
+ 'Failed to delete legacy filekey for {path}',
+ ['app' => 'encryption', 'path' => $path]
+ );
+ }
foreach ($shareKeys as $uid => $keyFile) {
$this->keyManager->setShareKey($this->path, $uid, $keyFile);
}