diff options
author | Thomas Müller <thomas.mueller@tmit.eu> | 2015-04-07 16:46:45 +0200 |
---|---|---|
committer | Thomas Müller <thomas.mueller@tmit.eu> | 2015-04-07 16:46:45 +0200 |
commit | 1fbf5d86df7ba4001ca826d9dfb8fad073924fde (patch) | |
tree | 9260b35011fabbbf69747419282d193fa7a9089c /apps/encryption | |
parent | 2182ae0d278f466e7f117b03bf4ebca0e6e9fe9b (diff) | |
parent | 2d2cb09715554926945de29b80f033905a219abd (diff) | |
download | nextcloud-server-1fbf5d86df7ba4001ca826d9dfb8fad073924fde.tar.gz nextcloud-server-1fbf5d86df7ba4001ca826d9dfb8fad073924fde.zip |
Merge pull request #14472 from owncloud/feature/wipencryptionapp
encryption 2.0 app
Diffstat (limited to 'apps/encryption')
37 files changed, 4335 insertions, 0 deletions
diff --git a/apps/encryption/appinfo/app.php b/apps/encryption/appinfo/app.php new file mode 100644 index 00000000000..240a1726715 --- /dev/null +++ b/apps/encryption/appinfo/app.php @@ -0,0 +1,27 @@ +<?php +/** + * @author Clark Tomlinson <clark@owncloud.com> + * @since 2/19/15, 9:52 AM + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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\AppInfo; + +$app = new Application(); +$app->registerEncryptionModule(); +$app->registerHooks(); +$app->registerSettings(); diff --git a/apps/encryption/appinfo/application.php b/apps/encryption/appinfo/application.php new file mode 100644 index 00000000000..0d1bd0d6bed --- /dev/null +++ b/apps/encryption/appinfo/application.php @@ -0,0 +1,190 @@ +<?php +/** + * @author Clark Tomlinson <clark@owncloud.com> + * @since 3/11/15, 11:03 AM + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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\AppInfo; + + +use OC\Files\Filesystem; +use OC\Files\View; +use OCA\Encryption\Crypto\Crypt; +use OCA\Encryption\HookManager; +use OCA\Encryption\Hooks\UserHooks; +use OCA\Encryption\KeyManager; +use OCA\Encryption\Recovery; +use OCA\Encryption\Users\Setup; +use OCA\Encryption\Util; +use OCP\App; +use OCP\AppFramework\IAppContainer; +use OCP\Encryption\IManager; +use OCP\IConfig; + + +class Application extends \OCP\AppFramework\App { + /** + * @var IManager + */ + private $encryptionManager; + /** + * @var IConfig + */ + private $config; + + /** + * @param $appName + * @param array $urlParams + */ + public function __construct($urlParams = array()) { + parent::__construct('encryption', $urlParams); + $this->encryptionManager = \OC::$server->getEncryptionManager(); + $this->config = \OC::$server->getConfig(); + $this->registerServices(); + } + + /** + * + */ + public function registerHooks() { + if (!$this->config->getSystemValue('maintenance', false)) { + + $container = $this->getContainer(); + $server = $container->getServer(); + // Register our hooks and fire them. + $hookManager = new HookManager(); + + $hookManager->registerHook([ + new UserHooks($container->query('KeyManager'), + $server->getLogger(), + $container->query('UserSetup'), + $server->getUserSession(), + $container->query('Util'), + new \OCA\Encryption\Session($server->getSession()), + $container->query('Crypt'), + $container->query('Recovery')) + ]); + + $hookManager->fireHooks(); + + } else { + // Logout user if we are in maintenance to force re-login + $this->getContainer()->getServer()->getUserSession()->logout(); + } + } + + /** + * + */ + public function registerEncryptionModule() { + $container = $this->getContainer(); + $container->registerService('EncryptionModule', function (IAppContainer $c) { + return new \OCA\Encryption\Crypto\Encryption( + $c->query('Crypt'), + $c->query('KeyManager'), + $c->query('Util')); + }); + $module = $container->query('EncryptionModule'); + $this->encryptionManager->registerEncryptionModule($module); + } + + /** + * + */ + public function registerServices() { + $container = $this->getContainer(); + + $container->registerService('Crypt', + function (IAppContainer $c) { + $server = $c->getServer(); + return new Crypt($server->getLogger(), + $server->getUserSession(), + $server->getConfig()); + }); + + $container->registerService('KeyManager', + function (IAppContainer $c) { + $server = $c->getServer(); + + return new KeyManager($server->getEncryptionKeyStorage(\OCA\Encryption\Crypto\Encryption::ID), + $c->query('Crypt'), + $server->getConfig(), + $server->getUserSession(), + new \OCA\Encryption\Session($server->getSession()), + $server->getLogger(), + $c->query('Util') + ); + }); + + + $container->registerService('Recovery', + function (IAppContainer $c) { + $server = $c->getServer(); + + return new Recovery( + $server->getUserSession(), + $c->query('Crypt'), + $server->getSecureRandom(), + $c->query('KeyManager'), + $server->getConfig(), + $server->getEncryptionKeyStorage(\OCA\Encryption\Crypto\Encryption::ID), + $server->getEncryptionFilesHelper(), + new \OC\Files\View()); + }); + + $container->registerService('RecoveryController', function (IAppContainer $c) { + $server = $c->getServer(); + return new \OCA\Encryption\Controller\RecoveryController( + $c->getAppName(), + $server->getRequest(), + $server->getConfig(), + $server->getL10N($c->getAppName()), + $c->query('Recovery')); + }); + + $container->registerService('UserSetup', + function (IAppContainer $c) { + $server = $c->getServer(); + return new Setup($server->getLogger(), + $server->getUserSession(), + $c->query('Crypt'), + $c->query('KeyManager')); + }); + + $container->registerService('Util', + function (IAppContainer $c) { + $server = $c->getServer(); + + return new Util( + new View(), + $c->query('Crypt'), + $server->getLogger(), + $server->getUserSession(), + $server->getConfig()); + }); + + } + + /** + * + */ + public function registerSettings() { + // Register settings scripts + App::registerAdmin('encryption', 'settings/settings-admin'); + App::registerPersonal('encryption', 'settings/settings-personal'); + } +} diff --git a/apps/encryption/appinfo/info.xml b/apps/encryption/appinfo/info.xml new file mode 100644 index 00000000000..e4a7d790e9c --- /dev/null +++ b/apps/encryption/appinfo/info.xml @@ -0,0 +1,36 @@ +<?xml version="1.0"?> +<info> + <id>encryption</id> + <description> + This application encrypts all files accessed by ownCloud at rest, + wherever they are stored. As an example, with this application + enabled, external cloud based Amazon S3 storage will be encrypted, + protecting this data on storage outside of the control of the Admin. + When this application is enabled for the first time, all files are + encrypted as users log in and are prompted for their password. The + recommended recovery key option enables recovery of files in case + the key is lost. + Note that this app encrypts all files that are touched by ownCloud, + so external storage providers and applications such as SharePoint + will see new files encrypted when they are accessed. Encryption is + based on AES 128 or 256 bit keys. More information is available in + the Encryption documentation + </description> +<name>Encryption</name> + <license>AGPL</license> + <author>Bjoern Schiessle, Clark Tomlinson</author> + <requiremin>8</requiremin> + <shipped>true</shipped> + <documentation> + <user>user-encryption</user> + <admin>admin-encryption</admin> + </documentation> + <rememberlogin>false</rememberlogin> + <types> + <filesystem/> + </types> + <dependencies> + <lib>openssl</lib> + </dependencies> + +</info> diff --git a/apps/encryption/appinfo/routes.php b/apps/encryption/appinfo/routes.php new file mode 100644 index 00000000000..d4867f5fdaa --- /dev/null +++ b/apps/encryption/appinfo/routes.php @@ -0,0 +1,44 @@ +<?php +/** + * @author Clark Tomlinson <clark@owncloud.com> + * @since 2/19/15, 11:22 AM + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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\AppInfo; + +(new Application())->registerRoutes($this, array('routes' => array( + + [ + 'name' => 'Recovery#adminRecovery', + 'url' => '/ajax/adminRecovery', + 'verb' => 'POST' + ], + [ + 'name' => 'Recovery#changeRecoveryPassword', + 'url' => '/ajax/changeRecoveryPassword', + 'verb' => 'POST' + ], + [ + 'name' => 'Recovery#userSetRecovery', + 'url' => '/ajax/userSetRecovery', + 'verb' => 'POST' + ] + + +))); diff --git a/apps/encryption/controller/recoverycontroller.php b/apps/encryption/controller/recoverycontroller.php new file mode 100644 index 00000000000..da55d81f63a --- /dev/null +++ b/apps/encryption/controller/recoverycontroller.php @@ -0,0 +1,160 @@ +<?php +/** + * @author Clark Tomlinson <clark@owncloud.com> + * @since 2/19/15, 11:25 AM + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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\Controller; + + +use OCA\Encryption\Recovery; +use OCP\AppFramework\Controller; +use OCP\IConfig; +use OCP\IL10N; +use OCP\IRequest; +use OCP\JSON; +use OCP\AppFramework\Http\DataResponse; + +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 Recovery $recovery + */ + public function __construct($AppName, IRequest $request, IConfig $config, IL10N $l10n, Recovery $recovery) { + parent::__construct($AppName, $request); + $this->config = $config; + $this->l = $l10n; + $this->recovery = $recovery; + } + + public function adminRecovery($recoveryPassword, $confirmPassword, $adminEnableRecovery) { + // Check if both passwords are the same + if (empty($recoveryPassword)) { + $errorMessage = (string) $this->l->t('Missing recovery key password'); + return new DataResponse(['data' => ['message' => $errorMessage]], 500); + } + + if (empty($confirmPassword)) { + $errorMessage = (string) $this->l->t('Please repeat the recovery key password'); + return new DataResponse(['data' => ['message' => $errorMessage]], 500); + } + + if ($recoveryPassword !== $confirmPassword) { + $errorMessage = (string) $this->l->t('Repeated recovery key password does not match the provided recovery key password'); + return new DataResponse(['data' => ['message' => $errorMessage]], 500); + } + + if (isset($adminEnableRecovery) && $adminEnableRecovery === '1') { + if ($this->recovery->enableAdminRecovery($recoveryPassword)) { + return new DataResponse(['status' =>'success', 'data' => array('message' => (string) $this->l->t('Recovery key successfully enabled'))]); + } + return new DataResponse(['data' => array('message' => (string) $this->l->t('Could not enable recovery key. Please check your recovery key password!'))]); + } elseif (isset($adminEnableRecovery) && $adminEnableRecovery === '0') { + if ($this->recovery->disableAdminRecovery($recoveryPassword)) { + return new DataResponse(['data' => array('message' => (string) $this->l->t('Recovery key successfully disabled'))]); + } + return new DataResponse(['data' => array('message' => (string) $this->l->t('Could not disable recovery key. Please check your recovery key password!'))]); + } + } + + public function changeRecoveryPassword($newPassword, $oldPassword, $confirmPassword) { + //check if both passwords are the same + if (empty($oldPassword)) { + $errorMessage = (string) $this->l->t('Please provide the old recovery password'); + return new DataResponse(array('data' => array('message' => $errorMessage))); + } + + if (empty($newPassword)) { + $errorMessage = (string) $this->l->t('Please provide a new recovery password'); + return new DataResponse (array('data' => array('message' => $errorMessage))); + } + + if (empty($confirmPassword)) { + $errorMessage = (string) $this->l->t('Please repeat the new recovery password'); + return new DataResponse(array('data' => array('message' => $errorMessage))); + } + + if ($newPassword !== $confirmPassword) { + $errorMessage = (string) $this->l->t('Repeated recovery key password does not match the provided recovery key password'); + return new DataResponse(array('data' => array('message' => $errorMessage))); + } + + $result = $this->recovery->changeRecoveryKeyPassword($newPassword, $oldPassword); + + if ($result) { + return new DataResponse( + array( + 'status' => 'success' , + 'data' => array( + 'message' => (string) $this->l->t('Password successfully changed.')) + ) + ); + } else { + return new DataResponse( + array( + 'data' => array + ('message' => (string) $this->l->t('Could not change the password. Maybe the old password was not correct.')) + ) + ); + } + } + + /** + * @NoAdminRequired + */ + public function userSetRecovery($userEnableRecovery) { + if ($userEnableRecovery === '0' || $userEnableRecovery === '1') { + + $result = $this->recovery->setRecoveryForUser($userEnableRecovery); + + if ($result) { + return new DataResponse( + array( + 'status' => 'success', + 'data' => array( + 'message' => (string) $this->l->t('Recovery Key enabled')) + ) + ); + } else { + return new DataResponse( + array( + 'data' => array + ('message' => (string) $this->l->t('Could not enable the recovery key, please try again or contact your administrator')) + ) + ); + } + } + } + +} diff --git a/apps/encryption/css/settings-personal.css b/apps/encryption/css/settings-personal.css new file mode 100644 index 00000000000..8eb5bedcb06 --- /dev/null +++ b/apps/encryption/css/settings-personal.css @@ -0,0 +1,10 @@ +/* Copyright (c) 2013, Sam Tuke, <samtuke@owncloud.com> + This file is licensed under the Affero General Public License version 3 or later. + See the COPYING-README file. */ + +#encryptAllError +, #encryptAllSuccess +, #recoveryEnabledError +, #recoveryEnabledSuccess { + display: none; +} diff --git a/apps/encryption/hooks/contracts/ihook.php b/apps/encryption/hooks/contracts/ihook.php new file mode 100644 index 00000000000..2cc01fd7c9b --- /dev/null +++ b/apps/encryption/hooks/contracts/ihook.php @@ -0,0 +1,32 @@ +<?php +/** + * @author Clark Tomlinson <clark@owncloud.com> + * @since 2/19/15, 10:03 AM + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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/hooks/userhooks.php b/apps/encryption/hooks/userhooks.php new file mode 100644 index 00000000000..1ec0950d941 --- /dev/null +++ b/apps/encryption/hooks/userhooks.php @@ -0,0 +1,286 @@ +<?php +/** + * @author Clark Tomlinson <clark@owncloud.com> + * @since 2/19/15, 10:02 AM + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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 OCP\Util as OCUtil; +use OCA\Encryption\Hooks\Contracts\IHook; +use OCA\Encryption\KeyManager; +use OCA\Encryption\Crypto\Crypt; +use OCA\Encryption\Users\Setup; +use OCP\App; +use OCP\ILogger; +use OCP\IUserSession; +use OCA\Encryption\Util; +use OCA\Encryption\Session; +use OCA\Encryption\Recovery; + +class UserHooks implements IHook { + /** + * @var KeyManager + */ + private $keyManager; + /** + * @var ILogger + */ + private $logger; + /** + * @var Setup + */ + private $userSetup; + /** + * @var IUserSession + */ + private $user; + /** + * @var Util + */ + private $util; + /** + * @var Session + */ + private $session; + /** + * @var Recovery + */ + private $recovery; + /** + * @var Crypt + */ + private $crypt; + + /** + * UserHooks constructor. + * + * @param KeyManager $keyManager + * @param ILogger $logger + * @param Setup $userSetup + * @param IUserSession $user + * @param Util $util + * @param Session $session + * @param Crypt $crypt + * @param Recovery $recovery + */ + public function __construct(KeyManager $keyManager, + ILogger $logger, + Setup $userSetup, + IUserSession $user, + Util $util, + Session $session, + Crypt $crypt, + Recovery $recovery) { + + $this->keyManager = $keyManager; + $this->logger = $logger; + $this->userSetup = $userSetup; + $this->user = $user; + $this->util = $util; + $this->session = $session; + $this->recovery = $recovery; + $this->crypt = $crypt; + } + + /** + * Connects Hooks + * + * @return null + */ + public function addHooks() { + OCUtil::connectHook('OC_User', 'post_login', $this, 'login'); + OCUtil::connectHook('OC_User', 'logout', $this, 'logout'); + OCUtil::connectHook('OC_User', + 'post_setPassword', + $this, + 'setPassphrase'); + OCUtil::connectHook('OC_User', + 'pre_setPassword', + $this, + 'preSetPassphrase'); + 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 bool + */ + public function login($params) { + + if (!App::isEnabled('encryption')) { + return true; + } + + // ensure filesystem is loaded + // Todo: update? + if (!\OC\Files\Filesystem::$loaded) { + \OC_Util::setupFS($params['uid']); + } + + // setup user, if user not ready force relogin + if (!$this->userSetup->setupUser($params['uid'], $params['password'])) { + return false; + } + + $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) { + + if (App::isEnabled('encryption')) { + $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) { + + if (App::isEnabled('encryption')) { + $this->keyManager->deletePublicKey($params['uid']); + } + } + + /** + * If the password can't be changed within ownCloud, than update the key password in advance. + * + * @param array $params : uid, password + * @return bool + */ + public function preSetPassphrase($params) { + if (App::isEnabled('encryption')) { + + if (!$this->user->getUser()->canChangePassword()) { + $this->setPassphrase($params); + } + } + } + + /** + * Change a user's encryption passphrase + * + * @param array $params keys: uid, password + * @return bool + */ + public function setPassphrase($params) { + + // Get existing decrypted private key + $privateKey = $this->session->getPrivateKey(); + + if ($params['uid'] === $this->user->getUser()->getUID() && $privateKey) { + + // Encrypt private key with new user pwd as passphrase + $encryptedPrivateKey = $this->crypt->symmetricEncryptFileContent($privateKey, + $params['password']); + + // Save private key + if ($encryptedPrivateKey) { + $this->keyManager->setPrivateKey($this->user->getUser()->getUID(), + $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 reencrypt file keys + $user = $params['uid']; + $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 ( + ($this->recovery->isRecoveryEnabledForUser($user) && $recoveryPassword) + || !$this->keyManager->userHasKeys($user) + || !$this->util->userHasFiles($user) + ) { + + // backup old keys + //$this->backupAllKeys('recovery'); + + $newUserPassword = $params['password']; + + $keyPair = $this->crypt->createKeyPair(); + + // Save public key + $this->keyManager->setPublicKey($user, $keyPair['publicKey']); + + // Encrypt private key with new password + $encryptedKey = $this->crypt->symmetricEncryptFileContent($keyPair['privateKey'], + $newUserPassword); + + if ($encryptedKey) { + $this->keyManager->setPrivateKey($user, $encryptedKey); + + if ($recoveryPassword) { // if recovery key is set we can re-encrypt the key files + $this->recovery->recoverUsersFiles($recoveryPassword, $user); + } + } else { + $this->logger->error('Encryption Could not update users encryption password'); + } + } + } + } + + + + /** + * after password reset we create a new key pair for the user + * + * @param array $params + */ + public function postPasswordReset($params) { + $password = $params['password']; + + $this->keyManager->replaceUserKeys($params['uid']); + $this->userSetup->setupServerSide($params['uid'], $password); + } +} diff --git a/apps/encryption/img/app.svg b/apps/encryption/img/app.svg new file mode 100644 index 00000000000..1157c71c66e --- /dev/null +++ b/apps/encryption/img/app.svg @@ -0,0 +1,51 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xml:space="preserve" + height="16px" + width="16px" + version="1.1" + y="0px" + x="0px" + viewBox="0 0 71 100" + id="svg2" + inkscape:version="0.48.5 r10040" + sodipodi:docname="app.svg"><metadata + id="metadata10"><rdf:RDF><cc:Work + rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs + id="defs8" /><sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1920" + inkscape:window-height="1014" + id="namedview6" + showgrid="false" + inkscape:zoom="14.75" + inkscape:cx="-21.423729" + inkscape:cy="8" + inkscape:window-x="0" + inkscape:window-y="27" + inkscape:window-maximized="1" + inkscape:current-layer="svg2" /><path + d="m8 1c-2.2091 0-4 1.7909-4 4v2h-1v7h10v-7h-1v-2c0-2.2091-1.791-4-4-4zm0 2c1.1046 0 2 0.89543 2 2v2h-4v-2c0-1.1046 0.8954-2 2-2z" + transform="matrix(6.25,0,0,6.25,-14.5,0)" + id="path4" + style="fill:#ffffff;fill-opacity:1" /><path + style="fill:none" + d="m 3.0644068,10.508475 0,-3.4576275 0.4655371,0 0.465537,0 0.049537,-1.2033899 C 4.1094633,4.2818838 4.1578923,4.0112428 4.4962182,3.3259708 4.7075644,2.8978935 4.9002217,2.6327599 5.2605792,2.2740624 6.7855365,0.75613022 8.9920507,0.69157582 10.623172,2.1171729 c 0.384104,0.3357058 0.882069,1.0763131 1.054177,1.5678422 0.147302,0.4206856 0.262873,1.6086448 0.266436,2.7387137 l 0.002,0.6271187 0.508474,0 0.508475,0 0,3.4576275 0,3.457627 -4.9491527,0 -4.9491525,0 0,-3.457627 z M 10.065882,6.3559322 c -0.02012,-0.3822034 -0.04774,-0.7076271 -0.0614,-0.7231639 -0.013653,-0.015537 -0.024824,0.281921 -0.024824,0.661017 l 0,0.6892655 -1.9630041,0 -1.963004,0 -0.023717,-0.4576271 -0.023717,-0.4576271 -0.013279,0.4915254 -0.013279,0.4915255 2.0613978,0 2.0613972,0 -0.03657,-0.6949153 0,0 z M 6.5396275,3.7118644 C 6.648082,3.5720339 6.7197092,3.4576271 6.6987988,3.4576271 c -0.062956,0 -0.5835446,0.6841947 -0.5835446,0.7669359 0,0.042237 0.051116,0.00136 0.1135916,-0.090834 0.062475,-0.092195 0.2023271,-0.2820343 0.3107817,-0.4218648 z M 9.7498983,4.0169492 C 9.6961899,3.9144068 9.5352369,3.723769 9.392225,3.5933098 L 9.1322034,3.356111 9.3784249,3.6272081 c 0.1354218,0.1491033 0.2814105,0.3397411 0.3244192,0.4236394 0.043009,0.083898 0.093162,0.1525423 0.1114515,0.1525423 0.01829,0 -0.010689,-0.083898 -0.064397,-0.1864406 l 0,0 z M 7.3032896,3.1315382 C 7.2704731,3.0987216 6.877102,3.3089557 6.8306315,3.3841466 6.8091904,3.4188389 6.911918,3.3813452 7.0589148,3.300827 7.2059117,3.2203088 7.3158803,3.1441289 7.3032896,3.1315382 l 0,0 z" + id="path3007" + inkscape:connector-curvature="0" + transform="matrix(6.25,0,0,6.25,-14.5,0)" /></svg>
\ No newline at end of file diff --git a/apps/encryption/js/detect-migration.js b/apps/encryption/js/detect-migration.js new file mode 100644 index 00000000000..f5627edf4e4 --- /dev/null +++ b/apps/encryption/js/detect-migration.js @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2013 + * Bjoern Schiessle <schiessle@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or later. + * See the COPYING-README file. + */ + + +$(document).ready(function(){ + $('form[name="login"]').on('submit', function() { + var user = $('#user').val(); + var password = $('#password').val(); + $.ajax({ + type: 'POST', + url: OC.linkTo('files_encryption', 'ajax/getMigrationStatus.php'), + dataType: 'json', + data: {user: user, password: password}, + async: false, + success: function(response) { + if (response.data.migrationStatus === OC.Encryption.MIGRATION_OPEN) { + var message = t('files_encryption', 'Initial encryption started... This can take some time. Please wait.'); + $('#messageText').text(message); + $('#message').removeClass('hidden').addClass('update'); + } else if (response.data.migrationStatus === OC.Encryption.MIGRATION_IN_PROGRESS) { + var message = t('files_encryption', 'Initial encryption running... Please try again later.'); + $('#messageText').text(message); + $('#message').removeClass('hidden').addClass('update'); + } + } + }); + }); + +}); diff --git a/apps/encryption/js/encryption.js b/apps/encryption/js/encryption.js new file mode 100644 index 00000000000..d2d1c3a1fc5 --- /dev/null +++ b/apps/encryption/js/encryption.js @@ -0,0 +1,16 @@ +/** + * Copyright (c) 2014 + * Bjoern Schiessle <schiessle@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or later. + * See the COPYING-README file. + */ + +/** + * @namespace + * @memberOf OC + */ +OC.Encryption={ + MIGRATION_OPEN:0, + MIGRATION_COMPLETED:1, + MIGRATION_IN_PROGRESS:-1, +}; diff --git a/apps/encryption/js/settings-admin.js b/apps/encryption/js/settings-admin.js new file mode 100644 index 00000000000..36765adf3e4 --- /dev/null +++ b/apps/encryption/js/settings-admin.js @@ -0,0 +1,55 @@ +/** + * Copyright (c) 2013 + * Sam Tuke <samtuke@owncloud.com> + * Robin Appelman <icewind1991@gmail.com> + * Bjoern Schiessle <schiessle@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or later. + * See the COPYING-README file. + */ + +$(document).ready(function(){ + + $( 'input:radio[name="adminEnableRecovery"]' ).change( + function() { + var recoveryStatus = $( this ).val(); + var oldStatus = (1+parseInt(recoveryStatus)) % 2; + var recoveryPassword = $( '#encryptionRecoveryPassword' ).val(); + var confirmPassword = $( '#repeatEncryptionRecoveryPassword' ).val(); + OC.msg.startSaving('#encryptionSetRecoveryKey .msg'); + $.post( + OC.generateUrl('/apps/encryption/ajax/adminRecovery') + , { adminEnableRecovery: recoveryStatus, recoveryPassword: recoveryPassword, confirmPassword: confirmPassword } + , function( result ) { + OC.msg.finishedSaving('#encryptionSetRecoveryKey .msg', result); + if (result.status === "error") { + $('input:radio[name="adminEnableRecovery"][value="'+oldStatus.toString()+'"]').attr("checked", "true"); + } else { + if (recoveryStatus === "0") { + $('p[name="changeRecoveryPasswordBlock"]').addClass("hidden"); + } else { + $('input:password[name="changeRecoveryPassword"]').val(""); + $('p[name="changeRecoveryPasswordBlock"]').removeClass("hidden"); + } + } + } + ); + } + ); + + // change recovery password + + $('button:button[name="submitChangeRecoveryKey"]').click(function() { + var oldRecoveryPassword = $('#oldEncryptionRecoveryPassword').val(); + var newRecoveryPassword = $('#newEncryptionRecoveryPassword').val(); + var confirmNewPassword = $('#repeatedNewEncryptionRecoveryPassword').val(); + OC.msg.startSaving('#encryptionChangeRecoveryKey .msg'); + $.post( + OC.generateUrl('/apps/encryption/ajax/changeRecoveryPassword') + , { oldPassword: oldRecoveryPassword, newPassword: newRecoveryPassword, confirmPassword: confirmNewPassword } + , function( data ) { + OC.msg.finishedSaving('#encryptionChangeRecoveryKey .msg', data); + } + ); + }); + +}); diff --git a/apps/encryption/js/settings-personal.js b/apps/encryption/js/settings-personal.js new file mode 100644 index 00000000000..dcfbba4ecde --- /dev/null +++ b/apps/encryption/js/settings-personal.js @@ -0,0 +1,62 @@ +/** + * Copyright (c) 2013, Sam Tuke <samtuke@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or later. + * See the COPYING-README file. + */ + +function updatePrivateKeyPasswd() { + var oldPrivateKeyPassword = $('input:password[id="oldPrivateKeyPassword"]').val(); + var newPrivateKeyPassword = $('input:password[id="newPrivateKeyPassword"]').val(); + OC.msg.startSaving('#encryption .msg'); + $.post( + OC.generateUrl('/apps/encryption/ajax/updatePrivateKeyPassword') + , { oldPassword: oldPrivateKeyPassword, newPassword: newPrivateKeyPassword } + , function( data ) { + if (data.status === "error") { + OC.msg.finishedSaving('#encryption .msg', data); + } else { + OC.msg.finishedSaving('#encryption .msg', data); + } + } + ); +} + +$(document).ready(function(){ + + // Trigger ajax on recoveryAdmin status change + $( 'input:radio[name="userEnableRecovery"]' ).change( + function() { + var recoveryStatus = $( this ).val(); + OC.msg.startAction('#userEnableRecovery .msg', 'Updating recovery keys. This can take some time...'); + $.post( + OC.generateUrl('/apps/encryption/ajax/userSetRecovery') + , { userEnableRecovery: recoveryStatus } + , function( data ) { + OC.msg.finishedAction('#userEnableRecovery .msg', data); + } + ); + // Ensure page is not reloaded on form submit + return false; + } + ); + + // update private key password + + $('input:password[name="changePrivateKeyPassword"]').keyup(function(event) { + var oldPrivateKeyPassword = $('input:password[id="oldPrivateKeyPassword"]').val(); + var newPrivateKeyPassword = $('input:password[id="newPrivateKeyPassword"]').val(); + if (newPrivateKeyPassword !== '' && oldPrivateKeyPassword !== '' ) { + $('button:button[name="submitChangePrivateKeyPassword"]').removeAttr("disabled"); + if(event.which === 13) { + updatePrivateKeyPasswd(); + } + } else { + $('button:button[name="submitChangePrivateKeyPassword"]').attr("disabled", "true"); + } + }); + + $('button:button[name="submitChangePrivateKeyPassword"]').click(function() { + updatePrivateKeyPasswd(); + }); + +}); diff --git a/apps/encryption/l10n/.gitkeep b/apps/encryption/l10n/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d --- /dev/null +++ b/apps/encryption/l10n/.gitkeep diff --git a/apps/encryption/lib/crypto/crypt.php b/apps/encryption/lib/crypto/crypt.php new file mode 100644 index 00000000000..c0b737a3daa --- /dev/null +++ b/apps/encryption/lib/crypto/crypt.php @@ -0,0 +1,457 @@ +<?php +/** + * @author Clark Tomlinson <clark@owncloud.com> + * @since 2/19/15, 1:42 PM + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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\Crypto; + + +use OC\Encryption\Exceptions\DecryptionFailedException; +use OC\Encryption\Exceptions\EncryptionFailedException; +use OCA\Encryption\Exceptions\MultiKeyDecryptException; +use OCA\Encryption\Exceptions\MultiKeyEncryptException; +use OCP\Encryption\Exceptions\GenericEncryptionException; +use OCP\IConfig; +use OCP\ILogger; +use OCP\IUser; +use OCP\IUserSession; + +class Crypt { + + const DEFAULT_CIPHER = 'AES-256-CFB'; + + const HEADER_START = 'HBEGIN'; + const HEADER_END = 'HEND'; + /** + * @var ILogger + */ + private $logger; + /** + * @var IUser + */ + private $user; + /** + * @var IConfig + */ + private $config; + + /** + * @param ILogger $logger + * @param IUserSession $userSession + * @param IConfig $config + */ + public function __construct(ILogger $logger, IUserSession $userSession, IConfig $config) { + $this->logger = $logger; + $this->user = $userSession && $userSession->isLoggedIn() ? $userSession->getUser() : false; + $this->config = $config; + } + + /** + * @return array|bool + */ + public function createKeyPair() { + + $log = $this->logger; + $res = $this->getOpenSSLPKey(); + + if (!$res) { + $log->error("Encryption Library could'nt generate users key-pair for {$this->user->getUID()}", + ['app' => 'encryption']); + + if (openssl_error_string()) { + $log->error('Encryption library openssl_pkey_new() fails: ' . openssl_error_string(), + ['app' => 'encryption']); + } + } elseif (openssl_pkey_export($res, + $privateKey, + null, + $this->getOpenSSLConfig())) { + $keyDetails = openssl_pkey_get_details($res); + $publicKey = $keyDetails['key']; + + return [ + 'publicKey' => $publicKey, + 'privateKey' => $privateKey + ]; + } + $log->error('Encryption library couldn\'t export users private key, please check your servers openSSL configuration.' . $this->user->getUID(), + ['app' => 'encryption']); + if (openssl_error_string()) { + $log->error('Encryption Library:' . openssl_error_string(), + ['app' => 'encryption']); + } + + return false; + } + + /** + * @return resource + */ + public function getOpenSSLPKey() { + $config = $this->getOpenSSLConfig(); + return openssl_pkey_new($config); + } + + /** + * @return array + */ + private function getOpenSSLConfig() { + $config = ['private_key_bits' => 4096]; + $config = array_merge(\OC::$server->getConfig()->getSystemValue('openssl', + []), + $config); + return $config; + } + + /** + * @param string $plainContent + * @param string $passPhrase + * @return bool|string + * @throws GenericEncryptionException + */ + public function symmetricEncryptFileContent($plainContent, $passPhrase) { + + if (!$plainContent) { + $this->logger->error('Encryption Library, symmetrical encryption failed no content given', + ['app' => 'encryption']); + return false; + } + + $iv = $this->generateIv(); + + $encryptedContent = $this->encrypt($plainContent, + $iv, + $passPhrase, + $this->getCipher()); + // combine content to encrypt the IV identifier and actual IV + $catFile = $this->concatIV($encryptedContent, $iv); + $padded = $this->addPadding($catFile); + + return $padded; + } + + /** + * @param string $plainContent + * @param string $iv + * @param string $passPhrase + * @param string $cipher + * @return string + * @throws EncryptionFailedException + */ + private function encrypt($plainContent, $iv, $passPhrase = '', $cipher = self::DEFAULT_CIPHER) { + $encryptedContent = openssl_encrypt($plainContent, + $cipher, + $passPhrase, + false, + $iv); + + if (!$encryptedContent) { + $error = 'Encryption (symmetric) of content failed'; + $this->logger->error($error . openssl_error_string(), + ['app' => 'encryption']); + throw new EncryptionFailedException($error); + } + + return $encryptedContent; + } + + /** + * @return mixed|string + */ + public function getCipher() { + $cipher = $this->config->getSystemValue('cipher', self::DEFAULT_CIPHER); + if ($cipher !== 'AES-256-CFB' && $cipher !== 'AES-128-CFB') { + $this->logger->warning('Wrong cipher defined in config.php only AES-128-CFB and AES-256-CFB are supported. Fall back' . self::DEFAULT_CIPHER, + ['app' => 'encryption']); + $cipher = self::DEFAULT_CIPHER; + } + + return $cipher; + } + + /** + * @param string $encryptedContent + * @param string $iv + * @return string + */ + private function concatIV($encryptedContent, $iv) { + return $encryptedContent . '00iv00' . $iv; + } + + /** + * @param $data + * @return string + */ + private function addPadding($data) { + return $data . 'xx'; + } + + /** + * @param string $recoveryKey + * @param string $password + * @return bool|string + */ + public function decryptPrivateKey($recoveryKey, $password) { + + $header = $this->parseHeader($recoveryKey); + $cipher = $this->getCipher(); + + // If we found a header we need to remove it from the key we want to decrypt + if (!empty($header)) { + $recoveryKey = substr($recoveryKey, + strpos($recoveryKey, + self::HEADER_END) + strlen(self::HEADER_START)); + } + + $plainKey = $this->symmetricDecryptFileContent($recoveryKey, + $password, + $cipher); + + // Check if this is a valid private key + $res = openssl_get_privatekey($plainKey); + if (is_resource($res)) { + $sslInfo = openssl_pkey_get_details($res); + if (!isset($sslInfo['key'])) { + return false; + } + } else { + return false; + } + + return $plainKey; + } + + /** + * @param $keyFileContents + * @param string $passPhrase + * @param string $cipher + * @return string + * @throws DecryptionFailedException + */ + public function symmetricDecryptFileContent($keyFileContents, $passPhrase = '', $cipher = self::DEFAULT_CIPHER) { + // Remove Padding + $noPadding = $this->removePadding($keyFileContents); + + $catFile = $this->splitIv($noPadding); + + return $this->decrypt($catFile['encrypted'], + $catFile['iv'], + $passPhrase, + $cipher); + } + + /** + * @param $padded + * @return bool|string + */ + private function removePadding($padded) { + if (substr($padded, -2) === 'xx') { + return substr($padded, 0, -2); + } + return false; + } + + /** + * @param $catFile + * @return array + */ + private function splitIv($catFile) { + // Fetch encryption metadata from end of file + $meta = substr($catFile, -22); + + // Fetch IV from end of file + $iv = substr($meta, -16); + + // Remove IV and IV Identifier text to expose encrypted content + + $encrypted = substr($catFile, 0, -22); + + return [ + 'encrypted' => $encrypted, + 'iv' => $iv + ]; + } + + /** + * @param $encryptedContent + * @param $iv + * @param string $passPhrase + * @param string $cipher + * @return string + * @throws DecryptionFailedException + */ + private function decrypt($encryptedContent, $iv, $passPhrase = '', $cipher = self::DEFAULT_CIPHER) { + $plainContent = openssl_decrypt($encryptedContent, + $cipher, + $passPhrase, + false, + $iv); + + if ($plainContent) { + return $plainContent; + } else { + throw new DecryptionFailedException('Encryption library: Decryption (symmetric) of content failed: ' . openssl_error_string()); + } + } + + /** + * @param $data + * @return array + */ + private function parseHeader($data) { + $result = []; + + if (substr($data, 0, strlen(self::HEADER_START)) === self::HEADER_START) { + $endAt = strpos($data, self::HEADER_END); + $header = substr($data, 0, $endAt + strlen(self::HEADER_END)); + + // +1 not to start with an ':' which would result in empty element at the beginning + $exploded = explode(':', + substr($header, strlen(self::HEADER_START) + 1)); + + $element = array_shift($exploded); + + while ($element != self::HEADER_END) { + $result[$element] = array_shift($exploded); + $element = array_shift($exploded); + } + } + + return $result; + } + + /** + * @return string + * @throws GenericEncryptionException + */ + private function generateIv() { + $random = openssl_random_pseudo_bytes(12, $strong); + if ($random) { + if (!$strong) { + // If OpenSSL indicates randomness is insecure log error + $this->logger->error('Encryption Library: Insecure symmetric key was generated using openssl_random_psudo_bytes()', + ['app' => 'encryption']); + } + + /* + * We encode the iv purely for string manipulation + * purposes -it gets decoded before use + */ + return base64_encode($random); + } + // If we ever get here we've failed anyway no need for an else + throw new GenericEncryptionException('Generating IV Failed'); + } + + /** + * Generate a pseudo random 256-bit ASCII key, used as file key + * @return string + */ + public static function generateFileKey() { + // Generate key + $key = base64_encode(openssl_random_pseudo_bytes(32, $strong)); + if (!$key || !$strong) { + // If OpenSSL indicates randomness is insecure, log error + throw new \Exception('Encryption library, Insecure symmetric key was generated using openssl_random_pseudo_bytes()'); + } + + return $key; + } + + /** + * Check if a file's contents contains an IV and is symmetrically encrypted + * + * @param $content + * @return bool + */ + public function isCatFileContent($content) { + if (!$content) { + return false; + } + + $noPadding = $this->removePadding($content); + + // Fetch encryption metadata from end of file + $meta = substr($noPadding, -22); + + // Fetch identifier from start of metadata + $identifier = substr($meta, 0, 6); + + if ($identifier === '00iv00') { + return true; + } + return false; + } + + /** + * @param $encKeyFile + * @param $shareKey + * @param $privateKey + * @return mixed + * @throws MultiKeyDecryptException + */ + public function multiKeyDecrypt($encKeyFile, $shareKey, $privateKey) { + if (!$encKeyFile) { + throw new MultiKeyDecryptException('Cannot multikey decrypt empty plain content'); + } + + if (openssl_open($encKeyFile, $plainContent, $shareKey, $privateKey)) { + return $plainContent; + } else { + throw new MultiKeyDecryptException('multikeydecrypt with share key failed:' . openssl_error_string()); + } + } + + /** + * @param $plainContent + * @param array $keyFiles + * @return array + * @throws MultiKeyEncryptException + */ + public function multiKeyEncrypt($plainContent, array $keyFiles) { + // openssl_seal returns false without errors if plaincontent is empty + // so trigger our own error + if (empty($plainContent)) { + throw new MultiKeyEncryptException('Cannot multikeyencrypt empty plain content'); + } + + // Set empty vars to be set by openssl by reference + $sealed = ''; + $shareKeys = []; + $mappedShareKeys = []; + + if (openssl_seal($plainContent, $sealed, $shareKeys, $keyFiles)) { + $i = 0; + + // Ensure each shareKey is labelled with its corresponding key id + foreach ($keyFiles as $userId => $publicKey) { + $mappedShareKeys[$userId] = $shareKeys[$i]; + $i++; + } + + return [ + 'keys' => $mappedShareKeys, + 'data' => $sealed + ]; + } else { + throw new MultiKeyEncryptException('multikeyencryption failed ' . openssl_error_string()); + } + } +} + diff --git a/apps/encryption/lib/crypto/encryption.php b/apps/encryption/lib/crypto/encryption.php new file mode 100644 index 00000000000..7c633b7411f --- /dev/null +++ b/apps/encryption/lib/crypto/encryption.php @@ -0,0 +1,328 @@ +<?php +/** + * @author Clark Tomlinson <fallen013@gmail.com> + * @since 3/6/15, 2:28 PM + * @link http:/www.clarkt.com + * @copyright Clark Tomlinson © 2015 + * + */ + +namespace OCA\Encryption\Crypto; + + +use OCA\Encryption\Util; +use OCP\Encryption\IEncryptionModule; +use OCA\Encryption\KeyManager; + +class Encryption implements IEncryptionModule { + + const ID = 'OC_DEFAULT_MODULE'; + + /** + * @var Crypt + */ + private $crypt; + + /** @var string */ + private $cipher; + + /** @var string */ + private $path; + + /** @var string */ + private $user; + + /** @var string */ + private $fileKey; + + /** @var string */ + private $writeCache; + + /** @var KeyManager */ + private $keyManager; + + /** @var array */ + private $accessList; + + /** @var boolean */ + private $isWriteOperation; + + /** @var Util */ + private $util; + + /** + * + * @param \OCA\Encryption\Crypto\Crypt $crypt + * @param KeyManager $keyManager + * @param Util $util + */ + public function __construct(Crypt $crypt, KeyManager $keyManager, Util $util) { + $this->crypt = $crypt; + $this->keyManager = $keyManager; + $this->util = $util; + } + + /** + * @return string defining the technical unique id + */ + public function getId() { + return self::ID; + } + + /** + * In comparison to getKey() this function returns a human readable (maybe translated) name + * + * @return string + */ + public function getDisplayName() { + return 'ownCloud Default Encryption'; + } + + /** + * start receiving chunks from a file. This is the place where you can + * perform some initial step before starting encrypting/decrypting the + * chunks + * + * @param string $path to the file + * @param string $user who read/write the file + * @param array $header contains the header data read from the file + * @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 + */ + public function begin($path, $user, $header, $accessList) { + + if (isset($header['cipher'])) { + $this->cipher = $header['cipher']; + } else { + $this->cipher = $this->crypt->getCipher(); + } + + $this->path = $this->getPathToRealFile($path); + $this->accessList = $accessList; + $this->user = $user; + $this->writeCache = ''; + $this->isWriteOperation = false; + + $this->fileKey = $this->keyManager->getFileKey($this->path, $this->user); + + return array('cipher' => $this->cipher); + } + + /** + * last chunk received. This is the place where you can perform some final + * operation and return some remaining data if something is left in your + * buffer. + * + * @param string $path to the file + * @return string remained data which should be written to the file in case + * of a write operation + */ + public function end($path) { + $result = ''; + if ($this->isWriteOperation) { + if (!empty($this->writeCache)) { + $result = $this->crypt->symmetricEncryptFileContent($this->writeCache, $this->fileKey); + $this->writeCache = ''; + } + $publicKeys = array(); + foreach ($this->accessList['users'] as $uid) { + $publicKeys[$uid] = $this->keyManager->getPublicKey($uid); + } + + $publicKeys = $this->keyManager->addSystemKeys($this->accessList, $publicKeys); + + $encryptedKeyfiles = $this->crypt->multiKeyEncrypt($this->fileKey, $publicKeys); + $this->keyManager->setAllFileKeys($this->path, $encryptedKeyfiles); + } + return $result; + } + + /** + * encrypt data + * + * @param string $data you want to encrypt + * @return mixed encrypted data + */ + public function encrypt($data) { + $this->isWriteOperation = true; + if (empty($this->fileKey)) { + $this->fileKey = $this->crypt->generateFileKey(); + } + + // If extra data is left over from the last round, make sure it + // is integrated into the next 6126 / 8192 block + if ($this->writeCache) { + + // Concat writeCache to start of $data + $data = $this->writeCache . $data; + + // Clear the write cache, ready for reuse - it has been + // flushed and its old contents processed + $this->writeCache = ''; + + } + + $encrypted = ''; + // While there still remains some data to be processed & written + while (strlen($data) > 0) { + + // Remaining length for this iteration, not of the + // entire file (may be greater than 8192 bytes) + $remainingLength = strlen($data); + + // If data remaining to be written is less than the + // size of 1 6126 byte block + if ($remainingLength < 6126) { + + // Set writeCache to contents of $data + // The writeCache will be carried over to the + // next write round, and added to the start of + // $data to ensure that written blocks are + // always the correct length. If there is still + // data in writeCache after the writing round + // has finished, then the data will be written + // to disk by $this->flush(). + $this->writeCache = $data; + + // Clear $data ready for next round + $data = ''; + + } else { + + // Read the chunk from the start of $data + $chunk = substr($data, 0, 6126); + + $encrypted .= $this->crypt->symmetricEncryptFileContent($chunk, $this->fileKey); + + // Remove the chunk we just processed from + // $data, leaving only unprocessed data in $data + // var, for handling on the next round + $data = substr($data, 6126); + + } + + } + + return $encrypted; + } + + /** + * decrypt data + * + * @param string $data you want to decrypt + * @return mixed decrypted data + */ + public function decrypt($data) { + $result = ''; + if (!empty($data)) { + $result = $this->crypt->symmetricDecryptFileContent($data, $this->fileKey); + } + return $result; + } + + /** + * update encrypted file, e.g. give additional users access to the file + * + * @param string $path path to the file which should be updated + * @param string $uid of the user who performs the operation + * @param array $accessList who has access to the file contains the key 'users' and 'public' + * @return boolean + */ + public function update($path, $uid, $accessList) { + $fileKey = $this->keyManager->getFileKey($path, $uid); + $publicKeys = array(); + foreach ($accessList['users'] as $user) { + $publicKeys[$user] = $this->keyManager->getPublicKey($user); + } + + $publicKeys = $this->keyManager->addSystemKeys($accessList, $publicKeys); + + $encryptedFileKey = $this->crypt->multiKeyEncrypt($fileKey, $publicKeys); + + $this->keyManager->deleteAllFileKeys($path); + + $this->keyManager->setAllFileKeys($path, $encryptedFileKey); + + return true; + } + + /** + * add system keys such as the public share key and the recovery key + * + * @param array $accessList + * @param array $publicKeys + * @return array + */ + public function addSystemKeys(array $accessList, array $publicKeys) { + if (!empty($accessList['public'])) { + $publicKeys[$this->keyManager->getPublicShareKeyId()] = $this->keyManager->getPublicShareKey(); + } + + if ($this->keyManager->recoveryKeyExists() && + $this->util->recoveryEnabled($this->user)) { + + $publicKeys[$this->keyManager->getRecoveryKeyId()] = $this->keyManager->getRecoveryKey(); + } + + + return $publicKeys; + } + + + /** + * should the file be encrypted or not + * + * @param string $path + * @return boolean + */ + public function shouldEncrypt($path) { + $parts = explode('/', $path); + if (count($parts) < 3) { + return false; + } + + if ($parts[2] == 'files') { + return true; + } + if ($parts[2] == 'files_versions') { + return true; + } + + return false; + } + + /** + * calculate unencrypted size + * + * @param string $path to file + * @return integer unencrypted size + */ + public function calculateUnencryptedSize($path) { + // TODO: Implement calculateUnencryptedSize() method. + } + + /** + * get size of the unencrypted payload per block. + * ownCloud read/write files with a block size of 8192 byte + * + * @return integer + */ + public function getUnencryptedBlockSize() { + return 6126; + } + + protected function getPathToRealFile($path) { + $realPath = $path; + $parts = explode('/', $path); + if ($parts[2] === 'files_versions') { + $realPath = '/' . $parts[1] . '/files/' . implode('/', array_slice($parts, 3)); + $length = strrpos($realPath, '.'); + $realPath = substr($realPath, 0, $length); + } + + return $realPath; + } +} diff --git a/apps/encryption/lib/exceptions/multikeydecryptexception.php b/apps/encryption/lib/exceptions/multikeydecryptexception.php new file mode 100644 index 00000000000..1466d35eda3 --- /dev/null +++ b/apps/encryption/lib/exceptions/multikeydecryptexception.php @@ -0,0 +1,9 @@ +<?php + +namespace OCA\Encryption\Exceptions; + +use OCP\Encryption\Exceptions\GenericEncryptionException; + +class MultiKeyDecryptException extends GenericEncryptionException { + +} diff --git a/apps/encryption/lib/exceptions/multikeyencryptexception.php b/apps/encryption/lib/exceptions/multikeyencryptexception.php new file mode 100644 index 00000000000..daf528e2cf7 --- /dev/null +++ b/apps/encryption/lib/exceptions/multikeyencryptexception.php @@ -0,0 +1,9 @@ +<?php + +namespace OCA\Encryption\Exceptions; + +use OCP\Encryption\Exceptions\GenericEncryptionException; + +class MultiKeyEncryptException extends GenericEncryptionException { + +} diff --git a/apps/encryption/lib/exceptions/privatekeymissingexception.php b/apps/encryption/lib/exceptions/privatekeymissingexception.php new file mode 100644 index 00000000000..50d75870b20 --- /dev/null +++ b/apps/encryption/lib/exceptions/privatekeymissingexception.php @@ -0,0 +1,38 @@ +<?php + /** + * @author Clark Tomlinson <clark@owncloud.com> + * @since 2/25/15, 9:39 AM + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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\Exceptions; + +use OCP\Encryption\Exceptions\GenericEncryptionException; + +class PrivateKeyMissingException extends GenericEncryptionException { + + /** + * @param string $userId + */ + public function __construct($userId) { + if(empty($userId)) { + $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 new file mode 100644 index 00000000000..9638c28e427 --- /dev/null +++ b/apps/encryption/lib/exceptions/publickeymissingexception.php @@ -0,0 +1,20 @@ +<?php + + +namespace OCA\Encryption\Exceptions; + +use OCP\Encryption\Exceptions\GenericEncryptionException; + +class PublicKeyMissingException extends GenericEncryptionException { + + /** + * @param string $userId + */ + public function __construct($userId) { + if(empty($userId)) { + $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 new file mode 100644 index 00000000000..19ee142a622 --- /dev/null +++ b/apps/encryption/lib/hookmanager.php @@ -0,0 +1,66 @@ +<?php +/** + * @author Clark Tomlinson <clark@owncloud.com> + * @since 2/19/15, 10:13 AM + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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 { + + 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 + * + * @var $instance IHook + */ + $instance->addHooks(); + } + + } + +} diff --git a/apps/encryption/lib/keymanager.php b/apps/encryption/lib/keymanager.php new file mode 100644 index 00000000000..1f71a891e81 --- /dev/null +++ b/apps/encryption/lib/keymanager.php @@ -0,0 +1,511 @@ +<?php + +namespace OCA\Encryption; + +use OC\Encryption\Exceptions\DecryptionFailedException; +use OCA\Encryption\Exceptions\PrivateKeyMissingException; +use OCA\Encryption\Exceptions\PublicKeyMissingException; +use OCA\Encryption\Crypto\Crypt; +use OCP\Encryption\Keys\IStorage; +use OCP\IConfig; +use OCP\ILogger; +use OCP\IUserSession; + +class KeyManager { + + /** + * @var Session + */ + protected $session; + /** + * @var IStorage + */ + private $keyStorage; + /** + * @var Crypt + */ + private $crypt; + /** + * @var string + */ + private $recoveryKeyId; + /** + * @var string + */ + private $publicShareKeyId; + /** + * @var string UserID + */ + private $keyId; + /** + * @var string + */ + private $publicKeyId = 'publicKey'; + /** + * @var string + */ + private $privateKeyId = 'privateKey'; + + /** + * @var string + */ + private $shareKeyId = 'shareKey'; + + /** + * @var string + */ + private $fileKeyId = 'fileKey'; + /** + * @var IConfig + */ + private $config; + /** + * @var ILogger + */ + private $log; + /** + * @var Util + */ + private $util; + + /** + * @param IStorage $keyStorage + * @param Crypt $crypt + * @param IConfig $config + * @param IUserSession $userSession + * @param Session $session + * @param ILogger $log + * @param Util $util + */ + public function __construct( + IStorage $keyStorage, + Crypt $crypt, + IConfig $config, + IUserSession $userSession, + Session $session, + ILogger $log, + Util $util + ) { + + $this->util = $util; + $this->session = $session; + $this->keyStorage = $keyStorage; + $this->crypt = $crypt; + $this->config = $config; + $this->log = $log; + + $this->recoveryKeyId = $this->config->getAppValue('encryption', + 'recoveryKeyId'); + if (empty($this->recoveryKeyId)) { + $this->recoveryKeyId = 'recoveryKey_' . substr(md5(time()), 0, 8); + $this->config->setAppValue('encryption', + 'recoveryKeyId', + $this->recoveryKeyId); + } + + $this->publicShareKeyId = $this->config->getAppValue('encryption', + 'publicShareKeyId'); + if (empty($this->publicShareKeyId)) { + $this->publicShareKeyId = 'pubShare_' . substr(md5(time()), 0, 8); + $this->config->setAppValue('encryption', 'publicShareKeyId', $this->publicShareKeyId); + } + + $shareKey = $this->getPublicShareKey(); + if (empty($shareKey)) { + $keyPair = $this->crypt->createKeyPair(); + + // Save public key + $this->keyStorage->setSystemUserKey( + $this->publicShareKeyId . '.publicKey', $keyPair['publicKey']); + + // Encrypt private key empty passphrase + $encryptedKey = $this->crypt->symmetricEncryptFileContent($keyPair['privateKey'], ''); + $this->keyStorage->setSystemUserKey($this->publicShareKeyId . '.privateKey', $encryptedKey); + } + + $this->keyId = $userSession && $userSession->isLoggedIn() ? $userSession->getUser()->getUID() : false; + $this->log = $log; + } + + /** + * @return bool + */ + public function recoveryKeyExists() { + $key = $this->getRecoveryKey(); + return (!empty($key)); + } + + /** + * get recovery key + * + * @return string + */ + public function getRecoveryKey() { + return $this->keyStorage->getSystemUserKey($this->recoveryKeyId . '.publicKey'); + } + + /** + * get recovery key ID + * + * @return string + */ + public function getRecoveryKeyId() { + return $this->recoveryKeyId; + } + + /** + * @param $password + * @return bool + */ + public function checkRecoveryPassword($password) { + $recoveryKey = $this->keyStorage->getSystemUserKey($this->recoveryKeyId . '.privateKey'); + $decryptedRecoveryKey = $this->crypt->decryptPrivateKey($recoveryKey, + $password); + + if ($decryptedRecoveryKey) { + return true; + } + return false; + } + + /** + * @param string $uid + * @param string $password + * @param string $keyPair + * @return bool + */ + public function storeKeyPair($uid, $password, $keyPair) { + // Save Public Key + $this->setPublicKey($uid, $keyPair['publicKey']); + + $encryptedKey = $this->crypt->symmetricEncryptFileContent($keyPair['privateKey'], + $password); + + if ($encryptedKey) { + $this->setPrivateKey($uid, $encryptedKey); + return true; + } + return false; + } + + /** + * @param string $password + * @param array $keyPair + * @return bool + */ + public function setRecoveryKey($password, $keyPair) { + // Save Public Key + $this->keyStorage->setSystemUserKey($this->getRecoveryKeyId(). '.publicKey', $keyPair['publicKey']); + + $encryptedKey = $this->crypt->symmetricEncryptFileContent($keyPair['privateKey'], + $password); + + if ($encryptedKey) { + $this->setSystemPrivateKey($this->getRecoveryKeyId(), $encryptedKey); + return true; + } + return false; + } + + /** + * @param $userId + * @param $key + * @return bool + */ + public function setPublicKey($userId, $key) { + return $this->keyStorage->setUserKey($userId, $this->publicKeyId, $key); + } + + /** + * @param $userId + * @param $key + * @return bool + */ + public function setPrivateKey($userId, $key) { + return $this->keyStorage->setUserKey($userId, + $this->privateKeyId, + $key); + } + + /** + * write file key to key storage + * + * @param string $path + * @param string $key + * @return boolean + */ + public function setFileKey($path, $key) { + return $this->keyStorage->setFileKey($path, $this->fileKeyId, $key); + } + + /** + * set all file keys (the file key and the corresponding share keys) + * + * @param string $path + * @param array $keys + */ + public function setAllFileKeys($path, $keys) { + $this->setFileKey($path, $keys['data']); + foreach ($keys['keys'] as $uid => $keyFile) { + $this->setShareKey($path, $uid, $keyFile); + } + } + + /** + * write share key to the key storage + * + * @param string $path + * @param string $uid + * @param string $key + * @return boolean + */ + public function setShareKey($path, $uid, $key) { + $keyId = $uid . '.' . $this->shareKeyId; + return $this->keyStorage->setFileKey($path, $keyId, $key); + } + + /** + * Decrypt private key and store it + * + * @param string $uid userid + * @param string $passPhrase users password + * @return boolean + */ + public function init($uid, $passPhrase) { + try { + $privateKey = $this->getPrivateKey($uid); + $privateKey = $this->crypt->decryptPrivateKey($privateKey, + $passPhrase); + } catch (PrivateKeyMissingException $e) { + return false; + } catch (DecryptionFailedException $e) { + return false; + } + + $this->session->setPrivateKey($privateKey); + $this->session->setStatus(Session::INIT_SUCCESSFUL); + + return true; + } + + /** + * @param $userId + * @return mixed + * @throws PrivateKeyMissingException + */ + public function getPrivateKey($userId) { + $privateKey = $this->keyStorage->getUserKey($userId, + $this->privateKeyId); + + if (strlen($privateKey) !== 0) { + return $privateKey; + } + throw new PrivateKeyMissingException($userId); + } + + /** + * @param $path + * @param $uid + * @return string + */ + public function getFileKey($path, $uid) { + $encryptedFileKey = $this->keyStorage->getFileKey($path, $this->fileKeyId); + + if (is_null($uid)) { + $uid = $this->getPublicShareKeyId(); + $shareKey = $this->getShareKey($path, $uid); + $privateKey = $this->keyStorage->getSystemUserKey($this->publicShareKeyId . '.privateKey'); + $privateKey = $this->crypt->symmetricDecryptFileContent($privateKey); + } else { + $shareKey = $this->getShareKey($path, $uid); + $privateKey = $this->session->getPrivateKey(); + } + + if ($encryptedFileKey && $shareKey && $privateKey) { + return $this->crypt->multiKeyDecrypt($encryptedFileKey, + $shareKey, + $privateKey); + } + + return ''; + } + + /** + * get the encrypted file key + * + * @param $path + * @return string + */ + public function getEncryptedFileKey($path) { + $encryptedFileKey = $this->keyStorage->getFileKey($path, + $this->fileKeyId); + + return $encryptedFileKey; + } + + /** + * delete share key + * + * @param string $path + * @param string $keyId + * @return boolean + */ + public function deleteShareKey($path, $keyId) { + return $this->keyStorage->deleteFileKey($path, $keyId . '.' . $this->shareKeyId); + } + + + /** + * @param $path + * @param $uid + * @return mixed + */ + public function getShareKey($path, $uid) { + $keyId = $uid . '.' . $this->shareKeyId; + return $this->keyStorage->getFileKey($path, $keyId); + } + + /** + * @param $userId + * @return bool + */ + public function userHasKeys($userId) { + try { + $this->getPrivateKey($userId); + $this->getPublicKey($userId); + } catch (PrivateKeyMissingException $e) { + return false; + } catch (PublicKeyMissingException $e) { + return false; + } + return true; + } + + /** + * @param $userId + * @return mixed + * @throws PublicKeyMissingException + */ + public function getPublicKey($userId) { + $publicKey = $this->keyStorage->getUserKey($userId, $this->publicKeyId); + + if (strlen($publicKey) !== 0) { + return $publicKey; + } + throw new PublicKeyMissingException($userId); + } + + public function getPublicShareKeyId() { + return $this->publicShareKeyId; + } + + /** + * get public key for public link shares + * + * @return string + */ + public function getPublicShareKey() { + return $this->keyStorage->getSystemUserKey($this->publicShareKeyId . '.publicKey'); + } + + /** + * @param $purpose + * @param bool $timestamp + * @param bool $includeUserKeys + */ + public function backupAllKeys($purpose, $timestamp = true, $includeUserKeys = true) { +// $backupDir = $this->keyStorage->; + } + + /** + * @param string $uid + */ + public function replaceUserKeys($uid) { + $this->backupAllKeys('password_reset'); + $this->deletePublicKey($uid); + $this->deletePrivateKey($uid); + } + + /** + * @param $uid + * @return bool + */ + public function deletePublicKey($uid) { + return $this->keyStorage->deleteUserKey($uid, $this->publicKeyId); + } + + /** + * @param $uid + * @return bool + */ + private function deletePrivateKey($uid) { + return $this->keyStorage->deleteUserKey($uid, $this->privateKeyId); + } + + public function deleteAllFileKeys($path) { + return $this->keyStorage->deleteAllFileKeys($path); + } + + /** + * @param array $userIds + * @return array + * @throws PublicKeyMissingException + */ + public function getPublicKeys(array $userIds) { + $keys = []; + + foreach ($userIds as $userId) { + try { + $keys[$userId] = $this->getPublicKey($userId); + } catch (PublicKeyMissingException $e) { + continue; + } + } + + return $keys; + + } + + /** + * @param string $keyId + * @return string returns openssl key + */ + public function getSystemPrivateKey($keyId) { + return $this->keyStorage->getSystemUserKey($keyId . '.' . $this->privateKeyId); + } + + /** + * @param string $keyId + * @param string $key + * @return string returns openssl key + */ + public function setSystemPrivateKey($keyId, $key) { + return $this->keyStorage->setSystemUserKey($keyId . '.' . $this->privateKeyId, $key); + } + + /** + * add system keys such as the public share key and the recovery key + * + * @param array $accessList + * @param array $publicKeys + * @return array + * @throws PublicKeyMissingException + */ + public function addSystemKeys(array $accessList, array $publicKeys) { + if (!empty($accessList['public'])) { + $publicShareKey = $this->getPublicShareKey(); + if (empty($publicShareKey)) { + throw new PublicKeyMissingException($this->getPublicShareKeyId()); + } + $publicKeys[$this->getPublicShareKeyId()] = $publicShareKey; + } + + if ($this->recoveryKeyExists() && + $this->util->isRecoveryEnabledForUser()) { + + $publicKeys[$this->getRecoveryKeyId()] = $this->getRecoveryKey(); + } + + return $publicKeys; + } +} diff --git a/apps/encryption/lib/recovery.php b/apps/encryption/lib/recovery.php new file mode 100644 index 00000000000..34acdd0a6e3 --- /dev/null +++ b/apps/encryption/lib/recovery.php @@ -0,0 +1,316 @@ +<?php +/** + * @author Clark Tomlinson <clark@owncloud.com> + * @since 2/19/15, 11:45 AM + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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\Crypto\Crypt; +use OCP\Encryption\Keys\IStorage; +use OCP\IConfig; +use OCP\IUser; +use OCP\IUserSession; +use OCP\PreConditionNotMetException; +use OCP\Security\ISecureRandom; +use OC\Files\View; +use OCP\Encryption\IFile; + +class Recovery { + + + /** + * @var null|IUser + */ + protected $user; + /** + * @var Crypt + */ + protected $crypt; + /** + * @var ISecureRandom + */ + private $random; + /** + * @var KeyManager + */ + private $keyManager; + /** + * @var IConfig + */ + private $config; + /** + * @var IStorage + */ + private $keyStorage; + /** + * @var View + */ + private $view; + /** + * @var IFile + */ + private $file; + /** + * @var string + */ + private $recoveryKeyId; + + /** + * @param IUserSession $user + * @param Crypt $crypt + * @param ISecureRandom $random + * @param KeyManager $keyManager + * @param IConfig $config + * @param IStorage $keyStorage + * @param IFile $file + * @param View $view + */ + public function __construct(IUserSession $user, + Crypt $crypt, + ISecureRandom $random, + KeyManager $keyManager, + IConfig $config, + IStorage $keyStorage, + IFile $file, + View $view) { + $this->user = ($user && $user->isLoggedIn()) ? $user->getUser() : false; + $this->crypt = $crypt; + $this->random = $random; + $this->keyManager = $keyManager; + $this->config = $config; + $this->keyStorage = $keyStorage; + $this->view = $view; + $this->file = $file; + } + + /** + * @param $recoveryKeyId + * @param $password + * @return bool + */ + public function enableAdminRecovery($password) { + $appConfig = $this->config; + $keyManager = $this->keyManager; + + if (!$keyManager->recoveryKeyExists()) { + $keyPair = $this->crypt->createKeyPair(); + + $this->keyManager->setRecoveryKey($password, $keyPair); + } + + if ($keyManager->checkRecoveryPassword($password)) { + $appConfig->setAppValue('encryption', 'recoveryAdminEnabled', 1); + return true; + } + + return false; + } + + /** + * change recovery key id + * + * @param string $newPassword + * @param string $oldPassword + */ + public function changeRecoveryKeyPassword($newPassword, $oldPassword) { + $recoveryKey = $this->keyManager->getSystemPrivateKey($this->keyManager->getRecoveryKeyId()); + $decryptedRecoveryKey = $this->crypt->decryptPrivateKey($recoveryKey, $oldPassword); + $encryptedRecoveryKey = $this->crypt->symmetricEncryptFileContent($decryptedRecoveryKey, $newPassword); + if ($encryptedRecoveryKey) { + $this->keyManager->setSystemPrivateKey($this->keyManager->getRecoveryKeyId(), $encryptedRecoveryKey); + return true; + } + return false; + } + + /** + * @param $recoveryPassword + * @return bool + */ + public function disableAdminRecovery($recoveryPassword) { + $keyManager = $this->keyManager; + + if ($keyManager->checkRecoveryPassword($recoveryPassword)) { + // Set recoveryAdmin as disabled + $this->config->setAppValue('encryption', 'recoveryAdminEnabled', 0); + return true; + } + return false; + } + + /** + * check if recovery is enabled for user + * + * @param string $user if no user is given we check the current logged-in user + * + * @return bool + */ + public function isRecoveryEnabledForUser($user = '') { + $uid = empty($user) ? $this->user->getUID() : $user; + $recoveryMode = $this->config->getUserValue($uid, + 'encryption', + 'recoveryEnabled', + 0); + + return ($recoveryMode === '1'); + } + + /** + * check if recovery is key is enabled by the administrator + * + * @return bool + */ + public function isRecoveryKeyEnabled() { + $enabled = $this->config->getAppValue('encryption', 'recoveryAdminEnabled', 0); + + return ($enabled === '1'); + } + + /** + * @param string $value + * @return bool + */ + public function setRecoveryForUser($value) { + + try { + $this->config->setUserValue($this->user->getUID(), + 'encryption', + 'recoveryEnabled', + $value); + + if ($value === '1') { + $this->addRecoveryKeys('/' . $this->user->getUID() . '/files/'); + } else { + $this->removeRecoveryKeys('/' . $this->user->getUID() . '/files/'); + } + + return true; + } catch (PreConditionNotMetException $e) { + return false; + } + } + + /** + * add recovery key to all encrypted files + */ + private function addRecoveryKeys($path) { + $dirContent = $this->view->getDirectoryContent($path); + foreach ($dirContent as $item) { + $filePath = $item->getPath(); + if ($item['type'] === 'dir') { + $this->addRecoveryKeys($filePath . '/'); + } else { + $fileKey = $this->keyManager->getFileKey($filePath, $this->user->getUID()); + if (!empty($fileKey)) { + $accessList = $this->file->getAccessList($filePath); + $publicKeys = array(); + foreach ($accessList['users'] as $uid) { + $publicKeys[$uid] = $this->keyManager->getPublicKey($uid); + } + + $publicKeys = $this->keyManager->addSystemKeys($accessList, $publicKeys); + + $encryptedKeyfiles = $this->crypt->multiKeyEncrypt($fileKey, $publicKeys); + $this->keyManager->setAllFileKeys($filePath, $encryptedKeyfiles); + } + } + } + } + + /** + * remove recovery key to all encrypted files + */ + private function removeRecoveryKeys($path) { + $dirContent = $this->view->getDirectoryContent($path); + foreach ($dirContent as $item) { + $filePath = $item->getPath(); + if ($item['type'] === 'dir') { + $this->removeRecoveryKeys($filePath . '/'); + } else { + $this->keyManager->deleteShareKey($filePath, $this->keyManager->getRecoveryKeyId()); + } + } + } + + /** + * recover users files with the recovery key + * + * @param string $recoveryPassword + * @param string $user + */ + public function recoverUsersFiles($recoveryPassword, $user) { + $encryptedKey = $this->keyManager->getSystemPrivateKey($this->keyManager->getRecoveryKeyId()); + + $privateKey = $this->crypt->decryptPrivateKey($encryptedKey, + $recoveryPassword); + + $this->recoverAllFiles('/' . $user . '/files/', $privateKey); + } + + /** + * @param $path + * @param $privateKey + */ + private function recoverAllFiles($path, $privateKey) { + $dirContent = $this->view->getDirectoryContent($path); + + foreach ($dirContent as $item) { + // Get relative path from encryption/keyfiles + $filePath = $item->getPath(); + if ($this->view->is_dir($filePath)) { + $this->recoverAllFiles($filePath . '/', $privateKey); + } else { + $this->recoverFile($filePath, $privateKey); + } + } + + } + + /** + * @param string $path + * @param string $privateKey + */ + private function recoverFile($path, $privateKey) { + $encryptedFileKey = $this->keyManager->getEncryptedFileKey($path); + $shareKey = $this->keyManager->getShareKey($path, $this->keyManager->getRecoveryKeyId()); + + if ($encryptedFileKey && $shareKey && $privateKey) { + $fileKey = $this->crypt->multiKeyDecrypt($encryptedFileKey, + $shareKey, + $privateKey); + } + + if (!empty($fileKey)) { + $accessList = $this->file->getAccessList($path); + $publicKeys = array(); + foreach ($accessList['users'] as $uid) { + $publicKeys[$uid] = $this->keyManager->getPublicKey($uid); + } + + $publicKeys = $this->keyManager->addSystemKeys($accessList, $publicKeys); + + $encryptedKeyfiles = $this->crypt->multiKeyEncrypt($fileKey, $publicKeys); + $this->keyManager->setAllFileKeys($path, $encryptedKeyfiles); + } + + } + + +} diff --git a/apps/encryption/lib/session.php b/apps/encryption/lib/session.php new file mode 100644 index 00000000000..e705611fa6e --- /dev/null +++ b/apps/encryption/lib/session.php @@ -0,0 +1,114 @@ +<?php + +/** + * ownCloud + * + * @copyright (C) 2015 ownCloud, Inc. + * + * @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\Encryption; + +use \OCP\ISession; + +class Session { + + /** @var ISession */ + protected $session; + + const NOT_INITIALIZED = '0'; + const INIT_EXECUTED = '1'; + const INIT_SUCCESSFUL = '2'; + + public function __construct(ISession $session) { + $this->session = $session; + } + + /** + * Sets status of encryption app + * + * @param string $status INIT_SUCCESSFUL, INIT_EXECUTED, NOT_INITIALIZED + */ + public function setStatus($status) { + $this->session->set('encryptionInitialized', $status); + } + + /** + * Gets status if we already tried to initialize the encryption app + * + * @return string init status INIT_SUCCESSFUL, INIT_EXECUTED, NOT_INITIALIZED + */ + public function getStatus() { + $status = $this->session->get('encryptionInitialized'); + if (is_null($status)) { + $status = self::NOT_INITIALIZED; + } + + return $status; + } + + /** + * Gets user or public share private key from session + * + * @return string $privateKey The user's plaintext private key + * @throws Exceptions\PrivateKeyMissingException + */ + 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); + } + return $key; + } + + /** + * check if private key is set + * + * @return boolean + */ + public function isPrivateKeySet() { + $key = $this->session->get('privateKey'); + if (is_null($key)) { + return false; + } + + return true; + } + + /** + * Sets user private key to session + * + * @param string $key users private key + * + * @note this should only be set on login + */ + public function setPrivateKey($key) { + $this->session->set('privateKey', $key); + } + + + /** + * remove keys from session + */ + public function clear() { + $this->session->remove('publicSharePrivateKey'); + $this->session->remove('privateKey'); + $this->session->remove('encryptionInitialized'); + + } + +} diff --git a/apps/encryption/lib/users/setup.php b/apps/encryption/lib/users/setup.php new file mode 100644 index 00000000000..e80bf6003e6 --- /dev/null +++ b/apps/encryption/lib/users/setup.php @@ -0,0 +1,72 @@ +<?php +/** + * @author Clark Tomlinson <fallen013@gmail.com> + * @since 3/6/15, 11:36 AM + * @link http:/www.clarkt.com + * @copyright Clark Tomlinson © 2015 + * + */ + +namespace OCA\Encryption\Users; + + +use OCA\Encryption\Crypto\Crypt; +use OCA\Encryption\KeyManager; +use OCP\ILogger; +use OCP\IUserSession; + +class Setup { + /** + * @var Crypt + */ + private $crypt; + /** + * @var KeyManager + */ + private $keyManager; + /** + * @var ILogger + */ + private $logger; + /** + * @var bool|string + */ + private $user; + + + /** + * @param ILogger $logger + * @param IUserSession $userSession + * @param Crypt $crypt + * @param KeyManager $keyManager + */ + public function __construct(ILogger $logger, IUserSession $userSession, Crypt $crypt, KeyManager $keyManager) { + $this->logger = $logger; + $this->user = $userSession && $userSession->isLoggedIn() ? $userSession->getUser()->getUID() : false; + $this->crypt = $crypt; + $this->keyManager = $keyManager; + } + + /** + * @param $uid userid + * @param $password user password + * @return bool + */ + public function setupUser($uid, $password) { + return $this->setupServerSide($uid, $password); + } + + /** + * @param $uid userid + * @param $password user password + * @return bool + */ + public function setupServerSide($uid, $password) { + // Check if user already has keys + if (!$this->keyManager->userHasKeys($uid)) { + return $this->keyManager->storeKeyPair($uid, $password, + $this->crypt->createKeyPair()); + } + return true; + } +} diff --git a/apps/encryption/lib/util.php b/apps/encryption/lib/util.php new file mode 100644 index 00000000000..6b6b8b6b38c --- /dev/null +++ b/apps/encryption/lib/util.php @@ -0,0 +1,117 @@ +<?php +/** + * @author Clark Tomlinson <clark@owncloud.com> + * @since 3/17/15, 10:31 AM + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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 OC\Files\View; +use OCA\Encryption\Crypto\Crypt; +use OCP\IConfig; +use OCP\ILogger; +use OCP\IUser; +use OCP\IUserSession; +use OCP\PreConditionNotMetException; + +class Util { + /** + * @var View + */ + private $files; + /** + * @var Crypt + */ + private $crypt; + /** + * @var ILogger + */ + private $logger; + /** + * @var bool|IUser + */ + private $user; + /** + * @var IConfig + */ + private $config; + + /** + * Util constructor. + * + * @param View $files + * @param Crypt $crypt + * @param ILogger $logger + * @param IUserSession $userSession + * @param IConfig $config + */ + public function __construct(View $files, + Crypt $crypt, + ILogger $logger, + IUserSession $userSession, + IConfig $config + ) { + $this->files = $files; + $this->crypt = $crypt; + $this->logger = $logger; + $this->user = $userSession && $userSession->isLoggedIn() ? $userSession->getUser() : false; + $this->config = $config; + } + + /** + * @return bool + */ + public function isRecoveryEnabledForUser() { + $recoveryMode = $this->config->getUserValue($this->user->getUID(), + 'encryption', + 'recoveryEnabled', + 0); + + return ($recoveryMode === '1'); + } + + /** + * @param $enabled + * @return bool + */ + public function setRecoveryForUser($enabled) { + $value = $enabled ? '1' : '0'; + + try { + $this->config->setUserValue($this->user->getUID(), + 'encryption', + 'recoveryEnabled', + $value); + return true; + } catch (PreConditionNotMetException $e) { + return false; + } + } + + /** + * @param string $uid + * @return bool + */ + public function userHasFiles($uid) { + return $this->files->file_exists($uid . '/files'); + } + + +} diff --git a/apps/encryption/settings/settings-admin.php b/apps/encryption/settings/settings-admin.php new file mode 100644 index 00000000000..36e9c532bbd --- /dev/null +++ b/apps/encryption/settings/settings-admin.php @@ -0,0 +1,21 @@ +<?php +/** + * Copyright (c) 2015 Clark Tomlinson <clark@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +\OC_Util::checkAdminUser(); + +$tmpl = new OCP\Template('encryption', 'settings-admin'); + +// Check if an adminRecovery account is enabled for recovering files after lost pwd +$recoveryAdminEnabled = \OC::$server->getConfig()->getAppValue('encryption', 'recoveryAdminEnabled', '0'); +$session = new \OCA\Encryption\Session(\OC::$server->getSession()); + + +$tmpl->assign('recoveryEnabled', $recoveryAdminEnabled); +$tmpl->assign('initStatus', $session->getStatus()); + +return $tmpl->fetchPage(); diff --git a/apps/encryption/settings/settings-personal.php b/apps/encryption/settings/settings-personal.php new file mode 100644 index 00000000000..ec3d30f457d --- /dev/null +++ b/apps/encryption/settings/settings-personal.php @@ -0,0 +1,58 @@ +<?php +/** + * Copyright (c) 2015 Clark Tomlinson <clark@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +$session = new \OCA\Encryption\Session(\OC::$server->getSession()); +$userSession = \OC::$server->getUserSession(); + +$template = new OCP\Template('encryption', 'settings-personal'); +$crypt = new \OCA\Encryption\Crypto\Crypt( + \OC::$server->getLogger(), + $userSession, + \OC::$server->getConfig()); + +$util = new \OCA\Encryption\Util( + new \OC\Files\View(), + $crypt, + \OC::$server->getLogger(), + $userSession, + \OC::$server->getConfig()); + +$keyManager = new \OCA\Encryption\KeyManager( + \OC::$server->getEncryptionKeyStorage(\OCA\Encryption\Crypto\Encryption::ID), + $crypt, + \OC::$server->getConfig(), + $userSession, + $session, + \OC::$server->getLogger(), $util); + +$user = $userSession->getUser()->getUID(); + +$view = new \OC\Files\View('/'); + + + +$privateKeySet = $session->isPrivateKeySet(); +// did we tried to initialize the keys for this session? +$initialized = $session->getStatus(); + +$recoveryAdminEnabled = \OC::$server->getConfig()->getAppValue('encryption', 'recoveryAdminEnabled'); +$recoveryEnabledForUser = $util->isRecoveryEnabledForUser(); + +$result = false; + +if ($recoveryAdminEnabled || !$privateKeySet) { + $template->assign('recoveryEnabled', $recoveryAdminEnabled); + $template->assign('recoveryEnabledForUser', $recoveryEnabledForUser); + $template->assign('privateKeySet', $privateKeySet); + $template->assign('initialized', $initialized); + + $result = $template->fetchPage(); +} + +return $result; + diff --git a/apps/encryption/templates/settings-admin.php b/apps/encryption/templates/settings-admin.php new file mode 100644 index 00000000000..b64e75512e7 --- /dev/null +++ b/apps/encryption/templates/settings-admin.php @@ -0,0 +1,73 @@ +<?php +/** @var array $_ */ +/** @var OC_L10N $l */ +script('encryption', 'settings-admin'); +script('core', 'multiselect'); +?> +<form id="encryption" class="section"> + <h2><?php p($l->t('ownCloud basic encryption module')); ?></h2> + + <?php if(!$_["initStatus"]): ?> + <?php p($l->t("Encryption App is enabled but your keys are not initialized, please log-out and log-in again")); ?> + <?php else: ?> + <p id="encryptionSetRecoveryKey"> + <?php p($l->t("Enable recovery key (allow to recover users files in case of password loss):")); ?> + <span class="msg"></span> + <br/> + <br/> + <input type="password" name="encryptionRecoveryPassword" id="encryptionRecoveryPassword"/> + <label for="recoveryPassword"><?php p($l->t("Recovery key password")); ?></label> + <br/> + <input type="password" name="encryptionRecoveryPassword" id="repeatEncryptionRecoveryPassword"/> + <label for="repeatEncryptionRecoveryPassword"><?php p($l->t("Repeat Recovery key password")); ?></label> + <br/> + <input + type='radio' + id='adminEnableRecovery' + name='adminEnableRecovery' + value='1' + <?php echo($_["recoveryEnabled"] === '1' ? 'checked="checked"' : ''); ?> /> + <label for="adminEnableRecovery"><?php p($l->t("Enabled")); ?></label> + <br/> + + <input + type='radio' + id='adminDisableRecovery' + name='adminEnableRecovery' + value='0' + <?php echo($_["recoveryEnabled"] === '0' ? 'checked="checked"' : ''); ?> /> + <label for="adminDisableRecovery"><?php p($l->t("Disabled")); ?></label> + </p> + <br/><br/> + + <p name="changeRecoveryPasswordBlock" id="encryptionChangeRecoveryKey" <?php if ($_['recoveryEnabled'] === '0') print_unescaped('class="hidden"');?>> + <strong><?php p($l->t("Change recovery key password:")); ?></strong> + <span class="msg"></span> + <br/><br/> + <input + type="password" + name="changeRecoveryPassword" + id="oldEncryptionRecoveryPassword" /> + <label for="oldEncryptionRecoveryPassword"><?php p($l->t("Old Recovery key password")); ?></label> + <br/> + <br/> + <input + type="password" + name="changeRecoveryPassword" + id="newEncryptionRecoveryPassword" /> + <label for="newEncryptionRecoveryPassword"><?php p($l->t("New Recovery key password")); ?></label> + <br/> + <input + type="password" + name="changeRecoveryPassword" + id="repeatedNewEncryptionRecoveryPassword" /> + <label for="repeatEncryptionRecoveryPassword"><?php p($l->t("Repeat New Recovery key password")); ?></label> + <br/> + <button + type="button" + name="submitChangeRecoveryKey"> + <?php p($l->t("Change Password")); ?> + </button> + </p> + <?php endif; ?> +</form> diff --git a/apps/encryption/templates/settings-personal.php b/apps/encryption/templates/settings-personal.php new file mode 100644 index 00000000000..6b8821ca8a8 --- /dev/null +++ b/apps/encryption/templates/settings-personal.php @@ -0,0 +1,72 @@ +<?php + /** @var array $_ */ + /** @var OC_L10N $l */ +script('encryption', 'settings-personal'); +script('core', 'multiselect'); +?> +<form id="encryption" class="section"> + <h2><?php p($l->t('ownCloud basic encryption module')); ?></h2> + + <?php if ($_["initialized"] === \OCA\Encryption\Session::NOT_INITIALIZED ): ?> + + <?php p($l->t("Encryption App is enabled but your keys are not initialized, please log-out and log-in again")); ?> + + <?php elseif ( $_["initialized"] === \OCA\Encryption\Session::INIT_EXECUTED ): ?> + <p> + <a name="changePKPasswd" /> + <label for="changePrivateKeyPasswd"> + <em><?php p( $l->t( "Your private key password no longer matches your log-in password." ) ); ?></em> + </label> + <br /> + <?php p( $l->t( "Set your old private key password to your current log-in password:" ) ); ?> + <?php if ( $_["recoveryEnabledForUser"] ): + p( $l->t( " If you don't remember your old password you can ask your administrator to recover your files." ) ); + endif; ?> + <br /> + <input + type="password" + name="changePrivateKeyPassword" + id="oldPrivateKeyPassword" /> + <label for="oldPrivateKeyPassword"><?php p($l->t( "Old log-in password" )); ?></label> + <br /> + <input + type="password" + name="changePrivateKeyPassword" + id="newPrivateKeyPassword" /> + <label for="newRecoveryPassword"><?php p($l->t( "Current log-in password" )); ?></label> + <br /> + <button + type="button" + name="submitChangePrivateKeyPassword" + disabled><?php p($l->t( "Update Private Key Password" )); ?> + </button> + <span class="msg"></span> + </p> + + <?php elseif ( $_["recoveryEnabled"] && $_["privateKeySet"] && $_["initialized"] === \OCA\Encryption\Session::INIT_SUCCESSFUL ): ?> + <br /> + <p id="userEnableRecovery"> + <label for="userEnableRecovery"><?php p( $l->t( "Enable password recovery:" ) ); ?></label> + <span class="msg"></span> + <br /> + <em><?php p( $l->t( "Enabling this option will allow you to reobtain access to your encrypted files in case of password loss" ) ); ?></em> + <br /> + <input + type='radio' + id='userEnableRecovery' + name='userEnableRecovery' + value='1' + <?php echo ( $_["recoveryEnabledForUser"] ? 'checked="checked"' : '' ); ?> /> + <label for="userEnableRecovery"><?php p( $l->t( "Enabled" ) ); ?></label> + <br /> + + <input + type='radio' + id='userDisableRecovery' + name='userEnableRecovery' + value='0' + <?php echo ( $_["recoveryEnabledForUser"] === false ? 'checked="checked"' : '' ); ?> /> + <label for="userDisableRecovery"><?php p( $l->t( "Disabled" ) ); ?></label> + </p> + <?php endif; ?> +</form> diff --git a/apps/encryption/tests/lib/HookManagerTest.php b/apps/encryption/tests/lib/HookManagerTest.php new file mode 100644 index 00000000000..3c360ff3504 --- /dev/null +++ b/apps/encryption/tests/lib/HookManagerTest.php @@ -0,0 +1,74 @@ +<?php +/** + * @author Clark Tomlinson <clark@owncloud.com> + * @since 3/31/15, 1:54 PM + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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\Tests; + + +use OCA\Encryption\HookManager; +use Test\TestCase; + +class HookManagerTest extends TestCase { + + /** + * @var HookManager + */ + private static $instance; + + /** + * + */ + public function testRegisterHookWithArray() { + self::$instance->registerHook([ + $this->getMockBuilder('OCA\Encryption\Hooks\Contracts\IHook')->disableOriginalConstructor()->getMock(), + $this->getMockBuilder('OCA\Encryption\Hooks\Contracts\IHook')->disableOriginalConstructor()->getMock(), + $this->getMock('NotIHook') + ]); + + $hookInstances = \Test_Helper::invokePrivate(self::$instance, 'hookInstances'); + // Make sure our type checking works + $this->assertCount(2, $hookInstances); + } + + + /** + * + */ + public static function setUpBeforeClass() { + parent::setUpBeforeClass(); + // have to make instance static to preserve data between tests + self::$instance = new HookManager(); + + } + + /** + * + */ + public function testRegisterHooksWithInstance() { + $mock = $this->getMockBuilder('OCA\Encryption\Hooks\Contracts\IHook')->disableOriginalConstructor()->getMock(); + self::$instance->registerHook($mock); + + $hookInstances = \Test_Helper::invokePrivate(self::$instance, 'hookInstances'); + $this->assertCount(3, $hookInstances); + + } + +} diff --git a/apps/encryption/tests/lib/KeyManagerTest.php b/apps/encryption/tests/lib/KeyManagerTest.php new file mode 100644 index 00000000000..d12578bb8d2 --- /dev/null +++ b/apps/encryption/tests/lib/KeyManagerTest.php @@ -0,0 +1,286 @@ +<?php +/** + * @author Clark Tomlinson <fallen013@gmail.com> + * @since 3/5/15, 10:53 AM + * @link http:/www.clarkt.com + * @copyright Clark Tomlinson © 2015 + * + */ + +namespace OCA\Encryption\Tests; + + +use OCA\Encryption\KeyManager; +use Test\TestCase; + +class KeyManagerTest extends TestCase { + /** + * @var KeyManager + */ + private $instance; + /** + * @var string + */ + private $userId; + + /** @var string */ + private $systemKeyId; + + /** @var \PHPUnit_Framework_MockObject_MockObject */ + private $keyStorageMock; + + /** @var \PHPUnit_Framework_MockObject_MockObject */ + private $cryptMock; + + /** @var \PHPUnit_Framework_MockObject_MockObject */ + private $userMock; + + /** @var \PHPUnit_Framework_MockObject_MockObject */ + private $sessionMock; + + /** @var \PHPUnit_Framework_MockObject_MockObject */ + private $logMock; + + /** @var \PHPUnit_Framework_MockObject_MockObject */ + private $utilMock; + + /** @var \PHPUnit_Framework_MockObject_MockObject */ + private $configMock; + + public function setUp() { + parent::setUp(); + $this->userId = 'user1'; + $this->systemKeyId = 'systemKeyId'; + $this->keyStorageMock = $this->getMock('OCP\Encryption\Keys\IStorage'); + $this->cryptMock = $this->getMockBuilder('OCA\Encryption\Crypto\Crypt') + ->disableOriginalConstructor() + ->getMock(); + $this->configMock = $this->getMock('OCP\IConfig'); + $this->configMock->expects($this->any()) + ->method('getAppValue') + ->willReturn($this->systemKeyId); + $this->userMock = $this->getMock('OCP\IUserSession'); + $this->sessionMock = $this->getMockBuilder('OCA\Encryption\Session') + ->disableOriginalConstructor() + ->getMock(); + $this->logMock = $this->getMock('OCP\ILogger'); + $this->utilMock = $this->getMockBuilder('OCA\Encryption\Util') + ->disableOriginalConstructor() + ->getMock(); + + $this->instance = new KeyManager( + $this->keyStorageMock, + $this->cryptMock, + $this->configMock, + $this->userMock, + $this->sessionMock, + $this->logMock, + $this->utilMock); + } + + public function testDeleteShareKey() { + $this->keyStorageMock->expects($this->any()) + ->method('deleteFileKey') + ->with($this->equalTo('/path'), $this->equalTo('keyId.shareKey')) + ->willReturn(true); + + $this->assertTrue( + $this->instance->deleteShareKey('/path', 'keyId') + ); + } + + public function testGetPrivateKey() { + $this->keyStorageMock->expects($this->any()) + ->method('getUserKey') + ->with($this->equalTo($this->userId), $this->equalTo('privateKey')) + ->willReturn('privateKey'); + + + $this->assertSame('privateKey', + $this->instance->getPrivateKey($this->userId) + ); + } + + public function testGetPublicKey() { + $this->keyStorageMock->expects($this->any()) + ->method('getUserKey') + ->with($this->equalTo($this->userId), $this->equalTo('publicKey')) + ->willReturn('publicKey'); + + + $this->assertSame('publicKey', + $this->instance->getPublicKey($this->userId) + ); + } + + public function testRecoveryKeyExists() { + $this->keyStorageMock->expects($this->any()) + ->method('getSystemUserKey') + ->with($this->equalTo($this->systemKeyId . '.publicKey')) + ->willReturn('recoveryKey'); + + + $this->assertTrue($this->instance->recoveryKeyExists()); + } + + public function testCheckRecoveryKeyPassword() { + $this->keyStorageMock->expects($this->any()) + ->method('getSystemUserKey') + ->with($this->equalTo($this->systemKeyId . '.privateKey')) + ->willReturn('recoveryKey'); + $this->cryptMock->expects($this->any()) + ->method('decryptPrivateKey') + ->with($this->equalTo('recoveryKey'), $this->equalTo('pass')) + ->willReturn('decryptedRecoveryKey'); + + $this->assertTrue($this->instance->checkRecoveryPassword('pass')); + } + + public function testSetPublicKey() { + $this->keyStorageMock->expects($this->any()) + ->method('setUserKey') + ->with( + $this->equalTo($this->userId), + $this->equalTo('publicKey'), + $this->equalTo('key')) + ->willReturn(true); + + + $this->assertTrue( + $this->instance->setPublicKey($this->userId, 'key') + ); + } + + public function testSetPrivateKey() { + $this->keyStorageMock->expects($this->any()) + ->method('setUserKey') + ->with( + $this->equalTo($this->userId), + $this->equalTo('privateKey'), + $this->equalTo('key')) + ->willReturn(true); + + + $this->assertTrue( + $this->instance->setPrivateKey($this->userId, 'key') + ); + } + + public function testUserHasKeys() { + $this->keyStorageMock->expects($this->exactly(2)) + ->method('getUserKey') + ->with($this->equalTo($this->userId), $this->anything()) + ->willReturn('key'); + + + $this->assertTrue( + $this->instance->userHasKeys($this->userId) + ); + } + + public function testInit() { + $this->keyStorageMock->expects($this->any()) + ->method('getUserKey') + ->with($this->equalTo($this->userId), $this->equalTo('privateKey')) + ->willReturn('privateKey'); + $this->cryptMock->expects($this->any()) + ->method('decryptPrivateKey') + ->with($this->equalTo('privateKey'), $this->equalTo('pass')) + ->willReturn('decryptedPrivateKey'); + + + $this->assertTrue( + $this->instance->init($this->userId, 'pass') + ); + + } + + public function testSetRecoveryKey() { + $this->keyStorageMock->expects($this->exactly(2)) + ->method('setSystemUserKey') + ->willReturn(true); + $this->cryptMock->expects($this->any()) + ->method('symmetricEncryptFileContent') + ->with($this->equalTo('privateKey'), $this->equalTo('pass')) + ->willReturn('decryptedPrivateKey'); + + + $this->assertTrue( + $this->instance->setRecoveryKey('pass', + array('publicKey' => 'publicKey', 'privateKey' => 'privateKey')) + ); + } + + public function testSetSystemPrivateKey() { + $this->keyStorageMock->expects($this->exactly(1)) + ->method('setSystemUserKey') + ->with($this->equalTo('keyId.privateKey'), $this->equalTo('key')) + ->willReturn(true); + + + $this->assertTrue( + $this->instance->setSystemPrivateKey('keyId', 'key') + ); + } + + public function testGetSystemPrivateKey() { + $this->keyStorageMock->expects($this->exactly(1)) + ->method('getSystemUserKey') + ->with($this->equalTo('keyId.privateKey')) + ->willReturn('systemPrivateKey'); + + + $this->assertSame('systemPrivateKey', + $this->instance->getSystemPrivateKey('keyId') + ); + } + + public function testGetEncryptedFileKey() { + $this->keyStorageMock->expects($this->once()) + ->method('getFileKey') + ->with('/', 'fileKey') + ->willReturn(true); + + $this->assertTrue($this->instance->getEncryptedFileKey('/')); + } + + public function testGetFileKey() { + $this->keyStorageMock->expects($this->exactly(4)) + ->method('getFileKey') + ->willReturn(true); + + $this->keyStorageMock->expects($this->once()) + ->method('getSystemUserKey') + ->willReturn(true); + + $this->cryptMock->expects($this->once()) + ->method('symmetricDecryptFileContent') + ->willReturn(true); + + $this->cryptMock->expects($this->once()) + ->method('multiKeyDecrypt') + ->willReturn(true); + + $this->assertTrue($this->instance->getFileKey('/', null)); + $this->assertEmpty($this->instance->getFileKey('/', $this->userId)); + } + + public function testDeletePrivateKey() { + $this->keyStorageMock->expects($this->once()) + ->method('deleteUserKey') + ->with('user1', 'privateKey') + ->willReturn(true); + + $this->assertTrue(\Test_Helper::invokePrivate($this->instance, + 'deletePrivateKey', + [$this->userId])); + } + + public function testDeleteAllFileKeys() { + $this->keyStorageMock->expects($this->once()) + ->method('deleteAllFileKeys') + ->willReturn(true); + + $this->assertTrue($this->instance->deleteAllFileKeys('/')); + } +} diff --git a/apps/encryption/tests/lib/RecoveryTest.php b/apps/encryption/tests/lib/RecoveryTest.php new file mode 100644 index 00000000000..701762b56d6 --- /dev/null +++ b/apps/encryption/tests/lib/RecoveryTest.php @@ -0,0 +1,265 @@ +<?php +/** + * @author Clark Tomlinson <clark@owncloud.com> + * @since 4/3/15, 9:57 AM + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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\Tests; + + +use OCA\Encryption\Recovery; +use Test\TestCase; + +class RecoveryTest extends TestCase { + private static $tempStorage = []; + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $fileMock; + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $viewMock; + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $userSessionMock; + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $keyManagerMock; + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $configMock; + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $cryptMock; + /** + * @var Recovery + */ + private $instance; + + public function testEnableAdminRecovery() { + $this->keyManagerMock->expects($this->exactly(2)) + ->method('recoveryKeyExists') + ->willReturnOnConsecutiveCalls(false, true); + + $this->cryptMock->expects($this->once()) + ->method('createKeyPair') + ->willReturn(true); + + $this->keyManagerMock->expects($this->once()) + ->method('setRecoveryKey') + ->willReturn(false); + + $this->keyManagerMock->expects($this->exactly(2)) + ->method('checkRecoveryPassword') + ->willReturnOnConsecutiveCalls(true, false); + + $this->assertTrue($this->instance->enableAdminRecovery('password')); + $this->assertArrayHasKey('recoveryAdminEnabled', self::$tempStorage); + $this->assertEquals(1, self::$tempStorage['recoveryAdminEnabled']); + + $this->assertFalse($this->instance->enableAdminRecovery('password')); + } + + public function testChangeRecoveryKeyPassword() { + $this->assertFalse($this->instance->changeRecoveryKeyPassword('password', + 'passwordOld')); + + $this->keyManagerMock->expects($this->once()) + ->method('getSystemPrivateKey'); + + $this->cryptMock->expects($this->once()) + ->method('decryptPrivateKey'); + + $this->cryptMock->expects($this->once()) + ->method('symmetricEncryptFileContent') + ->willReturn(true); + + $this->assertTrue($this->instance->changeRecoveryKeyPassword('password', + 'passwordOld')); + } + + public function testDisableAdminRecovery() { + + $this->keyManagerMock->expects($this->exactly(2)) + ->method('checkRecoveryPassword') + ->willReturnOnConsecutiveCalls(true, false); + + $this->assertArrayHasKey('recoveryAdminEnabled', self::$tempStorage); + $this->assertTrue($this->instance->disableAdminRecovery('password')); + $this->assertEquals(0, self::$tempStorage['recoveryAdminEnabled']); + + $this->assertFalse($this->instance->disableAdminRecovery('password')); + } + + public function testIsRecoveryEnabledForUser() { + + $this->configMock->expects($this->exactly(2)) + ->method('getUserValue') + ->willReturnOnConsecutiveCalls('1', '0'); + + $this->assertTrue($this->instance->isRecoveryEnabledForUser()); + $this->assertFalse($this->instance->isRecoveryEnabledForUser('admin')); + } + + public function testIsRecoveryKeyEnabled() { + $this->assertFalse($this->instance->isRecoveryKeyEnabled()); + self::$tempStorage['recoveryAdminEnabled'] = '1'; + $this->assertTrue($this->instance->isRecoveryKeyEnabled()); + } + + public function testSetRecoveryFolderForUser() { + $this->viewMock->expects($this->exactly(2)) + ->method('getDirectoryContent') + ->willReturn([]); + $this->assertTrue($this->instance->setRecoveryForUser(0)); + $this->assertTrue($this->instance->setRecoveryForUser('1')); + } + + public function testRecoverUserFiles() { + $this->viewMock->expects($this->once()) + ->method('getDirectoryContent') + ->willReturn([]); + + $this->cryptMock->expects($this->once()) + ->method('decryptPrivateKey'); + $this->assertNull($this->instance->recoverUsersFiles('password', + 'admin')); + } + + public function testRecoverFile() { + $this->keyManagerMock->expects($this->once()) + ->method('getEncryptedFileKey') + ->willReturn(true); + + $this->keyManagerMock->expects($this->once()) + ->method('getShareKey') + ->willReturn(true); + + $this->cryptMock->expects($this->once()) + ->method('multiKeyDecrypt') + ->willReturn(true); + + $this->fileMock->expects($this->once()) + ->method('getAccessList') + ->willReturn(['users' => ['admin']]); + + $this->keyManagerMock->expects($this->once()) + ->method('getPublicKey') + ->willReturn('publicKey'); + + $this->keyManagerMock->expects($this->once()) + ->method('addSystemKeys') + ->willReturn(['admin' => 'publicKey']); + + + $this->cryptMock->expects($this->once()) + ->method('multiKeyEncrypt'); + + $this->keyManagerMock->expects($this->once()) + ->method('setAllFileKeys'); + + $this->assertNull(\Test_Helper::invokePrivate($this->instance, + 'recoverFile', + ['/', 'testkey'])); + } + + protected function setUp() { + parent::setUp(); + + + $this->userSessionMock = $this->getMockBuilder('OCP\IUserSession') + ->disableOriginalConstructor() + ->setMethods([ + 'isLoggedIn', + 'getUID', + 'login', + 'logout', + 'setUser', + 'getUser' + ]) + ->getMock(); + + $this->userSessionMock->expects($this->any())->method('getUID')->will($this->returnValue('admin')); + + $this->userSessionMock->expects($this->any()) + ->method($this->anything()) + ->will($this->returnSelf()); + + $this->cryptMock = $this->getMockBuilder('OCA\Encryption\Crypto\Crypt')->disableOriginalConstructor()->getMock(); + $randomMock = $this->getMock('OCP\Security\ISecureRandom'); + $this->keyManagerMock = $this->getMockBuilder('OCA\Encryption\KeyManager')->disableOriginalConstructor()->getMock(); + $this->configMock = $this->getMock('OCP\IConfig'); + $keyStorageMock = $this->getMock('OCP\Encryption\Keys\IStorage'); + $this->fileMock = $this->getMock('OCP\Encryption\IFile'); + $this->viewMock = $this->getMock('OC\Files\View'); + + $this->configMock->expects($this->any()) + ->method('setAppValue') + ->will($this->returnCallback([$this, 'setValueTester'])); + + $this->configMock->expects($this->any()) + ->method('getAppValue') + ->will($this->returnCallback([$this, 'getValueTester'])); + + $this->instance = new Recovery($this->userSessionMock, + $this->cryptMock, + $randomMock, + $this->keyManagerMock, + $this->configMock, + $keyStorageMock, + $this->fileMock, + $this->viewMock); + } + + + /** + * @param $app + * @param $key + * @param $value + */ + public function setValueTester($app, $key, $value) { + self::$tempStorage[$key] = $value; + } + + /** + * @param $key + */ + public function removeValueTester($key) { + unset(self::$tempStorage[$key]); + } + + /** + * @param $app + * @param $key + * @return mixed + */ + public function getValueTester($app, $key) { + if (!empty(self::$tempStorage[$key])) { + return self::$tempStorage[$key]; + } + return null; + } + + +} diff --git a/apps/encryption/tests/lib/SessionTest.php b/apps/encryption/tests/lib/SessionTest.php new file mode 100644 index 00000000000..f7e026808f0 --- /dev/null +++ b/apps/encryption/tests/lib/SessionTest.php @@ -0,0 +1,140 @@ +<?php +/** + * @author Clark Tomlinson <clark@owncloud.com> + * @since 3/31/15, 10:19 AM + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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\Tests; + + +use OCA\Encryption\Session; +use Test\TestCase; + +class SessionTest extends TestCase { + private static $tempStorage = []; + /** + * @var Session + */ + private $instance; + private $sessionMock; + + /** + * @expectedException \OCA\Encryption\Exceptions\PrivateKeyMissingException + * @expectedExceptionMessage Private Key missing for user: please try to log-out and log-in again + */ + public function testThatGetPrivateKeyThrowsExceptionWhenNotSet() { + $this->instance->getPrivateKey(); + } + + /** + * @depends testThatGetPrivateKeyThrowsExceptionWhenNotSet + */ + public function testSetAndGetPrivateKey() { + $this->instance->setPrivateKey('dummyPrivateKey'); + $this->assertEquals('dummyPrivateKey', $this->instance->getPrivateKey()); + + } + + /** + * @depends testSetAndGetPrivateKey + */ + public function testIsPrivateKeySet() { + $this->assertTrue($this->instance->isPrivateKeySet()); + + unset(self::$tempStorage['privateKey']); + $this->assertFalse($this->instance->isPrivateKeySet()); + + // Set private key back so we can test clear method + self::$tempStorage['privateKey'] = 'dummyPrivateKey'; + } + + /** + * + */ + public function testSetAndGetStatusWillSetAndReturn() { + // Check if get status will return 0 if it has not been set before + $this->assertEquals(0, $this->instance->getStatus()); + + $this->instance->setStatus(Session::NOT_INITIALIZED); + $this->assertEquals(0, $this->instance->getStatus()); + + $this->instance->setStatus(Session::INIT_EXECUTED); + $this->assertEquals(1, $this->instance->getStatus()); + + $this->instance->setStatus(Session::INIT_SUCCESSFUL); + $this->assertEquals(2, $this->instance->getStatus()); + } + + /** + * @param $key + * @param $value + */ + public function setValueTester($key, $value) { + self::$tempStorage[$key] = $value; + } + + /** + * @param $key + */ + public function removeValueTester($key) { + unset(self::$tempStorage[$key]); + } + + /** + * @param $key + * @return mixed + */ + public function getValueTester($key) { + if (!empty(self::$tempStorage[$key])) { + return self::$tempStorage[$key]; + } + return null; + } + + /** + * + */ + public function testClearWillRemoveValues() { + $this->instance->clear(); + $this->assertEmpty(self::$tempStorage); + } + + /** + * + */ + protected function setUp() { + parent::setUp(); + $this->sessionMock = $this->getMock('OCP\ISession'); + + $this->sessionMock->expects($this->any()) + ->method('set') + ->will($this->returnCallback([$this, "setValueTester"])); + + $this->sessionMock->expects($this->any()) + ->method('get') + ->will($this->returnCallback([$this, "getValueTester"])); + + $this->sessionMock->expects($this->any()) + ->method('remove') + ->will($this->returnCallback([$this, "removeValueTester"])); + + + $this->instance = new Session($this->sessionMock); + } +} diff --git a/apps/encryption/tests/lib/UtilTest.php b/apps/encryption/tests/lib/UtilTest.php new file mode 100644 index 00000000000..fa87a629f2f --- /dev/null +++ b/apps/encryption/tests/lib/UtilTest.php @@ -0,0 +1,128 @@ +<?php +/** + * @author Clark Tomlinson <clark@owncloud.com> + * @since 3/31/15, 3:49 PM + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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\Tests; + + +use OCA\Encryption\Util; +use Test\TestCase; + +class UtilTest extends TestCase { + private static $tempStorage = []; + private $configMock; + private $filesMock; + /** + * @var Util + */ + private $instance; + + public function testSetRecoveryForUser() { + $this->instance->setRecoveryForUser('1'); + $this->assertArrayHasKey('recoveryEnabled', self::$tempStorage); + } + + /** + * + */ + public function testIsRecoveryEnabledForUser() { + $this->assertTrue($this->instance->isRecoveryEnabledForUser()); + + // Assert recovery will return default value if not set + unset(self::$tempStorage['recoveryEnabled']); + $this->assertEquals(0, $this->instance->isRecoveryEnabledForUser()); + } + + public function testUserHasFiles() { + $this->filesMock->expects($this->once()) + ->method('file_exists') + ->will($this->returnValue(true)); + + $this->assertTrue($this->instance->userHasFiles('admin')); + } + + protected function setUp() { + parent::setUp(); + $this->filesMock = $this->getMock('OC\Files\View'); + + $cryptMock = $this->getMockBuilder('OCA\Encryption\Crypto\Crypt') + ->disableOriginalConstructor() + ->getMock(); + $loggerMock = $this->getMock('OCP\ILogger'); + $userSessionMock = $this->getMockBuilder('OCP\IUserSession') + ->disableOriginalConstructor() + ->setMethods([ + 'isLoggedIn', + 'getUID', + 'login', + 'logout', + 'setUser', + 'getUser' + ]) + ->getMock(); + + $userSessionMock->method('isLoggedIn')->will($this->returnValue(true)); + + $userSessionMock->method('getUID')->will($this->returnValue('admin')); + + $userSessionMock->expects($this->any()) + ->method($this->anything()) + ->will($this->returnSelf()); + + + $this->configMock = $configMock = $this->getMock('OCP\IConfig'); + + $this->configMock->expects($this->any()) + ->method('getUserValue') + ->will($this->returnCallback([$this, 'getValueTester'])); + + $this->configMock->expects($this->any()) + ->method('setUserValue') + ->will($this->returnCallback([$this, 'setValueTester'])); + + $this->instance = new Util($this->filesMock, $cryptMock, $loggerMock, $userSessionMock, $configMock); + } + + /** + * @param $userId + * @param $app + * @param $key + * @param $value + */ + public function setValueTester($userId, $app, $key, $value) { + self::$tempStorage[$key] = $value; + } + + /** + * @param $userId + * @param $app + * @param $key + * @param $default + * @return mixed + */ + public function getValueTester($userId, $app, $key, $default) { + if (!empty(self::$tempStorage[$key])) { + return self::$tempStorage[$key]; + } + return $default ?: null; + } + +} diff --git a/apps/encryption/tests/lib/crypto/encryptionTest.php b/apps/encryption/tests/lib/crypto/encryptionTest.php new file mode 100644 index 00000000000..52a322463a9 --- /dev/null +++ b/apps/encryption/tests/lib/crypto/encryptionTest.php @@ -0,0 +1,78 @@ +<?php + +/** + * ownCloud + * + * @copyright (C) 2015 ownCloud, Inc. + * + * @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\Encryption\Tests\Crypto; + +use Test\TestCase; +use OCA\Encryption\Crypto\Encryption; + +class EncryptionTest extends TestCase { + + /** @var Encryption */ + private $instance; + + /** @var \PHPUnit_Framework_MockObject_MockObject */ + private $keyManagerMock; + + /** @var \PHPUnit_Framework_MockObject_MockObject */ + private $cryptMock; + + /** @var \PHPUnit_Framework_MockObject_MockObject */ + private $utilMock; + + public function setUp() { + parent::setUp(); + + $this->cryptMock = $this->getMockBuilder('OCA\Encryption\Crypto\Crypt') + ->disableOriginalConstructor() + ->getMock(); + $this->utilMock = $this->getMockBuilder('OCA\Encryption\Util') + ->disableOriginalConstructor() + ->getMock(); + $this->keyManagerMock = $this->getMockBuilder('OCA\Encryption\KeyManager') + ->disableOriginalConstructor() + ->getMock(); + + $this->instance = new Encryption($this->cryptMock, $this->keyManagerMock, $this->utilMock); + } + + /** + * @dataProvider dataProviderForTestGetPathToRealFile + */ + public function testGetPathToRealFile($path, $expected) { + $this->assertSame($expected, + \Test_Helper::invokePrivate($this->instance, 'getPathToRealFile', array($path)) + ); + } + + public function dataProviderForTestGetPathToRealFile() { + return array( + array('/user/files/foo/bar.txt', '/user/files/foo/bar.txt'), + array('/user/files/foo.txt', '/user/files/foo.txt'), + array('/user/files_versions/foo.txt.v543534', '/user/files/foo.txt'), + array('/user/files_versions/foo/bar.txt.v5454', '/user/files/foo/bar.txt'), + ); + } + + +}
\ No newline at end of file diff --git a/apps/encryption/tests/lib/users/SetupTest.php b/apps/encryption/tests/lib/users/SetupTest.php new file mode 100644 index 00000000000..6a66df3674b --- /dev/null +++ b/apps/encryption/tests/lib/users/SetupTest.php @@ -0,0 +1,81 @@ +<?php +/** + * @author Clark Tomlinson <clark@owncloud.com> + * @since 4/6/15, 11:50 AM + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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\Tests\Users; + + +use OCA\Encryption\Users\Setup; +use Test\TestCase; + +class SetupTest extends TestCase { + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $keyManagerMock; + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $cryptMock; + /** + * @var Setup + */ + private $instance; + + public function testSetupServerSide() { + $this->keyManagerMock->expects($this->exactly(2)) + ->method('userHasKeys') + ->with('admin') + ->willReturnOnConsecutiveCalls(true, false); + + $this->assertTrue($this->instance->setupServerSide('admin', + 'password')); + + $this->keyManagerMock->expects($this->once()) + ->method('storeKeyPair') + ->with('admin', 'password') + ->willReturn(false); + + $this->assertFalse($this->instance->setupServerSide('admin', + 'password')); + } + + protected function setUp() { + parent::setUp(); + $logMock = $this->getMock('OCP\ILogger'); + $userSessionMock = $this->getMockBuilder('OCP\IUserSession') + ->disableOriginalConstructor() + ->getMock(); + $this->cryptMock = $this->getMockBuilder('OCA\Encryption\Crypto\Crypt') + ->disableOriginalConstructor() + ->getMock(); + + $this->keyManagerMock = $this->getMockBuilder('OCA\Encryption\KeyManager') + ->disableOriginalConstructor() + ->getMock(); + + $this->instance = new Setup($logMock, + $userSessionMock, + $this->cryptMock, + $this->keyManagerMock); + } + +} |