util.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385
  1. <?php
  2. /**
  3. * Copyright (c) 2012 Sam Tuke <samtuke@owncloud.com>
  4. * This file is licensed under the Affero General Public License version 3 or
  5. * later.
  6. * See the COPYING-README file.
  7. */
  8. require_once __DIR__ . '/../../../lib/base.php';
  9. require_once __DIR__ . '/../lib/crypt.php';
  10. require_once __DIR__ . '/../lib/keymanager.php';
  11. require_once __DIR__ . '/../lib/proxy.php';
  12. require_once __DIR__ . '/../lib/stream.php';
  13. require_once __DIR__ . '/../lib/util.php';
  14. require_once __DIR__ . '/../appinfo/app.php';
  15. use OCA\Encryption;
  16. /**
  17. * Class Test_Encryption_Util
  18. */
  19. class Test_Encryption_Util extends \PHPUnit_Framework_TestCase {
  20. const TEST_ENCRYPTION_UTIL_USER1 = "test-util-user1";
  21. const TEST_ENCRYPTION_UTIL_LEGACY_USER = "test-legacy-user";
  22. public $userId;
  23. public $encryptionDir;
  24. public $publicKeyDir;
  25. public $pass;
  26. /**
  27. * @var OC_FilesystemView
  28. */
  29. public $view;
  30. public $keyfilesPath;
  31. public $publicKeyPath;
  32. public $privateKeyPath;
  33. /**
  34. * @var \OCA\Encryption\Util
  35. */
  36. public $util;
  37. public $dataShort;
  38. public $legacyEncryptedData;
  39. public $legacyEncryptedDataKey;
  40. public $legacyKey;
  41. public $stateFilesTrashbin;
  42. public static function setUpBeforeClass() {
  43. // reset backend
  44. \OC_User::clearBackends();
  45. \OC_User::useBackend('database');
  46. // Filesystem related hooks
  47. \OCA\Encryption\Helper::registerFilesystemHooks();
  48. // clear and register hooks
  49. \OC_FileProxy::clearProxies();
  50. \OC_FileProxy::register(new OCA\Encryption\Proxy());
  51. // create test user
  52. \Test_Encryption_Util::loginHelper(\Test_Encryption_Util::TEST_ENCRYPTION_UTIL_USER1, true);
  53. \Test_Encryption_Util::loginHelper(\Test_Encryption_Util::TEST_ENCRYPTION_UTIL_LEGACY_USER, true);
  54. }
  55. function setUp() {
  56. \OC_User::setUserId(\Test_Encryption_Util::TEST_ENCRYPTION_UTIL_USER1);
  57. $this->userId = \Test_Encryption_Util::TEST_ENCRYPTION_UTIL_USER1;
  58. $this->pass = \Test_Encryption_Util::TEST_ENCRYPTION_UTIL_USER1;
  59. // set content for encrypting / decrypting in tests
  60. $this->dataUrl = __DIR__ . '/../lib/crypt.php';
  61. $this->dataShort = 'hats';
  62. $this->dataLong = file_get_contents(__DIR__ . '/../lib/crypt.php');
  63. $this->legacyData = __DIR__ . '/legacy-text.txt';
  64. $this->legacyEncryptedData = __DIR__ . '/legacy-encrypted-text.txt';
  65. $this->legacyEncryptedDataKey = __DIR__ . '/encryption.key';
  66. $this->legacyKey = "30943623843030686906\0\0\0\0";
  67. $keypair = Encryption\Crypt::createKeypair();
  68. $this->genPublicKey = $keypair['publicKey'];
  69. $this->genPrivateKey = $keypair['privateKey'];
  70. $this->publicKeyDir = '/' . 'public-keys';
  71. $this->encryptionDir = '/' . $this->userId . '/' . 'files_encryption';
  72. $this->keyfilesPath = $this->encryptionDir . '/' . 'keyfiles';
  73. $this->publicKeyPath =
  74. $this->publicKeyDir . '/' . $this->userId . '.public.key'; // e.g. data/public-keys/admin.public.key
  75. $this->privateKeyPath =
  76. $this->encryptionDir . '/' . $this->userId . '.private.key'; // e.g. data/admin/admin.private.key
  77. $this->view = new \OC_FilesystemView('/');
  78. $this->util = new Encryption\Util($this->view, $this->userId);
  79. // remember files_trashbin state
  80. $this->stateFilesTrashbin = OC_App::isEnabled('files_trashbin');
  81. // we don't want to tests with app files_trashbin enabled
  82. \OC_App::disable('files_trashbin');
  83. }
  84. function tearDown() {
  85. // reset app files_trashbin
  86. if ($this->stateFilesTrashbin) {
  87. OC_App::enable('files_trashbin');
  88. }
  89. else {
  90. OC_App::disable('files_trashbin');
  91. }
  92. }
  93. public static function tearDownAfterClass() {
  94. // cleanup test user
  95. \OC_User::deleteUser(\Test_Encryption_Util::TEST_ENCRYPTION_UTIL_USER1);
  96. \OC_User::deleteUser(\Test_Encryption_Util::TEST_ENCRYPTION_UTIL_LEGACY_USER);
  97. }
  98. /**
  99. * @medium
  100. * @brief test that paths set during User construction are correct
  101. */
  102. function testKeyPaths() {
  103. $util = new Encryption\Util($this->view, $this->userId);
  104. $this->assertEquals($this->publicKeyDir, $util->getPath('publicKeyDir'));
  105. $this->assertEquals($this->encryptionDir, $util->getPath('encryptionDir'));
  106. $this->assertEquals($this->keyfilesPath, $util->getPath('keyfilesPath'));
  107. $this->assertEquals($this->publicKeyPath, $util->getPath('publicKeyPath'));
  108. $this->assertEquals($this->privateKeyPath, $util->getPath('privateKeyPath'));
  109. }
  110. /**
  111. * @medium
  112. * @brief test setup of encryption directories
  113. */
  114. function testSetupServerSide() {
  115. $this->assertEquals(true, $this->util->setupServerSide($this->pass));
  116. }
  117. /**
  118. * @medium
  119. * @brief test checking whether account is ready for encryption,
  120. */
  121. function testUserIsReady() {
  122. $this->assertEquals(true, $this->util->ready());
  123. }
  124. /**
  125. * @brief test checking whether account is not ready for encryption,
  126. */
  127. // function testUserIsNotReady() {
  128. // $this->view->unlink($this->publicKeyDir);
  129. //
  130. // $params['uid'] = $this->userId;
  131. // $params['password'] = $this->pass;
  132. // $this->assertFalse(OCA\Encryption\Hooks::login($params));
  133. //
  134. // $this->view->unlink($this->privateKeyPath);
  135. // }
  136. /**
  137. * @medium
  138. * @brief test checking whether account is not ready for encryption,
  139. */
  140. function testIsLegacyUser() {
  141. \Test_Encryption_Util::loginHelper(\Test_Encryption_Util::TEST_ENCRYPTION_UTIL_LEGACY_USER);
  142. $userView = new \OC_FilesystemView('/' . \Test_Encryption_Util::TEST_ENCRYPTION_UTIL_LEGACY_USER);
  143. // Disable encryption proxy to prevent recursive calls
  144. $proxyStatus = \OC_FileProxy::$enabled;
  145. \OC_FileProxy::$enabled = false;
  146. $encryptionKeyContent = file_get_contents($this->legacyEncryptedDataKey);
  147. $userView->file_put_contents('/encryption.key', $encryptionKeyContent);
  148. \OC_FileProxy::$enabled = $proxyStatus;
  149. $params['uid'] = \Test_Encryption_Util::TEST_ENCRYPTION_UTIL_LEGACY_USER;
  150. $params['password'] = \Test_Encryption_Util::TEST_ENCRYPTION_UTIL_LEGACY_USER;
  151. $this->setMigrationStatus(0, \Test_Encryption_Util::TEST_ENCRYPTION_UTIL_LEGACY_USER);
  152. $this->assertTrue(OCA\Encryption\Hooks::login($params));
  153. $this->assertEquals($this->legacyKey, \OC::$session->get('legacyKey'));
  154. }
  155. /**
  156. * @medium
  157. */
  158. function testRecoveryEnabledForUser() {
  159. $util = new Encryption\Util($this->view, $this->userId);
  160. // Record the value so we can return it to it's original state later
  161. $enabled = $util->recoveryEnabledForUser();
  162. $this->assertTrue($util->setRecoveryForUser(1));
  163. $this->assertEquals(1, $util->recoveryEnabledForUser());
  164. $this->assertTrue($util->setRecoveryForUser(0));
  165. $this->assertEquals(0, $util->recoveryEnabledForUser());
  166. // Return the setting to it's previous state
  167. $this->assertTrue($util->setRecoveryForUser($enabled));
  168. }
  169. /**
  170. * @medium
  171. */
  172. function testGetUidAndFilename() {
  173. \OC_User::setUserId(\Test_Encryption_Util::TEST_ENCRYPTION_UTIL_USER1);
  174. $filename = '/tmp-' . time() . '.test';
  175. // Disable encryption proxy to prevent recursive calls
  176. $proxyStatus = \OC_FileProxy::$enabled;
  177. \OC_FileProxy::$enabled = false;
  178. $this->view->file_put_contents($this->userId . '/files/' . $filename, $this->dataShort);
  179. // Re-enable proxy - our work is done
  180. \OC_FileProxy::$enabled = $proxyStatus;
  181. $util = new Encryption\Util($this->view, $this->userId);
  182. list($fileOwnerUid, $file) = $util->getUidAndFilename($filename);
  183. $this->assertEquals(\Test_Encryption_Util::TEST_ENCRYPTION_UTIL_USER1, $fileOwnerUid);
  184. $this->assertEquals($file, $filename);
  185. $this->view->unlink($this->userId . '/files/' . $filename);
  186. }
  187. /**
  188. < * @brief Test that data that is read by the crypto stream wrapper
  189. */
  190. function testGetFileSize() {
  191. \Test_Encryption_Util::loginHelper(\Test_Encryption_Util::TEST_ENCRYPTION_UTIL_USER1);
  192. $filename = 'tmp-' . time();
  193. $externalFilename = '/' . $this->userId . '/files/' . $filename;
  194. // Test for 0 byte files
  195. $problematicFileSizeData = "";
  196. $cryptedFile = $this->view->file_put_contents($externalFilename, $problematicFileSizeData);
  197. $this->assertTrue(is_int($cryptedFile));
  198. $this->assertEquals($this->util->getFileSize($externalFilename), 0);
  199. $decrypt = $this->view->file_get_contents($externalFilename);
  200. $this->assertEquals($problematicFileSizeData, $decrypt);
  201. $this->view->unlink($this->userId . '/files/' . $filename);
  202. // Test a file with 18377 bytes as in https://github.com/owncloud/mirall/issues/1009
  203. $problematicFileSizeData = str_pad("", 18377, "abc");
  204. $cryptedFile = $this->view->file_put_contents($externalFilename, $problematicFileSizeData);
  205. $this->assertTrue(is_int($cryptedFile));
  206. $this->assertEquals($this->util->getFileSize($externalFilename), 18377);
  207. $decrypt = $this->view->file_get_contents($externalFilename);
  208. $this->assertEquals($problematicFileSizeData, $decrypt);
  209. $this->view->unlink($this->userId . '/files/' . $filename);
  210. }
  211. /**
  212. * @medium
  213. */
  214. function testIsSharedPath() {
  215. $sharedPath = '/user1/files/Shared/test';
  216. $path = '/user1/files/test';
  217. $this->assertTrue($this->util->isSharedPath($sharedPath));
  218. $this->assertFalse($this->util->isSharedPath($path));
  219. }
  220. /**
  221. * @large
  222. */
  223. function testEncryptLegacyFiles() {
  224. \Test_Encryption_Util::loginHelper(\Test_Encryption_Util::TEST_ENCRYPTION_UTIL_LEGACY_USER);
  225. $userView = new \OC_FilesystemView('/' . \Test_Encryption_Util::TEST_ENCRYPTION_UTIL_LEGACY_USER);
  226. $view = new \OC_FilesystemView('/' . \Test_Encryption_Util::TEST_ENCRYPTION_UTIL_LEGACY_USER . '/files');
  227. // Disable encryption proxy to prevent recursive calls
  228. $proxyStatus = \OC_FileProxy::$enabled;
  229. \OC_FileProxy::$enabled = false;
  230. $encryptionKeyContent = file_get_contents($this->legacyEncryptedDataKey);
  231. $userView->file_put_contents('/encryption.key', $encryptionKeyContent);
  232. $legacyEncryptedData = file_get_contents($this->legacyEncryptedData);
  233. $view->mkdir('/test/');
  234. $view->mkdir('/test/subtest/');
  235. $view->file_put_contents('/test/subtest/legacy-encrypted-text.txt', $legacyEncryptedData);
  236. $fileInfo = $view->getFileInfo('/test/subtest/legacy-encrypted-text.txt');
  237. $fileInfo['encrypted'] = true;
  238. $view->putFileInfo('/test/subtest/legacy-encrypted-text.txt', $fileInfo);
  239. \OC_FileProxy::$enabled = $proxyStatus;
  240. $params['uid'] = \Test_Encryption_Util::TEST_ENCRYPTION_UTIL_LEGACY_USER;
  241. $params['password'] = \Test_Encryption_Util::TEST_ENCRYPTION_UTIL_LEGACY_USER;
  242. $util = new Encryption\Util($this->view, \Test_Encryption_Util::TEST_ENCRYPTION_UTIL_LEGACY_USER);
  243. $this->setMigrationStatus(0, \Test_Encryption_Util::TEST_ENCRYPTION_UTIL_LEGACY_USER);
  244. $this->assertTrue(OCA\Encryption\Hooks::login($params));
  245. $this->assertEquals($this->legacyKey, \OC::$session->get('legacyKey'));
  246. $files = $util->findEncFiles('/' . \Test_Encryption_Util::TEST_ENCRYPTION_UTIL_LEGACY_USER . '/files/');
  247. $this->assertTrue(is_array($files));
  248. $found = false;
  249. foreach ($files['encrypted'] as $encryptedFile) {
  250. if ($encryptedFile['name'] === 'legacy-encrypted-text.txt') {
  251. $found = true;
  252. break;
  253. }
  254. }
  255. $this->assertTrue($found);
  256. }
  257. /**
  258. * @param $user
  259. * @param bool $create
  260. * @param bool $password
  261. */
  262. public static function loginHelper($user, $create = false, $password = false) {
  263. if ($create) {
  264. \OC_User::createUser($user, $user);
  265. }
  266. if ($password === false) {
  267. $password = $user;
  268. }
  269. \OC_Util::tearDownFS();
  270. \OC_User::setUserId('');
  271. \OC\Files\Filesystem::tearDown();
  272. \OC_Util::setupFS($user);
  273. \OC_User::setUserId($user);
  274. $params['uid'] = $user;
  275. $params['password'] = $password;
  276. OCA\Encryption\Hooks::login($params);
  277. }
  278. /**
  279. * helper function to set migration status to the right value
  280. * to be able to test the migration path
  281. *
  282. * @param $status needed migration status for test
  283. * @param $user for which user the status should be set
  284. * @return boolean
  285. */
  286. private function setMigrationStatus($status, $user) {
  287. $sql = 'UPDATE `*PREFIX*encryption` SET `migration_status` = ? WHERE `uid` = ?';
  288. $args = array(
  289. $status,
  290. $user
  291. );
  292. $query = \OCP\DB::prepare($sql);
  293. if ($query->execute($args)) {
  294. return true;
  295. } else {
  296. return false;
  297. }
  298. }
  299. }