diff --git a/apps/files_encryption/ajax/changeRecoveryPassword.php b/apps/files_encryption/ajax/changeRecoveryPassword.php index b0594f967b..366f634a51 100644 --- a/apps/files_encryption/ajax/changeRecoveryPassword.php +++ b/apps/files_encryption/ajax/changeRecoveryPassword.php @@ -22,28 +22,28 @@ $return = false; $oldPassword = $_POST['oldPassword']; $newPassword = $_POST['newPassword']; +$view = new \OC\Files\View('/'); $util = new \OCA\Encryption\Util(new \OC_FilesystemView('/'), \OCP\User::getUser()); -$result = $util->checkRecoveryPassword($oldPassword); +$proxyStatus = \OC_FileProxy::$enabled; +\OC_FileProxy::$enabled = false; -if ($result) { - $keyId = $util->getRecoveryKeyId(); - $keyPath = '/owncloud_private_key/' . $keyId . '.private.key'; - $view = new \OC\Files\View('/'); +$keyId = $util->getRecoveryKeyId(); +$keyPath = '/owncloud_private_key/' . $keyId . '.private.key'; - $proxyStatus = \OC_FileProxy::$enabled; - \OC_FileProxy::$enabled = false; +$encryptedRecoveryKey = $view->file_get_contents($keyPath); +$decryptedRecoveryKey = \OCA\Encryption\Crypt::decryptPrivateKey($encryptedRecoveryKey, $oldPassword); + +if ($decryptedRecoveryKey) { - $encryptedRecoveryKey = $view->file_get_contents($keyPath); - $decryptedRecoveryKey = \OCA\Encryption\Crypt::symmetricDecryptFileContent($encryptedRecoveryKey, $oldPassword); $encryptedRecoveryKey = \OCA\Encryption\Crypt::symmetricEncryptFileContent($decryptedRecoveryKey, $newPassword); $view->file_put_contents($keyPath, $encryptedRecoveryKey); - \OC_FileProxy::$enabled = $proxyStatus; - $return = true; } +\OC_FileProxy::$enabled = $proxyStatus; + // success or failure if ($return) { \OCP\JSON::success(array('data' => array('message' => $l->t('Password successfully changed.')))); diff --git a/apps/files_encryption/ajax/updatePrivateKeyPassword.php b/apps/files_encryption/ajax/updatePrivateKeyPassword.php new file mode 100644 index 0000000000..6fd63dae9c --- /dev/null +++ b/apps/files_encryption/ajax/updatePrivateKeyPassword.php @@ -0,0 +1,54 @@ + + * This file is licensed under the Affero General Public License version 3 or later. + * See the COPYING-README file. + * + * @brief Script to change recovery key password + * + */ + +use OCA\Encryption; + +\OCP\JSON::checkLoggedIn(); +\OCP\JSON::checkAppEnabled('files_encryption'); +\OCP\JSON::callCheck(); + +$l = OC_L10N::get('core'); + +$return = false; + +$oldPassword = $_POST['oldPassword']; +$newPassword = $_POST['newPassword']; + +$view = new \OC\Files\View('/'); +$session = new \OCA\Encryption\Session($view); +$user = \OCP\User::getUser(); + +$proxyStatus = \OC_FileProxy::$enabled; +\OC_FileProxy::$enabled = false; + +$keyPath = '/' . $user . '/files_encryption/' . $user . '.private.key'; + +$encryptedKey = $view->file_get_contents($keyPath); +$decryptedKey = \OCA\Encryption\Crypt::decryptPrivateKey($encryptedKey, $oldPassword); + +if ($decryptedKey) { + + $encryptedKey = \OCA\Encryption\Crypt::symmetricEncryptFileContent($decryptedKey, $newPassword); + $view->file_put_contents($keyPath, $encryptedKey); + + $session->setPrivateKey($decryptedKey); + + $return = true; +} + +\OC_FileProxy::$enabled = $proxyStatus; + +// success or failure +if ($return) { + \OCP\JSON::success(array('data' => array('message' => $l->t('Private key password successfully updated.')))); +} else { + \OCP\JSON::error(array('data' => array('message' => $l->t('Could not update the private key password. Maybe the old password was not correct.')))); +} \ No newline at end of file diff --git a/apps/files_encryption/appinfo/app.php b/apps/files_encryption/appinfo/app.php index f69e04b5bb..419bef1ede 100644 --- a/apps/files_encryption/appinfo/app.php +++ b/apps/files_encryption/appinfo/app.php @@ -35,9 +35,10 @@ if (!OC_Config::getValue('maintenance', false)) { $view = new OC_FilesystemView('/'); $session = new \OCA\Encryption\Session($view); + $user = \OCP\USER::getUser(); // check if user has a private key if ( - !$session->getPrivateKey(\OCP\USER::getUser()) + !$view->file_exists('/' . $user . '/files_encryption/' . $user . '.private.key') && OCA\Encryption\Crypt::mode() === 'server' ) { diff --git a/apps/files_encryption/files/error.php b/apps/files_encryption/files/error.php new file mode 100644 index 0000000000..63c74e4e79 --- /dev/null +++ b/apps/files_encryption/files/error.php @@ -0,0 +1,24 @@ +t('Your private key is not valid! Maybe your password was changed from outside. You can update your private key password in your personal settings to regain access to your files'); + + if(isset($_GET['p']) && $_GET['p'] === '1') { + header('HTTP/1.0 404 ' . $errorMsg); + } + + // check if ajax request + if(!empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') { + \OCP\JSON::error(array('data' => array('message' => $errorMsg))); + } else { + header('HTTP/1.0 404 ' . $errorMsg); + $tmpl = new OC_Template('files_encryption', 'invalid_private_key', 'guest'); + $tmpl->printPage(); + } + + exit; +} +?> diff --git a/apps/files_encryption/hooks/hooks.php b/apps/files_encryption/hooks/hooks.php index 0580b713d1..7698b95cfd 100644 --- a/apps/files_encryption/hooks/hooks.php +++ b/apps/files_encryption/hooks/hooks.php @@ -60,11 +60,16 @@ class Hooks { $encryptedKey = Keymanager::getPrivateKey($view, $params['uid']); - $privateKey = Crypt::symmetricDecryptFileContent($encryptedKey, $params['password']); + $privateKey = Crypt::decryptPrivateKey($encryptedKey, $params['password']); + + if ($privateKey === false) { + \OCP\Util::writeLog('Encryption library', 'Private key for user "' . $params['uid'] + . '" is not valid! Maybe the user password was changed from outside if so please change it back to gain access', \OCP\Util::ERROR); + } $session = new \OCA\Encryption\Session($view); - $session->setPrivateKey($privateKey, $params['uid']); + $session->setPrivateKey($privateKey); // Check if first-run file migration has already been performed $ready = false; @@ -160,7 +165,7 @@ class Hooks { public static function setPassphrase($params) { // Only attempt to change passphrase if server-side encryption - // is in use (client-side encryption does not have access to + // is in use (client-side encryption does not have access to // the necessary keys) if (Crypt::mode() === 'server') { @@ -345,7 +350,7 @@ class Hooks { $sharingEnabled = \OCP\Share::isEnabled(); // get the path including mount point only if not a shared folder - if(strncmp($path, '/Shared' , strlen('/Shared') !== 0)) { + if (strncmp($path, '/Shared', strlen('/Shared') !== 0)) { // get path including the the storage mount point $path = $util->getPathWithMountPoint($params['itemSource']); } @@ -422,14 +427,14 @@ class Hooks { } // get the path including mount point only if not a shared folder - if(strncmp($path, '/Shared' , strlen('/Shared') !== 0)) { + if (strncmp($path, '/Shared', strlen('/Shared') !== 0)) { // get path including the the storage mount point $path = $util->getPathWithMountPoint($params['itemSource']); } // if we unshare a folder we need a list of all (sub-)files if ($params['itemType'] === 'folder') { - $allFiles = $util->getAllFiles( $path ); + $allFiles = $util->getAllFiles($path); } else { $allFiles = array($path); } diff --git a/apps/files_encryption/js/settings-personal.js b/apps/files_encryption/js/settings-personal.js index 312b672ad4..d6535a25b7 100644 --- a/apps/files_encryption/js/settings-personal.js +++ b/apps/files_encryption/js/settings-personal.js @@ -4,7 +4,25 @@ * See the COPYING-README file. */ +function updatePrivateKeyPasswd() { + var oldPrivateKeyPassword = $('input:password[id="oldPrivateKeyPassword"]').val(); + var newPrivateKeyPassword = $('input:password[id="newPrivateKeyPassword"]').val(); + OC.msg.startSaving('#encryption .msg'); + $.post( + OC.filePath( 'files_encryption', 'ajax', 'updatePrivateKeyPassword.php' ) + , { oldPassword: oldPrivateKeyPassword, newPassword: newPrivateKeyPassword } + , function( data ) { + if (data.status === "error") { + OC.msg.finishedSaving('#encryption .msg', data); + } else { + OC.msg.finishedSaving('#encryption .msg', data); + } + } + ); +} + $(document).ready(function(){ + // Trigger ajax on recoveryAdmin status change $( 'input:radio[name="userEnableRecovery"]' ).change( function() { @@ -57,4 +75,24 @@ $(document).ready(function(){ } ); + + // update private key password + + $('input:password[name="changePrivateKeyPassword"]').keyup(function(event) { + var oldPrivateKeyPassword = $('input:password[id="oldPrivateKeyPassword"]').val(); + var newPrivateKeyPassword = $('input:password[id="newPrivateKeyPassword"]').val(); + if (newPrivateKeyPassword !== '' && oldPrivateKeyPassword !== '' ) { + $('button:button[name="submitChangePrivateKeyPassword"]').removeAttr("disabled"); + if(event.which === 13) { + updatePrivateKeyPasswd(); + } + } else { + $('button:button[name="submitChangePrivateKeyPassword"]').attr("disabled", "true"); + } + }); + + $('button:button[name="submitChangePrivateKeyPassword"]').click(function() { + updatePrivateKeyPasswd(); + }); + }); \ No newline at end of file diff --git a/apps/files_encryption/lib/crypt.php b/apps/files_encryption/lib/crypt.php index ced9ab7c67..cd41390d1c 100755 --- a/apps/files_encryption/lib/crypt.php +++ b/apps/files_encryption/lib/crypt.php @@ -351,6 +351,34 @@ class Crypt { } + /** + * @brief Decrypt private key and check if the result is a valid keyfile + * @param string $encryptedKey encrypted keyfile + * @param string $passphrase to decrypt keyfile + * @returns encrypted private key or false + * + * This function decrypts a file + */ + public static function decryptPrivateKey($encryptedKey, $passphrase) { + + $plainKey = self::symmetricDecryptFileContent($encryptedKey, $passphrase); + + // check if this a valid private key + $res = openssl_pkey_get_private($plainKey); + if (is_resource($res)) { + $sslInfo = openssl_pkey_get_details($res); + if (!isset($sslInfo['key'])) { + $plainKey = false; + } + } else { + $plainKey = false; + } + + return $plainKey; + + } + + /** * @brief Creates symmetric keyfile content using a generated key * @param string $plainContent content to be encrypted diff --git a/apps/files_encryption/lib/helper.php b/apps/files_encryption/lib/helper.php index 184e178249..a22c139c50 100755 --- a/apps/files_encryption/lib/helper.php +++ b/apps/files_encryption/lib/helper.php @@ -74,7 +74,7 @@ class Helper { if (!$util->ready()) { \OCP\Util::writeLog('Encryption library', 'User account "' . $util->getUserId() - . '" is not ready for encryption; configuration started', \OCP\Util::DEBUG); + . '" is not ready for encryption; configuration started', \OCP\Util::DEBUG); if (!$util->setupServerSide($password)) { return false; @@ -94,6 +94,7 @@ class Helper { * @return bool */ public static function adminEnableRecovery($recoveryKeyId, $recoveryPassword) { + $view = new \OC\Files\View('/'); if ($recoveryKeyId === null) { @@ -128,13 +129,6 @@ class Helper { // Save private key $view->file_put_contents('/owncloud_private_key/' . $recoveryKeyId . '.private.key', $encryptedPrivateKey); - // create control file which let us check later on if the entered password was correct. - $encryptedControlData = \OCA\Encryption\Crypt::keyEncrypt("ownCloud", $keypair['publicKey']); - if (!$view->is_dir('/control-file')) { - $view->mkdir('/control-file'); - } - $view->file_put_contents('/control-file/controlfile.enc', $encryptedControlData); - \OC_FileProxy::$enabled = true; // Set recoveryAdmin as enabled @@ -201,4 +195,17 @@ class Helper { return $relPath; } + + /** + * @brief redirect to a error page + */ + public static function redirectToErrorPage() { + $location = \OC_Helper::linkToAbsolute('apps/files_encryption/files', 'error.php'); + $post = 0; + if(count($_POST) > 0) { + $post = 1; + } + header('Location: ' . $location . '?p=' . $post); + exit(); + } } \ No newline at end of file diff --git a/apps/files_encryption/lib/session.php b/apps/files_encryption/lib/session.php index bff1737554..1911386cd1 100644 --- a/apps/files_encryption/lib/session.php +++ b/apps/files_encryption/lib/session.php @@ -88,9 +88,10 @@ class Session { $proxyStatus = \OC_FileProxy::$enabled; \OC_FileProxy::$enabled = false; - $encryptedKey = $this->view->file_get_contents( '/owncloud_private_key/' . $publicShareKeyId . '.private.key' ); - $privateKey = Crypt::symmetricDecryptFileContent( $encryptedKey, '' ); - $this->setPublicSharePrivateKey( $privateKey ); + $encryptedKey = $this->view->file_get_contents( + '/owncloud_private_key/' . $publicShareKeyId . '.private.key'); + $privateKey = Crypt::decryptPrivateKey($encryptedKey, ''); + $this->setPublicSharePrivateKey($privateKey); \OC_FileProxy::$enabled = $proxyStatus; } @@ -121,7 +122,7 @@ class Session { if (\OCA\Encryption\Helper::isPublicAccess()) { return $this->getPublicSharePrivateKey(); } else { - if (!is_null( \OC::$session->get('privateKey') )) { + if (!is_null(\OC::$session->get('privateKey'))) { return \OC::$session->get('privateKey'); } else { return false; @@ -136,7 +137,7 @@ class Session { */ public function setPublicSharePrivateKey($privateKey) { - \OC::$session->set('publicSharePrivateKey', $privateKey); + \OC::$session->set('publicSharePrivateKey', $privateKey); return true; @@ -149,7 +150,7 @@ class Session { */ public function getPublicSharePrivateKey() { - if (!is_null( \OC::$session->get('publicSharePrivateKey') )) { + if (!is_null(\OC::$session->get('publicSharePrivateKey'))) { return \OC::$session->get('publicSharePrivateKey'); } else { return false; @@ -176,7 +177,7 @@ class Session { */ public function getLegacyKey() { - if ( !is_null( \OC::$session->get('legacyKey') ) ) { + if (!is_null(\OC::$session->get('legacyKey'))) { return \OC::$session->get('legacyKey'); diff --git a/apps/files_encryption/lib/stream.php b/apps/files_encryption/lib/stream.php index 072c528664..3c1eb2c5f5 100644 --- a/apps/files_encryption/lib/stream.php +++ b/apps/files_encryption/lib/stream.php @@ -56,18 +56,21 @@ class Stream { private $relPath; // rel path to users file dir private $userId; private $handle; // Resource returned by fopen - private $path; - private $readBuffer; // For streams that dont support seeking private $meta = array(); // Header / meta for source stream - private $count; private $writeCache; private $size; private $unencryptedSize; private $publicKey; - private $keyfile; private $encKeyfile; - private static $view; // a fsview object set to user dir + /** + * @var \OC\Files\View + */ private $rootView; // a fsview object set to '/' + /** + * @var \OCA\Encryption\Session + */ + private $session; + private $privateKey; /** * @param $path @@ -82,6 +85,10 @@ class Stream { $this->rootView = new \OC_FilesystemView('/'); } + $this->session = new \OCA\Encryption\Session($this->rootView); + + $this->privateKey = $this->session->getPrivateKey($this->userId); + $util = new Util($this->rootView, \OCP\USER::getUser()); $this->userId = $util->getUserId(); @@ -109,6 +116,11 @@ class Stream { } else { + if($this->privateKey === false) { + // if private key is not valid redirect user to a error page + \OCA\Encryption\Helper::redirectToErrorPage(); + } + $this->size = $this->rootView->filesize($this->rawPath, $mode); } @@ -118,7 +130,7 @@ class Stream { if (!is_resource($this->handle)) { - \OCP\Util::writeLog('files_encryption', 'failed to open file "' . $this->rawPath . '"', \OCP\Util::ERROR); + \OCP\Util::writeLog('Encryption library', 'failed to open file "' . $this->rawPath . '"', \OCP\Util::ERROR); } else { @@ -156,7 +168,7 @@ class Stream { // $count will always be 8192 https://bugs.php.net/bug.php?id=21641 // This makes this function a lot simpler, but will break this class if the above 'bug' gets 'fixed' - \OCP\Util::writeLog('files_encryption', 'PHP "bug" 21641 no longer holds, decryption system requires refactoring', \OCP\Util::FATAL); + \OCP\Util::writeLog('Encryption library', 'PHP "bug" 21641 no longer holds, decryption system requires refactoring', \OCP\Util::FATAL); die(); @@ -165,7 +177,7 @@ class Stream { // Get the data from the file handle $data = fread($this->handle, 8192); - $result = ''; + $result = null; if (strlen($data)) { @@ -175,10 +187,11 @@ class Stream { throw new \Exception( 'Encryption key not found for "' . $this->rawPath . '" during attempted read via stream'); - } + } else { - // Decrypt data - $result = Crypt::symmetricDecryptFileContent($data, $this->plainKey); + // Decrypt data + $result = Crypt::symmetricDecryptFileContent($data, $this->plainKey); + } } @@ -228,13 +241,18 @@ class Stream { // If a keyfile already exists if ($this->encKeyfile) { - $session = new \OCA\Encryption\Session( $this->rootView ); + // if there is no valid private key return false + if ($this->privateKey === false) { - $privateKey = $session->getPrivateKey($this->userId); + // if private key is not valid redirect user to a error page + \OCA\Encryption\Helper::redirectToErrorPage(); + + return false; + } $shareKey = Keymanager::getShareKey($this->rootView, $this->userId, $this->relPath); - $this->plainKey = Crypt::multiKeyDecrypt($this->encKeyfile, $shareKey, $privateKey); + $this->plainKey = Crypt::multiKeyDecrypt($this->encKeyfile, $shareKey, $this->privateKey); return true; @@ -257,6 +275,12 @@ class Stream { */ public function stream_write($data) { + // if there is no valid private key return false + if ($this->privateKey === false) { + $this->size = 0; + return strlen($data); + } + // Disable the file proxies so that encryption is not // automatically attempted when the file is written to disk - // we are handling that separately here and we don't want to @@ -424,6 +448,28 @@ class Stream { $this->flush(); + // if there is no valid private key return false + if ($this->privateKey === false) { + + // cleanup + if ($this->meta['mode'] !== 'r' && $this->meta['mode'] !== 'rb') { + + // Disable encryption proxy to prevent recursive calls + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; + + if ($this->rootView->file_exists($this->rawPath) && $this->size === 0) { + $this->rootView->unlink($this->rawPath); + } + + // Re-enable proxy - our work is done + \OC_FileProxy::$enabled = $proxyStatus; + } + + // if private key is not valid redirect user to a error page + \OCA\Encryption\Helper::redirectToErrorPage(); + } + if ( $this->meta['mode'] !== 'r' and $this->meta['mode'] !== 'rb' @@ -450,16 +496,14 @@ class Stream { // Encrypt enc key for all sharing users $this->encKeyfiles = Crypt::multiKeyEncrypt($this->plainKey, $publicKeys); - $view = new \OC_FilesystemView('/'); - // Save the new encrypted file key Keymanager::setFileKey($this->rootView, $this->relPath, $this->userId, $this->encKeyfiles['data']); // Save the sharekeys - Keymanager::setShareKeys($view, $this->relPath, $this->encKeyfiles['keys']); + Keymanager::setShareKeys($this->rootView, $this->relPath, $this->encKeyfiles['keys']); // get file info - $fileInfo = $view->getFileInfo($this->rawPath); + $fileInfo = $this->rootView->getFileInfo($this->rawPath); if (!is_array($fileInfo)) { $fileInfo = array(); } @@ -473,7 +517,7 @@ class Stream { $fileInfo['unencrypted_size'] = $this->unencryptedSize; // set fileinfo - $view->putFileInfo($this->rawPath, $fileInfo); + $this->rootView->putFileInfo($this->rawPath, $fileInfo); } return fclose($this->handle); diff --git a/apps/files_encryption/lib/util.php b/apps/files_encryption/lib/util.php index 463e4fae79..94defa726a 100644 --- a/apps/files_encryption/lib/util.php +++ b/apps/files_encryption/lib/util.php @@ -305,7 +305,7 @@ class Util { if (\OCP\DB::isError($result)) { \OCP\Util::writeLog('Encryption library', \OC_DB::getErrorMessage($result), \OCP\Util::ERROR); } else { - if($result->numRows() > 0) { + if ($result->numRows() > 0) { $row = $result->fetchRow(); if (isset($row['recovery_enabled'])) { $recoveryEnabled[] = $row['recovery_enabled']; @@ -445,7 +445,7 @@ class Util { // If the file uses old // encryption system - } elseif ( Crypt::isLegacyEncryptedContent( $data, $relPath ) ) { + } elseif (Crypt::isLegacyEncryptedContent($data, $relPath)) { $found['legacy'][] = array( 'name' => $file, @@ -576,7 +576,9 @@ class Util { // get relative path $relativePath = \OCA\Encryption\Helper::stripUserFilesPath($path); - if (isset($pathParts[2]) && $pathParts[2] === 'files' && $this->view->file_exists($path) && $this->isEncryptedPath($path)) { + if (isset($pathParts[2]) && $pathParts[2] === 'files' && $this->view->file_exists($path) + && $this->isEncryptedPath($path) + ) { // get the size from filesystem $fullPath = $this->view->getLocalFile($path); @@ -646,7 +648,7 @@ class Util { return $result; } - + /** * @param $path * @return bool @@ -690,28 +692,32 @@ class Util { $relPath = $plainFile['path']; //relative to /data - $rawPath = '/'.$this->userId . '/files/' . $plainFile['path']; + $rawPath = '/' . $this->userId . '/files/' . $plainFile['path']; // Open plain file handle for binary reading - $plainHandle = $this->view->fopen( $rawPath, 'rb' ); + $plainHandle = $this->view->fopen($rawPath, 'rb'); // Open enc file handle for binary writing, with same filename as original plain file - $encHandle = fopen( 'crypt://' . $relPath.'.tmp', 'wb' ); + $encHandle = fopen('crypt://' . $relPath . '.tmp', 'wb'); // Move plain file to a temporary location - $size = stream_copy_to_stream( $plainHandle, $encHandle ); + $size = stream_copy_to_stream($plainHandle, $encHandle); fclose($encHandle); $fakeRoot = $this->view->getRoot(); - $this->view->chroot('/'.$this->userId.'/files'); + $this->view->chroot('/' . $this->userId . '/files'); $this->view->rename($relPath . '.tmp', $relPath); $this->view->chroot($fakeRoot); // Add the file to the cache - \OC\Files\Filesystem::putFileInfo( $relPath, array( 'encrypted' => true, 'size' => $size, 'unencrypted_size' => $size ) ); + \OC\Files\Filesystem::putFileInfo($relPath, array( + 'encrypted' => true, + 'size' => $size, + 'unencrypted_size' => $size + )); } // Encrypt legacy encrypted files @@ -822,7 +828,7 @@ class Util { if (\OCP\DB::isError($result)) { \OCP\Util::writeLog('Encryption library', \OC_DB::getErrorMessage($result), \OCP\Util::ERROR); } else { - if($result->numRows() > 0) { + if ($result->numRows() > 0) { $row = $result->fetchRow(); $path = substr($row['path'], strlen('files')); } @@ -1113,7 +1119,7 @@ class Util { if (\OCP\DB::isError($result)) { \OCP\Util::writeLog('Encryption library', \OC_DB::getErrorMessage($result), \OCP\Util::ERROR); } else { - if($result->numRows() > 0) { + if ($result->numRows() > 0) { $row = $result->fetchRow(); if (isset($row['migration_status'])) { $migrationStatus[] = $row['migration_status']; @@ -1199,7 +1205,8 @@ class Util { $result = array(); - $content = $this->view->getDirectoryContent(\OC\Files\Filesystem::normalizePath($this->userFilesDir . '/' . $dir)); + $content = $this->view->getDirectoryContent(\OC\Files\Filesystem::normalizePath( + $this->userFilesDir . '/' . $dir)); // handling for re shared folders $pathSplit = explode('/', $dir); @@ -1260,7 +1267,7 @@ class Util { if (\OCP\DB::isError($result)) { \OCP\Util::writeLog('Encryption library', \OC_DB::getErrorMessage($result), \OCP\Util::ERROR); } else { - if($result->numRows() > 0) { + if ($result->numRows() > 0) { $row = $result->fetchRow(); } } @@ -1286,7 +1293,7 @@ class Util { if (\OCP\DB::isError($result)) { \OCP\Util::writeLog('Encryption library', \OC_DB::getErrorMessage($result), \OCP\Util::ERROR); } else { - if($result->numRows() > 0) { + if ($result->numRows() > 0) { $row = $result->fetchRow(); } } @@ -1311,7 +1318,7 @@ class Util { if (\OCP\DB::isError($result)) { \OCP\Util::writeLog('Encryption library', \OC_DB::getErrorMessage($result), \OCP\Util::ERROR); } else { - if($result->numRows() > 0) { + if ($result->numRows() > 0) { $source = $result->fetchRow(); } } @@ -1332,7 +1339,7 @@ class Util { if (\OCP\DB::isError($result)) { \OCP\Util::writeLog('Encryption library', \OC_DB::getErrorMessage($result), \OCP\Util::ERROR); } else { - if($result->numRows() > 0) { + if ($result->numRows() > 0) { $item = $result->fetchRow(); } } @@ -1380,26 +1387,24 @@ class Util { */ public function checkRecoveryPassword($password) { + $result = false; $pathKey = '/owncloud_private_key/' . $this->recoveryKeyId . ".private.key"; - $pathControlData = '/control-file/controlfile.enc'; $proxyStatus = \OC_FileProxy::$enabled; \OC_FileProxy::$enabled = false; $recoveryKey = $this->view->file_get_contents($pathKey); - $decryptedRecoveryKey = Crypt::symmetricDecryptFileContent($recoveryKey, $password); + $decryptedRecoveryKey = Crypt::decryptPrivateKey($recoveryKey, $password); - $controlData = $this->view->file_get_contents($pathControlData); - $decryptedControlData = Crypt::keyDecrypt($controlData, $decryptedRecoveryKey); + if ($decryptedRecoveryKey) { + $result = true; + } \OC_FileProxy::$enabled = $proxyStatus; - if ($decryptedControlData === 'ownCloud') { - return true; - } - return false; + return $result; } /** @@ -1528,7 +1533,7 @@ class Util { $encryptedKey = $this->view->file_get_contents( '/owncloud_private_key/' . $this->recoveryKeyId . '.private.key'); - $privateKey = Crypt::symmetricDecryptFileContent($encryptedKey, $recoveryPassword); + $privateKey = Crypt::decryptPrivateKey($encryptedKey, $recoveryPassword); \OC_FileProxy::$enabled = $proxyStatus; @@ -1544,7 +1549,7 @@ class Util { list($storage, $internalPath) = \OC\Files\Cache\Cache::getById($id); $mount = \OC\Files\Filesystem::getMountByStorageId($storage); $mountPoint = $mount[0]->getMountPoint(); - $path = \OC\Files\Filesystem::normalizePath($mountPoint.'/'.$internalPath); + $path = \OC\Files\Filesystem::normalizePath($mountPoint . '/' . $internalPath); // reformat the path to be relative e.g. /user/files/folder becomes /folder/ $relativePath = \OCA\Encryption\Helper::stripUserFilesPath($path); diff --git a/apps/files_encryption/settings-personal.php b/apps/files_encryption/settings-personal.php index 3e96565949..fddc3ea5ee 100644 --- a/apps/files_encryption/settings-personal.php +++ b/apps/files_encryption/settings-personal.php @@ -14,15 +14,26 @@ $tmpl = new OCP\Template('files_encryption', 'settings-personal'); $user = \OCP\USER::getUser(); $view = new \OC_FilesystemView('/'); $util = new \OCA\Encryption\Util($view, $user); +$session = new \OCA\Encryption\Session($view); + +$privateKeySet = ($session->getPrivateKey() !== false) ? true : false; $recoveryAdminEnabled = OC_Appconfig::getValue('files_encryption', 'recoveryAdminEnabled'); $recoveryEnabledForUser = $util->recoveryEnabledForUser(); -\OCP\Util::addscript('files_encryption', 'settings-personal'); -\OCP\Util::addScript('settings', 'personal'); +$result = false; -$tmpl->assign('recoveryEnabled', $recoveryAdminEnabled); -$tmpl->assign('recoveryEnabledForUser', $recoveryEnabledForUser); +if ($recoveryAdminEnabled || !$privateKeySet) { -return $tmpl->fetchPage(); + \OCP\Util::addscript('files_encryption', 'settings-personal'); + \OCP\Util::addScript('settings', 'personal'); + + $tmpl->assign('recoveryEnabled', $recoveryAdminEnabled); + $tmpl->assign('recoveryEnabledForUser', $recoveryEnabledForUser); + $tmpl->assign('privateKeySet', $privateKeySet); + + $result = $tmpl->fetchPage(); +} + +return $result; diff --git a/apps/files_encryption/templates/invalid_private_key.php b/apps/files_encryption/templates/invalid_private_key.php new file mode 100644 index 0000000000..5c086d6514 --- /dev/null +++ b/apps/files_encryption/templates/invalid_private_key.php @@ -0,0 +1,10 @@ + diff --git a/apps/files_encryption/templates/settings-admin.php b/apps/files_encryption/templates/settings-admin.php index 18fea1845f..c420b006c4 100644 --- a/apps/files_encryption/templates/settings-admin.php +++ b/apps/files_encryption/templates/settings-admin.php @@ -1,54 +1,56 @@
- +

- t( 'Encryption' )); ?> -
+ t('Encryption')); ?> +

