diff options
Diffstat (limited to 'lib/private/encryption')
-rw-r--r-- | lib/private/encryption/exceptions/encryptionheaderkeyexistsexception.php | 29 | ||||
-rw-r--r-- | lib/private/encryption/exceptions/modulealreadyexistsexception.php | 28 | ||||
-rw-r--r-- | lib/private/encryption/exceptions/moduledoesnotexistsexception.php | 28 | ||||
-rw-r--r-- | lib/private/encryption/keys/factory.php | 52 | ||||
-rw-r--r-- | lib/private/encryption/keys/storage.php | 320 | ||||
-rw-r--r-- | lib/private/encryption/manager.php | 170 | ||||
-rw-r--r-- | lib/private/encryption/update.php | 111 | ||||
-rw-r--r-- | lib/private/encryption/util.php | 401 |
8 files changed, 1139 insertions, 0 deletions
diff --git a/lib/private/encryption/exceptions/encryptionheaderkeyexistsexception.php b/lib/private/encryption/exceptions/encryptionheaderkeyexistsexception.php new file mode 100644 index 00000000000..d401f0323ba --- /dev/null +++ b/lib/private/encryption/exceptions/encryptionheaderkeyexistsexception.php @@ -0,0 +1,29 @@ +<?php + +/** + * ownCloud + * + * @copyright (C) 2015 ownCloud, Inc. + * + * @author Bjoern Schiessle <schiessle@owncloud.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + */ + +namespace OC\Encryption\Exceptions; + + +class EncryptionHeaderKeyExistsException extends \Exception { + +}
\ No newline at end of file diff --git a/lib/private/encryption/exceptions/modulealreadyexistsexception.php b/lib/private/encryption/exceptions/modulealreadyexistsexception.php new file mode 100644 index 00000000000..41fc0188e24 --- /dev/null +++ b/lib/private/encryption/exceptions/modulealreadyexistsexception.php @@ -0,0 +1,28 @@ +<?php + +/** + * ownCloud + * + * @copyright (C) 2015 ownCloud, Inc. + * + * @author Bjoern Schiessle <schiessle@owncloud.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + */ + +namespace OC\Encryption\Exceptions; + +class ModuleAlreadyExistsException extends \Exception { + +} diff --git a/lib/private/encryption/exceptions/moduledoesnotexistsexception.php b/lib/private/encryption/exceptions/moduledoesnotexistsexception.php new file mode 100644 index 00000000000..5507bd03dab --- /dev/null +++ b/lib/private/encryption/exceptions/moduledoesnotexistsexception.php @@ -0,0 +1,28 @@ +<?php + +/** + * ownCloud + * + * @copyright (C) 2015 ownCloud, Inc. + * + * @author Bjoern Schiessle <schiessle@owncloud.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + */ + +namespace OC\Encryption\Exceptions; + +class ModuleDoesNotExistsException extends \Exception { + +} diff --git a/lib/private/encryption/keys/factory.php b/lib/private/encryption/keys/factory.php new file mode 100644 index 00000000000..a214b238615 --- /dev/null +++ b/lib/private/encryption/keys/factory.php @@ -0,0 +1,52 @@ +<?php + +/** + * ownCloud + * + * @copyright (C) 2015 ownCloud, Inc. + * + * @author Bjoern Schiessle <schiessle@owncloud.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + */ + +namespace OC\Encryption\Keys; + +use OC\Encryption\Util; +use OC\Files\View; +use OC\User; + +/** + * Factory provides KeyStorage for different encryption modules + */ +class Factory { + /** @var array */ + protected $instances = array(); + + /** + * get a KeyStorage instance + * + * @param string $encryptionModuleId + * @param View $view + * @param Util $util + * @return Storage + */ + public function get($encryptionModuleId,View $view, Util $util) { + if (!isset($this->instances[$encryptionModuleId])) { + $this->instances[$encryptionModuleId] = new Storage($encryptionModuleId, $view, $util); + } + return $this->instances[$encryptionModuleId]; + } + +} diff --git a/lib/private/encryption/keys/storage.php b/lib/private/encryption/keys/storage.php new file mode 100644 index 00000000000..041db2a2cb8 --- /dev/null +++ b/lib/private/encryption/keys/storage.php @@ -0,0 +1,320 @@ +<?php + +/** + * ownCloud + * + * @copyright (C) 2015 ownCloud, Inc. + * + * @author Bjoern Schiessle <schiessle@owncloud.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + */ + +namespace OC\Encryption\Keys; + +use OC\Encryption\Util; +use OC\Files\View; +use OCA\Files_Encryption\Exception\EncryptionException; + +class Storage implements \OCP\Encryption\Keys\IStorage { + + /** @var View */ + private $view; + + /** @var Util */ + private $util; + + // base dir where all the file related keys are stored + private $keys_base_dir; + private $encryption_base_dir; + + private $keyCache = array(); + + /** @var string */ + private $encryptionModuleId; + + /** + * @param string $encryptionModuleId + * @param View $view + * @param Util $util + */ + public function __construct($encryptionModuleId, View $view, Util $util) { + $this->view = $view; + $this->util = $util; + $this->encryptionModuleId = $encryptionModuleId; + + $this->encryption_base_dir = '/files_encryption'; + $this->keys_base_dir = $this->encryption_base_dir .'/keys'; + } + + /** + * get user specific key + * + * @param string $uid ID if the user for whom we want the key + * @param string $keyId id of the key + * + * @return mixed key + */ + public function getUserKey($uid, $keyId) { + $path = $this->constructUserKeyPath($keyId, $uid); + return $this->getKey($path); + } + + /** + * get file specific key + * + * @param string $path path to file + * @param string $keyId id of the key + * + * @return mixed key + */ + public function getFileKey($path, $keyId) { + $keyDir = $this->getFileKeyDir($path); + return $this->getKey($keyDir . $keyId); + } + + /** + * get system-wide encryption keys not related to a specific user, + * e.g something like a key for public link shares + * + * @param string $keyId id of the key + * + * @return mixed key + */ + public function getSystemUserKey($keyId) { + $path = $this->constructUserKeyPath($keyId); + return $this->getKey($path); + } + + /** + * set user specific key + * + * @param string $uid ID if the user for whom we want the key + * @param string $keyId id of the key + * @param mixed $key + */ + public function setUserKey($uid, $keyId, $key) { + $path = $this->constructUserKeyPath($keyId, $uid); + return $this->setKey($path, $key); + } + + /** + * set file specific key + * + * @param string $path path to file + * @param string $keyId id of the key + * @param boolean + */ + public function setFileKey($path, $keyId, $key) { + $keyDir = $this->getFileKeyDir($path); + return $this->setKey($keyDir . $keyId, $key); + } + + /** + * set system-wide encryption keys not related to a specific user, + * e.g something like a key for public link shares + * + * @param string $keyId id of the key + * @param mixed $key + * + * @return mixed key + */ + public function setSystemUserKey($keyId, $key) { + $path = $this->constructUserKeyPath($keyId); + return $this->setKey($path, $key); + } + + /** + * delete user specific key + * + * @param string $uid ID if the user for whom we want to delete the key + * @param string $keyId id of the key + * + * @return boolean + */ + public function deleteUserKey($uid, $keyId) { + $path = $this->constructUserKeyPath($keyId, $uid); + return $this->view->unlink($path); + } + + /** + * delete file specific key + * + * @param string $path path to file + * @param string $keyId id of the key + * + * @return boolean + */ + public function deleteFileKey($path, $keyId) { + $keyDir = $this->getFileKeyDir($path); + return $this->view->unlink($keyDir . $keyId); + } + + /** + * delete all file keys for a given file + * + * @param string $path to the file + * @return boolean + */ + public function deleteAllFileKeys($path) { + $keyDir = $this->getFileKeyDir($path); + return $this->view->deleteAll(dirname($keyDir)); + } + + /** + * delete system-wide encryption keys not related to a specific user, + * e.g something like a key for public link shares + * + * @param string $keyId id of the key + * + * @return boolean + */ + public function deleteSystemUserKey($keyId) { + $path = $this->constructUserKeyPath($keyId); + return $this->view->unlink($path); + } + + + /** + * construct path to users key + * + * @param string $keyId + * @param string $uid + * @return string + */ + protected function constructUserKeyPath($keyId, $uid = null) { + + if ($uid === null) { + $path = $this->encryption_base_dir . '/' . $this->encryptionModuleId . '/' . $keyId; + } else { + $path = '/' . $uid . $this->encryption_base_dir . '/' + . $this->encryptionModuleId . '/' . $uid . '.' . $keyId; + } + + return $path; + } + + /** + * read key from hard disk + * + * @param string $path to key + * @return string + */ + private function getKey($path) { + + $key = ''; + + if ($this->view->file_exists($path)) { + if (isset($this->keyCache[$path])) { + $key = $this->keyCache[$path]; + } else { + $key = $this->view->file_get_contents($path); + $this->keyCache[$path] = $key; + } + } + + return $key; + } + + /** + * write key to disk + * + * + * @param string $path path to key directory + * @param string $key key + * @return bool + */ + private function setKey($path, $key) { + $this->keySetPreparation(dirname($path)); + + $result = $this->view->file_put_contents($path, $key); + + if (is_int($result) && $result > 0) { + $this->keyCache[$path] = $key; + return true; + } + + return false; + } + + /** + * get path to key folder for a given file + * + * @param string $path path to the file, relative to data/ + * @return string + * @throws EncryptionException + * @internal param string $keyId + */ + private function getFileKeyDir($path) { + + if ($this->view->is_dir($path)) { + throw new EncryptionException('file was expected but directory was given', EncryptionException::GENERIC); + } + + list($owner, $filename) = $this->util->getUidAndFilename($path); + $filename = $this->util->stripPartialFileExtension($filename); + + // in case of system wide mount points the keys are stored directly in the data directory + if ($this->util->isSystemWideMountPoint($filename)) { + $keyPath = $this->keys_base_dir . $filename . '/'; + } else { + $keyPath = '/' . $owner . $this->keys_base_dir . $filename . '/'; + } + + return \OC\Files\Filesystem::normalizePath($keyPath . $this->encryptionModuleId . '/', false); + } + + /** + * move keys if a file was renamed + * + * @param string $source + * @param string $target + * @param string $owner + * @param bool $systemWide + */ + public function renameKeys($source, $target, $owner, $systemWide) { + if ($systemWide) { + $sourcePath = $this->keys_base_dir . $source . '/'; + $targetPath = $this->keys_base_dir . $target . '/'; + } else { + $sourcePath = '/' . $owner . $this->keys_base_dir . $source . '/'; + $targetPath = '/' . $owner . $this->keys_base_dir . $target . '/'; + } + + if ($this->view->file_exists($sourcePath)) { + $this->keySetPreparation(dirname($targetPath)); + $this->view->rename($sourcePath, $targetPath); + } + } + + /** + * Make preparations to filesystem for saving a keyfile + * + * @param string $path relative to the views root + */ + protected function keySetPreparation($path) { + // If the file resides within a subdirectory, create it + 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/lib/private/encryption/manager.php b/lib/private/encryption/manager.php new file mode 100644 index 00000000000..5164025239c --- /dev/null +++ b/lib/private/encryption/manager.php @@ -0,0 +1,170 @@ +<?php + +/** + * ownCloud + * + * @copyright (C) 2015 ownCloud, Inc. + * + * @author Bjoern Schiessle <schiessle@owncloud.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + */ + +namespace OC\Encryption; + +use OCP\Encryption\IEncryptionModule; + +class Manager implements \OCP\Encryption\IManager { + + /** @var array */ + protected $encryptionModules; + + /** @var \OCP\IConfig */ + protected $config; + + /** + * @param \OCP\IConfig $config + */ + public function __construct(\OCP\IConfig $config) { + $this->encryptionModules = array(); + $this->config = $config; + } + + /** + * Check if encryption is enabled + * + * @return bool true if enabled, false if not + */ + public function isEnabled() { + + $installed = $this->config->getSystemValue('installed', false); + if (!$installed) { + return false; + } + + $enabled = $this->config->getAppValue('core', 'encryption_enabled', 'no'); + return $enabled === 'yes'; + } + + /** + * Registers an encryption module + * + * @param IEncryptionModule $module + * @throws Exceptions\ModuleAlreadyExistsException + */ + public function registerEncryptionModule(IEncryptionModule $module) { + $id = $module->getId(); + $name = $module->getDisplayName(); + if (isset($this->encryptionModules[$id])) { + $message = 'Id "' . $id . '" already used by encryption module "' . $name . '"'; + throw new Exceptions\ModuleAlreadyExistsException($message); + + } + + $defaultEncryptionModuleId = $this->getDefaultEncryptionModuleId(); + + if (empty($defaultEncryptionModuleId)) { + $this->setDefaultEncryptionModule($id); + } + + $this->encryptionModules[$id] = $module; + } + + /** + * Unregisters an encryption module + * + * @param IEncryptionModule $module + */ + public function unregisterEncryptionModule(IEncryptionModule $module) { + unset($this->encryptionModules[$module->getId()]); + } + + /** + * get a list of all encryption modules + * + * @return IEncryptionModule[] + */ + public function getEncryptionModules() { + return $this->encryptionModules; + } + + /** + * get a specific encryption module + * + * @param string $moduleId + * @return IEncryptionModule + * @throws Exceptions\ModuleDoesNotExistsException + */ + public function getEncryptionModule($moduleId) { + if (isset($this->encryptionModules[$moduleId])) { + return $this->encryptionModules[$moduleId]; + } else { + $message = "Module with id: $moduleId does not exists."; + throw new Exceptions\ModuleDoesNotExistsException($message); + } + } + + /** + * get default encryption module + * + * @return \OCP\Encryption\IEncryptionModule + * @throws Exceptions\ModuleDoesNotExistsException + */ + public function getDefaultEncryptionModule() { + $defaultModuleId = $this->getDefaultEncryptionModuleId(); + if (!empty($defaultModuleId)) { + if (isset($this->encryptionModules[$defaultModuleId])) { + return $this->encryptionModules[$defaultModuleId]; + } else { + $message = 'Default encryption module not loaded'; + throw new Exceptions\ModuleDoesNotExistsException($message); + } + } else { + $message = 'No default encryption module defined'; + throw new Exceptions\ModuleDoesNotExistsException($message); + } + + } + + /** + * set default encryption module Id + * + * @param string $moduleId + * @return bool + */ + public function setDefaultEncryptionModule($moduleId) { + try { + $this->config->setAppValue('core', 'default_encryption_module', $moduleId); + return true; + } catch (\Exception $e) { + return false; + } + + } + + /** + * get default encryption module Id + * + * @return string + */ + protected function getDefaultEncryptionModuleId() { + try { + return $this->config->getAppValue('core', 'default_encryption_module'); + } catch (\Exception $e) { + return ''; + } + } + + +} diff --git a/lib/private/encryption/update.php b/lib/private/encryption/update.php new file mode 100644 index 00000000000..649cf0285a6 --- /dev/null +++ b/lib/private/encryption/update.php @@ -0,0 +1,111 @@ +<?php + +/** + * ownCloud + * + * @copyright (C) 2015 ownCloud, Inc. + * + * @author Bjoern Schiessle <schiessle@owncloud.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + */ + +namespace OC\Encryption; + +use \OC\Files\Mount; +use \OC\Files\View; + +/** + * update encrypted files, e.g. because a file was shared + */ +class Update { + + /** @var \OC\Files\View */ + protected $view; + + /** @var \OC\Encryption\Util */ + protected $util; + + /** @var \OC\Files\Mount\Manager */ + protected $mountManager; + + /** @var \OC\Encryption\Manager */ + protected $encryptionManager; + + /** @var string */ + protected $uid; + + /** + * + * @param \OC\Files\View $view + * @param \OC\Encryption\Util $util + * @param \OC\Files\Mount\Manager $mountManager + * @param \OC\Encryption\Manager $encryptionManager + * @param string $uid + */ + public function __construct( + View $view, + Util $util, + Mount\Manager $mountManager, + Manager $encryptionManager, + $uid + ) { + + $this->view = $view; + $this->util = $util; + $this->mountManager = $mountManager; + $this->encryptionManager = $encryptionManager; + $this->uid = $uid; + } + + public function postShared($params) { + if ($params['itemType'] === 'file' || $params['itemType'] === 'folder') { + $this->update($params['fileSource']); + } + } + + public function postUnshared($params) { + if ($params['itemType'] === 'file' || $params['itemType'] === 'folder') { + $this->update($params['fileSource']); + } + } + + /** + * update keyfiles and share keys recursively + * + * @param int $fileSource file source id + */ + private function update($fileSource) { + $path = \OC\Files\Filesystem::getPath($fileSource); + $absPath = '/' . $this->uid . '/files' . $path; + + $mount = $this->mountManager->find($path); + $mountPoint = $mount->getMountPoint(); + + // if a folder was shared, get a list of all (sub-)folders + if ($this->view->is_dir($absPath)) { + $allFiles = $this->util->getAllFiles($absPath, $mountPoint); + } else { + $allFiles = array($absPath); + } + + $encryptionModule = $this->encryptionManager->getDefaultEncryptionModule(); + + foreach ($allFiles as $path) { + $usersSharing = $this->util->getSharingUsersArray($path); + $encryptionModule->update($absPath, $usersSharing); + } + } + +}
\ No newline at end of file diff --git a/lib/private/encryption/util.php b/lib/private/encryption/util.php new file mode 100644 index 00000000000..2c6ff266841 --- /dev/null +++ b/lib/private/encryption/util.php @@ -0,0 +1,401 @@ +<?php + +/** + * ownCloud + * + * @copyright (C) 2015 ownCloud, Inc. + * + * @author Bjoern Schiessle <schiessle@owncloud.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + */ + +namespace OC\Encryption; + +use OC\Encryption\Exceptions\EncryptionHeaderToLargeException; +use OC\Encryption\Exceptions\EncryptionHeaderKeyExistsException; +use OCP\Encryption\IEncryptionModule; + +class Util { + + const HEADER_START = 'HBEGIN'; + const HEADER_END = 'HEND'; + const HEADER_PADDING_CHAR = '-'; + + const HEADER_ENCRYPTION_MODULE_KEY = 'oc_encryption_module'; + + /** + * block size will always be 8192 for a PHP stream + * @see https://bugs.php.net/bug.php?id=21641 + * @var integer + */ + protected $headerSize = 8192; + + /** + * block size will always be 8192 for a PHP stream + * @see https://bugs.php.net/bug.php?id=21641 + * @var integer + */ + protected $blockSize = 8192; + + /** @var \OC\Files\View */ + protected $view; + + /** @var array */ + protected $ocHeaderKeys; + + /** @var \OC\User\Manager */ + protected $userManager; + + /** @var array paths excluded from encryption */ + protected $excludedPaths; + + /** + * @param \OC\Files\View $view root view + */ + public function __construct(\OC\Files\View $view, \OC\User\Manager $userManager) { + $this->ocHeaderKeys = [ + self::HEADER_ENCRYPTION_MODULE_KEY + ]; + + $this->view = $view; + $this->userManager = $userManager; + + $this->excludedPaths[] = 'files_encryption'; + } + + /** + * read encryption module ID from header + * + * @param array $header + * @return string + */ + public function getEncryptionModuleId(array $header) { + $id = ''; + $encryptionModuleKey = self::HEADER_ENCRYPTION_MODULE_KEY; + + if (isset($header[$encryptionModuleKey])) { + $id = $header[$encryptionModuleKey]; + } + + return $id; + } + + /** + * read header into array + * + * @param string $header + * @return array + */ + public function readHeader($header) { + + $result = array(); + + if (substr($header, 0, strlen(self::HEADER_START)) === self::HEADER_START) { + $endAt = strpos($header, self::HEADER_END); + if ($endAt !== false) { + $header = substr($header, 0, $endAt + strlen(self::HEADER_END)); + + // +1 to not start with an ':' which would result in empty element at the beginning + $exploded = explode(':', substr($header, strlen(self::HEADER_START)+1)); + + $element = array_shift($exploded); + while ($element !== self::HEADER_END) { + $result[$element] = array_shift($exploded); + $element = array_shift($exploded); + } + } + } + + return $result; + } + + /** + * create header for encrypted file + * + * @param array $headerData + * @param IEncryptionModule $encryptionModule + * @return string + * @throws EncryptionHeaderToLargeException if header has to many arguments + * @throws EncryptionHeaderKeyExistsException if header key is already in use + */ + public function createHeader(array $headerData, IEncryptionModule $encryptionModule) { + $header = self::HEADER_START . ':' . self::HEADER_ENCRYPTION_MODULE_KEY . ':' . $encryptionModule->getId() . ':'; + foreach ($headerData as $key => $value) { + if (in_array($key, $this->ocHeaderKeys)) { + throw new EncryptionHeaderKeyExistsException('header key "'. $key . '" already reserved by ownCloud'); + } + $header .= $key . ':' . $value . ':'; + } + $header .= self::HEADER_END; + + if (strlen($header) > $this->getHeaderSize()) { + throw new EncryptionHeaderToLargeException('max header size exceeded', EncryptionException::ENCRYPTION_HEADER_TO_LARGE); + } + + $paddedHeader = str_pad($header, $this->headerSize, self::HEADER_PADDING_CHAR, STR_PAD_RIGHT); + + return $paddedHeader; + } + + /** + * Find, sanitise and format users sharing a file + * @note This wraps other methods into a portable bundle + * @param string $path path relative to current users files folder + * @return array + */ + public function getSharingUsersArray($path) { + + // Make sure that a share key is generated for the owner too + list($owner, $ownerPath) = $this->getUidAndFilename($path); + + // always add owner to the list of users with access to the file + $userIds = array($owner); + + if (!$this->isFile($ownerPath)) { + return array('users' => $userIds, 'public' => false); + } + + $ownerPath = substr($ownerPath, strlen('/files')); + $ownerPath = $this->stripPartialFileExtension($ownerPath); + + // Find out who, if anyone, is sharing the file + $result = \OCP\Share::getUsersSharingFile($ownerPath, $owner); + $userIds = \array_merge($userIds, $result['users']); + $public = $result['public'] || $result['remote']; + + // check if it is a group mount + if (\OCP\App::isEnabled("files_external")) { + $mounts = \OC_Mount_Config::getSystemMountPoints(); + foreach ($mounts as $mount) { + if ($mount['mountpoint'] == substr($ownerPath, 1, strlen($mount['mountpoint']))) { + $mountedFor = $this->getUserWithAccessToMountPoint($mount['applicable']['users'], $mount['applicable']['groups']); + $userIds = array_merge($userIds, $mountedFor); + } + } + } + + // Remove duplicate UIDs + $uniqueUserIds = array_unique($userIds); + + return array('users' => $uniqueUserIds, 'public' => $public); + } + + /** + * go recursively through a dir and collect all files and sub files. + * + * @param string $dir relative to the users files folder + * @param strinf $mountPoint + * @return array with list of files relative to the users files folder + */ + public function getAllFiles($dir, $mountPoint = '') { + $result = array(); + $dirList = array($dir); + + while ($dirList) { + $dir = array_pop($dirList); + $content = $this->view->getDirectoryContent($dir); + + foreach ($content as $c) { + // getDirectoryContent() returns the paths relative to the mount points, so we need + // to re-construct the complete path + $path = ($mountPoint !== '') ? $mountPoint . '/' . $c['path'] : $c['path']; + if ($c['type'] === 'dir') { + $dirList[] = $path; + } else { + $result[] = $path; + } + } + + } + + return $result; + } + + /** + * check if it is a file uploaded by the user stored in data/user/files + * or a metadata file + * + * @param string $path + * @return boolean + */ + protected function isFile($path) { + if (substr($path, 0, strlen('/files/')) === '/files/') { + return true; + } + return false; + } + + /** + * return size of encryption header + * + * @return integer + */ + public function getHeaderSize() { + return $this->headerSize; + } + + /** + * return size of block read by a PHP stream + * + * @return integer + */ + public function getBlockSize() { + return $this->blockSize; + } + + /** + * get the owner and the path for the owner + * + * @param string $path + * @return array + * @throws \BadMethodCallException + */ + public function getUidAndFilename($path) { + + $parts = explode('/', $path); + $uid = ''; + if (count($parts) > 2) { + $uid = $parts[1]; + } + if (!$this->userManager->userExists($uid)) { + throw new \BadMethodCallException('path needs to be relative to the system wide data folder and point to a user specific file'); + } + + $pathinfo = pathinfo($path); + $partfile = false; + $parentFolder = false; + if (array_key_exists('extension', $pathinfo) && $pathinfo['extension'] === 'part') { + // if the real file exists we check this file + $filePath = $pathinfo['dirname'] . '/' . $pathinfo['filename']; + if ($this->view->file_exists($filePath)) { + $pathToCheck = $pathinfo['dirname'] . '/' . $pathinfo['filename']; + } else { // otherwise we look for the parent + $pathToCheck = $pathinfo['dirname']; + $parentFolder = true; + } + $partfile = true; + } else { + $pathToCheck = $path; + } + + $pathToCheck = substr($pathToCheck, strlen('/' . $uid)); + + $this->view->chroot('/' . $uid); + $owner = $this->view->getOwner($pathToCheck); + + // Check that UID is valid + if (!$this->userManager->userExists($owner)) { + throw new \BadMethodCallException('path needs to be relative to the system wide data folder and point to a user specific file'); + } + + \OC\Files\Filesystem::initMountPoints($owner); + + $info = $this->view->getFileInfo($pathToCheck); + $this->view->chroot('/' . $owner); + $ownerPath = $this->view->getPath($info->getId()); + $this->view->chroot('/'); + + if ($parentFolder) { + $ownerPath = $ownerPath . '/'. $pathinfo['filename']; + } + + if ($partfile) { + $ownerPath = $ownerPath . '.' . $pathinfo['extension']; + } + + return array( + $owner, + \OC\Files\Filesystem::normalizePath($ownerPath) + ); + } + + /** + * Remove .path extension from a file path + * @param string $path Path that may identify a .part file + * @return string File path without .part extension + * @note this is needed for reusing keys + */ + public function stripPartialFileExtension($path) { + $extension = pathinfo($path, PATHINFO_EXTENSION); + + if ( $extension === 'part') { + + $newLength = strlen($path) - 5; // 5 = strlen(".part") + $fPath = substr($path, 0, $newLength); + + // if path also contains a transaction id, we remove it too + $extension = pathinfo($fPath, PATHINFO_EXTENSION); + if(substr($extension, 0, 12) === 'ocTransferId') { // 12 = strlen("ocTransferId") + $newLength = strlen($fPath) - strlen($extension) -1; + $fPath = substr($fPath, 0, $newLength); + } + return $fPath; + + } else { + return $path; + } + } + + protected function getUserWithAccessToMountPoint($users, $groups) { + $result = array(); + if (in_array('all', $users)) { + $result = \OCP\User::getUsers(); + } else { + $result = array_merge($result, $users); + foreach ($groups as $group) { + $result = array_merge($result, \OC_Group::usersInGroup($group)); + } + } + + return $result; + } + + /** + * check if the file is stored on a system wide mount point + * @param string $path relative to /data/user with leading '/' + * @return boolean + */ + public function isSystemWideMountPoint($path) { + $normalizedPath = ltrim($path, '/'); + if (\OCP\App::isEnabled("files_external")) { + $mounts = \OC_Mount_Config::getSystemMountPoints(); + foreach ($mounts as $mount) { + if ($mount['mountpoint'] == substr($normalizedPath, 0, strlen($mount['mountpoint']))) { + if ($this->isMountPointApplicableToUser($mount)) { + return true; + } + } + } + } + return false; + } + + /** + * check if it is a path which is excluded by ownCloud from encryption + * + * @param string $path + * @return boolean + */ + public function isExcluded($path) { + $root = explode('/', $path, 2); + if (isset($root[0])) { + if (in_array($root[0], $this->excludedPaths)) { + return true; + } + } + return false; + } + +} |