diff options
Diffstat (limited to 'apps/files_encryption/lib')
-rw-r--r-- | apps/files_encryption/lib/capabilities.php | 2 | ||||
-rw-r--r-- | apps/files_encryption/lib/crypt.php | 41 | ||||
-rw-r--r-- | apps/files_encryption/lib/exceptions.php | 55 | ||||
-rw-r--r-- | apps/files_encryption/lib/helper.php | 161 | ||||
-rw-r--r-- | apps/files_encryption/lib/hooks.php | 626 | ||||
-rw-r--r-- | apps/files_encryption/lib/keymanager.php | 594 | ||||
-rw-r--r-- | apps/files_encryption/lib/migration.php | 264 | ||||
-rw-r--r-- | apps/files_encryption/lib/proxy.php | 27 | ||||
-rw-r--r-- | apps/files_encryption/lib/session.php | 75 | ||||
-rw-r--r-- | apps/files_encryption/lib/stream.php | 51 | ||||
-rw-r--r-- | apps/files_encryption/lib/util.php | 211 |
11 files changed, 1372 insertions, 735 deletions
diff --git a/apps/files_encryption/lib/capabilities.php b/apps/files_encryption/lib/capabilities.php index ef94c9e086d..e6e4ee7d419 100644 --- a/apps/files_encryption/lib/capabilities.php +++ b/apps/files_encryption/lib/capabilities.php @@ -6,7 +6,7 @@ * See the COPYING-README file. */ -namespace OCA\Encryption; +namespace OCA\Files_Encryption; class Capabilities { diff --git a/apps/files_encryption/lib/crypt.php b/apps/files_encryption/lib/crypt.php index 59b191097af..38993ba65b0 100644 --- a/apps/files_encryption/lib/crypt.php +++ b/apps/files_encryption/lib/crypt.php @@ -3,10 +3,12 @@ /**
* ownCloud
*
- * @author Sam Tuke, Frank Karlitschek, Robin Appelman
- * @copyright 2012 Sam Tuke samtuke@owncloud.com,
- * Robin Appelman icewind@owncloud.com, Frank Karlitschek
- * frank@owncloud.org
+ * @copyright (C) 2014 ownCloud, Inc.
+ *
+ * @author Bjoern Schiessle <schiessle@owncloud.com>
+ * @author Sam Tuke <samtuke@owncloud.com>
+ * @author Frank Karlitschek <frank@owncloud.com>
+ * @author Robin Appelman <icewind@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
@@ -23,8 +25,7 @@ *
*/
-namespace OCA\Encryption;
-use OCA\Encryption\Exceptions\EncryptionException;
+namespace OCA\Files_Encryption;
/**
* Class for common cryptography functionality
@@ -132,7 +133,7 @@ class Crypt { * Check if a file's contents contains an IV and is symmetrically encrypted
* @param string $content
* @return boolean
- * @note see also OCA\Encryption\Util->isEncryptedPath()
+ * @note see also \OCA\Files_Encryption\Util->isEncryptedPath()
*/
public static function isCatfileContent($content) {
@@ -189,7 +190,7 @@ class Crypt { * @param string $passphrase
* @param string $cypher used for encryption, currently we support AES-128-CFB and AES-256-CFB
* @return string encrypted file content
- * @throws \OCA\Encryption\Exceptions\EncryptionException
+ * @throws \OCA\Files_Encryption\Exception\EncryptionException
*/
private static function encrypt($plainContent, $iv, $passphrase = '', $cipher = Crypt::DEFAULT_CIPHER) {
@@ -198,7 +199,7 @@ class Crypt { if (!$encryptedContent) {
$error = "Encryption (symmetric) of content failed: " . openssl_error_string();
\OCP\Util::writeLog('Encryption library', $error, \OCP\Util::ERROR);
- throw new Exceptions\EncryptionException($error, 50);
+ throw new Exception\EncryptionException($error, Exception\EncryptionException::ENCRYPTION_FAILED);
}
return $encryptedContent;
@@ -290,7 +291,7 @@ class Crypt { $padded = self::addPadding($catfile);
return $padded;
- } catch (EncryptionException $e) {
+ } catch (Exception\EncryptionException $e) {
$message = 'Could not encrypt file content (code: ' . $e->getCode() . '): ';
\OCP\Util::writeLog('files_encryption', $message . $e->getMessage(), \OCP\Util::ERROR);
return false;
@@ -378,7 +379,7 @@ class Crypt { * @param string $plainContent content to be encrypted
* @param array $publicKeys array keys must be the userId of corresponding user
* @return array keys: keys (array, key = userId), data
- * @throws \OCA\Encryption\Exceptions\\MultiKeyEncryptException if encryption failed
+ * @throws \OCA\Files_Encryption\Exception\MultiKeyEncryptException if encryption failed
* @note symmetricDecryptFileContent() can decrypt files created using this method
*/
public static function multiKeyEncrypt($plainContent, array $publicKeys) {
@@ -386,7 +387,7 @@ class Crypt { // openssl_seal returns false without errors if $plainContent
// is empty, so trigger our own error
if (empty($plainContent)) {
- throw new Exceptions\MultiKeyEncryptException('Cannot multiKeyEncrypt empty plain content', 10);
+ throw new Exception\MultiKeyEncryptException('Cannot multiKeyEncrypt empty plain content', Exception\MultiKeyEncryptException::EMPTY_DATA);
}
// Set empty vars to be set by openssl by reference
@@ -413,7 +414,8 @@ class Crypt { );
} else {
- throw new Exceptions\MultiKeyEncryptException('multi key encryption failed: ' . openssl_error_string(), 20);
+ throw new Exception\MultiKeyEncryptException('multi key encryption failed: ' . openssl_error_string(),
+ Exception\MultiKeyEncryptException::OPENSSL_SEAL_FAILED);
}
}
@@ -423,7 +425,7 @@ class Crypt { * @param string $encryptedContent
* @param string $shareKey
* @param mixed $privateKey
- * @throws \OCA\Encryption\Exceptions\\MultiKeyDecryptException if decryption failed
+ * @throws \OCA\Files_Encryption\Exception\MultiKeyDecryptException if decryption failed
* @internal param string $plainContent contains decrypted content
* @return string $plainContent decrypted string
* @note symmetricDecryptFileContent() can be used to decrypt files created using this method
@@ -433,7 +435,8 @@ class Crypt { public static function multiKeyDecrypt($encryptedContent, $shareKey, $privateKey) {
if (!$encryptedContent) {
- throw new Exceptions\MultiKeyDecryptException('Cannot mutliKeyDecrypt empty plain content', 10);
+ throw new Exception\MultiKeyDecryptException('Cannot mutliKeyDecrypt empty plain content',
+ Exception\MultiKeyDecryptException::EMPTY_DATA);
}
if (openssl_open($encryptedContent, $plainContent, $shareKey, $privateKey)) {
@@ -441,7 +444,8 @@ class Crypt { return $plainContent;
} else {
- throw new Exceptions\MultiKeyDecryptException('multiKeyDecrypt with share-key' . $shareKey . 'failed: ' . openssl_error_string(), 20);
+ throw new Exception\MultiKeyDecryptException('multiKeyDecrypt with share-key' . $shareKey . 'failed: ' . openssl_error_string(),
+ Exception\MultiKeyDecryptException::OPENSSL_OPEN_FAILED);
}
}
@@ -550,14 +554,15 @@ class Crypt { * get chiper from header
*
* @param array $header
- * @throws \OCA\Encryption\Exceptions\EncryptionException
+ * @throws \OCA\Files_Encryption\Exception\EncryptionException
*/
public static function getCipher($header) {
$cipher = isset($header['cipher']) ? $header['cipher'] : 'AES-128-CFB';
if ($cipher !== 'AES-256-CFB' && $cipher !== 'AES-128-CFB') {
- throw new \OCA\Encryption\Exceptions\EncryptionException('file header broken, no supported cipher defined', 40);
+ throw new Exception\EncryptionException('file header broken, no supported cipher defined',
+ Exception\EncryptionException::UNKNOWN_CIPHER);
}
return $cipher;
diff --git a/apps/files_encryption/lib/exceptions.php b/apps/files_encryption/lib/exceptions.php deleted file mode 100644 index 3ea27faf406..00000000000 --- a/apps/files_encryption/lib/exceptions.php +++ /dev/null @@ -1,55 +0,0 @@ -<?php -/** - * ownCloud - * - * @author Bjoern Schiessle - * @copyright 2014 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\Exceptions; - -/** - * General encryption exception - * Possible Error Codes: - * 10 - unexpected end of encryption header - * 20 - unexpected blog size - * 30 - encryption header to large - * 40 - unknown cipher - * 50 - encryption failed - */ -class EncryptionException extends \Exception { -} - -/** - * Throw this exception if multi key encrytion fails - * - * Possible error codes: - * 10 - empty plain content was given - * 20 - openssl_seal failed - */ -class MultiKeyEncryptException extends EncryptionException { -} - -/** - * Throw this encryption if multi key decryption failed - * - * Possible error codes: - * 10 - empty encrypted content was given - * 20 - openssl_open failed - */ -class MultiKeyDecryptException extends EncryptionException { -} diff --git a/apps/files_encryption/lib/helper.php b/apps/files_encryption/lib/helper.php index 53c380ab2b3..b9d45f67363 100644 --- a/apps/files_encryption/lib/helper.php +++ b/apps/files_encryption/lib/helper.php @@ -3,8 +3,10 @@ /** * ownCloud * - * @author Florin Peter - * @copyright 2013 Florin Peter <owncloud@florin-peter.de> + * @copyright (C) 2014 ownCloud, Inc. + * + * @author Florin Peter <owncloud@florin-peter.de> + * @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 @@ -21,11 +23,11 @@ * */ -namespace OCA\Encryption; +namespace OCA\Files_Encryption; /** * Class to manage registration of hooks an various helper methods - * @package OCA\Encryption + * @package OCA\Files_Encryption */ class Helper { @@ -37,9 +39,9 @@ class Helper { */ public static function registerShareHooks() { - \OCP\Util::connectHook('OCP\Share', 'pre_shared', 'OCA\Encryption\Hooks', 'preShared'); - \OCP\Util::connectHook('OCP\Share', 'post_shared', 'OCA\Encryption\Hooks', 'postShared'); - \OCP\Util::connectHook('OCP\Share', 'post_unshare', 'OCA\Encryption\Hooks', 'postUnshare'); + \OCP\Util::connectHook('OCP\Share', 'pre_shared', 'OCA\Files_Encryption\Hooks', 'preShared'); + \OCP\Util::connectHook('OCP\Share', 'post_shared', 'OCA\Files_Encryption\Hooks', 'postShared'); + \OCP\Util::connectHook('OCP\Share', 'post_unshare', 'OCA\Files_Encryption\Hooks', 'postUnshare'); } /** @@ -48,12 +50,12 @@ class Helper { */ public static function registerUserHooks() { - \OCP\Util::connectHook('OC_User', 'post_login', 'OCA\Encryption\Hooks', 'login'); - \OCP\Util::connectHook('OC_User', 'logout', 'OCA\Encryption\Hooks', 'logout'); - \OCP\Util::connectHook('OC_User', 'post_setPassword', 'OCA\Encryption\Hooks', 'setPassphrase'); - \OCP\Util::connectHook('OC_User', 'pre_setPassword', 'OCA\Encryption\Hooks', 'preSetPassphrase'); - \OCP\Util::connectHook('OC_User', 'post_createUser', 'OCA\Encryption\Hooks', 'postCreateUser'); - \OCP\Util::connectHook('OC_User', 'post_deleteUser', 'OCA\Encryption\Hooks', 'postDeleteUser'); + \OCP\Util::connectHook('OC_User', 'post_login', 'OCA\Files_Encryption\Hooks', 'login'); + \OCP\Util::connectHook('OC_User', 'logout', 'OCA\Files_Encryption\Hooks', 'logout'); + \OCP\Util::connectHook('OC_User', 'post_setPassword', 'OCA\Files_Encryption\Hooks', 'setPassphrase'); + \OCP\Util::connectHook('OC_User', 'pre_setPassword', 'OCA\Files_Encryption\Hooks', 'preSetPassphrase'); + \OCP\Util::connectHook('OC_User', 'post_createUser', 'OCA\Files_Encryption\Hooks', 'postCreateUser'); + \OCP\Util::connectHook('OC_User', 'post_deleteUser', 'OCA\Files_Encryption\Hooks', 'postDeleteUser'); } /** @@ -62,14 +64,15 @@ class Helper { */ public static function registerFilesystemHooks() { - \OCP\Util::connectHook('OC_Filesystem', 'rename', 'OCA\Encryption\Hooks', 'preRename'); - \OCP\Util::connectHook('OC_Filesystem', 'post_rename', 'OCA\Encryption\Hooks', 'postRenameOrCopy'); - \OCP\Util::connectHook('OC_Filesystem', 'copy', 'OCA\Encryption\Hooks', 'preCopy'); - \OCP\Util::connectHook('OC_Filesystem', 'post_copy', 'OCA\Encryption\Hooks', 'postRenameOrCopy'); - \OCP\Util::connectHook('OC_Filesystem', 'post_delete', 'OCA\Encryption\Hooks', 'postDelete'); - \OCP\Util::connectHook('OC_Filesystem', 'delete', 'OCA\Encryption\Hooks', 'preDelete'); - \OCP\Util::connectHook('OC_Filesystem', 'post_umount', 'OCA\Encryption\Hooks', 'postUmount'); - \OCP\Util::connectHook('OC_Filesystem', 'umount', 'OCA\Encryption\Hooks', 'preUmount'); + \OCP\Util::connectHook('OC_Filesystem', 'rename', 'OCA\Files_Encryption\Hooks', 'preRename'); + \OCP\Util::connectHook('OC_Filesystem', 'post_rename', 'OCA\Files_Encryption\Hooks', 'postRenameOrCopy'); + \OCP\Util::connectHook('OC_Filesystem', 'copy', 'OCA\Files_Encryption\Hooks', 'preCopy'); + \OCP\Util::connectHook('OC_Filesystem', 'post_copy', 'OCA\Files_Encryption\Hooks', 'postRenameOrCopy'); + \OCP\Util::connectHook('OC_Filesystem', 'post_delete', 'OCA\Files_Encryption\Hooks', 'postDelete'); + \OCP\Util::connectHook('OC_Filesystem', 'delete', 'OCA\Files_Encryption\Hooks', 'preDelete'); + \OCP\Util::connectHook('\OC\Core\LostPassword\Controller\LostController', 'post_passwordReset', 'OCA\Files_Encryption\Hooks', 'postPasswordReset'); + \OCP\Util::connectHook('OC_Filesystem', 'post_umount', 'OCA\Files_Encryption\Hooks', 'postUnmount'); + \OCP\Util::connectHook('OC_Filesystem', 'umount', 'OCA\Files_Encryption\Hooks', 'preUnmount'); } /** @@ -78,8 +81,8 @@ class Helper { */ public static function registerAppHooks() { - \OCP\Util::connectHook('OC_App', 'pre_disable', 'OCA\Encryption\Hooks', 'preDisable'); - \OCP\Util::connectHook('OC_App', 'post_disable', 'OCA\Encryption\Hooks', 'postEnable'); + \OCP\Util::connectHook('OC_App', 'pre_disable', 'OCA\Files_Encryption\Hooks', 'preDisable'); + \OCP\Util::connectHook('OC_App', 'post_disable', 'OCA\Files_Encryption\Hooks', 'postEnable'); } /** @@ -105,12 +108,29 @@ class Helper { } /** + * get recovery key id + * + * @return string|bool recovery key ID or false + */ + public static function getRecoveryKeyId() { + $appConfig = \OC::$server->getAppConfig(); + $key = $appConfig->getValue('files_encryption', 'recoveryKeyId'); + + return ($key === null) ? false : $key; + } + + public static function getPublicShareKeyId() { + $appConfig = \OC::$server->getAppConfig(); + $key = $appConfig->getValue('files_encryption', 'publicShareKeyId'); + + return ($key === null) ? false : $key; + } + + /** * enable recovery * * @param string $recoveryKeyId * @param string $recoveryPassword - * @internal param \OCA\Encryption\Util $util - * @internal param string $password * @return bool */ public static function adminEnableRecovery($recoveryKeyId, $recoveryPassword) { @@ -123,40 +143,24 @@ class Helper { $appConfig->setValue('files_encryption', 'recoveryKeyId', $recoveryKeyId); } - if (!$view->is_dir('/owncloud_private_key')) { - $view->mkdir('/owncloud_private_key'); - } - - if ( - (!$view->file_exists("/public-keys/" . $recoveryKeyId . ".public.key") - || !$view->file_exists("/owncloud_private_key/" . $recoveryKeyId . ".private.key")) - ) { - - $keypair = \OCA\Encryption\Crypt::createKeypair(); + if (!Keymanager::recoveryKeyExists($view)) { - \OC_FileProxy::$enabled = false; + $keypair = Crypt::createKeypair(); // Save public key + Keymanager::setPublicKey($keypair['publicKey'], $recoveryKeyId); - if (!$view->is_dir('/public-keys')) { - $view->mkdir('/public-keys'); - } - - $view->file_put_contents('/public-keys/' . $recoveryKeyId . '.public.key', $keypair['publicKey']); - - $cipher = \OCA\Encryption\Helper::getCipher(); - $encryptedKey = \OCA\Encryption\Crypt::symmetricEncryptFileContent($keypair['privateKey'], $recoveryPassword, $cipher); + $cipher = Helper::getCipher(); + $encryptedKey = Crypt::symmetricEncryptFileContent($keypair['privateKey'], $recoveryPassword, $cipher); if ($encryptedKey) { - Keymanager::setPrivateSystemKey($encryptedKey, $recoveryKeyId . '.private.key'); + Keymanager::setPrivateSystemKey($encryptedKey, $recoveryKeyId); // Set recoveryAdmin as enabled $appConfig->setValue('files_encryption', 'recoveryAdminEnabled', 1); $return = true; } - \OC_FileProxy::$enabled = true; - } else { // get recovery key and check the password - $util = new \OCA\Encryption\Util(new \OC\Files\View('/'), \OCP\User::getUser()); + $util = new Util(new \OC\Files\View('/'), \OCP\User::getUser()); $return = $util->checkRecoveryPassword($recoveryPassword); if ($return) { $appConfig->setValue('files_encryption', 'recoveryAdminEnabled', 1); @@ -356,14 +360,14 @@ class Helper { if ($errorCode === null) { $init = $session->getInitialized(); switch ($init) { - case \OCA\Encryption\Session::INIT_EXECUTED: - $errorCode = \OCA\Encryption\Crypt::ENCRYPTION_PRIVATE_KEY_NOT_VALID_ERROR; + case Session::INIT_EXECUTED: + $errorCode = Crypt::ENCRYPTION_PRIVATE_KEY_NOT_VALID_ERROR; break; - case \OCA\Encryption\Session::NOT_INITIALIZED: - $errorCode = \OCA\Encryption\Crypt::ENCRYPTION_NOT_INITIALIZED_ERROR; + case Session::NOT_INITIALIZED: + $errorCode = Crypt::ENCRYPTION_NOT_INITIALIZED_ERROR; break; default: - $errorCode = \OCA\Encryption\Crypt::ENCRYPTION_UNKNOWN_ERROR; + $errorCode = Crypt::ENCRYPTION_UNKNOWN_ERROR; } } @@ -386,14 +390,10 @@ class Helper { * @return bool true if requirements are met */ public static function checkRequirements() { - $result = true; //openssl extension needs to be loaded - $result &= extension_loaded("openssl"); - // we need php >= 5.3.3 - $result &= version_compare(phpversion(), '5.3.3', '>='); + return extension_loaded("openssl"); - return (bool) $result; } /** @@ -427,52 +427,11 @@ class Helper { */ public static function getOpenSSLConfig() { $config = array('private_key_bits' => 4096); - $config = array_merge(\OCP\Config::getSystemValue('openssl', array()), $config); + $config = array_merge(\OC::$server->getConfig()->getSystemValue('openssl', array()), $config); return $config; } /** - * find all share keys for a given file - * - * @param string $filePath path to the file name relative to the user's files dir - * for example "subdir/filename.txt" - * @param string $shareKeyPath share key prefix path relative to the user's data dir - * for example "user1/files_encryption/share-keys/subdir/filename.txt" - * @param \OC\Files\View $rootView root view, relative to data/ - * @return array list of share key files, path relative to data/$user - */ - public static function findShareKeys($filePath, $shareKeyPath, \OC\Files\View $rootView) { - $result = array(); - - $user = \OCP\User::getUser(); - $util = new Util($rootView, $user); - // get current sharing state - $sharingEnabled = \OCP\Share::isEnabled(); - - // get users sharing this file - $usersSharing = $util->getSharingUsersArray($sharingEnabled, $filePath); - - $pathinfo = pathinfo($shareKeyPath); - - $baseDir = $pathinfo['dirname'] . '/'; - $fileName = $pathinfo['basename']; - foreach ($usersSharing as $user) { - $keyName = $fileName . '.' . $user . '.shareKey'; - if ($rootView->file_exists($baseDir . $keyName)) { - $result[] = $baseDir . $keyName; - } else { - \OC_Log::write( - 'Encryption library', - 'No share key found for user "' . $user . '" for file "' . $fileName . '"', - \OC_Log::WARN - ); - } - } - - return $result; - } - - /** * remember from which file the tmp file (getLocalFile() call) was created * @param string $tmpFile path of tmp file * @param string $originalFile path of the original file relative to data/ @@ -501,7 +460,7 @@ class Helper { */ public static function getCipher() { - $cipher = \OCP\Config::getSystemValue('cipher', Crypt::DEFAULT_CIPHER); + $cipher = \OC::$server->getConfig()->getSystemValue('cipher', Crypt::DEFAULT_CIPHER); if ($cipher !== 'AES-256-CFB' && $cipher !== 'AES-128-CFB') { \OCP\Util::writeLog('files_encryption', diff --git a/apps/files_encryption/lib/hooks.php b/apps/files_encryption/lib/hooks.php new file mode 100644 index 00000000000..7ddde0a3112 --- /dev/null +++ b/apps/files_encryption/lib/hooks.php @@ -0,0 +1,626 @@ +<?php
+
+/**
+ * ownCloud
+ *
+ * @copyright (C) 2014 ownCloud, Inc.
+ *
+ * @author Sam Tuke <samtuke@owncloud.org>
+ * @author Bjoern Schiessle <schiessle@owncloud.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCA\Files_Encryption;
+
+/**
+ * Class for hook specific logic
+ */
+class Hooks {
+
+ // file for which we want to rename the keys after the rename operation was successful
+ private static $renamedFiles = array();
+ // file for which we want to delete the keys after the delete operation was successful
+ private static $deleteFiles = array();
+ // file for which we want to delete the keys after the delete operation was successful
+ private static $unmountedFiles = array();
+
+ /**
+ * Startup encryption backend upon user login
+ * @note This method should never be called for users using client side encryption
+ */
+ public static function login($params) {
+
+ if (\OCP\App::isEnabled('files_encryption') === false) {
+ return true;
+ }
+
+
+ $l = new \OC_L10N('files_encryption');
+
+ $view = new \OC\Files\View('/');
+
+ // ensure filesystem is loaded
+ if (!\OC\Files\Filesystem::$loaded) {
+ \OC_Util::setupFS($params['uid']);
+ }
+
+ $privateKey = Keymanager::getPrivateKey($view, $params['uid']);
+
+ // if no private key exists, check server configuration
+ if (!$privateKey) {
+ //check if all requirements are met
+ if (!Helper::checkRequirements() || !Helper::checkConfiguration()) {
+ $error_msg = $l->t("Missing requirements.");
+ $hint = $l->t('Please make sure that OpenSSL together with the PHP extension is enabled and configured properly. For now, the encryption app has been disabled.');
+ \OC_App::disable('files_encryption');
+ \OCP\Util::writeLog('Encryption library', $error_msg . ' ' . $hint, \OCP\Util::ERROR);
+ \OCP\Template::printErrorPage($error_msg, $hint);
+ }
+ }
+
+ $util = new Util($view, $params['uid']);
+
+ // setup user, if user not ready force relogin
+ if (Helper::setupUser($util, $params['password']) === false) {
+ return false;
+ }
+
+ $session = $util->initEncryption($params);
+
+ // Check if first-run file migration has already been performed
+ $ready = false;
+ $migrationStatus = $util->getMigrationStatus();
+ if ($migrationStatus === Util::MIGRATION_OPEN && $session !== false) {
+ $ready = $util->beginMigration();
+ } elseif ($migrationStatus === Util::MIGRATION_IN_PROGRESS) {
+ // refuse login as long as the initial encryption is running
+ sleep(5);
+ \OCP\User::logout();
+ return false;
+ }
+
+ $result = true;
+
+ // If migration not yet done
+ if ($ready) {
+
+ // Encrypt existing user files
+ try {
+ $result = $util->encryptAll('/' . $params['uid'] . '/' . 'files');
+ } catch (\Exception $ex) {
+ \OCP\Util::writeLog('Encryption library', 'Initial encryption failed! Error: ' . $ex->getMessage(), \OCP\Util::FATAL);
+ $result = false;
+ }
+
+ if ($result) {
+ \OC_Log::write(
+ 'Encryption library', 'Encryption of existing files belonging to "' . $params['uid'] . '" completed'
+ , \OC_Log::INFO
+ );
+ // Register successful migration in DB
+ $util->finishMigration();
+ } else {
+ \OCP\Util::writeLog('Encryption library', 'Initial encryption failed!', \OCP\Util::FATAL);
+ $util->resetMigrationStatus();
+ \OCP\User::logout();
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * remove keys from session during logout
+ */
+ public static function logout() {
+ $session = new Session(new \OC\Files\View());
+ $session->removeKeys();
+ }
+
+ /**
+ * setup encryption backend upon user created
+ * @note This method should never be called for users using client side encryption
+ */
+ public static function postCreateUser($params) {
+
+ if (\OCP\App::isEnabled('files_encryption')) {
+ $view = new \OC\Files\View('/');
+ $util = new Util($view, $params['uid']);
+ Helper::setupUser($util, $params['password']);
+ }
+ }
+
+ /**
+ * cleanup encryption backend upon user deleted
+ * @note This method should never be called for users using client side encryption
+ */
+ public static function postDeleteUser($params) {
+
+ if (\OCP\App::isEnabled('files_encryption')) {
+ Keymanager::deletePublicKey(new \OC\Files\View(), $params['uid']);
+ }
+ }
+
+ /**
+ * If the password can't be changed within ownCloud, than update the key password in advance.
+ */
+ public static function preSetPassphrase($params) {
+ if (\OCP\App::isEnabled('files_encryption')) {
+ if ( ! \OC_User::canUserChangePassword($params['uid']) ) {
+ self::setPassphrase($params);
+ }
+ }
+ }
+
+ /**
+ * Change a user's encryption passphrase
+ * @param array $params keys: uid, password
+ */
+ public static function setPassphrase($params) {
+ if (\OCP\App::isEnabled('files_encryption') === false) {
+ return true;
+ }
+
+ // Only attempt to change passphrase if server-side encryption
+ // is in use (client-side encryption does not have access to
+ // the necessary keys)
+ if (Crypt::mode() === 'server') {
+
+ $view = new \OC\Files\View('/');
+ $session = new Session($view);
+
+ // Get existing decrypted private key
+ $privateKey = $session->getPrivateKey();
+
+ if ($params['uid'] === \OCP\User::getUser() && $privateKey) {
+
+ // Encrypt private key with new user pwd as passphrase
+ $encryptedPrivateKey = Crypt::symmetricEncryptFileContent($privateKey, $params['password'], Helper::getCipher());
+
+ // Save private key
+ if ($encryptedPrivateKey) {
+ Keymanager::setPrivateKey($encryptedPrivateKey, \OCP\User::getUser());
+ } else {
+ \OCP\Util::writeLog('files_encryption', 'Could not update users encryption password', \OCP\Util::ERROR);
+ }
+
+ // NOTE: Session does not need to be updated as the
+ // private key has not changed, only the passphrase
+ // used to decrypt it has changed
+
+
+ } else { // admin changed the password for a different user, create new keys and reencrypt file keys
+
+ $user = $params['uid'];
+ $util = new Util($view, $user);
+ $recoveryPassword = isset($params['recoveryPassword']) ? $params['recoveryPassword'] : null;
+
+ // we generate new keys if...
+ // ...we have a recovery password and the user enabled the recovery key
+ // ...encryption was activated for the first time (no keys exists)
+ // ...the user doesn't have any files
+ if (($util->recoveryEnabledForUser() && $recoveryPassword)
+ || !$util->userKeysExists()
+ || !$view->file_exists($user . '/files')) {
+
+ // backup old keys
+ $util->backupAllKeys('recovery');
+
+ $newUserPassword = $params['password'];
+
+ // make sure that the users home is mounted
+ \OC\Files\Filesystem::initMountPoints($user);
+
+ $keypair = Crypt::createKeypair();
+
+ // Disable encryption proxy to prevent recursive calls
+ $proxyStatus = \OC_FileProxy::$enabled;
+ \OC_FileProxy::$enabled = false;
+
+ // Save public key
+ Keymanager::setPublicKey($keypair['publicKey'], $user);
+
+ // Encrypt private key with new password
+ $encryptedKey = Crypt::symmetricEncryptFileContent($keypair['privateKey'], $newUserPassword, Helper::getCipher());
+ if ($encryptedKey) {
+ Keymanager::setPrivateKey($encryptedKey, $user);
+
+ if ($recoveryPassword) { // if recovery key is set we can re-encrypt the key files
+ $util = new Util($view, $user);
+ $util->recoverUsersFiles($recoveryPassword);
+ }
+ } else {
+ \OCP\Util::writeLog('files_encryption', 'Could not update users encryption password', \OCP\Util::ERROR);
+ }
+
+ \OC_FileProxy::$enabled = $proxyStatus;
+ }
+ }
+ }
+ }
+
+ /**
+ * after password reset we create a new key pair for the user
+ *
+ * @param array $params
+ */
+ public static function postPasswordReset($params) {
+ $uid = $params['uid'];
+ $password = $params['password'];
+
+ $util = new Util(new \OC\Files\View(), $uid);
+ $util->replaceUserKeys($password);
+ }
+
+ /*
+ * check if files can be encrypted to every user.
+ */
+ /**
+ * @param array $params
+ */
+ public static function preShared($params) {
+
+ if (\OCP\App::isEnabled('files_encryption') === false) {
+ return true;
+ }
+
+ $l = new \OC_L10N('files_encryption');
+ $users = array();
+ $view = new \OC\Files\View('/');
+
+ switch ($params['shareType']) {
+ case \OCP\Share::SHARE_TYPE_USER:
+ $users[] = $params['shareWith'];
+ break;
+ case \OCP\Share::SHARE_TYPE_GROUP:
+ $users = \OC_Group::usersInGroup($params['shareWith']);
+ break;
+ }
+
+ $notConfigured = array();
+ foreach ($users as $user) {
+ if (!Keymanager::publicKeyExists($view, $user)) {
+ $notConfigured[] = $user;
+ }
+ }
+
+ if (count($notConfigured) > 0) {
+ $params['run'] = false;
+ $params['error'] = $l->t('Following users are not set up for encryption:') . ' ' . join(', ' , $notConfigured);
+ }
+
+ }
+
+ /**
+ * update share keys if a file was shared
+ */
+ public static function postShared($params) {
+
+ if (\OCP\App::isEnabled('files_encryption') === false) {
+ return true;
+ }
+
+ if ($params['itemType'] === 'file' || $params['itemType'] === 'folder') {
+
+ $path = \OC\Files\Filesystem::getPath($params['fileSource']);
+
+ self::updateKeyfiles($path);
+ }
+ }
+
+ /**
+ * update keyfiles and share keys recursively
+ *
+ * @param string $path to the file/folder
+ */
+ private static function updateKeyfiles($path) {
+ $view = new \OC\Files\View('/');
+ $userId = \OCP\User::getUser();
+ $session = new Session($view);
+ $util = new Util($view, $userId);
+ $sharingEnabled = \OCP\Share::isEnabled();
+
+ $mountManager = \OC\Files\Filesystem::getMountManager();
+ $mount = $mountManager->find('/' . $userId . '/files' . $path);
+ $mountPoint = $mount->getMountPoint();
+
+ // if a folder was shared, get a list of all (sub-)folders
+ if ($view->is_dir('/' . $userId . '/files' . $path)) {
+ $allFiles = $util->getAllFiles($path, $mountPoint);
+ } else {
+ $allFiles = array($path);
+ }
+
+ foreach ($allFiles as $path) {
+ $usersSharing = $util->getSharingUsersArray($sharingEnabled, $path);
+ $util->setSharedFileKeyfiles($session, $usersSharing, $path);
+ }
+ }
+
+ /**
+ * unshare file/folder from a user with whom you shared the file before
+ */
+ public static function postUnshare($params) {
+
+ if (\OCP\App::isEnabled('files_encryption') === false) {
+ return true;
+ }
+
+ if ($params['itemType'] === 'file' || $params['itemType'] === 'folder') {
+
+ $view = new \OC\Files\View('/');
+ $userId = $params['uidOwner'];
+ $userView = new \OC\Files\View('/' . $userId . '/files');
+ $util = new Util($view, $userId);
+ $path = $userView->getPath($params['fileSource']);
+
+ // for group shares get a list of the group members
+ if ($params['shareType'] === \OCP\Share::SHARE_TYPE_GROUP) {
+ $userIds = \OC_Group::usersInGroup($params['shareWith']);
+ } else {
+ if ($params['shareType'] === \OCP\Share::SHARE_TYPE_LINK || $params['shareType'] === \OCP\Share::SHARE_TYPE_REMOTE) {
+ $userIds = array($util->getPublicShareKeyId());
+ } else {
+ $userIds = array($params['shareWith']);
+ }
+ }
+
+ $mountManager = \OC\Files\Filesystem::getMountManager();
+ $mount = $mountManager->find('/' . $userId . '/files' . $path);
+ $mountPoint = $mount->getMountPoint();
+
+ // if we unshare a folder we need a list of all (sub-)files
+ if ($params['itemType'] === 'folder') {
+ $allFiles = $util->getAllFiles($path, $mountPoint);
+ } else {
+ $allFiles = array($path);
+ }
+
+ foreach ($allFiles as $path) {
+
+ // check if the user still has access to the file, otherwise delete share key
+ $sharingUsers = $util->getSharingUsersArray(true, $path);
+
+ // Unshare every user who no longer has access to the file
+ $delUsers = array_diff($userIds, $sharingUsers);
+ $keyPath = Keymanager::getKeyPath($view, $util, $path);
+
+ // delete share key
+ Keymanager::delShareKey($view, $delUsers, $keyPath, $userId, $path);
+ }
+
+ }
+ }
+
+ /**
+ * mark file as renamed so that we know the original source after the file was renamed
+ * @param array $params with the old path and the new path
+ */
+ public static function preRename($params) {
+ self::preRenameOrCopy($params, 'rename');
+ }
+
+ /**
+ * mark file as copied so that we know the original source after the file was copied
+ * @param array $params with the old path and the new path
+ */
+ public static function preCopy($params) {
+ self::preRenameOrCopy($params, 'copy');
+ }
+
+ private static function preRenameOrCopy($params, $operation) {
+ $user = \OCP\User::getUser();
+ $view = new \OC\Files\View('/');
+ $util = new Util($view, $user);
+
+ // we only need to rename the keys if the rename happens on the same mountpoint
+ // otherwise we perform a stream copy, so we get a new set of keys
+ $mp1 = $view->getMountPoint('/' . $user . '/files/' . $params['oldpath']);
+ $mp2 = $view->getMountPoint('/' . $user . '/files/' . $params['newpath']);
+
+ $oldKeysPath = Keymanager::getKeyPath($view, $util, $params['oldpath']);
+
+ if ($mp1 === $mp2) {
+ self::$renamedFiles[$params['oldpath']] = array(
+ 'operation' => $operation,
+ 'oldKeysPath' => $oldKeysPath,
+ );
+ } else {
+ self::$renamedFiles[$params['oldpath']] = array(
+ 'operation' => 'cleanup',
+ 'oldKeysPath' => $oldKeysPath,
+ );
+ }
+ }
+
+ /**
+ * after a file is renamed/copied, rename/copy its keyfile and share-keys also fix the file size and fix also the sharing
+ *
+ * @param array $params array with oldpath and newpath
+ */
+ public static function postRenameOrCopy($params) {
+
+ if (\OCP\App::isEnabled('files_encryption') === false) {
+ return true;
+ }
+
+ $view = new \OC\Files\View('/');
+ $userId = \OCP\User::getUser();
+ $util = new Util($view, $userId);
+
+ if (isset(self::$renamedFiles[$params['oldpath']]['operation']) &&
+ isset(self::$renamedFiles[$params['oldpath']]['oldKeysPath'])) {
+ $operation = self::$renamedFiles[$params['oldpath']]['operation'];
+ $oldKeysPath = self::$renamedFiles[$params['oldpath']]['oldKeysPath'];
+ unset(self::$renamedFiles[$params['oldpath']]);
+ if ($operation === 'cleanup') {
+ return $view->unlink($oldKeysPath);
+ }
+ } else {
+ \OCP\Util::writeLog('Encryption library', "can't get path and owner from the file before it was renamed", \OCP\Util::DEBUG);
+ return false;
+ }
+
+ list($ownerNew, $pathNew) = $util->getUidAndFilename($params['newpath']);
+
+ if ($util->isSystemWideMountPoint($pathNew)) {
+ $newKeysPath = 'files_encryption/keys/' . $pathNew;
+ } else {
+ $newKeysPath = $ownerNew . '/files_encryption/keys/' . $pathNew;
+ }
+
+ // create key folders if it doesn't exists
+ if (!$view->file_exists(dirname($newKeysPath))) {
+ $view->mkdir(dirname($newKeysPath));
+ }
+
+ $view->$operation($oldKeysPath, $newKeysPath);
+
+ // update sharing-keys
+ self::updateKeyfiles($params['newpath']);
+ }
+
+ /**
+ * set migration status and the init status back to '0' so that all new files get encrypted
+ * if the app gets enabled again
+ * @param array $params contains the app ID
+ */
+ public static function preDisable($params) {
+ if ($params['app'] === 'files_encryption') {
+
+ \OC::$server->getConfig()->deleteAppFromAllUsers('files_encryption');
+
+ $session = new Session(new \OC\Files\View('/'));
+ $session->setInitialized(Session::NOT_INITIALIZED);
+ }
+ }
+
+ /**
+ * set the init status to 'NOT_INITIALIZED' (0) if the app gets enabled
+ * @param array $params contains the app ID
+ */
+ public static function postEnable($params) {
+ if ($params['app'] === 'files_encryption') {
+ $session = new Session(new \OC\Files\View('/'));
+ $session->setInitialized(Session::NOT_INITIALIZED);
+ }
+ }
+
+ /**
+ * if the file was really deleted we remove the encryption keys
+ * @param array $params
+ * @return boolean|null
+ */
+ public static function postDelete($params) {
+
+ $path = $params[\OC\Files\Filesystem::signal_param_path];
+
+ if (!isset(self::$deleteFiles[$path])) {
+ return true;
+ }
+
+ $deletedFile = self::$deleteFiles[$path];
+ $keyPath = $deletedFile['keyPath'];
+
+ // we don't need to remember the file any longer
+ unset(self::$deleteFiles[$path]);
+
+ $view = new \OC\Files\View('/');
+
+ // return if the file still exists and wasn't deleted correctly
+ if ($view->file_exists('/' . \OCP\User::getUser() . '/files/' . $path)) {
+ return true;
+ }
+
+ // Delete keyfile & shareKey so it isn't orphaned
+ $view->unlink($keyPath);
+
+ }
+
+ /**
+ * remember the file which should be deleted and it's owner
+ * @param array $params
+ * @return boolean|null
+ */
+ public static function preDelete($params) {
+ $view = new \OC\Files\View('/');
+ $path = $params[\OC\Files\Filesystem::signal_param_path];
+
+ // skip this method if the trash bin is enabled or if we delete a file
+ // outside of /data/user/files
+ if (\OCP\App::isEnabled('files_trashbin')) {
+ return true;
+ }
+
+ $util = new Util($view, \OCP\USER::getUser());
+
+ $keysPath = Keymanager::getKeyPath($view, $util, $path);
+
+ self::$deleteFiles[$path] = array(
+ 'keyPath' => $keysPath);
+ }
+
+ /**
+ * unmount file from yourself
+ * remember files/folders which get unmounted
+ */
+ public static function preUnmount($params) {
+ $view = new \OC\Files\View('/');
+ $user = \OCP\User::getUser();
+ $path = $params[\OC\Files\Filesystem::signal_param_path];
+
+ $util = new Util($view, $user);
+ list($owner, $ownerPath) = $util->getUidAndFilename($path);
+
+ $keysPath = Keymanager::getKeyPath($view, $util, $path);
+
+ self::$unmountedFiles[$path] = array(
+ 'keyPath' => $keysPath,
+ 'owner' => $owner,
+ 'ownerPath' => $ownerPath
+ );
+ }
+
+ /**
+ * unmount file from yourself
+ */
+ public static function postUnmount($params) {
+
+ $path = $params[\OC\Files\Filesystem::signal_param_path];
+ $user = \OCP\User::getUser();
+
+ if (!isset(self::$unmountedFiles[$path])) {
+ return true;
+ }
+
+ $umountedFile = self::$unmountedFiles[$path];
+ $keyPath = $umountedFile['keyPath'];
+ $owner = $umountedFile['owner'];
+ $ownerPath = $umountedFile['ownerPath'];
+
+ $view = new \OC\Files\View();
+
+ // we don't need to remember the file any longer
+ unset(self::$unmountedFiles[$path]);
+
+ // check if the user still has access to the file, otherwise delete share key
+ $sharingUsers = \OCP\Share::getUsersSharingFile($path, $user);
+ if (!in_array($user, $sharingUsers['users'])) {
+ Keymanager::delShareKey($view, array($user), $keyPath, $owner, $ownerPath);
+ }
+ }
+
+}
diff --git a/apps/files_encryption/lib/keymanager.php b/apps/files_encryption/lib/keymanager.php index 9560126ef33..925bba578f4 100644 --- a/apps/files_encryption/lib/keymanager.php +++ b/apps/files_encryption/lib/keymanager.php @@ -3,8 +3,9 @@ /** * ownCloud * - * @author Bjoern Schiessle - * @copyright 2012 Bjoern Schiessle <schiessle@owncloud.com> + * @copyright (C) 2014 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 @@ -21,7 +22,7 @@ * */ -namespace OCA\Encryption; +namespace OCA\Files_Encryption; /** * Class to manage storage and retrieval of encryption keys @@ -29,48 +30,96 @@ namespace OCA\Encryption; */ class Keymanager { + // base dir where all the file related keys are stored + private static $keys_base_dir = '/files_encryption/keys/'; + private static $encryption_base_dir = '/files_encryption'; + private static $public_key_dir = '/files_encryption/public_keys'; + + private static $key_cache = array(); // cache keys + /** - * retrieve the ENCRYPTED private key from a user + * read key from hard disk * - * @param \OC\Files\View $view - * @param string $user - * @return string private key or false (hopefully) - * @note the key returned by this method must be decrypted before use + * @param string $path to key + * @return string|bool either the key or false */ - public static function getPrivateKey(\OC\Files\View $view, $user) { + private static function getKey($path, $view) { - $path = '/' . $user . '/' . 'files_encryption' . '/' . $user . '.private.key'; $key = false; - $proxyStatus = \OC_FileProxy::$enabled; - \OC_FileProxy::$enabled = false; + if (isset(self::$key_cache[$path])) { + $key = self::$key_cache[$path]; + } else { - if ($view->file_exists($path)) { - $key = $view->file_get_contents($path); - } + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; - \OC_FileProxy::$enabled = $proxyStatus; + if ($view->file_exists($path)) { + $key = $view->file_get_contents($path); + self::$key_cache[$path] = $key; + } + + \OC_FileProxy::$enabled = $proxyStatus; + + } return $key; } /** - * retrieve public key for a specified user + * write key to disk + * + * + * @param string $path path to key directory + * @param string $name key name + * @param string $key key * @param \OC\Files\View $view - * @param string $userId - * @return string public key or false + * @return bool */ - public static function getPublicKey(\OC\Files\View $view, $userId) { - + private static function setKey($path, $name, $key, $view) { $proxyStatus = \OC_FileProxy::$enabled; \OC_FileProxy::$enabled = false; - $result = $view->file_get_contents('/public-keys/' . $userId . '.public.key'); + self::keySetPreparation($view, $path); + $pathToKey = \OC\Files\Filesystem::normalizePath($path . '/' . $name); + $result = $view->file_put_contents($pathToKey, $key); \OC_FileProxy::$enabled = $proxyStatus; - return $result; + if (is_int($result) && $result > 0) { + self::$key_cache[$pathToKey] = $key; + return true; + } + return false; + } + + /** + * retrieve the ENCRYPTED private key from a user + * + * @param \OC\Files\View $view + * @param string $user + * @return string private key or false (hopefully) + * @note the key returned by this method must be decrypted before use + */ + public static function getPrivateKey(\OC\Files\View $view, $user) { + $path = '/' . $user . '/' . 'files_encryption' . '/' . $user . '.privateKey'; + return self::getKey($path, $view); + } + + /** + * retrieve public key for a specified user + * @param \OC\Files\View $view + * @param string $userId + * @return string public key or false + */ + public static function getPublicKey(\OC\Files\View $view, $userId) { + $path = self::$public_key_dir . '/' . $userId . '.publicKey'; + return self::getKey($path, $view); + } + + public static function getPublicKeyPath() { + return self::$public_key_dir; } /** @@ -97,11 +146,8 @@ class Keymanager { public static function getPublicKeys(\OC\Files\View $view, array $userIds) { $keys = array(); - foreach ($userIds as $userId) { - $keys[$userId] = self::getPublicKey($view, $userId); - } return $keys; @@ -112,7 +158,7 @@ class Keymanager { * store file encryption key * * @param \OC\Files\View $view - * @param \OCA\Encryption\Util $util + * @param \OCA\Files_Encryption\Util $util * @param string $path relative path of the file, including filename * @param string $catfile keyfile content * @return bool true/false @@ -120,259 +166,238 @@ class Keymanager { * asymmetrically encrypt the keyfile before passing it to this method */ public static function setFileKey(\OC\Files\View $view, $util, $path, $catfile) { + $path = self::getKeyPath($view, $util, $path); + return self::setKey($path, 'fileKey', $catfile, $view); - $proxyStatus = \OC_FileProxy::$enabled; - \OC_FileProxy::$enabled = false; + } + + /** + * get path to key folder for a given file + * + * @param \OC\Files\View $view relative to data directory + * @param \OCA\Files_Encryption\Util $util + * @param string $path path to the file, relative to the users file directory + * @return string + */ + public static function getKeyPath($view, $util, $path) { + + if ($view->is_dir('/' . \OCP\User::getUser() . '/' . $path)) { + throw new Exception\EncryptionException('file was expected but directoy was given', Exception\EncryptionException::GENERIC); + } list($owner, $filename) = $util->getUidAndFilename($path); + $filename = Helper::stripPartialFileExtension($filename); + $filePath_f = ltrim($filename, '/'); // in case of system wide mount points the keys are stored directly in the data directory if ($util->isSystemWideMountPoint($filename)) { - $basePath = '/files_encryption/keyfiles'; + $keyPath = self::$keys_base_dir . $filePath_f . '/'; } else { - $basePath = '/' . $owner . '/files_encryption/keyfiles'; + $keyPath = '/' . $owner . self::$keys_base_dir . $filePath_f . '/'; } - $targetPath = self::keySetPreparation($view, $filename, $basePath); - - // try reusing key file if part file - if (Helper::isPartialFilePath($targetPath)) { + return $keyPath; + } - $result = $view->file_put_contents( - $basePath . '/' . Helper::stripPartialFileExtension($targetPath) . '.key', $catfile); + /** + * get path to file key for a given file + * + * @param \OC\Files\View $view relative to data directory + * @param \OCA\Files_Encryption\Util $util + * @param string $path path to the file, relative to the users file directory + * @return string + */ + public static function getFileKeyPath($view, $util, $path) { + $keyDir = self::getKeyPath($view, $util, $path); + return $keyDir . 'fileKey'; + } - } else { + /** + * get path to share key for a given user + * + * @param \OC\Files\View $view relateive to data directory + * @param \OCA\Files_Encryption\Util $util + * @param string $path path to file relative to the users files directoy + * @param string $uid user for whom we want the share-key path + * @retrun string + */ + public static function getShareKeyPath($view, $util, $path, $uid) { + $keyDir = self::getKeyPath($view, $util, $path); + return $keyDir . $uid . '.shareKey'; + } - $result = $view->file_put_contents($basePath . '/' . $targetPath . '.key', $catfile); + /** + * delete key + * + * @param \OC\Files\View $view + * @param string $path + * @return boolean + */ + private static function deleteKey($view, $path) { + $normalizedPath = \OC\Files\Filesystem::normalizePath($path); + $result = $view->unlink($normalizedPath); + if ($result) { + unset(self::$key_cache[$normalizedPath]); + return true; } - \OC_FileProxy::$enabled = $proxyStatus; + return false; + } + + /** + * delete public key from a given user + * + * @param \OC\Files\View $view + * @param string $uid user + * @return bool + */ + public static function deletePublicKey($view, $uid) { + + $result = false; + + if (!\OCP\User::userExists($uid)) { + $publicKey = self::$public_key_dir . '/' . $uid . '.publicKey'; + self::deleteKey($view, $publicKey); + } return $result; + } + /** + * check if public key for user exists + * + * @param \OC\Files\View $view + * @param string $uid + */ + public static function publicKeyExists($view, $uid) { + return $view->file_exists(self::$public_key_dir . '/'. $uid . '.publicKey'); } + + /** * retrieve keyfile for an encrypted file * @param \OC\Files\View $view - * @param \OCA\Encryption\Util $util + * @param \OCA\Files_Encryption\Util $util * @param string|false $filePath - * @internal param \OCA\Encryption\file $string name * @return string file key or false * @note The keyfile returned is asymmetrically encrypted. Decryption * of the keyfile must be performed by client code */ public static function getFileKey($view, $util, $filePath) { + $path = self::getFileKeyPath($view, $util, $filePath); + return self::getKey($path, $view); + } + /** + * store private key from the user + * @param string $key + * @return bool + * @note Encryption of the private key must be performed by client code + * as no encryption takes place here + */ + public static function setPrivateKey($key, $user = '') { - list($owner, $filename) = $util->getUidAndFilename($filePath); - $filename = Helper::stripPartialFileExtension($filename); - $filePath_f = ltrim($filename, '/'); - - // in case of system wide mount points the keys are stored directly in the data directory - if ($util->isSystemWideMountPoint($filename)) { - $keyfilePath = '/files_encryption/keyfiles/' . $filePath_f . '.key'; - } else { - $keyfilePath = '/' . $owner . '/files_encryption/keyfiles/' . $filePath_f . '.key'; - } - - $proxyStatus = \OC_FileProxy::$enabled; - \OC_FileProxy::$enabled = false; - - if ($view->file_exists($keyfilePath)) { - - $result = $view->file_get_contents($keyfilePath); - - } else { - - $result = false; - - } - - \OC_FileProxy::$enabled = $proxyStatus; + $user = $user === '' ? \OCP\User::getUser() : $user; + $path = '/' . $user . '/files_encryption'; + $header = Crypt::generateHeader(); - return $result; + return self::setKey($path, $user . '.privateKey', $header . $key, new \OC\Files\View()); } /** - * Delete a keyfile + * check if recovery key exists * * @param \OC\Files\View $view - * @param string $path path of the file the key belongs to - * @param string $userId the user to whom the file belongs - * @return bool Outcome of unlink operation - * @note $path must be relative to data/user/files. e.g. mydoc.txt NOT - * /data/admin/files/mydoc.txt + * @return bool */ - public static function deleteFileKey($view, $path, $userId=null) { + public static function recoveryKeyExists($view) { - $trimmed = ltrim($path, '/'); - - if ($trimmed === '') { - \OCP\Util::writeLog('Encryption library', - 'Can\'t delete file-key empty path given!', \OCP\Util::ERROR); - return false; - } + $result = false; - if ($userId === null) { - $userId = Helper::getUser($path); + $recoveryKeyId = Helper::getRecoveryKeyId(); + if ($recoveryKeyId) { + $result = ($view->file_exists(self::$public_key_dir . '/' . $recoveryKeyId . ".publicKey") + && $view->file_exists(self::$encryption_base_dir . '/' . $recoveryKeyId . ".privateKey")); } - $util = new Util($view, $userId); - if($util->isSystemWideMountPoint($path)) { - $keyPath = '/files_encryption/keyfiles/' . $trimmed; - } else { - $keyPath = '/' . $userId . '/files_encryption/keyfiles/' . $trimmed; - } + return $result; + } + public static function publicShareKeyExists($view) { $result = false; - $fileExists = $view->file_exists('/' . $userId . '/files/' . $trimmed); - if ($view->is_dir($keyPath) && !$fileExists) { - \OCP\Util::writeLog('files_encryption', 'deleteFileKey: delete file key: ' . $keyPath, \OCP\Util::DEBUG); - $result = $view->unlink($keyPath); - } elseif ($view->file_exists($keyPath . '.key') && !$fileExists) { - \OCP\Util::writeLog('files_encryption', 'deleteFileKey: delete file key: ' . $keyPath, \OCP\Util::DEBUG); - $result = $view->unlink($keyPath . '.key'); - - } + $publicShareKeyId = Helper::getPublicShareKeyId(); + if ($publicShareKeyId) { + $result = ($view->file_exists(self::$public_key_dir . '/' . $publicShareKeyId . ".publicKey") + && $view->file_exists(self::$encryption_base_dir . '/' . $publicShareKeyId . ".privateKey")); - if ($fileExists) { - \OCP\Util::writeLog('Encryption library', - 'Did not delete the file key, file still exists: ' . '/' . $userId . '/files/' . $trimmed, \OCP\Util::ERROR); - } elseif (!$result) { - \OCP\Util::writeLog('Encryption library', - 'Could not delete keyfile; does not exist: "' . $keyPath, \OCP\Util::ERROR); } return $result; - } /** - * store private key from the user + * store public key from the user * @param string $key + * @param string $user + * * @return bool - * @note Encryption of the private key must be performed by client code - * as no encryption takes place here */ - public static function setPrivateKey($key, $user = '') { + public static function setPublicKey($key, $user = '') { - if ($user === '') { - $user = \OCP\User::getUser(); - } - - $header = Crypt::generateHeader(); - - $view = new \OC\Files\View('/' . $user . '/files_encryption'); - - $proxyStatus = \OC_FileProxy::$enabled; - \OC_FileProxy::$enabled = false; - - if (!$view->file_exists('')) { - $view->mkdir(''); - } - - $result = $view->file_put_contents($user . '.private.key', $header . $key); - - \OC_FileProxy::$enabled = $proxyStatus; - - return $result; + $user = $user === '' ? \OCP\User::getUser() : $user; + return self::setKey(self::$public_key_dir, $user . '.publicKey', $key, new \OC\Files\View('/')); } /** * write private system key (recovery and public share key) to disk * * @param string $key encrypted key - * @param string $keyName name of the key file + * @param string $keyName name of the key * @return boolean */ public static function setPrivateSystemKey($key, $keyName) { + $keyName = $keyName . '.privateKey'; $header = Crypt::generateHeader(); - $view = new \OC\Files\View('/owncloud_private_key'); - - $proxyStatus = \OC_FileProxy::$enabled; - \OC_FileProxy::$enabled = false; - - if (!$view->file_exists('')) { - $view->mkdir(''); - } - - $result = $view->file_put_contents($keyName, $header . $key); - - \OC_FileProxy::$enabled = $proxyStatus; - - return $result; + return self::setKey(self::$encryption_base_dir, $keyName,$header . $key, new \OC\Files\View()); } /** - * store share key + * read private system key (recovery and public share key) from disk * - * @param \OC\Files\View $view - * @param string $path where the share key is stored - * @param string $shareKey - * @return bool true/false - * @note The keyfile is not encrypted here. Client code must - * asymmetrically encrypt the keyfile before passing it to this method + * @param string $keyName name of the key + * @return string|boolean private system key or false */ - private static function setShareKey(\OC\Files\View $view, $path, $shareKey) { - - $proxyStatus = \OC_FileProxy::$enabled; - \OC_FileProxy::$enabled = false; - - $result = $view->file_put_contents($path, $shareKey); - - \OC_FileProxy::$enabled = $proxyStatus; - - if (is_int($result) && $result > 0) { - return true; - } else { - return false; - } + public static function getPrivateSystemKey($keyName) { + $path = $keyName . '.privateKey'; + return self::getKey($path, new \OC\Files\View(self::$encryption_base_dir)); } /** * store multiple share keys for a single file * @param \OC\Files\View $view - * @param \OCA\Encryption\Util $util + * @param \OCA\Files_Encryption\Util $util * @param string $path * @param array $shareKeys * @return bool */ - public static function setShareKeys(\OC\Files\View $view, $util, $path, array $shareKeys) { - - // $shareKeys must be an array with the following format: - // [userId] => [encrypted key] - - list($owner, $filename) = $util->getUidAndFilename($path); + public static function setShareKeys($view, $util, $path, array $shareKeys) { // in case of system wide mount points the keys are stored directly in the data directory - if ($util->isSystemWideMountPoint($filename)) { - $basePath = '/files_encryption/share-keys'; - } else { - $basePath = '/' . $owner . '/files_encryption/share-keys'; - } + $basePath = Keymanager::getKeyPath($view, $util, $path); - $shareKeyPath = self::keySetPreparation($view, $filename, $basePath); + self::keySetPreparation($view, $basePath); $result = true; foreach ($shareKeys as $userId => $shareKey) { - - // try reusing key file if part file - if (Helper::isPartialFilePath($shareKeyPath)) { - $writePath = $basePath . '/' . Helper::stripPartialFileExtension($shareKeyPath) . '.' . $userId . '.shareKey'; - } else { - $writePath = $basePath . '/' . $shareKeyPath . '.' . $userId . '.shareKey'; - } - - if (!self::setShareKey($view, $writePath, $shareKey)) { - + if (!self::setKey($basePath, $userId . '.shareKey', $shareKey, $view)) { // If any of the keys are not set, flag false $result = false; } @@ -386,95 +411,15 @@ class Keymanager { * retrieve shareKey for an encrypted file * @param \OC\Files\View $view * @param string $userId - * @param \OCA\Encryption\Util $util + * @param \OCA\Files_Encryption\Util $util * @param string $filePath * @return string file key or false * @note The sharekey returned is encrypted. Decryption * of the keyfile must be performed by client code */ - public static function getShareKey(\OC\Files\View $view, $userId, $util, $filePath) { - - // try reusing key file if part file - $proxyStatus = \OC_FileProxy::$enabled; - \OC_FileProxy::$enabled = false; - - list($owner, $filename) = $util->getUidAndFilename($filePath); - $filename = Helper::stripPartialFileExtension($filename); - // in case of system wide mount points the keys are stored directly in the data directory - if ($util->isSystemWideMountPoint($filename)) { - $shareKeyPath = '/files_encryption/share-keys/' . $filename . '.' . $userId . '.shareKey'; - } else { - $shareKeyPath = '/' . $owner . '/files_encryption/share-keys/' . $filename . '.' . $userId . '.shareKey'; - } - - if ($view->file_exists($shareKeyPath)) { - - $result = $view->file_get_contents($shareKeyPath); - - } else { - - $result = false; - - } - - \OC_FileProxy::$enabled = $proxyStatus; - - return $result; - - } - - /** - * delete all share keys of a given file - * @param \OC\Files\View $view - * @param string $userId owner of the file - * @param string $filePath path to the file, relative to the owners file dir - */ - public static function delAllShareKeys($view, $userId, $filePath) { - - $filePath = ltrim($filePath, '/'); - - if ($view->file_exists('/' . $userId . '/files/' . $filePath)) { - \OCP\Util::writeLog('Encryption library', - 'File still exists, stop deleting share keys!', \OCP\Util::ERROR); - return false; - } - - if ($filePath === '') { - \OCP\Util::writeLog('Encryption library', - 'Can\'t delete share-keys empty path given!', \OCP\Util::ERROR); - return false; - } - - $util = new util($view, $userId); - - if ($util->isSystemWideMountPoint($filePath)) { - $baseDir = '/files_encryption/share-keys/'; - } else { - $baseDir = $userId . '/files_encryption/share-keys/'; - } - - $result = true; - - if ($view->is_dir($baseDir . $filePath)) { - \OCP\Util::writeLog('files_encryption', 'delAllShareKeys: delete share keys: ' . $baseDir . $filePath, \OCP\Util::DEBUG); - $result = $view->unlink($baseDir . $filePath); - } else { - $sharingEnabled = \OCP\Share::isEnabled(); - $users = $util->getSharingUsersArray($sharingEnabled, $filePath); - foreach($users as $user) { - $keyName = $baseDir . $filePath . '.' . $user . '.shareKey'; - if ($view->file_exists($keyName)) { - \OCP\Util::writeLog( - 'files_encryption', - 'dellAllShareKeys: delete share keys: "' . $keyName . '"', - \OCP\Util::DEBUG - ); - $result &= $view->unlink($keyName); - } - } - } - - return (bool)$result; + public static function getShareKey($view, $userId, $util, $filePath) { + $path = self::getShareKeyPath($view, $util, $filePath, $userId); + return self::getKey($path, $view); } /** @@ -482,45 +427,19 @@ class Keymanager { * * @param \OC\Files\View $view relative to data/ * @param array $userIds list of users we want to remove - * @param string $filename the owners name of the file for which we want to remove the users relative to data/user/files - * @param string $owner owner of the file + * @param string $keyPath + * @param string $owner the owner of the file + * @param string $ownerPath the owners name of the file for which we want to remove the users relative to data/user/files */ - public static function delShareKey($view, $userIds, $filename, $owner) { - - $proxyStatus = \OC_FileProxy::$enabled; - \OC_FileProxy::$enabled = false; - - $util = new Util($view, $owner); + public static function delShareKey($view, $userIds, $keysPath, $owner, $ownerPath) { - if ($util->isSystemWideMountPoint($filename)) { - $shareKeyPath = \OC\Files\Filesystem::normalizePath('/files_encryption/share-keys/' . $filename); - } else { - $shareKeyPath = \OC\Files\Filesystem::normalizePath('/' . $owner . '/files_encryption/share-keys/' . $filename); + $key = array_search($owner, $userIds, true); + if ($key !== false && $view->file_exists('/' . $owner . '/files/' . $ownerPath)) { + unset($userIds[$key]); } - if ($view->is_dir($shareKeyPath)) { + self::recursiveDelShareKeys($keysPath, $userIds, $view); - self::recursiveDelShareKeys($shareKeyPath, $userIds, $owner, $view); - - } else { - - foreach ($userIds as $userId) { - - if ($userId === $owner && $view->file_exists('/' . $owner . '/files/' . $filename)) { - \OCP\Util::writeLog('files_encryption', 'Tried to delete owner key, but the file still exists!', \OCP\Util::FATAL); - continue; - } - $result = $view->unlink($shareKeyPath . '.' . $userId . '.shareKey'); - \OCP\Util::writeLog('files_encryption', 'delShareKey: delete share key: ' . $shareKeyPath . '.' . $userId . '.shareKey' , \OCP\Util::DEBUG); - if (!$result) { - \OCP\Util::writeLog('Encryption library', - 'Could not delete shareKey; does not exist: "' . $shareKeyPath . '.' . $userId - . '.shareKey"', \OCP\Util::ERROR); - } - } - } - - \OC_FileProxy::$enabled = $proxyStatus; } /** @@ -528,35 +447,23 @@ class Keymanager { * * @param string $dir directory * @param array $userIds user ids for which the share keys should be deleted - * @param string $owner owner of the file * @param \OC\Files\View $view view relative to data/ */ - private static function recursiveDelShareKeys($dir, $userIds, $owner, $view) { + private static function recursiveDelShareKeys($dir, $userIds, $view) { $dirContent = $view->opendir($dir); - $dirSlices = explode('/', ltrim($dir, '/')); - $realFileDir = '/' . $owner . '/files/' . implode('/', array_slice($dirSlices, 3)) . '/'; if (is_resource($dirContent)) { while (($file = readdir($dirContent)) !== false) { if (!\OC\Files\Filesystem::isIgnoredDir($file)) { if ($view->is_dir($dir . '/' . $file)) { - self::recursiveDelShareKeys($dir . '/' . $file, $userIds, $owner, $view); + self::recursiveDelShareKeys($dir . '/' . $file, $userIds, $view); } else { foreach ($userIds as $userId) { - $fileNameFromShareKey = self::getFilenameFromShareKey($file, $userId); - if (!$fileNameFromShareKey) { - continue; + if ($userId . '.shareKey' === $file) { + \OCP\Util::writeLog('files_encryption', 'recursiveDelShareKey: delete share key: ' . $file, \OCP\Util::DEBUG); + self::deleteKey($view, $dir . '/' . $file); } - $realFile = $realFileDir . $fileNameFromShareKey; - - if ($userId === $owner && - $view->file_exists($realFile)) { - \OCP\Util::writeLog('files_encryption', 'original file still exists, keep owners share key!', \OCP\Util::ERROR); - continue; - } - \OCP\Util::writeLog('files_encryption', 'recursiveDelShareKey: delete share key: ' . $file, \OCP\Util::DEBUG); - $view->unlink($dir . '/' . $file); } } } @@ -567,21 +474,15 @@ class Keymanager { /** * Make preparations to vars and filesystem for saving a keyfile - * @param string|boolean $path + * + * @param \OC\Files\View $view + * @param string $path relatvie to the views root * @param string $basePath */ - protected static function keySetPreparation(\OC\Files\View $view, $path, $basePath) { - - $targetPath = ltrim($path, '/'); - - $path_parts = pathinfo($targetPath); - + protected static function keySetPreparation($view, $path) { // If the file resides within a subdirectory, create it - if ( - isset($path_parts['dirname']) - && !$view->file_exists($basePath . '/' . $path_parts['dirname']) - ) { - $sub_dirs = explode('/', $basePath . '/' . $path_parts['dirname']); + if (!$view->file_exists($path)) { + $sub_dirs = explode('/', $path); $dir = ''; foreach ($sub_dirs as $sub_dir) { $dir .= '/' . $sub_dir; @@ -590,27 +491,6 @@ class Keymanager { } } } - - return $targetPath; - } - /** - * extract filename from share key name - * @param string $shareKey (filename.userid.sharekey) - * @param string $userId - * @return string|false filename or false - */ - protected static function getFilenameFromShareKey($shareKey, $userId) { - $expectedSuffix = '.' . $userId . '.' . 'shareKey'; - $suffixLen = strlen($expectedSuffix); - - $suffix = substr($shareKey, -$suffixLen); - - if ($suffix !== $expectedSuffix) { - return false; - } - - return substr($shareKey, 0, -$suffixLen); - } } diff --git a/apps/files_encryption/lib/migration.php b/apps/files_encryption/lib/migration.php index 59389911b5b..1bab1dfe4a5 100644 --- a/apps/files_encryption/lib/migration.php +++ b/apps/files_encryption/lib/migration.php @@ -2,8 +2,9 @@ /** * ownCloud * - * @author Thomas Müller - * @copyright 2014 Thomas Müller deepdiver@owncloud.com + * @copyright (C) 2014 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 @@ -25,26 +26,257 @@ namespace OCA\Files_Encryption; class Migration { - public function __construct($tableName = 'encryption') { - $this->tableName = $tableName; + /** + * @var \OC\Files\View + */ + private $view; + private $public_share_key_id; + private $recovery_key_id; + + public function __construct() { + $this->view = new \OC\Files\View(); + $this->public_share_key_id = Helper::getPublicShareKeyId(); + $this->recovery_key_id = Helper::getRecoveryKeyId(); + } + + public function reorganizeFolderStructure() { + + $this->createPathForKeys('/files_encryption'); + + // backup system wide folders + $this->backupSystemWideKeys(); + + // rename public keys + $this->renamePublicKeys(); + + // rename system wide mount point + $this->renameFileKeys('', '/files_encryption/keyfiles'); + + // rename system private keys + $this->renameSystemPrivateKeys(); + + // delete old system wide folders + $this->view->deleteAll('/public-keys'); + $this->view->deleteAll('/owncloud_private_key'); + $this->view->deleteAll('/files_encryption/share-keys'); + $this->view->deleteAll('/files_encryption/keyfiles'); + + $users = \OCP\User::getUsers(); + foreach ($users as $user) { + // backup all keys + if ($this->backupUserKeys($user)) { + // create new 'key' folder + $this->view->mkdir($user . '/files_encryption/keys'); + // rename users private key + $this->renameUsersPrivateKey($user); + // rename file keys + $path = $user . '/files_encryption/keyfiles'; + $this->renameFileKeys($user, $path); + $trashPath = $user . '/files_trashbin/keyfiles'; + if (\OC_App::isEnabled('files_trashbin') && $this->view->is_dir($trashPath)) { + $this->renameFileKeys($user, $trashPath, true); + $this->view->deleteAll($trashPath); + $this->view->deleteAll($user . '/files_trashbin/share-keys'); + } + // delete old folders + $this->deleteOldKeys($user); + } + } + } + + private function backupSystemWideKeys() { + $backupDir = 'encryption_migration_backup_' . date("Y-m-d_H-i-s"); + $this->view->mkdir($backupDir); + $this->view->copy('owncloud_private_key', $backupDir . '/owncloud_private_key'); + $this->view->copy('public-keys', $backupDir . '/public-keys'); + $this->view->copy('files_encryption', $backupDir . '/files_encryption'); + } + + private function backupUserKeys($user) { + $encryptionDir = $user . '/files_encryption'; + if ($this->view->is_dir($encryptionDir)) { + $backupDir = $user . '/encryption_migration_backup_' . date("Y-m-d_H-i-s"); + $this->view->mkdir($backupDir); + $this->view->copy($encryptionDir, $backupDir); + return true; + } + return false; + } + + private function renamePublicKeys() { + $dh = $this->view->opendir('public-keys'); + + $this->createPathForKeys('files_encryption/public_keys'); + + if (is_resource($dh)) { + while (($oldPublicKey = readdir($dh)) !== false) { + if (!\OC\Files\Filesystem::isIgnoredDir($oldPublicKey)) { + $newPublicKey = substr($oldPublicKey, 0, strlen($oldPublicKey) - strlen('.public.key')) . '.publicKey'; + $this->view->rename('public-keys/' . $oldPublicKey , 'files_encryption/public_keys/' . $newPublicKey); + } + } + closedir($dh); + } + } + + private function renameSystemPrivateKeys() { + $dh = $this->view->opendir('owncloud_private_key'); + + if (is_resource($dh)) { + while (($oldPrivateKey = readdir($dh)) !== false) { + if (!\OC\Files\Filesystem::isIgnoredDir($oldPrivateKey)) { + $newPrivateKey = substr($oldPrivateKey, 0, strlen($oldPrivateKey) - strlen('.private.key')) . '.privateKey'; + $this->view->rename('owncloud_private_key/' . $oldPrivateKey , 'files_encryption/' . $newPrivateKey); + } + } + closedir($dh); + } + } + + private function renameUsersPrivateKey($user) { + $oldPrivateKey = $user . '/files_encryption/' . $user . '.private.key'; + $newPrivateKey = substr($oldPrivateKey, 0, strlen($oldPrivateKey) - strlen('.private.key')) . '.privateKey'; + + $this->view->rename($oldPrivateKey, $newPrivateKey); + } + + private function getFileName($file, $trash) { + + $extLength = strlen('.key'); + + if ($trash) { + $parts = explode('.', $file); + if ($parts[count($parts) - 1] !== 'key') { + $extLength = $extLength + strlen('.' . $parts[count($parts) - 1]); + } + } + + $filename = substr($file, 0, strlen($file) - $extLength); + + return $filename; + } + + private function getExtension($file, $trash) { + + $extension = ''; + + if ($trash) { + $parts = explode('.', $file); + if ($parts[count($parts) - 1] !== 'key') { + $extension = '.' . $parts[count($parts) - 1]; + } + } + + return $extension; + } + + private function getFilePath($path, $user, $trash) { + $offset = $trash ? strlen($user . '/files_trashbin/keyfiles') : strlen($user . '/files_encryption/keyfiles'); + return substr($path, $offset); + } + + private function getTargetDir($user, $filePath, $filename, $extension, $trash) { + if ($trash) { + $targetDir = $user . '/files_trashbin/keys/' . $filePath . '/' . $filename . $extension; + } else { + $targetDir = $user . '/files_encryption/keys/' . $filePath . '/' . $filename . $extension; + } + + return $targetDir; + } + + private function renameFileKeys($user, $path, $trash = false) { + + $dh = $this->view->opendir($path); + + if (is_resource($dh)) { + while (($file = readdir($dh)) !== false) { + if (!\OC\Files\Filesystem::isIgnoredDir($file)) { + if ($this->view->is_dir($path . '/' . $file)) { + $this->renameFileKeys($user, $path . '/' . $file, $trash); + } else { + $filename = $this->getFileName($file, $trash); + $filePath = $this->getFilePath($path, $user, $trash); + $extension = $this->getExtension($file, $trash); + $targetDir = $this->getTargetDir($user, $filePath, $filename, $extension, $trash); + $this->createPathForKeys($targetDir); + $this->view->copy($path . '/' . $file, $targetDir . '/fileKey'); + $this->renameShareKeys($user, $filePath, $filename, $targetDir, $trash); + } + } + } + closedir($dh); + } } - // migrate settings from oc_encryption to oc_preferences - public function dropTableEncryption() { - $tableName = $this->tableName; - if (!\OC_DB::tableExists($tableName)) { - return; + private function getOldShareKeyPath($user, $filePath, $trash) { + if ($trash) { + $oldShareKeyPath = $user . '/files_trashbin/share-keys/' . $filePath; + } else { + $oldShareKeyPath = $user . '/files_encryption/share-keys/' . $filePath; } - $sql = "select `uid`, max(`recovery_enabled`) as `recovery_enabled`, min(`migration_status`) as `migration_status` from `*PREFIX*$tableName` group by `uid`"; - $query = \OCP\DB::prepare($sql); - $result = $query->execute(array())->fetchAll(); - foreach ($result as $row) { - \OC_Preferences::setValue($row['uid'], 'files_encryption', 'recovery_enabled', $row['recovery_enabled']); - \OC_Preferences::setValue($row['uid'], 'files_encryption', 'migration_status', $row['migration_status']); + return $oldShareKeyPath; + } + + private function getUidFromShareKey($file, $filename, $trash) { + $extLength = strlen('.shareKey'); + if ($trash) { + $parts = explode('.', $file); + if ($parts[count($parts) - 1] !== 'shareKey') { + $extLength = $extLength + strlen('.' . $parts[count($parts) - 1]); + } } - \OC_DB::dropTable($tableName); + $uid = substr($file, strlen($filename) + 1, $extLength * -1); + + return $uid; } + private function renameShareKeys($user, $filePath, $filename, $target, $trash) { + $oldShareKeyPath = $this->getOldShareKeyPath($user, $filePath, $trash); + $dh = $this->view->opendir($oldShareKeyPath); + + if (is_resource($dh)) { + while (($file = readdir($dh)) !== false) { + if (!\OC\Files\Filesystem::isIgnoredDir($file)) { + if ($this->view->is_dir($oldShareKeyPath . '/' . $file)) { + continue; + } else { + if (substr($file, 0, strlen($filename) +1) === $filename . '.') { + + $uid = $this->getUidFromShareKey($file, $filename, $trash); + if ($uid === $this->public_share_key_id || + $uid === $this->recovery_key_id || + \OCP\User::userExists($uid)) { + $this->view->copy($oldShareKeyPath . '/' . $file, $target . '/' . $uid . '.shareKey'); + } + } + } + + } + } + closedir($dh); + } + } + + private function deleteOldKeys($user) { + $this->view->deleteAll($user . '/files_encryption/keyfiles'); + $this->view->deleteAll($user . '/files_encryption/share-keys'); + } + + private function createPathForKeys($path) { + if (!$this->view->file_exists($path)) { + $sub_dirs = explode('/', $path); + $dir = ''; + foreach ($sub_dirs as $sub_dir) { + $dir .= '/' . $sub_dir; + if (!$this->view->is_dir($dir)) { + $this->view->mkdir($dir); + } + } + } + } + + } diff --git a/apps/files_encryption/lib/proxy.php b/apps/files_encryption/lib/proxy.php index 3b9dcbe7767..ba78c81aa35 100644 --- a/apps/files_encryption/lib/proxy.php +++ b/apps/files_encryption/lib/proxy.php @@ -3,10 +3,11 @@ /** * ownCloud * - * @author Bjoern Schiessle, Sam Tuke, Robin Appelman - * @copyright 2012 Sam Tuke <samtuke@owncloud.com> - * 2012 Robin Appelman <icewind1991@gmail.com> - * 2014 Bjoern Schiessle <schiessle@owncloud.com> + * @copyright (C) 2014 ownCloud, Inc. + * + * @author Bjoern Schiessle <schiessle@owncloud.com> + * @author Sam Tuke <samtuke@owncloud.com> + * @author Robin Appelman <icewind1991@gmail.com> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE @@ -29,11 +30,11 @@ * webui. */ -namespace OCA\Encryption; +namespace OCA\Files_Encryption; /** * Class Proxy - * @package OCA\Encryption + * @package OCA\Files_Encryption */ class Proxy extends \OC_FileProxy { @@ -91,12 +92,10 @@ class Proxy extends \OC_FileProxy { private function shouldEncrypt($path, $mode = 'w') { $userId = Helper::getUser($path); - $session = new Session(new \OC\Files\View()); // don't call the crypt stream wrapper, if... if ( - $session->getInitialized() !== Session::INIT_SUCCESSFUL // encryption successful initialized - || Crypt::mode() !== 'server' // we are not in server-side-encryption mode + Crypt::mode() !== 'server' // we are not in server-side-encryption mode || $this->isExcludedPath($path, $userId) // if path is excluded from encryption || substr($path, 0, 8) === 'crypt://' // we are already in crypt mode ) { @@ -131,7 +130,7 @@ class Proxy extends \OC_FileProxy { $view = new \OC\Files\View('/'); // get relative path - $relativePath = \OCA\Encryption\Helper::stripUserFilesPath($path); + $relativePath = Helper::stripUserFilesPath($path); if (!isset($relativePath)) { return true; @@ -206,11 +205,11 @@ class Proxy extends \OC_FileProxy { public function postFile_get_contents($path, $data) { $plainData = null; - $view = new \OC\Files\View('/'); // If data is a catfile if ( Crypt::mode() === 'server' + && $this->shouldEncrypt($path) && Crypt::isCatfileContent($data) ) { @@ -339,15 +338,15 @@ class Proxy extends \OC_FileProxy { } // get relative path - $relativePath = \OCA\Encryption\Helper::stripUserFilesPath($path); + $relativePath = Helper::stripUserFilesPath($path); // if path is empty we cannot resolve anything if (empty($relativePath)) { return $size; } - // get file info from database/cache if not .part file - if (empty($fileInfo) && !Helper::isPartialFilePath($path)) { + // get file info from database/cache + if (empty($fileInfo)) { $proxyState = \OC_FileProxy::$enabled; \OC_FileProxy::$enabled = false; $fileInfo = $view->getFileInfo($path); diff --git a/apps/files_encryption/lib/session.php b/apps/files_encryption/lib/session.php index 7bd4fd02421..4c70bc7e8fc 100644 --- a/apps/files_encryption/lib/session.php +++ b/apps/files_encryption/lib/session.php @@ -2,8 +2,10 @@ /** * ownCloud * - * @author Sam Tuke - * @copyright 2012 Sam Tuke samtuke@owncloud.com + * @copyright (C) 2014 ownCloud, Inc. + * + * @author Sam Tuke <samtuke@owncloud.com> + * @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 @@ -20,7 +22,7 @@ * */ -namespace OCA\Encryption; +namespace OCA\Files_Encryption; /** * Class for handling encryption related session data @@ -29,6 +31,7 @@ namespace OCA\Encryption; class Session { private $view; + private static $publicShareKey = false; const NOT_INITIALIZED = '0'; const INIT_EXECUTED = '1'; @@ -45,64 +48,48 @@ class Session { $this->view = $view; - if (!$this->view->is_dir('owncloud_private_key')) { + if (!$this->view->is_dir('files_encryption')) { - $this->view->mkdir('owncloud_private_key'); + $this->view->mkdir('files_encryption'); } $appConfig = \OC::$server->getAppConfig(); - $publicShareKeyId = $appConfig->getValue('files_encryption', 'publicShareKeyId'); + $publicShareKeyId = Helper::getPublicShareKeyId(); - if ($publicShareKeyId === null) { + if ($publicShareKeyId === false) { $publicShareKeyId = 'pubShare_' . substr(md5(time()), 0, 8); $appConfig->setValue('files_encryption', 'publicShareKeyId', $publicShareKeyId); } - if ( - !$this->view->file_exists("/public-keys/" . $publicShareKeyId . ".public.key") - || !$this->view->file_exists("/owncloud_private_key/" . $publicShareKeyId . ".private.key") - ) { + if (!Keymanager::publicShareKeyExists($view)) { $keypair = Crypt::createKeypair(); - // Disable encryption proxy to prevent recursive calls - $proxyStatus = \OC_FileProxy::$enabled; - \OC_FileProxy::$enabled = false; // Save public key - - if (!$view->is_dir('/public-keys')) { - $view->mkdir('/public-keys'); - } - - $this->view->file_put_contents('/public-keys/' . $publicShareKeyId . '.public.key', $keypair['publicKey']); + Keymanager::setPublicKey($keypair['publicKey'], $publicShareKeyId); // Encrypt private key empty passphrase - $cipher = \OCA\Encryption\Helper::getCipher(); - $encryptedKey = \OCA\Encryption\Crypt::symmetricEncryptFileContent($keypair['privateKey'], '', $cipher); + $cipher = Helper::getCipher(); + $encryptedKey = Crypt::symmetricEncryptFileContent($keypair['privateKey'], '', $cipher); if ($encryptedKey) { - Keymanager::setPrivateSystemKey($encryptedKey, $publicShareKeyId . '.private.key'); + Keymanager::setPrivateSystemKey($encryptedKey, $publicShareKeyId); } else { \OCP\Util::writeLog('files_encryption', 'Could not create public share keys', \OCP\Util::ERROR); } - \OC_FileProxy::$enabled = $proxyStatus; - } - if (\OCA\Encryption\Helper::isPublicAccess()) { + if (Helper::isPublicAccess() && !self::getPublicSharePrivateKey()) { // Disable encryption proxy to prevent recursive calls $proxyStatus = \OC_FileProxy::$enabled; \OC_FileProxy::$enabled = false; - $encryptedKey = $this->view->file_get_contents( - '/owncloud_private_key/' . $publicShareKeyId . '.private.key'); + $encryptedKey = Keymanager::getPrivateSystemKey($publicShareKeyId); $privateKey = Crypt::decryptPrivateKey($encryptedKey, ''); - $this->setPublicSharePrivateKey($privateKey); - - $this->setInitialized(\OCA\Encryption\Session::INIT_SUCCESSFUL); + self::setPublicSharePrivateKey($privateKey); \OC_FileProxy::$enabled = $proxyStatus; } @@ -127,8 +114,8 @@ class Session { * remove keys from session */ public function removeKeys() { - \OC::$session->remove('publicSharePrivateKey'); - \OC::$session->remove('privateKey'); + \OC::$server->getSession()->remove('publicSharePrivateKey'); + \OC::$server->getSession()->remove('privateKey'); } /** @@ -164,6 +151,8 @@ class Session { public function getInitialized() { if (!is_null(\OC::$server->getSession()->get('encryptionInitialized'))) { return \OC::$server->getSession()->get('encryptionInitialized'); + } else if (Helper::isPublicAccess() && self::getPublicSharePrivateKey()) { + return self::INIT_SUCCESSFUL; } else { return self::NOT_INITIALIZED; } @@ -176,8 +165,8 @@ class Session { */ public function getPrivateKey() { // return the public share private key if this is a public access - if (\OCA\Encryption\Helper::isPublicAccess()) { - return $this->getPublicSharePrivateKey(); + if (Helper::isPublicAccess()) { + return self::getPublicSharePrivateKey(); } else { if (!is_null(\OC::$server->getSession()->get('privateKey'))) { return \OC::$server->getSession()->get('privateKey'); @@ -192,12 +181,9 @@ class Session { * @param string $privateKey * @return bool */ - public function setPublicSharePrivateKey($privateKey) { - - \OC::$server->getSession()->set('publicSharePrivateKey', $privateKey); - + private static function setPublicSharePrivateKey($privateKey) { + self::$publicShareKey = $privateKey; return true; - } /** @@ -205,13 +191,8 @@ class Session { * @return string $privateKey * */ - public function getPublicSharePrivateKey() { - - if (!is_null(\OC::$server->getSession()->get('publicSharePrivateKey'))) { - return \OC::$server->getSession()->get('publicSharePrivateKey'); - } else { - return false; - } + private static function getPublicSharePrivateKey() { + return self::$publicShareKey; } } diff --git a/apps/files_encryption/lib/stream.php b/apps/files_encryption/lib/stream.php index f74812a7253..17da4eb1cdc 100644 --- a/apps/files_encryption/lib/stream.php +++ b/apps/files_encryption/lib/stream.php @@ -2,10 +2,11 @@ /** * ownCloud * - * @author Bjoern Schiessle, Robin Appelman - * @copyright 2014 Bjoern Schiessle <schiessle@owncloud.com> - * 2012 Sam Tuke <samtuke@owncloud.com>, - * 2011 Robin Appelman <icewind1991@gmail.com> + * @copyright (C) 2014 ownCloud, Inc. + * + * @author Bjoern Schiessle <schiessle@owncloud.com> + * @author Robin Appelman <icewind@owncloud.com> + * @author Sam Tuke <samtuke@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 @@ -29,7 +30,9 @@ * and then fopen('crypt://streams/foo'); */ -namespace OCA\Encryption; +namespace OCA\Files_Encryption; + +use OCA\Files_Encryption\Exception\EncryptionException; /** * Provides 'crypt://' stream wrapper protocol. @@ -79,7 +82,7 @@ class Stream { private $rootView; // a fsview object set to '/' /** - * @var \OCA\Encryption\Session + * @var \OCA\Files_Encryption\Session */ private $session; private $privateKey; @@ -90,6 +93,7 @@ class Stream { * @param int $options * @param string $opened_path * @return bool + * @throw \OCA\Files_Encryption\Exception\EncryptionException */ public function stream_open($path, $mode, $options, &$opened_path) { @@ -103,9 +107,13 @@ class Stream { $this->rootView = new \OC\Files\View('/'); } - $this->session = new \OCA\Encryption\Session($this->rootView); + $this->session = new Session($this->rootView); $this->privateKey = $this->session->getPrivateKey(); + if ($this->privateKey === false) { + throw new EncryptionException('Session does not contain a private key, maybe your login password changed?', + EncryptionException::PRIVATE_KEY_MISSING); + } $normalizedPath = \OC\Files\Filesystem::normalizePath(str_replace('crypt://', '', $path)); if ($originalFile = Helper::getPathFromTmpFile($normalizedPath)) { @@ -155,7 +163,7 @@ class Stream { if($this->privateKey === false) { // if private key is not valid redirect user to a error page - \OCA\Encryption\Helper::redirectToErrorPage($this->session); + Helper::redirectToErrorPage($this->session); } $this->size = $this->rootView->filesize($this->rawPath); @@ -244,7 +252,7 @@ class Stream { /** * @param int $count * @return bool|string - * @throws \OCA\Encryption\Exceptions\EncryptionException + * @throws \OCA\Files_Encryption\Exception\EncryptionException */ public function stream_read($count) { @@ -252,7 +260,7 @@ class Stream { if ($count !== Crypt::BLOCKSIZE) { \OCP\Util::writeLog('Encryption library', 'PHP "bug" 21641 no longer holds, decryption system requires refactoring', \OCP\Util::FATAL); - throw new \OCA\Encryption\Exceptions\EncryptionException('expected a blog size of 8192 byte', 20); + throw new EncryptionException('expected a blog size of 8192 byte', EncryptionException::UNEXPECTED_BLOG_SIZE); } // Get the data from the file handle @@ -322,7 +330,7 @@ class Stream { // Fetch and decrypt keyfile // Fetch existing keyfile - $util = new \OCA\Encryption\Util($this->rootView, $this->userId); + $util = new Util($this->rootView, $this->userId); $this->encKeyfile = Keymanager::getFileKey($this->rootView, $util, $this->relPath); // If a keyfile already exists @@ -333,13 +341,13 @@ class Stream { // if there is no valid private key return false if ($this->privateKey === false) { // if private key is not valid redirect user to a error page - \OCA\Encryption\Helper::redirectToErrorPage($this->session); + Helper::redirectToErrorPage($this->session); return false; } if ($shareKey === false) { // if no share key is available redirect user to a error page - \OCA\Encryption\Helper::redirectToErrorPage($this->session, \OCA\Encryption\Crypt::ENCRYPTION_NO_SHARE_KEY_FOUND); + Helper::redirectToErrorPage($this->session, Crypt::ENCRYPTION_NO_SHARE_KEY_FOUND); return false; } @@ -360,14 +368,14 @@ class Stream { /** * write header at beginning of encrypted file * - * @throws Exceptions\EncryptionException + * @throws \OCA\Files_Encryption\Exception\EncryptionException */ private function writeHeader() { $header = Crypt::generateHeader(); if (strlen($header) > Crypt::BLOCKSIZE) { - throw new Exceptions\EncryptionException('max header size exceeded', 30); + throw new EncryptionException('max header size exceeded', EncryptionException::ENCRYPTION_HEADER_TO_LARGE); } $paddedHeader = str_pad($header, Crypt::BLOCKSIZE, self::PADDING_CHAR, STR_PAD_RIGHT); @@ -573,6 +581,7 @@ class Stream { \OC_FileProxy::$enabled = false; if ($this->rootView->file_exists($this->rawPath) && $this->size === 0) { + fclose($this->handle); $this->rootView->unlink($this->rawPath); } @@ -581,7 +590,7 @@ class Stream { } // if private key is not valid redirect user to a error page - \OCA\Encryption\Helper::redirectToErrorPage($this->session); + Helper::redirectToErrorPage($this->session); } if ( @@ -632,13 +641,17 @@ class Stream { $path = Helper::stripPartialFileExtension($this->rawPath); $fileInfo = array( + 'mimetype' => $this->rootView->getMimeType($this->rawPath), 'encrypted' => true, - 'size' => $this->size, 'unencrypted_size' => $this->unencryptedSize, ); - // set fileinfo - $this->rootView->putFileInfo($path, $fileInfo); + // if we write a part file we also store the unencrypted size for + // the part file so that it can be re-used later + $this->rootView->putFileInfo($this->rawPath, $fileInfo); + if ($path !== $this->rawPath) { + $this->rootView->putFileInfo($path, $fileInfo); + } } diff --git a/apps/files_encryption/lib/util.php b/apps/files_encryption/lib/util.php index ce5e8c8b54c..4aaf7aa2571 100644 --- a/apps/files_encryption/lib/util.php +++ b/apps/files_encryption/lib/util.php @@ -2,10 +2,11 @@ /** * ownCloud * - * @author Sam Tuke, Frank Karlitschek, Bjoern Schiessle - * @copyright 2012 Sam Tuke <samtuke@owncloud.com>, - * Frank Karlitschek <frank@owncloud.org>, - * Bjoern Schiessle <schiessle@owncloud.com> + * @copyright (C) 2014 ownCloud, Inc. + * + * @author Sam Tuke <samtuke@owncloud.com>, + * @author Frank Karlitschek <frank@owncloud.org>, + * @author Bjoern Schiessle <schiessle@owncloud.com> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE @@ -22,7 +23,7 @@ * */ -namespace OCA\Encryption; +namespace OCA\Files_Encryption; /** * Class for utilities relating to encrypted file storage system @@ -44,10 +45,10 @@ class Util { private $client; // Client side encryption mode flag private $publicKeyDir; // Dir containing all public user keys private $encryptionDir; // Dir containing user's files_encryption - private $keyfilesPath; // Dir containing user's keyfiles - private $shareKeysPath; // Dir containing env keys for shared files + private $keysPath; // Dir containing all file related encryption keys private $publicKeyPath; // Path to user's public key private $privateKeyPath; // Path to user's private key + private $userFilesDir; private $publicShareKeyId; private $recoveryKeyId; private $isPublic; @@ -72,18 +73,17 @@ class Util { $this->fileFolderName = 'files'; $this->userFilesDir = '/' . $userId . '/' . $this->fileFolderName; // TODO: Does this need to be user configurable? - $this->publicKeyDir = '/' . 'public-keys'; + $this->publicKeyDir = Keymanager::getPublicKeyPath(); $this->encryptionDir = '/' . $this->userId . '/' . 'files_encryption'; - $this->keyfilesPath = $this->encryptionDir . '/' . 'keyfiles'; - $this->shareKeysPath = $this->encryptionDir . '/' . 'share-keys'; + $this->keysPath = $this->encryptionDir . '/' . 'keys'; $this->publicKeyPath = - $this->publicKeyDir . '/' . $this->userId . '.public.key'; // e.g. data/public-keys/admin.public.key + $this->publicKeyDir . '/' . $this->userId . '.publicKey'; // e.g. data/public-keys/admin.publicKey $this->privateKeyPath = - $this->encryptionDir . '/' . $this->userId . '.private.key'; // e.g. data/admin/admin.private.key + $this->encryptionDir . '/' . $this->userId . '.privateKey'; // e.g. data/admin/admin.privateKey // make sure that the owners home is mounted \OC\Files\Filesystem::initMountPoints($userId); - if (\OCA\Encryption\Helper::isPublicAccess()) { + if (Helper::isPublicAccess()) { $this->keyId = $this->publicShareKeyId; $this->isPublic = true; } else { @@ -99,8 +99,7 @@ class Util { if ( !$this->view->file_exists($this->encryptionDir) - or !$this->view->file_exists($this->keyfilesPath) - or !$this->view->file_exists($this->shareKeysPath) + or !$this->view->file_exists($this->keysPath) or !$this->view->file_exists($this->publicKeyPath) or !$this->view->file_exists($this->privateKeyPath) ) { @@ -125,6 +124,18 @@ class Util { } /** + * create a new public/private key pair for the user + * + * @param string $password password for the private key + */ + public function replaceUserKeys($password) { + $this->backupAllKeys('password_reset'); + $this->view->unlink($this->publicKeyPath); + $this->view->unlink($this->privateKeyPath); + $this->setupServerSide($password); + } + + /** * Sets up user folders and keys for serverside encryption * * @param string $passphrase to encrypt server-stored private key with @@ -137,8 +148,7 @@ class Util { $this->userDir, $this->publicKeyDir, $this->encryptionDir, - $this->keyfilesPath, - $this->shareKeysPath + $this->keysPath ); // Check / create all necessary dirs @@ -215,7 +225,7 @@ class Util { */ public function recoveryEnabledForUser() { - $recoveryMode = \OC_Preferences::getValue($this->userId, 'files_encryption', 'recovery_enabled', '0'); + $recoveryMode = \OC::$server->getConfig()->getUserValue($this->userId, 'files_encryption', 'recovery_enabled', '0'); return ($recoveryMode === '1') ? true : false; @@ -229,7 +239,12 @@ class Util { public function setRecoveryForUser($enabled) { $value = $enabled ? '1' : '0'; - return \OC_Preferences::setValue($this->userId, 'files_encryption', 'recovery_enabled', $value); + try { + \OC::$server->getConfig()->setUserValue($this->userId, 'files_encryption', 'recovery_enabled', $value); + return true; + } catch(\OCP\PreConditionNotMetException $e) { + return false; + } } @@ -262,7 +277,7 @@ class Util { if ($file !== "." && $file !== "..") { $filePath = $directory . '/' . $this->view->getRelativePath('/' . $file); - $relPath = \OCA\Encryption\Helper::stripUserFilesPath($filePath); + $relPath = Helper::stripUserFilesPath($filePath); // If the path is a directory, search // its contents @@ -436,13 +451,13 @@ class Util { } } fclose($stream); - $relPath = \OCA\Encryption\Helper::stripUserFilesPath($path); + $relPath = Helper::stripUserFilesPath($path); $shareKey = Keymanager::getShareKey($this->view, $this->keyId, $this, $relPath); if($shareKey===false) { \OC_FileProxy::$enabled = $proxyStatus; return $result; } - $session = new \OCA\Encryption\Session($this->view); + $session = new Session($this->view); $privateKey = $session->getPrivateKey(); $plainKeyfile = $this->decryptKeyfile($relPath, $privateKey); $plainKey = Crypt::multiKeyDecrypt($plainKeyfile, $shareKey, $privateKey); @@ -715,8 +730,8 @@ class Util { } if ($successful) { - $this->view->rename($this->keyfilesPath, $this->keyfilesPath . '.backup'); - $this->view->rename($this->shareKeysPath, $this->shareKeysPath . '.backup'); + $this->backupAllKeys('decryptAll'); + $this->view->deleteAll($this->keysPath); } \OC_FileProxy::$enabled = true; @@ -833,9 +848,9 @@ class Util { break; - case 'keyfilesPath': + case 'keysPath': - return $this->keyfilesPath; + return $this->keysPath; break; @@ -857,6 +872,25 @@ class Util { } /** + * Returns whether the given user is ready for encryption. + * Also returns true if the given user is the public user + * or the recovery key user. + * + * @param string $user user to check + * + * @return boolean true if the user is ready, false otherwise + */ + private function isUserReady($user) { + if ($user === $this->publicShareKeyId + || $user === $this->recoveryKeyId + ) { + return true; + } + $util = new Util($this->view, $user); + return $util->ready(); + } + + /** * Filter an array of UIDs to return only ones ready for sharing * @param array $unfilteredUsers users to be checked for sharing readiness * @return array as multi-dimensional array. keys: ready, unready @@ -868,16 +902,9 @@ class Util { // Loop through users and create array of UIDs that need new keyfiles foreach ($unfilteredUsers as $user) { - - $util = new Util($this->view, $user); - // Check that the user is encryption capable, or is the - // public system user 'ownCloud' (for public shares) - if ( - $user === $this->publicShareKeyId - or $user === $this->recoveryKeyId - or $util->ready() - ) { + // public system user (for public shares) + if ($this->isUserReady($user)) { // Construct array of ready UIDs for Keymanager{} $readyIds[] = $user; @@ -960,7 +987,7 @@ class Util { $plainKeyfile = $this->decryptKeyfile($filePath, $privateKey); // Re-enc keyfile to (additional) sharekeys $multiEncKey = Crypt::multiKeyEncrypt($plainKeyfile, $userPubKeys); - } catch (Exceptions\EncryptionException $e) { + } catch (Exception\EncryptionException $e) { $msg = 'set shareFileKeyFailed (code: ' . $e->getCode() . '): ' . $e->getMessage(); \OCP\Util::writeLog('files_encryption', $msg, \OCP\Util::FATAL); return false; @@ -1013,7 +1040,7 @@ class Util { // Make sure that a share key is generated for the owner too list($owner, $ownerPath) = $this->getUidAndFilename($filePath); - $ownerPath = \OCA\Encryption\Helper::stripPartialFileExtension($ownerPath); + $ownerPath = Helper::stripPartialFileExtension($ownerPath); // always add owner to the list of users with access to the file $userIds = array($owner); @@ -1080,7 +1107,12 @@ class Util { // convert to string if preCondition is set $preCondition = ($preCondition === null) ? null : (string)$preCondition; - return \OC_Preferences::setValue($this->userId, 'files_encryption', 'migration_status', (string)$status, $preCondition); + try { + \OC::$server->getConfig()->setUserValue($this->userId, 'files_encryption', 'migration_status', (string)$status, $preCondition); + return true; + } catch(\OCP\PreConditionNotMetException $e) { + return false; + } } @@ -1132,9 +1164,9 @@ class Util { $migrationStatus = false; if (\OCP\User::userExists($this->userId)) { - $migrationStatus = \OC_Preferences::getValue($this->userId, 'files_encryption', 'migration_status'); + $migrationStatus = \OC::$server->getConfig()->getUserValue($this->userId, 'files_encryption', 'migration_status', null); if ($migrationStatus === null) { - \OC_Preferences::setValue($this->userId, 'files_encryption', 'migration_status', (string)self::MIGRATION_OPEN); + \OC::$server->getConfig()->setUserValue($this->userId, 'files_encryption', 'migration_status', (string)self::MIGRATION_OPEN); $migrationStatus = self::MIGRATION_OPEN; } } @@ -1175,13 +1207,7 @@ class Util { // handle public access if ($this->isPublic) { - $filename = $path; - $fileOwnerUid = $this->userId; - - return array( - $fileOwnerUid, - $filename - ); + return array($this->userId, $path); } else { // Check that UID is valid @@ -1341,22 +1367,14 @@ class Util { public function checkRecoveryPassword($password) { $result = false; - $pathKey = '/owncloud_private_key/' . $this->recoveryKeyId . ".private.key"; - - $proxyStatus = \OC_FileProxy::$enabled; - \OC_FileProxy::$enabled = false; - - $recoveryKey = $this->view->file_get_contents($pathKey); + $recoveryKey = Keymanager::getPrivateSystemKey($this->recoveryKeyId); $decryptedRecoveryKey = Crypt::decryptPrivateKey($recoveryKey, $password); if ($decryptedRecoveryKey) { $result = true; } - \OC_FileProxy::$enabled = $proxyStatus; - - return $result; } @@ -1371,19 +1389,17 @@ class Util { * add recovery key to all encrypted files */ public function addRecoveryKeys($path = '/') { - $dirContent = $this->view->getDirectoryContent($this->keyfilesPath . $path); + $dirContent = $this->view->getDirectoryContent($this->keysPath . '/' . $path); foreach ($dirContent as $item) { // get relative path from files_encryption/keyfiles/ - $filePath = substr($item['path'], strlen('files_encryption/keyfiles')); - if ($item['type'] === 'dir') { + $filePath = substr($item['path'], strlen('files_encryption/keys')); + if ($this->view->is_dir($this->userFilesDir . '/' . $filePath)) { $this->addRecoveryKeys($filePath . '/'); } else { - $session = new \OCA\Encryption\Session(new \OC\Files\View('/')); + $session = new Session(new \OC\Files\View('/')); $sharingEnabled = \OCP\Share::isEnabled(); - // remove '.key' extension from path e.g. 'file.txt.key' to 'file.txt' - $file = substr($filePath, 0, -4); - $usersSharing = $this->getSharingUsersArray($sharingEnabled, $file); - $this->setSharedFileKeyfiles($session, $usersSharing, $file); + $usersSharing = $this->getSharingUsersArray($sharingEnabled, $filePath); + $this->setSharedFileKeyfiles($session, $usersSharing, $filePath); } } } @@ -1392,16 +1408,14 @@ class Util { * remove recovery key to all encrypted files */ public function removeRecoveryKeys($path = '/') { - $dirContent = $this->view->getDirectoryContent($this->keyfilesPath . $path); + $dirContent = $this->view->getDirectoryContent($this->keysPath . '/' . $path); foreach ($dirContent as $item) { // get relative path from files_encryption/keyfiles - $filePath = substr($item['path'], strlen('files_encryption/keyfiles')); - if ($item['type'] === 'dir') { + $filePath = substr($item['path'], strlen('files_encryption/keys')); + if ($this->view->is_dir($this->userFilesDir . '/' . $filePath)) { $this->removeRecoveryKeys($filePath . '/'); } else { - // remove '.key' extension from path e.g. 'file.txt.key' to 'file.txt' - $file = substr($filePath, 0, -4); - $this->view->unlink($this->shareKeysPath . '/' . $file . '.' . $this->recoveryKeyId . '.shareKey'); + $this->view->unlink($this->keysPath . '/' . $filePath . '/' . $this->recoveryKeyId . '.shareKey'); } } } @@ -1431,27 +1445,17 @@ class Util { } $filteredUids = $this->filterShareReadyUsers($userIds); - $proxyStatus = \OC_FileProxy::$enabled; - \OC_FileProxy::$enabled = false; - //decrypt file key - $encKeyfile = $this->view->file_get_contents($this->keyfilesPath . $file . ".key"); - $shareKey = $this->view->file_get_contents( - $this->shareKeysPath . $file . "." . $this->recoveryKeyId . ".shareKey"); + $encKeyfile = Keymanager::getFileKey($this->view, $this, $file); + $shareKey = Keymanager::getShareKey($this->view, $this->recoveryKeyId, $this, $file); $plainKeyfile = Crypt::multiKeyDecrypt($encKeyfile, $shareKey, $privateKey); // encrypt file key again to all users, this time with the new public key for the recovered use $userPubKeys = Keymanager::getPublicKeys($this->view, $filteredUids['ready']); $multiEncKey = Crypt::multiKeyEncrypt($plainKeyfile, $userPubKeys); - // write new keys to filesystem TDOO! - $this->view->file_put_contents($this->keyfilesPath . $file . '.key', $multiEncKey['data']); - foreach ($multiEncKey['keys'] as $userId => $shareKey) { - $shareKeyPath = $this->shareKeysPath . $file . '.' . $userId . '.shareKey'; - $this->view->file_put_contents($shareKeyPath, $shareKey); - } + Keymanager::setFileKey($this->view, $this, $file, $multiEncKey['data']); + Keymanager::setShareKeys($this->view, $this, $file, $multiEncKey['keys']); - // Return proxy to original status - \OC_FileProxy::$enabled = $proxyStatus; } /** @@ -1460,16 +1464,14 @@ class Util { * @param string $privateKey private recovery key which is used to decrypt the files */ private function recoverAllFiles($path, $privateKey) { - $dirContent = $this->view->getDirectoryContent($this->keyfilesPath . $path); + $dirContent = $this->view->getDirectoryContent($this->keysPath . '/' . $path); foreach ($dirContent as $item) { // get relative path from files_encryption/keyfiles - $filePath = substr($item['path'], strlen('files_encryption/keyfiles')); - if ($item['type'] === 'dir') { + $filePath = substr($item['path'], strlen('files_encryption/keys')); + if ($this->view->is_dir($this->userFilesDir . '/' . $filePath)) { $this->recoverAllFiles($filePath . '/', $privateKey); } else { - // remove '.key' extension from path e.g. 'file.txt.key' to 'file.txt' - $file = substr($filePath, 0, -4); - $this->recoverFile($file, $privateKey); + $this->recoverFile($filePath, $privateKey); } } } @@ -1480,16 +1482,9 @@ class Util { */ public function recoverUsersFiles($recoveryPassword) { - // Disable encryption proxy to prevent recursive calls - $proxyStatus = \OC_FileProxy::$enabled; - \OC_FileProxy::$enabled = false; - - $encryptedKey = $this->view->file_get_contents( - '/owncloud_private_key/' . $this->recoveryKeyId . '.private.key'); + $encryptedKey = Keymanager::getPrivateSystemKey( $this->recoveryKeyId); $privateKey = Crypt::decryptPrivateKey($encryptedKey, $recoveryPassword); - \OC_FileProxy::$enabled = $proxyStatus; - $this->recoverAllFiles('/', $privateKey); } @@ -1503,10 +1498,9 @@ class Util { $backupDir = $this->encryptionDir . '/backup.'; $backupDir .= ($purpose === '') ? date("Y-m-d_H-i-s") . '/' : $purpose . '.' . date("Y-m-d_H-i-s") . '/'; $this->view->mkdir($backupDir); - $this->view->copy($this->shareKeysPath, $backupDir . 'share-keys/'); - $this->view->copy($this->keyfilesPath, $backupDir . 'keyfiles/'); - $this->view->copy($this->privateKeyPath, $backupDir . $this->userId . '.private.key'); - $this->view->copy($this->publicKeyPath, $backupDir . $this->userId . '.public.key'); + $this->view->copy($this->keysPath, $backupDir . 'keys/'); + $this->view->copy($this->privateKeyPath, $backupDir . $this->userId . '.privateKey'); + $this->view->copy($this->publicKeyPath, $backupDir . $this->userId . '.publicKey'); } /** @@ -1559,14 +1553,17 @@ class Util { */ public function initEncryption($params) { - $session = new \OCA\Encryption\Session($this->view); + $session = new Session($this->view); // we tried to initialize the encryption app for this session - $session->setInitialized(\OCA\Encryption\Session::INIT_EXECUTED); + $session->setInitialized(Session::INIT_EXECUTED); $encryptedKey = Keymanager::getPrivateKey($this->view, $params['uid']); - $privateKey = Crypt::decryptPrivateKey($encryptedKey, $params['password']); + $privateKey = false; + if ($encryptedKey) { + $privateKey = Crypt::decryptPrivateKey($encryptedKey, $params['password']); + } if ($privateKey === false) { \OCP\Util::writeLog('Encryption library', 'Private key for user "' . $params['uid'] @@ -1575,7 +1572,7 @@ class Util { } $session->setPrivateKey($privateKey); - $session->setInitialized(\OCA\Encryption\Session::INIT_SUCCESSFUL); + $session->setInitialized(Session::INIT_SUCCESSFUL); return $session; } @@ -1584,7 +1581,7 @@ class Util { * remove encryption related keys from the session */ public function closeEncryptionSession() { - $session = new \OCA\Encryption\Session($this->view); + $session = new Session($this->view); $session->closeSession(); } |