From 429cab1d3f64d0b5c61e16bfb7a1894bdc410827 Mon Sep 17 00:00:00 2001 From: Bjoern Schiessle Date: Tue, 17 Mar 2015 13:11:38 +0100 Subject: [PATCH] fix keystorage and add unit tests --- lib/private/encryption/keys/factory.php | 52 +++++ .../{keystorage.php => keys/storage.php} | 81 +++---- lib/private/encryption/util.php | 74 +++++- lib/private/server.php | 12 +- .../{ikeystorage.php => keys/istorage.php} | 10 +- tests/lib/encryption/keys/storage.php | 211 ++++++++++++++++++ 6 files changed, 374 insertions(+), 66 deletions(-) create mode 100644 lib/private/encryption/keys/factory.php rename lib/private/encryption/{keystorage.php => keys/storage.php} (71%) rename lib/public/encryption/{ikeystorage.php => keys/istorage.php} (92%) create mode 100644 tests/lib/encryption/keys/storage.php diff --git a/lib/private/encryption/keys/factory.php b/lib/private/encryption/keys/factory.php new file mode 100644 index 000000000000..a214b238615c --- /dev/null +++ b/lib/private/encryption/keys/factory.php @@ -0,0 +1,52 @@ + + * + * 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 . + */ + +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/keystorage.php b/lib/private/encryption/keys/storage.php similarity index 71% rename from lib/private/encryption/keystorage.php rename to lib/private/encryption/keys/storage.php index fbc427edf0f1..fba86e1737cb 100644 --- a/lib/private/encryption/keystorage.php +++ b/lib/private/encryption/keys/storage.php @@ -21,13 +21,13 @@ * License along with this library. If not, see . */ -namespace OC\Encryption; +namespace OC\Encryption\Keys; use OC\Encryption\Util; use OC\Files\View; use OCA\Files_Encryption\Exception\EncryptionException; -class KeyStorage implements \OCP\Encryption\IKeyStorage { +class Storage implements \OCP\Encryption\Keys\IStorage { /** @var View */ private $view; @@ -36,18 +36,26 @@ class KeyStorage implements \OCP\Encryption\IKeyStorage { private $util; // 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 $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(View $view, 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'; } /** @@ -59,7 +67,8 @@ public function __construct(View $view, Util $util) { * @return mixed key */ public function getUserKey($uid, $keyId) { - $path = '/' . $uid . self::$encryption_base_dir . '/' . $uid . '.' . $keyId; + $path = '/' . $uid . $this->encryption_base_dir . '/' + . $this->encryptionModuleId . '/' . $uid . '.' . $keyId; return $this->getKey($path); } @@ -85,7 +94,7 @@ public function getFileKey($path, $keyId) { * @return mixed key */ public function getSystemUserKey($keyId) { - $path = '/' . self::$encryption_base_dir . '/' . $keyId; + $path = $this->encryption_base_dir . '/' . $this->encryptionModuleId . '/' . $keyId; return $this->getKey($path); } @@ -97,7 +106,8 @@ public function getSystemUserKey($keyId) { * @param mixed $key */ public function setUserKey($uid, $keyId, $key) { - $path = '/' . $uid . self::$encryption_base_dir . '/' . $uid . '.' . $keyId; + $path = '/' . $uid . $this->encryption_base_dir . '/' + . $this->encryptionModuleId . '/' . $uid . '.' . $keyId; return $this->setKey($path, $key); } @@ -123,7 +133,8 @@ public function setFileKey($path, $keyId, $key) { * @return mixed key */ public function setSystemUserKey($keyId, $key) { - $path = '/' . self::$encryption_base_dir . '/' . $keyId; + $path = $this->encryption_base_dir . '/' + . $this->encryptionModuleId . '/' . $keyId; return $this->setKey($path, $key); } @@ -138,18 +149,13 @@ private function getKey($path) { $key = ''; - if (isset($this->keyCache[$path])) { - $key = $this->keyCache[$path]; - } else { - - /** @var \OCP\Files\Storage $storage */ - list($storage, $internalPath) = $this->view->resolvePath($path); - - if ($storage->file_exists($internalPath)) { - $key = $storage->file_get_contents($internalPath); + 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; @@ -166,9 +172,7 @@ private function getKey($path) { private function setKey($path, $key) { $this->keySetPreparation(dirname($path)); - /** @var \OCP\Files\Storage $storage */ - list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($path); - $result = $storage->file_put_contents($internalPath, $key); + $result = $this->view->file_put_contents($path, $key); if (is_int($result) && $result > 0) { $this->keyCache[$path] = $key; @@ -181,32 +185,28 @@ private function setKey($path, $key) { /** * get path to key folder for a given file * - * @param string $path path to the file, relative to the users file directory + * @param string $path path to the file, relative to data/ * @return string * @throws EncryptionException * @internal param string $keyId */ private function getFileKeyDir($path) { - // - // TODO: NO DEPRICATED API !!! - // - if ($this->view->is_dir('/' . \OCP\User::getUser() . '/' . $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); - $filePath_f = ltrim($filename, '/'); // in case of system wide mount points the keys are stored directly in the data directory if ($this->util->isSystemWideMountPoint($filename)) { - $keyPath = self::$keys_base_dir . $filePath_f . '/'; + $keyPath = $this->keys_base_dir . $filename . '/'; } else { - $keyPath = '/' . $owner . self::$keys_base_dir . $filePath_f . '/'; + $keyPath = '/' . $owner . $this->keys_base_dir . $filename . '/'; } - return $keyPath; + return \OC\Files\Filesystem::normalizePath($keyPath . $this->encryptionModuleId . '/', false); } /** @@ -228,23 +228,4 @@ protected function keySetPreparation($path) { } } - /** - * Check if encryption system is ready to begin encrypting - * all the things - * - * @return bool - */ - public function ready() { - $paths = [ - self::$encryption_base_dir, - self::$keys_base_dir - ]; - foreach ($paths as $path) { - if (!$this->view->file_exists($path)) { - return false; - } - } - return true; - } - } diff --git a/lib/private/encryption/util.php b/lib/private/encryption/util.php index 9fae8ebd427a..b7bf26e76c10 100644 --- a/lib/private/encryption/util.php +++ b/lib/private/encryption/util.php @@ -49,13 +49,25 @@ class Util { */ protected $blockSize = 8192; - /** $var array */ + /** @var \OC\Files\View */ + protected $view; + + /** @var array */ protected $ocHeaderKeys; - public function __construct() { + /** @var \OC\User\Manager */ + protected $userManager; + + /** + * @param \OC\Files\View $view root view + */ + public function __construct(\OC\Files\View $view, \OC\User\Manager $userManager) { $this->ocHeaderKeys = [ self::HEADER_ENCRYPTION_MODULE => self::HEADER_ENCRYPTION_MODULE_KEY ]; + + $this->view = $view; + $this->userManager = $userManager; } /** @@ -186,8 +198,62 @@ public function getBlockSize() { return $this->blockSize; } - public function getUidAndFilename($filename) { - // TODO find a better implementation than in the current encyption app + public function getUidAndFilename($path) { + + $parts = explode('/', $path); + 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 (!\OCP\User::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) + ); } /** diff --git a/lib/private/server.php b/lib/private/server.php index d0f22b482c9d..456b06cace0b 100644 --- a/lib/private/server.php +++ b/lib/private/server.php @@ -48,8 +48,8 @@ function __construct($webRoot) { return new Encryption\Manager($c->getConfig()); }); - $this->registerService('EncryptionKeyStorage', function ($c) { - return new Encryption\KeyStorage(new \OC\Files\View(), new \OC\Encryption\Util()); + $this->registerService('EncryptionKeyStorageFactory', function ($c) { + return new Encryption\Keys\Factory(); }); $this->registerService('PreviewManager', function ($c) { @@ -338,10 +338,14 @@ function getEncryptionManager() { } /** + * @param string $encryptionModuleId encryption module ID + * * @return \OCP\Encryption\IKeyStorage */ - function getEncryptionKeyStorage() { - return $this->query('EncryptionKeyStorage'); + function getEncryptionKeyStorage($encryptionModuleId) { + $view = new \OC\Files\View(); + $util = new \OC\Encryption\Util($view, new \OC\User\Manager()); + return $this->query('EncryptionKeyStorageFactory')->get($encryptionModuleId, $view, $util); } /** diff --git a/lib/public/encryption/ikeystorage.php b/lib/public/encryption/keys/istorage.php similarity index 92% rename from lib/public/encryption/ikeystorage.php rename to lib/public/encryption/keys/istorage.php index 2ab5048709e4..e77d593df83f 100644 --- a/lib/public/encryption/ikeystorage.php +++ b/lib/public/encryption/keys/istorage.php @@ -21,9 +21,9 @@ * License along with this library. If not, see . */ -namespace OCP\Encryption; +namespace OCP\Encryption\Keys; -interface IKeyStorage { +interface IStorage { /** * get user specific key @@ -84,10 +84,4 @@ public function setFileKey($path, $keyId, $key); */ public function setSystemUserKey($keyId, $key); - /** - * Return if encryption is setup and ready encrypt things - * - * @return bool - */ - public function ready(); } diff --git a/tests/lib/encryption/keys/storage.php b/tests/lib/encryption/keys/storage.php new file mode 100644 index 000000000000..12f4aaf04a48 --- /dev/null +++ b/tests/lib/encryption/keys/storage.php @@ -0,0 +1,211 @@ + + * + * 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 . + */ + +namespace Test\Encryption\Keys; + +use OC\Encryption\Keys\Storage; +use Test\TestCase; + +class StorageTest extends TestCase { + + /** @var \PHPUnit_Framework_MockObject_MockObject */ + protected $util; + + /** @var \PHPUnit_Framework_MockObject_MockObject */ + protected $view; + + public function setUp() { + parent::setUp(); + + $this->util = $this->getMockBuilder('OC\Encryption\Util') + ->disableOriginalConstructor() + ->getMock(); + + $this->view = $this->getMockBuilder('OC\Files\View') + ->disableOriginalConstructor() + ->getMock(); + + } + + public function testSetFileKey() { + $this->util->expects($this->any()) + ->method('getUidAndFilename') + ->willReturn(array('user1', '/files/foo.txt')); + $this->util->expects($this->any()) + ->method('stripPartialFileExtension') + ->willReturnArgument(0); + $this->util->expects($this->any()) + ->method('isSystemWideMountPoint') + ->willReturn(false); + $this->view->expects($this->once()) + ->method('file_put_contents') + ->with($this->equalTo('/user1/files_encryption/keys/files/foo.txt/encModule/fileKey'), + $this->equalTo('key')) + ->willReturn(strlen('key')); + + $storage = new Storage('encModule', $this->view, $this->util); + + $this->assertTrue( + $storage->setFileKey('user1/files/foo.txt', 'fileKey', 'key') + ); + } + + public function testGetFileKey() { + $this->util->expects($this->any()) + ->method('getUidAndFilename') + ->willReturn(array('user1', '/files/foo.txt')); + $this->util->expects($this->any()) + ->method('stripPartialFileExtension') + ->willReturnArgument(0); + $this->util->expects($this->any()) + ->method('isSystemWideMountPoint') + ->willReturn(false); + $this->view->expects($this->once()) + ->method('file_get_contents') + ->with($this->equalTo('/user1/files_encryption/keys/files/foo.txt/encModule/fileKey')) + ->willReturn('key'); + $this->view->expects($this->once()) + ->method('file_exists') + ->with($this->equalTo('/user1/files_encryption/keys/files/foo.txt/encModule/fileKey')) + ->willReturn(true); + + $storage = new Storage('encModule', $this->view, $this->util); + + $this->assertSame('key', + $storage->getFileKey('user1/files/foo.txt', 'fileKey') + ); + } + + public function testSetFileKeySystemWide() { + $this->util->expects($this->any()) + ->method('getUidAndFilename') + ->willReturn(array('user1', '/files/foo.txt')); + $this->util->expects($this->any()) + ->method('isSystemWideMountPoint') + ->willReturn(true); + $this->util->expects($this->any()) + ->method('stripPartialFileExtension') + ->willReturnArgument(0); + $this->view->expects($this->once()) + ->method('file_put_contents') + ->with($this->equalTo('/files_encryption/keys/files/foo.txt/encModule/fileKey'), + $this->equalTo('key')) + ->willReturn(strlen('key')); + + $storage = new Storage('encModule', $this->view, $this->util); + + $this->assertTrue( + $storage->setFileKey('user1/files/foo.txt', 'fileKey', 'key') + ); + } + + public function testGetFileKeySystemWide() { + $this->util->expects($this->any()) + ->method('getUidAndFilename') + ->willReturn(array('user1', '/files/foo.txt')); + $this->util->expects($this->any()) + ->method('stripPartialFileExtension') + ->willReturnArgument(0); + $this->util->expects($this->any()) + ->method('isSystemWideMountPoint') + ->willReturn(true); + $this->view->expects($this->once()) + ->method('file_get_contents') + ->with($this->equalTo('/files_encryption/keys/files/foo.txt/encModule/fileKey')) + ->willReturn('key'); + $this->view->expects($this->once()) + ->method('file_exists') + ->with($this->equalTo('/files_encryption/keys/files/foo.txt/encModule/fileKey')) + ->willReturn(true); + + $storage = new Storage('encModule', $this->view, $this->util); + + $this->assertSame('key', + $storage->getFileKey('user1/files/foo.txt', 'fileKey') + ); + } + + public function testSetSystemUserKey() { + $this->view->expects($this->once()) + ->method('file_put_contents') + ->with($this->equalTo('/files_encryption/encModule/shareKey_56884'), + $this->equalTo('key')) + ->willReturn(strlen('key')); + + $storage = new Storage('encModule', $this->view, $this->util); + + $this->assertTrue( + $storage->setSystemUserKey('shareKey_56884', 'key') + ); + } + + public function testSetUserKey() { + $this->view->expects($this->once()) + ->method('file_put_contents') + ->with($this->equalTo('/user1/files_encryption/encModule/user1.publicKey'), + $this->equalTo('key')) + ->willReturn(strlen('key')); + + $storage = new Storage('encModule', $this->view, $this->util); + + $this->assertTrue( + $storage->setUserKey('user1', 'publicKey', 'key') + ); + } + + public function testGetSystemUserKey() { + $this->view->expects($this->once()) + ->method('file_get_contents') + ->with($this->equalTo('/files_encryption/encModule/shareKey_56884')) + ->willReturn('key'); + $this->view->expects($this->once()) + ->method('file_exists') + ->with($this->equalTo('/files_encryption/encModule/shareKey_56884')) + ->willReturn(true); + + $storage = new Storage('encModule', $this->view, $this->util); + + $this->assertSame('key', + $storage->getSystemUserKey('shareKey_56884') + ); + } + + public function testGetUserKey() { + $this->view->expects($this->once()) + ->method('file_get_contents') + ->with($this->equalTo('/user1/files_encryption/encModule/user1.publicKey')) + ->willReturn('key'); + $this->view->expects($this->once()) + ->method('file_exists') + ->with($this->equalTo('/user1/files_encryption/encModule/user1.publicKey')) + ->willReturn(true); + + $storage = new Storage('encModule', $this->view, $this->util); + + $this->assertSame('key', + $storage->getUserKey('user1', 'publicKey') + ); + } + + +}