diff options
Diffstat (limited to 'apps/files_encryption/lib')
-rw-r--r-- | apps/files_encryption/lib/capabilities.php | 2 | ||||
-rwxr-xr-x | apps/files_encryption/lib/crypt.php | 117 | ||||
-rwxr-xr-x | apps/files_encryption/lib/helper.php | 27 | ||||
-rwxr-xr-x | apps/files_encryption/lib/keymanager.php | 2 | ||||
-rw-r--r-- | apps/files_encryption/lib/proxy.php | 11 | ||||
-rw-r--r-- | apps/files_encryption/lib/stream.php | 102 | ||||
-rw-r--r-- | apps/files_encryption/lib/util.php | 374 |
7 files changed, 371 insertions, 264 deletions
diff --git a/apps/files_encryption/lib/capabilities.php b/apps/files_encryption/lib/capabilities.php index 72baddcd049..ef94c9e086d 100644 --- a/apps/files_encryption/lib/capabilities.php +++ b/apps/files_encryption/lib/capabilities.php @@ -20,4 +20,4 @@ class Capabilities { )); } -}
\ No newline at end of file +} diff --git a/apps/files_encryption/lib/crypt.php b/apps/files_encryption/lib/crypt.php index c3e88e5944e..3947b7d0c3b 100755 --- a/apps/files_encryption/lib/crypt.php +++ b/apps/files_encryption/lib/crypt.php @@ -25,7 +25,6 @@ namespace OCA\Encryption;
-//require_once '../3rdparty/Crypt_Blowfish/Blowfish.php';
require_once realpath(dirname(__FILE__) . '/../3rdparty/Crypt_Blowfish/Blowfish.php');
/**
@@ -86,7 +85,7 @@ class Crypt { * blocks with encryption alone, hence padding is added to achieve the
* required length.
*/
- public static function addPadding($data) {
+ private static function addPadding($data) {
$padded = $data . 'xx';
@@ -99,7 +98,7 @@ class Crypt { * @param string $padded padded data to remove padding from
* @return string unpadded data on success, false on error
*/
- public static function removePadding($padded) {
+ private static function removePadding($padded) {
if (substr($padded, -2) === 'xx') {
@@ -207,7 +206,7 @@ class Crypt { * @param string $passphrase
* @return string encrypted file content
*/
- public static function encrypt($plainContent, $iv, $passphrase = '') {
+ private static function encrypt($plainContent, $iv, $passphrase = '') {
if ($encryptedContent = openssl_encrypt($plainContent, 'AES-128-CFB', $passphrase, false, $iv)) {
return $encryptedContent;
@@ -228,7 +227,7 @@ class Crypt { * @throws \Exception
* @return string decrypted file content
*/
- public static function decrypt($encryptedContent, $iv, $passphrase) {
+ private static function decrypt($encryptedContent, $iv, $passphrase) {
if ($plainContent = openssl_decrypt($encryptedContent, 'AES-128-CFB', $passphrase, false, $iv)) {
@@ -248,7 +247,7 @@ class Crypt { * @param string $iv IV to be concatenated
* @returns string concatenated content
*/
- public static function concatIv($content, $iv) {
+ private static function concatIv($content, $iv) {
$combined = $content . '00iv00' . $iv;
@@ -261,7 +260,7 @@ class Crypt { * @param string $catFile concatenated data to be split
* @returns array keys: encrypted, iv
*/
- public static function splitIv($catFile) {
+ private static function splitIv($catFile) {
// Fetch encryption metadata from end of file
$meta = substr($catFile, -22);
@@ -378,34 +377,6 @@ class Crypt { }
-
- /**
- * @brief Creates symmetric keyfile content using a generated key
- * @param string $plainContent content to be encrypted
- * @returns array keys: key, encrypted
- * @note symmetricDecryptFileContent() can be used to decrypt files created using this method
- *
- * This function decrypts a file
- */
- public static function symmetricEncryptFileContentKeyfile($plainContent) {
-
- $key = self::generateKey();
-
- if ($encryptedContent = self::symmetricEncryptFileContent($plainContent, $key)) {
-
- return array(
- 'key' => $key,
- 'encrypted' => $encryptedContent
- );
-
- } else {
-
- return false;
-
- }
-
- }
-
/**
* @brief Create asymmetrically encrypted keyfile content using a generated key
* @param string $plainContent content to be encrypted
@@ -489,42 +460,10 @@ class Crypt { }
/**
- * @brief Asymetrically encrypt a string using a public key
- * @param $plainContent
- * @param $publicKey
- * @return string encrypted file
- */
- public static function keyEncrypt($plainContent, $publicKey) {
-
- openssl_public_encrypt($plainContent, $encryptedContent, $publicKey);
-
- return $encryptedContent;
-
- }
-
- /**
- * @brief Asymetrically decrypt a file using a private key
- * @param $encryptedContent
- * @param $privatekey
- * @return string decrypted file
- */
- public static function keyDecrypt($encryptedContent, $privatekey) {
-
- $result = @openssl_private_decrypt($encryptedContent, $plainContent, $privatekey);
-
- if ($result) {
- return $plainContent;
- }
-
- return $result;
-
- }
-
- /**
* @brief Generates a pseudo random initialisation vector
* @return String $iv generated IV
*/
- public static function generateIv() {
+ private static function generateIv() {
if ($random = openssl_random_pseudo_bytes(12, $strong)) {
@@ -550,7 +489,7 @@ class Crypt { }
/**
- * @brief Generate a pseudo random 1024kb ASCII key
+ * @brief Generate a pseudo random 1024kb ASCII key, used as file key
* @returns $key Generated key
*/
public static function generateKey() {
@@ -576,13 +515,13 @@ class Crypt { }
/**
- * @brief Get the blowfish encryption handeler for a key
+ * @brief Get the blowfish encryption handler for a key
* @param $key string (optional)
* @return \Crypt_Blowfish blowfish object
*
- * if the key is left out, the default handeler will be used
+ * if the key is left out, the default handler will be used
*/
- public static function getBlowfish($key = '') {
+ private static function getBlowfish($key = '') {
if ($key) {
@@ -597,38 +536,6 @@ class Crypt { }
/**
- * @param $passphrase
- * @return mixed
- */
- public static function legacyCreateKey($passphrase) {
-
- // Generate a random integer
- $key = mt_rand(10000, 99999) . mt_rand(10000, 99999) . mt_rand(10000, 99999) . mt_rand(10000, 99999);
-
- // Encrypt the key with the passphrase
- $legacyEncKey = self::legacyEncrypt($key, $passphrase);
-
- return $legacyEncKey;
-
- }
-
- /**
- * @brief encrypts content using legacy blowfish system
- * @param string $content the cleartext message you want to encrypt
- * @param string $passphrase
- * @returns string encrypted content
- *
- * This function encrypts an content
- */
- public static function legacyEncrypt($content, $passphrase = '') {
-
- $bf = self::getBlowfish($passphrase);
-
- return $bf->encrypt($content);
-
- }
-
- /**
* @brief decrypts content using legacy blowfish system
* @param string $content the cleartext message you want to decrypt
* @param string $passphrase
@@ -665,4 +572,4 @@ class Crypt { }
}
-}
\ No newline at end of file +}
diff --git a/apps/files_encryption/lib/helper.php b/apps/files_encryption/lib/helper.php index b09c584c0b8..0209a5d18b7 100755 --- a/apps/files_encryption/lib/helper.php +++ b/apps/files_encryption/lib/helper.php @@ -199,6 +199,12 @@ class Helper { public static function stripUserFilesPath($path) { $trimmed = ltrim($path, '/'); $split = explode('/', $trimmed); + + // it is not a file relative to data/user/files + if (count($split) < 3 || $split[1] !== 'files') { + return false; + } + $sliced = array_slice($split, 2); $relPath = implode('/', $sliced); @@ -206,6 +212,27 @@ class Helper { } /** + * @brief get path to the correspondig file in data/user/files + * @param string $path path to a version or a file in the trash + * @return string path to correspondig file relative to data/user/files + */ + public static function getPathToRealFile($path) { + $trimmed = ltrim($path, '/'); + $split = explode('/', $trimmed); + + if (count($split) < 3 || $split[1] !== "files_versions") { + return false; + } + + $sliced = array_slice($split, 2); + $realPath = implode('/', $sliced); + //remove the last .v + $realPath = substr($realPath, 0, strrpos($realPath, '.v')); + + return $realPath; + } + + /** * @brief redirect to a error page */ public static function redirectToErrorPage() { diff --git a/apps/files_encryption/lib/keymanager.php b/apps/files_encryption/lib/keymanager.php index b2fd650f18d..5386de486e1 100755 --- a/apps/files_encryption/lib/keymanager.php +++ b/apps/files_encryption/lib/keymanager.php @@ -593,4 +593,4 @@ class Keymanager { return $targetPath; } -}
\ No newline at end of file +} diff --git a/apps/files_encryption/lib/proxy.php b/apps/files_encryption/lib/proxy.php index 735eba911a9..eb7ba60cb9d 100644 --- a/apps/files_encryption/lib/proxy.php +++ b/apps/files_encryption/lib/proxy.php @@ -116,7 +116,7 @@ class Proxy extends \OC_FileProxy { return true; } - $handle = fopen('crypt://' . $relativePath . '.etmp', 'w'); + $handle = fopen('crypt://' . $path . '.etmp', 'w'); if (is_resource($handle)) { // write data to stream @@ -154,9 +154,6 @@ class Proxy extends \OC_FileProxy { $plainData = null; $view = new \OC_FilesystemView('/'); - // get relative path - $relativePath = \OCA\Encryption\Helper::stripUserFilesPath($path); - // init session $session = new \OCA\Encryption\Session($view); @@ -166,7 +163,7 @@ class Proxy extends \OC_FileProxy { && Crypt::isCatfileContent($data) ) { - $handle = fopen('crypt://' . $relativePath, 'r'); + $handle = fopen('crypt://' . $path, 'r'); if (is_resource($handle)) { while (($plainDataChunk = fgets($handle, 8192)) !== false) { @@ -296,14 +293,14 @@ class Proxy extends \OC_FileProxy { // Open the file using the crypto stream wrapper // protocol and let it do the decryption work instead - $result = fopen('crypt://' . $relativePath, $meta['mode']); + $result = fopen('crypt://' . $path, $meta['mode']); } elseif ( self::shouldEncrypt($path) and $meta ['mode'] !== 'r' and $meta['mode'] !== 'rb' ) { - $result = fopen('crypt://' . $relativePath, $meta['mode']); + $result = fopen('crypt://' . $path, $meta['mode']); } // Re-enable the proxy diff --git a/apps/files_encryption/lib/stream.php b/apps/files_encryption/lib/stream.php index 3c1eb2c5f5e..335ea3733eb 100644 --- a/apps/files_encryption/lib/stream.php +++ b/apps/files_encryption/lib/stream.php @@ -62,6 +62,7 @@ class Stream { private $unencryptedSize; private $publicKey; private $encKeyfile; + private $newFile; // helper var, we only need to write the keyfile for new files /** * @var \OC\Files\View */ @@ -73,13 +74,16 @@ class Stream { private $privateKey; /** - * @param $path + * @param $path raw path relative to data/ * @param $mode * @param $options * @param $opened_path * @return bool */ public function stream_open($path, $mode, $options, &$opened_path) { + + // assume that the file already exist before we decide it finally in getKey() + $this->newFile = false; if (!isset($this->rootView)) { $this->rootView = new \OC_FilesystemView('/'); @@ -93,12 +97,21 @@ class Stream { $this->userId = $util->getUserId(); - // Strip identifier text from path, this gives us the path relative to data/<user>/files - $this->relPath = \OC\Files\Filesystem::normalizePath(str_replace('crypt://', '', $path)); - // rawPath is relative to the data directory - $this->rawPath = $util->getUserFilesDir() . $this->relPath; + $this->rawPath = \OC\Files\Filesystem::normalizePath(str_replace('crypt://', '', $path)); + // Strip identifier text from path, this gives us the path relative to data/<user>/files + $this->relPath = Helper::stripUserFilesPath($this->rawPath); + // if raw path doesn't point to a real file, check if it is a version or a file in the trash bin + if ($this->relPath === false) { + $this->relPath = Helper::getPathToRealFile($this->rawPath); + } + + if($this->relPath === false) { + \OCP\Util::writeLog('Encryption library', 'failed to open file "' . $this->rawPath . '" expecting a path to user/files or to user/files_versions', \OCP\Util::ERROR); + return false; + } + // Disable fileproxies so we can get the file size and open the source file without recursive encryption $proxyStatus = \OC_FileProxy::$enabled; \OC_FileProxy::$enabled = false; @@ -258,6 +271,8 @@ class Stream { } else { + $this->newFile = true; + return false; } @@ -436,9 +451,7 @@ class Stream { fwrite($this->handle, $encrypted); $this->writeCache = ''; - } - } /** @@ -451,56 +464,63 @@ class Stream { // if there is no valid private key return false if ($this->privateKey === false) { - // cleanup - if ($this->meta['mode'] !== 'r' && $this->meta['mode'] !== 'rb') { + // 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; + // 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 ($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' - and $this->size > 0 + $this->meta['mode'] !== 'r' && + $this->meta['mode'] !== 'rb' && + $this->size > 0 ) { - // Disable encryption proxy to prevent recursive calls - $proxyStatus = \OC_FileProxy::$enabled; - \OC_FileProxy::$enabled = false; + // only write keyfiles if it was a new file + if ($this->newFile === true) { + + // Disable encryption proxy to prevent recursive calls + $proxyStatus = \OC_FileProxy::$enabled; + \OC_FileProxy::$enabled = false; - // Fetch user's public key - $this->publicKey = Keymanager::getPublicKey($this->rootView, $this->userId); + // Fetch user's public key + $this->publicKey = Keymanager::getPublicKey($this->rootView, $this->userId); - // Check if OC sharing api is enabled - $sharingEnabled = \OCP\Share::isEnabled(); + // Check if OC sharing api is enabled + $sharingEnabled = \OCP\Share::isEnabled(); - $util = new Util($this->rootView, $this->userId); + $util = new Util($this->rootView, $this->userId); - // Get all users sharing the file includes current user - $uniqueUserIds = $util->getSharingUsersArray($sharingEnabled, $this->relPath, $this->userId); + // Get all users sharing the file includes current user + $uniqueUserIds = $util->getSharingUsersArray($sharingEnabled, $this->relPath, $this->userId); - // Fetch public keys for all sharing users - $publicKeys = Keymanager::getPublicKeys($this->rootView, $uniqueUserIds); + // Fetch public keys for all sharing users + $publicKeys = Keymanager::getPublicKeys($this->rootView, $uniqueUserIds); - // Encrypt enc key for all sharing users - $this->encKeyfiles = Crypt::multiKeyEncrypt($this->plainKey, $publicKeys); + // Encrypt enc key for all sharing users + $this->encKeyfiles = Crypt::multiKeyEncrypt($this->plainKey, $publicKeys); - // Save the new encrypted file key - Keymanager::setFileKey($this->rootView, $this->relPath, $this->userId, $this->encKeyfiles['data']); + // Save the new encrypted file key + Keymanager::setFileKey($this->rootView, $this->relPath, $this->userId, $this->encKeyfiles['data']); - // Save the sharekeys - Keymanager::setShareKeys($this->rootView, $this->relPath, $this->encKeyfiles['keys']); + // Save the sharekeys + Keymanager::setShareKeys($this->rootView, $this->relPath, $this->encKeyfiles['keys']); + + // Re-enable proxy - our work is done + \OC_FileProxy::$enabled = $proxyStatus; + } // get file info $fileInfo = $this->rootView->getFileInfo($this->rawPath); @@ -508,9 +528,6 @@ class Stream { $fileInfo = array(); } - // Re-enable proxy - our work is done - \OC_FileProxy::$enabled = $proxyStatus; - // set encryption data $fileInfo['encrypted'] = true; $fileInfo['size'] = $this->size; @@ -521,7 +538,6 @@ class Stream { } return fclose($this->handle); - } } diff --git a/apps/files_encryption/lib/util.php b/apps/files_encryption/lib/util.php index 50e823585d7..b8d68623493 100644 --- a/apps/files_encryption/lib/util.php +++ b/apps/files_encryption/lib/util.php @@ -21,30 +21,6 @@ * */ -# Bugs -# ---- -# Sharing a file to a user without encryption set up will not provide them with access but won't notify the sharer -# Sharing all files to admin for recovery purposes still in progress -# Possibly public links are broken (not tested since last merge of master) - - -# Missing features -# ---------------- -# Make sure user knows if large files weren't encrypted - - -# Test -# ---- -# Test that writing files works when recovery is enabled, and sharing API is disabled -# Test trashbin support - - -// Old Todo: -// - Crypt/decrypt button in the userinterface -// - Setting if crypto should be on by default -// - Add a setting "DonĀ“t encrypt files larger than xx because of performance -// reasons" - namespace OCA\Encryption; /** @@ -57,45 +33,6 @@ namespace OCA\Encryption; class Util { - // Web UI: - - //// DONE: files created via web ui are encrypted - //// DONE: file created & encrypted via web ui are readable in web ui - //// DONE: file created & encrypted via web ui are readable via webdav - - - // WebDAV: - - //// DONE: new data filled files added via webdav get encrypted - //// DONE: new data filled files added via webdav are readable via webdav - //// DONE: reading unencrypted files when encryption is enabled works via - //// webdav - //// DONE: files created & encrypted via web ui are readable via webdav - - - // Legacy support: - - //// DONE: add method to check if file is encrypted using new system - //// DONE: add method to check if file is encrypted using old system - //// DONE: add method to fetch legacy key - //// DONE: add method to decrypt legacy encrypted data - - - // Admin UI: - - //// DONE: changing user password also changes encryption passphrase - - //// TODO: add support for optional recovery in case of lost passphrase / keys - //// TODO: add admin optional required long passphrase for users - //// TODO: implement flag system to allow user to specify encryption by folder, subfolder, etc. - - - // Integration testing: - - //// TODO: test new encryption with versioning - //// DONE: test new encryption with sharing - //// TODO: test new encryption with proxies - const MIGRATION_COMPLETED = 1; // migration to new encryption completed const MIGRATION_IN_PROGRESS = -1; // migration is running const MIGRATION_OPEN = 0; // user still needs to be migrated @@ -403,7 +340,7 @@ class Util { $filePath = $directory . '/' . $this->view->getRelativePath('/' . $file); $relPath = \OCA\Encryption\Helper::stripUserFilesPath($filePath); - // If the path is a directory, search + // If the path is a directory, search // its contents if ($this->view->is_dir($filePath)) { @@ -419,8 +356,8 @@ class Util { $isEncryptedPath = $this->isEncryptedPath($filePath); // If the file is encrypted - // NOTE: If the userId is - // empty or not set, file will + // NOTE: If the userId is + // empty or not set, file will // detected as plain // NOTE: This is inefficient; // scanning every file like this @@ -565,9 +502,6 @@ class Util { // split the path parts $pathParts = explode('/', $path); - // get relative path - $relativePath = \OCA\Encryption\Helper::stripUserFilesPath($path); - if (isset($pathParts[2]) && $pathParts[2] === 'files' && $this->view->file_exists($path) && $this->isEncryptedPath($path) ) { @@ -580,7 +514,7 @@ class Util { $lastChunkNr = floor($size / 8192); // open stream - $stream = fopen('crypt://' . $relativePath, "r"); + $stream = fopen('crypt://' . $path, "r"); if (is_resource($stream)) { // calculate last chunk position @@ -663,6 +597,205 @@ class Util { } /** + * @brief encrypt versions from given file + * @param array $filelist list of encrypted files, relative to data/user/files + * @return boolean + */ + private function encryptVersions($filelist) { + + $successful = true; + + if (\OCP\App::isEnabled('files_versions')) { + + foreach ($filelist as $filename) { + + $versions = \OCA\Files_Versions\Storage::getVersions($this->userId, $filename); + foreach ($versions as $version) { + + $path = '/' . $this->userId . '/files_versions/' . $version['path'] . '.v' . $version['version']; + + $encHandle = fopen('crypt://' . $path . '.part', 'wb'); + + if ($encHandle === false) { + \OCP\Util::writeLog('Encryption library', 'couldn\'t open "' . $path . '", decryption failed!', \OCP\Util::FATAL); + $successful = false; + continue; + } + + $plainHandle = $this->view->fopen($path, 'rb'); + if ($plainHandle === false) { + \OCP\Util::writeLog('Encryption library', 'couldn\'t open "' . $path . '.part", decryption failed!', \OCP\Util::FATAL); + $successful = false; + continue; + } + + stream_copy_to_stream($plainHandle, $encHandle); + + fclose($encHandle); + fclose($plainHandle); + + $this->view->rename($path . '.part', $path); + } + } + } + + return $successful; + } + + /** + * @brief decrypt versions from given file + * @param string $filelist list of decrypted files, relative to data/user/files + * @return boolean + */ + private function decryptVersions($filelist) { + + $successful = true; + + if (\OCP\App::isEnabled('files_versions')) { + + foreach ($filelist as $filename) { + + $versions = \OCA\Files_Versions\Storage::getVersions($this->userId, $filename); + foreach ($versions as $version) { + + $path = '/' . $this->userId . '/files_versions/' . $version['path'] . '.v' . $version['version']; + + $encHandle = fopen('crypt://' . $path, 'rb'); + + if ($encHandle === false) { + \OCP\Util::writeLog('Encryption library', 'couldn\'t open "' . $path . '", decryption failed!', \OCP\Util::FATAL); + $successful = false; + continue; + } + + $plainHandle = $this->view->fopen($path . '.part', 'wb'); + if ($plainHandle === false) { + \OCP\Util::writeLog('Encryption library', 'couldn\'t open "' . $path . '.part", decryption failed!', \OCP\Util::FATAL); + $successful = false; + continue; + } + + stream_copy_to_stream($encHandle, $plainHandle); + + fclose($encHandle); + fclose($plainHandle); + + $this->view->rename($path . '.part', $path); + } + } + } + + return $successful; + } + + /** + * @brief Decrypt all files + * @return bool + */ + public function decryptAll() { + + $found = $this->findEncFiles($this->userId . '/files'); + + $successful = true; + + if ($found) { + + $versionStatus = \OCP\App::isEnabled('files_versions'); + \OC_App::disable('files_versions'); + + $decryptedFiles = array(); + + // Encrypt unencrypted files + foreach ($found['encrypted'] as $encryptedFile) { + + //get file info + $fileInfo = \OC\Files\Filesystem::getFileInfo($encryptedFile['path']); + + //relative to data/<user>/file + $relPath = Helper::stripUserFilesPath($encryptedFile['path']); + + //relative to /data + $rawPath = $encryptedFile['path']; + + //get timestamp + $timestamp = $this->view->filemtime($rawPath); + + //enable proxy to use OC\Files\View to access the original file + \OC_FileProxy::$enabled = true; + + // Open enc file handle for binary reading + $encHandle = $this->view->fopen($rawPath, 'rb'); + + // Disable proxy to prevent file being encrypted again + \OC_FileProxy::$enabled = false; + + if ($encHandle === false) { + \OCP\Util::writeLog('Encryption library', 'couldn\'t open "' . $rawPath . '", decryption failed!', \OCP\Util::FATAL); + $successful = false; + continue; + } + + // Open plain file handle for binary writing, with same filename as original plain file + $plainHandle = $this->view->fopen($rawPath . '.part', 'wb'); + if ($plainHandle === false) { + \OCP\Util::writeLog('Encryption library', 'couldn\'t open "' . $rawPath . '.part", decryption failed!', \OCP\Util::FATAL); + $successful = false; + continue; + } + + // Move plain file to a temporary location + $size = stream_copy_to_stream($encHandle, $plainHandle); + if ($size === 0) { + \OCP\Util::writeLog('Encryption library', 'Zero bytes copied of "' . $rawPath . '", decryption failed!', \OCP\Util::FATAL); + $successful = false; + continue; + } + + fclose($encHandle); + fclose($plainHandle); + + $fakeRoot = $this->view->getRoot(); + $this->view->chroot('/' . $this->userId . '/files'); + + $this->view->rename($relPath . '.part', $relPath); + + $this->view->chroot($fakeRoot); + + //set timestamp + $this->view->touch($rawPath, $timestamp); + + // Add the file to the cache + \OC\Files\Filesystem::putFileInfo($relPath, array( + 'encrypted' => false, + 'size' => $size, + 'unencrypted_size' => $size, + 'etag' => $fileInfo['etag'] + )); + + $decryptedFiles[] = $relPath; + + } + + if ($versionStatus) { + \OC_App::enable('files_versions'); + } + + if (!$this->decryptVersions($decryptedFiles)) { + $successful = false; + } + + if ($successful) { + $this->view->deleteAll($this->keyfilesPath); + $this->view->deleteAll($this->shareKeysPath); + } + + \OC_FileProxy::$enabled = true; + } + + return $successful; + } + + /** * @brief Encrypt all files in a directory * @param string $dirPath the directory whose files will be encrypted * @param null $legacyPassphrase @@ -672,30 +805,44 @@ class Util { */ public function encryptAll($dirPath, $legacyPassphrase = null, $newPassphrase = null) { - if ($found = $this->findEncFiles($dirPath)) { + $found = $this->findEncFiles($dirPath); + + if ($found) { // Disable proxy to prevent file being encrypted twice \OC_FileProxy::$enabled = false; + $versionStatus = \OCP\App::isEnabled('files_versions'); + \OC_App::disable('files_versions'); + + $encryptedFiles = array(); + // Encrypt unencrypted files foreach ($found['plain'] as $plainFile) { + //get file info + $fileInfo = \OC\Files\Filesystem::getFileInfo($plainFile['path']); + //relative to data/<user>/file $relPath = $plainFile['path']; //relative to /data $rawPath = '/' . $this->userId . '/files/' . $plainFile['path']; + // keep timestamp + $timestamp = $this->view->filemtime($rawPath); + // Open plain file handle for binary reading $plainHandle = $this->view->fopen($rawPath, 'rb'); // Open enc file handle for binary writing, with same filename as original plain file - $encHandle = fopen('crypt://' . $relPath . '.part', 'wb'); + $encHandle = fopen('crypt://' . $rawPath . '.part', 'wb'); // 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'); @@ -704,12 +851,19 @@ class Util { $this->view->chroot($fakeRoot); + // set timestamp + $this->view->touch($rawPath, $timestamp); + // Add the file to the cache \OC\Files\Filesystem::putFileInfo($relPath, array( - 'encrypted' => true, - 'size' => $size, - 'unencrypted_size' => $size - )); + 'encrypted' => true, + 'size' => $size, + 'unencrypted_size' => $size, + 'etag' => $fileInfo['etag'] + )); + + $encryptedFiles[] = $relPath; + } // Encrypt legacy encrypted files @@ -750,6 +904,12 @@ class Util { \OC_FileProxy::$enabled = true; + if ($versionStatus) { + \OC_App::enable('files_versions'); + } + + $this->encryptVersions($encryptedFiles); + // If files were found, return true return true; } else { @@ -878,46 +1038,22 @@ class Util { } /** - * @brief Decrypt a keyfile without knowing how it was encrypted + * @brief Decrypt a keyfile * @param string $filePath - * @param string $fileOwner * @param string $privateKey * @return bool|string - * @note Checks whether file was encrypted with openssl_seal or - * openssl_encrypt, and decrypts accrdingly - * @note This was used when 2 types of encryption for keyfiles was used, - * but now we've switched to exclusively using openssl_seal() */ - public function decryptUnknownKeyfile($filePath, $fileOwner, $privateKey) { + private function decryptKeyfile($filePath, $privateKey) { // Get the encrypted keyfile - // NOTE: the keyfile format depends on how it was encrypted! At - // this stage we don't know how it was encrypted $encKeyfile = Keymanager::getFileKey($this->view, $this->userId, $filePath); - // We need to decrypt the keyfile - // Has the file been shared yet? - if ( - $this->userId === $fileOwner - && !Keymanager::getShareKey($this->view, $this->userId, $filePath) // NOTE: we can't use isShared() here because it's a post share hook so it always returns true - ) { - - // The file has no shareKey, and its keyfile must be - // decrypted conventionally - $plainKeyfile = Crypt::keyDecrypt($encKeyfile, $privateKey); - + // The file has a shareKey and must use it for decryption + $shareKey = Keymanager::getShareKey($this->view, $this->userId, $filePath); - } else { - - // The file has a shareKey and must use it for decryption - $shareKey = Keymanager::getShareKey($this->view, $this->userId, $filePath); - - $plainKeyfile = Crypt::multiKeyDecrypt($encKeyfile, $shareKey, $privateKey); - - } + $plainKeyfile = Crypt::multiKeyDecrypt($encKeyfile, $shareKey, $privateKey); return $plainKeyfile; - } /** @@ -956,7 +1092,7 @@ class Util { $fileOwner = \OC\Files\Filesystem::getOwner($filePath); // Decrypt keyfile - $plainKeyfile = $this->decryptUnknownKeyfile($filePath, $fileOwner, $privateKey); + $plainKeyfile = $this->decryptKeyfile($filePath, $privateKey); // Re-enc keyfile to (additional) sharekeys $multiEncKey = Crypt::multiKeyEncrypt($plainKeyfile, $userPubKeys); @@ -1012,7 +1148,7 @@ class Util { } - // If recovery is enabled, add the + // If recovery is enabled, add the // Admin UID to list of users to share to if ($recoveryEnabled) { // Find recoveryAdmin user ID @@ -1579,4 +1715,28 @@ class Util { return false; } + /** + * @brief decrypt private key and add it to the current session + * @param array $params with 'uid' and 'password' + * @return mixed session or false + */ + public function initEncryption($params) { + + $encryptedKey = Keymanager::getPrivateKey($this->view, $params['uid']); + + $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); + return false; + } + + $session = new \OCA\Encryption\Session($this->view); + + $session->setPrivateKey($privateKey); + + return $session; + } + } |