/** * @param $path */ public function handleFile($path) { // Disable encryption proxy to prevent recursive calls $proxyStatus = \OC_FileProxy::$enabled; \OC_FileProxy::$enabled = false; $view = new \OC_FilesystemView('/'); $session = new \OCA\Encryption\Session($view); $userId = Helper::getUser($path); $util = new Util($view, $userId); // split the path parts $pathParts = explode('/', $path); // get relative path $relativePath = \OCA\Encryption\Helper::stripUserFilesPath($path); // only if file is on 'files' folder fix file size and sharing if (isset($pathParts[2]) && $pathParts[2] === 'files' && $util->fixFileSize($path)) { // get sharing app state $sharingEnabled = \OCP\Share::isEnabled(); // get users $usersSharing = $util->getSharingUsersArray($sharingEnabled, $relativePath); // update sharing-keys $util->setSharedFileKeyfiles($session, $usersSharing, $relativePath); } \OC_FileProxy::$enabled = $proxyStatus; }
/** * @param string $path * @param int $size * @return int|bool */ public function postFileSize($path, $size, $fileInfo = null) { $view = new \OC\Files\View('/'); $userId = Helper::getUser($path); $util = new Util($view, $userId); // if encryption is no longer enabled or if the files aren't migrated yet // we return the default file size if (!\OCP\App::isEnabled('files_encryption') || $util->getMigrationStatus() !== Util::MIGRATION_COMPLETED) { return $size; } // if path is a folder do nothing if ($view->is_dir($path)) { $proxyState = \OC_FileProxy::$enabled; \OC_FileProxy::$enabled = false; $fileInfo = $view->getFileInfo($path); \OC_FileProxy::$enabled = $proxyState; if (isset($fileInfo['unencrypted_size']) && $fileInfo['unencrypted_size'] > 0) { return $fileInfo['unencrypted_size']; } return $size; } // get relative path $relativePath = \OCA\Encryption\Helper::stripUserFilesPath($path); // if path is empty we cannot resolve anything if (empty($relativePath)) { return $size; } // get file info from database/cache if not .part file if (empty($fileInfo) && !Helper::isPartialFilePath($path)) { $proxyState = \OC_FileProxy::$enabled; \OC_FileProxy::$enabled = false; $fileInfo = $view->getFileInfo($path); \OC_FileProxy::$enabled = $proxyState; } // if file is encrypted return real file size if (isset($fileInfo['encrypted']) && $fileInfo['encrypted'] === true) { // try to fix unencrypted file size if it doesn't look plausible if ((int) $fileInfo['size'] > 0 && (int) $fileInfo['unencrypted_size'] === 0) { $fixSize = $util->getFileSize($path); $fileInfo['unencrypted_size'] = $fixSize; // put file info if not .part file if (!Helper::isPartialFilePath($relativePath)) { $view->putFileInfo($path, array('unencrypted_size' => $fixSize)); } } $size = $fileInfo['unencrypted_size']; } else { $fileInfoUpdates = array(); $fixSize = $util->getFileSize($path); if ($fixSize > 0) { $size = $fixSize; $fileInfoUpdates['encrypted'] = true; $fileInfoUpdates['unencrypted_size'] = $size; // put file info if not .part file if (!Helper::isPartialFilePath($relativePath)) { $view->putFileInfo($path, $fileInfoUpdates); } } } return $size; }
/** * Find all files and their encryption status within a directory * @param string $directory The path of the parent directory to search * @param bool $found the founded files if called again * @return array keys: plain, encrypted, broken * @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 */ public function findEncFiles($directory, &$found = false) { // Disable proxy - we don't want files to be decrypted before // we handle them \OC_FileProxy::$enabled = false; if ($found === false) { $found = array('plain' => array(), 'encrypted' => array(), 'broken' => array()); } if ($this->view->is_dir($directory) && ($handle = $this->view->opendir($directory))) { if (is_resource($handle)) { while (false !== ($file = readdir($handle))) { if ($file !== "." && $file !== "..") { $filePath = $directory . '/' . $this->view->getRelativePath('/' . $file); $relPath = \OCA\Encryption\Helper::stripUserFilesPath($filePath); // If the path is a directory, search // its contents if ($this->view->is_dir($filePath)) { $this->findEncFiles($filePath, $found); // If the path is a file, determine // its encryption status } elseif ($this->view->is_file($filePath)) { // Disable proxies again, some- // where they got re-enabled :/ \OC_FileProxy::$enabled = false; $isEncryptedPath = $this->isEncryptedPath($filePath); // If the file is encrypted // NOTE: If the userId is // empty or not set, file will // detected as plain // NOTE: This is inefficient; // scanning every file like this // will eat server resources :( if ($isEncryptedPath) { $fileKey = Keymanager::getFileKey($this->view, $this, $relPath); $shareKey = Keymanager::getShareKey($this->view, $this->userId, $this, $relPath); // if file is encrypted but now file key is available, throw exception if ($fileKey === false || $shareKey === false) { \OCP\Util::writeLog('encryption library', 'No keys available to decrypt the file: ' . $filePath, \OCP\Util::ERROR); $found['broken'][] = array('name' => $file, 'path' => $filePath); } else { $found['encrypted'][] = array('name' => $file, 'path' => $filePath); } // If the file is not encrypted } else { $found['plain'][] = array('name' => $file, 'path' => $relPath); } } } } } } \OC_FileProxy::$enabled = true; return $found; }
/** * get the file size of the unencrypted file * @param string $path absolute path * @return bool */ public function getFileSize($path) { $result = 0; // Disable encryption proxy to prevent recursive calls $proxyStatus = \OC_FileProxy::$enabled; \OC_FileProxy::$enabled = false; // split the path parts $pathParts = explode('/', $path); if (isset($pathParts[2]) && $pathParts[2] === 'files' && $this->view->file_exists($path) && $this->isEncryptedPath($path)) { $cipher = 'AES-128-CFB'; $realSize = 0; // get the size from filesystem $size = $this->view->filesize($path); // open stream $stream = $this->view->fopen($path, "r"); if (is_resource($stream)) { // if the file contains a encryption header we // we set the cipher // and we update the size if ($this->containHeader($path)) { $data = fread($stream, Crypt::BLOCKSIZE); $header = Crypt::parseHeader($data); $cipher = Crypt::getCipher($header); $size -= Crypt::BLOCKSIZE; } // fast path, else the calculation for $lastChunkNr is bogus if ($size === 0) { \OC_FileProxy::$enabled = $proxyStatus; return 0; } // calculate last chunk nr // next highest is end of chunks, one subtracted is last one // we have to read the last chunk, we can't just calculate it (because of padding etc) $lastChunkNr = ceil($size / Crypt::BLOCKSIZE) - 1; // calculate last chunk position $lastChunkPos = $lastChunkNr * Crypt::BLOCKSIZE; // get the content of the last chunk if (@fseek($stream, $lastChunkPos, SEEK_CUR) === 0) { $realSize += $lastChunkNr * 6126; } $lastChunkContentEncrypted = ''; $count = Crypt::BLOCKSIZE; while ($count > 0) { $data = fread($stream, Crypt::BLOCKSIZE); $count = strlen($data); $lastChunkContentEncrypted .= $data; if (strlen($lastChunkContentEncrypted) > Crypt::BLOCKSIZE) { $realSize += 6126; $lastChunkContentEncrypted = substr($lastChunkContentEncrypted, Crypt::BLOCKSIZE); } } fclose($stream); $relPath = \OCA\Encryption\Helper::stripUserFilesPath($path); $shareKey = Keymanager::getShareKey($this->view, $this->keyId, $this, $relPath); if ($shareKey === false) { \OC_FileProxy::$enabled = $proxyStatus; return $result; } $session = new \OCA\Encryption\Session($this->view); $privateKey = $session->getPrivateKey(); $plainKeyfile = $this->decryptKeyfile($relPath, $privateKey); $plainKey = Crypt::multiKeyDecrypt($plainKeyfile, $shareKey, $privateKey); $lastChunkContent = Crypt::symmetricDecryptFileContent($lastChunkContentEncrypted, $plainKey, $cipher); // calc the real file size with the size of the last chunk $realSize += strlen($lastChunkContent); // store file size $result = $realSize; } } \OC_FileProxy::$enabled = $proxyStatus; return $result; }
/** * 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) { //relative to data/<user>/file $relPath = Helper::stripUserFilesPath($encryptedFile['path']); //get file info $fileInfo = \OC\Files\Filesystem::getFileInfo($relPath); //relative to /data $rawPath = $encryptedFile['path']; //get timestamp $timestamp = $fileInfo['mtime']; //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); //set timestamp $this->view->touch($relPath, $timestamp); $this->view->chroot($fakeRoot); // Add the file to the cache \OC\Files\Filesystem::putFileInfo($relPath, array('encrypted' => false, 'size' => $size, 'unencrypted_size' => 0, 'etag' => $fileInfo['etag'])); $decryptedFiles[] = $relPath; } if ($versionStatus) { \OC_App::enable('files_versions'); } if (!$this->decryptVersions($decryptedFiles)) { $successful = false; } // if there are broken encrypted files than the complete decryption // was not successful if (!empty($found['broken'])) { $successful = false; } if ($successful) { $this->view->rename($this->keyfilesPath, $this->keyfilesPath . '.backup'); $this->view->rename($this->shareKeysPath, $this->shareKeysPath . '.backup'); } \OC_FileProxy::$enabled = true; } return $successful; }
/** * Get the path including the storage mount point * @param int $id * @return string the path including the mount point like AmazonS3/folder/file.txt */ public function getPathWithMountPoint($id) { 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); // reformat the path to be relative e.g. /user/files/folder becomes /folder/ $relativePath = \OCA\Encryption\Helper::stripUserFilesPath($path); return $relativePath; }