Recovery.php 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. <?php
  2. /**
  3. * SPDX-FileCopyrightText: 2019-2024 Nextcloud GmbH and Nextcloud contributors
  4. * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
  5. * SPDX-License-Identifier: AGPL-3.0-only
  6. */
  7. namespace OCA\Encryption;
  8. use OC\Files\View;
  9. use OCA\Encryption\Crypto\Crypt;
  10. use OCP\Encryption\IFile;
  11. use OCP\IConfig;
  12. use OCP\IUser;
  13. use OCP\IUserSession;
  14. use OCP\PreConditionNotMetException;
  15. class Recovery {
  16. /**
  17. * @var null|IUser
  18. */
  19. protected $user;
  20. /**
  21. * @param IUserSession $userSession
  22. * @param Crypt $crypt
  23. * @param KeyManager $keyManager
  24. * @param IConfig $config
  25. * @param IFile $file
  26. * @param View $view
  27. */
  28. public function __construct(
  29. IUserSession $userSession,
  30. protected Crypt $crypt,
  31. private KeyManager $keyManager,
  32. private IConfig $config,
  33. private IFile $file,
  34. private View $view,
  35. ) {
  36. $this->user = ($userSession->isLoggedIn()) ? $userSession->getUser() : null;
  37. }
  38. /**
  39. * @param string $password
  40. * @return bool
  41. */
  42. public function enableAdminRecovery($password) {
  43. $appConfig = $this->config;
  44. $keyManager = $this->keyManager;
  45. if (!$keyManager->recoveryKeyExists()) {
  46. $keyPair = $this->crypt->createKeyPair();
  47. if (!is_array($keyPair)) {
  48. return false;
  49. }
  50. $this->keyManager->setRecoveryKey($password, $keyPair);
  51. }
  52. if ($keyManager->checkRecoveryPassword($password)) {
  53. $appConfig->setAppValue('encryption', 'recoveryAdminEnabled', '1');
  54. return true;
  55. }
  56. return false;
  57. }
  58. /**
  59. * change recovery key id
  60. *
  61. * @param string $newPassword
  62. * @param string $oldPassword
  63. * @return bool
  64. */
  65. public function changeRecoveryKeyPassword($newPassword, $oldPassword) {
  66. $recoveryKey = $this->keyManager->getSystemPrivateKey($this->keyManager->getRecoveryKeyId());
  67. $decryptedRecoveryKey = $this->crypt->decryptPrivateKey($recoveryKey, $oldPassword);
  68. if ($decryptedRecoveryKey === false) {
  69. return false;
  70. }
  71. $encryptedRecoveryKey = $this->crypt->encryptPrivateKey($decryptedRecoveryKey, $newPassword);
  72. $header = $this->crypt->generateHeader();
  73. if ($encryptedRecoveryKey) {
  74. $this->keyManager->setSystemPrivateKey($this->keyManager->getRecoveryKeyId(), $header . $encryptedRecoveryKey);
  75. return true;
  76. }
  77. return false;
  78. }
  79. /**
  80. * @param string $recoveryPassword
  81. * @return bool
  82. */
  83. public function disableAdminRecovery($recoveryPassword) {
  84. $keyManager = $this->keyManager;
  85. if ($keyManager->checkRecoveryPassword($recoveryPassword)) {
  86. // Set recoveryAdmin as disabled
  87. $this->config->setAppValue('encryption', 'recoveryAdminEnabled', '0');
  88. return true;
  89. }
  90. return false;
  91. }
  92. /**
  93. * check if recovery is enabled for user
  94. *
  95. * @param string $user if no user is given we check the current logged-in user
  96. *
  97. * @return bool
  98. */
  99. public function isRecoveryEnabledForUser($user = '') {
  100. $uid = $user === '' ? $this->user->getUID() : $user;
  101. $recoveryMode = $this->config->getUserValue($uid,
  102. 'encryption',
  103. 'recoveryEnabled',
  104. 0);
  105. return ($recoveryMode === '1');
  106. }
  107. /**
  108. * check if recovery is key is enabled by the administrator
  109. *
  110. * @return bool
  111. */
  112. public function isRecoveryKeyEnabled() {
  113. $enabled = $this->config->getAppValue('encryption', 'recoveryAdminEnabled', '0');
  114. return ($enabled === '1');
  115. }
  116. /**
  117. * @param string $value
  118. * @return bool
  119. */
  120. public function setRecoveryForUser($value) {
  121. try {
  122. $this->config->setUserValue($this->user->getUID(),
  123. 'encryption',
  124. 'recoveryEnabled',
  125. $value);
  126. if ($value === '1') {
  127. $this->addRecoveryKeys('/' . $this->user->getUID() . '/files/');
  128. } else {
  129. $this->removeRecoveryKeys('/' . $this->user->getUID() . '/files/');
  130. }
  131. return true;
  132. } catch (PreConditionNotMetException $e) {
  133. return false;
  134. }
  135. }
  136. /**
  137. * add recovery key to all encrypted files
  138. */
  139. private function addRecoveryKeys(string $path): void {
  140. $dirContent = $this->view->getDirectoryContent($path);
  141. foreach ($dirContent as $item) {
  142. $filePath = $item->getPath();
  143. if ($item['type'] === 'dir') {
  144. $this->addRecoveryKeys($filePath . '/');
  145. } else {
  146. $fileKey = $this->keyManager->getFileKey($filePath, $this->user->getUID(), null);
  147. if (!empty($fileKey)) {
  148. $accessList = $this->file->getAccessList($filePath);
  149. $publicKeys = [];
  150. foreach ($accessList['users'] as $uid) {
  151. $publicKeys[$uid] = $this->keyManager->getPublicKey($uid);
  152. }
  153. $publicKeys = $this->keyManager->addSystemKeys($accessList, $publicKeys, $this->user->getUID());
  154. $shareKeys = $this->crypt->multiKeyEncrypt($fileKey, $publicKeys);
  155. $this->keyManager->deleteLegacyFileKey($filePath);
  156. foreach ($shareKeys as $uid => $keyFile) {
  157. $this->keyManager->setShareKey($filePath, $uid, $keyFile);
  158. }
  159. }
  160. }
  161. }
  162. }
  163. /**
  164. * remove recovery key to all encrypted files
  165. */
  166. private function removeRecoveryKeys(string $path): void {
  167. $dirContent = $this->view->getDirectoryContent($path);
  168. foreach ($dirContent as $item) {
  169. $filePath = $item->getPath();
  170. if ($item['type'] === 'dir') {
  171. $this->removeRecoveryKeys($filePath . '/');
  172. } else {
  173. $this->keyManager->deleteShareKey($filePath, $this->keyManager->getRecoveryKeyId());
  174. }
  175. }
  176. }
  177. /**
  178. * recover users files with the recovery key
  179. */
  180. public function recoverUsersFiles(string $recoveryPassword, string $user): void {
  181. $encryptedKey = $this->keyManager->getSystemPrivateKey($this->keyManager->getRecoveryKeyId());
  182. $privateKey = $this->crypt->decryptPrivateKey($encryptedKey, $recoveryPassword);
  183. if ($privateKey !== false) {
  184. $this->recoverAllFiles('/' . $user . '/files/', $privateKey, $user);
  185. }
  186. }
  187. /**
  188. * recover users files
  189. */
  190. private function recoverAllFiles(string $path, string $privateKey, string $uid): void {
  191. $dirContent = $this->view->getDirectoryContent($path);
  192. foreach ($dirContent as $item) {
  193. // Get relative path from encryption/keyfiles
  194. $filePath = $item->getPath();
  195. if ($this->view->is_dir($filePath)) {
  196. $this->recoverAllFiles($filePath . '/', $privateKey, $uid);
  197. } else {
  198. $this->recoverFile($filePath, $privateKey, $uid);
  199. }
  200. }
  201. }
  202. /**
  203. * recover file
  204. */
  205. private function recoverFile(string $path, string $privateKey, string $uid): void {
  206. $encryptedFileKey = $this->keyManager->getEncryptedFileKey($path);
  207. $shareKey = $this->keyManager->getShareKey($path, $this->keyManager->getRecoveryKeyId());
  208. if ($encryptedFileKey && $shareKey && $privateKey) {
  209. $fileKey = $this->crypt->multiKeyDecryptLegacy($encryptedFileKey,
  210. $shareKey,
  211. $privateKey);
  212. } elseif ($shareKey && $privateKey) {
  213. $fileKey = $this->crypt->multiKeyDecrypt($shareKey, $privateKey);
  214. }
  215. if (!empty($fileKey)) {
  216. $accessList = $this->file->getAccessList($path);
  217. $publicKeys = [];
  218. foreach ($accessList['users'] as $user) {
  219. $publicKeys[$user] = $this->keyManager->getPublicKey($user);
  220. }
  221. $publicKeys = $this->keyManager->addSystemKeys($accessList, $publicKeys, $uid);
  222. $shareKeys = $this->crypt->multiKeyEncrypt($fileKey, $publicKeys);
  223. $this->keyManager->deleteLegacyFileKey($path);
  224. foreach ($shareKeys as $uid => $keyFile) {
  225. $this->keyManager->setShareKey($path, $uid, $keyFile);
  226. }
  227. }
  228. }
  229. }