aboutsummaryrefslogtreecommitdiffstats
path: root/apps/files_encryption/lib
diff options
context:
space:
mode:
Diffstat (limited to 'apps/files_encryption/lib')
-rw-r--r--apps/files_encryption/lib/capabilities.php2
-rw-r--r--apps/files_encryption/lib/crypt.php41
-rw-r--r--apps/files_encryption/lib/exceptions.php55
-rw-r--r--apps/files_encryption/lib/helper.php161
-rw-r--r--apps/files_encryption/lib/hooks.php626
-rw-r--r--apps/files_encryption/lib/keymanager.php594
-rw-r--r--apps/files_encryption/lib/migration.php264
-rw-r--r--apps/files_encryption/lib/proxy.php27
-rw-r--r--apps/files_encryption/lib/session.php75
-rw-r--r--apps/files_encryption/lib/stream.php51
-rw-r--r--apps/files_encryption/lib/util.php211
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();
}