diff options
Diffstat (limited to 'apps/encryption/lib')
32 files changed, 757 insertions, 1545 deletions
diff --git a/apps/encryption/lib/AppInfo/Application.php b/apps/encryption/lib/AppInfo/Application.php index 7d794ab15a6..b1bf93b9dea 100644 --- a/apps/encryption/lib/AppInfo/Application.php +++ b/apps/encryption/lib/AppInfo/Application.php @@ -1,92 +1,111 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Bjoern Schiessle <bjoern@schiessle.org> - * @author Björn Schießle <bjoern@schiessle.org> - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Clark Tomlinson <fallen013@gmail.com> - * @author Morris Jobke <hey@morrisjobke.de> - * @author Thomas Müller <thomas.mueller@tmit.eu> - * - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * 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, version 3, - * along with this program. If not, see <http://www.gnu.org/licenses/> - * + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Encryption\AppInfo; +use OC\Core\Events\BeforePasswordResetEvent; +use OC\Core\Events\PasswordResetEvent; use OCA\Encryption\Crypto\Crypt; use OCA\Encryption\Crypto\DecryptAll; use OCA\Encryption\Crypto\EncryptAll; use OCA\Encryption\Crypto\Encryption; -use OCA\Encryption\HookManager; -use OCA\Encryption\Hooks\UserHooks; use OCA\Encryption\KeyManager; -use OCA\Encryption\Recovery; +use OCA\Encryption\Listeners\UserEventsListener; use OCA\Encryption\Session; use OCA\Encryption\Users\Setup; use OCA\Encryption\Util; +use OCP\AppFramework\App; +use OCP\AppFramework\Bootstrap\IBootContext; +use OCP\AppFramework\Bootstrap\IBootstrap; +use OCP\AppFramework\Bootstrap\IRegistrationContext; use OCP\Encryption\IManager; +use OCP\EventDispatcher\IEventDispatcher; use OCP\IConfig; +use OCP\IL10N; +use OCP\IUserSession; +use OCP\User\Events\BeforePasswordUpdatedEvent; +use OCP\User\Events\PasswordUpdatedEvent; +use OCP\User\Events\UserCreatedEvent; +use OCP\User\Events\UserDeletedEvent; +use OCP\User\Events\UserLoggedInEvent; +use OCP\User\Events\UserLoggedInWithCookieEvent; +use OCP\User\Events\UserLoggedOutEvent; use Psr\Log\LoggerInterface; -class Application extends \OCP\AppFramework\App { - /** - * @param array $urlParams - */ - public function __construct($urlParams = []) { - parent::__construct('encryption', $urlParams); +class Application extends App implements IBootstrap { + public const APP_ID = 'encryption'; + + public function __construct(array $urlParams = []) { + parent::__construct(self::APP_ID, $urlParams); + } + + public function register(IRegistrationContext $context): void { + } + + public function boot(IBootContext $context): void { + \OCP\Util::addScript(self::APP_ID, 'encryption'); + + $context->injectFn(function (IManager $encryptionManager) use ($context): void { + if (!($encryptionManager instanceof \OC\Encryption\Manager)) { + return; + } + + if (!$encryptionManager->isReady()) { + return; + } + + $context->injectFn($this->registerEncryptionModule(...)); + $context->injectFn($this->registerEventListeners(...)); + $context->injectFn($this->setUp(...)); + }); } public function setUp(IManager $encryptionManager) { if ($encryptionManager->isEnabled()) { /** @var Setup $setup */ - $setup = $this->getContainer()->query(Setup::class); + $setup = $this->getContainer()->get(Setup::class); $setup->setupSystem(); } } - /** - * register hooks - */ - public function registerHooks(IConfig $config) { - if (!$config->getSystemValueBool('maintenance')) { - $container = $this->getContainer(); - $server = $container->getServer(); - // Register our hooks and fire them. - $hookManager = new HookManager(); - - $hookManager->registerHook([ - new UserHooks($container->query(KeyManager::class), - $server->getUserManager(), - $server->get(LoggerInterface::class), - $container->query(Setup::class), - $server->getUserSession(), - $container->query(Util::class), - $container->query(Session::class), - $container->query(Crypt::class), - $container->query(Recovery::class)) - ]); + public function registerEventListeners( + IConfig $config, + IEventDispatcher $eventDispatcher, + IManager $encryptionManager, + Util $util, + ): void { + if (!$encryptionManager->isEnabled()) { + return; + } - $hookManager->fireHooks(); - } else { + if ($config->getSystemValueBool('maintenance')) { // Logout user if we are in maintenance to force re-login - $this->getContainer()->getServer()->getUserSession()->logout(); + $this->getContainer()->get(IUserSession::class)->logout(); + return; + } + + // No maintenance so register all events + $eventDispatcher->addServiceListener(UserLoggedInEvent::class, UserEventsListener::class); + $eventDispatcher->addServiceListener(UserLoggedInWithCookieEvent::class, UserEventsListener::class); + $eventDispatcher->addServiceListener(UserLoggedOutEvent::class, UserEventsListener::class); + if (!$util->isMasterKeyEnabled()) { + // Only make sense if no master key is used + $eventDispatcher->addServiceListener(UserCreatedEvent::class, UserEventsListener::class); + $eventDispatcher->addServiceListener(UserDeletedEvent::class, UserEventsListener::class); + $eventDispatcher->addServiceListener(BeforePasswordUpdatedEvent::class, UserEventsListener::class); + $eventDispatcher->addServiceListener(PasswordUpdatedEvent::class, UserEventsListener::class); + $eventDispatcher->addServiceListener(BeforePasswordResetEvent::class, UserEventsListener::class); + $eventDispatcher->addServiceListener(PasswordResetEvent::class, UserEventsListener::class); } } - public function registerEncryptionModule(IManager $encryptionManager) { + public function registerEncryptionModule( + IManager $encryptionManager, + ) { $container = $this->getContainer(); $encryptionManager->registerEncryptionModule( @@ -94,14 +113,14 @@ class Application extends \OCP\AppFramework\App { Encryption::DISPLAY_NAME, function () use ($container) { return new Encryption( - $container->query(Crypt::class), - $container->query(KeyManager::class), - $container->query(Util::class), - $container->query(Session::class), - $container->query(EncryptAll::class), - $container->query(DecryptAll::class), - $container->getServer()->get(LoggerInterface::class), - $container->getServer()->getL10N($container->getAppName()) + $container->get(Crypt::class), + $container->get(KeyManager::class), + $container->get(Util::class), + $container->get(Session::class), + $container->get(EncryptAll::class), + $container->get(DecryptAll::class), + $container->get(LoggerInterface::class), + $container->get(IL10N::class), ); }); } diff --git a/apps/encryption/lib/Command/DisableMasterKey.php b/apps/encryption/lib/Command/DisableMasterKey.php index 5a07e294248..0b8b8e39e78 100644 --- a/apps/encryption/lib/Command/DisableMasterKey.php +++ b/apps/encryption/lib/Command/DisableMasterKey.php @@ -1,26 +1,8 @@ <?php + /** - * @copyright Copyright (c) 2017 Bjoern Schiessle <bjoern@schiessle.org> - * - * @author Bjoern Schiessle <bjoern@schiessle.org> - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Joas Schilling <coding@schilljs.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/>. - * + * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCA\Encryption\Command; @@ -33,31 +15,15 @@ use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Question\ConfirmationQuestion; class DisableMasterKey extends Command { - - /** @var Util */ - protected $util; - - /** @var IConfig */ - protected $config; - - /** @var QuestionHelper */ - protected $questionHelper; - - /** - * @param Util $util - * @param IConfig $config - * @param QuestionHelper $questionHelper - */ - public function __construct(Util $util, - IConfig $config, - QuestionHelper $questionHelper) { - $this->util = $util; - $this->config = $config; - $this->questionHelper = $questionHelper; + public function __construct( + protected Util $util, + protected IConfig $config, + protected QuestionHelper $questionHelper, + ) { parent::__construct(); } - protected function configure() { + protected function configure(): void { $this ->setName('encryption:disable-master-key') ->setDescription('Disable the master key and use per-user keys instead. Only available for fresh installations with no existing encrypted data! There is no way to enable it again.'); @@ -68,21 +34,23 @@ class DisableMasterKey extends Command { if (!$isMasterKeyEnabled) { $output->writeln('Master key already disabled'); - } else { - $question = new ConfirmationQuestion( - 'Warning: Only perform this operation for a fresh installations with no existing encrypted data! ' - . 'There is no way to enable the master key again. ' - . 'We strongly recommend to keep the master key, it provides significant performance improvements ' - . 'and is easier to handle for both, users and administrators. ' - . 'Do you really want to switch to per-user keys? (y/n) ', false); - if ($this->questionHelper->ask($input, $output, $question)) { - $this->config->setAppValue('encryption', 'useMasterKey', '0'); - $output->writeln('Master key successfully disabled.'); - } else { - $output->writeln('aborted.'); - return 1; - } + return self::SUCCESS; + } + + $question = new ConfirmationQuestion( + 'Warning: Only perform this operation for a fresh installations with no existing encrypted data! ' + . 'There is no way to enable the master key again. ' + . 'We strongly recommend to keep the master key, it provides significant performance improvements ' + . 'and is easier to handle for both, users and administrators. ' + . 'Do you really want to switch to per-user keys? (y/n) ', false); + + if ($this->questionHelper->ask($input, $output, $question)) { + $this->config->setAppValue('encryption', 'useMasterKey', '0'); + $output->writeln('Master key successfully disabled.'); + return self::SUCCESS; } - return 0; + + $output->writeln('aborted.'); + return self::FAILURE; } } diff --git a/apps/encryption/lib/Command/DropLegacyFileKey.php b/apps/encryption/lib/Command/DropLegacyFileKey.php index f0a5f36f30f..a9add1ad93b 100644 --- a/apps/encryption/lib/Command/DropLegacyFileKey.php +++ b/apps/encryption/lib/Command/DropLegacyFileKey.php @@ -3,25 +3,8 @@ 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/>. - * + * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCA\Encryption\Command; @@ -75,10 +58,10 @@ class DropLegacyFileKey extends Command { if ($result) { $output->writeln('All scanned files are properly encrypted.'); - return 0; + return self::SUCCESS; } - return 1; + return self::FAILURE; } private function scanFolder(OutputInterface $output, string $folder): bool { @@ -131,10 +114,10 @@ class DropLegacyFileKey extends Command { $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); + 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>Failed to copy ' . $target . ' data into ' . $source . '</error>'); $output->writeln('<error>Leaving both files in there to avoid data loss</error>'); return; } diff --git a/apps/encryption/lib/Command/EnableMasterKey.php b/apps/encryption/lib/Command/EnableMasterKey.php index 7c49988c7fb..0d8b893e0e2 100644 --- a/apps/encryption/lib/Command/EnableMasterKey.php +++ b/apps/encryption/lib/Command/EnableMasterKey.php @@ -1,25 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Björn Schießle <bjoern@schiessle.org> - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Joas Schilling <coding@schilljs.com> - * - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * 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, version 3, - * along with this program. If not, see <http://www.gnu.org/licenses/> - * + * SPDX-FileCopyrightText: 2019-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Encryption\Command; @@ -32,31 +16,15 @@ use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Question\ConfirmationQuestion; class EnableMasterKey extends Command { - - /** @var Util */ - protected $util; - - /** @var IConfig */ - protected $config; - - /** @var QuestionHelper */ - protected $questionHelper; - - /** - * @param Util $util - * @param IConfig $config - * @param QuestionHelper $questionHelper - */ - public function __construct(Util $util, - IConfig $config, - QuestionHelper $questionHelper) { - $this->util = $util; - $this->config = $config; - $this->questionHelper = $questionHelper; + public function __construct( + protected Util $util, + protected IConfig $config, + protected QuestionHelper $questionHelper, + ) { parent::__construct(); } - protected function configure() { + protected function configure(): void { $this ->setName('encryption:enable-master-key') ->setDescription('Enable the master key. Only available for fresh installations with no existing encrypted data! There is also no way to disable it again.'); @@ -67,18 +35,20 @@ class EnableMasterKey extends Command { if ($isAlreadyEnabled) { $output->writeln('Master key already enabled'); - } else { - $question = new ConfirmationQuestion( - 'Warning: Only available for fresh installations with no existing encrypted data! ' + return self::SUCCESS; + } + + $question = new ConfirmationQuestion( + 'Warning: Only available for fresh installations with no existing encrypted data! ' . 'There is also no way to disable it again. Do you want to continue? (y/n) ', false); - if ($this->questionHelper->ask($input, $output, $question)) { - $this->config->setAppValue('encryption', 'useMasterKey', '1'); - $output->writeln('Master key successfully enabled.'); - } else { - $output->writeln('aborted.'); - return 1; - } + + if ($this->questionHelper->ask($input, $output, $question)) { + $this->config->setAppValue('encryption', 'useMasterKey', '1'); + $output->writeln('Master key successfully enabled.'); + return self::SUCCESS; } - return 0; + + $output->writeln('aborted.'); + return self::FAILURE; } } diff --git a/apps/encryption/lib/Command/FixEncryptedVersion.php b/apps/encryption/lib/Command/FixEncryptedVersion.php index d0fbf1adb31..462e3a5cc2a 100644 --- a/apps/encryption/lib/Command/FixEncryptedVersion.php +++ b/apps/encryption/lib/Command/FixEncryptedVersion.php @@ -1,23 +1,9 @@ <?php + /** - * @author Sujith Haridasan <sharidasan@owncloud.com> - * @author Ilja Neumann <ineumann@owncloud.com> - * - * @copyright Copyright (c) 2019, ownCloud GmbH - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * 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, version 3, - * along with this program. If not, see <http://www.gnu.org/licenses/> - * + * SPDX-FileCopyrightText: 2021-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2019 ownCloud GmbH + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Encryption\Command; @@ -26,6 +12,7 @@ use OC\Files\Storage\Wrapper\Encryption; use OC\Files\View; use OC\ServerNotAvailableException; use OCA\Encryption\Util; +use OCP\Encryption\Exceptions\InvalidHeaderException; use OCP\Files\IRootFolder; use OCP\HintException; use OCP\IConfig; @@ -39,7 +26,7 @@ use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; class FixEncryptedVersion extends Command { - private bool $supportLegacy; + private bool $supportLegacy = false; public function __construct( private IConfig $config, @@ -49,8 +36,6 @@ class FixEncryptedVersion extends Command { private Util $util, private View $view, ) { - $this->supportLegacy = false; - parent::__construct(); } @@ -83,47 +68,49 @@ class FixEncryptedVersion extends Command { if ($skipSignatureCheck) { $output->writeln("<error>Repairing is not possible when \"encryption_skip_signature_check\" is set. Please disable this flag in the configuration.</error>\n"); - return 1; + return self::FAILURE; } if (!$this->util->isMasterKeyEnabled()) { $output->writeln("<error>Repairing only works with master key encryption.</error>\n"); - return 1; + return self::FAILURE; } $user = $input->getArgument('user'); $all = $input->getOption('all'); $pathOption = \trim(($input->getOption('path') ?? ''), '/'); + if (!$user && !$all) { + $output->writeln('Either a user id or --all needs to be provided'); + return self::FAILURE; + } + if ($user) { if ($all) { - $output->writeln("Specifying a user id and --all are mutually exclusive"); - return 1; + $output->writeln('Specifying a user id and --all are mutually exclusive'); + return self::FAILURE; } if ($this->userManager->get($user) === null) { $output->writeln("<error>User id $user does not exist. Please provide a valid user id</error>"); - return 1; + return self::FAILURE; } return $this->runForUser($user, $pathOption, $output); - } elseif ($all) { - $result = 0; - $this->userManager->callForSeenUsers(function (IUser $user) use ($pathOption, $output, &$result) { - $output->writeln("Processing files for " . $user->getUID()); - $result = $this->runForUser($user->getUID(), $pathOption, $output); - return $result === 0; - }); - return $result; - } else { - $output->writeln("Either a user id or --all needs to be provided"); - return 1; } + + $result = 0; + $this->userManager->callForSeenUsers(function (IUser $user) use ($pathOption, $output, &$result) { + $output->writeln('Processing files for ' . $user->getUID()); + $result = $this->runForUser($user->getUID(), $pathOption, $output); + return $result === 0; + }); + return $result; } private function runForUser(string $user, string $pathOption, OutputInterface $output): int { $pathToWalk = "/$user/files"; - if ($pathOption !== "") { + if ($pathOption !== '') { $pathToWalk = "$pathToWalk/$pathOption"; } return $this->walkPathOfUser($user, $pathToWalk, $output); @@ -136,13 +123,13 @@ class FixEncryptedVersion extends Command { $this->setupUserFs($user); if (!$this->view->file_exists($path)) { $output->writeln("<error>Path \"$path\" does not exist. Please provide a valid path.</error>"); - return 1; + return self::FAILURE; } if ($this->view->is_file($path)) { $output->writeln("Verifying the content of file \"$path\""); $this->verifyFileContent($path, $output); - return 0; + return self::SUCCESS; } $directories = []; $directories[] = $path; @@ -158,7 +145,7 @@ class FixEncryptedVersion extends Command { } } } - return 0; + return self::SUCCESS; } /** @@ -210,7 +197,7 @@ class FixEncryptedVersion extends Command { \fclose($handle); return true; - } catch (ServerNotAvailableException $e) { + } catch (ServerNotAvailableException|InvalidHeaderException $e) { // not a "bad signature" error and likely "legacy cipher" exception // this could mean that the file is maybe not encrypted but the encrypted version is set if (!$this->supportLegacy && $ignoreCorrectEncVersionCall === true) { @@ -219,7 +206,7 @@ class FixEncryptedVersion extends Command { } return false; } catch (HintException $e) { - $this->logger->warning("Issue: " . $e->getMessage()); + $this->logger->warning('Issue: ' . $e->getMessage()); // If allowOnce is set to false, this becomes recursive. if ($ignoreCorrectEncVersionCall === true) { // Lets rectify the file by correcting encrypted version @@ -268,7 +255,7 @@ class FixEncryptedVersion extends Command { // try with zero first $cacheInfo = ['encryptedVersion' => 0, 'encrypted' => 0]; $cache->put($fileCache->getPath(), $cacheInfo); - $output->writeln("<info>Set the encrypted version to 0 (unencrypted)</info>"); + $output->writeln('<info>Set the encrypted version to 0 (unencrypted)</info>'); if ($this->verifyFileContent($path, $output, false) === true) { $output->writeln("<info>Fixed the file: \"$path\" with version 0 (unencrypted)</info>"); return true; @@ -282,7 +269,7 @@ class FixEncryptedVersion extends Command { $cache->put($fileCache->getPath(), $cacheInfo); $output->writeln("<info>Decrement the encrypted version to $encryptedVersion</info>"); if ($this->verifyFileContent($path, $output, false) === true) { - $output->writeln("<info>Fixed the file: \"$path\" with version " . $encryptedVersion . "</info>"); + $output->writeln("<info>Fixed the file: \"$path\" with version " . $encryptedVersion . '</info>'); return true; } $encryptedVersion--; @@ -305,7 +292,7 @@ class FixEncryptedVersion extends Command { $cache->put($fileCache->getPath(), $cacheInfo); $output->writeln("<info>Increment the encrypted version to $newEncryptedVersion</info>"); if ($this->verifyFileContent($path, $output, false) === true) { - $output->writeln("<info>Fixed the file: \"$path\" with version " . $newEncryptedVersion . "</info>"); + $output->writeln("<info>Fixed the file: \"$path\" with version " . $newEncryptedVersion . '</info>'); return true; } $increment++; diff --git a/apps/encryption/lib/Command/FixKeyLocation.php b/apps/encryption/lib/Command/FixKeyLocation.php index 5001da4bb92..da529a4be2f 100644 --- a/apps/encryption/lib/Command/FixKeyLocation.php +++ b/apps/encryption/lib/Command/FixKeyLocation.php @@ -2,23 +2,8 @@ declare(strict_types=1); /** - * @copyright Copyright (c) 2022 Robin Appelman <robin@icewind.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/>. - * + * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCA\Encryption\Command; @@ -43,29 +28,21 @@ use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; class FixKeyLocation extends Command { - private IUserManager $userManager; - private IUserMountCache $userMountCache; - private Util $encryptionUtil; - private IRootFolder $rootFolder; private string $keyRootDirectory; private View $rootView; private Manager $encryptionManager; public function __construct( - IUserManager $userManager, - IUserMountCache $userMountCache, - Util $encryptionUtil, - IRootFolder $rootFolder, - IManager $encryptionManager + private IUserManager $userManager, + private IUserMountCache $userMountCache, + private Util $encryptionUtil, + private IRootFolder $rootFolder, + IManager $encryptionManager, ) { - $this->userManager = $userManager; - $this->userMountCache = $userMountCache; - $this->encryptionUtil = $encryptionUtil; - $this->rootFolder = $rootFolder; $this->keyRootDirectory = rtrim($this->encryptionUtil->getKeyStorageRoot(), '/'); $this->rootView = new View(); if (!$encryptionManager instanceof Manager) { - throw new \Exception("Wrong encryption manager"); + throw new \Exception('Wrong encryption manager'); } $this->encryptionManager = $encryptionManager; @@ -80,7 +57,7 @@ class FixKeyLocation extends Command { ->setName('encryption:fix-key-location') ->setDescription('Fix the location of encryption keys for external storage') ->addOption('dry-run', null, InputOption::VALUE_NONE, "Only list files that require key migration, don't try to perform any migration") - ->addArgument('user', InputArgument::REQUIRED, "User id to fix the key locations for"); + ->addArgument('user', InputArgument::REQUIRED, 'User id to fix the key locations for'); } protected function execute(InputInterface $input, OutputInterface $output): int { @@ -89,7 +66,7 @@ class FixKeyLocation extends Command { $user = $this->userManager->get($userId); if (!$user) { $output->writeln("<error>User $userId not found</error>"); - return 1; + return self::FAILURE; } \OC_Util::setupFS($user->getUID()); @@ -98,7 +75,7 @@ class FixKeyLocation extends Command { foreach ($mounts as $mount) { $mountRootFolder = $this->rootFolder->get($mount->getMountPoint()); if (!$mountRootFolder instanceof Folder) { - $output->writeln("<error>System wide mount point is not a directory, skipping: " . $mount->getMountPoint() . "</error>"); + $output->writeln('<error>System wide mount point is not a directory, skipping: ' . $mount->getMountPoint() . '</error>'); continue; } @@ -112,14 +89,14 @@ class FixKeyLocation extends Command { // key was stored incorrectly as user key, migrate if ($dryRun) { - $output->writeln("<info>" . $file->getPath() . "</info> needs migration"); + $output->writeln('<info>' . $file->getPath() . '</info> needs migration'); } else { - $output->write("Migrating key for <info>" . $file->getPath() . "</info> "); + $output->write('Migrating key for <info>' . $file->getPath() . '</info> '); if ($this->copyUserKeyToSystemAndValidate($user, $file)) { - $output->writeln("<info>✓</info>"); + $output->writeln('<info>✓</info>'); } else { - $output->writeln("<fg=red>❌</>"); - $output->writeln(" Failed to validate key for <error>" . $file->getPath() . "</error>, key will not be migrated"); + $output->writeln('<fg=red>❌</>'); + $output->writeln(' Failed to validate key for <error>' . $file->getPath() . '</error>, key will not be migrated'); } } } else { @@ -130,42 +107,42 @@ class FixKeyLocation extends Command { if ($isActuallyEncrypted) { if ($dryRun) { if ($shouldBeEncrypted) { - $output->write("<info>" . $file->getPath() . "</info> needs migration"); + $output->write('<info>' . $file->getPath() . '</info> needs migration'); } else { - $output->write("<info>" . $file->getPath() . "</info> needs decryption"); + $output->write('<info>' . $file->getPath() . '</info> needs decryption'); } $foundKey = $this->findUserKeyForSystemFile($user, $file); if ($foundKey) { - $output->writeln(", valid key found at <info>" . $foundKey . "</info>"); + $output->writeln(', valid key found at <info>' . $foundKey . '</info>'); } else { - $output->writeln(" <error>❌ No key found</error>"); + $output->writeln(' <error>❌ No key found</error>'); } } else { if ($shouldBeEncrypted) { - $output->write("<info>Migrating key for " . $file->getPath() . "</info>"); + $output->write('<info>Migrating key for ' . $file->getPath() . '</info>'); } else { - $output->write("<info>Decrypting " . $file->getPath() . "</info>"); + $output->write('<info>Decrypting ' . $file->getPath() . '</info>'); } $foundKey = $this->findUserKeyForSystemFile($user, $file); if ($foundKey) { if ($shouldBeEncrypted) { $systemKeyPath = $this->getSystemKeyPath($file); $this->rootView->copy($foundKey, $systemKeyPath); - $output->writeln(" Migrated key from <info>" . $foundKey . "</info>"); + $output->writeln(' Migrated key from <info>' . $foundKey . '</info>'); } else { $this->decryptWithSystemKey($file, $foundKey); - $output->writeln(" Decrypted with key from <info>" . $foundKey . "</info>"); + $output->writeln(' Decrypted with key from <info>' . $foundKey . '</info>'); } } else { - $output->writeln(" <error>❌ No key found</error>"); + $output->writeln(' <error>❌ No key found</error>'); } } } else { if ($dryRun) { - $output->writeln("<info>" . $file->getPath() . " needs to be marked as not encrypted</info>"); + $output->writeln('<info>' . $file->getPath() . ' needs to be marked as not encrypted</info>'); } else { $this->markAsUnEncrypted($file); - $output->writeln("<info>" . $file->getPath() . " marked as not encrypted</info>"); + $output->writeln('<info>' . $file->getPath() . ' marked as not encrypted</info>'); } } } @@ -173,7 +150,7 @@ class FixKeyLocation extends Command { } } - return 0; + return self::SUCCESS; } private function getUserRelativePath(string $path): string { @@ -186,7 +163,6 @@ class FixKeyLocation extends Command { } /** - * @param IUser $user * @return ICachedMountInfo[] */ private function getSystemMountsForUser(IUser $user): array { @@ -201,7 +177,6 @@ class FixKeyLocation extends Command { /** * Get all files in a folder which are marked as encrypted * - * @param Folder $folder * @return \Generator<File> */ private function getAllEncryptedFiles(Folder $folder) { @@ -242,10 +217,6 @@ class FixKeyLocation extends Command { /** * Check that the user key stored for a file can decrypt the file - * - * @param IUser $user - * @param File $node - * @return bool */ private function copyUserKeyToSystemAndValidate(IUser $user, File $node): bool { $path = trim(substr($node->getPath(), strlen($user->getUID()) + 1), '/'); @@ -282,7 +253,6 @@ class FixKeyLocation extends Command { /** * Get the contents of a file without decrypting it * - * @param File $node * @return resource */ private function openWithoutDecryption(File $node, string $mode) { @@ -303,16 +273,13 @@ class FixKeyLocation extends Command { } /** @var resource|false $handle */ if ($handle === false) { - throw new \Exception("Failed to open " . $node->getPath()); + throw new \Exception('Failed to open ' . $node->getPath()); } return $handle; } /** * Check if the data stored for a file is encrypted, regardless of it's metadata - * - * @param File $node - * @return bool */ private function isDataEncrypted(File $node): bool { $handle = $this->openWithoutDecryption($node, 'r'); @@ -325,9 +292,6 @@ class FixKeyLocation extends Command { /** * Attempt to find a key (stored for user) for a file (that needs a system key) even when it's not stored in the expected location - * - * @param File $node - * @return string */ private function findUserKeyForSystemFile(IUser $user, File $node): ?string { $userKeyPath = $this->getUserBaseKeyPath($user); @@ -343,8 +307,6 @@ class FixKeyLocation extends Command { /** * Attempt to find a key for a file even when it's not stored in the expected location * - * @param string $basePath - * @param string $name * @return \Generator<string> */ private function findKeysByFileName(string $basePath, string $name) { @@ -354,7 +316,7 @@ class FixKeyLocation extends Command { /** @var false|resource $dh */ $dh = $this->rootView->opendir($basePath); if (!$dh) { - throw new \Exception("Invalid base path " . $basePath); + throw new \Exception('Invalid base path ' . $basePath); } while ($child = readdir($dh)) { if ($child != '..' && $child != '.') { @@ -371,11 +333,6 @@ class FixKeyLocation extends Command { /** * Test if the provided key is valid as a system key for the file - * - * @param IUser $user - * @param string $key - * @param File $node - * @return bool */ private function testSystemKey(IUser $user, string $key, File $node): bool { $systemKeyPath = $this->getSystemKeyPath($node); @@ -393,10 +350,6 @@ class FixKeyLocation extends Command { /** * Decrypt a file with the specified system key and mark the key as not-encrypted - * - * @param File $node - * @param string $key - * @return void */ private function decryptWithSystemKey(File $node, string $key): void { $storage = $node->getStorage(); @@ -413,7 +366,7 @@ class FixKeyLocation extends Command { /** @var false|resource $source */ $source = $storage->fopen($node->getInternalPath(), 'r'); if (!$source) { - throw new \Exception("Failed to open " . $node->getPath() . " with " . $key); + throw new \Exception('Failed to open ' . $node->getPath() . ' with ' . $key); } $decryptedNode = $node->getParent()->newFile($name); @@ -433,7 +386,7 @@ class FixKeyLocation extends Command { } if ($this->isDataEncrypted($decryptedNode)) { - throw new \Exception($node->getPath() . " still encrypted after attempting to decrypt with " . $key); + throw new \Exception($node->getPath() . ' still encrypted after attempting to decrypt with ' . $key); } $this->markAsUnEncrypted($decryptedNode); diff --git a/apps/encryption/lib/Command/RecoverUser.php b/apps/encryption/lib/Command/RecoverUser.php index 97a26b1d404..8da962ac8b1 100644 --- a/apps/encryption/lib/Command/RecoverUser.php +++ b/apps/encryption/lib/Command/RecoverUser.php @@ -1,26 +1,8 @@ <?php + /** - * @copyright Copyright (c) 2018 Bjoern Schiessle <bjoern@schiessle.org> - * - * @author Bjoern Schiessle <bjoern@schiessle.org> - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Joas Schilling <coding@schilljs.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/>. - * + * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCA\Encryption\Command; @@ -35,33 +17,16 @@ use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Question\Question; class RecoverUser extends Command { - - /** @var Util */ - protected $util; - - /** @var IUserManager */ - protected $userManager; - - /** @var QuestionHelper */ - protected $questionHelper; - - /** - * @param Util $util - * @param IConfig $config - * @param IUserManager $userManager - * @param QuestionHelper $questionHelper - */ - public function __construct(Util $util, + public function __construct( + protected Util $util, IConfig $config, - IUserManager $userManager, - QuestionHelper $questionHelper) { - $this->util = $util; - $this->questionHelper = $questionHelper; - $this->userManager = $userManager; + protected IUserManager $userManager, + protected QuestionHelper $questionHelper, + ) { parent::__construct(); } - protected function configure() { + protected function configure(): void { $this ->setName('encryption:recover-user') ->setDescription('Recover user data in case of password lost. This only works if the user enabled the recovery key.'); @@ -78,20 +43,20 @@ class RecoverUser extends Command { if ($isMasterKeyEnabled) { $output->writeln('You use the master key, no individual user recovery needed.'); - return 0; + return self::SUCCESS; } $uid = $input->getArgument('user'); $userExists = $this->userManager->userExists($uid); if ($userExists === false) { $output->writeln('User "' . $uid . '" unknown.'); - return 1; + return self::FAILURE; } $recoveryKeyEnabled = $this->util->isRecoveryEnabledForUser($uid); if ($recoveryKeyEnabled === false) { $output->writeln('Recovery key is not enabled for: ' . $uid); - return 1; + return self::FAILURE; } $question = new Question('Please enter the recovery key password: '); @@ -107,6 +72,6 @@ class RecoverUser extends Command { $output->write('Start to recover users files... This can take some time...'); $this->userManager->get($uid)->setPassword($newLoginPassword, $recoveryPassword); $output->writeln('Done.'); - return 0; + return self::SUCCESS; } } diff --git a/apps/encryption/lib/Command/ScanLegacyFormat.php b/apps/encryption/lib/Command/ScanLegacyFormat.php index 8e0178af486..1e46a3d7545 100644 --- a/apps/encryption/lib/Command/ScanLegacyFormat.php +++ b/apps/encryption/lib/Command/ScanLegacyFormat.php @@ -3,26 +3,8 @@ declare(strict_types=1); /** - * @copyright Copyright (c) 2020, Roeland Jago Douma <roeland@famdouma.nl> - * - * @author essys <essys@users.noreply.github.com> - * @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/>. - * + * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCA\Encryption\Command; @@ -36,40 +18,20 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; class ScanLegacyFormat extends Command { - /** @var Util */ - protected $util; - - /** @var IConfig */ - protected $config; - - /** @var QuestionHelper */ - protected $questionHelper; - - /** @var IUserManager */ - private $userManager; - - /** @var View */ - private $rootView; - - /** - * @param Util $util - * @param IConfig $config - * @param QuestionHelper $questionHelper - */ - public function __construct(Util $util, - IConfig $config, - QuestionHelper $questionHelper, - IUserManager $userManager) { + private View $rootView; + + public function __construct( + protected Util $util, + protected IConfig $config, + protected QuestionHelper $questionHelper, + private IUserManager $userManager, + ) { parent::__construct(); - $this->util = $util; - $this->config = $config; - $this->questionHelper = $questionHelper; - $this->userManager = $userManager; $this->rootView = new View(); } - protected function configure() { + protected function configure(): void { $this ->setName('encryption:scan:legacy-format') ->setDescription('Scan the files for the legacy format'); @@ -96,10 +58,10 @@ class ScanLegacyFormat extends Command { if ($result) { $output->writeln('All scanned files are properly encrypted. You can disable the legacy compatibility mode.'); - return 0; + return self::SUCCESS; } - return 1; + return self::FAILURE; } private function scanFolder(OutputInterface $output, string $folder): bool { @@ -130,10 +92,8 @@ class ScanLegacyFormat extends Command { /** * setup user file system - * - * @param string $uid */ - protected function setupUserFS($uid) { + protected function setupUserFS(string $uid): void { \OC_Util::tearDownFS(); \OC_Util::setupFS($uid); } diff --git a/apps/encryption/lib/Controller/RecoveryController.php b/apps/encryption/lib/Controller/RecoveryController.php index 743e17e5eff..d75406e6319 100644 --- a/apps/encryption/lib/Controller/RecoveryController.php +++ b/apps/encryption/lib/Controller/RecoveryController.php @@ -1,34 +1,16 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Björn Schießle <bjoern@schiessle.org> - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Clark Tomlinson <fallen013@gmail.com> - * @author Lukas Reschke <lukas@statuscode.ch> - * @author Morris Jobke <hey@morrisjobke.de> - * @author Roeland Jago Douma <roeland@famdouma.nl> - * - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * 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, version 3, - * along with this program. If not, see <http://www.gnu.org/licenses/> - * + * SPDX-FileCopyrightText: 2019-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Encryption\Controller; use OCA\Encryption\Recovery; use OCP\AppFramework\Controller; use OCP\AppFramework\Http; +use OCP\AppFramework\Http\Attribute\NoAdminRequired; use OCP\AppFramework\Http\DataResponse; use OCP\IConfig; use OCP\IL10N; @@ -36,34 +18,20 @@ use OCP\IRequest; class RecoveryController extends Controller { /** - * @var IConfig - */ - private $config; - /** - * @var IL10N - */ - private $l; - /** - * @var Recovery - */ - private $recovery; - - /** * @param string $AppName * @param IRequest $request * @param IConfig $config - * @param IL10N $l10n + * @param IL10N $l * @param Recovery $recovery */ - public function __construct($AppName, + public function __construct( + $AppName, IRequest $request, - IConfig $config, - IL10N $l10n, - Recovery $recovery) { + private IConfig $config, + private IL10N $l, + private Recovery $recovery, + ) { parent::__construct($AppName, $request); - $this->config = $config; - $this->l = $l10n; - $this->recovery = $recovery; } /** @@ -155,11 +123,10 @@ class RecoveryController extends Controller { } /** - * @NoAdminRequired - * * @param string $userEnableRecovery * @return DataResponse */ + #[NoAdminRequired] public function userSetRecovery($userEnableRecovery) { if ($userEnableRecovery === '0' || $userEnableRecovery === '1') { $result = $this->recovery->setRecoveryForUser($userEnableRecovery); diff --git a/apps/encryption/lib/Controller/SettingsController.php b/apps/encryption/lib/Controller/SettingsController.php index fa4c3d2ae2b..8548ea51c04 100644 --- a/apps/encryption/lib/Controller/SettingsController.php +++ b/apps/encryption/lib/Controller/SettingsController.php @@ -1,25 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Björn Schießle <bjoern@schiessle.org> - * @author Joas Schilling <coding@schilljs.com> - * @author Morris Jobke <hey@morrisjobke.de> - * - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * 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, version 3, - * along with this program. If not, see <http://www.gnu.org/licenses/> - * + * SPDX-FileCopyrightText: 2019-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Encryption\Controller; @@ -29,6 +13,8 @@ use OCA\Encryption\Session; use OCA\Encryption\Util; use OCP\AppFramework\Controller; use OCP\AppFramework\Http; +use OCP\AppFramework\Http\Attribute\NoAdminRequired; +use OCP\AppFramework\Http\Attribute\UseSession; use OCP\AppFramework\Http\DataResponse; use OCP\IL10N; use OCP\IRequest; @@ -38,34 +24,10 @@ use OCP\IUserSession; class SettingsController extends Controller { - /** @var IL10N */ - private $l; - - /** @var IUserManager */ - private $userManager; - - /** @var IUserSession */ - private $userSession; - - /** @var KeyManager */ - private $keyManager; - - /** @var Crypt */ - private $crypt; - - /** @var Session */ - private $session; - - /** @var ISession */ - private $ocSession; - - /** @var Util */ - private $util; - /** * @param string $AppName * @param IRequest $request - * @param IL10N $l10n + * @param IL10N $l * @param IUserManager $userManager * @param IUserSession $userSession * @param KeyManager $keyManager @@ -74,37 +36,29 @@ class SettingsController extends Controller { * @param ISession $ocSession * @param Util $util */ - public function __construct($AppName, + public function __construct( + $AppName, IRequest $request, - IL10N $l10n, - IUserManager $userManager, - IUserSession $userSession, - KeyManager $keyManager, - Crypt $crypt, - Session $session, - ISession $ocSession, - Util $util + private IL10N $l, + private IUserManager $userManager, + private IUserSession $userSession, + private KeyManager $keyManager, + private Crypt $crypt, + private Session $session, + private ISession $ocSession, + private Util $util, ) { parent::__construct($AppName, $request); - $this->l = $l10n; - $this->userSession = $userSession; - $this->userManager = $userManager; - $this->keyManager = $keyManager; - $this->crypt = $crypt; - $this->session = $session; - $this->ocSession = $ocSession; - $this->util = $util; } /** - * @NoAdminRequired - * @UseSession - * * @param string $oldPassword * @param string $newPassword * @return DataResponse */ + #[NoAdminRequired] + #[UseSession] public function updatePrivateKeyPassword($oldPassword, $newPassword) { $result = false; $uid = $this->userSession->getUser()->getUID(); @@ -153,11 +107,10 @@ class SettingsController extends Controller { } /** - * @UseSession - * * @param bool $encryptHomeStorage * @return DataResponse */ + #[UseSession] public function setEncryptHomeStorage($encryptHomeStorage) { $this->util->setEncryptHomeStorage($encryptHomeStorage); return new DataResponse(); diff --git a/apps/encryption/lib/Controller/StatusController.php b/apps/encryption/lib/Controller/StatusController.php index 29582a66ad3..341ad6bc49f 100644 --- a/apps/encryption/lib/Controller/StatusController.php +++ b/apps/encryption/lib/Controller/StatusController.php @@ -1,33 +1,15 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Bjoern Schiessle <bjoern@schiessle.org> - * @author Björn Schießle <bjoern@schiessle.org> - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Joas Schilling <coding@schilljs.com> - * @author Morris Jobke <hey@morrisjobke.de> - * @author Thomas Müller <thomas.mueller@tmit.eu> - * - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * 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, version 3, - * along with this program. If not, see <http://www.gnu.org/licenses/> - * + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Encryption\Controller; use OCA\Encryption\Session; use OCP\AppFramework\Controller; +use OCP\AppFramework\Http\Attribute\NoAdminRequired; use OCP\AppFramework\Http\DataResponse; use OCP\Encryption\IManager; use OCP\IL10N; @@ -35,38 +17,27 @@ use OCP\IRequest; class StatusController extends Controller { - /** @var IL10N */ - private $l; - - /** @var Session */ - private $session; - - /** @var IManager */ - private $encryptionManager; - /** * @param string $AppName * @param IRequest $request - * @param IL10N $l10n + * @param IL10N $l * @param Session $session * @param IManager $encryptionManager */ - public function __construct($AppName, + public function __construct( + $AppName, IRequest $request, - IL10N $l10n, - Session $session, - IManager $encryptionManager + private IL10N $l, + private Session $session, + private IManager $encryptionManager, ) { parent::__construct($AppName, $request); - $this->l = $l10n; - $this->session = $session; - $this->encryptionManager = $encryptionManager; } /** - * @NoAdminRequired * @return DataResponse */ + #[NoAdminRequired] public function getStatus() { $status = 'error'; $message = 'no valid init status'; diff --git a/apps/encryption/lib/Crypto/Crypt.php b/apps/encryption/lib/Crypto/Crypt.php index 2d212c1f055..463ca4e22bb 100644 --- a/apps/encryption/lib/Crypto/Crypt.php +++ b/apps/encryption/lib/Crypto/Crypt.php @@ -1,34 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Bjoern Schiessle <bjoern@schiessle.org> - * @author Björn Schießle <bjoern@schiessle.org> - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Clark Tomlinson <fallen013@gmail.com> - * @author Côme Chilliet <come.chilliet@nextcloud.com> - * @author Joas Schilling <coding@schilljs.com> - * @author Kevin Niehage <kevin@niehage.name> - * @author Lukas Reschke <lukas@statuscode.ch> - * @author Morris Jobke <hey@morrisjobke.de> - * @author Roeland Jago Douma <roeland@famdouma.nl> - * @author Stefan Weiberg <sweiberg@suse.com> - * @author Thomas Müller <thomas.mueller@tmit.eu> - * - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * 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, version 3, - * along with this program. If not, see <http://www.gnu.org/licenses/> - * + * SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Encryption\Crypto; @@ -108,7 +83,7 @@ class Crypt { /** * create new private/public key-pair for user * - * @return array|bool + * @return array{publicKey: string, privateKey: string}|false */ public function createKeyPair() { $res = $this->getOpenSSLPKey(); @@ -180,7 +155,7 @@ class Crypt { $this->getCipher()); // Create a signature based on the key as well as the current version - $sig = $this->createSignature($encryptedContent, $passPhrase.'_'.$version.'_'.$position); + $sig = $this->createSignature($encryptedContent, $passPhrase . '_' . $version . '_' . $position); // combine content to encrypt the IV identifier and actual IV $catFile = $this->concatIV($encryptedContent, $iv); @@ -482,7 +457,7 @@ class Crypt { if ($enforceSignature) { throw new GenericEncryptionException('Bad Signature', $this->l->t('Bad Signature')); } else { - $this->logger->info("Signature check skipped", ['app' => 'encryption']); + $this->logger->info('Signature check skipped', ['app' => 'encryption']); } } } @@ -776,7 +751,7 @@ class Crypt { $result = false; // check if RC4 is used - if (strcasecmp($cipher_algo, "rc4") === 0) { + if (strcasecmp($cipher_algo, 'rc4') === 0) { // decrypt the intermediate key with RSA if (openssl_private_decrypt($encrypted_key, $intermediate, $private_key, OPENSSL_PKCS1_PADDING)) { // decrypt the file key with the intermediate key @@ -785,7 +760,7 @@ class Crypt { $result = (strlen($output) === strlen($data)); } } else { - throw new DecryptionFailedException('Unsupported cipher '.$cipher_algo); + throw new DecryptionFailedException('Unsupported cipher ' . $cipher_algo); } return $result; @@ -801,7 +776,7 @@ class Crypt { $result = false; // check if RC4 is used - if (strcasecmp($cipher_algo, "rc4") === 0) { + if (strcasecmp($cipher_algo, 'rc4') === 0) { // make sure that there is at least one public key to use if (count($public_key) >= 1) { // generate the intermediate key @@ -832,7 +807,7 @@ class Crypt { } } } else { - throw new EncryptionFailedException('Unsupported cipher '.$cipher_algo); + throw new EncryptionFailedException('Unsupported cipher ' . $cipher_algo); } return $result; diff --git a/apps/encryption/lib/Crypto/DecryptAll.php b/apps/encryption/lib/Crypto/DecryptAll.php index e982da72086..362f43b8672 100644 --- a/apps/encryption/lib/Crypto/DecryptAll.php +++ b/apps/encryption/lib/Crypto/DecryptAll.php @@ -1,27 +1,13 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Björn Schießle <bjoern@schiessle.org> - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * 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, version 3, - * along with this program. If not, see <http://www.gnu.org/licenses/> - * + * SPDX-FileCopyrightText: 2019-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Encryption\Crypto; +use OCA\Encryption\Exceptions\PrivateKeyMissingException; use OCA\Encryption\KeyManager; use OCA\Encryption\Session; use OCA\Encryption\Util; @@ -33,21 +19,6 @@ use Symfony\Component\Console\Question\Question; class DecryptAll { - /** @var Util */ - protected $util; - - /** @var QuestionHelper */ - protected $questionHelper; - - /** @var Crypt */ - protected $crypt; - - /** @var KeyManager */ - protected $keyManager; - - /** @var Session */ - protected $session; - /** * @param Util $util * @param KeyManager $keyManager @@ -56,17 +27,12 @@ class DecryptAll { * @param QuestionHelper $questionHelper */ public function __construct( - Util $util, - KeyManager $keyManager, - Crypt $crypt, - Session $session, - QuestionHelper $questionHelper + protected Util $util, + protected KeyManager $keyManager, + protected Crypt $crypt, + protected Session $session, + protected QuestionHelper $questionHelper, ) { - $this->util = $util; - $this->keyManager = $keyManager; - $this->crypt = $crypt; - $this->session = $session; - $this->questionHelper = $questionHelper; } /** @@ -88,7 +54,7 @@ class DecryptAll { $recoveryKeyId = $this->keyManager->getRecoveryKeyId(); if (!empty($user)) { $output->writeln('You can only decrypt the users files if you know'); - $output->writeln('the users password or if he activated the recovery key.'); + $output->writeln('the users password or if they activated the recovery key.'); $output->writeln(''); $questionUseLoginPassword = new ConfirmationQuestion( 'Do you want to use the users login password to decrypt all files? (y/n) ', @@ -133,7 +99,7 @@ class DecryptAll { * @param string $user * @param string $password * @return bool|string - * @throws \OCA\Encryption\Exceptions\PrivateKeyMissingException + * @throws PrivateKeyMissingException */ protected function getPrivateKey($user, $password) { $recoveryKeyId = $this->keyManager->getRecoveryKeyId(); diff --git a/apps/encryption/lib/Crypto/EncryptAll.php b/apps/encryption/lib/Crypto/EncryptAll.php index 5420e729898..4ed75b85a93 100644 --- a/apps/encryption/lib/Crypto/EncryptAll.php +++ b/apps/encryption/lib/Crypto/EncryptAll.php @@ -1,29 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Bjoern Schiessle <bjoern@schiessle.org> - * @author Björn Schießle <bjoern@schiessle.org> - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Kenneth Newwood <kenneth@newwood.name> - * @author Morris Jobke <hey@morrisjobke.de> - * @author Roeland Jago Douma <roeland@famdouma.nl> - * @author Thomas Müller <thomas.mueller@tmit.eu> - * - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * 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, version 3, - * along with this program. If not, see <http://www.gnu.org/licenses/> - * + * SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Encryption\Crypto; @@ -32,6 +12,7 @@ use OC\Files\View; use OCA\Encryption\KeyManager; use OCA\Encryption\Users\Setup; use OCA\Encryption\Util; +use OCP\Files\FileInfo; use OCP\IConfig; use OCP\IL10N; use OCP\IUser; @@ -40,6 +21,7 @@ use OCP\L10N\IFactory; use OCP\Mail\Headers\AutoSubmitted; use OCP\Mail\IMailer; use OCP\Security\ISecureRandom; +use Psr\Log\LoggerInterface; use Symfony\Component\Console\Helper\ProgressBar; use Symfony\Component\Console\Helper\QuestionHelper; use Symfony\Component\Console\Helper\Table; @@ -49,72 +31,29 @@ use Symfony\Component\Console\Question\ConfirmationQuestion; class EncryptAll { - /** @var Setup */ - protected $userSetup; - - /** @var IUserManager */ - protected $userManager; - - /** @var View */ - protected $rootView; - - /** @var KeyManager */ - protected $keyManager; - - /** @var Util */ - protected $util; - - /** @var array */ + /** @var array */ protected $userPasswords; - /** @var IConfig */ - protected $config; - - /** @var IMailer */ - protected $mailer; - - /** @var IL10N */ - protected $l; - - /** @var IFactory */ - protected $l10nFactory; - - /** @var QuestionHelper */ - protected $questionHelper; - - /** @var OutputInterface */ + /** @var OutputInterface */ protected $output; - /** @var InputInterface */ + /** @var InputInterface */ protected $input; - /** @var ISecureRandom */ - protected $secureRandom; - public function __construct( - Setup $userSetup, - IUserManager $userManager, - View $rootView, - KeyManager $keyManager, - Util $util, - IConfig $config, - IMailer $mailer, - IL10N $l, - IFactory $l10nFactory, - QuestionHelper $questionHelper, - ISecureRandom $secureRandom + protected Setup $userSetup, + protected IUserManager $userManager, + protected View $rootView, + protected KeyManager $keyManager, + protected Util $util, + protected IConfig $config, + protected IMailer $mailer, + protected IL10N $l, + protected IFactory $l10nFactory, + protected QuestionHelper $questionHelper, + protected ISecureRandom $secureRandom, + protected LoggerInterface $logger, ) { - $this->userSetup = $userSetup; - $this->userManager = $userManager; - $this->rootView = $rootView; - $this->keyManager = $keyManager; - $this->util = $util; - $this->config = $config; - $this->mailer = $mailer; - $this->l = $l; - $this->l10nFactory = $l10nFactory; - $this->questionHelper = $questionHelper; - $this->secureRandom = $secureRandom; // store one time passwords for the users $this->userPasswords = []; } @@ -223,7 +162,7 @@ class EncryptAll { $userNo++; } } - $progress->setMessage("all files encrypted"); + $progress->setMessage('all files encrypted'); $progress->finish(); } @@ -264,33 +203,42 @@ class EncryptAll { while ($root = array_pop($directories)) { $content = $this->rootView->getDirectoryContent($root); foreach ($content as $file) { - $path = $root . '/' . $file['name']; - if ($this->rootView->is_dir($path)) { + $path = $root . '/' . $file->getName(); + if ($file->isShared()) { + $progress->setMessage("Skip shared file/folder $path"); + $progress->advance(); + continue; + } elseif ($file->getType() === FileInfo::TYPE_FOLDER) { $directories[] = $path; continue; } else { $progress->setMessage("encrypt files for user $userCount: $path"); $progress->advance(); - if ($this->encryptFile($path) === false) { - $progress->setMessage("encrypt files for user $userCount: $path (already encrypted)"); + try { + if ($this->encryptFile($file, $path) === false) { + $progress->setMessage("encrypt files for user $userCount: $path (already encrypted)"); + $progress->advance(); + } + } catch (\Exception $e) { + $progress->setMessage("Failed to encrypt path $path: " . $e->getMessage()); $progress->advance(); + $this->logger->error( + 'Failed to encrypt path {path}', + [ + 'user' => $uid, + 'path' => $path, + 'exception' => $e, + ] + ); } } } } } - /** - * encrypt file - * - * @param string $path - * @return bool - */ - protected function encryptFile($path) { - + protected function encryptFile(FileInfo $fileInfo, string $path): bool { // skip already encrypted files - $fileInfo = $this->rootView->getFileInfo($path); - if ($fileInfo !== false && $fileInfo->isEncrypted()) { + if ($fileInfo->isEncrypted()) { return true; } @@ -298,7 +246,14 @@ class EncryptAll { $target = $path . '.encrypted.' . time(); try { - $this->rootView->copy($source, $target); + $copySuccess = $this->rootView->copy($source, $target); + if ($copySuccess === false) { + /* Copy failed, abort */ + if ($this->rootView->file_exists($target)) { + $this->rootView->unlink($target); + } + throw new \Exception('Copy failed for ' . $source); + } $this->rootView->rename($target, $source); } catch (DecryptionFailedException $e) { if ($this->rootView->file_exists($target)) { diff --git a/apps/encryption/lib/Crypto/Encryption.php b/apps/encryption/lib/Crypto/Encryption.php index 1481d3a9a23..6d388624e48 100644 --- a/apps/encryption/lib/Crypto/Encryption.php +++ b/apps/encryption/lib/Crypto/Encryption.php @@ -1,41 +1,16 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Bjoern Schiessle <bjoern@schiessle.org> - * @author Björn Schießle <bjoern@schiessle.org> - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Clark Tomlinson <fallen013@gmail.com> - * @author Jan-Christoph Borchardt <hey@jancborchardt.net> - * @author Joas Schilling <coding@schilljs.com> - * @author Julius Härtl <jus@bitgrid.net> - * @author Lukas Reschke <lukas@statuscode.ch> - * @author Morris Jobke <hey@morrisjobke.de> - * @author Robin Appelman <robin@icewind.nl> - * @author Roeland Jago Douma <roeland@famdouma.nl> - * @author Thomas Müller <thomas.mueller@tmit.eu> - * @author Valdnet <47037905+Valdnet@users.noreply.github.com> - * - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * 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, version 3, - * along with this program. If not, see <http://www.gnu.org/licenses/> - * + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Encryption\Crypto; use OC\Encryption\Exceptions\DecryptionFailedException; use OC\Files\Cache\Scanner; use OC\Files\View; +use OCA\Encryption\Exceptions\MultiKeyEncryptException; use OCA\Encryption\Exceptions\PublicKeyMissingException; use OCA\Encryption\KeyManager; use OCA\Encryption\Session; @@ -80,8 +55,6 @@ class Encryption implements IEncryptionModule { /** @var int Current version of the file */ private int $version = 0; - private bool $useLegacyFileKey = true; - /** @var array remember encryption signature version */ private static $rememberVersion = []; @@ -127,8 +100,8 @@ class Encryption implements IEncryptionModule { * @param array $accessList who has access to the file contains the key 'users' and 'public' * * @return array $header contain data as key-value pairs which should be - * written to the header, in case of a write operation - * or if no additional data is needed return a empty array + * written to the header, in case of a write operation + * or if no additional data is needed return a empty array */ public function begin($path, $user, $mode, array $header, array $accessList) { $this->path = $this->getPathToRealFile($path); @@ -138,7 +111,6 @@ class Encryption implements IEncryptionModule { $this->writeCache = ''; $this->useLegacyBase64Encoding = true; - $this->useLegacyFileKey = ($header['useLegacyFileKey'] ?? 'true') !== 'false'; if (isset($header['encoding'])) { $this->useLegacyBase64Encoding = $header['encoding'] !== Crypt::BINARY_ENCODING_FORMAT; @@ -152,19 +124,10 @@ class Encryption implements IEncryptionModule { } } - if ($this->session->decryptAllModeActivated()) { - $shareKey = $this->keyManager->getShareKey($this->path, $this->session->getDecryptAllUid()); - if ($this->useLegacyFileKey) { - $encryptedFileKey = $this->keyManager->getEncryptedFileKey($this->path); - $this->fileKey = $this->crypt->multiKeyDecryptLegacy($encryptedFileKey, - $shareKey, - $this->session->getDecryptAllKey()); - } else { - $this->fileKey = $this->crypt->multiKeyDecrypt($shareKey, $this->session->getDecryptAllKey()); - } - } else { - $this->fileKey = $this->keyManager->getFileKey($this->path, $this->user, $this->useLegacyFileKey); - } + /* If useLegacyFileKey is not specified in header, auto-detect, to be safe */ + $useLegacyFileKey = (($header['useLegacyFileKey'] ?? '') == 'false' ? false : null); + + $this->fileKey = $this->keyManager->getFileKey($this->path, $this->user, $useLegacyFileKey, $this->session->decryptAllModeActivated()); // always use the version from the original file, also part files // need to have a correct version number if they get moved over to the @@ -225,7 +188,7 @@ class Encryption implements IEncryptionModule { * of a write operation * @throws PublicKeyMissingException * @throws \Exception - * @throws \OCA\Encryption\Exceptions\MultiKeyEncryptException + * @throws MultiKeyEncryptException */ public function end($path, $position = '0') { $result = ''; @@ -470,7 +433,7 @@ class Encryption implements IEncryptionModule { * e.g. if all encryption keys exists * * @param string $path - * @param string $uid user for whom we want to check if he can read the file + * @param string $uid user for whom we want to check if they can read the file * @return bool * @throws DecryptionFailedException */ @@ -483,8 +446,8 @@ class Encryption implements IEncryptionModule { // error message because in this case it means that the file was // shared with the user at a point where the user didn't had a // valid private/public key - $msg = 'Encryption module "' . $this->getDisplayName() . - '" is not able to read ' . $path; + $msg = 'Encryption module "' . $this->getDisplayName() + . '" is not able to read ' . $path; $hint = $this->l->t('Cannot read this file, probably this is a shared file. Please ask the file owner to reshare the file with you.'); $this->logger->warning($msg); throw new DecryptionFailedException($msg, $hint); diff --git a/apps/encryption/lib/Exceptions/MultiKeyDecryptException.php b/apps/encryption/lib/Exceptions/MultiKeyDecryptException.php index 562f6955ae4..1246d51190b 100644 --- a/apps/encryption/lib/Exceptions/MultiKeyDecryptException.php +++ b/apps/encryption/lib/Exceptions/MultiKeyDecryptException.php @@ -1,23 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Thomas Müller <thomas.mueller@tmit.eu> - * - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * 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, version 3, - * along with this program. If not, see <http://www.gnu.org/licenses/> - * + * SPDX-FileCopyrightText: 2019-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Encryption\Exceptions; diff --git a/apps/encryption/lib/Exceptions/MultiKeyEncryptException.php b/apps/encryption/lib/Exceptions/MultiKeyEncryptException.php index 3771e7e36ba..60394af45c2 100644 --- a/apps/encryption/lib/Exceptions/MultiKeyEncryptException.php +++ b/apps/encryption/lib/Exceptions/MultiKeyEncryptException.php @@ -1,23 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Thomas Müller <thomas.mueller@tmit.eu> - * - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * 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, version 3, - * along with this program. If not, see <http://www.gnu.org/licenses/> - * + * SPDX-FileCopyrightText: 2019-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Encryption\Exceptions; diff --git a/apps/encryption/lib/Exceptions/PrivateKeyMissingException.php b/apps/encryption/lib/Exceptions/PrivateKeyMissingException.php index b8fd4fb9234..15fe8f4e72f 100644 --- a/apps/encryption/lib/Exceptions/PrivateKeyMissingException.php +++ b/apps/encryption/lib/Exceptions/PrivateKeyMissingException.php @@ -1,26 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Björn Schießle <bjoern@schiessle.org> - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Clark Tomlinson <fallen013@gmail.com> - * @author Thomas Müller <thomas.mueller@tmit.eu> - * - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * 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, version 3, - * along with this program. If not, see <http://www.gnu.org/licenses/> - * + * SPDX-FileCopyrightText: 2019-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Encryption\Exceptions; @@ -33,7 +16,7 @@ class PrivateKeyMissingException extends GenericEncryptionException { */ public function __construct($userId) { if (empty($userId)) { - $userId = "<no-user-id-given>"; + $userId = '<no-user-id-given>'; } parent::__construct("Private Key missing for user: $userId"); } diff --git a/apps/encryption/lib/Exceptions/PublicKeyMissingException.php b/apps/encryption/lib/Exceptions/PublicKeyMissingException.php index 73edc5c710d..78eeeccf47d 100644 --- a/apps/encryption/lib/Exceptions/PublicKeyMissingException.php +++ b/apps/encryption/lib/Exceptions/PublicKeyMissingException.php @@ -1,24 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Thomas Müller <thomas.mueller@tmit.eu> - * - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * 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, version 3, - * along with this program. If not, see <http://www.gnu.org/licenses/> - * + * SPDX-FileCopyrightText: 2019-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Encryption\Exceptions; @@ -31,7 +16,7 @@ class PublicKeyMissingException extends GenericEncryptionException { */ public function __construct($userId) { if (empty($userId)) { - $userId = "<no-user-id-given>"; + $userId = '<no-user-id-given>'; } parent::__construct("Public Key missing for user: $userId"); } diff --git a/apps/encryption/lib/HookManager.php b/apps/encryption/lib/HookManager.php deleted file mode 100644 index 0e7ec5cfd56..00000000000 --- a/apps/encryption/lib/HookManager.php +++ /dev/null @@ -1,59 +0,0 @@ -<?php -/** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Björn Schießle <bjoern@schiessle.org> - * @author Clark Tomlinson <fallen013@gmail.com> - * @author Daniel Kesselberg <mail@danielkesselberg.de> - * - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * 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, version 3, - * along with this program. If not, see <http://www.gnu.org/licenses/> - * - */ -namespace OCA\Encryption; - -use OCA\Encryption\Hooks\Contracts\IHook; - -class HookManager { - /** @var IHook[] */ - private $hookInstances = []; - - /** - * @param array|IHook $instances - * - This accepts either a single instance of IHook or an array of instances of IHook - * @return bool - */ - public function registerHook($instances) { - if (is_array($instances)) { - foreach ($instances as $instance) { - if (!$instance instanceof IHook) { - return false; - } - $this->hookInstances[] = $instance; - } - } elseif ($instances instanceof IHook) { - $this->hookInstances[] = $instances; - } - return true; - } - - public function fireHooks() { - foreach ($this->hookInstances as $instance) { - /** - * Fire off the add hooks method of each instance stored in cache - */ - $instance->addHooks(); - } - } -} diff --git a/apps/encryption/lib/Hooks/Contracts/IHook.php b/apps/encryption/lib/Hooks/Contracts/IHook.php deleted file mode 100644 index 54128d10327..00000000000 --- a/apps/encryption/lib/Hooks/Contracts/IHook.php +++ /dev/null @@ -1,31 +0,0 @@ -<?php -/** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Clark Tomlinson <fallen013@gmail.com> - * - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * 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, version 3, - * along with this program. If not, see <http://www.gnu.org/licenses/> - * - */ -namespace OCA\Encryption\Hooks\Contracts; - -interface IHook { - /** - * Connects Hooks - * - * @return null - */ - public function addHooks(); -} diff --git a/apps/encryption/lib/Hooks/UserHooks.php b/apps/encryption/lib/Hooks/UserHooks.php deleted file mode 100644 index 487e1089495..00000000000 --- a/apps/encryption/lib/Hooks/UserHooks.php +++ /dev/null @@ -1,286 +0,0 @@ -<?php -/** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Bjoern Schiessle <bjoern@schiessle.org> - * @author Björn Schießle <bjoern@schiessle.org> - * @author Clark Tomlinson <fallen013@gmail.com> - * @author Joas Schilling <coding@schilljs.com> - * @author Morris Jobke <hey@morrisjobke.de> - * @author Roeland Jago Douma <roeland@famdouma.nl> - * @author Thomas Müller <thomas.mueller@tmit.eu> - * - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * 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, version 3, - * along with this program. If not, see <http://www.gnu.org/licenses/> - * - */ -namespace OCA\Encryption\Hooks; - -use OC\Files\Filesystem; -use OCA\Encryption\Crypto\Crypt; -use OCA\Encryption\Hooks\Contracts\IHook; -use OCA\Encryption\KeyManager; -use OCA\Encryption\Recovery; -use OCA\Encryption\Session; -use OCA\Encryption\Users\Setup; -use OCA\Encryption\Util; -use OCP\Encryption\Exceptions\GenericEncryptionException; -use OCP\IUserManager; -use OCP\IUserSession; -use OCP\Util as OCUtil; -use Psr\Log\LoggerInterface; - -class UserHooks implements IHook { - /** - * list of user for which we perform a password reset - * @var array<string, true> - */ - protected static array $passwordResetUsers = []; - - public function __construct( - private KeyManager $keyManager, - private IUserManager $userManager, - private LoggerInterface $logger, - private Setup $userSetup, - private IUserSession $userSession, - private Util $util, - private Session $session, - private Crypt $crypt, - private Recovery $recovery, - ) { - } - - /** - * Connects Hooks - * - * @return null - */ - public function addHooks() { - OCUtil::connectHook('OC_User', 'post_login', $this, 'login'); - OCUtil::connectHook('OC_User', 'logout', $this, 'logout'); - - // this hooks only make sense if no master key is used - if ($this->util->isMasterKeyEnabled() === false) { - OCUtil::connectHook('OC_User', - 'post_setPassword', - $this, - 'setPassphrase'); - - OCUtil::connectHook('OC_User', - 'pre_setPassword', - $this, - 'preSetPassphrase'); - - OCUtil::connectHook('\OC\Core\LostPassword\Controller\LostController', - 'post_passwordReset', - $this, - 'postPasswordReset'); - - OCUtil::connectHook('\OC\Core\LostPassword\Controller\LostController', - 'pre_passwordReset', - $this, - 'prePasswordReset'); - - OCUtil::connectHook('OC_User', - 'post_createUser', - $this, - 'postCreateUser'); - - OCUtil::connectHook('OC_User', - 'post_deleteUser', - $this, - 'postDeleteUser'); - } - } - - - /** - * Startup encryption backend upon user login - * - * @note This method should never be called for users using client side encryption - * @param array $params - * @return boolean|null - */ - public function login($params) { - // ensure filesystem is loaded - if (!\OC\Files\Filesystem::$loaded) { - $this->setupFS($params['uid']); - } - if ($this->util->isMasterKeyEnabled() === false) { - $this->userSetup->setupUser($params['uid'], $params['password']); - } - - $this->keyManager->init($params['uid'], $params['password']); - } - - /** - * remove keys from session during logout - */ - public function logout() { - $this->session->clear(); - } - - /** - * setup encryption backend upon user created - * - * @note This method should never be called for users using client side encryption - * @param array $params - */ - public function postCreateUser($params) { - $this->userSetup->setupUser($params['uid'], $params['password']); - } - - /** - * cleanup encryption backend upon user deleted - * - * @param array $params : uid, password - * @note This method should never be called for users using client side encryption - */ - public function postDeleteUser($params) { - $this->keyManager->deletePublicKey($params['uid']); - } - - public function prePasswordReset($params) { - $user = $params['uid']; - self::$passwordResetUsers[$user] = true; - } - - public function postPasswordReset($params) { - $uid = $params['uid']; - $password = $params['password']; - $this->keyManager->backupUserKeys('passwordReset', $uid); - $this->keyManager->deleteUserKeys($uid); - $this->userSetup->setupUser($uid, $password); - unset(self::$passwordResetUsers[$uid]); - } - - /** - * If the password can't be changed within Nextcloud, than update the key password in advance. - * - * @param array $params : uid, password - * @return boolean|null - */ - public function preSetPassphrase($params) { - $user = $this->userManager->get($params['uid']); - - if ($user && !$user->canChangePassword()) { - $this->setPassphrase($params); - } - } - - /** - * Change a user's encryption passphrase - * - * @param array $params keys: uid, password - * @return boolean|null - */ - public function setPassphrase($params) { - // if we are in the process to resetting a user password, we have nothing - // to do here - if (isset(self::$passwordResetUsers[$params['uid']])) { - return true; - } - - // Get existing decrypted private key - $user = $this->userSession->getUser(); - - // current logged in user changes his own password - if ($user && $params['uid'] === $user->getUID()) { - $privateKey = $this->session->getPrivateKey(); - - // Encrypt private key with new user pwd as passphrase - $encryptedPrivateKey = $this->crypt->encryptPrivateKey($privateKey, $params['password'], $params['uid']); - - // Save private key - if ($encryptedPrivateKey) { - $this->keyManager->setPrivateKey($user->getUID(), - $this->crypt->generateHeader() . $encryptedPrivateKey); - } else { - $this->logger->error('Encryption could not update users encryption password'); - } - - // NOTE: Session does not need to be updated as the - // private key has not changed, only the passphrase - // used to decrypt it has changed - } else { // admin changed the password for a different user, create new keys and re-encrypt file keys - $userId = $params['uid']; - $this->initMountPoints($userId); - $recoveryPassword = $params['recoveryPassword'] ?? null; - - $recoveryKeyId = $this->keyManager->getRecoveryKeyId(); - $recoveryKey = $this->keyManager->getSystemPrivateKey($recoveryKeyId); - try { - $decryptedRecoveryKey = $this->crypt->decryptPrivateKey($recoveryKey, $recoveryPassword); - } catch (\Exception $e) { - $decryptedRecoveryKey = false; - } - if ($decryptedRecoveryKey === false) { - $message = 'Can not decrypt the recovery key. Maybe you provided the wrong password. Try again.'; - throw new GenericEncryptionException($message, $message); - } - - // we generate new keys if... - // ...we have a recovery password and the user enabled the recovery key - // ...encryption was activated for the first time (no keys exists) - // ...the user doesn't have any files - if ( - ($this->recovery->isRecoveryEnabledForUser($userId) && $recoveryPassword) - || !$this->keyManager->userHasKeys($userId) - || !$this->util->userHasFiles($userId) - ) { - // backup old keys - //$this->backupAllKeys('recovery'); - - $newUserPassword = $params['password']; - - $keyPair = $this->crypt->createKeyPair(); - - // Save public key - $this->keyManager->setPublicKey($userId, $keyPair['publicKey']); - - // Encrypt private key with new password - $encryptedKey = $this->crypt->encryptPrivateKey($keyPair['privateKey'], $newUserPassword, $userId); - - if ($encryptedKey) { - $this->keyManager->setPrivateKey($userId, $this->crypt->generateHeader() . $encryptedKey); - - if ($recoveryPassword) { // if recovery key is set we can re-encrypt the key files - $this->recovery->recoverUsersFiles($recoveryPassword, $userId); - } - } else { - $this->logger->error('Encryption Could not update users encryption password'); - } - } - } - } - - /** - * init mount points for given user - * - * @param string $user - * @throws \OC\User\NoUserException - */ - protected function initMountPoints($user) { - Filesystem::initMountPoints($user); - } - - /** - * setup file system for user - * - * @param string $uid user id - */ - protected function setupFS($uid) { - \OC_Util::setupFS($uid); - } -} diff --git a/apps/encryption/lib/KeyManager.php b/apps/encryption/lib/KeyManager.php index 7d6380f3b83..f9c1ef94634 100644 --- a/apps/encryption/lib/KeyManager.php +++ b/apps/encryption/lib/KeyManager.php @@ -1,33 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Arthur Schiwon <blizzz@arthur-schiwon.de> - * @author Bjoern Schiessle <bjoern@schiessle.org> - * @author Björn Schießle <bjoern@schiessle.org> - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Clark Tomlinson <fallen013@gmail.com> - * @author Julius Härtl <jus@bitgrid.net> - * @author Lukas Reschke <lukas@statuscode.ch> - * @author Morris Jobke <hey@morrisjobke.de> - * @author Roeland Jago Douma <roeland@famdouma.nl> - * @author Thomas Müller <thomas.mueller@tmit.eu> - * @author Vincent Petry <vincent@nextcloud.com> - * - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * 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, version 3, - * along with this program. If not, see <http://www.gnu.org/licenses/> - * + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Encryption; @@ -235,8 +211,8 @@ class KeyManager { */ public function setRecoveryKey($password, $keyPair) { // Save Public Key - $this->keyStorage->setSystemUserKey($this->getRecoveryKeyId(). - '.' . $this->publicKeyId, + $this->keyStorage->setSystemUserKey($this->getRecoveryKeyId() + . '.' . $this->publicKeyId, $keyPair['publicKey'], Encryption::ID); @@ -311,11 +287,9 @@ class KeyManager { /** * Decrypt private key and store it * - * @param string $uid user id - * @param string $passPhrase users password * @return boolean */ - public function init($uid, $passPhrase) { + public function init(string $uid, ?string $passPhrase) { $this->session->setStatus(Session::INIT_EXECUTED); try { @@ -324,6 +298,10 @@ class KeyManager { $passPhrase = $this->getMasterKeyPassword(); $privateKey = $this->getSystemPrivateKey($uid); } else { + if ($passPhrase === null) { + $this->logger->warning('Master key is disabled but not passphrase provided.'); + return false; + } $privateKey = $this->getPrivateKey($uid); } $privateKey = $this->crypt->decryptPrivateKey($privateKey, $passPhrase, $uid); @@ -367,12 +345,9 @@ class KeyManager { } /** - * @param string $path - * @param $uid * @param ?bool $useLegacyFileKey null means try both - * @return string */ - public function getFileKey(string $path, ?string $uid, ?bool $useLegacyFileKey): string { + public function getFileKey(string $path, ?string $uid, ?bool $useLegacyFileKey, bool $useDecryptAll = false): string { if ($uid === '') { $uid = null; } @@ -385,8 +360,10 @@ class KeyManager { return ''; } } - - if ($this->util->isMasterKeyEnabled()) { + if ($useDecryptAll) { + $shareKey = $this->getShareKey($path, $this->session->getDecryptAllUid()); + $privateKey = $this->session->getDecryptAllKey(); + } elseif ($this->util->isMasterKeyEnabled()) { $uid = $this->getMasterKeyId(); $shareKey = $this->getShareKey($path, $uid); if ($publicAccess) { @@ -656,8 +633,8 @@ class KeyManager { $publicKeys[$this->getPublicShareKeyId()] = $publicShareKey; } - if ($this->recoveryKeyExists() && - $this->util->isRecoveryEnabledForUser($uid)) { + if ($this->recoveryKeyExists() + && $this->util->isRecoveryEnabledForUser($uid)) { $publicKeys[$this->getRecoveryKeyId()] = $this->getRecoveryKey(); } diff --git a/apps/encryption/lib/Listeners/UserEventsListener.php b/apps/encryption/lib/Listeners/UserEventsListener.php new file mode 100644 index 00000000000..3f61fde599b --- /dev/null +++ b/apps/encryption/lib/Listeners/UserEventsListener.php @@ -0,0 +1,144 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCA\Encryption\Listeners; + +use OC\Core\Events\BeforePasswordResetEvent; +use OC\Core\Events\PasswordResetEvent; +use OC\Files\SetupManager; +use OCA\Encryption\KeyManager; +use OCA\Encryption\Services\PassphraseService; +use OCA\Encryption\Session; +use OCA\Encryption\Users\Setup; +use OCA\Encryption\Util; +use OCP\EventDispatcher\Event; +use OCP\EventDispatcher\IEventListener; +use OCP\IUser; +use OCP\IUserManager; +use OCP\IUserSession; +use OCP\User\Events\BeforePasswordUpdatedEvent; +use OCP\User\Events\PasswordUpdatedEvent; +use OCP\User\Events\UserCreatedEvent; +use OCP\User\Events\UserDeletedEvent; +use OCP\User\Events\UserLoggedInEvent; +use OCP\User\Events\UserLoggedInWithCookieEvent; +use OCP\User\Events\UserLoggedOutEvent; + +/** + * @template-implements IEventListener<UserCreatedEvent|UserDeletedEvent|UserLoggedInEvent|UserLoggedInWithCookieEvent|UserLoggedOutEvent|BeforePasswordUpdatedEvent|PasswordUpdatedEvent|BeforePasswordResetEvent|PasswordResetEvent> + */ +class UserEventsListener implements IEventListener { + + public function __construct( + private Util $util, + private Setup $userSetup, + private Session $session, + private KeyManager $keyManager, + private IUserManager $userManager, + private IUserSession $userSession, + private SetupManager $setupManager, + private PassphraseService $passphraseService, + ) { + } + + public function handle(Event $event): void { + if ($event instanceof UserCreatedEvent) { + $this->onUserCreated($event->getUid(), $event->getPassword()); + } elseif ($event instanceof UserDeletedEvent) { + $this->onUserDeleted($event->getUid()); + } elseif ($event instanceof UserLoggedInEvent || $event instanceof UserLoggedInWithCookieEvent) { + $this->onUserLogin($event->getUser(), $event->getPassword()); + } elseif ($event instanceof UserLoggedOutEvent) { + $this->onUserLogout(); + } elseif ($event instanceof BeforePasswordUpdatedEvent) { + $this->onBeforePasswordUpdated($event->getUser(), $event->getPassword(), $event->getRecoveryPassword()); + } elseif ($event instanceof PasswordUpdatedEvent) { + $this->onPasswordUpdated($event->getUid(), $event->getPassword(), $event->getRecoveryPassword()); + } elseif ($event instanceof BeforePasswordResetEvent) { + $this->onBeforePasswordReset($event->getUid()); + } elseif ($event instanceof PasswordResetEvent) { + $this->onPasswordReset($event->getUid(), $event->getPassword()); + } + } + + /** + * Startup encryption backend upon user login + */ + private function onUserLogin(IUser $user, ?string $password): void { + // ensure filesystem is loaded + $this->setupManager->setupForUser($user); + if ($this->util->isMasterKeyEnabled() === false) { + // Skip if no master key and the password is not provided + if ($password === null) { + return; + } + + $this->userSetup->setupUser($user->getUID(), $password); + } + + $this->keyManager->init($user->getUID(), $password); + } + + /** + * Remove keys from session during logout + */ + private function onUserLogout(): void { + $this->session->clear(); + } + + /** + * Setup encryption backend upon user created + * + * This method should never be called for users using client side encryption + */ + protected function onUserCreated(string $userId, string $password): void { + $this->userSetup->setupUser($userId, $password); + } + + /** + * Cleanup encryption backend upon user deleted + * + * This method should never be called for users using client side encryption + */ + protected function onUserDeleted(string $userId): void { + $this->keyManager->deletePublicKey($userId); + } + + /** + * If the password can't be changed within Nextcloud, than update the key password in advance. + */ + public function onBeforePasswordUpdated(IUser $user, string $password, ?string $recoveryPassword = null): void { + if (!$user->canChangePassword()) { + $this->passphraseService->setPassphraseForUser($user->getUID(), $password, $recoveryPassword); + } + } + + /** + * Change a user's encryption passphrase + */ + public function onPasswordUpdated(string $userId, string $password, ?string $recoveryPassword): void { + $this->passphraseService->setPassphraseForUser($userId, $password, $recoveryPassword); + } + + /** + * Set user password resetting state to allow ignoring "reset"-requests on password update + */ + public function onBeforePasswordReset(string $userId): void { + $this->passphraseService->setProcessingReset($userId); + } + + /** + * Create new encryption keys on password reset and backup the old one + */ + public function onPasswordReset(string $userId, string $password): void { + $this->keyManager->backupUserKeys('passwordReset', $userId); + $this->keyManager->deleteUserKeys($userId); + $this->userSetup->setupUser($userId, $password); + $this->passphraseService->setProcessingReset($userId, false); + } +} diff --git a/apps/encryption/lib/Migration/SetMasterKeyStatus.php b/apps/encryption/lib/Migration/SetMasterKeyStatus.php index a80d7144cc4..5f98308de89 100644 --- a/apps/encryption/lib/Migration/SetMasterKeyStatus.php +++ b/apps/encryption/lib/Migration/SetMasterKeyStatus.php @@ -1,24 +1,8 @@ <?php + /** - * @copyright Copyright (c) 2017 Bjoern Schiessle <bjoern@schiessle.org> - * - * @author Bjoern Schiessle <bjoern@schiessle.org> - * - * @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/>. - * + * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCA\Encryption\Migration; @@ -34,12 +18,9 @@ use OCP\Migration\IRepairStep; class SetMasterKeyStatus implements IRepairStep { - /** @var IConfig */ - private $config; - - - public function __construct(IConfig $config) { - $this->config = $config; + public function __construct( + private IConfig $config, + ) { } /** diff --git a/apps/encryption/lib/Recovery.php b/apps/encryption/lib/Recovery.php index 66ad59266a5..38e78f5e822 100644 --- a/apps/encryption/lib/Recovery.php +++ b/apps/encryption/lib/Recovery.php @@ -1,28 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Björn Schießle <bjoern@schiessle.org> - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Clark Tomlinson <fallen013@gmail.com> - * @author Joas Schilling <coding@schilljs.com> - * @author Lukas Reschke <lukas@statuscode.ch> - * @author Roeland Jago Douma <roeland@famdouma.nl> - * - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * 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, version 3, - * along with this program. If not, see <http://www.gnu.org/licenses/> - * + * SPDX-FileCopyrightText: 2019-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Encryption; @@ -39,26 +20,6 @@ class Recovery { * @var null|IUser */ protected $user; - /** - * @var Crypt - */ - protected $crypt; - /** - * @var KeyManager - */ - private $keyManager; - /** - * @var IConfig - */ - private $config; - /** - * @var View - */ - private $view; - /** - * @var IFile - */ - private $file; /** * @param IUserSession $userSession @@ -68,18 +29,15 @@ class Recovery { * @param IFile $file * @param View $view */ - public function __construct(IUserSession $userSession, - Crypt $crypt, - KeyManager $keyManager, - IConfig $config, - IFile $file, - View $view) { + public function __construct( + IUserSession $userSession, + protected Crypt $crypt, + private KeyManager $keyManager, + private IConfig $config, + private IFile $file, + private View $view, + ) { $this->user = ($userSession->isLoggedIn()) ? $userSession->getUser() : null; - $this->crypt = $crypt; - $this->keyManager = $keyManager; - $this->config = $config; - $this->view = $view; - $this->file = $file; } /** diff --git a/apps/encryption/lib/Services/PassphraseService.php b/apps/encryption/lib/Services/PassphraseService.php new file mode 100644 index 00000000000..bdcc3f1108a --- /dev/null +++ b/apps/encryption/lib/Services/PassphraseService.php @@ -0,0 +1,148 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCA\Encryption\Services; + +use OC\Files\Filesystem; +use OCA\Encryption\Crypto\Crypt; +use OCA\Encryption\KeyManager; +use OCA\Encryption\Recovery; +use OCA\Encryption\Session; +use OCA\Encryption\Util; +use OCP\Encryption\Exceptions\GenericEncryptionException; +use OCP\IUser; +use OCP\IUserManager; +use OCP\IUserSession; +use Psr\Log\LoggerInterface; + +class PassphraseService { + + /** @var array<string, bool> */ + private static array $passwordResetUsers = []; + + public function __construct( + private Util $util, + private Crypt $crypt, + private Session $session, + private Recovery $recovery, + private KeyManager $keyManager, + private LoggerInterface $logger, + private IUserManager $userManager, + private IUserSession $userSession, + ) { + } + + public function setProcessingReset(string $uid, bool $processing = true): void { + if ($processing) { + self::$passwordResetUsers[$uid] = true; + } else { + unset(self::$passwordResetUsers[$uid]); + } + } + + /** + * Change a user's encryption passphrase + */ + public function setPassphraseForUser(string $userId, string $password, ?string $recoveryPassword = null): bool { + // if we are in the process to resetting a user password, we have nothing + // to do here + if (isset(self::$passwordResetUsers[$userId])) { + return true; + } + + if ($this->util->isMasterKeyEnabled()) { + $this->logger->error('setPassphraseForUser should never be called when master key is enabled'); + return true; + } + + // Check user exists on backend + $user = $this->userManager->get($userId); + if ($user === null) { + return false; + } + + // Get existing decrypted private key + $currentUser = $this->userSession->getUser(); + + // current logged in user changes his own password + if ($currentUser !== null && $userId === $currentUser->getUID()) { + $privateKey = $this->session->getPrivateKey(); + + // Encrypt private key with new user pwd as passphrase + $encryptedPrivateKey = $this->crypt->encryptPrivateKey($privateKey, $password, $userId); + + // Save private key + if ($encryptedPrivateKey !== false) { + $key = $this->crypt->generateHeader() . $encryptedPrivateKey; + $this->keyManager->setPrivateKey($userId, $key); + return true; + } + + $this->logger->error('Encryption could not update users encryption password'); + + // NOTE: Session does not need to be updated as the + // private key has not changed, only the passphrase + // used to decrypt it has changed + } else { + // admin changed the password for a different user, create new keys and re-encrypt file keys + $recoveryPassword = $recoveryPassword ?? ''; + $this->initMountPoints($user); + + $recoveryKeyId = $this->keyManager->getRecoveryKeyId(); + $recoveryKey = $this->keyManager->getSystemPrivateKey($recoveryKeyId); + try { + $this->crypt->decryptPrivateKey($recoveryKey, $recoveryPassword); + } catch (\Exception) { + $message = 'Can not decrypt the recovery key. Maybe you provided the wrong password. Try again.'; + throw new GenericEncryptionException($message, $message); + } + + // we generate new keys if... + // ...we have a recovery password and the user enabled the recovery key + // ...encryption was activated for the first time (no keys exists) + // ...the user doesn't have any files + if ( + ($this->recovery->isRecoveryEnabledForUser($userId) && $recoveryPassword !== '') + || !$this->keyManager->userHasKeys($userId) + || !$this->util->userHasFiles($userId) + ) { + $keyPair = $this->crypt->createKeyPair(); + if ($keyPair === false) { + $this->logger->error('Could not create new private key-pair for user.'); + return false; + } + + // Save public key + $this->keyManager->setPublicKey($userId, $keyPair['publicKey']); + + // Encrypt private key with new password + $encryptedKey = $this->crypt->encryptPrivateKey($keyPair['privateKey'], $password, $userId); + if ($encryptedKey === false) { + $this->logger->error('Encryption could not update users encryption password'); + return false; + } + + $this->keyManager->setPrivateKey($userId, $this->crypt->generateHeader() . $encryptedKey); + + if ($recoveryPassword !== '') { + // if recovery key is set we can re-encrypt the key files + $this->recovery->recoverUsersFiles($recoveryPassword, $userId); + } + return true; + } + } + return false; + } + + /** + * Init mount points for given user + */ + private function initMountPoints(IUser $user): void { + Filesystem::initMountPoints($user); + } +} diff --git a/apps/encryption/lib/Session.php b/apps/encryption/lib/Session.php index ad07008d480..df1e5d664ad 100644 --- a/apps/encryption/lib/Session.php +++ b/apps/encryption/lib/Session.php @@ -1,28 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Bjoern Schiessle <bjoern@schiessle.org> - * @author Björn Schießle <bjoern@schiessle.org> - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Clark Tomlinson <fallen013@gmail.com> - * @author Lukas Reschke <lukas@statuscode.ch> - * @author Roeland Jago Douma <roeland@famdouma.nl> - * - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * 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, version 3, - * along with this program. If not, see <http://www.gnu.org/licenses/> - * + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Encryption; @@ -31,9 +12,6 @@ use OCP\ISession; class Session { - /** @var ISession */ - protected $session; - public const NOT_INITIALIZED = '0'; public const INIT_EXECUTED = '1'; public const INIT_SUCCESSFUL = '2'; @@ -41,8 +19,9 @@ class Session { /** * @param ISession $session */ - public function __construct(ISession $session) { - $this->session = $session; + public function __construct( + protected ISession $session, + ) { } /** @@ -87,7 +66,7 @@ class Session { public function getPrivateKey() { $key = $this->session->get('privateKey'); if (is_null($key)) { - throw new Exceptions\PrivateKeyMissingException('please try to log-out and log-in again', 0); + throw new PrivateKeyMissingException('please try to log-out and log-in again'); } return $key; } diff --git a/apps/encryption/lib/Settings/Admin.php b/apps/encryption/lib/Settings/Admin.php index 9ce765723d8..a5de4ba68ff 100644 --- a/apps/encryption/lib/Settings/Admin.php +++ b/apps/encryption/lib/Settings/Admin.php @@ -1,27 +1,8 @@ <?php + /** - * @copyright Copyright (c) 2016 Arthur Schiwon <blizzz@arthur-schiwon.de> - * - * @author Arthur Schiwon <blizzz@arthur-schiwon.de> - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Julius Härtl <jus@bitgrid.net> - * @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/>. - * + * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCA\Encryption\Settings; @@ -45,7 +26,7 @@ class Admin implements ISettings { private IUserSession $userSession, private IConfig $config, private IUserManager $userManager, - private ISession $session + private ISession $session, ) { } @@ -91,8 +72,8 @@ class Admin implements ISettings { /** * @return int whether the form should be rather on the top or bottom of - * the admin section. The forms are arranged in ascending order of the - * priority values. It is required to return a value between 0 and 100. + * the admin section. The forms are arranged in ascending order of the + * priority values. It is required to return a value between 0 and 100. * * E.g.: 70 */ diff --git a/apps/encryption/lib/Settings/Personal.php b/apps/encryption/lib/Settings/Personal.php index 70b85a667a9..8814d3afb58 100644 --- a/apps/encryption/lib/Settings/Personal.php +++ b/apps/encryption/lib/Settings/Personal.php @@ -1,25 +1,8 @@ <?php + /** - * @copyright Copyright (c) 2017 Arthur Schiwon <blizzz@arthur-schiwon.de> - * - * @author Arthur Schiwon <blizzz@arthur-schiwon.de> - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * - * @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/>. - * + * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCA\Encryption\Settings; @@ -32,20 +15,12 @@ use OCP\Settings\ISettings; class Personal implements ISettings { - /** @var IConfig */ - private $config; - /** @var Session */ - private $session; - /** @var Util */ - private $util; - /** @var IUserSession */ - private $userSession; - - public function __construct(IConfig $config, Session $session, Util $util, IUserSession $userSession) { - $this->config = $config; - $this->session = $session; - $this->util = $util; - $this->userSession = $userSession; + public function __construct( + private IConfig $config, + private Session $session, + private Util $util, + private IUserSession $userSession, + ) { } /** @@ -82,8 +57,8 @@ class Personal implements ISettings { /** * @return int whether the form should be rather on the top or bottom of - * the admin section. The forms are arranged in ascending order of the - * priority values. It is required to return a value between 0 and 100. + * the admin section. The forms are arranged in ascending order of the + * priority values. It is required to return a value between 0 and 100. * * E.g.: 70 * @since 9.1 diff --git a/apps/encryption/lib/Users/Setup.php b/apps/encryption/lib/Users/Setup.php index 8b0ddaa8cd0..f2189d6dab2 100644 --- a/apps/encryption/lib/Users/Setup.php +++ b/apps/encryption/lib/Users/Setup.php @@ -1,29 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Björn Schießle <bjoern@schiessle.org> - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Clark Tomlinson <fallen013@gmail.com> - * @author Julius Härtl <jus@bitgrid.net> - * @author Lukas Reschke <lukas@statuscode.ch> - * @author Morris Jobke <hey@morrisjobke.de> - * @author Thomas Müller <thomas.mueller@tmit.eu> - * - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * 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, version 3, - * along with this program. If not, see <http://www.gnu.org/licenses/> - * + * SPDX-FileCopyrightText: 2019-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Encryption\Users; @@ -31,15 +11,11 @@ use OCA\Encryption\Crypto\Crypt; use OCA\Encryption\KeyManager; class Setup { - /** @var Crypt */ - private $crypt; - /** @var KeyManager */ - private $keyManager; - public function __construct(Crypt $crypt, - KeyManager $keyManager) { - $this->crypt = $crypt; - $this->keyManager = $keyManager; + public function __construct( + private Crypt $crypt, + private KeyManager $keyManager, + ) { } /** diff --git a/apps/encryption/lib/Util.php b/apps/encryption/lib/Util.php index f1b43413be3..ccbdcdcb242 100644 --- a/apps/encryption/lib/Util.php +++ b/apps/encryption/lib/Util.php @@ -1,33 +1,16 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Bjoern Schiessle <bjoern@schiessle.org> - * @author Björn Schießle <bjoern@schiessle.org> - * @author Clark Tomlinson <fallen013@gmail.com> - * @author Morris Jobke <hey@morrisjobke.de> - * @author Phil Davis <phil.davis@inf.org> - * @author Robin Appelman <robin@icewind.nl> - * - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * 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, version 3, - * along with this program. If not, see <http://www.gnu.org/licenses/> - * + * SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Encryption; +use OC\Files\Storage\Storage; use OC\Files\View; use OCA\Encryption\Crypto\Crypt; +use OCP\Files\Storage\IStorage; use OCP\IConfig; use OCP\IUser; use OCP\IUserManager; @@ -44,11 +27,7 @@ class Util { private IConfig $config, private IUserManager $userManager, ) { - $this->files = $files; - $this->crypt = $crypt; $this->user = $userSession->isLoggedIn() ? $userSession->getUser() : false; - $this->config = $config; - $this->userManager = $userManager; } /** @@ -142,21 +121,16 @@ class Util { if (count($parts) > 1) { $owner = $parts[1]; if ($this->userManager->userExists($owner) === false) { - throw new \BadMethodCallException('Unknown user: ' . - 'method expects path to a user folder relative to the data folder'); + throw new \BadMethodCallException('Unknown user: ' + . 'method expects path to a user folder relative to the data folder'); } } return $owner; } - /** - * get storage of path - * - * @param string $path - * @return \OC\Files\Storage\Storage|null - */ - public function getStorage($path) { + public function getStorage(string $path): ?IStorage { return $this->files->getMount($path)->getStorage(); } + } |