2015-02-24 21:05:19 +03:00
|
|
|
<?php
|
|
|
|
/**
|
|
|
|
* @author Clark Tomlinson <clark@owncloud.com>
|
|
|
|
* @since 2/19/15, 1:20 PM
|
|
|
|
* @copyright Copyright (c) 2015, 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;
|
|
|
|
|
|
|
|
|
2015-03-25 00:29:10 +03:00
|
|
|
use OC\Encryption\Exceptions\DecryptionFailedException;
|
2015-02-24 21:05:19 +03:00
|
|
|
use OC\Encryption\Exceptions\PrivateKeyMissingException;
|
|
|
|
use OC\Encryption\Exceptions\PublicKeyMissingException;
|
|
|
|
use OCA\Encryption\Crypto\Crypt;
|
2015-03-25 00:29:10 +03:00
|
|
|
use OCP\Encryption\Keys\IStorage;
|
|
|
|
use OCP\ICache;
|
|
|
|
use OCP\ICacheFactory;
|
2015-02-24 21:05:19 +03:00
|
|
|
use OCP\IConfig;
|
2015-03-25 00:29:10 +03:00
|
|
|
use OCP\ILogger;
|
2015-02-24 21:05:19 +03:00
|
|
|
use OCP\IUserSession;
|
|
|
|
|
|
|
|
class KeyManager {
|
|
|
|
|
|
|
|
/**
|
2015-03-25 00:29:10 +03:00
|
|
|
* @var ICache
|
|
|
|
*/
|
|
|
|
public static $cacheFactory;
|
|
|
|
/**
|
|
|
|
* @var IStorage
|
2015-02-24 21:05:19 +03:00
|
|
|
*/
|
|
|
|
private $keyStorage;
|
|
|
|
/**
|
|
|
|
* @var Crypt
|
|
|
|
*/
|
|
|
|
private $crypt;
|
|
|
|
/**
|
|
|
|
* @var string
|
|
|
|
*/
|
|
|
|
private $recoveryKeyId;
|
|
|
|
/**
|
|
|
|
* @var string
|
|
|
|
*/
|
|
|
|
private $publicShareKeyId;
|
|
|
|
/**
|
|
|
|
* @var string UserID
|
|
|
|
*/
|
|
|
|
private $keyId;
|
2015-03-25 00:29:10 +03:00
|
|
|
/**
|
|
|
|
* @var string
|
|
|
|
*/
|
|
|
|
private $publicKeyId = 'public';
|
|
|
|
/**
|
|
|
|
* @var string
|
|
|
|
*/
|
|
|
|
private $privateKeyId = 'private';
|
2015-02-24 21:05:19 +03:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @var string
|
|
|
|
*/
|
2015-03-25 00:29:10 +03:00
|
|
|
private $shareKeyId = 'sharekey';
|
|
|
|
|
2015-02-24 21:05:19 +03:00
|
|
|
/**
|
|
|
|
* @var string
|
|
|
|
*/
|
2015-03-25 00:29:10 +03:00
|
|
|
private $fileKeyId = 'filekey';
|
2015-02-24 21:05:19 +03:00
|
|
|
/**
|
|
|
|
* @var IConfig
|
|
|
|
*/
|
|
|
|
private $config;
|
2015-03-25 00:29:10 +03:00
|
|
|
/**
|
|
|
|
* @var ILogger
|
|
|
|
*/
|
|
|
|
private $log;
|
2015-02-24 21:05:19 +03:00
|
|
|
|
|
|
|
/**
|
2015-03-25 00:29:10 +03:00
|
|
|
* @param IStorage $keyStorage
|
2015-02-24 21:05:19 +03:00
|
|
|
* @param Crypt $crypt
|
|
|
|
* @param IConfig $config
|
|
|
|
* @param IUserSession $userSession
|
2015-03-25 00:29:10 +03:00
|
|
|
* @param ICacheFactory $cacheFactory
|
|
|
|
* @param ILogger $log
|
2015-02-24 21:05:19 +03:00
|
|
|
*/
|
2015-03-25 00:29:10 +03:00
|
|
|
public function __construct(IStorage $keyStorage, Crypt $crypt, IConfig $config, IUserSession $userSession, ICacheFactory $cacheFactory, ILogger $log) {
|
2015-02-24 21:05:19 +03:00
|
|
|
|
|
|
|
$this->keyStorage = $keyStorage;
|
|
|
|
$this->crypt = $crypt;
|
|
|
|
$this->config = $config;
|
2015-03-25 00:29:10 +03:00
|
|
|
$this->recoveryKeyId = $this->config->getAppValue('encryption',
|
|
|
|
'recoveryKeyId');
|
|
|
|
$this->publicShareKeyId = $this->config->getAppValue('encryption',
|
|
|
|
'publicShareKeyId');
|
2015-02-24 21:05:19 +03:00
|
|
|
$this->keyId = $userSession && $userSession->isLoggedIn() ? $userSession->getUser()->getUID() : false;
|
|
|
|
|
2015-03-25 00:29:10 +03:00
|
|
|
self::$cacheFactory = $cacheFactory;
|
|
|
|
self::$cacheFactory = self::$cacheFactory->create('encryption');
|
|
|
|
$this->log = $log;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public function recoveryKeyExists() {
|
|
|
|
return (strlen($this->keyStorage->getSystemUserKey($this->recoveryKeyId)) !== 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param $password
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public function checkRecoveryPassword($password) {
|
|
|
|
$recoveryKey = $this->keyStorage->getSystemUserKey($this->recoveryKeyId);
|
|
|
|
$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->symmetricEncryptFileContent($keyPair['privateKey'],
|
|
|
|
$password);
|
|
|
|
|
|
|
|
if ($encryptedKey) {
|
|
|
|
$this->setPrivateKey($uid, $encryptedKey);
|
|
|
|
$this->config->setAppValue('encryption', 'recoveryAdminEnabled', 1);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param $userId
|
|
|
|
* @param $key
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public function setPublicKey($userId, $key) {
|
|
|
|
return $this->keyStorage->setUserKey($userId, $this->publicKeyId, $key);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param $userId
|
|
|
|
* @param $key
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public function setPrivateKey($userId, $key) {
|
|
|
|
return $this->keyStorage->setUserKey($userId,
|
|
|
|
$this->privateKeyId,
|
|
|
|
$key);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Decrypt private key and store it
|
|
|
|
*
|
|
|
|
* @param string $uid userid
|
|
|
|
* @param string $passPhrase users password
|
|
|
|
* @return ICache
|
|
|
|
*/
|
|
|
|
public function init($uid, $passPhrase) {
|
|
|
|
try {
|
|
|
|
$privateKey = $this->getPrivateKey($uid);
|
|
|
|
$privateKey = $this->crypt->decryptPrivateKey($privateKey,
|
|
|
|
$passPhrase);
|
|
|
|
} catch (PrivateKeyMissingException $e) {
|
|
|
|
return false;
|
|
|
|
} catch (DecryptionFailedException $e) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
self::$cacheFactory->set('privateKey', $privateKey);
|
|
|
|
self::$cacheFactory->set('initStatus', true);
|
|
|
|
|
|
|
|
|
|
|
|
return self::$cacheFactory;
|
2015-02-24 21:05:19 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param $userId
|
|
|
|
* @return mixed
|
|
|
|
* @throws PrivateKeyMissingException
|
|
|
|
*/
|
|
|
|
public function getPrivateKey($userId) {
|
2015-03-25 00:29:10 +03:00
|
|
|
$privateKey = $this->keyStorage->getUserKey($userId,
|
|
|
|
$this->privateKeyId);
|
2015-02-24 21:05:19 +03:00
|
|
|
|
|
|
|
if (strlen($privateKey) !== 0) {
|
|
|
|
return $privateKey;
|
|
|
|
}
|
|
|
|
throw new PrivateKeyMissingException();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2015-03-25 00:29:10 +03:00
|
|
|
* @param $path
|
2015-02-24 21:05:19 +03:00
|
|
|
* @return mixed
|
|
|
|
*/
|
2015-03-25 00:29:10 +03:00
|
|
|
public function getFileKey($path) {
|
|
|
|
return $this->keyStorage->getFileKey($path, $this->fileKeyId);
|
|
|
|
}
|
2015-02-24 21:05:19 +03:00
|
|
|
|
2015-03-25 00:29:10 +03:00
|
|
|
/**
|
|
|
|
* @param $path
|
|
|
|
* @return mixed
|
|
|
|
*/
|
|
|
|
public function getShareKey($path) {
|
|
|
|
return $this->keyStorage->getFileKey($path, $this->keyId . $this->shareKeyId);
|
2015-02-24 21:05:19 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2015-03-25 00:29:10 +03:00
|
|
|
* Change a user's encryption passphrase
|
|
|
|
*
|
|
|
|
* @param array $params keys: uid, password
|
|
|
|
* @param IUserSession $user
|
|
|
|
* @param Util $util
|
2015-02-24 21:05:19 +03:00
|
|
|
* @return bool
|
|
|
|
*/
|
2015-03-25 00:29:10 +03:00
|
|
|
public function setPassphrase($params, IUserSession $user, Util $util) {
|
|
|
|
|
|
|
|
// Only attempt to change passphrase if server-side encryption
|
|
|
|
// is in use (client-side encryption does not have access to
|
|
|
|
// the necessary keys)
|
|
|
|
if ($this->crypt->mode() === 'server') {
|
|
|
|
|
|
|
|
// Get existing decrypted private key
|
|
|
|
$privateKey = self::$cacheFactory->get('privateKey');
|
|
|
|
|
|
|
|
if ($params['uid'] === $user->getUser()->getUID() && $privateKey) {
|
|
|
|
|
|
|
|
// Encrypt private key with new user pwd as passphrase
|
|
|
|
$encryptedPrivateKey = $this->crypt->symmetricEncryptFileContent($privateKey,
|
|
|
|
$params['password']);
|
|
|
|
|
|
|
|
// Save private key
|
|
|
|
if ($encryptedPrivateKey) {
|
|
|
|
$this->setPrivateKey($user->getUser()->getUID(),
|
|
|
|
$encryptedPrivateKey);
|
|
|
|
} else {
|
|
|
|
$this->log->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 reencrypt file keys
|
|
|
|
|
|
|
|
$user = $params['uid'];
|
|
|
|
$recoveryPassword = isset($params['recoveryPassword']) ? $params['recoveryPassword'] : null;
|
|
|
|
|
|
|
|
// 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 (($util->recoveryEnabledForUser() && $recoveryPassword)
|
|
|
|
|
|
|
|
|| !$this->userHasKeys($user)
|
|
|
|
|| !$util->userHasFiles($user)
|
|
|
|
) {
|
|
|
|
|
|
|
|
// backup old keys
|
|
|
|
$this->backupAllKeys('recovery');
|
|
|
|
|
|
|
|
$newUserPassword = $params['password'];
|
|
|
|
|
|
|
|
$keypair = $this->crypt->createKeyPair();
|
|
|
|
|
|
|
|
// Disable encryption proxy to prevent recursive calls
|
|
|
|
$proxyStatus = \OC_FileProxy::$enabled;
|
|
|
|
\OC_FileProxy::$enabled = false;
|
|
|
|
|
|
|
|
// Save public key
|
|
|
|
$this->setPublicKey($user, $keypair['publicKey']);
|
|
|
|
|
|
|
|
// Encrypt private key with new password
|
|
|
|
$encryptedKey = $this->crypt->symmetricEncryptFileContent($keypair['privateKey'],
|
|
|
|
$newUserPassword);
|
|
|
|
|
|
|
|
if ($encryptedKey) {
|
|
|
|
$this->setPrivateKey($user, $encryptedKey);
|
|
|
|
|
|
|
|
if ($recoveryPassword) { // if recovery key is set we can re-encrypt the key files
|
|
|
|
$util->recoverUsersFiles($recoveryPassword);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
$this->log->error('Encryption Could not update users encryption password');
|
|
|
|
}
|
|
|
|
|
|
|
|
\OC_FileProxy::$enabled = $proxyStatus;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-02-24 21:05:19 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param $userId
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public function userHasKeys($userId) {
|
|
|
|
try {
|
|
|
|
$this->getPrivateKey($userId);
|
|
|
|
$this->getPublicKey($userId);
|
|
|
|
} catch (PrivateKeyMissingException $e) {
|
|
|
|
return false;
|
|
|
|
} catch (PublicKeyMissingException $e) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2015-03-25 00:29:10 +03:00
|
|
|
* @param $userId
|
|
|
|
* @return mixed
|
|
|
|
* @throws PublicKeyMissingException
|
2015-02-24 21:05:19 +03:00
|
|
|
*/
|
2015-03-25 00:29:10 +03:00
|
|
|
public function getPublicKey($userId) {
|
|
|
|
$publicKey = $this->keyStorage->getUserKey($userId, $this->publicKeyId);
|
2015-02-24 21:05:19 +03:00
|
|
|
|
2015-03-25 00:29:10 +03:00
|
|
|
if (strlen($publicKey) !== 0) {
|
|
|
|
return $publicKey;
|
2015-02-24 21:05:19 +03:00
|
|
|
}
|
2015-03-25 00:29:10 +03:00
|
|
|
throw new PublicKeyMissingException();
|
2015-02-24 21:05:19 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2015-03-25 00:29:10 +03:00
|
|
|
* @param $purpose
|
|
|
|
* @param bool $timestamp
|
|
|
|
* @param bool $includeUserKeys
|
2015-02-24 21:05:19 +03:00
|
|
|
*/
|
2015-03-25 00:29:10 +03:00
|
|
|
public function backupAllKeys($purpose, $timestamp = true, $includeUserKeys = true) {
|
|
|
|
// $backupDir = $this->keyStorage->;
|
2015-02-24 21:05:19 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2015-03-25 00:29:10 +03:00
|
|
|
* @param string $uid
|
2015-02-24 21:05:19 +03:00
|
|
|
*/
|
2015-03-25 00:29:10 +03:00
|
|
|
public function replaceUserKeys($uid) {
|
|
|
|
$this->backupAllKeys('password_reset');
|
|
|
|
$this->deletePublicKey($uid);
|
|
|
|
$this->deletePrivateKey($uid);
|
2015-02-24 21:05:19 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2015-03-25 00:29:10 +03:00
|
|
|
* @param $uid
|
2015-02-24 21:05:19 +03:00
|
|
|
* @return bool
|
|
|
|
*/
|
2015-03-25 00:29:10 +03:00
|
|
|
public function deletePublicKey($uid) {
|
|
|
|
return $this->keyStorage->deleteUserKey($uid, $this->publicKeyId);
|
2015-02-24 21:05:19 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2015-03-25 00:29:10 +03:00
|
|
|
* @param $uid
|
2015-02-24 21:05:19 +03:00
|
|
|
* @return bool
|
|
|
|
*/
|
2015-03-25 00:29:10 +03:00
|
|
|
private function deletePrivateKey($uid) {
|
|
|
|
return $this->keyStorage->deleteUserKey($uid, $this->privateKeyId);
|
2015-02-24 21:05:19 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2015-03-25 00:29:10 +03:00
|
|
|
* @param array $userIds
|
|
|
|
* @return array
|
|
|
|
* @throws PublicKeyMissingException
|
2015-02-24 21:05:19 +03:00
|
|
|
*/
|
2015-03-25 00:29:10 +03:00
|
|
|
public function getPublicKeys(array $userIds) {
|
|
|
|
$keys = [];
|
2015-02-24 21:05:19 +03:00
|
|
|
|
2015-03-25 00:29:10 +03:00
|
|
|
foreach ($userIds as $userId) {
|
|
|
|
try {
|
|
|
|
$keys[$userId] = $this->getPublicKey($userId);
|
|
|
|
} catch (PublicKeyMissingException $e) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
2015-02-24 21:05:19 +03:00
|
|
|
|
2015-03-25 00:29:10 +03:00
|
|
|
return $keys;
|
2015-02-24 21:05:19 +03:00
|
|
|
|
|
|
|
}
|
|
|
|
}
|