util.php 45 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743
  1. <?php
  2. /**
  3. * ownCloud
  4. *
  5. * @author Sam Tuke, Frank Karlitschek
  6. * @copyright 2012 Sam Tuke <samtuke@owncloud.com>,
  7. * Frank Karlitschek <frank@owncloud.org>
  8. *
  9. * This library is free software; you can redistribute it and/or
  10. * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
  11. * License as published by the Free Software Foundation; either
  12. * version 3 of the License, or any later version.
  13. *
  14. * This library is distributed in the hope that it will be useful,
  15. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
  18. *
  19. * You should have received a copy of the GNU Affero General Public
  20. * License along with this library. If not, see <http://www.gnu.org/licenses/>.
  21. *
  22. */
  23. namespace OCA\Encryption;
  24. /**
  25. * @brief Class for utilities relating to encrypted file storage system
  26. * @param \OC_FilesystemView $view expected to have OC '/' as root path
  27. * @param string $userId ID of the logged in user
  28. * @param int $client indicating status of client side encryption. Currently
  29. * unused, likely to become obsolete shortly
  30. */
  31. class Util {
  32. const MIGRATION_COMPLETED = 1; // migration to new encryption completed
  33. const MIGRATION_IN_PROGRESS = -1; // migration is running
  34. const MIGRATION_OPEN = 0; // user still needs to be migrated
  35. private $view; // OC_FilesystemView object for filesystem operations
  36. private $userId; // ID of the currently logged-in user
  37. private $client; // Client side encryption mode flag
  38. private $publicKeyDir; // Dir containing all public user keys
  39. private $encryptionDir; // Dir containing user's files_encryption
  40. private $keyfilesPath; // Dir containing user's keyfiles
  41. private $shareKeysPath; // Dir containing env keys for shared files
  42. private $publicKeyPath; // Path to user's public key
  43. private $privateKeyPath; // Path to user's private key
  44. private $publicShareKeyId;
  45. private $recoveryKeyId;
  46. private $isPublic;
  47. /**
  48. * @param \OC_FilesystemView $view
  49. * @param $userId
  50. * @param bool $client
  51. */
  52. public function __construct(\OC_FilesystemView $view, $userId, $client = false) {
  53. $this->view = $view;
  54. $this->userId = $userId;
  55. $this->client = $client;
  56. $this->isPublic = false;
  57. $this->publicShareKeyId = \OC_Appconfig::getValue('files_encryption', 'publicShareKeyId');
  58. $this->recoveryKeyId = \OC_Appconfig::getValue('files_encryption', 'recoveryKeyId');
  59. // if we are anonymous/public
  60. if (\OCA\Encryption\Helper::isPublicAccess()) {
  61. $this->userId = $this->publicShareKeyId;
  62. // only handle for files_sharing app
  63. if (isset($GLOBALS['app']) && $GLOBALS['app'] === 'files_sharing') {
  64. $this->userDir = '/' . $GLOBALS['fileOwner'];
  65. $this->fileFolderName = 'files';
  66. $this->userFilesDir = '/' . $GLOBALS['fileOwner'] . '/'
  67. . $this->fileFolderName; // TODO: Does this need to be user configurable?
  68. $this->publicKeyDir = '/' . 'public-keys';
  69. $this->encryptionDir = '/' . $GLOBALS['fileOwner'] . '/' . 'files_encryption';
  70. $this->keyfilesPath = $this->encryptionDir . '/' . 'keyfiles';
  71. $this->shareKeysPath = $this->encryptionDir . '/' . 'share-keys';
  72. $this->publicKeyPath =
  73. $this->publicKeyDir . '/' . $this->userId . '.public.key'; // e.g. data/public-keys/admin.public.key
  74. $this->privateKeyPath =
  75. '/owncloud_private_key/' . $this->userId . '.private.key'; // e.g. data/admin/admin.private.key
  76. $this->isPublic = true;
  77. }
  78. } else {
  79. $this->userDir = '/' . $this->userId;
  80. $this->fileFolderName = 'files';
  81. $this->userFilesDir =
  82. '/' . $this->userId . '/' . $this->fileFolderName; // TODO: Does this need to be user configurable?
  83. $this->publicKeyDir = '/' . 'public-keys';
  84. $this->encryptionDir = '/' . $this->userId . '/' . 'files_encryption';
  85. $this->keyfilesPath = $this->encryptionDir . '/' . 'keyfiles';
  86. $this->shareKeysPath = $this->encryptionDir . '/' . 'share-keys';
  87. $this->publicKeyPath =
  88. $this->publicKeyDir . '/' . $this->userId . '.public.key'; // e.g. data/public-keys/admin.public.key
  89. $this->privateKeyPath =
  90. $this->encryptionDir . '/' . $this->userId . '.private.key'; // e.g. data/admin/admin.private.key
  91. }
  92. }
  93. /**
  94. * @return bool
  95. */
  96. public function ready() {
  97. if (
  98. !$this->view->file_exists($this->encryptionDir)
  99. or !$this->view->file_exists($this->keyfilesPath)
  100. or !$this->view->file_exists($this->shareKeysPath)
  101. or !$this->view->file_exists($this->publicKeyPath)
  102. or !$this->view->file_exists($this->privateKeyPath)
  103. ) {
  104. return false;
  105. } else {
  106. return true;
  107. }
  108. }
  109. /**
  110. * @brief Sets up user folders and keys for serverside encryption
  111. *
  112. * @param string $passphrase to encrypt server-stored private key with
  113. * @return bool
  114. */
  115. public function setupServerSide($passphrase = null) {
  116. // Set directories to check / create
  117. $setUpDirs = array(
  118. $this->userDir,
  119. $this->userFilesDir,
  120. $this->publicKeyDir,
  121. $this->encryptionDir,
  122. $this->keyfilesPath,
  123. $this->shareKeysPath
  124. );
  125. // Check / create all necessary dirs
  126. foreach ($setUpDirs as $dirPath) {
  127. if (!$this->view->file_exists($dirPath)) {
  128. $this->view->mkdir($dirPath);
  129. }
  130. }
  131. // Create user keypair
  132. // we should never override a keyfile
  133. if (
  134. !$this->view->file_exists($this->publicKeyPath)
  135. && !$this->view->file_exists($this->privateKeyPath)
  136. ) {
  137. // Generate keypair
  138. $keypair = Crypt::createKeypair();
  139. if ($keypair) {
  140. \OC_FileProxy::$enabled = false;
  141. // Encrypt private key with user pwd as passphrase
  142. $encryptedPrivateKey = Crypt::symmetricEncryptFileContent($keypair['privateKey'], $passphrase);
  143. // Save key-pair
  144. if ($encryptedPrivateKey) {
  145. $this->view->file_put_contents($this->privateKeyPath, $encryptedPrivateKey);
  146. $this->view->file_put_contents($this->publicKeyPath, $keypair['publicKey']);
  147. }
  148. \OC_FileProxy::$enabled = true;
  149. }
  150. } else {
  151. // check if public-key exists but private-key is missing
  152. if ($this->view->file_exists($this->publicKeyPath) && !$this->view->file_exists($this->privateKeyPath)) {
  153. \OCP\Util::writeLog('Encryption library',
  154. 'public key exists but private key is missing for "' . $this->userId . '"', \OCP\Util::FATAL);
  155. return false;
  156. } else {
  157. if (!$this->view->file_exists($this->publicKeyPath) && $this->view->file_exists($this->privateKeyPath)
  158. ) {
  159. \OCP\Util::writeLog('Encryption library',
  160. 'private key exists but public key is missing for "' . $this->userId . '"', \OCP\Util::FATAL);
  161. return false;
  162. }
  163. }
  164. }
  165. // If there's no record for this user's encryption preferences
  166. if (false === $this->recoveryEnabledForUser()) {
  167. // create database configuration
  168. $sql = 'INSERT INTO `*PREFIX*encryption` (`uid`,`mode`,`recovery_enabled`) VALUES (?,?,?)';
  169. $args = array(
  170. $this->userId,
  171. 'server-side',
  172. 0
  173. );
  174. $query = \OCP\DB::prepare($sql);
  175. $query->execute($args);
  176. }
  177. return true;
  178. }
  179. /**
  180. * @return string
  181. */
  182. public function getPublicShareKeyId() {
  183. return $this->publicShareKeyId;
  184. }
  185. /**
  186. * @brief Check whether pwd recovery is enabled for a given user
  187. * @return bool 1 = yes, 0 = no, false = no record
  188. *
  189. * @note If records are not being returned, check for a hidden space
  190. * at the start of the uid in db
  191. */
  192. public function recoveryEnabledForUser() {
  193. $sql = 'SELECT `recovery_enabled` FROM `*PREFIX*encryption` WHERE `uid` = ?';
  194. $args = array($this->userId);
  195. $query = \OCP\DB::prepare($sql);
  196. $result = $query->execute($args);
  197. $recoveryEnabled = array();
  198. if (\OCP\DB::isError($result)) {
  199. \OCP\Util::writeLog('Encryption library', \OC_DB::getErrorMessage($result), \OCP\Util::ERROR);
  200. } else {
  201. if ($result->numRows() > 0) {
  202. $row = $result->fetchRow();
  203. if (isset($row['recovery_enabled'])) {
  204. $recoveryEnabled[] = $row['recovery_enabled'];
  205. }
  206. }
  207. }
  208. // If no record is found
  209. if (empty($recoveryEnabled)) {
  210. return false;
  211. // If a record is found
  212. } else {
  213. return $recoveryEnabled[0];
  214. }
  215. }
  216. /**
  217. * @brief Enable / disable pwd recovery for a given user
  218. * @param bool $enabled Whether to enable or disable recovery
  219. * @return bool
  220. */
  221. public function setRecoveryForUser($enabled) {
  222. $recoveryStatus = $this->recoveryEnabledForUser();
  223. // If a record for this user already exists, update it
  224. if (false === $recoveryStatus) {
  225. $sql = 'INSERT INTO `*PREFIX*encryption` (`uid`,`mode`,`recovery_enabled`) VALUES (?,?,?)';
  226. $args = array(
  227. $this->userId,
  228. 'server-side',
  229. $enabled
  230. );
  231. // Create a new record instead
  232. } else {
  233. $sql = 'UPDATE `*PREFIX*encryption` SET `recovery_enabled` = ? WHERE `uid` = ?';
  234. $args = array(
  235. $enabled,
  236. $this->userId
  237. );
  238. }
  239. return is_numeric(\OC_DB::executeAudited($sql, $args));
  240. }
  241. /**
  242. * @brief Find all files and their encryption status within a directory
  243. * @param string $directory The path of the parent directory to search
  244. * @param bool $found the founded files if called again
  245. * @return mixed false if 0 found, array on success. Keys: name, path
  246. * @note $directory needs to be a path relative to OC data dir. e.g.
  247. * /admin/files NOT /backup OR /home/www/oc/data/admin/files
  248. */
  249. public function findEncFiles($directory, &$found = false) {
  250. // Disable proxy - we don't want files to be decrypted before
  251. // we handle them
  252. \OC_FileProxy::$enabled = false;
  253. if ($found === false) {
  254. $found = array(
  255. 'plain' => array(),
  256. 'encrypted' => array(),
  257. 'legacy' => array()
  258. );
  259. }
  260. if (
  261. $this->view->is_dir($directory)
  262. && $handle = $this->view->opendir($directory)
  263. ) {
  264. while (false !== ($file = readdir($handle))) {
  265. if (
  266. $file !== "."
  267. && $file !== ".."
  268. ) {
  269. $filePath = $directory . '/' . $this->view->getRelativePath('/' . $file);
  270. $relPath = \OCA\Encryption\Helper::stripUserFilesPath($filePath);
  271. // If the path is a directory, search
  272. // its contents
  273. if ($this->view->is_dir($filePath)) {
  274. $this->findEncFiles($filePath, $found);
  275. // If the path is a file, determine
  276. // its encryption status
  277. } elseif ($this->view->is_file($filePath)) {
  278. // Disable proxies again, some-
  279. // where they got re-enabled :/
  280. \OC_FileProxy::$enabled = false;
  281. $isEncryptedPath = $this->isEncryptedPath($filePath);
  282. // If the file is encrypted
  283. // NOTE: If the userId is
  284. // empty or not set, file will
  285. // detected as plain
  286. // NOTE: This is inefficient;
  287. // scanning every file like this
  288. // will eat server resources :(
  289. if (
  290. Keymanager::getFileKey($this->view, $this->userId, $relPath)
  291. && $isEncryptedPath
  292. ) {
  293. $found['encrypted'][] = array(
  294. 'name' => $file,
  295. 'path' => $filePath
  296. );
  297. // If the file uses old
  298. // encryption system
  299. } elseif (Crypt::isLegacyEncryptedContent($isEncryptedPath, $relPath)) {
  300. $found['legacy'][] = array(
  301. 'name' => $file,
  302. 'path' => $filePath
  303. );
  304. // If the file is not encrypted
  305. } else {
  306. $found['plain'][] = array(
  307. 'name' => $file,
  308. 'path' => $relPath
  309. );
  310. }
  311. }
  312. }
  313. }
  314. \OC_FileProxy::$enabled = true;
  315. if (empty($found)) {
  316. return false;
  317. } else {
  318. return $found;
  319. }
  320. }
  321. \OC_FileProxy::$enabled = true;
  322. return false;
  323. }
  324. /**
  325. * @brief Fetch the last lines of a file efficiently
  326. * @note Safe to use on large files; does not read entire file to memory
  327. * @note Derivative of http://tekkie.flashbit.net/php/tail-functionality-in-php
  328. */
  329. public function tail($filename, $numLines) {
  330. \OC_FileProxy::$enabled = false;
  331. $text = '';
  332. $pos = -1;
  333. $handle = $this->view->fopen($filename, 'r');
  334. while ($numLines > 0) {
  335. --$pos;
  336. if (fseek($handle, $pos, SEEK_END) !== 0) {
  337. rewind($handle);
  338. $numLines = 0;
  339. } elseif (fgetc($handle) === "\n") {
  340. --$numLines;
  341. }
  342. $block_size = (-$pos) % 8192;
  343. if ($block_size === 0 || $numLines === 0) {
  344. $text = fread($handle, ($block_size === 0 ? 8192 : $block_size)) . $text;
  345. }
  346. }
  347. fclose($handle);
  348. \OC_FileProxy::$enabled = true;
  349. return $text;
  350. }
  351. /**
  352. * @brief Check if a given path identifies an encrypted file
  353. * @param string $path
  354. * @return boolean
  355. */
  356. public function isEncryptedPath($path) {
  357. // Disable encryption proxy so data retrieved is in its
  358. // original form
  359. $proxyStatus = \OC_FileProxy::$enabled;
  360. \OC_FileProxy::$enabled = false;
  361. // we only need 24 byte from the last chunk
  362. $data = '';
  363. $handle = $this->view->fopen($path, 'r');
  364. if (is_resource($handle) && !fseek($handle, -24, SEEK_END)) {
  365. $data = fgets($handle);
  366. }
  367. // re-enable proxy
  368. \OC_FileProxy::$enabled = $proxyStatus;
  369. return Crypt::isCatfileContent($data);
  370. }
  371. /**
  372. * @brief get the file size of the unencrypted file
  373. * @param string $path absolute path
  374. * @return bool
  375. */
  376. public function getFileSize($path) {
  377. $result = 0;
  378. // Disable encryption proxy to prevent recursive calls
  379. $proxyStatus = \OC_FileProxy::$enabled;
  380. \OC_FileProxy::$enabled = false;
  381. // split the path parts
  382. $pathParts = explode('/', $path);
  383. if (isset($pathParts[2]) && $pathParts[2] === 'files' && $this->view->file_exists($path)
  384. && $this->isEncryptedPath($path)
  385. ) {
  386. // get the size from filesystem
  387. $fullPath = $this->view->getLocalFile($path);
  388. $size = filesize($fullPath);
  389. // calculate last chunk nr
  390. $lastChunkNr = floor($size / 8192);
  391. // open stream
  392. $stream = fopen('crypt://' . $path, "r");
  393. if (is_resource($stream)) {
  394. // calculate last chunk position
  395. $lastChunckPos = ($lastChunkNr * 8192);
  396. // seek to end
  397. fseek($stream, $lastChunckPos);
  398. // get the content of the last chunk
  399. $lastChunkContent = fread($stream, 8192);
  400. // calc the real file size with the size of the last chunk
  401. $realSize = (($lastChunkNr * 6126) + strlen($lastChunkContent));
  402. // store file size
  403. $result = $realSize;
  404. }
  405. }
  406. \OC_FileProxy::$enabled = $proxyStatus;
  407. return $result;
  408. }
  409. /**
  410. * @brief fix the file size of the encrypted file
  411. * @param string $path absolute path
  412. * @return boolean true / false if file is encrypted
  413. */
  414. public function fixFileSize($path) {
  415. $result = false;
  416. // Disable encryption proxy to prevent recursive calls
  417. $proxyStatus = \OC_FileProxy::$enabled;
  418. \OC_FileProxy::$enabled = false;
  419. $realSize = $this->getFileSize($path);
  420. if ($realSize > 0) {
  421. $cached = $this->view->getFileInfo($path);
  422. $cached['encrypted'] = true;
  423. // set the size
  424. $cached['unencrypted_size'] = $realSize;
  425. // put file info
  426. $this->view->putFileInfo($path, $cached);
  427. $result = true;
  428. }
  429. \OC_FileProxy::$enabled = $proxyStatus;
  430. return $result;
  431. }
  432. /**
  433. * @param $path
  434. * @return bool
  435. */
  436. public function isSharedPath($path) {
  437. $trimmed = ltrim($path, '/');
  438. $split = explode('/', $trimmed);
  439. if (isset($split[2]) && $split[2] === 'Shared') {
  440. return true;
  441. } else {
  442. return false;
  443. }
  444. }
  445. /**
  446. * @brief encrypt versions from given file
  447. * @param array $filelist list of encrypted files, relative to data/user/files
  448. * @return boolean
  449. */
  450. private function encryptVersions($filelist) {
  451. $successful = true;
  452. if (\OCP\App::isEnabled('files_versions')) {
  453. foreach ($filelist as $filename) {
  454. $versions = \OCA\Files_Versions\Storage::getVersions($this->userId, $filename);
  455. foreach ($versions as $version) {
  456. $path = '/' . $this->userId . '/files_versions/' . $version['path'] . '.v' . $version['version'];
  457. $encHandle = fopen('crypt://' . $path . '.part', 'wb');
  458. if ($encHandle === false) {
  459. \OCP\Util::writeLog('Encryption library', 'couldn\'t open "' . $path . '", decryption failed!', \OCP\Util::FATAL);
  460. $successful = false;
  461. continue;
  462. }
  463. $plainHandle = $this->view->fopen($path, 'rb');
  464. if ($plainHandle === false) {
  465. \OCP\Util::writeLog('Encryption library', 'couldn\'t open "' . $path . '.part", decryption failed!', \OCP\Util::FATAL);
  466. $successful = false;
  467. continue;
  468. }
  469. stream_copy_to_stream($plainHandle, $encHandle);
  470. fclose($encHandle);
  471. fclose($plainHandle);
  472. $this->view->rename($path . '.part', $path);
  473. }
  474. }
  475. }
  476. return $successful;
  477. }
  478. /**
  479. * @brief decrypt versions from given file
  480. * @param string $filelist list of decrypted files, relative to data/user/files
  481. * @return boolean
  482. */
  483. private function decryptVersions($filelist) {
  484. $successful = true;
  485. if (\OCP\App::isEnabled('files_versions')) {
  486. foreach ($filelist as $filename) {
  487. $versions = \OCA\Files_Versions\Storage::getVersions($this->userId, $filename);
  488. foreach ($versions as $version) {
  489. $path = '/' . $this->userId . '/files_versions/' . $version['path'] . '.v' . $version['version'];
  490. $encHandle = fopen('crypt://' . $path, 'rb');
  491. if ($encHandle === false) {
  492. \OCP\Util::writeLog('Encryption library', 'couldn\'t open "' . $path . '", decryption failed!', \OCP\Util::FATAL);
  493. $successful = false;
  494. continue;
  495. }
  496. $plainHandle = $this->view->fopen($path . '.part', 'wb');
  497. if ($plainHandle === false) {
  498. \OCP\Util::writeLog('Encryption library', 'couldn\'t open "' . $path . '.part", decryption failed!', \OCP\Util::FATAL);
  499. $successful = false;
  500. continue;
  501. }
  502. stream_copy_to_stream($encHandle, $plainHandle);
  503. fclose($encHandle);
  504. fclose($plainHandle);
  505. $this->view->rename($path . '.part', $path);
  506. }
  507. }
  508. }
  509. return $successful;
  510. }
  511. /**
  512. * @brief Decrypt all files
  513. * @return bool
  514. */
  515. public function decryptAll() {
  516. $found = $this->findEncFiles($this->userId . '/files');
  517. $successful = true;
  518. if ($found) {
  519. $versionStatus = \OCP\App::isEnabled('files_versions');
  520. \OC_App::disable('files_versions');
  521. $decryptedFiles = array();
  522. // Encrypt unencrypted files
  523. foreach ($found['encrypted'] as $encryptedFile) {
  524. //get file info
  525. $fileInfo = \OC\Files\Filesystem::getFileInfo($encryptedFile['path']);
  526. //relative to data/<user>/file
  527. $relPath = Helper::stripUserFilesPath($encryptedFile['path']);
  528. //relative to /data
  529. $rawPath = $encryptedFile['path'];
  530. //get timestamp
  531. $timestamp = $this->view->filemtime($rawPath);
  532. //enable proxy to use OC\Files\View to access the original file
  533. \OC_FileProxy::$enabled = true;
  534. // Open enc file handle for binary reading
  535. $encHandle = $this->view->fopen($rawPath, 'rb');
  536. // Disable proxy to prevent file being encrypted again
  537. \OC_FileProxy::$enabled = false;
  538. if ($encHandle === false) {
  539. \OCP\Util::writeLog('Encryption library', 'couldn\'t open "' . $rawPath . '", decryption failed!', \OCP\Util::FATAL);
  540. $successful = false;
  541. continue;
  542. }
  543. // Open plain file handle for binary writing, with same filename as original plain file
  544. $plainHandle = $this->view->fopen($rawPath . '.part', 'wb');
  545. if ($plainHandle === false) {
  546. \OCP\Util::writeLog('Encryption library', 'couldn\'t open "' . $rawPath . '.part", decryption failed!', \OCP\Util::FATAL);
  547. $successful = false;
  548. continue;
  549. }
  550. // Move plain file to a temporary location
  551. $size = stream_copy_to_stream($encHandle, $plainHandle);
  552. if ($size === 0) {
  553. \OCP\Util::writeLog('Encryption library', 'Zero bytes copied of "' . $rawPath . '", decryption failed!', \OCP\Util::FATAL);
  554. $successful = false;
  555. continue;
  556. }
  557. fclose($encHandle);
  558. fclose($plainHandle);
  559. $fakeRoot = $this->view->getRoot();
  560. $this->view->chroot('/' . $this->userId . '/files');
  561. $this->view->rename($relPath . '.part', $relPath);
  562. $this->view->chroot($fakeRoot);
  563. //set timestamp
  564. $this->view->touch($rawPath, $timestamp);
  565. // Add the file to the cache
  566. \OC\Files\Filesystem::putFileInfo($relPath, array(
  567. 'encrypted' => false,
  568. 'size' => $size,
  569. 'unencrypted_size' => $size,
  570. 'etag' => $fileInfo['etag']
  571. ));
  572. $decryptedFiles[] = $relPath;
  573. }
  574. if ($versionStatus) {
  575. \OC_App::enable('files_versions');
  576. }
  577. if (!$this->decryptVersions($decryptedFiles)) {
  578. $successful = false;
  579. }
  580. if ($successful) {
  581. $this->view->deleteAll($this->keyfilesPath);
  582. $this->view->deleteAll($this->shareKeysPath);
  583. }
  584. \OC_FileProxy::$enabled = true;
  585. }
  586. return $successful;
  587. }
  588. /**
  589. * @brief Encrypt all files in a directory
  590. * @param string $dirPath the directory whose files will be encrypted
  591. * @param null $legacyPassphrase
  592. * @param null $newPassphrase
  593. * @return bool
  594. * @note Encryption is recursive
  595. */
  596. public function encryptAll($dirPath, $legacyPassphrase = null, $newPassphrase = null) {
  597. $found = $this->findEncFiles($dirPath);
  598. if ($found) {
  599. // Disable proxy to prevent file being encrypted twice
  600. \OC_FileProxy::$enabled = false;
  601. $versionStatus = \OCP\App::isEnabled('files_versions');
  602. \OC_App::disable('files_versions');
  603. $encryptedFiles = array();
  604. // Encrypt unencrypted files
  605. foreach ($found['plain'] as $plainFile) {
  606. //get file info
  607. $fileInfo = \OC\Files\Filesystem::getFileInfo($plainFile['path']);
  608. //relative to data/<user>/file
  609. $relPath = $plainFile['path'];
  610. //relative to /data
  611. $rawPath = '/' . $this->userId . '/files/' . $plainFile['path'];
  612. // keep timestamp
  613. $timestamp = $this->view->filemtime($rawPath);
  614. // Open plain file handle for binary reading
  615. $plainHandle = $this->view->fopen($rawPath, 'rb');
  616. // Open enc file handle for binary writing, with same filename as original plain file
  617. $encHandle = fopen('crypt://' . $rawPath . '.part', 'wb');
  618. // Move plain file to a temporary location
  619. $size = stream_copy_to_stream($plainHandle, $encHandle);
  620. fclose($encHandle);
  621. fclose($plainHandle);
  622. $fakeRoot = $this->view->getRoot();
  623. $this->view->chroot('/' . $this->userId . '/files');
  624. $this->view->rename($relPath . '.part', $relPath);
  625. $this->view->chroot($fakeRoot);
  626. // set timestamp
  627. $this->view->touch($rawPath, $timestamp);
  628. // Add the file to the cache
  629. \OC\Files\Filesystem::putFileInfo($relPath, array(
  630. 'encrypted' => true,
  631. 'size' => $size,
  632. 'unencrypted_size' => $size,
  633. 'etag' => $fileInfo['etag']
  634. ));
  635. $encryptedFiles[] = $relPath;
  636. }
  637. // Encrypt legacy encrypted files
  638. if (
  639. !empty($legacyPassphrase)
  640. && !empty($newPassphrase)
  641. ) {
  642. foreach ($found['legacy'] as $legacyFile) {
  643. // Fetch data from file
  644. $legacyData = $this->view->file_get_contents($legacyFile['path']);
  645. // decrypt data, generate catfile
  646. $decrypted = Crypt::legacyBlockDecrypt($legacyData, $legacyPassphrase);
  647. $rawPath = $legacyFile['path'];
  648. // enable proxy the ensure encryption is handled
  649. \OC_FileProxy::$enabled = true;
  650. // Open enc file handle for binary writing, with same filename as original plain file
  651. $encHandle = $this->view->fopen( $rawPath, 'wb' );
  652. if (is_resource($encHandle)) {
  653. // write data to stream
  654. fwrite($encHandle, $decrypted);
  655. // close stream
  656. fclose($encHandle);
  657. }
  658. // disable proxy to prevent file being encrypted twice
  659. \OC_FileProxy::$enabled = false;
  660. }
  661. }
  662. \OC_FileProxy::$enabled = true;
  663. if ($versionStatus) {
  664. \OC_App::enable('files_versions');
  665. }
  666. $this->encryptVersions($encryptedFiles);
  667. // If files were found, return true
  668. return true;
  669. } else {
  670. // If no files were found, return false
  671. return false;
  672. }
  673. }
  674. /**
  675. * @brief Return important encryption related paths
  676. * @param string $pathName Name of the directory to return the path of
  677. * @return string path
  678. */
  679. public function getPath($pathName) {
  680. switch ($pathName) {
  681. case 'publicKeyDir':
  682. return $this->publicKeyDir;
  683. break;
  684. case 'encryptionDir':
  685. return $this->encryptionDir;
  686. break;
  687. case 'keyfilesPath':
  688. return $this->keyfilesPath;
  689. break;
  690. case 'publicKeyPath':
  691. return $this->publicKeyPath;
  692. break;
  693. case 'privateKeyPath':
  694. return $this->privateKeyPath;
  695. break;
  696. }
  697. return false;
  698. }
  699. /**
  700. * @brief get path of a file.
  701. * @param int $fileId id of the file
  702. * @return string path of the file
  703. */
  704. public static function fileIdToPath($fileId) {
  705. $sql = 'SELECT `path` FROM `*PREFIX*filecache` WHERE `fileid` = ?';
  706. $query = \OCP\DB::prepare($sql);
  707. $result = $query->execute(array($fileId));
  708. $path = false;
  709. if (\OCP\DB::isError($result)) {
  710. \OCP\Util::writeLog('Encryption library', \OC_DB::getErrorMessage($result), \OCP\Util::ERROR);
  711. } else {
  712. if ($result->numRows() > 0) {
  713. $row = $result->fetchRow();
  714. $path = substr($row['path'], strlen('files'));
  715. }
  716. }
  717. return $path;
  718. }
  719. /**
  720. * @brief Filter an array of UIDs to return only ones ready for sharing
  721. * @param array $unfilteredUsers users to be checked for sharing readiness
  722. * @return array as multi-dimensional array. keys: ready, unready
  723. */
  724. public function filterShareReadyUsers($unfilteredUsers) {
  725. // This array will collect the filtered IDs
  726. $readyIds = $unreadyIds = array();
  727. // Loop through users and create array of UIDs that need new keyfiles
  728. foreach ($unfilteredUsers as $user) {
  729. $util = new Util($this->view, $user);
  730. // Check that the user is encryption capable, or is the
  731. // public system user 'ownCloud' (for public shares)
  732. if (
  733. $user === $this->publicShareKeyId
  734. or $user === $this->recoveryKeyId
  735. or $util->ready()
  736. ) {
  737. // Construct array of ready UIDs for Keymanager{}
  738. $readyIds[] = $user;
  739. } else {
  740. // Construct array of unready UIDs for Keymanager{}
  741. $unreadyIds[] = $user;
  742. // Log warning; we can't do necessary setup here
  743. // because we don't have the user passphrase
  744. \OCP\Util::writeLog('Encryption library',
  745. '"' . $user . '" is not setup for encryption', \OCP\Util::WARN);
  746. }
  747. }
  748. return array(
  749. 'ready' => $readyIds,
  750. 'unready' => $unreadyIds
  751. );
  752. }
  753. /**
  754. * @brief Decrypt a keyfile
  755. * @param string $filePath
  756. * @param string $privateKey
  757. * @return bool|string
  758. */
  759. private function decryptKeyfile($filePath, $privateKey) {
  760. // Get the encrypted keyfile
  761. $encKeyfile = Keymanager::getFileKey($this->view, $this->userId, $filePath);
  762. // The file has a shareKey and must use it for decryption
  763. $shareKey = Keymanager::getShareKey($this->view, $this->userId, $filePath);
  764. $plainKeyfile = Crypt::multiKeyDecrypt($encKeyfile, $shareKey, $privateKey);
  765. return $plainKeyfile;
  766. }
  767. /**
  768. * @brief Encrypt keyfile to multiple users
  769. * @param Session $session
  770. * @param array $users list of users which should be able to access the file
  771. * @param string $filePath path of the file to be shared
  772. * @return bool
  773. */
  774. public function setSharedFileKeyfiles(Session $session, array $users, $filePath) {
  775. // Make sure users are capable of sharing
  776. $filteredUids = $this->filterShareReadyUsers($users);
  777. // If we're attempting to share to unready users
  778. if (!empty($filteredUids['unready'])) {
  779. \OCP\Util::writeLog('Encryption library',
  780. 'Sharing to these user(s) failed as they are unready for encryption:"'
  781. . print_r($filteredUids['unready'], 1), \OCP\Util::WARN);
  782. return false;
  783. }
  784. // Get public keys for each user, ready for generating sharekeys
  785. $userPubKeys = Keymanager::getPublicKeys($this->view, $filteredUids['ready']);
  786. // Note proxy status then disable it
  787. $proxyStatus = \OC_FileProxy::$enabled;
  788. \OC_FileProxy::$enabled = false;
  789. // Get the current users's private key for decrypting existing keyfile
  790. $privateKey = $session->getPrivateKey();
  791. $fileOwner = \OC\Files\Filesystem::getOwner($filePath);
  792. // Decrypt keyfile
  793. $plainKeyfile = $this->decryptKeyfile($filePath, $privateKey);
  794. // Re-enc keyfile to (additional) sharekeys
  795. $multiEncKey = Crypt::multiKeyEncrypt($plainKeyfile, $userPubKeys);
  796. // Save the recrypted key to it's owner's keyfiles directory
  797. // Save new sharekeys to all necessary user directory
  798. if (
  799. !Keymanager::setFileKey($this->view, $filePath, $fileOwner, $multiEncKey['data'])
  800. || !Keymanager::setShareKeys($this->view, $filePath, $multiEncKey['keys'])
  801. ) {
  802. \OCP\Util::writeLog('Encryption library',
  803. 'Keyfiles could not be saved for users sharing ' . $filePath, \OCP\Util::ERROR);
  804. return false;
  805. }
  806. // Return proxy to original status
  807. \OC_FileProxy::$enabled = $proxyStatus;
  808. return true;
  809. }
  810. /**
  811. * @brief Find, sanitise and format users sharing a file
  812. * @note This wraps other methods into a portable bundle
  813. */
  814. public function getSharingUsersArray($sharingEnabled, $filePath, $currentUserId = false) {
  815. // Check if key recovery is enabled
  816. if (
  817. \OC_Appconfig::getValue('files_encryption', 'recoveryAdminEnabled')
  818. && $this->recoveryEnabledForUser()
  819. ) {
  820. $recoveryEnabled = true;
  821. } else {
  822. $recoveryEnabled = false;
  823. }
  824. // Make sure that a share key is generated for the owner too
  825. list($owner, $ownerPath) = $this->getUidAndFilename($filePath);
  826. $userIds = array();
  827. if ($sharingEnabled) {
  828. // Find out who, if anyone, is sharing the file
  829. $result = \OCP\Share::getUsersSharingFile($ownerPath, $owner, true);
  830. $userIds = $result['users'];
  831. if ($result['public']) {
  832. $userIds[] = $this->publicShareKeyId;
  833. }
  834. }
  835. // If recovery is enabled, add the
  836. // Admin UID to list of users to share to
  837. if ($recoveryEnabled) {
  838. // Find recoveryAdmin user ID
  839. $recoveryKeyId = \OC_Appconfig::getValue('files_encryption', 'recoveryKeyId');
  840. // Add recoveryAdmin to list of users sharing
  841. $userIds[] = $recoveryKeyId;
  842. }
  843. // add current user if given
  844. if ($currentUserId !== false) {
  845. $userIds[] = $currentUserId;
  846. }
  847. // check if it is a group mount
  848. if (\OCP\App::isEnabled("files_external")) {
  849. $mount = \OC_Mount_Config::getSystemMountPoints();
  850. foreach ($mount as $mountPoint => $data) {
  851. if ($mountPoint == substr($ownerPath, 1, strlen($mountPoint))) {
  852. $userIds = array_merge($userIds, $this->getUserWithAccessToMountPoint($data['applicable']['users'], $data['applicable']['groups']));
  853. }
  854. }
  855. }
  856. // Remove duplicate UIDs
  857. $uniqueUserIds = array_unique($userIds);
  858. return $uniqueUserIds;
  859. }
  860. private function getUserWithAccessToMountPoint($users, $groups) {
  861. $result = array();
  862. if (in_array('all', $users)) {
  863. $result = \OCP\User::getUsers();
  864. } else {
  865. $result = array_merge($result, $users);
  866. foreach ($groups as $group) {
  867. $result = array_merge($result, \OC_Group::usersInGroup($group));
  868. }
  869. }
  870. return $result;
  871. }
  872. /**
  873. * @brief start migration mode to initially encrypt users data
  874. * @return boolean
  875. */
  876. public function beginMigration() {
  877. $return = false;
  878. $sql = 'UPDATE `*PREFIX*encryption` SET `migration_status` = ? WHERE `uid` = ? and `migration_status` = ?';
  879. $args = array(self::MIGRATION_IN_PROGRESS, $this->userId, self::MIGRATION_OPEN);
  880. $query = \OCP\DB::prepare($sql);
  881. $manipulatedRows = $query->execute($args);
  882. if ($manipulatedRows === 1) {
  883. $return = true;
  884. \OCP\Util::writeLog('Encryption library', "Start migration to encryption mode for " . $this->userId, \OCP\Util::INFO);
  885. } else {
  886. \OCP\Util::writeLog('Encryption library', "Could not activate migration mode for " . $this->userId . ". Probably another process already started the initial encryption", \OCP\Util::WARN);
  887. }
  888. return $return;
  889. }
  890. /**
  891. * @brief close migration mode after users data has been encrypted successfully
  892. * @return boolean
  893. */
  894. public function finishMigration() {
  895. $return = false;
  896. $sql = 'UPDATE `*PREFIX*encryption` SET `migration_status` = ? WHERE `uid` = ? and `migration_status` = ?';
  897. $args = array(self::MIGRATION_COMPLETED, $this->userId, self::MIGRATION_IN_PROGRESS);
  898. $query = \OCP\DB::prepare($sql);
  899. $manipulatedRows = $query->execute($args);
  900. if ($manipulatedRows === 1) {
  901. $return = true;
  902. \OCP\Util::writeLog('Encryption library', "Finish migration successfully for " . $this->userId, \OCP\Util::INFO);
  903. } else {
  904. \OCP\Util::writeLog('Encryption library', "Could not deactivate migration mode for " . $this->userId, \OCP\Util::WARN);
  905. }
  906. return $return;
  907. }
  908. /**
  909. * @brief check if files are already migrated to the encryption system
  910. * @return migration status, false = in case of no record
  911. * @note If records are not being returned, check for a hidden space
  912. * at the start of the uid in db
  913. */
  914. public function getMigrationStatus() {
  915. $sql = 'SELECT `migration_status` FROM `*PREFIX*encryption` WHERE `uid` = ?';
  916. $args = array($this->userId);
  917. $query = \OCP\DB::prepare($sql);
  918. $result = $query->execute($args);
  919. $migrationStatus = array();
  920. if (\OCP\DB::isError($result)) {
  921. \OCP\Util::writeLog('Encryption library', \OC_DB::getErrorMessage($result), \OCP\Util::ERROR);
  922. } else {
  923. if ($result->numRows() > 0) {
  924. $row = $result->fetchRow();
  925. if (isset($row['migration_status'])) {
  926. $migrationStatus[] = $row['migration_status'];
  927. }
  928. }
  929. }
  930. // If no record is found
  931. if (empty($migrationStatus)) {
  932. \OCP\Util::writeLog('Encryption library', "Could not get migration status for " . $this->userId . ", no record found", \OCP\Util::ERROR);
  933. return false;
  934. // If a record is found
  935. } else {
  936. return (int)$migrationStatus[0];
  937. }
  938. }
  939. /**
  940. * @brief get uid of the owners of the file and the path to the file
  941. * @param string $path Path of the file to check
  942. * @throws \Exception
  943. * @note $shareFilePath must be relative to data/UID/files. Files
  944. * relative to /Shared are also acceptable
  945. * @return array
  946. */
  947. public function getUidAndFilename($path) {
  948. $view = new \OC\Files\View($this->userFilesDir);
  949. $fileOwnerUid = $view->getOwner($path);
  950. // handle public access
  951. if ($this->isPublic) {
  952. $filename = $path;
  953. $fileOwnerUid = $GLOBALS['fileOwner'];
  954. return array(
  955. $fileOwnerUid,
  956. $filename
  957. );
  958. } else {
  959. // Check that UID is valid
  960. if (!\OCP\User::userExists($fileOwnerUid)) {
  961. throw new \Exception(
  962. 'Could not find owner (UID = "' . var_export($fileOwnerUid, 1) . '") of file "' . $path . '"');
  963. }
  964. // NOTE: Bah, this dependency should be elsewhere
  965. \OC\Files\Filesystem::initMountPoints($fileOwnerUid);
  966. // If the file owner is the currently logged in user
  967. if ($fileOwnerUid === $this->userId) {
  968. // Assume the path supplied is correct
  969. $filename = $path;
  970. } else {
  971. $info = $view->getFileInfo($path);
  972. $ownerView = new \OC\Files\View('/' . $fileOwnerUid . '/files');
  973. // Fetch real file path from DB
  974. $filename = $ownerView->getPath($info['fileid']); // TODO: Check that this returns a path without including the user data dir
  975. }
  976. return array(
  977. $fileOwnerUid,
  978. \OC_Filesystem::normalizePath($filename)
  979. );
  980. }
  981. }
  982. /**
  983. * @brief go recursively through a dir and collect all files and sub files.
  984. * @param string $dir relative to the users files folder
  985. * @return array with list of files relative to the users files folder
  986. */
  987. public function getAllFiles($dir) {
  988. $result = array();
  989. $content = $this->view->getDirectoryContent(\OC\Files\Filesystem::normalizePath(
  990. $this->userFilesDir . '/' . $dir));
  991. // handling for re shared folders
  992. $pathSplit = explode('/', $dir);
  993. foreach ($content as $c) {
  994. $sharedPart = $pathSplit[sizeof($pathSplit) - 1];
  995. $targetPathSplit = array_reverse(explode('/', $c['path']));
  996. $path = '';
  997. // rebuild path
  998. foreach ($targetPathSplit as $pathPart) {
  999. if ($pathPart !== $sharedPart) {
  1000. $path = '/' . $pathPart . $path;
  1001. } else {
  1002. break;
  1003. }
  1004. }
  1005. $path = $dir . $path;
  1006. if ($c['type'] === 'dir') {
  1007. $result = array_merge($result, $this->getAllFiles($path));
  1008. } else {
  1009. $result[] = $path;
  1010. }
  1011. }
  1012. return $result;
  1013. }
  1014. /**
  1015. * @brief get shares parent.
  1016. * @param int $id of the current share
  1017. * @return array of the parent
  1018. */
  1019. public static function getShareParent($id) {
  1020. $sql = 'SELECT `file_target`, `item_type` FROM `*PREFIX*share` WHERE `id` = ?';
  1021. $query = \OCP\DB::prepare($sql);
  1022. $result = $query->execute(array($id));
  1023. $row = array();
  1024. if (\OCP\DB::isError($result)) {
  1025. \OCP\Util::writeLog('Encryption library', \OC_DB::getErrorMessage($result), \OCP\Util::ERROR);
  1026. } else {
  1027. if ($result->numRows() > 0) {
  1028. $row = $result->fetchRow();
  1029. }
  1030. }
  1031. return $row;
  1032. }
  1033. /**
  1034. * @brief get shares parent.
  1035. * @param int $id of the current share
  1036. * @return array of the parent
  1037. */
  1038. public static function getParentFromShare($id) {
  1039. $sql = 'SELECT `parent` FROM `*PREFIX*share` WHERE `id` = ?';
  1040. $query = \OCP\DB::prepare($sql);
  1041. $result = $query->execute(array($id));
  1042. $row = array();
  1043. if (\OCP\DB::isError($result)) {
  1044. \OCP\Util::writeLog('Encryption library', \OC_DB::getErrorMessage($result), \OCP\Util::ERROR);
  1045. } else {
  1046. if ($result->numRows() > 0) {
  1047. $row = $result->fetchRow();
  1048. }
  1049. }
  1050. return $row;
  1051. }
  1052. /**
  1053. * @brief get owner of the shared files.
  1054. * @param $id
  1055. * @internal param int $Id of a share
  1056. * @return string owner
  1057. */
  1058. public function getOwnerFromSharedFile($id) {
  1059. $query = \OCP\DB::prepare('SELECT `parent`, `uid_owner` FROM `*PREFIX*share` WHERE `id` = ?', 1);
  1060. $result = $query->execute(array($id));
  1061. $source = array();
  1062. if (\OCP\DB::isError($result)) {
  1063. \OCP\Util::writeLog('Encryption library', \OC_DB::getErrorMessage($result), \OCP\Util::ERROR);
  1064. } else {
  1065. if ($result->numRows() > 0) {
  1066. $source = $result->fetchRow();
  1067. }
  1068. }
  1069. $fileOwner = false;
  1070. if (isset($source['parent'])) {
  1071. $parent = $source['parent'];
  1072. while (isset($parent)) {
  1073. $query = \OCP\DB::prepare('SELECT `parent`, `uid_owner` FROM `*PREFIX*share` WHERE `id` = ?', 1);
  1074. $result = $query->execute(array($parent));
  1075. $item = array();
  1076. if (\OCP\DB::isError($result)) {
  1077. \OCP\Util::writeLog('Encryption library', \OC_DB::getErrorMessage($result), \OCP\Util::ERROR);
  1078. } else {
  1079. if ($result->numRows() > 0) {
  1080. $item = $result->fetchRow();
  1081. }
  1082. }
  1083. if (isset($item['parent'])) {
  1084. $parent = $item['parent'];
  1085. } else {
  1086. $fileOwner = $item['uid_owner'];
  1087. break;
  1088. }
  1089. }
  1090. } else {
  1091. $fileOwner = $source['uid_owner'];
  1092. }
  1093. return $fileOwner;
  1094. }
  1095. /**
  1096. * @return string
  1097. */
  1098. public function getUserId() {
  1099. return $this->userId;
  1100. }
  1101. /**
  1102. * @return string
  1103. */
  1104. public function getUserFilesDir() {
  1105. return $this->userFilesDir;
  1106. }
  1107. /**
  1108. * @param $password
  1109. * @return bool
  1110. */
  1111. public function checkRecoveryPassword($password) {
  1112. $result = false;
  1113. $pathKey = '/owncloud_private_key/' . $this->recoveryKeyId . ".private.key";
  1114. $proxyStatus = \OC_FileProxy::$enabled;
  1115. \OC_FileProxy::$enabled = false;
  1116. $recoveryKey = $this->view->file_get_contents($pathKey);
  1117. $decryptedRecoveryKey = Crypt::decryptPrivateKey($recoveryKey, $password);
  1118. if ($decryptedRecoveryKey) {
  1119. $result = true;
  1120. }
  1121. \OC_FileProxy::$enabled = $proxyStatus;
  1122. return $result;
  1123. }
  1124. /**
  1125. * @return string
  1126. */
  1127. public function getRecoveryKeyId() {
  1128. return $this->recoveryKeyId;
  1129. }
  1130. /**
  1131. * @brief add recovery key to all encrypted files
  1132. */
  1133. public function addRecoveryKeys($path = '/') {
  1134. $dirContent = $this->view->getDirectoryContent($this->keyfilesPath . $path);
  1135. foreach ($dirContent as $item) {
  1136. // get relative path from files_encryption/keyfiles/
  1137. $filePath = substr($item['path'], strlen('files_encryption/keyfiles'));
  1138. if ($item['type'] === 'dir') {
  1139. $this->addRecoveryKeys($filePath . '/');
  1140. } else {
  1141. $session = new \OCA\Encryption\Session(new \OC_FilesystemView('/'));
  1142. $sharingEnabled = \OCP\Share::isEnabled();
  1143. // remove '.key' extension from path e.g. 'file.txt.key' to 'file.txt'
  1144. $file = substr($filePath, 0, -4);
  1145. $usersSharing = $this->getSharingUsersArray($sharingEnabled, $file);
  1146. $this->setSharedFileKeyfiles($session, $usersSharing, $file);
  1147. }
  1148. }
  1149. }
  1150. /**
  1151. * @brief remove recovery key to all encrypted files
  1152. */
  1153. public function removeRecoveryKeys($path = '/') {
  1154. $dirContent = $this->view->getDirectoryContent($this->keyfilesPath . $path);
  1155. foreach ($dirContent as $item) {
  1156. // get relative path from files_encryption/keyfiles
  1157. $filePath = substr($item['path'], strlen('files_encryption/keyfiles'));
  1158. if ($item['type'] === 'dir') {
  1159. $this->removeRecoveryKeys($filePath . '/');
  1160. } else {
  1161. // remove '.key' extension from path e.g. 'file.txt.key' to 'file.txt'
  1162. $file = substr($filePath, 0, -4);
  1163. $this->view->unlink($this->shareKeysPath . '/' . $file . '.' . $this->recoveryKeyId . '.shareKey');
  1164. }
  1165. }
  1166. }
  1167. /**
  1168. * @brief decrypt given file with recovery key and encrypt it again to the owner and his new key
  1169. * @param string $file
  1170. * @param string $privateKey recovery key to decrypt the file
  1171. */
  1172. private function recoverFile($file, $privateKey) {
  1173. $sharingEnabled = \OCP\Share::isEnabled();
  1174. // Find out who, if anyone, is sharing the file
  1175. if ($sharingEnabled) {
  1176. $result = \OCP\Share::getUsersSharingFile($file, $this->userId, true);
  1177. $userIds = $result['users'];
  1178. $userIds[] = $this->recoveryKeyId;
  1179. if ($result['public']) {
  1180. $userIds[] = $this->publicShareKeyId;
  1181. }
  1182. } else {
  1183. $userIds = array(
  1184. $this->userId,
  1185. $this->recoveryKeyId
  1186. );
  1187. }
  1188. $filteredUids = $this->filterShareReadyUsers($userIds);
  1189. $proxyStatus = \OC_FileProxy::$enabled;
  1190. \OC_FileProxy::$enabled = false;
  1191. //decrypt file key
  1192. $encKeyfile = $this->view->file_get_contents($this->keyfilesPath . $file . ".key");
  1193. $shareKey = $this->view->file_get_contents(
  1194. $this->shareKeysPath . $file . "." . $this->recoveryKeyId . ".shareKey");
  1195. $plainKeyfile = Crypt::multiKeyDecrypt($encKeyfile, $shareKey, $privateKey);
  1196. // encrypt file key again to all users, this time with the new public key for the recovered use
  1197. $userPubKeys = Keymanager::getPublicKeys($this->view, $filteredUids['ready']);
  1198. $multiEncKey = Crypt::multiKeyEncrypt($plainKeyfile, $userPubKeys);
  1199. // write new keys to filesystem TDOO!
  1200. $this->view->file_put_contents($this->keyfilesPath . $file . '.key', $multiEncKey['data']);
  1201. foreach ($multiEncKey['keys'] as $userId => $shareKey) {
  1202. $shareKeyPath = $this->shareKeysPath . $file . '.' . $userId . '.shareKey';
  1203. $this->view->file_put_contents($shareKeyPath, $shareKey);
  1204. }
  1205. // Return proxy to original status
  1206. \OC_FileProxy::$enabled = $proxyStatus;
  1207. }
  1208. /**
  1209. * @brief collect all files and recover them one by one
  1210. * @param string $path to look for files keys
  1211. * @param string $privateKey private recovery key which is used to decrypt the files
  1212. */
  1213. private function recoverAllFiles($path, $privateKey) {
  1214. $dirContent = $this->view->getDirectoryContent($this->keyfilesPath . $path);
  1215. foreach ($dirContent as $item) {
  1216. // get relative path from files_encryption/keyfiles
  1217. $filePath = substr($item['path'], strlen('files_encryption/keyfiles'));
  1218. if ($item['type'] === 'dir') {
  1219. $this->recoverAllFiles($filePath . '/', $privateKey);
  1220. } else {
  1221. // remove '.key' extension from path e.g. 'file.txt.key' to 'file.txt'
  1222. $file = substr($filePath, 0, -4);
  1223. $this->recoverFile($file, $privateKey);
  1224. }
  1225. }
  1226. }
  1227. /**
  1228. * @brief recover users files in case of password lost
  1229. * @param string $recoveryPassword
  1230. */
  1231. public function recoverUsersFiles($recoveryPassword) {
  1232. // Disable encryption proxy to prevent recursive calls
  1233. $proxyStatus = \OC_FileProxy::$enabled;
  1234. \OC_FileProxy::$enabled = false;
  1235. $encryptedKey = $this->view->file_get_contents(
  1236. '/owncloud_private_key/' . $this->recoveryKeyId . '.private.key');
  1237. $privateKey = Crypt::decryptPrivateKey($encryptedKey, $recoveryPassword);
  1238. \OC_FileProxy::$enabled = $proxyStatus;
  1239. $this->recoverAllFiles('/', $privateKey);
  1240. }
  1241. /**
  1242. * Get the path including the storage mount point
  1243. * @param int $id
  1244. * @return string the path including the mount point like AmazonS3/folder/file.txt
  1245. */
  1246. public function getPathWithMountPoint($id) {
  1247. list($storage, $internalPath) = \OC\Files\Cache\Cache::getById($id);
  1248. $mount = \OC\Files\Filesystem::getMountByStorageId($storage);
  1249. $mountPoint = $mount[0]->getMountPoint();
  1250. $path = \OC\Files\Filesystem::normalizePath($mountPoint . '/' . $internalPath);
  1251. // reformat the path to be relative e.g. /user/files/folder becomes /folder/
  1252. $relativePath = \OCA\Encryption\Helper::stripUserFilesPath($path);
  1253. return $relativePath;
  1254. }
  1255. /**
  1256. * @brief check if the file is stored on a system wide mount point
  1257. * @param $path relative to /data/user with leading '/'
  1258. * @return boolean
  1259. */
  1260. public function isSystemWideMountPoint($path) {
  1261. if (\OCP\App::isEnabled("files_external")) {
  1262. $mount = \OC_Mount_Config::getSystemMountPoints();
  1263. foreach ($mount as $mountPoint => $data) {
  1264. if ($mountPoint == substr($path, 1, strlen($mountPoint))) {
  1265. return true;
  1266. }
  1267. }
  1268. }
  1269. return false;
  1270. }
  1271. /**
  1272. * @brief decrypt private key and add it to the current session
  1273. * @param array $params with 'uid' and 'password'
  1274. * @return mixed session or false
  1275. */
  1276. public function initEncryption($params) {
  1277. $encryptedKey = Keymanager::getPrivateKey($this->view, $params['uid']);
  1278. $privateKey = Crypt::decryptPrivateKey($encryptedKey, $params['password']);
  1279. if ($privateKey === false) {
  1280. \OCP\Util::writeLog('Encryption library', 'Private key for user "' . $params['uid']
  1281. . '" is not valid! Maybe the user password was changed from outside if so please change it back to gain access', \OCP\Util::ERROR);
  1282. return false;
  1283. }
  1284. $session = new \OCA\Encryption\Session($this->view);
  1285. $session->setPrivateKey($privateKey);
  1286. return $session;
  1287. }
  1288. }