From 6cc40552b1df187fa3075d33d0ef0d4ff3910337 Mon Sep 17 00:00:00 2001 From: Bjoern Schiessle Date: Fri, 9 Jan 2015 15:39:36 +0100 Subject: [PATCH 1/2] don't move encryption keys if a mount point was renamed --- apps/files_encryption/lib/hooks.php | 1254 ++++++++++++++------------- 1 file changed, 628 insertions(+), 626 deletions(-) diff --git a/apps/files_encryption/lib/hooks.php b/apps/files_encryption/lib/hooks.php index 7ddde0a311..1ffcee5e74 100644 --- a/apps/files_encryption/lib/hooks.php +++ b/apps/files_encryption/lib/hooks.php @@ -1,626 +1,628 @@ - - * @author Bjoern Schiessle - * - * 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 . - * - */ - -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); - } - } - -} + + * @author Bjoern Schiessle + * + * 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 . + * + */ + +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); + } + } + +} From 0500d3a506e9296727eb9bbc5cb0f9f513d38f2e Mon Sep 17 00:00:00 2001 From: Bjoern Schiessle Date: Fri, 9 Jan 2015 15:39:51 +0100 Subject: [PATCH 2/2] unit tests --- apps/files_encryption/tests/share.php | 90 +++++++++++++++++++++++++-- 1 file changed, 86 insertions(+), 4 deletions(-) diff --git a/apps/files_encryption/tests/share.php b/apps/files_encryption/tests/share.php index d29e6a191c..b6f5a1ffd2 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