1<?php
2/**
3 * @author Björn Schießle <bjoern@schiessle.org>
4 * @author Clark Tomlinson <fallen013@gmail.com>
5 * @author Lukas Reschke <lukas@statuscode.ch>
6 * @author Thomas Müller <thomas.mueller@tmit.eu>
7 * @author Vincent Petry <pvince81@owncloud.com>
8 *
9 * @copyright Copyright (c) 2019, ownCloud GmbH
10 * @license AGPL-3.0
11 *
12 * This code is free software: you can redistribute it and/or modify
13 * it under the terms of the GNU Affero General Public License, version 3,
14 * as published by the Free Software Foundation.
15 *
16 * This program 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 License, version 3,
22 * along with this program.  If not, see <http://www.gnu.org/licenses/>
23 *
24 */
25namespace OCA\Encryption;
26
27use OC\Encryption\Exceptions\DecryptionFailedException;
28use OC\Files\View;
29use OCA\Encryption\Crypto\Encryption;
30use OCA\Encryption\Exceptions\PrivateKeyMissingException;
31use OCA\Encryption\Exceptions\PublicKeyMissingException;
32use OCA\Encryption\Crypto\Crypt;
33use OCP\Encryption\Keys\IStorage;
34use OCP\IConfig;
35use OCP\ILogger;
36use OCP\IUserSession;
37
38class KeyManager {
39
40	/**
41	 * @var Session
42	 */
43	protected $session;
44	/**
45	 * @var IStorage
46	 */
47	private $keyStorage;
48	/**
49	 * @var Crypt
50	 */
51	private $crypt;
52	/**
53	 * @var string
54	 */
55	private $recoveryKeyId;
56	/**
57	 * @var string
58	 */
59	private $publicShareKeyId;
60	/**
61	 * @var string
62	 */
63	private $masterKeyId;
64	/**
65	 * @var string UserID
66	 */
67	private $keyId;
68	/**
69	 * @var string
70	 */
71	private $publicKeyId = 'publicKey';
72	/**
73	 * @var string
74	 */
75	private $privateKeyId = 'privateKey';
76
77	/**
78	 * @var string
79	 */
80	private $shareKeyId = 'shareKey';
81
82	/**
83	 * @var string
84	 */
85	private $fileKeyId = 'fileKey';
86	/**
87	 * @var IConfig
88	 */
89	private $config;
90	/**
91	 * @var ILogger
92	 */
93	private $log;
94	/**
95	 * @var Util
96	 */
97	private $util;
98
99	/**
100	 * @param IStorage $keyStorage
101	 * @param Crypt $crypt
102	 * @param IConfig $config
103	 * @param IUserSession $userSession
104	 * @param Session $session
105	 * @param ILogger $log
106	 * @param Util $util
107	 */
108	public function __construct(
109		IStorage $keyStorage,
110		Crypt $crypt,
111		IConfig $config,
112		IUserSession $userSession,
113		Session $session,
114		ILogger $log,
115		Util $util
116	) {
117		$this->util = $util;
118		$this->session = $session;
119		$this->keyStorage = $keyStorage;
120		$this->crypt = $crypt;
121		$this->config = $config;
122		$this->log = $log;
123
124		$this->recoveryKeyId = $this->config->getAppValue('encryption',
125			'recoveryKeyId');
126		if (empty($this->recoveryKeyId)) {
127			$this->recoveryKeyId = 'recoveryKey_' . \substr(\md5((string)\time()), 0, 8);
128			$this->config->setAppValue('encryption',
129				'recoveryKeyId',
130				$this->recoveryKeyId);
131		}
132
133		$this->setPublicShareKeyIDAndMasterKeyId();
134
135		$this->keyId = $userSession !== null && $userSession->isLoggedIn() ? $userSession->getUser()->getUID() : false;
136		$this->log = $log;
137	}
138
139	/**
140	 * check if key pair for public link shares exists, if not we create one
141	 */
142	public function validateShareKey() {
143		$shareKey = $this->getPublicShareKey();
144		if (empty($shareKey)) {
145			$keyPair = $this->crypt->createKeyPair();
146
147			// Save public key
148			$this->keyStorage->setSystemUserKey(
149				$this->publicShareKeyId . '.publicKey', $keyPair['publicKey'],
150				Encryption::ID);
151
152			// Encrypt private key empty passphrase
153			$encryptedKey = $this->crypt->encryptPrivateKey($keyPair['privateKey'], '');
154			$header = $this->crypt->generateHeader();
155			$this->setSystemPrivateKey($this->publicShareKeyId, $header . $encryptedKey);
156		}
157	}
158
159	/**
160	 * check if a key pair for the master key exists, if not we create one
161	 */
162	public function validateMasterKey() {
163		if ($this->util->isMasterKeyEnabled() === false) {
164			return;
165		}
166
167		$masterKey = $this->getPublicMasterKey();
168		if (empty($masterKey)) {
169			$keyPair = $this->crypt->createKeyPair();
170
171			// Save public key
172			$this->keyStorage->setSystemUserKey(
173				$this->masterKeyId . '.publicKey', $keyPair['publicKey'],
174				Encryption::ID);
175
176			// Encrypt private key with system password
177			$encryptedKey = $this->crypt->encryptPrivateKey($keyPair['privateKey'], $this->getMasterKeyPassword(), $this->masterKeyId);
178			$header = $this->crypt->generateHeader();
179			$this->setSystemPrivateKey($this->masterKeyId, $header . $encryptedKey);
180		}
181	}
182
183	/**
184	 * @return bool
185	 */
186	public function recoveryKeyExists() {
187		$key = $this->getRecoveryKey();
188		return (!empty($key));
189	}
190
191	/**
192	 * get recovery key
193	 *
194	 * @return string
195	 */
196	public function getRecoveryKey() {
197		return $this->keyStorage->getSystemUserKey($this->recoveryKeyId . '.publicKey', Encryption::ID);
198	}
199
200	/**
201	 * get recovery key ID
202	 *
203	 * @return string
204	 */
205	public function getRecoveryKeyId() {
206		return $this->recoveryKeyId;
207	}
208
209	/**
210	 * @param string $password
211	 * @return bool
212	 */
213	public function checkRecoveryPassword($password) {
214		$recoveryKey = $this->keyStorage->getSystemUserKey($this->recoveryKeyId . '.privateKey', Encryption::ID);
215		$decryptedRecoveryKey = $this->crypt->decryptPrivateKey($recoveryKey, $password);
216
217		if ($decryptedRecoveryKey) {
218			return true;
219		}
220		return false;
221	}
222
223	/**
224	 * @param string $uid
225	 * @param string $password
226	 * @param array $keyPair
227	 * @return bool
228	 */
229	public function storeKeyPair($uid, $password, $keyPair) {
230		// Save Public Key
231		$this->setPublicKey($uid, $keyPair['publicKey']);
232
233		$encryptedKey = $this->crypt->encryptPrivateKey($keyPair['privateKey'], $password, $uid);
234
235		$header = $this->crypt->generateHeader();
236
237		if ($encryptedKey) {
238			$this->setPrivateKey($uid, $header . $encryptedKey);
239			return true;
240		}
241		return false;
242	}
243
244	/**
245	 * @param string $password
246	 * @param array $keyPair
247	 * @return bool
248	 */
249	public function setRecoveryKey($password, $keyPair) {
250		// Save Public Key
251		$this->keyStorage->setSystemUserKey($this->getRecoveryKeyId() .
252			'.publicKey',
253			$keyPair['publicKey'],
254			Encryption::ID);
255
256		$encryptedKey = $this->crypt->encryptPrivateKey($keyPair['privateKey'], $password);
257		$header = $this->crypt->generateHeader();
258
259		if ($encryptedKey) {
260			$this->setSystemPrivateKey($this->getRecoveryKeyId(), $header . $encryptedKey);
261			return true;
262		}
263		return false;
264	}
265
266	/**
267	 * @param string $userId
268	 * @param string $key
269	 * @return bool
270	 */
271	public function setPublicKey($userId, $key) {
272		return $this->keyStorage->setUserKey($userId, $this->publicKeyId, $key, Encryption::ID);
273	}
274
275	/**
276	 * @param string $userId
277	 * @param string $key
278	 * @return bool
279	 */
280	public function setPrivateKey($userId, $key) {
281		return $this->keyStorage->setUserKey($userId,
282			$this->privateKeyId,
283			$key,
284			Encryption::ID);
285	}
286
287	/**
288	 * write file key to key storage
289	 *
290	 * @param string $path
291	 * @param string $key
292	 * @return boolean
293	 */
294	public function setFileKey($path, $key) {
295		return $this->keyStorage->setFileKey($path, $this->fileKeyId, $key, Encryption::ID);
296	}
297
298	/**
299	 * set all file keys (the file key and the corresponding share keys)
300	 *
301	 * @param string $path
302	 * @param array $keys
303	 */
304	public function setAllFileKeys($path, $keys) {
305		$this->setFileKey($path, $keys['data']);
306		foreach ($keys['keys'] as $uid => $keyFile) {
307			$this->setShareKey($path, $uid, $keyFile);
308		}
309	}
310
311	/**
312	 * write share key to the key storage
313	 *
314	 * @param string $path
315	 * @param string $uid
316	 * @param string $key
317	 * @return boolean
318	 */
319	public function setShareKey($path, $uid, $key) {
320		$keyId = $uid . '.' . $this->shareKeyId;
321		return $this->keyStorage->setFileKey($path, $keyId, $key, Encryption::ID);
322	}
323
324	/**
325	 * Decrypt private key and store it
326	 *
327	 * @param string $uid user id
328	 * @param string $passPhrase users password
329	 * @return boolean
330	 */
331	public function init($uid, $passPhrase) {
332		$this->session->setStatus(Session::INIT_EXECUTED);
333
334		try {
335			if ($this->util->isMasterKeyEnabled()) {
336				$uid = $this->getMasterKeyId();
337				$passPhrase = $this->getMasterKeyPassword();
338				$privateKey = $this->getSystemPrivateKey($uid);
339			} else {
340				$privateKey = $this->getPrivateKey($uid);
341			}
342			$privateKey = $this->crypt->decryptPrivateKey($privateKey, $passPhrase, $uid);
343		} catch (PrivateKeyMissingException $e) {
344			return false;
345		} catch (DecryptionFailedException $e) {
346			return false;
347		} catch (\Exception $e) {
348			$this->log->warning(
349				'Could not decrypt the private key from user "' . $uid . '"" during login. ' .
350				'Assume password change on the user back-end. Error message: '
351				. $e->getMessage()
352			);
353			return false;
354		}
355
356		if ($privateKey) {
357			$this->session->setPrivateKey($privateKey);
358			$this->session->setStatus(Session::INIT_SUCCESSFUL);
359			return true;
360		}
361
362		return false;
363	}
364
365	/**
366	 * @param string $userId
367	 * @return string
368	 * @throws PrivateKeyMissingException
369	 */
370	public function getPrivateKey($userId) {
371		$privateKey = $this->keyStorage->getUserKey($userId,
372			$this->privateKeyId, Encryption::ID);
373
374		if (\strlen($privateKey) !== 0) {
375			return $privateKey;
376		}
377		throw new PrivateKeyMissingException($userId);
378	}
379
380	/**
381	 * @param string $path
382	 * @param string $uid
383	 * @return string
384	 */
385	public function getFileKey($path, $uid) {
386		if ($uid === '') {
387			$uid = null;
388		}
389		$publicAccess = ($uid === null);
390		$encryptedFileKey = $this->keyStorage->getFileKey($path, $this->fileKeyId, Encryption::ID);
391
392		if (empty($encryptedFileKey)) {
393			return '';
394		}
395
396		if ($this->util->isMasterKeyEnabled()) {
397			$uid = $this->getMasterKeyId();
398			$shareKey = $this->getShareKey($path, $uid);
399			if ($publicAccess) {
400				$privateKey = $this->getSystemPrivateKey($uid);
401				$privateKey = $this->crypt->decryptPrivateKey($privateKey, $this->getMasterKeyPassword(), $uid);
402			} else {
403				// when logged in, the master key is already decrypted in the session
404				$privateKey = $this->session->getPrivateKey();
405			}
406		} elseif ($publicAccess) {
407			// use public share key for public links
408			$uid = $this->getPublicShareKeyId();
409			$shareKey = $this->getShareKey($path, $uid);
410			$privateKey = $this->keyStorage->getSystemUserKey($this->publicShareKeyId . '.privateKey', Encryption::ID);
411			$privateKey = $this->crypt->decryptPrivateKey($privateKey);
412		} else {
413			$shareKey = $this->getShareKey($path, $uid);
414			$privateKey = $this->session->getPrivateKey();
415		}
416
417		if ($encryptedFileKey && $shareKey && $privateKey) {
418			return $this->crypt->multiKeyDecrypt($encryptedFileKey,
419				$shareKey,
420				$privateKey);
421		}
422
423		return '';
424	}
425
426	/**
427	 * Get the current version of a file
428	 *
429	 * @param string $path
430	 * @param View $view
431	 * @return int
432	 */
433	public function getVersion($path, View $view) {
434		$fileInfo = $view->getFileInfo($path);
435		if ($fileInfo === false) {
436			return 0;
437		}
438		return $fileInfo->getEncryptedVersion();
439	}
440
441	/**
442	 * Set the current version of a file
443	 *
444	 * @param string $path
445	 * @param int $version
446	 * @param View $view
447	 */
448	public function setVersion($path, $version, View $view) {
449		$fileInfo = $view->getFileInfo($path);
450
451		if ($fileInfo !== false) {
452			$cache = $fileInfo->getStorage()->getCache();
453			$cache->update($fileInfo->getId(), ['encrypted' => $version, 'encryptedVersion' => $version]);
454		}
455	}
456
457	/**
458	 * get the encrypted file key
459	 *
460	 * @param string $path
461	 * @return string
462	 */
463	public function getEncryptedFileKey($path) {
464		$encryptedFileKey = $this->keyStorage->getFileKey($path,
465			$this->fileKeyId, Encryption::ID);
466
467		return $encryptedFileKey;
468	}
469
470	/**
471	 * delete share key
472	 *
473	 * @param string $path
474	 * @param string $keyId
475	 * @return boolean
476	 */
477	public function deleteShareKey($path, $keyId) {
478		return $this->keyStorage->deleteFileKey(
479			$path,
480			$keyId . '.' . $this->shareKeyId,
481			Encryption::ID);
482	}
483
484	/**
485	 * @param string $path
486	 * @param string $uid
487	 * @return mixed
488	 */
489	public function getShareKey($path, $uid) {
490		$keyId = $uid . '.' . $this->shareKeyId;
491		return $this->keyStorage->getFileKey($path, $keyId, Encryption::ID);
492	}
493
494	/**
495	 * check if user has a private and a public key
496	 *
497	 * @param string $userId
498	 * @return bool
499	 * @throws PrivateKeyMissingException
500	 * @throws PublicKeyMissingException
501	 */
502	public function userHasKeys($userId) {
503		$privateKey = $publicKey = true;
504		$exception = null;
505
506		try {
507			$this->getPrivateKey($userId);
508		} catch (PrivateKeyMissingException $e) {
509			$privateKey = false;
510			$exception = $e;
511		}
512		try {
513			$this->getPublicKey($userId);
514		} catch (PublicKeyMissingException $e) {
515			$publicKey = false;
516			$exception = $e;
517		}
518
519		if ($privateKey && $publicKey) {
520			return true;
521		} elseif (!$privateKey && !$publicKey) {
522			return false;
523		} else {
524			throw $exception;
525		}
526	}
527
528	/**
529	 * @param string $userId
530	 * @return mixed
531	 * @throws PublicKeyMissingException
532	 */
533	public function getPublicKey($userId) {
534		$publicKey = $this->keyStorage->getUserKey($userId, $this->publicKeyId, Encryption::ID);
535
536		if (\strlen($publicKey) !== 0) {
537			return $publicKey;
538		}
539		throw new PublicKeyMissingException($userId);
540	}
541
542	public function getPublicShareKeyId() {
543		return $this->publicShareKeyId;
544	}
545
546	/**
547	 * get public key for public link shares
548	 *
549	 * @return string
550	 */
551	public function getPublicShareKey() {
552		return $this->keyStorage->getSystemUserKey($this->publicShareKeyId . '.publicKey', Encryption::ID);
553	}
554
555	/**
556	 * @param string $purpose
557	 * @param bool $timestamp
558	 * @param bool $includeUserKeys
559	 */
560	public function backupAllKeys($purpose, $timestamp = true, $includeUserKeys = true) {
561//		$backupDir = $this->keyStorage->;
562	}
563
564	/**
565	 * creat a backup of the users private and public key and then  delete it
566	 *
567	 * @param string $uid
568	 */
569	public function deleteUserKeys($uid) {
570		$this->backupAllKeys('password_reset');
571		$this->deletePublicKey($uid);
572		$this->deletePrivateKey($uid);
573	}
574
575	/**
576	 * @param string $uid
577	 * @return bool
578	 */
579	public function deletePublicKey($uid) {
580		return $this->keyStorage->deleteUserKey($uid, $this->publicKeyId, Encryption::ID);
581	}
582
583	/**
584	 * @param string $uid
585	 * @return bool
586	 */
587	private function deletePrivateKey($uid) {
588		return $this->keyStorage->deleteUserKey($uid, $this->privateKeyId, Encryption::ID);
589	}
590
591	/**
592	 * @param string $path
593	 * @return bool
594	 */
595	public function deleteAllFileKeys($path) {
596		return $this->keyStorage->deleteAllFileKeys($path);
597	}
598
599	/**
600	 * @param array $userIds
601	 * @return array
602	 * @throws PublicKeyMissingException
603	 */
604	public function getPublicKeys(array $userIds) {
605		$keys = [];
606
607		foreach ($userIds as $userId) {
608			try {
609				$keys[$userId] = $this->getPublicKey($userId);
610			} catch (PublicKeyMissingException $e) {
611				continue;
612			}
613		}
614
615		return $keys;
616	}
617
618	/**
619	 * @param string $keyId
620	 * @return string returns openssl key
621	 */
622	public function getSystemPrivateKey($keyId) {
623		return $this->keyStorage->getSystemUserKey($keyId . '.' . $this->privateKeyId, Encryption::ID);
624	}
625
626	/**
627	 * @param string $keyId
628	 * @param string $key
629	 * @return string returns openssl key
630	 */
631	public function setSystemPrivateKey($keyId, $key) {
632		return $this->keyStorage->setSystemUserKey(
633			$keyId . '.' . $this->privateKeyId,
634			$key,
635			Encryption::ID);
636	}
637
638	/**
639	 * add system keys such as the public share key and the recovery key
640	 *
641	 * @param array $accessList
642	 * @param array $publicKeys
643	 * @param string $uid
644	 * @return array
645	 * @throws PublicKeyMissingException
646	 */
647	public function addSystemKeys(array $accessList, array $publicKeys, $uid) {
648		if (!empty($accessList['public'])) {
649			$publicShareKey = $this->getPublicShareKey();
650			if (empty($publicShareKey)) {
651				throw new PublicKeyMissingException($this->getPublicShareKeyId());
652			}
653			$publicKeys[$this->getPublicShareKeyId()] = $publicShareKey;
654		}
655
656		if ($this->recoveryKeyExists() &&
657			$this->util->isRecoveryEnabledForUser($uid)) {
658			$publicKeys[$this->getRecoveryKeyId()] = $this->getRecoveryKey();
659		}
660
661		return $publicKeys;
662	}
663
664	/**
665	 * get master key password
666	 *
667	 * @return string
668	 * @throws \Exception
669	 */
670	public function getMasterKeyPassword() {
671		$password = $this->config->getSystemValue('secret');
672		if (empty($password)) {
673			throw new \Exception('Can not get secret from ownCloud instance');
674		}
675
676		return $password;
677	}
678
679	/**
680	 * return master key id
681	 *
682	 * @return string
683	 */
684	public function getMasterKeyId() {
685		if ($this->config->getAppValue('encryption', 'masterKeyId') !== $this->masterKeyId) {
686			$this->masterKeyId = $this->config->getAppValue('encryption', 'masterKeyId');
687		}
688		return $this->masterKeyId;
689	}
690
691	/**
692	 * get public master key
693	 *
694	 * @return string
695	 */
696	public function getPublicMasterKey() {
697		return $this->keyStorage->getSystemUserKey($this->masterKeyId . '.publicKey', Encryption::ID);
698	}
699
700	/**
701	 * set publicShareKeyId and masterKeyId if not set
702	 */
703	public function setPublicShareKeyIDAndMasterKeyId() {
704		$this->publicShareKeyId = $this->config->getAppValue('encryption',
705			'publicShareKeyId');
706		if (($this->publicShareKeyId === null) || ($this->publicShareKeyId === '')) {
707			$this->publicShareKeyId = 'pubShare_' . \substr(\md5((string)\time()), 0, 8);
708			$this->config->setAppValue('encryption', 'publicShareKeyId', $this->publicShareKeyId);
709		}
710
711		$this->masterKeyId = $this->config->getAppValue('encryption',
712			'masterKeyId');
713		if (($this->masterKeyId === null) || ($this->masterKeyId === '')) {
714			$this->masterKeyId = 'master_' . \substr(\md5((string)\time()), 0, 8);
715			$this->config->setAppValue('encryption', 'masterKeyId', $this->masterKeyId);
716		}
717	}
718}
719