Updating keystorage movement and fixing hooks

This commit is contained in:
Clark Tomlinson 2015-03-24 17:29:10 -04:00 committed by Thomas Müller
parent 506222567e
commit 0c2f9ca849
15 changed files with 1225 additions and 446 deletions

View File

@ -21,16 +21,16 @@
namespace OCA\Encryption\AppInfo;
use OC\Files\Filesystem;
use OC\Files\View;
use OCA\Encryption\Crypto\Crypt;
use OCA\Encryption\HookManager;
use OCA\Encryption\Hooks\AppHooks;
use OCA\Encryption\Hooks\FileSystemHooks;
use OCA\Encryption\Hooks\ShareHooks;
use OCA\Encryption\Hooks\UserHooks;
use OCA\Encryption\KeyManager;
use OCA\Encryption\Migrator;
use OCA\Encryption\Recovery;
use OCA\Encryption\Users\Setup;
use OCA\Encryption\Util;
use OCP\App;
use OCP\AppFramework\IAppContainer;
use OCP\Encryption\IManager;
@ -81,14 +81,7 @@ class Encryption extends \OCP\AppFramework\App {
$hookManager = new HookManager();
$hookManager->registerHook([
new UserHooks($container->query('KeyManager'),
$server->getLogger(),
$container->query('UserSetup'),
$container->query('Migrator'),
$server->getUserSession()),
// new ShareHooks(),
// new FileSystemHooks(),
// new AppHooks()
new UserHooks($container->query('KeyManager'), $server->getLogger(), $container->query('UserSetup'), $server->getUserSession(), new \OCP\Util(), $container->query('Util')),
]);
$hookManager->fireHooks();
@ -103,7 +96,7 @@ class Encryption extends \OCP\AppFramework\App {
*
*/
public function registerEncryptionModule() {
// $this->encryptionManager->registerEncryptionModule(new \OCA\Encryption\Crypto\Encryption());
$this->encryptionManager->registerEncryptionModule(new \OCA\Encryption\Crypto\Encryption());
}
/**
@ -124,10 +117,13 @@ class Encryption extends \OCP\AppFramework\App {
function (IAppContainer $c) {
$server = $c->getServer();
return new KeyManager($server->getEncryptionKeyStorage(),
return new KeyManager($server->getEncryptionKeyStorage('encryption'),
$c->query('Crypt'),
$server->getConfig(),
$server->getUserSession());
$server->getUserSession(),
$server->getMemCacheFactory(),
$server->getLogger()
);
});
@ -141,7 +137,7 @@ class Encryption extends \OCP\AppFramework\App {
$server->getSecureRandom(),
$c->query('KeyManager'),
$server->getConfig(),
$server->getEncryptionKeyStorage());
$server->getEncryptionKeyStorage('encryption'));
});
$container->registerService('UserSetup',
@ -157,13 +153,26 @@ class Encryption extends \OCP\AppFramework\App {
function (IAppContainer $c) {
$server = $c->getServer();
return new Migrator($server->getUserSession(),
$server->getConfig(),
return new Migrator($server->getConfig(),
$server->getUserManager(),
$server->getLogger(),
$c->query('Crypt'));
});
$container->registerService('Util',
function (IAppContainer $c) {
$server = $c->getServer();
return new Util(new View(),
new Filesystem(),
$c->query('Crypt'),
$c->query('KeyManager'),
$server->getLogger(),
$server->getUserSession(),
$server->getConfig()
);
});
}
/**

View File

@ -22,15 +22,14 @@
namespace OCA\Encryption\Hooks;
use OCP\Util as OCUtil;
use OCA\Encryption\Hooks\Contracts\IHook;
use OCA\Encryption\KeyManager;
use OCA\Encryption\Migrator;
use OCA\Encryption\RequirementsChecker;
use OCA\Encryption\Users\Setup;
use OCP\App;
use OCP\ILogger;
use OCP\IUserSession;
use OCP\Util;
use OCA\Encryption\Util;
use Test\User;
class UserHooks implements IHook {
@ -46,14 +45,14 @@ class UserHooks implements IHook {
* @var Setup
*/
private $userSetup;
/**
* @var Migrator
*/
private $migrator;
/**
* @var IUserSession
*/
private $user;
/**
* @var Util
*/
private $util;
/**
* UserHooks constructor.
@ -61,17 +60,19 @@ class UserHooks implements IHook {
* @param KeyManager $keyManager
* @param ILogger $logger
* @param Setup $userSetup
* @param Migrator $migrator
* @param IUserSession $user
* @param OCUtil $ocUtil
* @param Util $util
* @internal param Migrator $migrator
*/
public function __construct(
KeyManager $keyManager, ILogger $logger, Setup $userSetup, Migrator $migrator, IUserSession $user) {
KeyManager $keyManager, ILogger $logger, Setup $userSetup, IUserSession $user, OCUtil $ocUtil, Util $util) {
$this->keyManager = $keyManager;
$this->logger = $logger;
$this->userSetup = $userSetup;
$this->migrator = $migrator;
$this->user = $user;
$this->util = $util;
}
/**
@ -80,12 +81,24 @@ class UserHooks implements IHook {
* @return null
*/
public function addHooks() {
Util::connectHook('OC_User', 'post_login', $this, 'login');
Util::connectHook('OC_User', 'logout', $this, 'logout');
Util::connectHook('OC_User', 'post_setPassword', $this, 'setPassphrase');
Util::connectHook('OC_User', 'pre_setPassword', $this, 'preSetPassphrase');
Util::connectHook('OC_User', 'post_createUser', $this, 'postCreateUser');
Util::connectHook('OC_User', 'post_deleteUser', $this, 'postDeleteUser');
OCUtil::connectHook('OC_User', 'post_login', $this, 'login');
OCUtil::connectHook('OC_User', 'logout', $this, 'logout');
OCUtil::connectHook('OC_User',
'post_setPassword',
$this,
'setPassphrase');
OCUtil::connectHook('OC_User',
'pre_setPassword',
$this,
'preSetPassphrase');
OCUtil::connectHook('OC_User',
'post_createUser',
$this,
'postCreateUser');
OCUtil::connectHook('OC_User',
'post_deleteUser',
$this,
'postDeleteUser');
}
@ -93,6 +106,8 @@ class UserHooks implements IHook {
* Startup encryption backend upon user login
*
* @note This method should never be called for users using client side encryption
* @param array $params
* @return bool
*/
public function login($params) {
@ -107,187 +122,66 @@ class UserHooks implements IHook {
}
// setup user, if user not ready force relogin
if (!$this->userSetup->setupUser($params['password'])) {
if (!$this->userSetup->setupUser($params['uid'], $params['password'])) {
return false;
}
$cache = $this->keyManager->init();
// Check if first-run file migration has already been performed
$ready = false;
$migrationStatus = $this->migrator->getStatus($params['uid']);
if ($migrationStatus === Migrator::$migrationOpen && $cache !== false) {
$ready = $this->migrator->beginMigration();
} elseif ($migrationStatus === Migrator::$migrationInProgress) {
// refuse login as long as the initial encryption is running
sleep(5);
$this->user->logout();
return false;
}
$result = true;
// If migration not yet done
if ($ready) {
// Encrypt existing user files
try {
$result = $util->encryptAll('/' . $params['uid'] . '/' . 'files');
} catch (\Exception $ex) {
\OCP\Util::writeLog('Encryption library', 'Initial encryption failed! Error: ' . $ex->getMessage(), \OCP\Util::FATAL);
$result = false;
}
if ($result) {
\OC_Log::write(
'Encryption library', 'Encryption of existing files belonging to "' . $params['uid'] . '" completed'
, \OC_Log::INFO
);
// Register successful migration in DB
$util->finishMigration();
} else {
\OCP\Util::writeLog('Encryption library', 'Initial encryption failed!', \OCP\Util::FATAL);
$util->resetMigrationStatus();
\OCP\User::logout();
}
}
return $result;
$this->keyManager->init($params['uid'], $params['password']);
}
/**
* remove keys from session during logout
*/
public function logout() {
$session = new Session(new \OC\Files\View());
$session->removeKeys();
KeyManager::$cacheFactory->clear();
}
/**
* setup encryption backend upon user created
*
* @note This method should never be called for users using client side encryption
* @param array $params
*/
public function postCreateUser($params) {
if (App::isEnabled('files_encryption')) {
$view = new \OC\Files\View('/');
$util = new Util($view, $params['uid']);
Helper::setupUser($util, $params['password']);
if (App::isEnabled('encryption')) {
$this->userSetup->setupUser($params['uid'], $params['password']);
}
}
/**
* cleanup encryption backend upon user deleted
*
* @param array $params : uid, password
* @note This method should never be called for users using client side encryption
*/
public function postDeleteUser($params) {
if (App::isEnabled('files_encryption')) {
Keymanager::deletePublicKey(new \OC\Files\View(), $params['uid']);
if (App::isEnabled('encryption')) {
$this->keyManager->deletePublicKey($params['uid']);
}
}
/**
* If the password can't be changed within ownCloud, than update the key password in advance.
*
* @param array $params : uid, password
* @return bool
*/
public function preSetPassphrase($params) {
if (App::isEnabled('files_encryption')) {
if (!\OC_User::canUserChangePassword($params['uid'])) {
self::setPassphrase($params);
if (App::isEnabled('encryption')) {
if (!$this->user->getUser()->canChangePassword()) {
if (App::isEnabled('encryption') === false) {
return true;
}
$this->keyManager->setPassphrase($params,
$this->user,
$this->util);
}
}
}
/**
* Change a user's encryption passphrase
*
* @param array $params keys: uid, password
*/
public function setPassphrase($params) {
if (App::isEnabled('files_encryption') === false) {
return true;
}
// Only attempt to change passphrase if server-side encryption
// is in use (client-side encryption does not have access to
// the necessary keys)
if (Crypt::mode() === 'server') {
$view = new \OC\Files\View('/');
$session = new Session($view);
// Get existing decrypted private key
$privateKey = $session->getPrivateKey();
if ($params['uid'] === \OCP\User::getUser() && $privateKey) {
// Encrypt private key with new user pwd as passphrase
$encryptedPrivateKey = Crypt::symmetricEncryptFileContent($privateKey, $params['password'], Helper::getCipher());
// Save private key
if ($encryptedPrivateKey) {
Keymanager::setPrivateKey($encryptedPrivateKey, \OCP\User::getUser());
} else {
\OCP\Util::writeLog('files_encryption', 'Could not update users encryption password', \OCP\Util::ERROR);
}
// 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'];
$util = new Util($view, $user);
$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)
|| !$util->userKeysExists()
|| !$view->file_exists($user . '/files')
) {
// backup old keys
$util->backupAllKeys('recovery');
$newUserPassword = $params['password'];
// make sure that the users home is mounted
\OC\Files\Filesystem::initMountPoints($user);
$keypair = Crypt::createKeypair();
// Disable encryption proxy to prevent recursive calls
$proxyStatus = \OC_FileProxy::$enabled;
\OC_FileProxy::$enabled = false;
// Save public key
Keymanager::setPublicKey($keypair['publicKey'], $user);
// Encrypt private key with new password
$encryptedKey = Crypt::symmetricEncryptFileContent($keypair['privateKey'], $newUserPassword, Helper::getCipher());
if ($encryptedKey) {
Keymanager::setPrivateKey($encryptedKey, $user);
if ($recoveryPassword) { // if recovery key is set we can re-encrypt the key files
$util = new Util($view, $user);
$util->recoverUsersFiles($recoveryPassword);
}
} else {
\OCP\Util::writeLog('files_encryption', 'Could not update users encryption password', \OCP\Util::ERROR);
}
\OC_FileProxy::$enabled = $proxyStatus;
}
}
}
}
/**
* after password reset we create a new key pair for the user
@ -295,10 +189,9 @@ class UserHooks implements IHook {
* @param array $params
*/
public function postPasswordReset($params) {
$uid = $params['uid'];
$password = $params['password'];
$util = new Util(new \OC\Files\View(), $uid);
$util->replaceUserKeys($password);
$this->keyManager->replaceUserKeys($params['uid']);
$this->userSetup->setupServerSide($params['uid'], $password);
}
}

View File

@ -18,7 +18,7 @@ class Encryption extends Crypt implements IEncryptionModule {
* @return string defining the technical unique id
*/
public function getId() {
// TODO: Implement getId() method.
return md5($this->getDisplayName());
}
/**
@ -27,7 +27,7 @@ class Encryption extends Crypt implements IEncryptionModule {
* @return string
*/
public function getDisplayName() {
// TODO: Implement getDisplayName() method.
return 'ownCloud Default Encryption';
}
/**
@ -44,7 +44,9 @@ class Encryption extends Crypt implements IEncryptionModule {
* or if no additional data is needed return a empty array
*/
public function begin($path, $header, $accessList) {
// TODO: Implement begin() method.
// return additional header information that needs to be written i.e. cypher used
}
/**
@ -68,6 +70,7 @@ class Encryption extends Crypt implements IEncryptionModule {
*/
public function encrypt($data) {
// Todo: xxx Update Signature and usages
// passphrase is file key decrypted with user private/share key
$this->symmetricEncryptFileContent($data);
}
@ -113,4 +116,14 @@ class Encryption extends Crypt implements IEncryptionModule {
public function calculateUnencryptedSize($path) {
// TODO: Implement calculateUnencryptedSize() method.
}
/**
* get size of the unencrypted payload per block.
* ownCloud read/write files with a block size of 8192 byte
*
* @return integer
*/
public function getUnencryptedBlockSize() {
// TODO: Implement getUnencryptedBlockSize() method.
}
}

View File

@ -25,6 +25,8 @@ namespace OCA\Encryption\Crypto;
use OC\Encryption\Exceptions\DecryptionFailedException;
use OC\Encryption\Exceptions\EncryptionFailedException;
use OC\Encryption\Exceptions\GenericEncryptionException;
use OCA\Files_Encryption\Exception\MultiKeyDecryptException;
use OCA\Files_Encryption\Exception\MultiKeyEncryptException;
use OCP\IConfig;
use OCP\ILogger;
use OCP\IUser;
@ -83,12 +85,17 @@ class Crypt {
$res = $this->getOpenSSLPKey();
if (!$res) {
$log->error("Encryption Library could'nt generate users key-pair for {$this->user->getUID()}", ['app' => 'encryption']);
$log->error("Encryption Library could'nt generate users key-pair for {$this->user->getUID()}",
['app' => 'encryption']);
if (openssl_error_string()) {
$log->error('Encryption library openssl_pkey_new() fails: ' . openssl_error_string(), ['app' => 'encryption']);
$log->error('Encryption library openssl_pkey_new() fails: ' . openssl_error_string(),
['app' => 'encryption']);
}
} elseif (openssl_pkey_export($res, $privateKey, null, $this->getOpenSSLConfig())) {
} elseif (openssl_pkey_export($res,
$privateKey,
null,
$this->getOpenSSLConfig())) {
$keyDetails = openssl_pkey_get_details($res);
$publicKey = $keyDetails['key'];
@ -97,9 +104,11 @@ class Crypt {
'privateKey' => $privateKey
];
}
$log->error('Encryption library couldn\'t export users private key, please check your servers openSSL configuration.' . $user->getUID(), ['app' => 'encryption']);
$log->error('Encryption library couldn\'t export users private key, please check your servers openSSL configuration.' . $user->getUID(),
['app' => 'encryption']);
if (openssl_error_string()) {
$log->error('Encryption Library:' . openssl_error_string(), ['app' => 'encryption']);
$log->error('Encryption Library:' . openssl_error_string(),
['app' => 'encryption']);
}
return false;
@ -118,7 +127,9 @@ class Crypt {
*/
private function getOpenSSLConfig() {
$config = ['private_key_bits' => 4096];
$config = array_merge(\OC::$server->getConfig()->getSystemValue('openssl', []), $config);
$config = array_merge(\OC::$server->getConfig()->getSystemValue('openssl',
[]),
$config);
return $config;
}
@ -131,14 +142,18 @@ class Crypt {
public function symmetricEncryptFileContent($plainContent, $passphrase) {
if (!$plainContent) {
$this->logger->error('Encryption Library, symmetrical encryption failed no content given', ['app' => 'encryption']);
$this->logger->error('Encryption Library, symmetrical encryption failed no content given',
['app' => 'encryption']);
return false;
}
$iv = $this->generateIv();
try {
$encryptedContent = $this->encrypt($plainContent, $iv, $passphrase, $this->getCipher());
$encryptedContent = $this->encrypt($plainContent,
$iv,
$passphrase,
$this->getCipher());
// combine content to encrypt the IV identifier and actual IV
$catFile = $this->concatIV($encryptedContent, $iv);
$padded = $this->addPadding($catFile);
@ -146,7 +161,8 @@ class Crypt {
return $padded;
} catch (EncryptionFailedException $e) {
$message = 'Could not encrypt file content (code: ' . $e->getCode() . '): ';
$this->logger->error('files_encryption' . $message . $e->getMessage(), ['app' => 'encryption']);
$this->logger->error('files_encryption' . $message . $e->getMessage(),
['app' => 'encryption']);
return false;
}
@ -161,11 +177,16 @@ class Crypt {
* @throws EncryptionFailedException
*/
private function encrypt($plainContent, $iv, $passphrase = '', $cipher = self::DEFAULT_CIPHER) {
$encryptedContent = openssl_encrypt($plainContent, $cipher, $passphrase, false, $iv);
$encryptedContent = openssl_encrypt($plainContent,
$cipher,
$passphrase,
false,
$iv);
if (!$encryptedContent) {
$error = 'Encryption (symmetric) of content failed';
$this->logger->error($error . openssl_error_string(), ['app' => 'encryption']);
$this->logger->error($error . openssl_error_string(),
['app' => 'encryption']);
throw new EncryptionFailedException($error);
}
@ -177,8 +198,9 @@ class Crypt {
*/
public function getCipher() {
$cipher = $this->config->getSystemValue('cipher', self::DEFAULT_CIPHER);
if ($cipher !== 'AES-256-CFB' || $cipher !== 'AES-128-CFB') {
$this->logger->warning('Wrong cipher defined in config.php only AES-128-CFB and AES-256-CFB are supported. Fall back' . self::DEFAULT_CIPHER, ['app' => 'encryption']);
if ($cipher !== 'AES-256-CFB' && $cipher !== 'AES-128-CFB') {
$this->logger->warning('Wrong cipher defined in config.php only AES-128-CFB and AES-256-CFB are supported. Fall back' . self::DEFAULT_CIPHER,
['app' => 'encryption']);
$cipher = self::DEFAULT_CIPHER;
}
@ -214,10 +236,14 @@ class Crypt {
// If we found a header we need to remove it from the key we want to decrypt
if (!empty($header)) {
$recoveryKey = substr($recoveryKey, strpos($recoveryKey, self::HEADEREND) + strlen(self::HEADERSTART));
$recoveryKey = substr($recoveryKey,
strpos($recoveryKey,
self::HEADEREND) + strlen(self::HEADERSTART));
}
$plainKey = $this->symmetricDecryptFileContent($recoveryKey, $password, $cipher);
$plainKey = $this->symmetricDecryptFileContent($recoveryKey,
$password,
$cipher);
// Check if this is a valid private key
$res = openssl_get_privatekey($plainKey);
@ -246,7 +272,10 @@ class Crypt {
$catFile = $this->splitIv($noPadding);
$plainContent = $this->decrypt($catFile['encrypted'], $catFile['iv'], $passphrase, $cipher);
$plainContent = $this->decrypt($catFile['encrypted'],
$catFile['iv'],
$passphrase,
$cipher);
if ($plainContent) {
return $plainContent;
@ -296,7 +325,11 @@ class Crypt {
* @throws DecryptionFailedException
*/
private function decrypt($encryptedContent, $iv, $passphrase = '', $cipher = self::DEFAULT_CIPHER) {
$plainContent = openssl_decrypt($encryptedContent, $cipher, $passphrase, false, $iv);
$plainContent = openssl_decrypt($encryptedContent,
$cipher,
$passphrase,
false,
$iv);
if ($plainContent) {
return $plainContent;
@ -317,7 +350,8 @@ class Crypt {
$header = substr($data, 0, $endAt + strlen(self::HEADEREND));
// +1 not to start with an ':' which would result in empty element at the beginning
$exploded = explode(':', substr($header, strlen(self::HEADERSTART) + 1));
$exploded = explode(':',
substr($header, strlen(self::HEADERSTART) + 1));
$element = array_shift($exploded);
@ -339,7 +373,8 @@ class Crypt {
if ($random) {
if (!$strong) {
// If OpenSSL indicates randomness is insecure log error
$this->logger->error('Encryption Library: Insecure symmetric key was generated using openssl_random_psudo_bytes()', ['app' => 'encryption']);
$this->logger->error('Encryption Library: Insecure symmetric key was generated using openssl_random_psudo_bytes()',
['app' => 'encryption']);
}
/*
@ -351,5 +386,85 @@ class Crypt {
// If we ever get here we've failed anyway no need for an else
throw new GenericEncryptionException('Generating IV Failed');
}
/**
* Check if a file's contents contains an IV and is symmetrically encrypted
*
* @param $content
* @return bool
*/
public function isCatFileContent($content) {
if (!$content) {
return false;
}
$noPadding = $this->removePadding($content);
// Fetch encryption metadata from end of file
$meta = substr($noPadding, -22);
// Fetch identifier from start of metadata
$identifier = substr($meta, 0, 6);
if ($identifier === '00iv00') {
return true;
}
return false;
}
/**
* @param $encKeyFile
* @param $shareKey
* @param $privateKey
* @return mixed
* @throws MultiKeyDecryptException
*/
public function multiKeyDecrypt($encKeyFile, $shareKey, $privateKey) {
if (!$encKeyFile) {
throw new MultiKeyDecryptException('Cannot multikey decrypt empty plain content');
}
if (openssl_open($encKeyFile, $plainContent, $shareKey, $privateKey)) {
return $plainContent;
} else {
throw new MultiKeyDecryptException('multikeydecrypt with share key failed');
}
}
/**
* @param $plainContent
* @param array $keyFiles
* @return array
* @throws MultiKeyEncryptException
*/
public function multiKeyEncrypt($plainContent, array $keyFiles) {
// openssl_seal returns false without errors if plaincontent is empty
// so trigger our own error
if (empty($plainContent)) {
throw new MultiKeyEncryptException('Cannot multikeyencrypt empty plain content');
}
// Set empty vars to be set by openssl by reference
$sealed = '';
$shareKeys = [];
$mappedShareKeys = [];
if (openssl_seal($plainContent, $sealed, $shareKeys, $keyFiles)) {
$i = 0;
// Ensure each shareKey is labelled with its coreesponding keyid
foreach ($keyFiles as $userId => $publicKey) {
$mappedShareKeys[$userId] = $shareKeys[$i];
$i++;
}
return [
'keys' => $mappedShareKeys,
'data' => $sealed
];
} else {
throw new MultiKeyEncryptException('multikeyencryption failed ' . openssl_error_string());
}
}
}

View File

@ -54,6 +54,8 @@ class HookManager {
public function fireHooks() {
foreach ($this->hookInstances as $instance) {
/**
* Fire off the add hooks method of each instance stored in cache
*
* @var $instance IHook
*/
$instance->addHooks();

View File

@ -22,21 +22,27 @@
namespace OCA\Encryption;
use OC\Encryption\Exceptions\DecryptionFailedException;
use OC\Encryption\Exceptions\PrivateKeyMissingException;
use OC\Encryption\Exceptions\PublicKeyMissingException;
use OCA\Encryption\Crypto\Crypt;
use OCP\Encryption\IKeyStorage;
use OCP\Encryption\Keys\IStorage;
use OCP\ICache;
use OCP\ICacheFactory;
use OCP\IConfig;
use OCP\IUser;
use OCP\ILogger;
use OCP\IUserSession;
class KeyManager {
/**
* @var IKeyStorage
* @var ICache
*/
public static $cacheFactory;
/**
* @var IStorage
*/
private $keyStorage;
/**
* @var Crypt
*/
@ -53,63 +59,55 @@ class KeyManager {
* @var string UserID
*/
private $keyId;
/**
* @var string
*/
private $publicKeyId = 'public';
/**
* @var string
*/
private $privateKeyId = 'private';
/**
* @var string
*/
private $publicKeyId = '.public';
private $shareKeyId = 'sharekey';
/**
* @var string
*/
private $privateKeyId = '.private';
private $fileKeyId = 'filekey';
/**
* @var IConfig
*/
private $config;
/**
* @var ILogger
*/
private $log;
/**
* @param IKeyStorage $keyStorage
* @param IStorage $keyStorage
* @param Crypt $crypt
* @param IConfig $config
* @param IUserSession $userSession
* @param ICacheFactory $cacheFactory
* @param ILogger $log
*/
public function __construct(IKeyStorage $keyStorage, Crypt $crypt, IConfig $config, IUserSession $userSession) {
public function __construct(IStorage $keyStorage, Crypt $crypt, IConfig $config, IUserSession $userSession, ICacheFactory $cacheFactory, ILogger $log) {
$this->keyStorage = $keyStorage;
$this->crypt = $crypt;
$this->config = $config;
$this->recoveryKeyId = $this->config->getAppValue('encryption', 'recoveryKeyId');
$this->publicShareKeyId = $this->config->getAppValue('encryption', 'publicShareKeyId');
$this->recoveryKeyId = $this->config->getAppValue('encryption',
'recoveryKeyId');
$this->publicShareKeyId = $this->config->getAppValue('encryption',
'publicShareKeyId');
$this->keyId = $userSession && $userSession->isLoggedIn() ? $userSession->getUser()->getUID() : false;
}
/**
* @param $userId
* @return mixed
* @throws PrivateKeyMissingException
*/
public function getPrivateKey($userId) {
$privateKey = $this->keyStorage->getUserKey($userId, $this->privateKeyId);
if (strlen($privateKey) !== 0) {
return $privateKey;
}
throw new PrivateKeyMissingException();
}
/**
* @param $userId
* @return mixed
* @throws PublicKeyMissingException
*/
public function getPublicKey($userId) {
$publicKey = $this->keyStorage->getUserKey($userId, $this->publicKeyId);
if (strlen($publicKey) !== 0) {
return $publicKey;
}
throw new PublicKeyMissingException();
self::$cacheFactory = $cacheFactory;
self::$cacheFactory = self::$cacheFactory->create('encryption');
$this->log = $log;
}
/**
@ -119,29 +117,14 @@ class KeyManager {
return (strlen($this->keyStorage->getSystemUserKey($this->recoveryKeyId)) !== 0);
}
/**
* @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;
}
/**
* @param $password
* @return bool
*/
public function checkRecoveryPassword($password) {
$recoveryKey = $this->keyStorage->getSystemUserKey($this->recoveryKeyId);
$decryptedRecoveryKey = $this->crypt->decryptPrivateKey($recoveryKey, $password);
$decryptedRecoveryKey = $this->crypt->decryptPrivateKey($recoveryKey,
$password);
if ($decryptedRecoveryKey) {
return true;
@ -149,6 +132,27 @@ class KeyManager {
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
@ -164,54 +168,234 @@ class KeyManager {
* @return bool
*/
public function setPrivateKey($userId, $key) {
return $this->keyStorage->setUserKey($userId, $this->privateKeyId, $key);
}
/**
* @param $password
* @param $keyPair
* @return bool
*/
public function storeKeyPair($password, $keyPair) {
// Save Public Key
$this->setPublicKey($this->keyId, $keyPair['publicKey']);
$encryptedKey = $this->crypt->symmetricEncryptFileContent($keyPair['privateKey'], $password);
if ($encryptedKey) {
$this->setPrivateKey($this->keyId, $encryptedKey);
$this->config->setAppValue('encryption', 'recoveryAdminEnabled', 1);
return true;
}
return false;
return $this->keyStorage->setUserKey($userId,
$this->privateKeyId,
$key);
}
/**
* @return bool
* Decrypt private key and store it
*
* @param string $uid userid
* @param string $passPhrase users password
* @return ICache
*/
public function ready() {
return $this->keyStorage->ready();
}
/**
* @return \OCP\ICache
* @throws PrivateKeyMissingException
*/
public function init() {
public function init($uid, $passPhrase) {
try {
$privateKey = $this->getPrivateKey($this->keyId);
$privateKey = $this->getPrivateKey($uid);
$privateKey = $this->crypt->decryptPrivateKey($privateKey,
$passPhrase);
} catch (PrivateKeyMissingException $e) {
return false;
} catch (DecryptionFailedException $e) {
return false;
}
$cache = \OC::$server->getMemCacheFactory();
self::$cacheFactory->set('privateKey', $privateKey);
self::$cacheFactory->set('initStatus', true);
$cacheInstance = $cache->create('Encryption');
$cacheInstance->set('privateKey', $privateKey);
return $cacheInstance;
return self::$cacheFactory;
}
/**
* @param $userId
* @return mixed
* @throws PrivateKeyMissingException
*/
public function getPrivateKey($userId) {
$privateKey = $this->keyStorage->getUserKey($userId,
$this->privateKeyId);
if (strlen($privateKey) !== 0) {
return $privateKey;
}
throw new PrivateKeyMissingException();
}
/**
* @param $path
* @return mixed
*/
public function getFileKey($path) {
return $this->keyStorage->getFileKey($path, $this->fileKeyId);
}
/**
* @param $path
* @return mixed
*/
public function getShareKey($path) {
return $this->keyStorage->getFileKey($path, $this->keyId . $this->shareKeyId);
}
/**
* Change a user's encryption passphrase
*
* @param array $params keys: uid, password
* @param IUserSession $user
* @param Util $util
* @return bool
*/
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;
}
}
}
}
/**
* @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;
}
/**
* @param $userId
* @return mixed
* @throws PublicKeyMissingException
*/
public function getPublicKey($userId) {
$publicKey = $this->keyStorage->getUserKey($userId, $this->publicKeyId);
if (strlen($publicKey) !== 0) {
return $publicKey;
}
throw new PublicKeyMissingException();
}
/**
* @param $purpose
* @param bool $timestamp
* @param bool $includeUserKeys
*/
public function backupAllKeys($purpose, $timestamp = true, $includeUserKeys = true) {
// $backupDir = $this->keyStorage->;
}
/**
* @param string $uid
*/
public function replaceUserKeys($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);
}
/**
* @param $uid
* @return bool
*/
private function deletePrivateKey($uid) {
return $this->keyStorage->deleteUserKey($uid, $this->privateKeyId);
}
/**
* @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;
}
}

View File

@ -1,123 +0,0 @@
<?php
/**
* @author Clark Tomlinson <fallen013@gmail.com>
* @since 3/9/15, 2:44 PM
* @link http:/www.clarkt.com
* @copyright Clark Tomlinson © 2015
*
*/
namespace OCA\Encryption;
use OCA\Encryption\Crypto\Crypt;
use OCP\IConfig;
use OCP\ILogger;
use OCP\IUserManager;
use OCP\IUserSession;
use OCP\PreConditionNotMetException;
class Migrator {
/**
* @var bool
*/
private $status = false;
/**
* @var IUserManager
*/
private $user;
/**
* @var IConfig
*/
private $config;
/**
* @var string
*/
public static $migrationOpen = '0';
/**
* @var string
*/
public static $migrationInProgress = '-1';
/**
* @var string
*/
public static $migrationComplete = '1';
/**
* @var IUserManager
*/
private $userManager;
/**
* @var ILogger
*/
private $log;
/**
* @var Crypt
*/
private $crypt;
/**
* Migrator constructor.
*
* @param IUserSession $userSession
* @param IConfig $config
* @param IUserManager $userManager
* @param ILogger $log
* @param Crypt $crypt
*/
public function __construct(IUserSession $userSession, IConfig $config, IUserManager $userManager, ILogger $log, Crypt $crypt) {
$this->user = $userSession && $userSession->isLoggedIn() ? $userSession->getUser() : false;
$this->config = $config;
$this->userManager = $userManager;
$this->log = $log;
$this->crypt = $crypt;
}
/**
* @param $userId
* @return bool|string
*/
public function getStatus($userId) {
if ($this->userManager->userExists($userId)) {
$this->status = $this->config->getUserValue($userId, 'encryption', 'migrationStatus', false);
if (!$this->status) {
$this->config->setUserValue($userId, 'encryption', 'migrationStatus', self::$migrationOpen);
$this->status = self::$migrationOpen;
}
}
return $this->status;
}
/**
* @return bool
*/
public function beginMigration() {
$status = $this->setMigrationStatus(self::$migrationInProgress, self::$migrationOpen);
if ($status) {
$this->log->info('Encryption Library Start migration to encrypt for ' . $this->user->getUID());
return $status;
}
$this->log->warning('Encryption Library Could not activate migration for ' . $this->user->getUID() . '. Probably another process already started the inital encryption');
return $status;
}
/**
* @param $status
* @param bool $preCondition
* @return bool
*/
private function setMigrationStatus($status, $preCondition = false) {
// Convert to string if preCondition is set
$preCondition = ($preCondition === false) ? false : (string)$preCondition;
try {
$this->config->setUserValue($this->user->getUID(), 'encryption', 'migrationStatus', (string)$status, $preCondition);
return true;
} catch (PreConditionNotMetException $e) {
return false;
}
}
}

View File

@ -22,11 +22,12 @@
namespace OCA\Encryption;
use OC\Files\View;
use OCA\Encryption\Crypto\Crypt;
use OCP\Encryption\IKeyStorage;
use OCP\Encryption\Keys\IStorage;
use OCP\IConfig;
use OCP\IUser;
use OCP\IUserSession;
use OCP\PreConditionNotMetException;
use OCP\Security\ISecureRandom;
class Recovery {
@ -58,20 +59,20 @@ class Recovery {
private $keyStorage;
/**
* @param IUser $user
* @param IUserSession $user
* @param Crypt $crypt
* @param ISecureRandom $random
* @param KeyManager $keyManager
* @param IConfig $config
* @param IKeyStorage $keyStorage
* @param IStorage $keyStorage
*/
public function __construct(IUser $user,
public function __construct(IUserSession $user,
Crypt $crypt,
ISecureRandom $random,
KeyManager $keyManager,
IConfig $config,
IKeyStorage $keyStorage) {
$this->user = $user;
IStorage $keyStorage) {
$this->user = $user && $user->isLoggedIn() ? $user->getUser() : false;
$this->crypt = $crypt;
$this->random = $random;
$this->keyManager = $keyManager;
@ -97,7 +98,7 @@ class Recovery {
if (!$keyManager->recoveryKeyExists()) {
$keyPair = $this->crypt->createKeyPair();
return $this->keyManager->storeKeyPair($password, $keyPair);
return $this->keyManager->storeKeyPair($this->user->getUID(), $password, $keyPair);
}
if ($keyManager->checkRecoveryPassword($password)) {
@ -131,4 +132,45 @@ class Recovery {
// No idea new way to do this....
}
/**
* @return bool
*/
public function recoveryEnabledForUser() {
$recoveryMode = $this->config->getUserValue($this->user->getUID(),
'encryption',
'recoveryEnabled',
0);
return ($recoveryMode === '1');
}
/**
* @param $enabled
* @return bool
*/
public function setRecoveryForUser($enabled) {
$value = $enabled ? '1' : '0';
try {
$this->config->setUserValue($this->user->getUID(),
'encryption',
'recoveryEnabled',
$value);
return true;
} catch (PreConditionNotMetException $e) {
return false;
}
}
/**
* @param $recoveryPassword
*/
public function recoverUsersFiles($recoveryPassword) {
// todo: get system private key here
// $this->keyManager->get
$privateKey = $this->crypt->decryptPrivateKey($encryptedKey,
$recoveryPassword);
$this->recoverAllFiles('/', $privateKey);
}
}

View File

@ -39,24 +39,24 @@ class Setup extends \OCA\Encryption\Setup {
}
/**
* @param $password
* @param $uid userid
* @param $password user password
* @return bool
*/
public function setupUser($password) {
if ($this->keyManager->ready()) {
$this->logger->debug('Encryption Library: User Account ' . $this->user->getUID() . ' Is not ready for encryption; configuration started');
return $this->setupServerSide($password);
}
public function setupUser($uid, $password) {
return $this->setupServerSide($uid, $password);
}
/**
* @param $password
* @param $uid userid
* @param $password user password
* @return bool
*/
private function setupServerSide($password) {
public function setupServerSide($uid, $password) {
// Check if user already has keys
if (!$this->keyManager->userHasKeys($this->user->getUID())) {
return $this->keyManager->storeKeyPair($password, $this->crypt->createKeyPair());
if (!$this->keyManager->userHasKeys($uid)) {
return $this->keyManager->storeKeyPair($uid, $password,
$this->crypt->createKeyPair());
}
return true;
}

View File

@ -0,0 +1,394 @@
<?php
/**
* @author Clark Tomlinson <clark@owncloud.com>
* @since 3/17/15, 10:31 AM
* @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;
use OC\Files\Filesystem;
use OC\Files\View;
use OCA\Encryption\Crypto\Crypt;
use OCA\Files_Versions\Storage;
use OCP\App;
use OCP\IConfig;
use OCP\ILogger;
use OCP\IUser;
use OCP\IUserSession;
use OCP\PreConditionNotMetException;
use OCP\Share;
class Util {
/**
* @var View
*/
private $files;
/**
* @var Filesystem
*/
private $filesystem;
/**
* @var Crypt
*/
private $crypt;
/**
* @var KeyManager
*/
private $keyManager;
/**
* @var ILogger
*/
private $logger;
/**
* @var bool|IUser
*/
private $user;
/**
* @var IConfig
*/
private $config;
/**
* Util constructor.
*
* @param View $files
* @param Filesystem $filesystem
* @param Crypt $crypt
* @param KeyManager $keyManager
* @param ILogger $logger
* @param IUserSession $userSession
* @param IConfig $config
*/
public function __construct(
View $files,
Filesystem $filesystem,
Crypt $crypt,
KeyManager $keyManager,
ILogger $logger,
IUserSession $userSession,
IConfig $config
) {
$this->files = $files;
$this->filesystem = $filesystem;
$this->crypt = $crypt;
$this->keyManager = $keyManager;
$this->logger = $logger;
$this->user = $userSession && $userSession->isLoggedIn() ? $userSession->getUser() : false;
$this->config = $config;
}
/**
* @param $dirPath
* @param bool $found
* @return array|bool
*/
private function findEncryptedFiles($dirPath, &$found = false) {
if ($found === false) {
$found = [
'plain' => [],
'encrypted' => [],
'broken' => [],
];
}
if ($this->files->is_dir($dirPath) && $handle = $this->files->opendir($dirPath)) {
if (is_resource($handle)) {
while (($file = readdir($handle) !== false)) {
if ($file !== '.' && $file !== '..') {
// Skip stray part files
if ($this->isPartialFilePath($file)) {
continue;
}
$filePath = $dirPath . '/' . $this->files->getRelativePath('/' . $file);
$relPath = $this->stripUserFilesPath($filePath);
// If the path is a directory, search its contents
if ($this->files->is_dir($filePath)) {
// Recurse back
$this->findEncryptedFiles($filePath);
/*
* If the path is a file,
* determine where they got re-enabled :/
*/
} elseif ($this->files->is_file($filePath)) {
$isEncryptedPath = $this->isEncryptedPath($filePath);
/**
* If the file is encrypted
*
* @note: if the userId is
* empty or not set, file will
* be detected as plain
* @note: this is inefficient;
* scanning every file like this
* will eat server resources :(
* fixMe: xxx find better way
*/
if ($isEncryptedPath) {
$fileKey = $this->keyManager->getFileKey($relPath);
$shareKey = $this->keyManager->getShareKey($relPath);
// If file is encrypted but now file key is available, throw exception
if (!$fileKey || !$shareKey) {
$this->logger->error('Encryption library, no keys avilable to decrypt the file: ' . $file);
$found['broken'][] = [
'name' => $file,
'path' => $filePath,
];
} else {
$found['encrypted'][] = [
'name' => $file,
'path' => $filePath
];
}
} else {
$found['plain'][] = [
'name' => $file,
'path' => $filePath
];
}
}
}
}
}
}
return $found;
}
/**
* @param $path
* @return bool
*/
private function isPartialFilePath($path) {
$extension = pathinfo($path, PATHINFO_EXTENSION);
if ($extension === 'part') {
return true;
}
return false;
}
/**
* @param $filePath
* @return bool|string
*/
private function stripUserFilesPath($filePath) {
$split = $this->splitPath($filePath);
// It is not a file relative to data/user/files
if (count($split) < 4 || $split[2] !== 'files') {
return false;
}
$sliced = array_slice($split, 3);
return implode('/', $sliced);
}
/**
* @param $filePath
* @return array
*/
private function splitPath($filePath) {
$normalized = $this->filesystem->normalizePath($filePath);
return explode('/', $normalized);
}
/**
* @param $filePath
* @return bool
*/
private function isEncryptedPath($filePath) {
$data = '';
// We only need 24 bytes from the last chunck
if ($this->files->file_exists($filePath)) {
$handle = $this->files->fopen($filePath, 'r');
if (is_resource($handle)) {
// Suppress fseek warning, we handle the case that fseek
// doesn't work in the else branch
if (@fseek($handle, -24, SEEK_END) === 0) {
$data = fgets($handle);
} else {
// if fseek failed on the storage we create a local copy
// from the file and read this one
fclose($handle);
$localFile = $this->files->getLocalFile($filePath);
$handle = fopen($localFile, 'r');
if (is_resource($handle) && fseek($handle,
-24,
SEEK_END) === 0
) {
$data = fgets($handle);
}
}
fclose($handle);
return $this->crypt->isCatfileContent($data);
}
}
}
/**
* @return bool
*/
public function recoveryEnabledForUser() {
$recoveryMode = $this->config->getUserValue($this->user->getUID(),
'encryption',
'recoveryEnabled',
0);
return ($recoveryMode === '1');
}
/**
* @param $enabled
* @return bool
*/
public function setRecoveryForUser($enabled) {
$value = $enabled ? '1' : '0';
try {
$this->config->setUserValue($this->user->getUID(),
'encryption',
'recoveryEnabled',
$value);
return true;
} catch (PreConditionNotMetException $e) {
return false;
}
}
/**
* @param $recoveryPassword
*/
public function recoverUsersFiles($recoveryPassword) {
// todo: get system private key here
// $this->keyManager->get
$privateKey = $this->crypt->decryptPrivateKey($encryptedKey,
$recoveryPassword);
$this->recoverAllFiles('/', $privateKey);
}
/**
* @param string $uid
* @return bool
*/
public function userHasFiles($uid) {
return $this->files->file_exists($uid . '/files');
}
/**
* @param $path
* @param $privateKey
*/
private function recoverAllFiles($path, $privateKey) {
// Todo relocate to storage
$dirContent = $this->files->getDirectoryContent($path);
foreach ($dirContent as $item) {
// Get relative path from encryption/keyfiles
$filePath = substr($item['path'], strlen('encryption/keys'));
if ($this->files->is_dir($this->user->getUID() . '/files' . '/' . $filePath)) {
$this->recoverAllFiles($filePath . '/', $privateKey);
} else {
$this->recoverFile($filePath, $privateKey);
}
}
}
/**
* @param $filePath
* @param $privateKey
*/
private function recoverFile($filePath, $privateKey) {
$sharingEnabled = Share::isEnabled();
// Find out who, if anyone, is sharing the file
if ($sharingEnabled) {
$result = Share::getUsersSharingFile($filePath,
$this->user->getUID(),
true);
$userIds = $result['users'];
$userIds[] = 'public';
} else {
$userIds = [
$this->user->getUID(),
$this->recoveryKeyId
];
}
$filteredUids = $this->filterShareReadyUsers($userIds);
// Decrypt file key
$encKeyFile = $this->keyManager->getFileKey($filePath);
$shareKey = $this->keyManager->getShareKey($filePath);
$plainKeyFile = $this->crypt->multiKeyDecrypt($encKeyFile,
$shareKey,
$privateKey);
// Encrypt the file key again to all users, this time with the new publick keyt for the recovered user
$userPublicKeys = $this->keyManager->getPublicKeys($filteredUids['ready']);
$multiEncryptionKey = $this->crypt->multiKeyEncrypt($plainKeyFile,
$userPublicKeys);
$this->keyManager->setFileKeys($multiEncryptionKey['data']);
$this->keyManager->setShareKeys($multiEncryptionKey['keys']);
}
/**
* @param $userIds
* @return array
*/
private function filterShareReadyUsers($userIds) {
// This array will collect the filtered IDs
$readyIds = $unreadyIds = [];
// Loop though users and create array of UIDs that need new keyfiles
foreach ($userIds as $user) {
// Check that the user is encryption capable, or is the
// public system user (for public shares)
if ($this->isUserReady($user)) {
// construct array of ready UIDs for keymanager
$readyIds[] = $user;
} else {
// Construct array of unready UIDs for keymanager
$unreadyIds[] = $user;
// Log warning; we cant do necessary setup here
// because we don't have the user passphrase
$this->logger->warning('Encryption Library ' . $this->user->getUID() . ' is not setup for encryption');
}
}
return [
'ready' => $readyIds,
'unready' => $unreadyIds
];
}
}

View File

@ -6,17 +6,17 @@
* See the COPYING-README file.
*/
use OCA\Encryption\KeyManager;
\OC_Util::checkAdminUser();
$tmpl = new OCP\Template('files_encryption', 'settings-admin');
// Check if an adminRecovery account is enabled for recovering files after lost pwd
$recoveryAdminEnabled = \OC::$server->getAppConfig()->getValue('files_encryption', 'recoveryAdminEnabled', '0');
$session = new \OCA\Files_Encryption\Session(new \OC\Files\View('/'));
$initStatus = $session->getInitialized();
$recoveryAdminEnabled = \OC::$server->getConfig()->getAppValue('encryption', 'recoveryAdminEnabled', '0');
$tmpl->assign('recoveryEnabled', $recoveryAdminEnabled);
$tmpl->assign('initStatus', $initStatus);
$tmpl->assign('initStatus', KeyManager::$cacheFactory->get('initStatus'));
\OCP\Util::addscript('files_encryption', 'settings-admin');
\OCP\Util::addscript('core', 'multiselect');

View File

@ -27,33 +27,42 @@ class KeyManagerTest extends TestCase {
*/
private $dummyKeys;
/**
*
*/
public function setUp() {
parent::setUp();
$keyStorageMock = $this->getMock('OCP\Encryption\IKeyStorage');
$cryptMock = $this->getMockBuilder('OCA\Encryption\Crypt')
$keyStorageMock = $this->getMock('OCP\Encryption\Keys\IStorage');
$keyStorageMock->method('getUserKey')
->will($this->returnValue(false));
$keyStorageMock->method('setUserKey')
->will($this->returnValue(true));
$cryptMock = $this->getMockBuilder('OCA\Encryption\Crypto\Crypt')
->disableOriginalConstructor()
->getMock();
$configMock = $this->getMock('OCP\IConfig');
$userMock = $this->getMock('OCP\IUser');
$userMock->expects($this->once())
$userMock = $this->getMock('OCP\IUserSession');
$userMock
->method('getUID')
->will($this->returnValue('admin'));
$cacheMock = $this->getMock('OCP\ICacheFactory');
$logMock = $this->getMock('OCP\ILogger');
$this->userId = 'admin';
$this->instance = new KeyManager($keyStorageMock, $cryptMock, $configMock, $userMock);
$this->instance = new KeyManager($keyStorageMock, $cryptMock, $configMock, $userMock, $cacheMock, $logMock);
$this->dummyKeys = ['public' => 'randomweakpublickeyhere',
'private' => 'randomweakprivatekeyhere'];
}
/**
* @expectedException OC\Encryption\Exceptions\PrivateKeyMissingException
* @expectedException \OC\Encryption\Exceptions\PrivateKeyMissingException
*/
public function testGetPrivateKey() {
$this->assertFalse($this->instance->getPrivateKey($this->userId));
}
/**
* @expectedException OC\Encryption\Exceptions\PublicKeyMissingException
* @expectedException \OC\Encryption\Exceptions\PublicKeyMissingException
*/
public function testGetPublicKey() {
$this->assertFalse($this->instance->getPublicKey($this->userId));
@ -73,18 +82,34 @@ class KeyManagerTest extends TestCase {
$this->assertFalse($this->instance->checkRecoveryPassword('pass'));
}
/**
*
*/
public function testSetPublicKey() {
$this->assertTrue($this->instance->setPublicKey($this->userId, $this->dummyKeys['public']));
}
/**
*
*/
public function testSetPrivateKey() {
$this->assertTrue($this->instance->setPrivateKey($this->userId, $this->dummyKeys['private']));
}
/**
*
*/
public function testUserHasKeys() {
$this->assertFalse($this->instance->userHasKeys($this->userId));
}
/**
*
*/
public function testInit() {
$this->assertFalse($this->instance->init($this->userId, 'pass'));
}
}

View File

@ -51,8 +51,7 @@ class MigratorTest extends TestCase {
parent::setUp();
$cryptMock = $this->getMockBuilder('OCA\Encryption\Crypto\Crypt')->disableOriginalConstructor()->getMock();
$this->instance = new Migrator($this->getMock('OCP\IUser'),
$this->getMock('OCP\IConfig'),
$this->instance = new Migrator($this->getMock('OCP\IConfig'),
$this->getMock('OCP\IUserManager'),
$this->getMock('OCP\ILogger'),
$cryptMock);

View File

@ -34,7 +34,233 @@ class Hooks {
// file for which we want to delete the keys after the delete operation was successful
private static $unmountedFiles = array();
/**
* Startup encryption backend upon user login
* @note This method should never be called for users using client side encryption
*/
public static function login($params) {
if (\OCP\App::isEnabled('files_encryption') === false) {
return true;
}
$l = new \OC_L10N('files_encryption');
$view = new \OC\Files\View('/');
// ensure filesystem is loaded
if (!\OC\Files\Filesystem::$loaded) {
\OC_Util::setupFS($params['uid']);
}
$privateKey = Keymanager::getPrivateKey($view, $params['uid']);
// if no private key exists, check server configuration
if (!$privateKey) {
//check if all requirements are met
if (!Helper::checkRequirements() || !Helper::checkConfiguration()) {
$error_msg = $l->t("Missing requirements.");
$hint = $l->t('Please make sure that OpenSSL together with the PHP extension is enabled and configured properly. For now, the encryption app has been disabled.');
\OC_App::disable('files_encryption');
\OCP\Util::writeLog('Encryption library', $error_msg . ' ' . $hint, \OCP\Util::ERROR);
\OCP\Template::printErrorPage($error_msg, $hint);
}
}
$util = new Util($view, $params['uid']);
// setup user, if user not ready force relogin
if (Helper::setupUser($util, $params['password']) === false) {
return false;
}
$session = $util->initEncryption($params);
// Check if first-run file migration has already been performed
$ready = false;
$migrationStatus = $util->getMigrationStatus();
if ($migrationStatus === Util::MIGRATION_OPEN && $session !== false) {
$ready = $util->beginMigration();
} elseif ($migrationStatus === Util::MIGRATION_IN_PROGRESS) {
// refuse login as long as the initial encryption is running
sleep(5);
\OCP\User::logout();
return false;
}
$result = true;
// If migration not yet done
if ($ready) {
// Encrypt existing user files
try {
$result = $util->encryptAll('/' . $params['uid'] . '/' . 'files');
} catch (\Exception $ex) {
\OCP\Util::writeLog('Encryption library', 'Initial encryption failed! Error: ' . $ex->getMessage(), \OCP\Util::FATAL);
$result = false;
}
if ($result) {
\OC_Log::write(
'Encryption library', 'Encryption of existing files belonging to "' . $params['uid'] . '" completed'
, \OC_Log::INFO
);
// Register successful migration in DB
$util->finishMigration();
} else {
\OCP\Util::writeLog('Encryption library', 'Initial encryption failed!', \OCP\Util::FATAL);
$util->resetMigrationStatus();
\OCP\User::logout();
}
}
return $result;
}
/**
* remove keys from session during logout
*/
public static function logout() {
$session = new Session(new \OC\Files\View());
$session->removeKeys();
}
/**
* setup encryption backend upon user created
* @note This method should never be called for users using client side encryption
*/
public static function postCreateUser($params) {
if (\OCP\App::isEnabled('files_encryption')) {
$view = new \OC\Files\View('/');
$util = new Util($view, $params['uid']);
Helper::setupUser($util, $params['password']);
}
}
/**
* cleanup encryption backend upon user deleted
* @note This method should never be called for users using client side encryption
*/
public static function postDeleteUser($params) {
if (\OCP\App::isEnabled('files_encryption')) {
Keymanager::deletePublicKey(new \OC\Files\View(), $params['uid']);
}
}
/**
* If the password can't be changed within ownCloud, than update the key password in advance.
*/
public static function preSetPassphrase($params) {
if (\OCP\App::isEnabled('files_encryption')) {
if ( ! \OC_User::canUserChangePassword($params['uid']) ) {
self::setPassphrase($params);
}
}
}
/**
* Change a user's encryption passphrase
* @param array $params keys: uid, password
*/
public static function setPassphrase($params) {
if (\OCP\App::isEnabled('files_encryption') === false) {
return true;
}
// Only attempt to change passphrase if server-side encryption
// is in use (client-side encryption does not have access to
// the necessary keys)
if (Crypt::mode() === 'server') {
$view = new \OC\Files\View('/');
$session = new Session($view);
// Get existing decrypted private key
$privateKey = $session->getPrivateKey();
if ($params['uid'] === \OCP\User::getUser() && $privateKey) {
// Encrypt private key with new user pwd as passphrase
$encryptedPrivateKey = Crypt::symmetricEncryptFileContent($privateKey, $params['password'], Helper::getCipher());
// Save private key
if ($encryptedPrivateKey) {
Keymanager::setPrivateKey($encryptedPrivateKey, \OCP\User::getUser());
} else {
\OCP\Util::writeLog('files_encryption', 'Could not update users encryption password', \OCP\Util::ERROR);
}
// 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'];
$util = new Util($view, $user);
$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)
|| !$util->userKeysExists()
|| !$view->file_exists($user . '/files')) {
// backup old keys
$util->backupAllKeys('recovery');
$newUserPassword = $params['password'];
// make sure that the users home is mounted
\OC\Files\Filesystem::initMountPoints($user);
$keypair = Crypt::createKeypair();
// Disable encryption proxy to prevent recursive calls
$proxyStatus = \OC_FileProxy::$enabled;
\OC_FileProxy::$enabled = false;
// Save public key
Keymanager::setPublicKey($keypair['publicKey'], $user);
// Encrypt private key with new password
$encryptedKey = Crypt::symmetricEncryptFileContent($keypair['privateKey'], $newUserPassword, Helper::getCipher());
if ($encryptedKey) {
Keymanager::setPrivateKey($encryptedKey, $user);
if ($recoveryPassword) { // if recovery key is set we can re-encrypt the key files
$util = new Util($view, $user);
$util->recoverUsersFiles($recoveryPassword);
}
} else {
\OCP\Util::writeLog('files_encryption', 'Could not update users encryption password', \OCP\Util::ERROR);
}
\OC_FileProxy::$enabled = $proxyStatus;
}
}
}
}
/**
* after password reset we create a new key pair for the user
*
* @param array $params
*/
public static function postPasswordReset($params) {
$uid = $params['uid'];
$password = $params['password'];
$util = new Util(new \OC\Files\View(), $uid);
$util->replaceUserKeys($password);
}
/*
* check if files can be encrypted to every user.

View File

@ -702,7 +702,7 @@ class Server extends SimpleContainer implements IServerContainer {
*
* @return \OCP\Security\ISecureRandom
*/
function getSecureRandom() {
function getSecureRandom() {
return $this->query('SecureRandom');
}