Merge pull request #8676 from owncloud/encryption_improvements

cleanup encryption code, improved return codes
This commit is contained in:
Morris Jobke 2014-06-02 18:28:18 +02:00
commit 390d8e53b4
4 changed files with 120 additions and 173 deletions

View file

@ -51,16 +51,16 @@ class Hooks {
$view = new \OC\Files\View('/'); $view = new \OC\Files\View('/');
// ensure filesystem is loaded // ensure filesystem is loaded
if(!\OC\Files\Filesystem::$loaded) { if (!\OC\Files\Filesystem::$loaded) {
\OC_Util::setupFS($params['uid']); \OC_Util::setupFS($params['uid']);
} }
$privateKey = \OCA\Encryption\Keymanager::getPrivateKey($view, $params['uid']); $privateKey = \OCA\Encryption\Keymanager::getPrivateKey($view, $params['uid']);
// if no private key exists, check server configuration // if no private key exists, check server configuration
if(!$privateKey) { if (!$privateKey) {
//check if all requirements are met //check if all requirements are met
if(!Helper::checkRequirements() || !Helper::checkConfiguration()) { if (!Helper::checkRequirements() || !Helper::checkConfiguration()) {
$error_msg = $l->t("Missing requirements."); $error_msg = $l->t("Missing requirements.");
$hint = $l->t('Please make sure that PHP 5.3.3 or newer is installed and that OpenSSL together with the PHP extension is enabled and configured properly. For now, the encryption app has been disabled.'); $hint = $l->t('Please make sure that PHP 5.3.3 or newer is installed and that OpenSSL together with the PHP extension is enabled and configured properly. For now, the encryption app has been disabled.');
\OC_App::disable('files_encryption'); \OC_App::disable('files_encryption');
@ -90,6 +90,8 @@ class Hooks {
return false; return false;
} }
$result = true;
// If migration not yet done // If migration not yet done
if ($ready) { if ($ready) {
@ -97,15 +99,12 @@ class Hooks {
// Set legacy encryption key if it exists, to support // Set legacy encryption key if it exists, to support
// depreciated encryption system // depreciated encryption system
if ( $encLegacyKey = $userView->file_get_contents('encryption.key');
$userView->file_exists('encryption.key') if ($encLegacyKey) {
&& $encLegacyKey = $userView->file_get_contents('encryption.key')
) {
$plainLegacyKey = Crypt::legacyDecrypt($encLegacyKey, $params['password']); $plainLegacyKey = Crypt::legacyDecrypt($encLegacyKey, $params['password']);
$session->setLegacyKey($plainLegacyKey); $session->setLegacyKey($plainLegacyKey);
} }
// Encrypt existing user files // Encrypt existing user files
@ -113,26 +112,24 @@ class Hooks {
$result = $util->encryptAll('/' . $params['uid'] . '/' . 'files', $session->getLegacyKey(), $params['password']); $result = $util->encryptAll('/' . $params['uid'] . '/' . 'files', $session->getLegacyKey(), $params['password']);
} catch (\Exception $ex) { } catch (\Exception $ex) {
\OCP\Util::writeLog('Encryption library', 'Initial encryption failed! Error: ' . $ex->getMessage(), \OCP\Util::FATAL); \OCP\Util::writeLog('Encryption library', 'Initial encryption failed! Error: ' . $ex->getMessage(), \OCP\Util::FATAL);
$util->resetMigrationStatus();
\OCP\User::logout();
$result = false; $result = false;
} }
if ($result) { if ($result) {
\OC_Log::write( \OC_Log::write(
'Encryption library', 'Encryption of existing files belonging to "' . $params['uid'] . '" completed' 'Encryption library', 'Encryption of existing files belonging to "' . $params['uid'] . '" completed'
, \OC_Log::INFO , \OC_Log::INFO
); );
// Register successful migration in DB // Register successful migration in DB
$util->finishMigration(); $util->finishMigration();
} else {
\OCP\Util::writeLog('Encryption library', 'Initial encryption failed!', \OCP\Util::FATAL);
$util->resetMigrationStatus();
\OCP\User::logout();
} }
} }
return true; return $result;
} }
/** /**

View file

@ -303,7 +303,7 @@ class Util {
* Find all files and their encryption status within a directory * Find all files and their encryption status within a directory
* @param string $directory The path of the parent directory to search * @param string $directory The path of the parent directory to search
* @param bool $found the founded files if called again * @param bool $found the founded files if called again
* @return mixed false if 0 found, array on success. Keys: name, path * @return array keys: plain, encrypted, legacy, broken
* @note $directory needs to be a path relative to OC data dir. e.g. * @note $directory needs to be a path relative to OC data dir. e.g.
* /admin/files NOT /backup OR /home/www/oc/data/admin/files * /admin/files NOT /backup OR /home/www/oc/data/admin/files
*/ */
@ -322,11 +322,8 @@ class Util {
); );
} }
if ( if ($this->view->is_dir($directory) && $handle = $this->view->opendir($directory)){
$this->view->is_dir($directory) if (is_resource($handle)) {
&& $handle = $this->view->opendir($directory)
) {
if(is_resource($handle)) {
while (false !== ($file = readdir($handle))) { while (false !== ($file = readdir($handle))) {
if ($file !== "." && $file !== "..") { if ($file !== "." && $file !== "..") {
@ -390,34 +387,16 @@ class Util {
'name' => $file, 'name' => $file,
'path' => $relPath 'path' => $relPath
); );
} }
} }
} }
} }
} }
\OC_FileProxy::$enabled = true;
if (empty($found)) {
return false;
} else {
return $found;
}
} }
\OC_FileProxy::$enabled = true; \OC_FileProxy::$enabled = true;
return false; return $found;
} }
/** /**
@ -571,28 +550,6 @@ class Util {
return $result; return $result;
} }
/**
* @param string $path
* @return bool
*/
public function isSharedPath($path) {
$trimmed = ltrim($path, '/');
$split = explode('/', $trimmed);
if (isset($split[2]) && $split[2] === 'Shared') {
return true;
} else {
return false;
}
}
/** /**
* encrypt versions from given file * encrypt versions from given file
* @param array $filelist list of encrypted files, relative to data/user/files * @param array $filelist list of encrypted files, relative to data/user/files
@ -808,121 +765,119 @@ class Util {
*/ */
public function encryptAll($dirPath, $legacyPassphrase = null, $newPassphrase = null) { public function encryptAll($dirPath, $legacyPassphrase = null, $newPassphrase = null) {
$result = true;
$found = $this->findEncFiles($dirPath); $found = $this->findEncFiles($dirPath);
if ($found) { // Disable proxy to prevent file being encrypted twice
\OC_FileProxy::$enabled = false;
// Disable proxy to prevent file being encrypted twice $versionStatus = \OCP\App::isEnabled('files_versions');
\OC_FileProxy::$enabled = false; \OC_App::disable('files_versions');
$versionStatus = \OCP\App::isEnabled('files_versions'); $encryptedFiles = array();
\OC_App::disable('files_versions');
$encryptedFiles = array(); // Encrypt unencrypted files
foreach ($found['plain'] as $plainFile) {
// Encrypt unencrypted files //get file info
foreach ($found['plain'] as $plainFile) { $fileInfo = \OC\Files\Filesystem::getFileInfo($plainFile['path']);
//get file info //relative to data/<user>/file
$fileInfo = \OC\Files\Filesystem::getFileInfo($plainFile['path']); $relPath = $plainFile['path'];
//relative to data/<user>/file //relative to /data
$relPath = $plainFile['path']; $rawPath = '/' . $this->userId . '/files/' . $plainFile['path'];
//relative to /data // keep timestamp
$rawPath = '/' . $this->userId . '/files/' . $plainFile['path']; $timestamp = $fileInfo['mtime'];
// keep timestamp // Open plain file handle for binary reading
$timestamp = $fileInfo['mtime']; $plainHandle = $this->view->fopen($rawPath, 'rb');
// Open plain file handle for binary reading // Open enc file handle for binary writing, with same filename as original plain file
$plainHandle = $this->view->fopen($rawPath, 'rb'); $encHandle = fopen('crypt://' . $rawPath . '.part', 'wb');
if (is_resource($encHandle) && is_resource($plainHandle)) {
// Move plain file to a temporary location
$size = stream_copy_to_stream($plainHandle, $encHandle);
fclose($encHandle);
fclose($plainHandle);
$fakeRoot = $this->view->getRoot();
$this->view->chroot('/' . $this->userId . '/files');
$this->view->rename($relPath . '.part', $relPath);
// set timestamp
$this->view->touch($relPath, $timestamp);
$encSize = $this->view->filesize($relPath);
$this->view->chroot($fakeRoot);
// Add the file to the cache
\OC\Files\Filesystem::putFileInfo($relPath, array(
'encrypted' => true,
'size' => $encSize,
'unencrypted_size' => $size,
'etag' => $fileInfo['etag']
));
$encryptedFiles[] = $relPath;
} else {
\OCP\Util::writeLog('files_encryption', 'initial encryption: could not encrypt ' . $rawPath, \OCP\Util::FATAL);
$result = false;
}
}
// Encrypt legacy encrypted files
if (!empty($legacyPassphrase) && !empty($newPassphrase)) {
foreach ($found['legacy'] as $legacyFile) {
// Fetch data from file
$legacyData = $this->view->file_get_contents($legacyFile['path']);
// decrypt data, generate catfile
$decrypted = Crypt::legacyBlockDecrypt($legacyData, $legacyPassphrase);
$rawPath = $legacyFile['path'];
// enable proxy the ensure encryption is handled
\OC_FileProxy::$enabled = true;
// Open enc file handle for binary writing, with same filename as original plain file // Open enc file handle for binary writing, with same filename as original plain file
$encHandle = fopen('crypt://' . $rawPath . '.part', 'wb'); $encHandle = $this->view->fopen($rawPath, 'wb');
if (is_resource($encHandle)) { if (is_resource($encHandle)) {
// Move plain file to a temporary location
$size = stream_copy_to_stream($plainHandle, $encHandle);
// write data to stream
fwrite($encHandle, $decrypted);
// close stream
fclose($encHandle); fclose($encHandle);
fclose($plainHandle); } else {
\OCP\Util::writeLog('files_encryption', 'initial encryption: could not encrypt legacy file ' . $rawPath, \OCP\Util::FATAL);
$fakeRoot = $this->view->getRoot(); $result = false;
$this->view->chroot('/' . $this->userId . '/files');
$this->view->rename($relPath . '.part', $relPath);
// set timestamp
$this->view->touch($relPath, $timestamp);
$encSize = $this->view->filesize($relPath);
$this->view->chroot($fakeRoot);
// Add the file to the cache
\OC\Files\Filesystem::putFileInfo($relPath, array(
'encrypted' => true,
'size' => $encSize,
'unencrypted_size' => $size,
'etag' => $fileInfo['etag']
));
$encryptedFiles[] = $relPath;
} }
// disable proxy to prevent file being encrypted twice
\OC_FileProxy::$enabled = false;
} }
// Encrypt legacy encrypted files
if (
!empty($legacyPassphrase)
&& !empty($newPassphrase)
) {
foreach ($found['legacy'] as $legacyFile) {
// Fetch data from file
$legacyData = $this->view->file_get_contents($legacyFile['path']);
// decrypt data, generate catfile
$decrypted = Crypt::legacyBlockDecrypt($legacyData, $legacyPassphrase);
$rawPath = $legacyFile['path'];
// enable proxy the ensure encryption is handled
\OC_FileProxy::$enabled = true;
// Open enc file handle for binary writing, with same filename as original plain file
$encHandle = $this->view->fopen( $rawPath, 'wb' );
if (is_resource($encHandle)) {
// write data to stream
fwrite($encHandle, $decrypted);
// close stream
fclose($encHandle);
}
// disable proxy to prevent file being encrypted twice
\OC_FileProxy::$enabled = false;
}
}
\OC_FileProxy::$enabled = true;
if ($versionStatus) {
\OC_App::enable('files_versions');
}
$this->encryptVersions($encryptedFiles);
// If files were found, return true
return true;
} else {
// If no files were found, return false
return false;
} }
\OC_FileProxy::$enabled = true;
if ($versionStatus) {
\OC_App::enable('files_versions');
}
$result = $result && $this->encryptVersions($encryptedFiles);
return $result;
} }
/** /**

View file

@ -18,15 +18,20 @@ use OCA\Encryption;
class Test_Encryption_Helper extends \PHPUnit_Framework_TestCase { class Test_Encryption_Helper extends \PHPUnit_Framework_TestCase {
const TEST_ENCRYPTION_HELPER_USER1 = "test-helper-user1"; const TEST_ENCRYPTION_HELPER_USER1 = "test-helper-user1";
const TEST_ENCRYPTION_HELPER_USER2 = "test-helper-user2";
public static function setUpBeforeClass() { public static function setUpBeforeClass() {
// create test user // create test user
\Test_Encryption_Util::loginHelper(\Test_Encryption_Helper::TEST_ENCRYPTION_HELPER_USER2, true);
\Test_Encryption_Util::loginHelper(\Test_Encryption_Helper::TEST_ENCRYPTION_HELPER_USER1, true); \Test_Encryption_Util::loginHelper(\Test_Encryption_Helper::TEST_ENCRYPTION_HELPER_USER1, true);
} }
public static function tearDownAfterClass() { public static function tearDownAfterClass() {
// cleanup test user // cleanup test user
\OC_User::deleteUser(\Test_Encryption_Helper::TEST_ENCRYPTION_HELPER_USER1); \OC_User::deleteUser(\Test_Encryption_Helper::TEST_ENCRYPTION_HELPER_USER1);
\OC_User::deleteUser(\Test_Encryption_Helper::TEST_ENCRYPTION_HELPER_USER2);
\OC_Hook::clear();
\OC_FileProxy::clearProxies();
} }
/** /**
@ -81,9 +86,11 @@ class Test_Encryption_Helper extends \PHPUnit_Framework_TestCase {
$path1 = "/" . self::TEST_ENCRYPTION_HELPER_USER1 . "/files/foo/bar.txt"; $path1 = "/" . self::TEST_ENCRYPTION_HELPER_USER1 . "/files/foo/bar.txt";
$path2 = "/" . self::TEST_ENCRYPTION_HELPER_USER1 . "/cache/foo/bar.txt"; $path2 = "/" . self::TEST_ENCRYPTION_HELPER_USER1 . "/cache/foo/bar.txt";
$path3 = "/" . self::TEST_ENCRYPTION_HELPER_USER1 . "/thumbnails/foo"; $path3 = "/" . self::TEST_ENCRYPTION_HELPER_USER2 . "/thumbnails/foo";
$path4 ="/" . "/" . self::TEST_ENCRYPTION_HELPER_USER1; $path4 ="/" . "/" . self::TEST_ENCRYPTION_HELPER_USER1;
\Test_Encryption_Util::loginHelper(self::TEST_ENCRYPTION_HELPER_USER1);
// if we are logged-in every path should return the currently logged-in user // if we are logged-in every path should return the currently logged-in user
$this->assertEquals(self::TEST_ENCRYPTION_HELPER_USER1, Encryption\Helper::getUser($path3)); $this->assertEquals(self::TEST_ENCRYPTION_HELPER_USER1, Encryption\Helper::getUser($path3));

View file

@ -306,18 +306,6 @@ class Test_Encryption_Util extends \PHPUnit_Framework_TestCase {
$this->view->unlink($this->userId . '/files/' . $filename); $this->view->unlink($this->userId . '/files/' . $filename);
} }
/**
* @medium
*/
function testIsSharedPath() {
$sharedPath = '/user1/files/Shared/test';
$path = '/user1/files/test';
$this->assertTrue($this->util->isSharedPath($sharedPath));
$this->assertFalse($this->util->isSharedPath($path));
}
function testEncryptAll() { function testEncryptAll() {
$filename = "/encryptAll" . uniqid() . ".txt"; $filename = "/encryptAll" . uniqid() . ".txt";