Commit 3fdb1937 authored by Morris Jobke's avatar Morris Jobke
Browse files

Merge pull request #12382 from owncloud/enc_reorganize_folders2

[encryption] reorganize folder structure (second try to make Jenkins happy)
parents e91dddd1 9ca9acf3
......@@ -55,16 +55,15 @@ $proxyStatus = \OC_FileProxy::$enabled;
\OC_FileProxy::$enabled = false;
$keyId = $util->getRecoveryKeyId();
$keyPath = '/owncloud_private_key/' . $keyId . '.private.key';
$encryptedRecoveryKey = $view->file_get_contents($keyPath);
$decryptedRecoveryKey = \OCA\Encryption\Crypt::decryptPrivateKey($encryptedRecoveryKey, $oldPassword);
$encryptedRecoveryKey = Encryption\Keymanager::getPrivateSystemKey($keyId);
$decryptedRecoveryKey = $encryptedRecoveryKey ? \OCA\Encryption\Crypt::decryptPrivateKey($encryptedRecoveryKey, $oldPassword) : false;
if ($decryptedRecoveryKey) {
$cipher = \OCA\Encryption\Helper::getCipher();
$encryptedKey = \OCA\Encryption\Crypt::symmetricEncryptFileContent($decryptedRecoveryKey, $newPassword, $cipher);
if ($encryptedKey) {
\OCA\Encryption\Keymanager::setPrivateSystemKey($encryptedKey, $keyId . '.private.key');
\OCA\Encryption\Keymanager::setPrivateSystemKey($encryptedKey, $keyId);
$return = true;
}
}
......
......@@ -36,10 +36,8 @@ if ($passwordCorrect !== false) {
$proxyStatus = \OC_FileProxy::$enabled;
\OC_FileProxy::$enabled = false;
$keyPath = '/' . $user . '/files_encryption/' . $user . '.private.key';
$encryptedKey = $view->file_get_contents($keyPath);
$decryptedKey = \OCA\Encryption\Crypt::decryptPrivateKey($encryptedKey, $oldPassword);
$encryptedKey = Encryption\Keymanager::getPrivateKey($view, $user);
$decryptedKey = $encryptedKey ? \OCA\Encryption\Crypt::decryptPrivateKey($encryptedKey, $oldPassword) : false;
if ($decryptedKey) {
$cipher = \OCA\Encryption\Helper::getCipher();
......
......@@ -4,7 +4,8 @@ use OCA\Files_Encryption\Migration;
$installedVersion=OCP\Config::getAppValue('files_encryption', 'installed_version');
if (version_compare($installedVersion, '0.6', '<')) {
// Migration OC7 -> OC8
if (version_compare($installedVersion, '0.7', '<')) {
$m = new Migration();
$m->dropTableEncryption();
$m->reorganizeFolderStructure();
}
......@@ -27,7 +27,7 @@ namespace OCA\Encryption\Exception;
* Base class for all encryption exception
*
* Possible Error Codes:
* 10 - unknown error
* 10 - generic error
* 20 - unexpected end of encryption header
* 30 - unexpected blog size
* 40 - encryption header to large
......@@ -38,7 +38,7 @@ namespace OCA\Encryption\Exception;
* 90 - private key missing
*/
class EncryptionException extends \Exception {
const UNKNOWN = 10;
const GENERIC = 10;
const UNEXPECTED_END_OF_ENCRYPTION_HEADER = 20;
const UNEXPECTED_BLOG_SIZE = 30;
const ENCRYPTION_HEADER_TO_LARGE = 40;
......
......@@ -3,8 +3,10 @@
/**
* ownCloud
*
* @author Sam Tuke
* @copyright 2012 Sam Tuke samtuke@owncloud.org
* @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
......@@ -35,7 +37,7 @@ class Hooks {
// 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 $umountedFiles = array();
private static $unmountedFiles = array();
/**
* Startup encryption backend upon user login
......@@ -150,18 +152,7 @@ class Hooks {
public static function postDeleteUser($params) {
if (\OCP\App::isEnabled('files_encryption')) {
$view = new \OC\Files\View('/');
// cleanup public key
$publicKey = '/public-keys/' . $params['uid'] . '.public.key';
// Disable encryption proxy to prevent recursive calls
$proxyStatus = \OC_FileProxy::$enabled;
\OC_FileProxy::$enabled = false;
$view->unlink($publicKey);
\OC_FileProxy::$enabled = $proxyStatus;
Keymanager::deletePublicKey(new \OC\Files\View(), $params['uid']);
}
}
......@@ -242,7 +233,7 @@ class Hooks {
\OC_FileProxy::$enabled = false;
// Save public key
$view->file_put_contents('/public-keys/' . $user . '.public.key', $keypair['publicKey']);
Keymanager::setPublicKey($keypair['publicKey'], $user);
// Encrypt private key with new password
$encryptedKey = \OCA\Encryption\Crypt::symmetricEncryptFileContent($keypair['privateKey'], $newUserPassword, Helper::getCipher());
......@@ -290,7 +281,7 @@ class Hooks {
$l = new \OC_L10N('files_encryption');
$users = array();
$view = new \OC\Files\View('/public-keys/');
$view = new \OC\Files\View('/');
switch ($params['shareType']) {
case \OCP\Share::SHARE_TYPE_USER:
......@@ -303,7 +294,7 @@ class Hooks {
$notConfigured = array();
foreach ($users as $user) {
if (!$view->file_exists($user . '.public.key')) {
if (!Keymanager::publicKeyExists($view, $user)) {
$notConfigured[] = $user;
}
}
......@@ -328,7 +319,7 @@ class Hooks {
$path = \OC\Files\Filesystem::getPath($params['fileSource']);
self::updateKeyfiles($path, $params['itemType']);
self::updateKeyfiles($path);
}
}
......@@ -336,9 +327,8 @@ class Hooks {
* update keyfiles and share keys recursively
*
* @param string $path to the file/folder
* @param string $type 'file' or 'folder'
*/
private static function updateKeyfiles($path, $type) {
private static function updateKeyfiles($path) {
$view = new \OC\Files\View('/');
$userId = \OCP\User::getUser();
$session = new \OCA\Encryption\Session($view);
......@@ -350,7 +340,7 @@ class Hooks {
$mountPoint = $mount->getMountPoint();
// if a folder was shared, get a list of all (sub-)folders
if ($type === 'folder') {
if ($view->is_dir('/' . $userId . '/files' . $path)) {
$allFiles = $util->getAllFiles($path, $mountPoint);
} else {
$allFiles = array($path);
......@@ -407,11 +397,10 @@ class Hooks {
// Unshare every user who no longer has access to the file
$delUsers = array_diff($userIds, $sharingUsers);
list($owner, $ownerPath) = $util->getUidAndFilename($path);
$keyPath = Keymanager::getKeyPath($view, $util, $path);
// delete share key
Keymanager::delShareKey($view, $delUsers, $ownerPath, $owner);
Keymanager::delShareKey($view, $delUsers, $keyPath, $userId, $path);
}
}
......@@ -437,37 +426,24 @@ class Hooks {
$user = \OCP\User::getUser();
$view = new \OC\Files\View('/');
$util = new Util($view, $user);
list($ownerOld, $pathOld) = $util->getUidAndFilename($params['oldpath']);
// 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']);
$type = $view->is_dir('/' . $user . '/files/' . $params['oldpath']) ? 'folder' : 'file';
$oldKeysPath = Keymanager::getKeyPath($view, $util, $params['oldpath']);
if ($mp1 === $mp2) {
if ($util->isSystemWideMountPoint($pathOld)) {
$oldShareKeyPath = 'files_encryption/share-keys/' . $pathOld;
} else {
$oldShareKeyPath = $ownerOld . '/' . 'files_encryption/share-keys/' . $pathOld;
}
// gather share keys here because in postRename() the file will be moved already
$oldShareKeys = Helper::findShareKeys($pathOld, $oldShareKeyPath, $view);
if (count($oldShareKeys) === 0) {
\OC_Log::write(
'Encryption library', 'No share keys found for "' . $pathOld . '"',
\OC_Log::WARN
);
}
self::$renamedFiles[$params['oldpath']] = array(
'uid' => $ownerOld,
'path' => $pathOld,
'type' => $type,
'operation' => $operation,
'sharekeys' => $oldShareKeys
'oldKeysPath' => $oldKeysPath,
);
} else {
self::$renamedFiles[$params['oldpath']] = array(
'operation' => 'cleanup',
'oldKeysPath' => $oldKeysPath,
);
}
}
......@@ -482,81 +458,40 @@ class Hooks {
return true;
}
// Disable encryption proxy to prevent recursive calls
$proxyStatus = \OC_FileProxy::$enabled;
\OC_FileProxy::$enabled = false;
$view = new \OC\Files\View('/');
$userId = \OCP\User::getUser();
$util = new Util($view, $userId);
$oldShareKeys = null;
if (isset(self::$renamedFiles[$params['oldpath']]['uid']) &&
isset(self::$renamedFiles[$params['oldpath']]['path'])) {
$ownerOld = self::$renamedFiles[$params['oldpath']]['uid'];
$pathOld = self::$renamedFiles[$params['oldpath']]['path'];
$type = self::$renamedFiles[$params['oldpath']]['type'];
if (isset(self::$renamedFiles[$params['oldpath']]['operation']) &&
isset(self::$renamedFiles[$params['oldpath']]['oldKeysPath'])) {
$operation = self::$renamedFiles[$params['oldpath']]['operation'];
$oldShareKeys = self::$renamedFiles[$params['oldpath']]['sharekeys'];
$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);
\OC_FileProxy::$enabled = $proxyStatus;
return false;
}
list($ownerNew, $pathNew) = $util->getUidAndFilename($params['newpath']);
// Format paths to be relative to user files dir
if ($util->isSystemWideMountPoint($pathOld)) {
$oldKeyfilePath = 'files_encryption/keyfiles/' . $pathOld;
$oldShareKeyPath = 'files_encryption/share-keys/' . $pathOld;
} else {
$oldKeyfilePath = $ownerOld . '/' . 'files_encryption/keyfiles/' . $pathOld;
$oldShareKeyPath = $ownerOld . '/' . 'files_encryption/share-keys/' . $pathOld;
}
if ($util->isSystemWideMountPoint($pathNew)) {
$newKeyfilePath = 'files_encryption/keyfiles/' . $pathNew;
$newShareKeyPath = 'files_encryption/share-keys/' . $pathNew;
} else {
$newKeyfilePath = $ownerNew . '/files_encryption/keyfiles/' . $pathNew;
$newShareKeyPath = $ownerNew . '/files_encryption/share-keys/' . $pathNew;
}
// create new key folders if it doesn't exists
if (!$view->file_exists(dirname($newShareKeyPath))) {
$view->mkdir(dirname($newShareKeyPath));
}
if (!$view->file_exists(dirname($newKeyfilePath))) {
$view->mkdir(dirname($newKeyfilePath));
}
// handle share keys
if ($type === 'file') {
$oldKeyfilePath .= '.key';
$newKeyfilePath .= '.key';
foreach ($oldShareKeys as $src) {
$dst = \OC\Files\Filesystem::normalizePath(str_replace($pathOld, $pathNew, $src));
$view->$operation($src, $dst);
}
$newKeysPath = 'files_encryption/keys/' . $pathNew;
} else {
// handle share-keys folders
$view->$operation($oldShareKeyPath, $newShareKeyPath);
$newKeysPath = $ownerNew . '/files_encryption/keys/' . $pathNew;
}
// Rename keyfile so it isn't orphaned
if ($view->file_exists($oldKeyfilePath)) {
$view->$operation($oldKeyfilePath, $newKeyfilePath);
// 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'], $type);
\OC_FileProxy::$enabled = $proxyStatus;
self::updateKeyfiles($params['newpath']);
}
/**
......@@ -592,37 +527,28 @@ class Hooks {
*/
public static function postDelete($params) {
if (!isset(self::$deleteFiles[$params[\OC\Files\Filesystem::signal_param_path]])) {
$path = $params[\OC\Files\Filesystem::signal_param_path];
if (!isset(self::$deleteFiles[$path])) {
return true;
}
$deletedFile = self::$deleteFiles[$params[\OC\Files\Filesystem::signal_param_path]];
$path = $deletedFile['path'];
$user = $deletedFile['uid'];
$deletedFile = self::$deleteFiles[$path];
$keyPath = $deletedFile['keyPath'];
// we don't need to remember the file any longer
unset(self::$deleteFiles[$params[\OC\Files\Filesystem::signal_param_path]]);
unset(self::$deleteFiles[$path]);
$view = new \OC\Files\View('/');
// return if the file still exists and wasn't deleted correctly
if ($view->file_exists('/' . $user . '/files/' . $path)) {
if ($view->file_exists('/' . \OCP\User::getUser() . '/files/' . $path)) {
return true;
}
// Disable encryption proxy to prevent recursive calls
$proxyStatus = \OC_FileProxy::$enabled;
\OC_FileProxy::$enabled = false;
// Delete keyfile & shareKey so it isn't orphaned
if (!Keymanager::deleteFileKey($view, $path, $user)) {
\OCP\Util::writeLog('Encryption library',
'Keyfile or shareKey could not be deleted for file "' . $user.'/files/'.$path . '"', \OCP\Util::ERROR);
}
Keymanager::delAllShareKeys($view, $user, $path);
$view->unlink($keyPath);
\OC_FileProxy::$enabled = $proxyStatus;
}
/**
......@@ -631,6 +557,7 @@ class Hooks {
* @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
......@@ -639,68 +566,61 @@ class Hooks {
return true;
}
$util = new Util(new \OC\Files\View('/'), \OCP\USER::getUser());
list($owner, $ownerPath) = $util->getUidAndFilename($path);
$util = new Util($view, \OCP\USER::getUser());
self::$deleteFiles[$params[\OC\Files\Filesystem::signal_param_path]] = array(
'uid' => $owner,
'path' => $ownerPath);
$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 preUmount($params) {
public static function preUnmount($params) {
$view = new \OC\Files\View('/');
$user = \OCP\User::getUser();
$path = $params[\OC\Files\Filesystem::signal_param_path];
$user = \OCP\USER::getUser();
$view = new \OC\Files\View();
$itemType = $view->is_dir('/' . $user . '/files' . $path) ? 'folder' : 'file';
$util = new Util($view, $user);
list($owner, $ownerPath) = $util->getUidAndFilename($path);
self::$umountedFiles[$params[\OC\Files\Filesystem::signal_param_path]] = array(
'uid' => $owner,
'path' => $ownerPath,
'itemType' => $itemType);
$keysPath = Keymanager::getKeyPath($view, $util, $path);
self::$unmountedFiles[$path] = array(
'keyPath' => $keysPath,
'owner' => $owner,
'ownerPath' => $ownerPath
);
}
/**
* unmount file from yourself
*/
public static function postUmount($params) {
public static function postUnmount($params) {
if (!isset(self::$umountedFiles[$params[\OC\Files\Filesystem::signal_param_path]])) {
$path = $params[\OC\Files\Filesystem::signal_param_path];
$user = \OCP\User::getUser();
if (!isset(self::$unmountedFiles[$path])) {
return true;
}
$umountedFile = self::$umountedFiles[$params[\OC\Files\Filesystem::signal_param_path]];
$path = $umountedFile['path'];
$user = $umountedFile['uid'];
$itemType = $umountedFile['itemType'];
$umountedFile = self::$unmountedFiles[$path];
$keyPath = $umountedFile['keyPath'];
$owner = $umountedFile['owner'];
$ownerPath = $umountedFile['ownerPath'];
$view = new \OC\Files\View();
$util = new Util($view, $user);
// we don't need to remember the file any longer
unset(self::$umountedFiles[$params[\OC\Files\Filesystem::signal_param_path]]);
unset(self::$unmountedFiles[$path]);
// if we unshare a folder we need a list of all (sub-)files
if ($itemType === 'folder') {
$allFiles = $util->getAllFiles($path);
} else {
$allFiles = array($path);
}
foreach ($allFiles as $path) {
// check if the user still has access to the file, otherwise delete share key
$sharingUsers = \OCP\Share::getUsersSharingFile($path, $user);
if (!in_array(\OCP\User::getUser(), $sharingUsers['users'])) {
Keymanager::delShareKey($view, array(\OCP\User::getUser()), $path, $user);
}
// check if the user still has access to the file, otherwise delete share key
$sharingUsers = \OCP\Share::getUsersSharingFile($path, $user);
if (!in_array(\OCP\User::getUser(), $sharingUsers['users'])) {
Keymanager::delShareKey($view, array(\OCP\User::getUser()), $keyPath, $owner, $ownerPath);
}
}
......
......@@ -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
......@@ -17,7 +19,7 @@
* 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/>.
* License alon with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
......@@ -68,9 +70,9 @@ class Helper {
\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\Core\LostPassword\Controller\LostController', 'post_passwordReset', 'OCA\Encryption\Hooks', 'postPasswordReset');
\OCP\Util::connectHook('OC_Filesystem', 'post_umount', 'OCA\Encryption\Hooks', 'postUnmount');
\OCP\Util::connectHook('OC_Filesystem', 'umount', 'OCA\Encryption\Hooks', 'preUnmount');
}
/**
......@@ -105,6 +107,25 @@ class Helper {
return true;
}
/**
* 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
*
......@@ -124,38 +145,22 @@ 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"))
) {
if (!Keymanager::recoveryKeyExists($view)) {
$keypair = \OCA\Encryption\Crypt::createKeypair();
\OC_FileProxy::$enabled = false;
// Save public key
if (!$view->is_dir('/public-keys')) {
$view->mkdir('/public-keys');
}
$view->file_put_contents('/public-keys/' . $recoveryKeyId . '.public.key', $keypair['publicKey']);
Keymanager::setPublicKey($keypair['publicKey'], $recoveryKeyId);
$cipher = \OCA\Encryption\Helper::getCipher();
$encryptedKey = \OCA\Encryption\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());
$return = $util->checkRecoveryPassword($recoveryPassword);
......@@ -432,47 +437,6 @@ class Helper {
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;