summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVincent Petry <pvince81@owncloud.com>2015-01-09 18:40:51 +0100
committerVincent Petry <pvince81@owncloud.com>2015-01-09 18:40:51 +0100
commit59a1d16d0fd3d67833bfb728ce03cebc7fec4043 (patch)
treee333dd79ea1098a79d0950b050145f8fbaaa56cd
parentda8228fa14ee5c2b5db7ba79bbba6da6f0bdcf26 (diff)
parent0500d3a506e9296727eb9bbc5cb0f9f513d38f2e (diff)
downloadnextcloud-server-59a1d16d0fd3d67833bfb728ce03cebc7fec4043.tar.gz
nextcloud-server-59a1d16d0fd3d67833bfb728ce03cebc7fec4043.zip
Merge pull request #13204 from owncloud/enc_fix_key_lost
Encryption fix key lost if group share gets renamed
-rw-r--r--apps/files_encryption/lib/hooks.php1254
-rwxr-xr-xapps/files_encryption/tests/share.php90
2 files changed, 714 insertions, 630 deletions
diff --git a/apps/files_encryption/lib/hooks.php b/apps/files_encryption/lib/hooks.php
index 7ddde0a3112..1ffcee5e74a 100644
--- a/apps/files_encryption/lib/hooks.php
+++ b/apps/files_encryption/lib/hooks.php
@@ -1,626 +1,628 @@
-<?php
-
-/**
- * ownCloud
- *
- * @copyright (C) 2014 ownCloud, Inc.
- *
- * @author Sam Tuke <samtuke@owncloud.org>
- * @author Bjoern Schiessle <schiessle@owncloud.com>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or any later version.
- *
- * This library 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 library. If not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-namespace OCA\Files_Encryption;
-
-/**
- * Class for hook specific logic
- */
-class Hooks {
-
- // file for which we want to rename the keys after the rename operation was successful
- private static $renamedFiles = array();
- // file for which we want to delete the keys after the delete operation was successful
- private static $deleteFiles = array();
- // file for which we want to delete the keys after the delete operation was successful
- private static $unmountedFiles = array();
-
- /**
- * Startup encryption backend upon user login
- * @note This method should never be called for users using client side encryption
- */
- public static function login($params) {
-
- if (\OCP\App::isEnabled('files_encryption') === false) {
- return true;
- }
-
-
- $l = new \OC_L10N('files_encryption');
-
- $view = new \OC\Files\View('/');
-
- // ensure filesystem is loaded
- if (!\OC\Files\Filesystem::$loaded) {
- \OC_Util::setupFS($params['uid']);
- }
-
- $privateKey = Keymanager::getPrivateKey($view, $params['uid']);
-
- // if no private key exists, check server configuration
- if (!$privateKey) {
- //check if all requirements are met
- if (!Helper::checkRequirements() || !Helper::checkConfiguration()) {
- $error_msg = $l->t("Missing requirements.");
- $hint = $l->t('Please make sure that OpenSSL together with the PHP extension is enabled and configured properly. For now, the encryption app has been disabled.');
- \OC_App::disable('files_encryption');
- \OCP\Util::writeLog('Encryption library', $error_msg . ' ' . $hint, \OCP\Util::ERROR);
- \OCP\Template::printErrorPage($error_msg, $hint);
- }
- }
-
- $util = new Util($view, $params['uid']);
-
- // setup user, if user not ready force relogin
- if (Helper::setupUser($util, $params['password']) === false) {
- return false;
- }
-
- $session = $util->initEncryption($params);
-
- // Check if first-run file migration has already been performed
- $ready = false;
- $migrationStatus = $util->getMigrationStatus();
- if ($migrationStatus === Util::MIGRATION_OPEN && $session !== false) {
- $ready = $util->beginMigration();
- } elseif ($migrationStatus === Util::MIGRATION_IN_PROGRESS) {
- // refuse login as long as the initial encryption is running
- sleep(5);
- \OCP\User::logout();
- return false;
- }
-
- $result = true;
-
- // If migration not yet done
- if ($ready) {
-
- // Encrypt existing user files
- try {
- $result = $util->encryptAll('/' . $params['uid'] . '/' . 'files');
- } catch (\Exception $ex) {
- \OCP\Util::writeLog('Encryption library', 'Initial encryption failed! Error: ' . $ex->getMessage(), \OCP\Util::FATAL);
- $result = false;
- }
-
- if ($result) {
- \OC_Log::write(
- 'Encryption library', 'Encryption of existing files belonging to "' . $params['uid'] . '" completed'
- , \OC_Log::INFO
- );
- // Register successful migration in DB
- $util->finishMigration();
- } else {
- \OCP\Util::writeLog('Encryption library', 'Initial encryption failed!', \OCP\Util::FATAL);
- $util->resetMigrationStatus();
- \OCP\User::logout();
- }
- }
-
- return $result;
- }
-
- /**
- * remove keys from session during logout
- */
- public static function logout() {
- $session = new Session(new \OC\Files\View());
- $session->removeKeys();
- }
-
- /**
- * setup encryption backend upon user created
- * @note This method should never be called for users using client side encryption
- */
- public static function postCreateUser($params) {
-
- if (\OCP\App::isEnabled('files_encryption')) {
- $view = new \OC\Files\View('/');
- $util = new Util($view, $params['uid']);
- Helper::setupUser($util, $params['password']);
- }
- }
-
- /**
- * cleanup encryption backend upon user deleted
- * @note This method should never be called for users using client side encryption
- */
- public static function postDeleteUser($params) {
-
- if (\OCP\App::isEnabled('files_encryption')) {
- Keymanager::deletePublicKey(new \OC\Files\View(), $params['uid']);
- }
- }
-
- /**
- * If the password can't be changed within ownCloud, than update the key password in advance.
- */
- public static function preSetPassphrase($params) {
- if (\OCP\App::isEnabled('files_encryption')) {
- if ( ! \OC_User::canUserChangePassword($params['uid']) ) {
- self::setPassphrase($params);
- }
- }
- }
-
- /**
- * Change a user's encryption passphrase
- * @param array $params keys: uid, password
- */
- public static function setPassphrase($params) {
- if (\OCP\App::isEnabled('files_encryption') === false) {
- return true;
- }
-
- // Only attempt to change passphrase if server-side encryption
- // is in use (client-side encryption does not have access to
- // the necessary keys)
- if (Crypt::mode() === 'server') {
-
- $view = new \OC\Files\View('/');
- $session = new Session($view);
-
- // Get existing decrypted private key
- $privateKey = $session->getPrivateKey();
-
- if ($params['uid'] === \OCP\User::getUser() && $privateKey) {
-
- // Encrypt private key with new user pwd as passphrase
- $encryptedPrivateKey = Crypt::symmetricEncryptFileContent($privateKey, $params['password'], Helper::getCipher());
-
- // Save private key
- if ($encryptedPrivateKey) {
- Keymanager::setPrivateKey($encryptedPrivateKey, \OCP\User::getUser());
- } else {
- \OCP\Util::writeLog('files_encryption', 'Could not update users encryption password', \OCP\Util::ERROR);
- }
-
- // 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 reencrypt file keys
-
- $user = $params['uid'];
- $util = new Util($view, $user);
- $recoveryPassword = isset($params['recoveryPassword']) ? $params['recoveryPassword'] : null;
-
- // 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 (($util->recoveryEnabledForUser() && $recoveryPassword)
- || !$util->userKeysExists()
- || !$view->file_exists($user . '/files')) {
-
- // backup old keys
- $util->backupAllKeys('recovery');
-
- $newUserPassword = $params['password'];
-
- // make sure that the users home is mounted
- \OC\Files\Filesystem::initMountPoints($user);
-
- $keypair = Crypt::createKeypair();
-
- // Disable encryption proxy to prevent recursive calls
- $proxyStatus = \OC_FileProxy::$enabled;
- \OC_FileProxy::$enabled = false;
-
- // Save public key
- Keymanager::setPublicKey($keypair['publicKey'], $user);
-
- // Encrypt private key with new password
- $encryptedKey = Crypt::symmetricEncryptFileContent($keypair['privateKey'], $newUserPassword, Helper::getCipher());
- if ($encryptedKey) {
- Keymanager::setPrivateKey($encryptedKey, $user);
-
- if ($recoveryPassword) { // if recovery key is set we can re-encrypt the key files
- $util = new Util($view, $user);
- $util->recoverUsersFiles($recoveryPassword);
- }
- } else {
- \OCP\Util::writeLog('files_encryption', 'Could not update users encryption password', \OCP\Util::ERROR);
- }
-
- \OC_FileProxy::$enabled = $proxyStatus;
- }
- }
- }
- }
-
- /**
- * after password reset we create a new key pair for the user
- *
- * @param array $params
- */
- public static function postPasswordReset($params) {
- $uid = $params['uid'];
- $password = $params['password'];
-
- $util = new Util(new \OC\Files\View(), $uid);
- $util->replaceUserKeys($password);
- }
-
- /*
- * check if files can be encrypted to every user.
- */
- /**
- * @param array $params
- */
- public static function preShared($params) {
-
- if (\OCP\App::isEnabled('files_encryption') === false) {
- return true;
- }
-
- $l = new \OC_L10N('files_encryption');
- $users = array();
- $view = new \OC\Files\View('/');
-
- switch ($params['shareType']) {
- case \OCP\Share::SHARE_TYPE_USER:
- $users[] = $params['shareWith'];
- break;
- case \OCP\Share::SHARE_TYPE_GROUP:
- $users = \OC_Group::usersInGroup($params['shareWith']);
- break;
- }
-
- $notConfigured = array();
- foreach ($users as $user) {
- if (!Keymanager::publicKeyExists($view, $user)) {
- $notConfigured[] = $user;
- }
- }
-
- if (count($notConfigured) > 0) {
- $params['run'] = false;
- $params['error'] = $l->t('Following users are not set up for encryption:') . ' ' . join(', ' , $notConfigured);
- }
-
- }
-
- /**
- * update share keys if a file was shared
- */
- public static function postShared($params) {
-
- if (\OCP\App::isEnabled('files_encryption') === false) {
- return true;
- }
-
- if ($params['itemType'] === 'file' || $params['itemType'] === 'folder') {
-
- $path = \OC\Files\Filesystem::getPath($params['fileSource']);
-
- self::updateKeyfiles($path);
- }
- }
-
- /**
- * update keyfiles and share keys recursively
- *
- * @param string $path to the file/folder
- */
- private static function updateKeyfiles($path) {
- $view = new \OC\Files\View('/');
- $userId = \OCP\User::getUser();
- $session = new Session($view);
- $util = new Util($view, $userId);
- $sharingEnabled = \OCP\Share::isEnabled();
-
- $mountManager = \OC\Files\Filesystem::getMountManager();
- $mount = $mountManager->find('/' . $userId . '/files' . $path);
- $mountPoint = $mount->getMountPoint();
-
- // if a folder was shared, get a list of all (sub-)folders
- if ($view->is_dir('/' . $userId . '/files' . $path)) {
- $allFiles = $util->getAllFiles($path, $mountPoint);
- } else {
- $allFiles = array($path);
- }
-
- foreach ($allFiles as $path) {
- $usersSharing = $util->getSharingUsersArray($sharingEnabled, $path);
- $util->setSharedFileKeyfiles($session, $usersSharing, $path);
- }
- }
-
- /**
- * unshare file/folder from a user with whom you shared the file before
- */
- public static function postUnshare($params) {
-
- if (\OCP\App::isEnabled('files_encryption') === false) {
- return true;
- }
-
- if ($params['itemType'] === 'file' || $params['itemType'] === 'folder') {
-
- $view = new \OC\Files\View('/');
- $userId = $params['uidOwner'];
- $userView = new \OC\Files\View('/' . $userId . '/files');
- $util = new Util($view, $userId);
- $path = $userView->getPath($params['fileSource']);
-
- // for group shares get a list of the group members
- if ($params['shareType'] === \OCP\Share::SHARE_TYPE_GROUP) {
- $userIds = \OC_Group::usersInGroup($params['shareWith']);
- } else {
- if ($params['shareType'] === \OCP\Share::SHARE_TYPE_LINK || $params['shareType'] === \OCP\Share::SHARE_TYPE_REMOTE) {
- $userIds = array($util->getPublicShareKeyId());
- } else {
- $userIds = array($params['shareWith']);
- }
- }
-
- $mountManager = \OC\Files\Filesystem::getMountManager();
- $mount = $mountManager->find('/' . $userId . '/files' . $path);
- $mountPoint = $mount->getMountPoint();
-
- // if we unshare a folder we need a list of all (sub-)files
- if ($params['itemType'] === 'folder') {
- $allFiles = $util->getAllFiles($path, $mountPoint);
- } else {
- $allFiles = array($path);
- }
-
- foreach ($allFiles as $path) {
-
- // check if the user still has access to the file, otherwise delete share key
- $sharingUsers = $util->getSharingUsersArray(true, $path);
-
- // Unshare every user who no longer has access to the file
- $delUsers = array_diff($userIds, $sharingUsers);
- $keyPath = Keymanager::getKeyPath($view, $util, $path);
-
- // delete share key
- Keymanager::delShareKey($view, $delUsers, $keyPath, $userId, $path);
- }
-
- }
- }
-
- /**
- * mark file as renamed so that we know the original source after the file was renamed
- * @param array $params with the old path and the new path
- */
- public static function preRename($params) {
- self::preRenameOrCopy($params, 'rename');
- }
-
- /**
- * mark file as copied so that we know the original source after the file was copied
- * @param array $params with the old path and the new path
- */
- public static function preCopy($params) {
- self::preRenameOrCopy($params, 'copy');
- }
-
- private static function preRenameOrCopy($params, $operation) {
- $user = \OCP\User::getUser();
- $view = new \OC\Files\View('/');
- $util = new Util($view, $user);
-
- // we only need to rename the keys if the rename happens on the same mountpoint
- // otherwise we perform a stream copy, so we get a new set of keys
- $mp1 = $view->getMountPoint('/' . $user . '/files/' . $params['oldpath']);
- $mp2 = $view->getMountPoint('/' . $user . '/files/' . $params['newpath']);
-
- $oldKeysPath = Keymanager::getKeyPath($view, $util, $params['oldpath']);
-
- if ($mp1 === $mp2) {
- self::$renamedFiles[$params['oldpath']] = array(
- 'operation' => $operation,
- 'oldKeysPath' => $oldKeysPath,
- );
- } else {
- self::$renamedFiles[$params['oldpath']] = array(
- 'operation' => 'cleanup',
- 'oldKeysPath' => $oldKeysPath,
- );
- }
- }
-
- /**
- * after a file is renamed/copied, rename/copy its keyfile and share-keys also fix the file size and fix also the sharing
- *
- * @param array $params array with oldpath and newpath
- */
- public static function postRenameOrCopy($params) {
-
- if (\OCP\App::isEnabled('files_encryption') === false) {
- return true;
- }
-
- $view = new \OC\Files\View('/');
- $userId = \OCP\User::getUser();
- $util = new Util($view, $userId);
-
- if (isset(self::$renamedFiles[$params['oldpath']]['operation']) &&
- isset(self::$renamedFiles[$params['oldpath']]['oldKeysPath'])) {
- $operation = self::$renamedFiles[$params['oldpath']]['operation'];
- $oldKeysPath = self::$renamedFiles[$params['oldpath']]['oldKeysPath'];
- unset(self::$renamedFiles[$params['oldpath']]);
- if ($operation === 'cleanup') {
- return $view->unlink($oldKeysPath);
- }
- } else {
- \OCP\Util::writeLog('Encryption library', "can't get path and owner from the file before it was renamed", \OCP\Util::DEBUG);
- return false;
- }
-
- list($ownerNew, $pathNew) = $util->getUidAndFilename($params['newpath']);
-
- if ($util->isSystemWideMountPoint($pathNew)) {
- $newKeysPath = 'files_encryption/keys/' . $pathNew;
- } else {
- $newKeysPath = $ownerNew . '/files_encryption/keys/' . $pathNew;
- }
-
- // create key folders if it doesn't exists
- if (!$view->file_exists(dirname($newKeysPath))) {
- $view->mkdir(dirname($newKeysPath));
- }
-
- $view->$operation($oldKeysPath, $newKeysPath);
-
- // update sharing-keys
- self::updateKeyfiles($params['newpath']);
- }
-
- /**
- * set migration status and the init status back to '0' so that all new files get encrypted
- * if the app gets enabled again
- * @param array $params contains the app ID
- */
- public static function preDisable($params) {
- if ($params['app'] === 'files_encryption') {
-
- \OC::$server->getConfig()->deleteAppFromAllUsers('files_encryption');
-
- $session = new Session(new \OC\Files\View('/'));
- $session->setInitialized(Session::NOT_INITIALIZED);
- }
- }
-
- /**
- * set the init status to 'NOT_INITIALIZED' (0) if the app gets enabled
- * @param array $params contains the app ID
- */
- public static function postEnable($params) {
- if ($params['app'] === 'files_encryption') {
- $session = new Session(new \OC\Files\View('/'));
- $session->setInitialized(Session::NOT_INITIALIZED);
- }
- }
-
- /**
- * if the file was really deleted we remove the encryption keys
- * @param array $params
- * @return boolean|null
- */
- public static function postDelete($params) {
-
- $path = $params[\OC\Files\Filesystem::signal_param_path];
-
- if (!isset(self::$deleteFiles[$path])) {
- return true;
- }
-
- $deletedFile = self::$deleteFiles[$path];
- $keyPath = $deletedFile['keyPath'];
-
- // we don't need to remember the file any longer
- unset(self::$deleteFiles[$path]);
-
- $view = new \OC\Files\View('/');
-
- // return if the file still exists and wasn't deleted correctly
- if ($view->file_exists('/' . \OCP\User::getUser() . '/files/' . $path)) {
- return true;
- }
-
- // Delete keyfile & shareKey so it isn't orphaned
- $view->unlink($keyPath);
-
- }
-
- /**
- * remember the file which should be deleted and it's owner
- * @param array $params
- * @return boolean|null
- */
- public static function preDelete($params) {
- $view = new \OC\Files\View('/');
- $path = $params[\OC\Files\Filesystem::signal_param_path];
-
- // skip this method if the trash bin is enabled or if we delete a file
- // outside of /data/user/files
- if (\OCP\App::isEnabled('files_trashbin')) {
- return true;
- }
-
- $util = new Util($view, \OCP\USER::getUser());
-
- $keysPath = Keymanager::getKeyPath($view, $util, $path);
-
- self::$deleteFiles[$path] = array(
- 'keyPath' => $keysPath);
- }
-
- /**
- * unmount file from yourself
- * remember files/folders which get unmounted
- */
- public static function preUnmount($params) {
- $view = new \OC\Files\View('/');
- $user = \OCP\User::getUser();
- $path = $params[\OC\Files\Filesystem::signal_param_path];
-
- $util = new Util($view, $user);
- list($owner, $ownerPath) = $util->getUidAndFilename($path);
-
- $keysPath = Keymanager::getKeyPath($view, $util, $path);
-
- self::$unmountedFiles[$path] = array(
- 'keyPath' => $keysPath,
- 'owner' => $owner,
- 'ownerPath' => $ownerPath
- );
- }
-
- /**
- * unmount file from yourself
- */
- public static function postUnmount($params) {
-
- $path = $params[\OC\Files\Filesystem::signal_param_path];
- $user = \OCP\User::getUser();
-
- if (!isset(self::$unmountedFiles[$path])) {
- return true;
- }
-
- $umountedFile = self::$unmountedFiles[$path];
- $keyPath = $umountedFile['keyPath'];
- $owner = $umountedFile['owner'];
- $ownerPath = $umountedFile['ownerPath'];
-
- $view = new \OC\Files\View();
-
- // we don't need to remember the file any longer
- unset(self::$unmountedFiles[$path]);
-
- // check if the user still has access to the file, otherwise delete share key
- $sharingUsers = \OCP\Share::getUsersSharingFile($path, $user);
- if (!in_array($user, $sharingUsers['users'])) {
- Keymanager::delShareKey($view, array($user), $keyPath, $owner, $ownerPath);
- }
- }
-
-}
+<?php
+
+/**
+ * ownCloud
+ *
+ * @copyright (C) 2014 ownCloud, Inc.
+ *
+ * @author Sam Tuke <samtuke@owncloud.org>
+ * @author Bjoern Schiessle <schiessle@owncloud.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This library 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 library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCA\Files_Encryption;
+
+/**
+ * Class for hook specific logic
+ */
+class Hooks {
+
+ // file for which we want to rename the keys after the rename operation was successful
+ private static $renamedFiles = array();
+ // file for which we want to delete the keys after the delete operation was successful
+ private static $deleteFiles = array();
+ // file for which we want to delete the keys after the delete operation was successful
+ private static $unmountedFiles = array();
+
+ /**
+ * Startup encryption backend upon user login
+ * @note This method should never be called for users using client side encryption
+ */
+ public static function login($params) {
+
+ if (\OCP\App::isEnabled('files_encryption') === false) {
+ return true;
+ }
+
+
+ $l = new \OC_L10N('files_encryption');
+
+ $view = new \OC\Files\View('/');
+
+ // ensure filesystem is loaded
+ if (!\OC\Files\Filesystem::$loaded) {
+ \OC_Util::setupFS($params['uid']);
+ }
+
+ $privateKey = Keymanager::getPrivateKey($view, $params['uid']);
+
+ // if no private key exists, check server configuration
+ if (!$privateKey) {
+ //check if all requirements are met
+ if (!Helper::checkRequirements() || !Helper::checkConfiguration()) {
+ $error_msg = $l->t("Missing requirements.");
+ $hint = $l->t('Please make sure that OpenSSL together with the PHP extension is enabled and configured properly. For now, the encryption app has been disabled.');
+ \OC_App::disable('files_encryption');
+ \OCP\Util::writeLog('Encryption library', $error_msg . ' ' . $hint, \OCP\Util::ERROR);
+ \OCP\Template::printErrorPage($error_msg, $hint);
+ }
+ }
+
+ $util = new Util($view, $params['uid']);
+
+ // setup user, if user not ready force relogin
+ if (Helper::setupUser($util, $params['password']) === false) {
+ return false;
+ }
+
+ $session = $util->initEncryption($params);
+
+ // Check if first-run file migration has already been performed
+ $ready = false;
+ $migrationStatus = $util->getMigrationStatus();
+ if ($migrationStatus === Util::MIGRATION_OPEN && $session !== false) {
+ $ready = $util->beginMigration();
+ } elseif ($migrationStatus === Util::MIGRATION_IN_PROGRESS) {
+ // refuse login as long as the initial encryption is running
+ sleep(5);
+ \OCP\User::logout();
+ return false;
+ }
+
+ $result = true;
+
+ // If migration not yet done
+ if ($ready) {
+
+ // Encrypt existing user files
+ try {
+ $result = $util->encryptAll('/' . $params['uid'] . '/' . 'files');
+ } catch (\Exception $ex) {
+ \OCP\Util::writeLog('Encryption library', 'Initial encryption failed! Error: ' . $ex->getMessage(), \OCP\Util::FATAL);
+ $result = false;
+ }
+
+ if ($result) {
+ \OC_Log::write(
+ 'Encryption library', 'Encryption of existing files belonging to "' . $params['uid'] . '" completed'
+ , \OC_Log::INFO
+ );
+ // Register successful migration in DB
+ $util->finishMigration();
+ } else {
+ \OCP\Util::writeLog('Encryption library', 'Initial encryption failed!', \OCP\Util::FATAL);
+ $util->resetMigrationStatus();
+ \OCP\User::logout();
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * remove keys from session during logout
+ */
+ public static function logout() {
+ $session = new Session(new \OC\Files\View());
+ $session->removeKeys();
+ }
+
+ /**
+ * setup encryption backend upon user created
+ * @note This method should never be called for users using client side encryption
+ */
+ public static function postCreateUser($params) {
+
+ if (\OCP\App::isEnabled('files_encryption')) {
+ $view = new \OC\Files\View('/');
+ $util = new Util($view, $params['uid']);
+ Helper::setupUser($util, $params['password']);
+ }
+ }
+
+ /**
+ * cleanup encryption backend upon user deleted
+ * @note This method should never be called for users using client side encryption
+ */
+ public static function postDeleteUser($params) {
+
+ if (\OCP\App::isEnabled('files_encryption')) {
+ Keymanager::deletePublicKey(new \OC\Files\View(), $params['uid']);
+ }
+ }
+
+ /**
+ * If the password can't be changed within ownCloud, than update the key password in advance.
+ */
+ public static function preSetPassphrase($params) {
+ if (\OCP\App::isEnabled('files_encryption')) {
+ if ( ! \OC_User::canUserChangePassword($params['uid']) ) {
+ self::setPassphrase($params);
+ }
+ }
+ }
+
+ /**
+ * Change a user's encryption passphrase
+ * @param array $params keys: uid, password
+ */
+ public static function setPassphrase($params) {
+ if (\OCP\App::isEnabled('files_encryption') === false) {
+ return true;
+ }
+
+ // Only attempt to change passphrase if server-side encryption
+ // is in use (client-side encryption does not have access to
+ // the necessary keys)
+ if (Crypt::mode() === 'server') {
+
+ $view = new \OC\Files\View('/');
+ $session = new Session($view);
+
+ // Get existing decrypted private key
+ $privateKey = $session->getPrivateKey();
+
+ if ($params['uid'] === \OCP\User::getUser() && $privateKey) {
+
+ // Encrypt private key with new user pwd as passphrase
+ $encryptedPrivateKey = Crypt::symmetricEncryptFileContent($privateKey, $params['password'], Helper::getCipher());
+
+ // Save private key
+ if ($encryptedPrivateKey) {
+ Keymanager::setPrivateKey($encryptedPrivateKey, \OCP\User::getUser());
+ } else {
+ \OCP\Util::writeLog('files_encryption', 'Could not update users encryption password', \OCP\Util::ERROR);
+ }
+
+ // 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 reencrypt file keys
+
+ $user = $params['uid'];
+ $util = new Util($view, $user);
+ $recoveryPassword = isset($params['recoveryPassword']) ? $params['recoveryPassword'] : null;
+
+ // 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 (($util->recoveryEnabledForUser() && $recoveryPassword)
+ || !$util->userKeysExists()
+ || !$view->file_exists($user . '/files')) {
+
+ // backup old keys
+ $util->backupAllKeys('recovery');
+
+ $newUserPassword = $params['password'];
+
+ // make sure that the users home is mounted
+ \OC\Files\Filesystem::initMountPoints($user);
+
+ $keypair = Crypt::createKeypair();
+
+ // Disable encryption proxy to prevent recursive calls
+ $proxyStatus = \OC_FileProxy::$enabled;
+ \OC_FileProxy::$enabled = false;
+
+ // Save public key
+ Keymanager::setPublicKey($keypair['publicKey'], $user);
+
+ // Encrypt private key with new password
+ $encryptedKey = Crypt::symmetricEncryptFileContent($keypair['privateKey'], $newUserPassword, Helper::getCipher());
+ if ($encryptedKey) {
+ Keymanager::setPrivateKey($encryptedKey, $user);
+
+ if ($recoveryPassword) { // if recovery key is set we can re-encrypt the key files
+ $util = new Util($view, $user);
+ $util->recoverUsersFiles($recoveryPassword);
+ }
+ } else {
+ \OCP\Util::writeLog('files_encryption', 'Could not update users encryption password', \OCP\Util::ERROR);
+ }
+
+ \OC_FileProxy::$enabled = $proxyStatus;
+ }
+ }
+ }
+ }
+
+ /**
+ * after password reset we create a new key pair for the user
+ *
+ * @param array $params
+ */
+ public static function postPasswordReset($params) {
+ $uid = $params['uid'];
+ $password = $params['password'];
+
+ $util = new Util(new \OC\Files\View(), $uid);
+ $util->replaceUserKeys($password);
+ }
+
+ /*
+ * check if files can be encrypted to every user.
+ */
+ /**
+ * @param array $params
+ */
+ public static function preShared($params) {
+
+ if (\OCP\App::isEnabled('files_encryption') === false) {
+ return true;
+ }
+
+ $l = new \OC_L10N('files_encryption');
+ $users = array();
+ $view = new \OC\Files\View('/');
+
+ switch ($params['shareType']) {
+ case \OCP\Share::SHARE_TYPE_USER:
+ $users[] = $params['shareWith'];
+ break;
+ case \OCP\Share::SHARE_TYPE_GROUP:
+ $users = \OC_Group::usersInGroup($params['shareWith']);
+ break;
+ }
+
+ $notConfigured = array();
+ foreach ($users as $user) {
+ if (!Keymanager::publicKeyExists($view, $user)) {
+ $notConfigured[] = $user;
+ }
+ }
+
+ if (count($notConfigured) > 0) {
+ $params['run'] = false;
+ $params['error'] = $l->t('Following users are not set up for encryption:') . ' ' . join(', ' , $notConfigured);
+ }
+
+ }
+
+ /**
+ * update share keys if a file was shared
+ */
+ public static function postShared($params) {
+
+ if (\OCP\App::isEnabled('files_encryption') === false) {
+ return true;
+ }
+
+ if ($params['itemType'] === 'file' || $params['itemType'] === 'folder') {
+
+ $path = \OC\Files\Filesystem::getPath($params['fileSource']);
+
+ self::updateKeyfiles($path);
+ }
+ }
+
+ /**
+ * update keyfiles and share keys recursively
+ *
+ * @param string $path to the file/folder
+ */
+ private static function updateKeyfiles($path) {
+ $view = new \OC\Files\View('/');
+ $userId = \OCP\User::getUser();
+ $session = new Session($view);
+ $util = new Util($view, $userId);
+ $sharingEnabled = \OCP\Share::isEnabled();
+
+ $mountManager = \OC\Files\Filesystem::getMountManager();
+ $mount = $mountManager->find('/' . $userId . '/files' . $path);
+ $mountPoint = $mount->getMountPoint();
+
+ // if a folder was shared, get a list of all (sub-)folders
+ if ($view->is_dir('/' . $userId . '/files' . $path)) {
+ $allFiles = $util->getAllFiles($path, $mountPoint);
+ } else {
+ $allFiles = array($path);
+ }
+
+ foreach ($allFiles as $path) {
+ $usersSharing = $util->getSharingUsersArray($sharingEnabled, $path);
+ $util->setSharedFileKeyfiles($session, $usersSharing, $path);
+ }
+ }
+
+ /**
+ * unshare file/folder from a user with whom you shared the file before
+ */
+ public static function postUnshare($params) {
+
+ if (\OCP\App::isEnabled('files_encryption') === false) {
+ return true;
+ }
+
+ if ($params['itemType'] === 'file' || $params['itemType'] === 'folder') {
+
+ $view = new \OC\Files\View('/');
+ $userId = $params['uidOwner'];
+ $userView = new \OC\Files\View('/' . $userId . '/files');
+ $util = new Util($view, $userId);
+ $path = $userView->getPath($params['fileSource']);
+
+ // for group shares get a list of the group members
+ if ($params['shareType'] === \OCP\Share::SHARE_TYPE_GROUP) {
+ $userIds = \OC_Group::usersInGroup($params['shareWith']);
+ } else {
+ if ($params['shareType'] === \OCP\Share::SHARE_TYPE_LINK || $params['shareType'] === \OCP\Share::SHARE_TYPE_REMOTE) {
+ $userIds = array($util->getPublicShareKeyId());
+ } else {
+ $userIds = array($params['shareWith']);
+ }
+ }
+
+ $mountManager = \OC\Files\Filesystem::getMountManager();
+ $mount = $mountManager->find('/' . $userId . '/files' . $path);
+ $mountPoint = $mount->getMountPoint();
+
+ // if we unshare a folder we need a list of all (sub-)files
+ if ($params['itemType'] === 'folder') {
+ $allFiles = $util->getAllFiles($path, $mountPoint);
+ } else {
+ $allFiles = array($path);
+ }
+
+ foreach ($allFiles as $path) {
+
+ // check if the user still has access to the file, otherwise delete share key
+ $sharingUsers = $util->getSharingUsersArray(true, $path);
+
+ // Unshare every user who no longer has access to the file
+ $delUsers = array_diff($userIds, $sharingUsers);
+ $keyPath = Keymanager::getKeyPath($view, $util, $path);
+
+ // delete share key
+ Keymanager::delShareKey($view, $delUsers, $keyPath, $userId, $path);
+ }
+
+ }
+ }
+
+ /**
+ * mark file as renamed so that we know the original source after the file was renamed
+ * @param array $params with the old path and the new path
+ */
+ public static function preRename($params) {
+ self::preRenameOrCopy($params, 'rename');
+ }
+
+ /**
+ * mark file as copied so that we know the original source after the file was copied
+ * @param array $params with the old path and the new path
+ */
+ public static function preCopy($params) {
+ self::preRenameOrCopy($params, 'copy');
+ }
+
+ private static function preRenameOrCopy($params, $operation) {
+ $user = \OCP\User::getUser();
+ $view = new \OC\Files\View('/');
+ $util = new Util($view, $user);
+
+ // we only need to rename the keys if the rename happens on the same mountpoint
+ // otherwise we perform a stream copy, so we get a new set of keys
+ $oldPath = \OC\Files\Filesystem::normalizePath('/' . $user . '/files/' . $params['oldpath']);
+ $newPath = \OC\Files\Filesystem::normalizePath('/' . $user . '/files/' . $params['newpath']);
+ $mp1 = $view->getMountPoint($oldPath);
+ $mp2 = $view->getMountPoint($newPath);
+
+ $oldKeysPath = Keymanager::getKeyPath($view, $util, $params['oldpath']);
+
+ if ($mp1 === $mp2) {
+ self::$renamedFiles[$params['oldpath']] = array(
+ 'operation' => $operation,
+ 'oldKeysPath' => $oldKeysPath,
+ );
+ } elseif ($mp1 !== $oldPath . '/') {
+ self::$renamedFiles[$params['oldpath']] = array(
+ 'operation' => 'cleanup',
+ 'oldKeysPath' => $oldKeysPath,
+ );
+ }
+ }
+
+ /**
+ * after a file is renamed/copied, rename/copy its keyfile and share-keys also fix the file size and fix also the sharing
+ *
+ * @param array $params array with oldpath and newpath
+ */
+ public static function postRenameOrCopy($params) {
+
+ if (\OCP\App::isEnabled('files_encryption') === false) {
+ return true;
+ }
+
+ $view = new \OC\Files\View('/');
+ $userId = \OCP\User::getUser();
+ $util = new Util($view, $userId);
+
+ if (isset(self::$renamedFiles[$params['oldpath']]['operation']) &&
+ isset(self::$renamedFiles[$params['oldpath']]['oldKeysPath'])) {
+ $operation = self::$renamedFiles[$params['oldpath']]['operation'];
+ $oldKeysPath = self::$renamedFiles[$params['oldpath']]['oldKeysPath'];
+ unset(self::$renamedFiles[$params['oldpath']]);
+ if ($operation === 'cleanup') {
+ return $view->unlink($oldKeysPath);
+ }
+ } else {
+ \OCP\Util::writeLog('Encryption library', "can't get path and owner from the file before it was renamed", \OCP\Util::DEBUG);
+ return false;
+ }
+
+ list($ownerNew, $pathNew) = $util->getUidAndFilename($params['newpath']);
+
+ if ($util->isSystemWideMountPoint($pathNew)) {
+ $newKeysPath = 'files_encryption/keys/' . $pathNew;
+ } else {
+ $newKeysPath = $ownerNew . '/files_encryption/keys/' . $pathNew;
+ }
+
+ // create key folders if it doesn't exists
+ if (!$view->file_exists(dirname($newKeysPath))) {
+ $view->mkdir(dirname($newKeysPath));
+ }
+
+ $view->$operation($oldKeysPath, $newKeysPath);
+
+ // update sharing-keys
+ self::updateKeyfiles($params['newpath']);
+ }
+
+ /**
+ * set migration status and the init status back to '0' so that all new files get encrypted
+ * if the app gets enabled again
+ * @param array $params contains the app ID
+ */
+ public static function preDisable($params) {
+ if ($params['app'] === 'files_encryption') {
+
+ \OC::$server->getConfig()->deleteAppFromAllUsers('files_encryption');
+
+ $session = new Session(new \OC\Files\View('/'));
+ $session->setInitialized(Session::NOT_INITIALIZED);
+ }
+ }
+
+ /**
+ * set the init status to 'NOT_INITIALIZED' (0) if the app gets enabled
+ * @param array $params contains the app ID
+ */
+ public static function postEnable($params) {
+ if ($params['app'] === 'files_encryption') {
+ $session = new Session(new \OC\Files\View('/'));
+ $session->setInitialized(Session::NOT_INITIALIZED);
+ }
+ }
+
+ /**
+ * if the file was really deleted we remove the encryption keys
+ * @param array $params
+ * @return boolean|null
+ */
+ public static function postDelete($params) {
+
+ $path = $params[\OC\Files\Filesystem::signal_param_path];
+
+ if (!isset(self::$deleteFiles[$path])) {
+ return true;
+ }
+
+ $deletedFile = self::$deleteFiles[$path];
+ $keyPath = $deletedFile['keyPath'];
+
+ // we don't need to remember the file any longer
+ unset(self::$deleteFiles[$path]);
+
+ $view = new \OC\Files\View('/');
+
+ // return if the file still exists and wasn't deleted correctly
+ if ($view->file_exists('/' . \OCP\User::getUser() . '/files/' . $path)) {
+ return true;
+ }
+
+ // Delete keyfile & shareKey so it isn't orphaned
+ $view->unlink($keyPath);
+
+ }
+
+ /**
+ * remember the file which should be deleted and it's owner
+ * @param array $params
+ * @return boolean|null
+ */
+ public static function preDelete($params) {
+ $view = new \OC\Files\View('/');
+ $path = $params[\OC\Files\Filesystem::signal_param_path];
+
+ // skip this method if the trash bin is enabled or if we delete a file
+ // outside of /data/user/files
+ if (\OCP\App::isEnabled('files_trashbin')) {
+ return true;
+ }
+
+ $util = new Util($view, \OCP\USER::getUser());
+
+ $keysPath = Keymanager::getKeyPath($view, $util, $path);
+
+ self::$deleteFiles[$path] = array(
+ 'keyPath' => $keysPath);
+ }
+
+ /**
+ * unmount file from yourself
+ * remember files/folders which get unmounted
+ */
+ public static function preUnmount($params) {
+ $view = new \OC\Files\View('/');
+ $user = \OCP\User::getUser();
+ $path = $params[\OC\Files\Filesystem::signal_param_path];
+
+ $util = new Util($view, $user);
+ list($owner, $ownerPath) = $util->getUidAndFilename($path);
+
+ $keysPath = Keymanager::getKeyPath($view, $util, $path);
+
+ self::$unmountedFiles[$path] = array(
+ 'keyPath' => $keysPath,
+ 'owner' => $owner,
+ 'ownerPath' => $ownerPath
+ );
+ }
+
+ /**
+ * unmount file from yourself
+ */
+ public static function postUnmount($params) {
+
+ $path = $params[\OC\Files\Filesystem::signal_param_path];
+ $user = \OCP\User::getUser();
+
+ if (!isset(self::$unmountedFiles[$path])) {
+ return true;
+ }
+
+ $umountedFile = self::$unmountedFiles[$path];
+ $keyPath = $umountedFile['keyPath'];
+ $owner = $umountedFile['owner'];
+ $ownerPath = $umountedFile['ownerPath'];
+
+ $view = new \OC\Files\View();
+
+ // we don't need to remember the file any longer
+ unset(self::$unmountedFiles[$path]);
+
+ // check if the user still has access to the file, otherwise delete share key
+ $sharingUsers = \OCP\Share::getUsersSharingFile($path, $user);
+ if (!in_array($user, $sharingUsers['users'])) {
+ Keymanager::delShareKey($view, array($user), $keyPath, $owner, $ownerPath);
+ }
+ }
+
+}
diff --git a/apps/files_encryption/tests/share.php b/apps/files_encryption/tests/share.php
index d29e6a191c8..b6f5a1ffd20 100755
--- a/apps/files_encryption/tests/share.php
+++ b/apps/files_encryption/tests/share.php
@@ -1032,7 +1032,7 @@ class Share extends TestCase {
/**
- * test moving a shared file out of the Shared folder
+ * test rename a shared file mount point
*/
function testRename() {
@@ -1055,7 +1055,10 @@ class Share extends TestCase {
// share the file
\OCP\Share::shareItem('file', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_USER, self::TEST_ENCRYPTION_SHARE_USER2, \OCP\Constants::PERMISSION_ALL);
- // check if share key for user2 exists
+ // check if share key for user1 and user2 exists
+ $this->assertTrue($this->view->file_exists(
+ '/' . self::TEST_ENCRYPTION_SHARE_USER1 . '/files_encryption/keys/'
+ . $this->filename . '/' . self::TEST_ENCRYPTION_SHARE_USER1 . '.shareKey'));
$this->assertTrue($this->view->file_exists(
'/' . self::TEST_ENCRYPTION_SHARE_USER1 . '/files_encryption/keys/'
. $this->filename . '/' . self::TEST_ENCRYPTION_SHARE_USER2 . '.shareKey'));
@@ -1073,9 +1076,10 @@ class Share extends TestCase {
// check if data is the same as we previously written
$this->assertEquals($this->dataShort, $retrievedCryptedFile);
+ \OC\Files\Filesystem::mkdir($this->folder1);
+
// move the file to a subfolder
- $this->view->rename('/' . self::TEST_ENCRYPTION_SHARE_USER2 . '/files/' . $this->filename,
- '/' . self::TEST_ENCRYPTION_SHARE_USER2 . '/files/' . $this->folder1 . $this->filename);
+ \OC\Files\Filesystem::rename($this->filename, $this->folder1 . $this->filename);
// check if we can read the moved file
$retrievedRenamedFile = $this->view->file_get_contents(
@@ -1084,11 +1088,89 @@ class Share extends TestCase {
// check if data is the same as we previously written
$this->assertEquals($this->dataShort, $retrievedRenamedFile);
+ // check if share key for user2 and user1 still exists
+ $this->assertTrue($this->view->file_exists(
+ '/' . self::TEST_ENCRYPTION_SHARE_USER1 . '/files_encryption/keys/'
+ . $this->filename . '/' . self::TEST_ENCRYPTION_SHARE_USER1 . '.shareKey'));
+ $this->assertTrue($this->view->file_exists(
+ '/' . self::TEST_ENCRYPTION_SHARE_USER1 . '/files_encryption/keys/'
+ . $this->filename . '/' . self::TEST_ENCRYPTION_SHARE_USER2 . '.shareKey'));
+
// cleanup
self::loginHelper(self::TEST_ENCRYPTION_SHARE_USER1);
$this->view->unlink('/' . self::TEST_ENCRYPTION_SHARE_USER1 . '/files/' . $this->filename);
}
+ function testRenameGroupShare() {
+ // login as admin
+ self::loginHelper(self::TEST_ENCRYPTION_SHARE_USER1);
+
+ // save file with content
+ $cryptedFile = file_put_contents('crypt:///' . self::TEST_ENCRYPTION_SHARE_USER1 . '/files/' . $this->filename, $this->dataShort);
+
+ // test that data was successfully written
+ $this->assertTrue(is_int($cryptedFile));
+
+ // get the file info from previous created file
+ $fileInfo = $this->view->getFileInfo(
+ '/' . self::TEST_ENCRYPTION_SHARE_USER1 . '/files/' . $this->filename);
+
+ // check if we have a valid file info
+ $this->assertTrue($fileInfo instanceof \OC\Files\FileInfo);
+
+ // share the file
+ \OCP\Share::shareItem('file', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_GROUP, self::TEST_ENCRYPTION_SHARE_GROUP1, \OCP\Constants::PERMISSION_ALL);
+
+ // check if share key for user1, user3 and user4 exists
+ $this->assertTrue($this->view->file_exists(
+ '/' . self::TEST_ENCRYPTION_SHARE_USER1 . '/files_encryption/keys/'
+ . $this->filename . '/' . self::TEST_ENCRYPTION_SHARE_USER1 . '.shareKey'));
+ $this->assertTrue($this->view->file_exists(
+ '/' . self::TEST_ENCRYPTION_SHARE_USER1 . '/files_encryption/keys/'
+ . $this->filename . '/' . self::TEST_ENCRYPTION_SHARE_USER3 . '.shareKey'));
+ $this->assertTrue($this->view->file_exists(
+ '/' . self::TEST_ENCRYPTION_SHARE_USER1 . '/files_encryption/keys/'
+ . $this->filename . '/' . self::TEST_ENCRYPTION_SHARE_USER4 . '.shareKey'));
+
+
+ // login as user2
+ self::loginHelper(self::TEST_ENCRYPTION_SHARE_USER3);
+
+ $this->assertTrue(\OC\Files\Filesystem::file_exists($this->filename));
+
+ // get file contents
+ $retrievedCryptedFile = \OC\Files\Filesystem::file_get_contents($this->filename);
+
+ // check if data is the same as we previously written
+ $this->assertEquals($this->dataShort, $retrievedCryptedFile);
+
+ \OC\Files\Filesystem::mkdir($this->folder1);
+
+ // move the file to a subfolder
+ \OC\Files\Filesystem::rename($this->filename, $this->folder1 . $this->filename);
+
+ // check if we can read the moved file
+ $retrievedRenamedFile = \OC\Files\Filesystem::file_get_contents($this->folder1 . $this->filename);
+
+ // check if data is the same as we previously written
+ $this->assertEquals($this->dataShort, $retrievedRenamedFile);
+
+ // check if share key for user1, user3 and user4 still exists
+ $this->assertTrue($this->view->file_exists(
+ '/' . self::TEST_ENCRYPTION_SHARE_USER1 . '/files_encryption/keys/'
+ . $this->filename . '/' . self::TEST_ENCRYPTION_SHARE_USER1 . '.shareKey'));
+ $this->assertTrue($this->view->file_exists(
+ '/' . self::TEST_ENCRYPTION_SHARE_USER1 . '/files_encryption/keys/'
+ . $this->filename . '/' . self::TEST_ENCRYPTION_SHARE_USER3 . '.shareKey'));
+ $this->assertTrue($this->view->file_exists(
+ '/' . self::TEST_ENCRYPTION_SHARE_USER1 . '/files_encryption/keys/'
+ . $this->filename . '/' . self::TEST_ENCRYPTION_SHARE_USER4 . '.shareKey'));
+
+ // cleanup
+ self::loginHelper(self::TEST_ENCRYPTION_SHARE_USER1);
+ \OC\Files\Filesystem::unlink($this->filename);
+ }
+
/**
* test if additional share keys are added if we move a folder to a shared parent
* @medium