diff --git a/apps/files_encryption/ajax/changeRecoveryPassword.php b/apps/files_encryption/ajax/changeRecoveryPassword.php index b0594f967ba30c88ff067ef122ba8f4b445eca50..366f634a51cc0504887abd1c599e320b404b4a5d 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 0000000000000000000000000000000000000000..6fd63dae9cdc2f7c5573a031ceedc4c9e091cebb --- /dev/null +++ b/apps/files_encryption/ajax/updatePrivateKeyPassword.php @@ -0,0 +1,54 @@ +<?php + +/** + * Copyright (c) 2013, Bjoern Schiessle <schiessle@owncloud.com> + * 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 f69e04b5bbf779403640c8ae7acaeab85f8d429f..419bef1edef09183b09e92a68cbc366bbfcfd8df 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 0000000000000000000000000000000000000000..63c74e4e797de99a5569b735b27ec3becec65f98 --- /dev/null +++ b/apps/files_encryption/files/error.php @@ -0,0 +1,24 @@ +<?php +if (!isset($_)) { //also provide standalone error page + require_once '../../../lib/base.php'; + + $l = OC_L10N::get('files_encryption'); + + $errorMsg = $l->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 0580b713d1a1ef0380b16c127d6519f9671c009f..7698b95cfd36b9c897722df0d37c6fb7d1b544b4 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 312b672ad464be90d5b3ef5cd466c5677141baa2..d6535a25b704b85efb0f6eed02536dd3396c3f9c 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 ced9ab7c67644b88cbb527d791ce0da3b22fb917..cd41390d1c56f21cefc9eda88c13ca91ed3dafb1 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 184e1782494912753cef197f2cbf998f6f401036..a22c139c503cb694f859e5efec633f3f9d8423c5 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 bff1737554b4bea7a231135ec6affc4d341a7fef..1911386cd12f06832d2406071a4092942191c7d7 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 072c52866445e91f7157a739ca2e939ccdc846b7..3c1eb2c5f5e5dcb4273c40040107d07a590fb18b 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) { + + // if private key is not valid redirect user to a error page + \OCA\Encryption\Helper::redirectToErrorPage(); - $privateKey = $session->getPrivateKey($this->userId); + 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 463e4fae794bf9e78a51ba24b11a8ff34e3caca0..94defa726a9da02bb216548a96d4175c6ecf3547 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 3e96565949b95f3cd09ef29ae1703fa3b8bab76b..fddc3ea5eee836494eeec89e41114a2a6de86278 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; + +if ($recoveryAdminEnabled || !$privateKeySet) { + + \OCP\Util::addscript('files_encryption', 'settings-personal'); + \OCP\Util::addScript('settings', 'personal'); + + $tmpl->assign('recoveryEnabled', $recoveryAdminEnabled); + $tmpl->assign('recoveryEnabledForUser', $recoveryEnabledForUser); + $tmpl->assign('privateKeySet', $privateKeySet); -$tmpl->assign('recoveryEnabled', $recoveryAdminEnabled); -$tmpl->assign('recoveryEnabledForUser', $recoveryEnabledForUser); + $result = $tmpl->fetchPage(); +} -return $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 0000000000000000000000000000000000000000..5c086d6514c1297fb80d5aaea13b9cf90ff3a5ba --- /dev/null +++ b/apps/files_encryption/templates/invalid_private_key.php @@ -0,0 +1,10 @@ +<ul> + <li class='error'> + <?php $location = \OC_Helper::linkToRoute( "settings_personal" ).'#changePKPasswd' ?> + + <?php p($l->t('Your private key is not valid! Maybe the your password was changed from outside.')); ?> + <br/> + <?php p($l->t('You can unlock your private key in your ')); ?> <a href="<?php echo $location?>"><?php p($l->t('personal settings')); ?>.</a> + <br/> + </li> +</ul> diff --git a/apps/files_encryption/templates/settings-admin.php b/apps/files_encryption/templates/settings-admin.php index 18fea1845f42189f9fce958377cd5984c5e1cccb..c420b006c4572132b9a58ac87dd7f4129e8a6c56 100644 --- a/apps/files_encryption/templates/settings-admin.php +++ b/apps/files_encryption/templates/settings-admin.php @@ -1,54 +1,56 @@ <form id="encryption"> <fieldset class="personalblock"> - + <p> - <strong><?php p($l->t( 'Encryption' )); ?></strong> - <br /> + <strong><?php p($l->t('Encryption')); ?></strong> + <br/> </p> + <p> - <?php p($l->t( "Enable encryption passwords recovery key (allow sharing to recovery key):" )); ?> - <br /> - <br /> - <input type="password" name="recoveryPassword" id="recoveryPassword" /> - <label for="recoveryPassword"><?php p($l->t( "Recovery account password" )); ?></label> - <br /> - <input - type='radio' - name='adminEnableRecovery' - value='1' - <?php echo ( $_["recoveryEnabled"] == 1 ? 'checked="checked"' : 'disabled' ); ?> /> - <?php p($l->t( "Enabled" )); ?> - <br /> - - <input - type='radio' - name='adminEnableRecovery' - value='0' - <?php echo ( $_["recoveryEnabled"] == 0 ? 'checked="checked"' : 'disabled' ); ?> /> - <?php p($l->t( "Disabled" )); ?> + <?php p($l->t("Enable encryption passwords recovery key (allow sharing to recovery key):")); ?> + <br/> + <br/> + <input type="password" name="recoveryPassword" id="recoveryPassword"/> + <label for="recoveryPassword"><?php p($l->t("Recovery account password")); ?></label> + <br/> + <input + type='radio' + name='adminEnableRecovery' + value='1' + <?php echo($_["recoveryEnabled"] == 1 ? 'checked="checked"' : 'disabled'); ?> /> + <?php p($l->t("Enabled")); ?> + <br/> + + <input + type='radio' + name='adminEnableRecovery' + value='0' + <?php echo($_["recoveryEnabled"] == 0 ? 'checked="checked"' : 'disabled'); ?> /> + <?php p($l->t("Disabled")); ?> </p> - <br /><br /> + <br/><br/> + <p> - <strong><?php p($l->t( "Change encryption passwords recovery key:" )); ?></strong> - <br /><br /> - <input + <strong><?php p($l->t("Change encryption passwords recovery key:")); ?></strong> + <br/><br/> + <input type="password" name="changeRecoveryPassword" id="oldRecoveryPassword" - <?php echo ( $_["recoveryEnabled"] == 0 ? 'disabled' : '' ); ?> /> - <label for="oldRecoveryPassword"><?php p($l->t( "Old Recovery account password" )); ?></label> - <br /> - <input + <?php echo($_["recoveryEnabled"] == 0 ? 'disabled' : ''); ?> /> + <label for="oldRecoveryPassword"><?php p($l->t("Old Recovery account password")); ?></label> + <br/> + <input type="password" name="changeRecoveryPassword" id="newRecoveryPassword" - <?php echo ( $_["recoveryEnabled"] == 0 ? 'disabled' : '' ); ?> /> - <label for="newRecoveryPassword"><?php p($l->t( "New Recovery account password" )); ?></label> - <br /> + <?php echo($_["recoveryEnabled"] == 0 ? 'disabled' : ''); ?> /> + <label for="newRecoveryPassword"><?php p($l->t("New Recovery account password")); ?></label> + <br/> <button type="button" name="submitChangeRecoveryKey" - disabled><?php p($l->t( "Change Password" )); ?> + disabled><?php p($l->t("Change Password")); ?> </button> <span class="msg"></span> </p> diff --git a/apps/files_encryption/templates/settings-personal.php b/apps/files_encryption/templates/settings-personal.php index 04d6e79179ea219190ced54d76f0321ca096d1d2..38512453207a0af2ab836589d427f1abc394ef16 100644 --- a/apps/files_encryption/templates/settings-personal.php +++ b/apps/files_encryption/templates/settings-personal.php @@ -3,12 +3,48 @@ <legend> <?php p( $l->t( 'Encryption' ) ); ?> </legend> + + <?php if ( ! $_["privateKeySet"] ): ?> + <p> + <a name="changePKPasswd" /> + <label for="changePrivateKeyPasswd"> + <?php p( $l->t( "Your private key password no longer match your log-in password:" ) ); ?> + </label> + <br /> + <em><?php p( $l->t( "Set your old private key password to your current log-in password." ) ); ?> + <?php if ( $_["recoveryEnabledForUser"] ): + p( $l->t( " If you don't remember your old password you can ask your administrator to recover your files." ) ); + endif; ?> + </em> + <br /> + <input + type="password" + name="changePrivateKeyPassword" + id="oldPrivateKeyPassword" /> + <label for="oldPrivateKeyPassword"><?php p($l->t( "Old log-in password" )); ?></label> + <br /> + <input + type="password" + name="changePrivateKeyPassword" + id="newPrivateKeyPassword" /> + <label for="newRecoveryPassword"><?php p($l->t( "Current log-in password" )); ?></label> + <br /> + <button + type="button" + name="submitChangePrivateKeyPassword" + disabled><?php p($l->t( "Update Private Key Password" )); ?> + </button> + <span class="msg"></span> + </p> + <?php endif; ?> + + <br /> - <?php if ( $_["recoveryEnabled"] ): ?> + <?php if ( $_["recoveryEnabled"] && $_["privateKeySet"] ): ?> <p> - <label for="userEnableRecovery"><?php p( $l->t( "Enable password recovery by sharing all files with your administrator:" ) ); ?></label> + <label for="userEnableRecovery"><?php p( $l->t( "Enable password recovery:" ) ); ?></label> <br /> - <em><?php p( $l->t( "Enabling this option will allow you to reobtain access to your encrypted files if your password is lost" ) ); ?></em> + <em><?php p( $l->t( "Enabling this option will allow you to reobtain access to your encrypted files in case of password loss" ) ); ?></em> <br /> <input type='radio' @@ -28,6 +64,7 @@ <div id="recoveryEnabledError"><?php p( $l->t( 'Could not update file recovery' ) ); ?></div> </p> <?php endif; ?> + <br /> </fieldset> </form> diff --git a/apps/files_encryption/tests/crypt.php b/apps/files_encryption/tests/crypt.php index c105e5ad319ff65ebf77670126f22425cb3ee2d7..9b97df22d1685932a7afba2db881b3c78a745dd8 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 15dd5058ebb9675d0d5f18718411299a23f933e2..6b5303158595974fb815fa1435663535b7e8c1fb 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 fbcf4a87f22c876de23c7c7a92338851eca01036..9377de5d39abc33d9df18ef5b2cb5266047db28b 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 c19c6893f13fe69d61c2b4aa7a7fa808044831bd..c082ee977da03f708e1dba7ac2fc5a643e641193 100644 --- a/core/lostpassword/templates/lostpassword.php +++ b/core/lostpassword/templates/lostpassword.php @@ -17,6 +17,14 @@ <input type="text" name="user" id="user" placeholder="" value="" autocomplete="off" required autofocus /> <label for="user" class="infield"><?php print_unescaped($l->t( 'Username' )); ?></label> <img class="svg" src="<?php print_unescaped(image_path('', 'actions/user.svg')); ?>" alt=""/> + <?php if ($_['encrypted']): ?> + <br /><br /> + <?php print_unescaped($l->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?')); ?><br /> + <input type="checkbox" name="continue" value="Yes" /> + <?php print_unescaped($l->t('Yes, I really want to reset my password now')); ?><br/><br/> + <?php else: ?> + <input type="checkbox" name="noEncryption" value="Yes" checked /> + <?php endif; ?> </p> <input type="submit" id="submit" value="<?php print_unescaped($l->t('Request reset')); ?>" /> </fieldset> diff --git a/settings/ajax/changepassword.php b/settings/ajax/changepassword.php index 308778105503ec286a5e634936e4c10db0ef4d2b..d409904ebc72ee1703b2b1280cf24942aec8d196 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'); + + $validRecoveryPassword = false; + $recoveryPasswordSupported = false; + if ($recoveryAdminEnabled) { + $validRecoveryPassword = $util->checkRecoveryPassword($recoveryPassword); + $recoveryEnabledForUser = $util->recoveryEnabledForUser(); + } -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 + 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'))); + } }