. * */ /** * @brief Encryption proxy which handles filesystem operations before and after * execution and encrypts, and handles keyfiles accordingly. Used for * webui. */ namespace OCA\Encryption; /** * Class Proxy * @package OCA\Encryption */ class Proxy extends \OC_FileProxy { private static $blackList = null; //mimetypes blacklisted from encryption private static $enableEncryption = null; /** * Check if a file requires encryption * @param string $path * @return bool * * Tests if server side encryption is enabled, and file is allowed by blacklists */ private static function shouldEncrypt( $path ) { if ( is_null( self::$enableEncryption ) ) { if ( \OCP\Config::getAppValue( 'files_encryption', 'enable_encryption', 'true' ) == 'true' && Crypt::mode() == 'server' ) { self::$enableEncryption = true; } else { self::$enableEncryption = false; } } if ( !self::$enableEncryption ) { return false; } if ( is_null( self::$blackList ) ) { self::$blackList = explode( ',', \OCP\Config::getAppValue( 'files_encryption', 'type_blacklist', '' ) ); } if ( Crypt::isCatfileContent( $path ) ) { return true; } $extension = substr( $path, strrpos( $path, '.' ) + 1 ); if ( array_search( $extension, self::$blackList ) === false ) { return true; } return false; } /** * @param $path * @param $data * @return bool */ public function preFile_put_contents( $path, &$data ) { if ( self::shouldEncrypt( $path ) ) { // Stream put contents should have been converted to fopen if ( !is_resource( $data ) ) { $userId = \OCP\USER::getUser(); $view = new \OC_FilesystemView( '/' ); $util = new Util( $view, $userId ); $session = new Session( $view ); $privateKey = $session->getPrivateKey(); $filePath = $util->stripUserFilesPath( $path ); // Set the filesize for userland, before encrypting $size = strlen( $data ); // Disable encryption proxy to prevent recursive calls $proxyStatus = \OC_FileProxy::$enabled; \OC_FileProxy::$enabled = false; // Check if there is an existing key we can reuse if ( $encKeyfile = Keymanager::getFileKey( $view, $userId, $filePath ) ) { // Fetch shareKey $shareKey = Keymanager::getShareKey( $view, $userId, $filePath ); // Decrypt the keyfile $plainKey = Crypt::multiKeyDecrypt( $encKeyfile, $shareKey, $privateKey ); } else { // Make a new key $plainKey = Crypt::generateKey(); } // Encrypt data $encData = Crypt::symmetricEncryptFileContent( $data, $plainKey ); $sharingEnabled = \OCP\Share::isEnabled(); // if file exists try to get sharing users if ( $view->file_exists( $path ) ) { $uniqueUserIds = $util->getSharingUsersArray( $sharingEnabled, $filePath, $userId ); } else { $uniqueUserIds[] = $userId; } // Fetch public keys for all users who will share the file $publicKeys = Keymanager::getPublicKeys( $view, $uniqueUserIds ); // Encrypt plain keyfile to multiple sharefiles $multiEncrypted = Crypt::multiKeyEncrypt( $plainKey, $publicKeys ); // Save sharekeys to user folders Keymanager::setShareKeys( $view, $filePath, $multiEncrypted['keys'] ); // Set encrypted keyfile as common varname $encKey = $multiEncrypted['data']; // Save keyfile for newly encrypted file in parallel directory tree Keymanager::setFileKey( $view, $filePath, $userId, $encKey ); // Replace plain content with encrypted content by reference $data = $encData; // Update the file cache with file info \OC\Files\Filesystem::putFileInfo( $filePath, array( 'encrypted' => true, 'size' => strlen( $data ), 'unencrypted_size' => $size ), '' ); // Re-enable proxy - our work is done \OC_FileProxy::$enabled = $proxyStatus; } } return true; } /** * @param string $path Path of file from which has been read * @param string $data Data that has been read from file */ public function postFile_get_contents( $path, $data ) { $userId = \OCP\USER::getUser(); $view = new \OC_FilesystemView( '/' ); $util = new Util( $view, $userId ); $relPath = $util->stripUserFilesPath( $path ); // Disable encryption proxy to prevent recursive calls $proxyStatus = \OC_FileProxy::$enabled; \OC_FileProxy::$enabled = false; // init session $session = new Session( $view ); // If data is a catfile if ( Crypt::mode() == 'server' && Crypt::isCatfileContent( $data ) ) { $privateKey = $session->getPrivateKey( $userId ); // Get the encrypted keyfile $encKeyfile = Keymanager::getFileKey( $view, $userId, $relPath ); // Attempt to fetch the user's shareKey $shareKey = Keymanager::getShareKey( $view, $userId, $relPath ); // Decrypt keyfile with shareKey $plainKeyfile = Crypt::multiKeyDecrypt( $encKeyfile, $shareKey, $privateKey ); $plainData = Crypt::symmetricDecryptFileContent( $data, $plainKeyfile ); } elseif ( Crypt::mode() == 'server' && isset( $_SESSION['legacyenckey'] ) && Crypt::isEncryptedMeta( $path ) ) { $plainData = Crypt::legacyDecrypt( $data, $session->getLegacyKey() ); } \OC_FileProxy::$enabled = $proxyStatus; if ( !isset( $plainData ) ) { $plainData = $data; } return $plainData; } /** * @brief When a file is deleted, remove its keyfile also */ public function preUnlink( $path ) { // let the trashbin handle this if ( \OCP\App::isEnabled( 'files_trashbin' ) ) { return true; } // Disable encryption proxy to prevent recursive calls $proxyStatus = \OC_FileProxy::$enabled; \OC_FileProxy::$enabled = false; $view = new \OC_FilesystemView( '/' ); $userId = \OCP\USER::getUser(); $util = new Util( $view, $userId ); // Format path to be relative to user files dir $relPath = $util->stripUserFilesPath( $path ); list( $owner, $ownerPath ) = $util->getUidAndFilename( $relPath ); // Delete keyfile & shareKey so it isn't orphaned if ( !Keymanager::deleteFileKey( $view, $owner, $ownerPath ) ) { \OC_Log::write( 'Encryption library', 'Keyfile or shareKey could not be deleted for file "' . $ownerPath . '"', \OC_Log::ERROR ); } Keymanager::delAllShareKeys( $view, $owner, $ownerPath ); \OC_FileProxy::$enabled = $proxyStatus; // If we don't return true then file delete will fail; better // to leave orphaned keyfiles than to disallow file deletion return true; } /** * @param $path * @return bool */ public function postTouch( $path ) { $this->handleFile( $path ); return true; } /** * @param $path * @param $result * @return resource */ public function postFopen( $path, &$result ) { if ( !$result ) { return $result; } // Reformat path for use with OC_FSV $path_split = explode( '/', $path ); $path_f = implode( '/', array_slice( $path_split, 3 ) ); // FIXME: handling for /userId/cache used by webdav for chunking. The cache chunks are NOT encrypted if ( count($path_split) >= 2 && $path_split[2] == 'cache' ) { return $result; } // Disable encryption proxy to prevent recursive calls $proxyStatus = \OC_FileProxy::$enabled; \OC_FileProxy::$enabled = false; $meta = stream_get_meta_data( $result ); $view = new \OC_FilesystemView( '' ); $util = new Util( $view, \OCP\USER::getUser() ); // If file is already encrypted, decrypt using crypto protocol if ( Crypt::mode() == 'server' && $util->isEncryptedPath( $path ) ) { // Close the original encrypted file fclose( $result ); // Open the file using the crypto stream wrapper // protocol and let it do the decryption work instead $result = fopen( 'crypt://' . $path_f, $meta['mode'] ); } elseif ( self::shouldEncrypt( $path ) and $meta ['mode'] != 'r' and $meta['mode'] != 'rb' ) { $result = fopen( 'crypt://' . $path_f, $meta['mode'] ); } // Re-enable the proxy \OC_FileProxy::$enabled = $proxyStatus; return $result; } /** * @param $path * @param $data * @return array */ public function postGetFileInfo( $path, $data ) { // if path is a folder do nothing if ( is_array( $data ) && array_key_exists( 'size', $data ) ) { // Disable encryption proxy to prevent recursive calls $proxyStatus = \OC_FileProxy::$enabled; \OC_FileProxy::$enabled = false; // get file size $data['size'] = self::postFileSize( $path, $data['size'] ); // Re-enable the proxy \OC_FileProxy::$enabled = $proxyStatus; } return $data; } /** * @param $path * @param $size * @return bool */ public function postFileSize( $path, $size ) { $view = new \OC_FilesystemView( '/' ); // if path is a folder do nothing if ( $view->is_dir( $path ) ) { return $size; } // Reformat path for use with OC_FSV $path_split = explode( '/', $path ); $path_f = implode( '/', array_slice( $path_split, 3 ) ); // if path is empty we cannot resolve anything if ( empty( $path_f ) ) { return $size; } $fileInfo = false; // get file info from database/cache if not .part file if ( !Keymanager::isPartialFilePath( $path ) ) { $fileInfo = $view->getFileInfo( $path ); } // if file is encrypted return real file size if ( is_array( $fileInfo ) && $fileInfo['encrypted'] === true ) { $size = $fileInfo['unencrypted_size']; } else { // self healing if file was removed from file cache if ( !is_array( $fileInfo ) ) { $fileInfo = array(); } $userId = \OCP\User::getUser(); $util = new Util( $view, $userId ); $fixSize = $util->getFileSize( $path ); if ( $fixSize > 0 ) { $size = $fixSize; $fileInfo['encrypted'] = true; $fileInfo['unencrypted_size'] = $size; // put file info if not .part file if ( !Keymanager::isPartialFilePath( $path_f ) ) { $view->putFileInfo( $path, $fileInfo ); } } } return $size; } /** * @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 Session( $view ); $userId = \OCP\User::getUser(); $util = new Util( $view, $userId ); // Reformat path for use with OC_FSV $path_split = explode( '/', $path ); $path_f = implode( '/', array_slice( $path_split, 3 ) ); // only if file is on 'files' folder fix file size and sharing if ( count($path_split) >= 2 && $path_split[2] == 'files' && $util->fixFileSize( $path ) ) { // get sharing app state $sharingEnabled = \OCP\Share::isEnabled(); // get users $usersSharing = $util->getSharingUsersArray( $sharingEnabled, $path_f ); // update sharing-keys $util->setSharedFileKeyfiles( $session, $usersSharing, $path_f ); } \OC_FileProxy::$enabled = $proxyStatus; } }