PassphraseService.php 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
  5. * SPDX-License-Identifier: AGPL-3.0-or-later
  6. */
  7. namespace OCA\Encryption\Services;
  8. use OC\Files\Filesystem;
  9. use OCA\Encryption\Crypto\Crypt;
  10. use OCA\Encryption\KeyManager;
  11. use OCA\Encryption\Recovery;
  12. use OCA\Encryption\Session;
  13. use OCA\Encryption\Util;
  14. use OCP\Encryption\Exceptions\GenericEncryptionException;
  15. use OCP\IUser;
  16. use OCP\IUserManager;
  17. use OCP\IUserSession;
  18. use Psr\Log\LoggerInterface;
  19. class PassphraseService {
  20. /** @var array<string, bool> */
  21. private static array $passwordResetUsers = [];
  22. public function __construct(
  23. private Util $util,
  24. private Crypt $crypt,
  25. private Session $session,
  26. private Recovery $recovery,
  27. private KeyManager $keyManager,
  28. private LoggerInterface $logger,
  29. private IUserManager $userManager,
  30. private IUserSession $userSession,
  31. ) {
  32. }
  33. public function setProcessingReset(string $uid, bool $processing = true): void {
  34. if ($processing) {
  35. self::$passwordResetUsers[$uid] = true;
  36. } else {
  37. unset(self::$passwordResetUsers[$uid]);
  38. }
  39. }
  40. /**
  41. * Change a user's encryption passphrase
  42. */
  43. public function setPassphraseForUser(string $userId, string $password, ?string $recoveryPassword = null): bool {
  44. // if we are in the process to resetting a user password, we have nothing
  45. // to do here
  46. if (isset(self::$passwordResetUsers[$userId])) {
  47. return true;
  48. }
  49. // Check user exists on backend
  50. $user = $this->userManager->get($userId);
  51. if ($user === null) {
  52. return false;
  53. }
  54. // Get existing decrypted private key
  55. $currentUser = $this->userSession->getUser();
  56. // current logged in user changes his own password
  57. if ($currentUser !== null && $userId === $currentUser->getUID()) {
  58. $privateKey = $this->session->getPrivateKey();
  59. // Encrypt private key with new user pwd as passphrase
  60. $encryptedPrivateKey = $this->crypt->encryptPrivateKey($privateKey, $password, $userId);
  61. // Save private key
  62. if ($encryptedPrivateKey !== false) {
  63. $key = $this->crypt->generateHeader() . $encryptedPrivateKey;
  64. $this->keyManager->setPrivateKey($userId, $key);
  65. return true;
  66. }
  67. $this->logger->error('Encryption could not update users encryption password');
  68. // NOTE: Session does not need to be updated as the
  69. // private key has not changed, only the passphrase
  70. // used to decrypt it has changed
  71. } else {
  72. // admin changed the password for a different user, create new keys and re-encrypt file keys
  73. $recoveryPassword = $recoveryPassword ?? '';
  74. $this->initMountPoints($user);
  75. $recoveryKeyId = $this->keyManager->getRecoveryKeyId();
  76. $recoveryKey = $this->keyManager->getSystemPrivateKey($recoveryKeyId);
  77. try {
  78. $this->crypt->decryptPrivateKey($recoveryKey, $recoveryPassword);
  79. } catch (\Exception) {
  80. $message = 'Can not decrypt the recovery key. Maybe you provided the wrong password. Try again.';
  81. throw new GenericEncryptionException($message, $message);
  82. }
  83. // we generate new keys if...
  84. // ...we have a recovery password and the user enabled the recovery key
  85. // ...encryption was activated for the first time (no keys exists)
  86. // ...the user doesn't have any files
  87. if (
  88. ($this->recovery->isRecoveryEnabledForUser($userId) && $recoveryPassword !== '')
  89. || !$this->keyManager->userHasKeys($userId)
  90. || !$this->util->userHasFiles($userId)
  91. ) {
  92. $keyPair = $this->crypt->createKeyPair();
  93. if ($keyPair === false) {
  94. $this->logger->error('Could not create new private key-pair for user.');
  95. return false;
  96. }
  97. // Save public key
  98. $this->keyManager->setPublicKey($userId, $keyPair['publicKey']);
  99. // Encrypt private key with new password
  100. $encryptedKey = $this->crypt->encryptPrivateKey($keyPair['privateKey'], $password, $userId);
  101. if ($encryptedKey === false) {
  102. $this->logger->error('Encryption could not update users encryption password');
  103. return false;
  104. }
  105. $this->keyManager->setPrivateKey($userId, $this->crypt->generateHeader() . $encryptedKey);
  106. if ($recoveryPassword !== '') {
  107. // if recovery key is set we can re-encrypt the key files
  108. $this->recovery->recoverUsersFiles($recoveryPassword, $userId);
  109. }
  110. return true;
  111. }
  112. }
  113. return false;
  114. }
  115. /**
  116. * Init mount points for given user
  117. */
  118. private function initMountPoints(IUser $user): void {
  119. Filesystem::initMountPoints($user);
  120. }
  121. }