summaryrefslogtreecommitdiffstats
path: root/apps/files_encryption/lib/proxy.php
diff options
context:
space:
mode:
Diffstat (limited to 'apps/files_encryption/lib/proxy.php')
-rw-r--r--apps/files_encryption/lib/proxy.php630
1 files changed, 353 insertions, 277 deletions
diff --git a/apps/files_encryption/lib/proxy.php b/apps/files_encryption/lib/proxy.php
index 55cddf2bec8..eaaeae9b619 100644
--- a/apps/files_encryption/lib/proxy.php
+++ b/apps/files_encryption/lib/proxy.php
@@ -1,41 +1,46 @@
<?php
/**
-* ownCloud
-*
-* @author Sam Tuke, Robin Appelman
-* @copyright 2012 Sam Tuke samtuke@owncloud.com, Robin Appelman
-* icewind1991@gmail.com
-*
-* This library is free software; you can redistribute it and/or
-* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
-* License as published by the Free Software Foundation; either
-* version 3 of the License, or any later version.
-*
-* This library is distributed in the hope that it will be useful,
-* but WITHOUT ANY WARRANTY; without even the implied warranty of
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-* GNU AFFERO GENERAL PUBLIC LICENSE for more details.
-*
-* You should have received a copy of the GNU Affero General Public
-* License along with this library. If not, see <http://www.gnu.org/licenses/>.
-*
-*/
+ * ownCloud
+ *
+ * @author Sam Tuke, Robin Appelman
+ * @copyright 2012 Sam Tuke samtuke@owncloud.com, Robin Appelman
+ * icewind1991@gmail.com
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
/**
-* @brief Encryption proxy which handles filesystem operations before and after
-* execution and encrypts, and handles keyfiles accordingly. Used for
-* webui.
-*/
+ * @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 extends \OC_FileProxy {
+/**
+ * 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
@@ -44,346 +49,417 @@ class Proxy extends \OC_FileProxy {
* 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'
+
+ 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', 'jpg,png,jpeg,avi,mpg,mpeg,mkv,mp3,oga,ogv,ogg' ) );
-
+
+ if ( is_null( self::$blackList ) ) {
+
+ self::$blackList = explode( ',', \OCP\Config::getAppValue( 'files_encryption', 'type_blacklist', '' ) );
+
}
-
- if ( Crypt::isCatfile( $path ) ) {
-
+
+ if ( Crypt::isCatfileContent( $path ) ) {
+
return true;
-
+
}
-
- $extension = substr( $path, strrpos( $path, '.' ) +1 );
-
+
+ $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 ) ) {
-
- if ( !is_resource( $data ) ) { //stream put contents should have been converted to fopen
-
+
+ // Stream put contents should have been converted to fopen
+ if ( !is_resource( $data ) ) {
+
$userId = \OCP\USER::getUser();
-
- $rootView = new \OC_FilesystemView( '/' );
-
+ $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;
-
- // TODO: Check if file is shared, if so, use multiKeyEncrypt
-
- // Encrypt plain data and fetch key
- $encrypted = Crypt::keyEncryptKeyfile( $data, Keymanager::getPublicKey( $rootView, $userId ) );
-
- // Replace plain content with encrypted content by reference
- $data = $encrypted['data'];
-
- $filePath = explode( '/', $path );
-
- $filePath = array_slice( $filePath, 3 );
-
- $filePath = '/' . implode( '/', $filePath );
-
- // TODO: make keyfile dir dynamic from app config
-
- $view = new \OC_FilesystemView( '/' );
-
+
+ // 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, $encrypted['key'] );
-
+ 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( $path, array( 'encrypted'=>true, 'size' => $size ), '' );
-
+ \OC\Files\Filesystem::putFileInfo( $filePath, array( 'encrypted' => true, 'size' => strlen( $data ), 'unencrypted_size' => $size ), '' );
+
// Re-enable proxy - our work is done
- \OC_FileProxy::$enabled = true;
-
+ \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 ) {
-
- // TODO: Use dependency injection to add required args for view and user etc. to this method
+
+ $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::isCatfile( $data )
+ if (
+ Crypt::mode() == 'server'
+ && Crypt::isCatfileContent( $data )
) {
-
- $split = explode( '/', $path );
-
- $filePath = array_slice( $split, 3 );
-
- $filePath = '/' . implode( '/', $filePath );
-
- //$cached = \OC\Files\Filesystem::getFileInfo( $path, '' );
-
- $view = new \OC_FilesystemView( '' );
-
- $userId = \OCP\USER::getUser();
-
- // TODO: Check if file is shared, if so, use multiKeyDecrypt
-
- $encryptedKeyfile = Keymanager::getFileKey( $view, $userId, $filePath );
-
- $session = new Session();
-
- $decrypted = Crypt::keyDecryptKeyfile( $data, $encryptedKeyfile, $session->getPrivateKey( $split[1] ) );
-
+
+ $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 )
+ Crypt::mode() == 'server'
+ && isset( $_SESSION['legacyenckey'] )
+ && Crypt::isEncryptedMeta( $path )
) {
-
- $decrypted = Crypt::legacyDecrypt( $data, $_SESSION['legacyenckey'] );
-
+ $plainData = Crypt::legacyDecrypt( $data, $session->getLegacyKey() );
}
-
- \OC_FileProxy::$enabled = true;
-
- if ( ! isset( $decrypted ) ) {
-
- $decrypted = $data;
-
+
+ \OC_FileProxy::$enabled = $proxyStatus;
+
+ if ( !isset( $plainData ) ) {
+
+ $plainData = $data;
+
}
-
- return $decrypted;
-
+
+ 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
- $trimmed = ltrim( $path, '/' );
- $split = explode( '/', $trimmed );
- $sliced = array_slice( $split, 2 );
- $relPath = implode( '/', $sliced );
-
- if ( $view->is_dir( $path ) ) {
-
- // Dirs must be handled separately as deleteFileKey
- // doesn't handle them
- $view->unlink( $userId . '/' . 'files_encryption' . '/' . 'keyfiles' . '/'. $relPath );
-
- } else {
-
- // Delete keyfile so it isn't orphaned
- $result = Keymanager::deleteFileKey( $view, $userId, $relPath );
-
- \OC_FileProxy::$enabled = true;
-
- return $result;
-
+ $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;
+
}
/**
- * @brief When a file is renamed, rename its keyfile also
- * @return bool Result of rename()
- * @note This is pre rather than post because using post didn't work
+ * @param $path
+ * @return bool
*/
- public function preRename( $oldPath, $newPath ) {
-
- // Disable encryption proxy to prevent recursive calls
- \OC_FileProxy::$enabled = false;
-
- $view = new \OC_FilesystemView( '/' );
-
- $userId = \OCP\USER::getUser();
-
- // Format paths to be relative to user files dir
- $oldTrimmed = ltrim( $oldPath, '/' );
- $oldSplit = explode( '/', $oldTrimmed );
- $oldSliced = array_slice( $oldSplit, 2 );
- $oldRelPath = implode( '/', $oldSliced );
- $oldKeyfilePath = $userId . '/' . 'files_encryption' . '/' . 'keyfiles' . '/' . $oldRelPath . '.key';
-
- $newTrimmed = ltrim( $newPath, '/' );
- $newSplit = explode( '/', $newTrimmed );
- $newSliced = array_slice( $newSplit, 2 );
- $newRelPath = implode( '/', $newSliced );
- $newKeyfilePath = $userId . '/' . 'files_encryption' . '/' . 'keyfiles' . '/' . $newRelPath . '.key';
-
- // Rename keyfile so it isn't orphaned
- $result = $view->rename( $oldKeyfilePath, $newKeyfilePath );
-
- \OC_FileProxy::$enabled = true;
-
- return $result;
-
+ public function postTouch( $path ) {
+ $this->handleFile( $path );
+
+ return true;
}
-
- public function postFopen( $path, &$result ){
-
+
+ /**
+ * @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 ) );
-
+ $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());
-
+
+ $util = new Util( $view, \OCP\USER::getUser() );
+
// If file is already encrypted, decrypt using crypto protocol
- if (
- Crypt::mode() == 'server'
- && $util->isEncryptedPath( $path )
+ 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'
+
+ } elseif (
+ self::shouldEncrypt( $path )
+ and $meta ['mode'] != 'r'
+ and $meta['mode'] != 'rb'
) {
- // If the file is not yet encrypted, but should be
- // encrypted when it's saved (it's not read only)
-
- // NOTE: this is the case for new files saved via WebDAV
-
- if (
- $view->file_exists( $path )
- and $view->filesize( $path ) > 0
- ) {
- $x = $view->file_get_contents( $path );
-
- $tmp = tmpfile();
-
-// // Make a temporary copy of the original file
-// \OCP\Files::streamCopy( $result, $tmp );
-//
-// // Close the original stream, we'll return another one
-// fclose( $result );
-//
-// $view->file_put_contents( $path_f, $tmp );
-//
-// fclose( $tmp );
-
- }
-
- $result = fopen( 'crypt://'.$path_f, $meta['mode'] );
-
+ $result = fopen( 'crypt://' . $path_f, $meta['mode'] );
}
-
+
// Re-enable the proxy
- \OC_FileProxy::$enabled = true;
-
+ \OC_FileProxy::$enabled = $proxyStatus;
+
return $result;
-
- }
- public function postGetMimeType( $path, $mime ) {
-
- if ( Crypt::isCatfile( $path ) ) {
-
- $mime = \OCP\Files::getMimeType( 'crypt://' . $path, 'w' );
-
- }
-
- return $mime;
-
}
- public function postStat( $path, $data ) {
-
- if ( Crypt::isCatfile( $path ) ) {
-
- $cached = \OC\Files\Filesystem::getFileInfo( $path, '' );
-
- $data['size'] = $cached['size'];
-
+ /**
+ * @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 ) {
-
- if ( Crypt::isCatfile( $path ) ) {
-
- $cached = \OC\Files\Filesystem::getFileInfo( $path, '' );
-
- return $cached['size'];
-
- } else {
-
+
+ $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;
}
}