+

- t( "Enable encryption passwords recovery key (allow sharing to recovery key):" )); ?> -
-
- - -
- /> - t( "Enabled" )); ?> -
- - /> - t( "Disabled" )); ?> + t("Enable encryption passwords recovery key (allow sharing to recovery key):")); ?> +
+
+ + +
+ /> + t("Enabled")); ?> +
+ + /> + t("Disabled")); ?>

-

+

+

- t( "Change encryption passwords recovery key:" )); ?> -

- t("Change encryption passwords recovery key:")); ?> +

+ /> - -
- /> + +
+ /> - -
+ /> + +

diff --git a/apps/files_encryption/templates/settings-personal.php b/apps/files_encryption/templates/settings-personal.php index 04d6e79179..3851245320 100644 --- a/apps/files_encryption/templates/settings-personal.php +++ b/apps/files_encryption/templates/settings-personal.php @@ -3,12 +3,48 @@ t( 'Encryption' ) ); ?> - - + +

- + +
- t( "Enabling this option will allow you to reobtain access to your encrypted files if your password is lost" ) ); ?> + t( "Set your old private key password to your current log-in password." ) ); ?> + t( " If you don't remember your old password you can ask your administrator to recover your files." ) ); + endif; ?> + +
+ + +
+ + +
+ + +

