You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

515 lines
15 KiB

13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
12 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
12 years ago
  1. <?php
  2. /**
  3. * ownCloud
  4. *
  5. * @author Florin Peter
  6. * @copyright 2013 Florin Peter <owncloud@florin-peter.de>
  7. *
  8. * This library is free software; you can redistribute it and/or
  9. * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
  10. * License as published by the Free Software Foundation; either
  11. * version 3 of the License, or any later version.
  12. *
  13. * This library is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
  17. *
  18. * You should have received a copy of the GNU Affero General Public
  19. * License along with this library. If not, see <http://www.gnu.org/licenses/>.
  20. *
  21. */
  22. namespace OCA\Encryption;
  23. /**
  24. * Class to manage registration of hooks an various helper methods
  25. * @package OCA\Encryption
  26. */
  27. class Helper {
  28. private static $tmpFileMapping; // Map tmp files to files in data/user/files
  29. /**
  30. * register share related hooks
  31. *
  32. */
  33. public static function registerShareHooks() {
  34. \OCP\Util::connectHook('OCP\Share', 'pre_shared', 'OCA\Encryption\Hooks', 'preShared');
  35. \OCP\Util::connectHook('OCP\Share', 'post_shared', 'OCA\Encryption\Hooks', 'postShared');
  36. \OCP\Util::connectHook('OCP\Share', 'post_unshare', 'OCA\Encryption\Hooks', 'postUnshare');
  37. }
  38. /**
  39. * register user related hooks
  40. *
  41. */
  42. public static function registerUserHooks() {
  43. \OCP\Util::connectHook('OC_User', 'post_login', 'OCA\Encryption\Hooks', 'login');
  44. \OCP\Util::connectHook('OC_User', 'logout', 'OCA\Encryption\Hooks', 'logout');
  45. \OCP\Util::connectHook('OC_User', 'post_setPassword', 'OCA\Encryption\Hooks', 'setPassphrase');
  46. \OCP\Util::connectHook('OC_User', 'pre_setPassword', 'OCA\Encryption\Hooks', 'preSetPassphrase');
  47. \OCP\Util::connectHook('OC_User', 'post_createUser', 'OCA\Encryption\Hooks', 'postCreateUser');
  48. \OCP\Util::connectHook('OC_User', 'post_deleteUser', 'OCA\Encryption\Hooks', 'postDeleteUser');
  49. }
  50. /**
  51. * register filesystem related hooks
  52. *
  53. */
  54. public static function registerFilesystemHooks() {
  55. \OCP\Util::connectHook('OC_Filesystem', 'rename', 'OCA\Encryption\Hooks', 'preRename');
  56. \OCP\Util::connectHook('OC_Filesystem', 'post_rename', 'OCA\Encryption\Hooks', 'postRenameOrCopy');
  57. \OCP\Util::connectHook('OC_Filesystem', 'copy', 'OCA\Encryption\Hooks', 'preCopy');
  58. \OCP\Util::connectHook('OC_Filesystem', 'post_copy', 'OCA\Encryption\Hooks', 'postRenameOrCopy');
  59. \OCP\Util::connectHook('OC_Filesystem', 'post_delete', 'OCA\Encryption\Hooks', 'postDelete');
  60. \OCP\Util::connectHook('OC_Filesystem', 'delete', 'OCA\Encryption\Hooks', 'preDelete');
  61. \OCP\Util::connectHook('OC_Filesystem', 'post_umount', 'OCA\Encryption\Hooks', 'postUmount');
  62. \OCP\Util::connectHook('OC_Filesystem', 'umount', 'OCA\Encryption\Hooks', 'preUmount');
  63. }
  64. /**
  65. * register app management related hooks
  66. *
  67. */
  68. public static function registerAppHooks() {
  69. \OCP\Util::connectHook('OC_App', 'pre_disable', 'OCA\Encryption\Hooks', 'preDisable');
  70. \OCP\Util::connectHook('OC_App', 'post_disable', 'OCA\Encryption\Hooks', 'postEnable');
  71. }
  72. /**
  73. * setup user for files_encryption
  74. *
  75. * @param Util $util
  76. * @param string $password
  77. * @return bool
  78. */
  79. public static function setupUser($util, $password) {
  80. // Check files_encryption infrastructure is ready for action
  81. if (!$util->ready()) {
  82. \OCP\Util::writeLog('Encryption library', 'User account "' . $util->getUserId()
  83. . '" is not ready for encryption; configuration started', \OCP\Util::DEBUG);
  84. if (!$util->setupServerSide($password)) {
  85. return false;
  86. }
  87. }
  88. return true;
  89. }
  90. /**
  91. * enable recovery
  92. *
  93. * @param string $recoveryKeyId
  94. * @param string $recoveryPassword
  95. * @internal param \OCA\Encryption\Util $util
  96. * @internal param string $password
  97. * @return bool
  98. */
  99. public static function adminEnableRecovery($recoveryKeyId, $recoveryPassword) {
  100. $view = new \OC\Files\View('/');
  101. $appConfig = \OC::$server->getAppConfig();
  102. if ($recoveryKeyId === null) {
  103. $recoveryKeyId = 'recovery_' . substr(md5(time()), 0, 8);
  104. $appConfig->setValue('files_encryption', 'recoveryKeyId', $recoveryKeyId);
  105. }
  106. if (!$view->is_dir('/owncloud_private_key')) {
  107. $view->mkdir('/owncloud_private_key');
  108. }
  109. if (
  110. (!$view->file_exists("/public-keys/" . $recoveryKeyId . ".public.key")
  111. || !$view->file_exists("/owncloud_private_key/" . $recoveryKeyId . ".private.key"))
  112. ) {
  113. $keypair = \OCA\Encryption\Crypt::createKeypair();
  114. \OC_FileProxy::$enabled = false;
  115. // Save public key
  116. if (!$view->is_dir('/public-keys')) {
  117. $view->mkdir('/public-keys');
  118. }
  119. $view->file_put_contents('/public-keys/' . $recoveryKeyId . '.public.key', $keypair['publicKey']);
  120. $cipher = \OCA\Encryption\Helper::getCipher();
  121. $encryptedKey = \OCA\Encryption\Crypt::symmetricEncryptFileContent($keypair['privateKey'], $recoveryPassword, $cipher);
  122. if ($encryptedKey) {
  123. Keymanager::setPrivateSystemKey($encryptedKey, $recoveryKeyId . '.private.key');
  124. // Set recoveryAdmin as enabled
  125. $appConfig->setValue('files_encryption', 'recoveryAdminEnabled', 1);
  126. $return = true;
  127. }
  128. \OC_FileProxy::$enabled = true;
  129. } else { // get recovery key and check the password
  130. $util = new \OCA\Encryption\Util(new \OC\Files\View('/'), \OCP\User::getUser());
  131. $return = $util->checkRecoveryPassword($recoveryPassword);
  132. if ($return) {
  133. $appConfig->setValue('files_encryption', 'recoveryAdminEnabled', 1);
  134. }
  135. }
  136. return $return;
  137. }
  138. /**
  139. * Check if a path is a .part file
  140. * @param string $path Path that may identify a .part file
  141. * @return bool
  142. */
  143. public static function isPartialFilePath($path) {
  144. $extension = pathinfo($path, PATHINFO_EXTENSION);
  145. if ( $extension === 'part') {
  146. return true;
  147. } else {
  148. return false;
  149. }
  150. }
  151. /**
  152. * Remove .path extension from a file path
  153. * @param string $path Path that may identify a .part file
  154. * @return string File path without .part extension
  155. * @note this is needed for reusing keys
  156. */
  157. public static function stripPartialFileExtension($path) {
  158. $extension = pathinfo($path, PATHINFO_EXTENSION);
  159. if ( $extension === 'part') {
  160. $newLength = strlen($path) - 5; // 5 = strlen(".part") = strlen(".etmp")
  161. $fPath = substr($path, 0, $newLength);
  162. // if path also contains a transaction id, we remove it too
  163. $extension = pathinfo($fPath, PATHINFO_EXTENSION);
  164. if(substr($extension, 0, 12) === 'ocTransferId') { // 12 = strlen("ocTransferId")
  165. $newLength = strlen($fPath) - strlen($extension) -1;
  166. $fPath = substr($fPath, 0, $newLength);
  167. }
  168. return $fPath;
  169. } else {
  170. return $path;
  171. }
  172. }
  173. /**
  174. * disable recovery
  175. *
  176. * @param string $recoveryPassword
  177. * @return bool
  178. */
  179. public static function adminDisableRecovery($recoveryPassword) {
  180. $util = new Util(new \OC\Files\View('/'), \OCP\User::getUser());
  181. $return = $util->checkRecoveryPassword($recoveryPassword);
  182. if ($return) {
  183. // Set recoveryAdmin as disabled
  184. \OC::$server->getAppConfig()->setValue('files_encryption', 'recoveryAdminEnabled', 0);
  185. }
  186. return $return;
  187. }
  188. /**
  189. * checks if access is public/anonymous user
  190. * @return bool
  191. */
  192. public static function isPublicAccess() {
  193. if (\OCP\User::getUser() === false) {
  194. return true;
  195. } else {
  196. return false;
  197. }
  198. }
  199. /**
  200. * Format a path to be relative to the /user/files/ directory
  201. * @param string $path the absolute path
  202. * @return string e.g. turns '/admin/files/test.txt' into 'test.txt'
  203. */
  204. public static function stripUserFilesPath($path) {
  205. $trimmed = ltrim($path, '/');
  206. $split = explode('/', $trimmed);
  207. // it is not a file relative to data/user/files
  208. if (count($split) < 3 || $split[1] !== 'files') {
  209. return false;
  210. }
  211. $sliced = array_slice($split, 2);
  212. $relPath = implode('/', $sliced);
  213. return $relPath;
  214. }
  215. /**
  216. * try to get the user from the path if no user is logged in
  217. * @param string $path
  218. * @return mixed user or false if we couldn't determine a user
  219. */
  220. public static function getUser($path) {
  221. $user = \OCP\User::getUser();
  222. // if we are logged in, then we return the userid
  223. if ($user) {
  224. return $user;
  225. }
  226. // if no user is logged in we try to access a publicly shared files.
  227. // In this case we need to try to get the user from the path
  228. $trimmed = ltrim($path, '/');
  229. $split = explode('/', $trimmed);
  230. // it is not a file relative to data/user/files
  231. if (count($split) < 2 || ($split[1] !== 'files' && $split[1] !== 'cache')) {
  232. return false;
  233. }
  234. $user = $split[0];
  235. if (\OCP\User::userExists($user)) {
  236. return $user;
  237. }
  238. return false;
  239. }
  240. /**
  241. * get path to the corresponding file in data/user/files if path points
  242. * to a version or to a file in cache
  243. * @param string $path path to a version or a file in the trash
  244. * @return string path to corresponding file relative to data/user/files
  245. */
  246. public static function getPathToRealFile($path) {
  247. $trimmed = ltrim($path, '/');
  248. $split = explode('/', $trimmed);
  249. $result = false;
  250. if (count($split) >= 3 && ($split[1] === "files_versions" || $split[1] === 'cache')) {
  251. $sliced = array_slice($split, 2);
  252. $result = implode('/', $sliced);
  253. if ($split[1] === "files_versions") {
  254. // we skip user/files
  255. $sliced = array_slice($split, 2);
  256. $relPath = implode('/', $sliced);
  257. //remove the last .v
  258. $result = substr($relPath, 0, strrpos($relPath, '.v'));
  259. }
  260. if ($split[1] === "cache") {
  261. // we skip /user/cache/transactionId
  262. $sliced = array_slice($split, 3);
  263. $result = implode('/', $sliced);
  264. //prepare the folders
  265. self::mkdirr($path, new \OC\Files\View('/'));
  266. }
  267. }
  268. return $result;
  269. }
  270. /**
  271. * create directory recursively
  272. * @param string $path
  273. * @param \OC\Files\View $view
  274. */
  275. public static function mkdirr($path, $view) {
  276. $dirname = \OC\Files\Filesystem::normalizePath(dirname($path));
  277. $dirParts = explode('/', $dirname);
  278. $dir = "";
  279. foreach ($dirParts as $part) {
  280. $dir = $dir . '/' . $part;
  281. if (!$view->file_exists($dir)) {
  282. $view->mkdir($dir);
  283. }
  284. }
  285. }
  286. /**
  287. * redirect to a error page
  288. * @param Session $session
  289. */
  290. public static function redirectToErrorPage($session, $errorCode = null) {
  291. if ($errorCode === null) {
  292. $init = $session->getInitialized();
  293. switch ($init) {
  294. case \OCA\Encryption\Session::INIT_EXECUTED:
  295. $errorCode = \OCA\Encryption\Crypt::ENCRYPTION_PRIVATE_KEY_NOT_VALID_ERROR;
  296. break;
  297. case \OCA\Encryption\Session::NOT_INITIALIZED:
  298. $errorCode = \OCA\Encryption\Crypt::ENCRYPTION_NOT_INITIALIZED_ERROR;
  299. break;
  300. default:
  301. $errorCode = \OCA\Encryption\Crypt::ENCRYPTION_UNKNOWN_ERROR;
  302. }
  303. }
  304. $location = \OC_Helper::linkToAbsolute('apps/files_encryption/files', 'error.php');
  305. $post = 0;
  306. if(count($_POST) > 0) {
  307. $post = 1;
  308. }
  309. if(defined('PHPUNIT_RUN') and PHPUNIT_RUN) {
  310. throw new \Exception("Encryption error: $errorCode");
  311. }
  312. header('Location: ' . $location . '?p=' . $post . '&errorCode=' . $errorCode);
  313. exit();
  314. }
  315. /**
  316. * check requirements for encryption app.
  317. * @return bool true if requirements are met
  318. */
  319. public static function checkRequirements() {
  320. $result = true;
  321. //openssl extension needs to be loaded
  322. $result &= extension_loaded("openssl");
  323. // we need php >= 5.3.3
  324. $result &= version_compare(phpversion(), '5.3.3', '>=');
  325. return (bool) $result;
  326. }
  327. /**
  328. * check some common errors if the server isn't configured properly for encryption
  329. * @return bool true if configuration seems to be OK
  330. */
  331. public static function checkConfiguration() {
  332. if(self::getOpenSSLPkey()) {
  333. return true;
  334. } else {
  335. while ($msg = openssl_error_string()) {
  336. \OCP\Util::writeLog('Encryption library', 'openssl_pkey_new() fails: ' . $msg, \OCP\Util::ERROR);
  337. }
  338. return false;
  339. }
  340. }
  341. /**
  342. * Create an openssl pkey with config-supplied settings
  343. * WARNING: This initializes a new private keypair, which is computationally expensive
  344. * @return resource The pkey resource created
  345. */
  346. public static function getOpenSSLPkey() {
  347. return openssl_pkey_new(self::getOpenSSLConfig());
  348. }
  349. /**
  350. * Return an array of OpenSSL config options, default + config
  351. * Used for multiple OpenSSL functions
  352. * @return array The combined defaults and config settings
  353. */
  354. public static function getOpenSSLConfig() {
  355. $config = array('private_key_bits' => 4096);
  356. $config = array_merge(\OCP\Config::getSystemValue('openssl', array()), $config);
  357. return $config;
  358. }
  359. /**
  360. * find all share keys for a given file
  361. *
  362. * @param string $filePath path to the file name relative to the user's files dir
  363. * for example "subdir/filename.txt"
  364. * @param string $shareKeyPath share key prefix path relative to the user's data dir
  365. * for example "user1/files_encryption/share-keys/subdir/filename.txt"
  366. * @param \OC\Files\View $rootView root view, relative to data/
  367. * @return array list of share key files, path relative to data/$user
  368. */
  369. public static function findShareKeys($filePath, $shareKeyPath, $rootView) {
  370. $result = array();
  371. $user = \OCP\User::getUser();
  372. $util = new Util($rootView, $user);
  373. // get current sharing state
  374. $sharingEnabled = \OCP\Share::isEnabled();
  375. // get users sharing this file
  376. $usersSharing = $util->getSharingUsersArray($sharingEnabled, $filePath);
  377. $pathinfo = pathinfo($shareKeyPath);
  378. $baseDir = $pathinfo['dirname'] . '/';
  379. $fileName = $pathinfo['basename'];
  380. foreach ($usersSharing as $user) {
  381. $keyName = $fileName . '.' . $user . '.shareKey';
  382. if ($rootView->file_exists($baseDir . $keyName)) {
  383. $result[] = $baseDir . $keyName;
  384. } else {
  385. \OC_Log::write(
  386. 'Encryption library',
  387. 'No share key found for user "' . $user . '" for file "' . $pathOld . '"',
  388. \OC_Log::WARN
  389. );
  390. }
  391. }
  392. return $result;
  393. }
  394. /**
  395. * remember from which file the tmp file (getLocalFile() call) was created
  396. * @param string $tmpFile path of tmp file
  397. * @param string $originalFile path of the original file relative to data/
  398. */
  399. public static function addTmpFileToMapper($tmpFile, $originalFile) {
  400. self::$tmpFileMapping[$tmpFile] = $originalFile;
  401. }
  402. /**
  403. * get the path of the original file
  404. * @param string $tmpFile path of the tmp file
  405. * @return string|false path of the original file or false
  406. */
  407. public static function getPathFromTmpFile($tmpFile) {
  408. if (isset(self::$tmpFileMapping[$tmpFile])) {
  409. return self::$tmpFileMapping[$tmpFile];
  410. }
  411. return false;
  412. }
  413. /**
  414. * read the cipher used for encryption from the config.php
  415. *
  416. * @return string
  417. */
  418. public static function getCipher() {
  419. $cipher = \OCP\Config::getSystemValue('cipher', Crypt::DEFAULT_CIPHER);
  420. if ($cipher !== 'AES-256-CFB' && $cipher !== 'AES-128-CFB') {
  421. \OCP\Util::writeLog('files_encryption',
  422. 'wrong cipher defined in config.php, only AES-128-CFB and AES-256-CFB is supported. Fall back ' . Crypt::DEFAULT_CIPHER,
  423. \OCP\Util::WARN);
  424. $cipher = Crypt::DEFAULT_CIPHER;
  425. }
  426. return $cipher;
  427. }
  428. }