aboutsummaryrefslogtreecommitdiffstats
path: root/apps/encryption/lib/Services/PassphraseService.php
blob: 0786cd3399ac25cebdca2ea29856de92ce140b82 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
<?php

declare(strict_types=1);
/**
 * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
 * SPDX-License-Identifier: AGPL-3.0-or-later
 */

namespace OCA\Encryption\Services;

use OC\Files\Filesystem;
use OCA\Encryption\Crypto\Crypt;
use OCA\Encryption\KeyManager;
use OCA\Encryption\Recovery;
use OCA\Encryption\Session;
use OCA\Encryption\Util;
use OCP\Encryption\Exceptions\GenericEncryptionException;
use OCP\IUser;
use OCP\IUserManager;
use OCP\IUserSession;
use Psr\Log\LoggerInterface;

class PassphraseService {

	/** @var array<string, bool> */
	private static array $passwordResetUsers = [];

	public function __construct(
		private Util $util,
		private Crypt $crypt,
		private Session $session,
		private Recovery $recovery,
		private KeyManager $keyManager,
		private LoggerInterface $logger,
		private IUserManager $userManager,
		private IUserSession $userSession,
	) {
	}

	public function setProcessingReset(string $uid, bool $processing = true): void {
		if ($processing) {
			self::$passwordResetUsers[$uid] = true;
		} else {
			unset(self::$passwordResetUsers[$uid]);
		}
	}

	/**
	 * Change a user's encryption passphrase
	 */
	public function setPassphraseForUser(string $userId, string $password, ?string $recoveryPassword = null): bool {
		// if we are in the process to resetting a user password, we have nothing
		// to do here
		if (isset(self::$passwordResetUsers[$userId])) {
			return true;
		}

		// Check user exists on backend
		$user = $this->userManager->get($userId);
		if ($user === null) {
			return false;
		}

		// Get existing decrypted private key
		$currentUser = $this->userSession->getUser();

		// current logged in user changes his own password
		if ($currentUser !== null && $userId === $currentUser->getUID()) {
			$privateKey = $this->session->getPrivateKey();

			// Encrypt private key with new user pwd as passphrase
			$encryptedPrivateKey = $this->crypt->encryptPrivateKey($privateKey, $password, $userId);

			// Save private key
			if ($encryptedPrivateKey !== false) {
				$key = $this->crypt->generateHeader() . $encryptedPrivateKey;
				$this->keyManager->setPrivateKey($userId, $key);
				return true;
			}

			$this->logger->error('Encryption could not update users encryption password');

			// NOTE: Session does not need to be updated as the
			// private key has not changed, only the passphrase
			// used to decrypt it has changed
		} else {
			// admin changed the password for a different user, create new keys and re-encrypt file keys
			$recoveryPassword = $recoveryPassword ?? '';
			$this->initMountPoints($user);

			$recoveryKeyId = $this->keyManager->getRecoveryKeyId();
			$recoveryKey = $this->keyManager->getSystemPrivateKey($recoveryKeyId);
			try {
				$this->crypt->decryptPrivateKey($recoveryKey, $recoveryPassword);
			} catch (\Exception) {
				$message = 'Can not decrypt the recovery key. Maybe you provided the wrong password. Try again.';
				throw new GenericEncryptionException($message, $message);
			}

			// we generate new keys if...
			// ...we have a recovery password and the user enabled the recovery key
			// ...encryption was activated for the first time (no keys exists)
			// ...the user doesn't have any files
			if (
				($this->recovery->isRecoveryEnabledForUser($userId) && $recoveryPassword !== '')
				|| !$this->keyManager->userHasKeys($userId)
				|| !$this->util->userHasFiles($userId)
			) {
				$keyPair = $this->crypt->createKeyPair();
				if ($keyPair === false) {
					$this->logger->error('Could not create new private key-pair for user.');
					return false;
				}

				// Save public key
				$this->keyManager->setPublicKey($userId, $keyPair['publicKey']);

				// Encrypt private key with new password
				$encryptedKey = $this->crypt->encryptPrivateKey($keyPair['privateKey'], $password, $userId);
				if ($encryptedKey === false) {
					$this->logger->error('Encryption could not update users encryption password');
					return false;
				}

				$this->keyManager->setPrivateKey($userId, $this->crypt->generateHeader() . $encryptedKey);

				if ($recoveryPassword !== '') {
					// if recovery key is set we can re-encrypt the key files
					$this->recovery->recoverUsersFiles($recoveryPassword, $userId);
				}
				return true;
			}
		}
		return false;
	}

	/**
	 * Init mount points for given user
	 */
	private function initMountPoints(IUser $user): void {
		Filesystem::initMountPoints($user);
	}
}