+ + +
+ + +

+ +
+ t( "Enabling this option will allow you to reobtain access to your encrypted files in case of password loss" ) ); ?>
t( 'Could not update file recovery' ) ); ?>

+
diff --git a/apps/files_encryption/tests/crypt.php b/apps/files_encryption/tests/crypt.php index c105e5ad31..9b97df22d1 100755 --- a/apps/files_encryption/tests/crypt.php +++ b/apps/files_encryption/tests/crypt.php @@ -92,8 +92,7 @@ class Test_Encryption_Crypt extends \PHPUnit_Framework_TestCase { // reset app files_trashbin if ($this->stateFilesTrashbin) { OC_App::enable('files_trashbin'); - } - else { + } else { OC_App::disable('files_trashbin'); } } @@ -240,6 +239,23 @@ class Test_Encryption_Crypt extends \PHPUnit_Framework_TestCase { } + function testDecryptPrivateKey() { + + // test successful decrypt + $crypted = Encryption\Crypt::symmetricEncryptFileContent($this->genPrivateKey, 'hat'); + + $decrypted = Encryption\Crypt::decryptPrivateKey($crypted, 'hat'); + + $this->assertEquals($this->genPrivateKey, $decrypted); + + //test private key decrypt with wrong password + $wrongPasswd = Encryption\Crypt::decryptPrivateKey($crypted, 'hat2'); + + $this->assertEquals(false, $wrongPasswd); + + } + + /** * @medium */ diff --git a/apps/files_encryption/tests/share.php b/apps/files_encryption/tests/share.php index 15dd5058eb..6b53031585 100755 --- a/apps/files_encryption/tests/share.php +++ b/apps/files_encryption/tests/share.php @@ -111,8 +111,7 @@ class Test_Encryption_Share extends \PHPUnit_Framework_TestCase { // reset app files_trashbin if ($this->stateFilesTrashbin) { OC_App::enable('files_trashbin'); - } - else { + } else { OC_App::disable('files_trashbin'); } } @@ -656,9 +655,6 @@ class Test_Encryption_Share extends \PHPUnit_Framework_TestCase { \OCA\Encryption\Helper::adminEnableRecovery(null, 'test123'); $recoveryKeyId = OC_Appconfig::getValue('files_encryption', 'recoveryKeyId'); - // check if control file created - $this->assertTrue($this->view->file_exists('/control-file/controlfile.enc')); - // login as admin \Test_Encryption_Util::loginHelper(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER1); @@ -761,9 +757,6 @@ class Test_Encryption_Share extends \PHPUnit_Framework_TestCase { \OCA\Encryption\Helper::adminEnableRecovery(null, 'test123'); $recoveryKeyId = OC_Appconfig::getValue('files_encryption', 'recoveryKeyId'); - // check if control file created - $this->assertTrue($this->view->file_exists('/control-file/controlfile.enc')); - // login as user1 \Test_Encryption_Util::loginHelper(\Test_Encryption_Share::TEST_ENCRYPTION_SHARE_USER2); diff --git a/core/lostpassword/controller.php b/core/lostpassword/controller.php index fbcf4a87f2..9377de5d39 100644 --- a/core/lostpassword/controller.php +++ b/core/lostpassword/controller.php @@ -8,8 +8,11 @@ class OC_Core_LostPassword_Controller { protected static function displayLostPasswordPage($error, $requested) { + $encrypted = OC_App::isEnabled('files_encryption'); OC_Template::printGuestPage('core/lostpassword', 'lostpassword', - array('error' => $error, 'requested' => $requested)); + array('error' => $error, + 'requested' => $requested, + 'encrypted' => $encrypted)); } protected static function displayResetPasswordPage($success, $args) { @@ -29,7 +32,14 @@ class OC_Core_LostPassword_Controller { } public static function sendEmail($args) { - if (OC_User::userExists($_POST['user'])) { + + if(isset($_POST['noEncryption']) || isset($_POST['continue'])) { + $continue = true; + } else { + $continue = false; + } + + if (OC_User::userExists($_POST['user']) && $continue) { $token = hash('sha256', OC_Util::generate_random_bytes(30).OC_Config::getValue('passwordsalt', '')); OC_Preferences::setValue($_POST['user'], 'owncloud', 'lostpassword', hash('sha256', $token)); // Hash the token again to prevent timing attacks diff --git a/core/lostpassword/templates/lostpassword.php b/core/lostpassword/templates/lostpassword.php index c19c6893f1..c082ee977d 100644 --- a/core/lostpassword/templates/lostpassword.php +++ b/core/lostpassword/templates/lostpassword.php @@ -17,6 +17,14 @@ + +

+ t('Your files seems to be encrypted. If you didn\'t have enabled the recovery key there will be no way to get your data back once the password was resetted. If you are not sure what to do, please contact your administrator first before continue. Do you really want to continue?')); ?>
+ + t('Yes, I really want to reset my password now')); ?>

