summaryrefslogtreecommitdiffstats
path: root/apps/encryption
diff options
context:
space:
mode:
authorClark Tomlinson <fallen013@gmail.com>2015-03-24 17:29:10 -0400
committerThomas Müller <thomas.mueller@tmit.eu>2015-04-07 13:30:27 +0200
commit0c2f9ca849ef41232511cf576cc9a9de2caa43f0 (patch)
treea75194b573985da4a68684cb5a74c9891db34696 /apps/encryption
parent506222567e71fc0d77fa77ee7805c93fa7655b6c (diff)
downloadnextcloud-server-0c2f9ca849ef41232511cf576cc9a9de2caa43f0.tar.gz
nextcloud-server-0c2f9ca849ef41232511cf576cc9a9de2caa43f0.zip
Updating keystorage movement and fixing hooks
Diffstat (limited to 'apps/encryption')
-rw-r--r--apps/encryption/appinfo/encryption.php43
-rw-r--r--apps/encryption/hooks/userhooks.php213
-rw-r--r--apps/encryption/lib/crypto/Encryption.php17
-rw-r--r--apps/encryption/lib/crypto/crypt.php153
-rw-r--r--apps/encryption/lib/hookmanager.php2
-rw-r--r--apps/encryption/lib/keymanager.php324
-rw-r--r--apps/encryption/lib/migrator.php123
-rw-r--r--apps/encryption/lib/recovery.php58
-rw-r--r--apps/encryption/lib/users/setup.php20
-rw-r--r--apps/encryption/lib/util.php394
-rw-r--r--apps/encryption/settings/settings-admin.php8
-rw-r--r--apps/encryption/tests/lib/KeyManagerTest.php39
-rw-r--r--apps/encryption/tests/lib/MigratorTest.php3
13 files changed, 975 insertions, 422 deletions
diff --git a/apps/encryption/appinfo/encryption.php b/apps/encryption/appinfo/encryption.php
index f2ab89aadef..1a4c17eb60a 100644
--- a/apps/encryption/appinfo/encryption.php
+++ b/apps/encryption/appinfo/encryption.php
@@ -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()
+ );
+ });
+
}
/**
diff --git a/apps/encryption/hooks/userhooks.php b/apps/encryption/hooks/userhooks.php
index 79de70a6d02..096fd3beb93 100644
--- a/apps/encryption/hooks/userhooks.php
+++ b/apps/encryption/hooks/userhooks.php
@@ -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 {
@@ -47,13 +46,13 @@ class UserHooks implements IHook {
*/
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,198 +122,76 @@ class UserHooks implements IHook {
}
// setup user, if user not ready force relogin
- if (!$this->userSetup->setupUser($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();
+ if (!$this->userSetup->setupUser($params['uid'], $params['password'])) {
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.
- */
- public function preSetPassphrase($params) {
- if (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
+ * @param array $params : uid, password
+ * @return bool
*/
- 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);
- }
+ public function preSetPassphrase($params) {
+ if (App::isEnabled('encryption')) {
- \OC_FileProxy::$enabled = $proxyStatus;
+ if (!$this->user->getUser()->canChangePassword()) {
+ if (App::isEnabled('encryption') === false) {
+ return true;
}
+ $this->keyManager->setPassphrase($params,
+ $this->user,
+ $this->util);
}
}
}
+
/**
* after password reset we create a new key pair for the user
*
* @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);
}
}
diff --git a/apps/encryption/lib/crypto/Encryption.php b/apps/encryption/lib/crypto/Encryption.php
index 123581b83ac..b8429d7124e 100644
--- a/apps/encryption/lib/crypto/Encryption.php
+++ b/apps/encryption/lib/crypto/Encryption.php
@@ -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.
+ }
}
diff --git a/apps/encryption/lib/crypto/crypt.php b/apps/encryption/lib/crypto/crypt.php
index 8018f11a370..f9fe4f9bece 100644
--- a/apps/encryption/lib/crypto/crypt.php
+++ b/apps/encryption/lib/crypto/crypt.php
@@ -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());
+ }
+ }
}
diff --git a/apps/encryption/lib/hookmanager.php b/apps/encryption/lib/hookmanager.php
index a535230a6a7..9e6d9cd855c 100644
--- a/apps/encryption/lib/hookmanager.php
+++ b/apps/encryption/lib/hookmanager.php
@@ -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();
diff --git a/apps/encryption/lib/keymanager.php b/apps/encryption/lib/keymanager.php
index 272bf0849c2..83b24c79b8c 100644
--- a/apps/encryption/lib/keymanager.php
+++ b/apps/encryption/lib/keymanager.php
@@ -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,35 +59,143 @@ 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;
+ self::$cacheFactory = $cacheFactory;
+ self::$cacheFactory = self::$cacheFactory->create('encryption');
+ $this->log = $log;
+ }
+
+ /**
+ * @return bool
+ */
+ public function recoveryKeyExists() {
+ return (strlen($this->keyStorage->getSystemUserKey($this->recoveryKeyId)) !== 0);
+ }
+
+ /**
+ * @param $password
+ * @return bool
+ */
+ public function checkRecoveryPassword($password) {
+ $recoveryKey = $this->keyStorage->getSystemUserKey($this->recoveryKeyId);
+ $decryptedRecoveryKey = $this->crypt->decryptPrivateKey($recoveryKey,
+ $password);
+
+ if ($decryptedRecoveryKey) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * @param string $uid
+ * @param string $password
+ * @param string $keyPair
+ * @return bool
+ */
+ public function storeKeyPair($uid, $password, $keyPair) {
+ // Save Public Key
+ $this->setPublicKey($uid, $keyPair['publicKey']);
+
+ $encryptedKey = $this->crypt->symmetricEncryptFileContent($keyPair['privateKey'],
+ $password);
+
+ if ($encryptedKey) {
+ $this->setPrivateKey($uid, $encryptedKey);
+ $this->config->setAppValue('encryption', 'recoveryAdminEnabled', 1);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * @param $userId
+ * @param $key
+ * @return bool
+ */
+ public function setPublicKey($userId, $key) {
+ return $this->keyStorage->setUserKey($userId, $this->publicKeyId, $key);
+ }
+
+ /**
+ * @param $userId
+ * @param $key
+ * @return bool
+ */
+ public function setPrivateKey($userId, $key) {
+ return $this->keyStorage->setUserKey($userId,
+ $this->privateKeyId,
+ $key);
+ }
+
+ /**
+ * Decrypt private key and store it
+ *
+ * @param string $uid userid
+ * @param string $passPhrase users password
+ * @return ICache
+ */
+ public function init($uid, $passPhrase) {
+ try {
+ $privateKey = $this->getPrivateKey($uid);
+ $privateKey = $this->crypt->decryptPrivateKey($privateKey,
+ $passPhrase);
+ } catch (PrivateKeyMissingException $e) {
+ return false;
+ } catch (DecryptionFailedException $e) {
+ return false;
+ }
+
+ self::$cacheFactory->set('privateKey', $privateKey);
+ self::$cacheFactory->set('initStatus', true);
+
+
+ return self::$cacheFactory;
}
/**
@@ -90,7 +204,8 @@ class KeyManager {
* @throws PrivateKeyMissingException
*/
public function getPrivateKey($userId) {
- $privateKey = $this->keyStorage->getUserKey($userId, $this->privateKeyId);
+ $privateKey = $this->keyStorage->getUserKey($userId,
+ $this->privateKeyId);
if (strlen($privateKey) !== 0) {
return $privateKey;
@@ -99,24 +214,105 @@ class KeyManager {
}
/**
- * @param $userId
+ * @param $path
* @return mixed
- * @throws PublicKeyMissingException
*/
- public function getPublicKey($userId) {
- $publicKey = $this->keyStorage->getUserKey($userId, $this->publicKeyId);
+ public function getFileKey($path) {
+ return $this->keyStorage->getFileKey($path, $this->fileKeyId);
+ }
- if (strlen($publicKey) !== 0) {
- return $publicKey;
- }
- throw new PublicKeyMissingException();
+ /**
+ * @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 recoveryKeyExists() {
- return (strlen($this->keyStorage->getSystemUserKey($this->recoveryKeyId)) !== 0);
+ 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;
+ }
+ }
+ }
}
/**
@@ -136,82 +332,70 @@ class KeyManager {
}
/**
- * @param $password
- * @return bool
+ * @param $userId
+ * @return mixed
+ * @throws PublicKeyMissingException
*/
- public function checkRecoveryPassword($password) {
- $recoveryKey = $this->keyStorage->getSystemUserKey($this->recoveryKeyId);
- $decryptedRecoveryKey = $this->crypt->decryptPrivateKey($recoveryKey, $password);
+ public function getPublicKey($userId) {
+ $publicKey = $this->keyStorage->getUserKey($userId, $this->publicKeyId);
- if ($decryptedRecoveryKey) {
- return true;
+ if (strlen($publicKey) !== 0) {
+ return $publicKey;
}
- return false;
+ throw new PublicKeyMissingException();
}
/**
- * @param $userId
- * @param $key
- * @return bool
+ * @param $purpose
+ * @param bool $timestamp
+ * @param bool $includeUserKeys
*/
- public function setPublicKey($userId, $key) {
- return $this->keyStorage->setUserKey($userId, $this->publicKeyId, $key);
+ public function backupAllKeys($purpose, $timestamp = true, $includeUserKeys = true) {
+// $backupDir = $this->keyStorage->;
}
/**
- * @param $userId
- * @param $key
- * @return bool
+ * @param string $uid
*/
- public function setPrivateKey($userId, $key) {
- return $this->keyStorage->setUserKey($userId, $this->privateKeyId, $key);
+ public function replaceUserKeys($uid) {
+ $this->backupAllKeys('password_reset');
+ $this->deletePublicKey($uid);
+ $this->deletePrivateKey($uid);
}
-
/**
- * @param $password
- * @param $keyPair
+ * @param $uid
* @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;
+ public function deletePublicKey($uid) {
+ return $this->keyStorage->deleteUserKey($uid, $this->publicKeyId);
}
/**
+ * @param $uid
* @return bool
*/
- public function ready() {
- return $this->keyStorage->ready();
+ private function deletePrivateKey($uid) {
+ return $this->keyStorage->deleteUserKey($uid, $this->privateKeyId);
}
-
/**
- * @return \OCP\ICache
- * @throws PrivateKeyMissingException
+ * @param array $userIds
+ * @return array
+ * @throws PublicKeyMissingException
*/
- public function init() {
- try {
- $privateKey = $this->getPrivateKey($this->keyId);
- } catch (PrivateKeyMissingException $e) {
- return false;
- }
+ public function getPublicKeys(array $userIds) {
+ $keys = [];
- $cache = \OC::$server->getMemCacheFactory();
+ foreach ($userIds as $userId) {
+ try {
+ $keys[$userId] = $this->getPublicKey($userId);
+ } catch (PublicKeyMissingException $e) {
+ continue;
+ }
+ }
- $cacheInstance = $cache->create('Encryption');
- $cacheInstance->set('privateKey', $privateKey);
+ return $keys;
- return $cacheInstance;
}
-
}
diff --git a/apps/encryption/lib/migrator.php b/apps/encryption/lib/migrator.php
deleted file mode 100644
index 8f7823cb1ae..00000000000
--- a/apps/encryption/lib/migrator.php
+++ /dev/null
@@ -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;
- }
- }
-}
diff --git a/apps/encryption/lib/recovery.php b/apps/encryption/lib/recovery.php
index 88350e96c53..457184b4b96 100644
--- a/apps/encryption/lib/recovery.php
+++ b/apps/encryption/lib/recovery.php
@@ -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);
+ }
+
}
diff --git a/apps/encryption/lib/users/setup.php b/apps/encryption/lib/users/setup.php
index 123d6973be9..662a4b4b6af 100644
--- a/apps/encryption/lib/users/setup.php
+++ b/apps/encryption/lib/users/setup.php
@@ -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;
}
diff --git a/apps/encryption/lib/util.php b/apps/encryption/lib/util.php
new file mode 100644
index 00000000000..5cc658a3136
--- /dev/null
+++ b/apps/encryption/lib/util.php
@@ -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
+ ];
+ }
+
+}
diff --git a/apps/encryption/settings/settings-admin.php b/apps/encryption/settings/settings-admin.php
index 0f5d56a3734..d3acdfe2ba4 100644
--- a/apps/encryption/settings/settings-admin.php
+++ b/apps/encryption/settings/settings-admin.php
@@ -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');
diff --git a/apps/encryption/tests/lib/KeyManagerTest.php b/apps/encryption/tests/lib/KeyManagerTest.php
index 260e69a73bf..01c5e1d2d07 100644
--- a/apps/encryption/tests/lib/KeyManagerTest.php
+++ b/apps/encryption/tests/lib/KeyManagerTest.php
@@ -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'));
+ }
+
}
diff --git a/apps/encryption/tests/lib/MigratorTest.php b/apps/encryption/tests/lib/MigratorTest.php
index a9d57b34209..0d30b9865b9 100644
--- a/apps/encryption/tests/lib/MigratorTest.php
+++ b/apps/encryption/tests/lib/MigratorTest.php
@@ -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);