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.

util.php 44KB

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