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.

keymanager.php 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500
  1. <?php
  2. /**
  3. * @author Björn Schießle <schiessle@owncloud.com>
  4. * @author Christopher Schäpers <kondou@ts.unde.re>
  5. * @author Florin Peter <github@florin-peter.de>
  6. * @author Joas Schilling <nickvergessen@owncloud.com>
  7. * @author Jörn Friedrich Dreyer <jfd@butonic.de>
  8. * @author Morris Jobke <hey@morrisjobke.de>
  9. * @author Robin Appelman <icewind@owncloud.com>
  10. * @author Robin McCorkell <rmccorkell@karoshi.org.uk>
  11. * @author Sam Tuke <mail@samtuke.com>
  12. * @author Thomas Müller <thomas.mueller@tmit.eu>
  13. * @author Vincent Petry <pvince81@owncloud.com>
  14. *
  15. * @copyright Copyright (c) 2015, ownCloud, Inc.
  16. * @license AGPL-3.0
  17. *
  18. * This code is free software: you can redistribute it and/or modify
  19. * it under the terms of the GNU Affero General Public License, version 3,
  20. * as published by the Free Software Foundation.
  21. *
  22. * This program is distributed in the hope that it will be useful,
  23. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  24. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  25. * GNU Affero General Public License for more details.
  26. *
  27. * You should have received a copy of the GNU Affero General Public License, version 3,
  28. * along with this program. If not, see <http://www.gnu.org/licenses/>
  29. *
  30. */
  31. namespace OCA\Files_Encryption;
  32. /**
  33. * Class to manage storage and retrieval of encryption keys
  34. * @note Where a method requires a view object, it's root must be '/'
  35. */
  36. class Keymanager {
  37. // base dir where all the file related keys are stored
  38. private static $keys_base_dir = '/files_encryption/keys/';
  39. private static $encryption_base_dir = '/files_encryption';
  40. private static $public_key_dir = '/files_encryption/public_keys';
  41. private static $key_cache = array(); // cache keys
  42. /**
  43. * read key from hard disk
  44. *
  45. * @param string $path to key
  46. * @param \OC\Files\View $view
  47. * @return string|bool either the key or false
  48. */
  49. private static function getKey($path, $view) {
  50. $key = false;
  51. if (isset(self::$key_cache[$path])) {
  52. $key = self::$key_cache[$path];
  53. } else {
  54. /** @var \OCP\Files\Storage $storage */
  55. list($storage, $internalPath) = $view->resolvePath($path);
  56. if ($storage->file_exists($internalPath)) {
  57. $key = $storage->file_get_contents($internalPath);
  58. self::$key_cache[$path] = $key;
  59. }
  60. }
  61. return $key;
  62. }
  63. /**
  64. * write key to disk
  65. *
  66. *
  67. * @param string $path path to key directory
  68. * @param string $name key name
  69. * @param string $key key
  70. * @param \OC\Files\View $view
  71. * @return bool
  72. */
  73. private static function setKey($path, $name, $key, $view) {
  74. self::keySetPreparation($view, $path);
  75. /** @var \OCP\Files\Storage $storage */
  76. $pathToKey = \OC\Files\Filesystem::normalizePath($path . '/' . $name);
  77. list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($pathToKey);
  78. $result = $storage->file_put_contents($internalPath, $key);
  79. if (is_int($result) && $result > 0) {
  80. self::$key_cache[$pathToKey] = $key;
  81. return true;
  82. }
  83. return false;
  84. }
  85. /**
  86. * retrieve the ENCRYPTED private key from a user
  87. *
  88. * @param \OC\Files\View $view
  89. * @param string $user
  90. * @return string private key or false (hopefully)
  91. * @note the key returned by this method must be decrypted before use
  92. */
  93. public static function getPrivateKey(\OC\Files\View $view, $user) {
  94. $path = '/' . $user . '/' . 'files_encryption' . '/' . $user . '.privateKey';
  95. return self::getKey($path, $view);
  96. }
  97. /**
  98. * retrieve public key for a specified user
  99. * @param \OC\Files\View $view
  100. * @param string $userId
  101. * @return string public key or false
  102. */
  103. public static function getPublicKey(\OC\Files\View $view, $userId) {
  104. $path = self::$public_key_dir . '/' . $userId . '.publicKey';
  105. return self::getKey($path, $view);
  106. }
  107. public static function getPublicKeyPath() {
  108. return self::$public_key_dir;
  109. }
  110. /**
  111. * Retrieve a user's public and private key
  112. * @param \OC\Files\View $view
  113. * @param string $userId
  114. * @return array keys: privateKey, publicKey
  115. */
  116. public static function getUserKeys(\OC\Files\View $view, $userId) {
  117. return array(
  118. 'publicKey' => self::getPublicKey($view, $userId),
  119. 'privateKey' => self::getPrivateKey($view, $userId)
  120. );
  121. }
  122. /**
  123. * Retrieve public keys for given users
  124. * @param \OC\Files\View $view
  125. * @param array $userIds
  126. * @return array of public keys for the specified users
  127. */
  128. public static function getPublicKeys(\OC\Files\View $view, array $userIds) {
  129. $keys = array();
  130. foreach ($userIds as $userId) {
  131. $keys[$userId] = self::getPublicKey($view, $userId);
  132. }
  133. return $keys;
  134. }
  135. /**
  136. * store file encryption key
  137. *
  138. * @param \OC\Files\View $view
  139. * @param \OCA\Files_Encryption\Util $util
  140. * @param string $path relative path of the file, including filename
  141. * @param string $catfile keyfile content
  142. * @return bool true/false
  143. * @note The keyfile is not encrypted here. Client code must
  144. * asymmetrically encrypt the keyfile before passing it to this method
  145. */
  146. public static function setFileKey(\OC\Files\View $view, $util, $path, $catfile) {
  147. $path = self::getKeyPath($view, $util, $path);
  148. return self::setKey($path, 'fileKey', $catfile, $view);
  149. }
  150. /**
  151. * get path to key folder for a given file
  152. *
  153. * @param \OC\Files\View $view relative to data directory
  154. * @param \OCA\Files_Encryption\Util $util
  155. * @param string $path path to the file, relative to the users file directory
  156. * @return string
  157. */
  158. public static function getKeyPath($view, $util, $path) {
  159. if ($view->is_dir('/' . \OCP\User::getUser() . '/' . $path)) {
  160. throw new Exception\EncryptionException('file was expected but directoy was given', Exception\EncryptionException::GENERIC);
  161. }
  162. list($owner, $filename) = $util->getUidAndFilename($path);
  163. $filename = Helper::stripPartialFileExtension($filename);
  164. $filePath_f = ltrim($filename, '/');
  165. // in case of system wide mount points the keys are stored directly in the data directory
  166. if ($util->isSystemWideMountPoint($filename)) {
  167. $keyPath = self::$keys_base_dir . $filePath_f . '/';
  168. } else {
  169. $keyPath = '/' . $owner . self::$keys_base_dir . $filePath_f . '/';
  170. }
  171. return $keyPath;
  172. }
  173. /**
  174. * get path to file key for a given file
  175. *
  176. * @param \OC\Files\View $view relative to data directory
  177. * @param \OCA\Files_Encryption\Util $util
  178. * @param string $path path to the file, relative to the users file directory
  179. * @return string
  180. */
  181. public static function getFileKeyPath($view, $util, $path) {
  182. $keyDir = self::getKeyPath($view, $util, $path);
  183. return $keyDir . 'fileKey';
  184. }
  185. /**
  186. * get path to share key for a given user
  187. *
  188. * @param \OC\Files\View $view relateive to data directory
  189. * @param \OCA\Files_Encryption\Util $util
  190. * @param string $path path to file relative to the users files directoy
  191. * @param string $uid user for whom we want the share-key path
  192. * @retrun string
  193. */
  194. public static function getShareKeyPath($view, $util, $path, $uid) {
  195. $keyDir = self::getKeyPath($view, $util, $path);
  196. return $keyDir . $uid . '.shareKey';
  197. }
  198. /**
  199. * delete key
  200. *
  201. * @param \OC\Files\View $view
  202. * @param string $path
  203. * @return boolean
  204. */
  205. private static function deleteKey($view, $path) {
  206. $normalizedPath = \OC\Files\Filesystem::normalizePath($path);
  207. $result = $view->unlink($normalizedPath);
  208. if ($result) {
  209. unset(self::$key_cache[$normalizedPath]);
  210. return true;
  211. }
  212. return false;
  213. }
  214. /**
  215. * delete public key from a given user
  216. *
  217. * @param \OC\Files\View $view
  218. * @param string $uid user
  219. * @return bool
  220. */
  221. public static function deletePublicKey($view, $uid) {
  222. $result = false;
  223. if (!\OCP\User::userExists($uid)) {
  224. $publicKey = self::$public_key_dir . '/' . $uid . '.publicKey';
  225. self::deleteKey($view, $publicKey);
  226. }
  227. return $result;
  228. }
  229. /**
  230. * check if public key for user exists
  231. *
  232. * @param \OC\Files\View $view
  233. * @param string $uid
  234. */
  235. public static function publicKeyExists($view, $uid) {
  236. return $view->file_exists(self::$public_key_dir . '/'. $uid . '.publicKey');
  237. }
  238. /**
  239. * retrieve keyfile for an encrypted file
  240. * @param \OC\Files\View $view
  241. * @param \OCA\Files_Encryption\Util $util
  242. * @param string|false $filePath
  243. * @return string file key or false
  244. * @note The keyfile returned is asymmetrically encrypted. Decryption
  245. * of the keyfile must be performed by client code
  246. */
  247. public static function getFileKey($view, $util, $filePath) {
  248. $path = self::getFileKeyPath($view, $util, $filePath);
  249. return self::getKey($path, $view);
  250. }
  251. /**
  252. * store private key from the user
  253. * @param string $key
  254. * @return bool
  255. * @note Encryption of the private key must be performed by client code
  256. * as no encryption takes place here
  257. */
  258. public static function setPrivateKey($key, $user = '') {
  259. $user = $user === '' ? \OCP\User::getUser() : $user;
  260. $path = '/' . $user . '/files_encryption';
  261. $header = Crypt::generateHeader();
  262. return self::setKey($path, $user . '.privateKey', $header . $key, new \OC\Files\View());
  263. }
  264. /**
  265. * check if recovery key exists
  266. *
  267. * @param \OC\Files\View $view
  268. * @return bool
  269. */
  270. public static function recoveryKeyExists($view) {
  271. $result = false;
  272. $recoveryKeyId = Helper::getRecoveryKeyId();
  273. if ($recoveryKeyId) {
  274. $result = ($view->file_exists(self::$public_key_dir . '/' . $recoveryKeyId . ".publicKey")
  275. && $view->file_exists(self::$encryption_base_dir . '/' . $recoveryKeyId . ".privateKey"));
  276. }
  277. return $result;
  278. }
  279. public static function publicShareKeyExists($view) {
  280. $result = false;
  281. $publicShareKeyId = Helper::getPublicShareKeyId();
  282. if ($publicShareKeyId) {
  283. $result = ($view->file_exists(self::$public_key_dir . '/' . $publicShareKeyId . ".publicKey")
  284. && $view->file_exists(self::$encryption_base_dir . '/' . $publicShareKeyId . ".privateKey"));
  285. }
  286. return $result;
  287. }
  288. /**
  289. * store public key from the user
  290. * @param string $key
  291. * @param string $user
  292. *
  293. * @return bool
  294. */
  295. public static function setPublicKey($key, $user = '') {
  296. $user = $user === '' ? \OCP\User::getUser() : $user;
  297. return self::setKey(self::$public_key_dir, $user . '.publicKey', $key, new \OC\Files\View('/'));
  298. }
  299. /**
  300. * write private system key (recovery and public share key) to disk
  301. *
  302. * @param string $key encrypted key
  303. * @param string $keyName name of the key
  304. * @return boolean
  305. */
  306. public static function setPrivateSystemKey($key, $keyName) {
  307. $keyName = $keyName . '.privateKey';
  308. $header = Crypt::generateHeader();
  309. return self::setKey(self::$encryption_base_dir, $keyName,$header . $key, new \OC\Files\View());
  310. }
  311. /**
  312. * read private system key (recovery and public share key) from disk
  313. *
  314. * @param string $keyName name of the key
  315. * @return string|boolean private system key or false
  316. */
  317. public static function getPrivateSystemKey($keyName) {
  318. $path = $keyName . '.privateKey';
  319. return self::getKey($path, new \OC\Files\View(self::$encryption_base_dir));
  320. }
  321. /**
  322. * store multiple share keys for a single file
  323. * @param \OC\Files\View $view
  324. * @param \OCA\Files_Encryption\Util $util
  325. * @param string $path
  326. * @param array $shareKeys
  327. * @return bool
  328. */
  329. public static function setShareKeys($view, $util, $path, array $shareKeys) {
  330. // in case of system wide mount points the keys are stored directly in the data directory
  331. $basePath = Keymanager::getKeyPath($view, $util, $path);
  332. self::keySetPreparation($view, $basePath);
  333. $result = true;
  334. foreach ($shareKeys as $userId => $shareKey) {
  335. if (!self::setKey($basePath, $userId . '.shareKey', $shareKey, $view)) {
  336. // If any of the keys are not set, flag false
  337. $result = false;
  338. }
  339. }
  340. // Returns false if any of the keys weren't set
  341. return $result;
  342. }
  343. /**
  344. * retrieve shareKey for an encrypted file
  345. * @param \OC\Files\View $view
  346. * @param string $userId
  347. * @param \OCA\Files_Encryption\Util $util
  348. * @param string $filePath
  349. * @return string file key or false
  350. * @note The sharekey returned is encrypted. Decryption
  351. * of the keyfile must be performed by client code
  352. */
  353. public static function getShareKey($view, $userId, $util, $filePath) {
  354. $path = self::getShareKeyPath($view, $util, $filePath, $userId);
  355. return self::getKey($path, $view);
  356. }
  357. /**
  358. * Delete a single user's shareKey for a single file
  359. *
  360. * @param \OC\Files\View $view relative to data/
  361. * @param array $userIds list of users we want to remove
  362. * @param string $keyPath
  363. * @param string $owner the owner of the file
  364. * @param string $ownerPath the owners name of the file for which we want to remove the users relative to data/user/files
  365. */
  366. public static function delShareKey($view, $userIds, $keysPath, $owner, $ownerPath) {
  367. $key = array_search($owner, $userIds, true);
  368. if ($key !== false && $view->file_exists('/' . $owner . '/files/' . $ownerPath)) {
  369. unset($userIds[$key]);
  370. }
  371. self::recursiveDelShareKeys($keysPath, $userIds, $view);
  372. }
  373. /**
  374. * recursively delete share keys from given users
  375. *
  376. * @param string $dir directory
  377. * @param array $userIds user ids for which the share keys should be deleted
  378. * @param \OC\Files\View $view view relative to data/
  379. */
  380. private static function recursiveDelShareKeys($dir, $userIds, $view) {
  381. $dirContent = $view->opendir($dir);
  382. if (is_resource($dirContent)) {
  383. while (($file = readdir($dirContent)) !== false) {
  384. if (!\OC\Files\Filesystem::isIgnoredDir($file)) {
  385. if ($view->is_dir($dir . '/' . $file)) {
  386. self::recursiveDelShareKeys($dir . '/' . $file, $userIds, $view);
  387. } else {
  388. foreach ($userIds as $userId) {
  389. if ($userId . '.shareKey' === $file) {
  390. \OCP\Util::writeLog('files_encryption', 'recursiveDelShareKey: delete share key: ' . $file, \OCP\Util::DEBUG);
  391. self::deleteKey($view, $dir . '/' . $file);
  392. }
  393. }
  394. }
  395. }
  396. }
  397. closedir($dirContent);
  398. }
  399. }
  400. /**
  401. * Make preparations to vars and filesystem for saving a keyfile
  402. *
  403. * @param \OC\Files\View $view
  404. * @param string $path relatvie to the views root
  405. * @param string $basePath
  406. */
  407. protected static function keySetPreparation($view, $path) {
  408. // If the file resides within a subdirectory, create it
  409. if (!$view->file_exists($path)) {
  410. $sub_dirs = explode('/', $path);
  411. $dir = '';
  412. foreach ($sub_dirs as $sub_dir) {
  413. $dir .= '/' . $sub_dir;
  414. if (!$view->is_dir($dir)) {
  415. $view->mkdir($dir);
  416. }
  417. }
  418. }
  419. }
  420. }