diff options
Diffstat (limited to 'apps/encryption/lib/KeyManager.php')
-rw-r--r-- | apps/encryption/lib/KeyManager.php | 697 |
1 files changed, 697 insertions, 0 deletions
diff --git a/apps/encryption/lib/KeyManager.php b/apps/encryption/lib/KeyManager.php new file mode 100644 index 00000000000..4f22c3def63 --- /dev/null +++ b/apps/encryption/lib/KeyManager.php @@ -0,0 +1,697 @@ +<?php +/** + * @author Björn Schießle <schiessle@owncloud.com> + * @author Clark Tomlinson <fallen013@gmail.com> + * @author Lukas Reschke <lukas@owncloud.com> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * @author Vincent Petry <pvince81@owncloud.com> + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ +namespace OCA\Encryption; + +use OC\Encryption\Exceptions\DecryptionFailedException; +use OC\Files\View; +use OCA\Encryption\Crypto\Encryption; +use OCA\Encryption\Exceptions\PrivateKeyMissingException; +use OCA\Encryption\Exceptions\PublicKeyMissingException; +use OCA\Encryption\Crypto\Crypt; +use OCP\Encryption\Keys\IStorage; +use OCP\IConfig; +use OCP\IDBConnection; +use OCP\ILogger; +use OCP\IUserSession; + +class KeyManager { + + /** + * @var Session + */ + protected $session; + /** + * @var IStorage + */ + private $keyStorage; + /** + * @var Crypt + */ + private $crypt; + /** + * @var string + */ + private $recoveryKeyId; + /** + * @var string + */ + private $publicShareKeyId; + /** + * @var string + */ + private $masterKeyId; + /** + * @var string UserID + */ + private $keyId; + /** + * @var string + */ + private $publicKeyId = 'publicKey'; + /** + * @var string + */ + private $privateKeyId = 'privateKey'; + + /** + * @var string + */ + private $shareKeyId = 'shareKey'; + + /** + * @var string + */ + private $fileKeyId = 'fileKey'; + /** + * @var IConfig + */ + private $config; + /** + * @var ILogger + */ + private $log; + /** + * @var Util + */ + private $util; + + /** + * @param IStorage $keyStorage + * @param Crypt $crypt + * @param IConfig $config + * @param IUserSession $userSession + * @param Session $session + * @param ILogger $log + * @param Util $util + */ + public function __construct( + IStorage $keyStorage, + Crypt $crypt, + IConfig $config, + IUserSession $userSession, + Session $session, + ILogger $log, + Util $util + ) { + + $this->util = $util; + $this->session = $session; + $this->keyStorage = $keyStorage; + $this->crypt = $crypt; + $this->config = $config; + $this->log = $log; + + $this->recoveryKeyId = $this->config->getAppValue('encryption', + 'recoveryKeyId'); + if (empty($this->recoveryKeyId)) { + $this->recoveryKeyId = 'recoveryKey_' . substr(md5(time()), 0, 8); + $this->config->setAppValue('encryption', + 'recoveryKeyId', + $this->recoveryKeyId); + } + + $this->publicShareKeyId = $this->config->getAppValue('encryption', + 'publicShareKeyId'); + if (empty($this->publicShareKeyId)) { + $this->publicShareKeyId = 'pubShare_' . substr(md5(time()), 0, 8); + $this->config->setAppValue('encryption', 'publicShareKeyId', $this->publicShareKeyId); + } + + $this->masterKeyId = $this->config->getAppValue('encryption', + 'masterKeyId'); + if (empty($this->masterKeyId)) { + $this->masterKeyId = 'master_' . substr(md5(time()), 0, 8); + $this->config->setAppValue('encryption', 'masterKeyId', $this->masterKeyId); + } + + $this->keyId = $userSession && $userSession->isLoggedIn() ? $userSession->getUser()->getUID() : false; + $this->log = $log; + } + + /** + * check if key pair for public link shares exists, if not we create one + */ + public function validateShareKey() { + $shareKey = $this->getPublicShareKey(); + if (empty($shareKey)) { + $keyPair = $this->crypt->createKeyPair(); + + // Save public key + $this->keyStorage->setSystemUserKey( + $this->publicShareKeyId . '.publicKey', $keyPair['publicKey'], + Encryption::ID); + + // Encrypt private key empty passphrase + $encryptedKey = $this->crypt->encryptPrivateKey($keyPair['privateKey'], ''); + $header = $this->crypt->generateHeader(); + $this->setSystemPrivateKey($this->publicShareKeyId, $header . $encryptedKey); + } + } + + /** + * check if a key pair for the master key exists, if not we create one + */ + public function validateMasterKey() { + + if ($this->util->isMasterKeyEnabled() === false) { + return; + } + + $masterKey = $this->getPublicMasterKey(); + if (empty($masterKey)) { + $keyPair = $this->crypt->createKeyPair(); + + // Save public key + $this->keyStorage->setSystemUserKey( + $this->masterKeyId . '.publicKey', $keyPair['publicKey'], + Encryption::ID); + + // Encrypt private key with system password + $encryptedKey = $this->crypt->encryptPrivateKey($keyPair['privateKey'], $this->getMasterKeyPassword(), $this->masterKeyId); + $header = $this->crypt->generateHeader(); + $this->setSystemPrivateKey($this->masterKeyId, $header . $encryptedKey); + } + } + + /** + * @return bool + */ + public function recoveryKeyExists() { + $key = $this->getRecoveryKey(); + return (!empty($key)); + } + + /** + * get recovery key + * + * @return string + */ + public function getRecoveryKey() { + return $this->keyStorage->getSystemUserKey($this->recoveryKeyId . '.publicKey', Encryption::ID); + } + + /** + * get recovery key ID + * + * @return string + */ + public function getRecoveryKeyId() { + return $this->recoveryKeyId; + } + + /** + * @param string $password + * @return bool + */ + public function checkRecoveryPassword($password) { + $recoveryKey = $this->keyStorage->getSystemUserKey($this->recoveryKeyId . '.privateKey', Encryption::ID); + $decryptedRecoveryKey = $this->crypt->decryptPrivateKey($recoveryKey, $password); + + if ($decryptedRecoveryKey) { + return true; + } + return false; + } + + /** + * @param string $uid + * @param string $password + * @param string $keyPair + * @return bool + */ + public function storeKeyPair($uid, $password, $keyPair) { + // Save Public Key + $this->setPublicKey($uid, $keyPair['publicKey']); + + $encryptedKey = $this->crypt->encryptPrivateKey($keyPair['privateKey'], $password, $uid); + + $header = $this->crypt->generateHeader(); + + if ($encryptedKey) { + $this->setPrivateKey($uid, $header . $encryptedKey); + return true; + } + return false; + } + + /** + * @param string $password + * @param array $keyPair + * @return bool + */ + public function setRecoveryKey($password, $keyPair) { + // Save Public Key + $this->keyStorage->setSystemUserKey($this->getRecoveryKeyId(). + '.publicKey', + $keyPair['publicKey'], + Encryption::ID); + + $encryptedKey = $this->crypt->encryptPrivateKey($keyPair['privateKey'], $password); + $header = $this->crypt->generateHeader(); + + if ($encryptedKey) { + $this->setSystemPrivateKey($this->getRecoveryKeyId(), $header . $encryptedKey); + return true; + } + return false; + } + + /** + * @param $userId + * @param $key + * @return bool + */ + public function setPublicKey($userId, $key) { + return $this->keyStorage->setUserKey($userId, $this->publicKeyId, $key, Encryption::ID); + } + + /** + * @param $userId + * @param string $key + * @return bool + */ + public function setPrivateKey($userId, $key) { + return $this->keyStorage->setUserKey($userId, + $this->privateKeyId, + $key, + Encryption::ID); + } + + /** + * write file key to key storage + * + * @param string $path + * @param string $key + * @return boolean + */ + public function setFileKey($path, $key) { + return $this->keyStorage->setFileKey($path, $this->fileKeyId, $key, Encryption::ID); + } + + /** + * set all file keys (the file key and the corresponding share keys) + * + * @param string $path + * @param array $keys + */ + public function setAllFileKeys($path, $keys) { + $this->setFileKey($path, $keys['data']); + foreach ($keys['keys'] as $uid => $keyFile) { + $this->setShareKey($path, $uid, $keyFile); + } + } + + /** + * write share key to the key storage + * + * @param string $path + * @param string $uid + * @param string $key + * @return boolean + */ + public function setShareKey($path, $uid, $key) { + $keyId = $uid . '.' . $this->shareKeyId; + return $this->keyStorage->setFileKey($path, $keyId, $key, Encryption::ID); + } + + /** + * Decrypt private key and store it + * + * @param string $uid user id + * @param string $passPhrase users password + * @return boolean + */ + public function init($uid, $passPhrase) { + + $this->session->setStatus(Session::INIT_EXECUTED); + + try { + if($this->util->isMasterKeyEnabled()) { + $uid = $this->getMasterKeyId(); + $passPhrase = $this->getMasterKeyPassword(); + $privateKey = $this->getSystemPrivateKey($uid); + } else { + $privateKey = $this->getPrivateKey($uid); + } + $privateKey = $this->crypt->decryptPrivateKey($privateKey, $passPhrase, $uid); + } catch (PrivateKeyMissingException $e) { + return false; + } catch (DecryptionFailedException $e) { + return false; + } + + if ($privateKey) { + $this->session->setPrivateKey($privateKey); + $this->session->setStatus(Session::INIT_SUCCESSFUL); + return true; + } + + return false; + } + + /** + * @param $userId + * @return string + * @throws PrivateKeyMissingException + */ + public function getPrivateKey($userId) { + $privateKey = $this->keyStorage->getUserKey($userId, + $this->privateKeyId, Encryption::ID); + + if (strlen($privateKey) !== 0) { + return $privateKey; + } + throw new PrivateKeyMissingException($userId); + } + + /** + * @param string $path + * @param $uid + * @return string + */ + public function getFileKey($path, $uid) { + $encryptedFileKey = $this->keyStorage->getFileKey($path, $this->fileKeyId, Encryption::ID); + + if (empty($encryptedFileKey)) { + return ''; + } + + if ($this->util->isMasterKeyEnabled()) { + $uid = $this->getMasterKeyId(); + } + + if (is_null($uid)) { + $uid = $this->getPublicShareKeyId(); + $shareKey = $this->getShareKey($path, $uid); + $privateKey = $this->keyStorage->getSystemUserKey($this->publicShareKeyId . '.privateKey', Encryption::ID); + $privateKey = $this->crypt->decryptPrivateKey($privateKey); + } else { + $shareKey = $this->getShareKey($path, $uid); + $privateKey = $this->session->getPrivateKey(); + } + + if ($encryptedFileKey && $shareKey && $privateKey) { + return $this->crypt->multiKeyDecrypt($encryptedFileKey, + $shareKey, + $privateKey); + } + + return ''; + } + + /** + * Get the current version of a file + * + * @param string $path + * @param View $view + * @return int + */ + public function getVersion($path, View $view) { + $fileInfo = $view->getFileInfo($path); + if($fileInfo === false) { + return 0; + } + return $fileInfo->getEncryptedVersion(); + } + + /** + * Set the current version of a file + * + * @param string $path + * @param int $version + * @param View $view + */ + public function setVersion($path, $version, View $view) { + $fileInfo= $view->getFileInfo($path); + + if($fileInfo !== false) { + $cache = $fileInfo->getStorage()->getCache(); + $cache->update($fileInfo->getId(), ['encrypted' => $version, 'encryptedVersion' => $version]); + } + } + + /** + * get the encrypted file key + * + * @param string $path + * @return string + */ + public function getEncryptedFileKey($path) { + $encryptedFileKey = $this->keyStorage->getFileKey($path, + $this->fileKeyId, Encryption::ID); + + return $encryptedFileKey; + } + + /** + * delete share key + * + * @param string $path + * @param string $keyId + * @return boolean + */ + public function deleteShareKey($path, $keyId) { + return $this->keyStorage->deleteFileKey( + $path, + $keyId . '.' . $this->shareKeyId, + Encryption::ID); + } + + + /** + * @param $path + * @param $uid + * @return mixed + */ + public function getShareKey($path, $uid) { + $keyId = $uid . '.' . $this->shareKeyId; + return $this->keyStorage->getFileKey($path, $keyId, Encryption::ID); + } + + /** + * check if user has a private and a public key + * + * @param string $userId + * @return bool + * @throws PrivateKeyMissingException + * @throws PublicKeyMissingException + */ + public function userHasKeys($userId) { + $privateKey = $publicKey = true; + $exception = null; + + try { + $this->getPrivateKey($userId); + } catch (PrivateKeyMissingException $e) { + $privateKey = false; + $exception = $e; + } + try { + $this->getPublicKey($userId); + } catch (PublicKeyMissingException $e) { + $publicKey = false; + $exception = $e; + } + + if ($privateKey && $publicKey) { + return true; + } elseif (!$privateKey && !$publicKey) { + return false; + } else { + throw $exception; + } + } + + /** + * @param $userId + * @return mixed + * @throws PublicKeyMissingException + */ + public function getPublicKey($userId) { + $publicKey = $this->keyStorage->getUserKey($userId, $this->publicKeyId, Encryption::ID); + + if (strlen($publicKey) !== 0) { + return $publicKey; + } + throw new PublicKeyMissingException($userId); + } + + public function getPublicShareKeyId() { + return $this->publicShareKeyId; + } + + /** + * get public key for public link shares + * + * @return string + */ + public function getPublicShareKey() { + return $this->keyStorage->getSystemUserKey($this->publicShareKeyId . '.publicKey', Encryption::ID); + } + + /** + * @param string $purpose + * @param bool $timestamp + * @param bool $includeUserKeys + */ + public function backupAllKeys($purpose, $timestamp = true, $includeUserKeys = true) { +// $backupDir = $this->keyStorage->; + } + + /** + * creat a backup of the users private and public key and then delete it + * + * @param string $uid + */ + public function deleteUserKeys($uid) { + $this->backupAllKeys('password_reset'); + $this->deletePublicKey($uid); + $this->deletePrivateKey($uid); + } + + /** + * @param $uid + * @return bool + */ + public function deletePublicKey($uid) { + return $this->keyStorage->deleteUserKey($uid, $this->publicKeyId, Encryption::ID); + } + + /** + * @param string $uid + * @return bool + */ + private function deletePrivateKey($uid) { + return $this->keyStorage->deleteUserKey($uid, $this->privateKeyId, Encryption::ID); + } + + /** + * @param string $path + * @return bool + */ + public function deleteAllFileKeys($path) { + return $this->keyStorage->deleteAllFileKeys($path); + } + + /** + * @param array $userIds + * @return array + * @throws PublicKeyMissingException + */ + public function getPublicKeys(array $userIds) { + $keys = []; + + foreach ($userIds as $userId) { + try { + $keys[$userId] = $this->getPublicKey($userId); + } catch (PublicKeyMissingException $e) { + continue; + } + } + + return $keys; + + } + + /** + * @param string $keyId + * @return string returns openssl key + */ + public function getSystemPrivateKey($keyId) { + return $this->keyStorage->getSystemUserKey($keyId . '.' . $this->privateKeyId, Encryption::ID); + } + + /** + * @param string $keyId + * @param string $key + * @return string returns openssl key + */ + public function setSystemPrivateKey($keyId, $key) { + return $this->keyStorage->setSystemUserKey( + $keyId . '.' . $this->privateKeyId, + $key, + Encryption::ID); + } + + /** + * add system keys such as the public share key and the recovery key + * + * @param array $accessList + * @param array $publicKeys + * @param string $uid + * @return array + * @throws PublicKeyMissingException + */ + public function addSystemKeys(array $accessList, array $publicKeys, $uid) { + if (!empty($accessList['public'])) { + $publicShareKey = $this->getPublicShareKey(); + if (empty($publicShareKey)) { + throw new PublicKeyMissingException($this->getPublicShareKeyId()); + } + $publicKeys[$this->getPublicShareKeyId()] = $publicShareKey; + } + + if ($this->recoveryKeyExists() && + $this->util->isRecoveryEnabledForUser($uid)) { + + $publicKeys[$this->getRecoveryKeyId()] = $this->getRecoveryKey(); + } + + return $publicKeys; + } + + /** + * get master key password + * + * @return string + * @throws \Exception + */ + public function getMasterKeyPassword() { + $password = $this->config->getSystemValue('secret'); + if (empty($password)){ + throw new \Exception('Can not get secret from ownCloud instance'); + } + + return $password; + } + + /** + * return master key id + * + * @return string + */ + public function getMasterKeyId() { + return $this->masterKeyId; + } + + /** + * get public master key + * + * @return string + */ + public function getPublicMasterKey() { + return $this->keyStorage->getSystemUserKey($this->masterKeyId . '.publicKey', Encryption::ID); + } +} |