+ + +

diff --git a/settings/ajax/changepassword.php b/settings/ajax/changepassword.php index 3087781055..d409904ebc 100644 --- a/settings/ajax/changepassword.php +++ b/settings/ajax/changepassword.php @@ -7,44 +7,44 @@ OC_JSON::checkLoggedIn(); // Manually load apps to ensure hooks work correctly (workaround for issue 1503) OC_APP::loadApps(); -$username = isset($_POST["username"]) ? $_POST["username"] : OC_User::getUser(); -$password = isset($_POST["password"]) ? $_POST["password"] : null; -$oldPassword=isset($_POST["oldpassword"])?$_POST["oldpassword"]:''; -$recoveryPassword=isset($_POST["recoveryPassword"])?$_POST["recoveryPassword"]:null; +$username = isset($_POST['username']) ? $_POST['username'] : OC_User::getUser(); +$password = isset($_POST['password']) ? $_POST['password'] : null; +$oldPassword = isset($_POST['oldpassword']) ? $_POST['oldpassword'] : ''; +$recoveryPassword = isset($_POST['recoveryPassword']) ? $_POST['recoveryPassword'] : null; $userstatus = null; -if(OC_User::isAdminUser(OC_User::getUser())) { +if (OC_User::isAdminUser(OC_User::getUser())) { $userstatus = 'admin'; } -if(OC_SubAdmin::isUserAccessible(OC_User::getUser(), $username)) { +if (OC_SubAdmin::isUserAccessible(OC_User::getUser(), $username)) { $userstatus = 'subadmin'; } -if(OC_User::getUser() === $username && OC_User::checkPassword($username, $oldPassword)) { +if (OC_User::getUser() === $username && OC_User::checkPassword($username, $oldPassword)) { $userstatus = 'user'; } -if(is_null($userstatus)) { - OC_JSON::error( array( "data" => array( "message" => "Authentication error" ))); +if (is_null($userstatus)) { + OC_JSON::error(array('data' => array('message' => 'Authentication error'))); exit(); } -$recoveryAdminEnabled = OC_Appconfig::getValue( 'files_encryption', 'recoveryAdminEnabled' ); - - -$validRecoveryPassword = false; -$recoveryPasswordSupported = false; - -if ($recoveryAdminEnabled) { +if (\OCP\App::isEnabled('files_encryption') && $userstatus !== 'user') { + //handle the recovery case $util = new \OCA\Encryption\Util(new \OC_FilesystemView('/'), $username); - $validRecoveryPassword = $util->checkRecoveryPassword($recoveryPassword); - $recoveryPasswordSupported = $util->recoveryEnabledForUser(); -} + $recoveryAdminEnabled = OC_Appconfig::getValue('files_encryption', 'recoveryAdminEnabled'); -if ($recoveryPasswordSupported && $recoveryPassword == '') { - OC_JSON::error(array("data" => array( "message" => "Please provide a admin recovery password, otherwise all user data will be lost" ))); -} elseif ( $recoveryPasswordSupported && ! $validRecoveryPassword) { - OC_JSON::error(array("data" => array( "message" => "Wrong admin recovery password. Please check the password and try again." ))); -} else { // now we know that everything is file regarding the recovery password, let's try to change the password + $validRecoveryPassword = false; + $recoveryPasswordSupported = false; + if ($recoveryAdminEnabled) { + $validRecoveryPassword = $util->checkRecoveryPassword($recoveryPassword); + $recoveryEnabledForUser = $util->recoveryEnabledForUser(); + } + + if ($recoveryEnabledForUser && $recoveryPassword === '') { + OC_JSON::error(array('data' => array('message' => 'Please provide a admin recovery password, otherwise all user data will be lost'))); + } elseif ($recoveryEnabledForUser && ! $validRecoveryPassword) { + OC_JSON::error(array('data' => array('message' => 'Wrong admin recovery password. Please check the password and try again.'))); + } else { // now we know that everything is fine regarding the recovery password, let's try to change the password $result = OC_User::setPassword($username, $password, $recoveryPassword); if (!$result && $recoveryPasswordSupported) { OC_JSON::error(array("data" => array( "message" => "Back-end doesn't support password change, but the users encryption key was successfully updated." ))); @@ -53,4 +53,12 @@ if ($recoveryPasswordSupported && $recoveryPassword == '') { } else { OC_JSON::success(array("data" => array( "username" => $username ))); } + + } +} else { // if user changes his own password or if encryption is disabled, proceed + if (!is_null($password) && OC_User::setPassword($username, $password)) { + OC_JSON::success(array('data' => array('username' => $username))); + } else { + OC_JSON::error(array('data' => array('message' => 'Unable to change password'))); + } }