Updating keystorage movement and fixing hooks
This commit is contained in:
parent
506222567e
commit
0c2f9ca849
|
@ -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()
|
||||
);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
];
|
||||
}
|
||||
|
||||
}
|
|
@ -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');
|
||||
|
|
|
@ -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'));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -702,7 +702,7 @@ class Server extends SimpleContainer implements IServerContainer {
|
|||
*
|
||||
* @return \OCP\Security\ISecureRandom
|
||||
*/
|
||||
function getSecureRandom() {
|
||||
function getSecureRandom() {
|
||||
return $this->query('SecureRandom');
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue