From 59ca312263d358c95b950266c678c71bf97716f3 Mon Sep 17 00:00:00 2001 From: Sam Tuke Date: Wed, 23 Jan 2013 19:24:26 +0000 Subject: [PATCH] Work on util: findFiles() and encryptAll(); both close to working Ecnryption unit tests are failing, recursion in filecache{} --- apps/files_encryption/appinfo/app.php | 6 +- apps/files_encryption/lib/crypt.php | 4 +- apps/files_encryption/lib/keymanager.php | 112 +++++++------ apps/files_encryption/lib/proxy.php | 10 +- apps/files_encryption/lib/util.php | 157 +++++++++++++----- .../templates/settings-personal.php | 13 +- apps/files_encryption/templates/settings.php | 63 +------ apps/files_encryption/test/crypt.php | 6 +- apps/files_encryption/test/util.php | 23 ++- 9 files changed, 213 insertions(+), 181 deletions(-) diff --git a/apps/files_encryption/appinfo/app.php b/apps/files_encryption/appinfo/app.php index cc78402d1d..fa293f4512 100644 --- a/apps/files_encryption/appinfo/app.php +++ b/apps/files_encryption/appinfo/app.php @@ -32,7 +32,7 @@ if ( && OCA\Encryption\Crypt::mode() == 'server' ) { - // Force the user to re-log in if the encryption key isn't unlocked + // Force the user to log-in again if the encryption key isn't unlocked // (happens when a user is logged in before the encryption app is // enabled) OCP\User::logout(); @@ -44,4 +44,6 @@ if ( } OCP\App::registerAdmin( 'files_encryption', 'settings' ); -OCP\App::registerPersonal( 'files_encryption', 'settings-personal' ); \ No newline at end of file + +// This is disabled until client-side encryption is supported: +// OCP\App::registerPersonal( 'files_encryption', 'settings-personal' ); \ No newline at end of file diff --git a/apps/files_encryption/lib/crypt.php b/apps/files_encryption/lib/crypt.php index 642187b910..2591e90227 100755 --- a/apps/files_encryption/lib/crypt.php +++ b/apps/files_encryption/lib/crypt.php @@ -130,7 +130,7 @@ class Crypt { * @return true / false * @note see also OCA\Encryption\Util->isEncryptedPath() */ - public static function isEncryptedContent( $content ) { + public static function isCatfile( $content ) { if ( !$content ) { @@ -192,7 +192,7 @@ class Crypt { $content and isset( $metadata['encrypted'] ) and $metadata['encrypted'] === true - and !self::isEncryptedContent( $content ) + and !self::isCatfile( $content ) ) { return true; diff --git a/apps/files_encryption/lib/keymanager.php b/apps/files_encryption/lib/keymanager.php index 61bc50721e..c6669081a2 100755 --- a/apps/files_encryption/lib/keymanager.php +++ b/apps/files_encryption/lib/keymanager.php @@ -146,10 +146,59 @@ class Keymanager { } + /** + * @brief store file encryption key + * + * @param string $path relative path of the file, including filename + * @param string $key + * @return bool true/false + * @note The keyfile is not encrypted here. Client code must + * asymmetrically encrypt the keyfile before passing it to this method + */ + public static function setFileKey( \OC_FilesystemView $view, $path, $userId, $catfile ) { + + $basePath = '/' . $userId . '/files_encryption/keyfiles'; + + $targetPath = self::keySetPreparation( $view, $path, $basePath, $userId ); + +// // update $keytarget and $userId if key belongs to a file shared by someone else +// $query = $dbClassName::prepare( "SELECT uid_owner, source, target FROM `*PREFIX*sharing` WHERE target = ? AND uid_shared_with = ?" ); +// +// $result = $query->execute( array ( '/'.$userId.'/files/'.$targetPath, $userId ) ); +// +// if ( $row = $result->fetchRow( ) ) { +// +// $targetPath = $row['source']; +// +// $targetPath_parts = explode( '/', $targetPath ); +// +// $userId = $targetPath_parts[1]; +// +// $rootview = new \OC_FilesystemView( '/' ); +// +// if ( ! $rootview->is_writable( $targetPath ) ) { +// +// \OC_Log::write( 'Encryption library', "File Key not updated because you don't have write access for the corresponding file", \OC_Log::ERROR ); +// +// return false; +// +// } +// +// $targetPath = str_replace( '/'.$userId.'/files/', '', $targetPath ); +// +// //TODO: check for write permission on shared file once the new sharing API is in place +// +// } + + // Save the keyfile in parallel directory + return $view->file_put_contents( $basePath . '/' . $targetPath . '.key', $catfile ); + + } + /** * @brief retrieve keyfile for an encrypted file * @param string file name - * @return string file key or false + * @return string file key or false on failure * @note The keyfile returned is asymmetrically encrypted. Decryption * of the keyfile must be performed by client code */ @@ -171,7 +220,17 @@ class Keymanager { // // } - return $view->file_get_contents( '/' . $userId . '/files_encryption/keyfiles/' . $filePath_f . '.key' ); + $catfilePath = '/' . $userId . '/files_encryption/keyfiles/' . $filePath_f . '.key'; + + if ( $view->file_exists( $catfilePath ) ) { + + return $view->file_get_contents( $catfilePath ); + + } else { + + return false; + + } } @@ -298,55 +357,6 @@ class Keymanager { } - /** - * @brief store file encryption key - * - * @param string $path relative path of the file, including filename - * @param string $key - * @return bool true/false - * @note The keyfile is not encrypted here. Client code must - * asymmetrically encrypt the keyfile before passing it to this method - */ - public static function setFileKey( \OC_FilesystemView $view, $path, $userId, $catfile ) { - - $basePath = '/' . $userId . '/files_encryption/keyfiles'; - - $targetPath = self::keySetPreparation( $view, $path, $basePath, $userId ); - -// // update $keytarget and $userId if key belongs to a file shared by someone else -// $query = $dbClassName::prepare( "SELECT uid_owner, source, target FROM `*PREFIX*sharing` WHERE target = ? AND uid_shared_with = ?" ); -// -// $result = $query->execute( array ( '/'.$userId.'/files/'.$targetPath, $userId ) ); -// -// if ( $row = $result->fetchRow( ) ) { -// -// $targetPath = $row['source']; -// -// $targetPath_parts = explode( '/', $targetPath ); -// -// $userId = $targetPath_parts[1]; -// -// $rootview = new \OC_FilesystemView( '/' ); -// -// if ( ! $rootview->is_writable( $targetPath ) ) { -// -// \OC_Log::write( 'Encryption library', "File Key not updated because you don't have write access for the corresponding file", \OC_Log::ERROR ); -// -// return false; -// -// } -// -// $targetPath = str_replace( '/'.$userId.'/files/', '', $targetPath ); -// -// //TODO: check for write permission on shared file once the new sharing API is in place -// -// } - - // Save the keyfile in parallel directory - return $view->file_put_contents( $basePath . '/' . $targetPath . '.key', $catfile ); - - } - /** * @brief change password of private encryption key * diff --git a/apps/files_encryption/lib/proxy.php b/apps/files_encryption/lib/proxy.php index 83c5e21c4b..6f00bd13b4 100644 --- a/apps/files_encryption/lib/proxy.php +++ b/apps/files_encryption/lib/proxy.php @@ -68,7 +68,7 @@ class Proxy extends \OC_FileProxy { } - if ( Crypt::isEncryptedContent( $path ) ) { + if ( Crypt::isCatfile( $path ) ) { return true; @@ -147,7 +147,7 @@ class Proxy extends \OC_FileProxy { // If data is a catfile if ( Crypt::mode() == 'server' - && Crypt::isEncryptedContent( $data ) + && Crypt::isCatfile( $data ) ) { $split = explode( '/', $path ); @@ -269,14 +269,14 @@ class Proxy extends \OC_FileProxy { } public function postGetMimeType($path,$mime){ - if( Crypt::isEncryptedContent($path)){ + if( Crypt::isCatfile($path)){ $mime = \OCP\Files::getMimeType('crypt://'.$path,'w'); } return $mime; } public function postStat($path,$data){ - if( Crypt::isEncryptedContent($path)){ + if( Crypt::isCatfile($path)){ $cached= \OC_FileCache_Cached::get($path,''); $data['size']=$cached['size']; } @@ -284,7 +284,7 @@ class Proxy extends \OC_FileProxy { } public function postFileSize($path,$size){ - if( Crypt::isEncryptedContent($path)){ + if( Crypt::isCatfile($path)){ $cached = \OC_FileCache_Cached::get($path,''); return $cached['size']; }else{ diff --git a/apps/files_encryption/lib/util.php b/apps/files_encryption/lib/util.php index 56e4d572c2..f70839e39f 100644 --- a/apps/files_encryption/lib/util.php +++ b/apps/files_encryption/lib/util.php @@ -94,6 +94,7 @@ class Util { private $view; // OC_FilesystemView object for filesystem operations + private $userId; // ID of the currently logged-in user private $pwd; // User Password private $client; // Client side encryption mode flag private $publicKeyDir; // Dir containing all public user keys @@ -108,6 +109,8 @@ class Util { $this->view = $view; $this->userId = $userId; $this->client = $client; + $this->userDir = '/' . $this->userId; + $this->userFilesDir = '/' . $this->userId . '/' . 'files'; $this->publicKeyDir = '/' . 'public-keys'; $this->encryptionDir = '/' . $this->userId . '/' . 'files_encryption'; $this->keyfilesPath = $this->encryptionDir . '/' . 'keyfiles'; @@ -120,7 +123,9 @@ class Util { public function ready() { if( - !$this->view->file_exists( $this->keyfilesPath ) + !$this->view->file_exists( $this->encryptionDir ) + or !$this->view->file_exists( $this->keyfilesPath ) + or !$this->view->file_exists( $this->shareKeysPath ) or !$this->view->file_exists( $this->publicKeyPath ) or !$this->view->file_exists( $this->privateKeyPath ) ) { @@ -141,6 +146,20 @@ class Util { */ public function setupServerSide( $passphrase = null ) { + // Create user dir + if( !$this->view->file_exists( $this->userDir ) ) { + + $this->view->mkdir( $this->userDir ); + + } + + // Create user files dir + if( !$this->view->file_exists( $this->userFilesDir ) ) { + + $this->view->mkdir( $this->userFilesDir ); + + } + // Create shared public key directory if( !$this->view->file_exists( $this->publicKeyDir ) ) { @@ -171,13 +190,13 @@ class Util { // Create user keypair if ( - !$this->view->file_exists( $this->publicKeyPath ) - or !$this->view->file_exists( $this->privateKeyPath ) + ! $this->view->file_exists( $this->publicKeyPath ) + or ! $this->view->file_exists( $this->privateKeyPath ) ) { // Generate keypair $keypair = Crypt::createKeypair(); - + \OC_FileProxy::$enabled = false; // Save public key @@ -193,52 +212,71 @@ class Util { } + $publicKey = Keymanager::getPublicKey( $this->view, $this->userId ); + + // Encrypt existing user files: + $this->encryptAll( $publicKey, $this->userFilesDir ); + return true; } - public function findFiles( $directory, $type = 'plain' ) { - - # TODO: test finding non plain content + /** + * @brief Find all files and their encryption status within a directory + * @param string $directory The path of the parent directory to search + * @return mixed false if 0 found, array on success. Keys: name, path + */ + public function findFiles( $directory ) { + + // Disable proxy - we don't want files to be decrypted before + // we handle them + \OC_FileProxy::$enabled = false; + + $found = array( 'plain' => array(), 'encrypted' => array(), 'legacy' => array() ); + + if ( + $this->view->is_dir( $directory ) + && $handle = $this->view->opendir( $directory ) + ) { - if ( $handle = $this->view->opendir( $directory ) ) { - while ( false !== ( $file = readdir( $handle ) ) ) { - + if ( $file != "." && $file != ".." ) { - + $filePath = $directory . '/' . $this->view->getRelativePath( '/' . $file ); - var_dump($filePath); - + // If the path is a directory, search + // its contents if ( $this->view->is_dir( $filePath ) ) { $this->findFiles( $filePath ); - + + // If the path is a file, determine + // its encryption status } elseif ( $this->view->is_file( $filePath ) ) { - - if ( $type == 'plain' ) { - - $this->files[] = array( 'name' => $file, 'path' => $filePath ); - - } elseif ( $type == 'encrypted' ) { - if ( Crypt::isEncryptedContent( $this->view->file_get_contents( $filePath ) ) ) { - - $this->files[] = array( 'name' => $file, 'path' => $filePath ); - - } + // Disable proxies again, some- + // how they get re-enabled :/ + \OC_FileProxy::$enabled = false; - } elseif ( $type == 'legacy' ) { + // If the file is encrypted + if ( Keymanager::getFileKey( $this->view, $this->userId, $file ) ) { - if ( Crypt::isLegacyEncryptedContent( $this->view->file_get_contents( $filePath ) ) ) { + $found['encrypted'][] = array( 'name' => $file, 'path' => $filePath ); + + // If the file uses old + // encryption system + } elseif ( Crypt::isLegacyEncryptedContent( $this->view->file_get_contents( $filePath ) ) ) { - $this->files[] = array( 'name' => $file, 'path' => $filePath ); + $found['legacy'][] = array( 'name' => $file, 'path' => $filePath ); - } + // If the file is not encrypted + } else { + + $found['plain'][] = array( 'name' => $file, 'path' => $filePath ); } @@ -248,18 +286,22 @@ class Util { } - if ( !empty( $this->files ) ) { + \OC_FileProxy::$enabled = true; - return $this->files; + if ( empty( $found ) ) { + + return false; } else { - return false; + return $found; } } + \OC_FileProxy::$enabled = true; + return false; } @@ -278,22 +320,55 @@ class Util { \OC_FileProxy::$enabled = true; - return Crypt::isEncryptedContent( $data ); + return Crypt::isCatfile( $data ); } - public function encryptAll( $directory ) { + /** + * @brief Encrypt all files in a directory + * @param string $publicKey the public key to encrypt files with + * @param string $dirPath the directory whose files will be encrypted + * @note Encryption is recursive + */ + public function encryptAll( $publicKey, $dirPath ) { - $plainFiles = $this->findFiles( $this->view, 'plain' ); + if ( $found = $this->findFiles( $dirPath ) ) { - if ( $this->encryptFiles( $plainFiles ) ) { - - return true; + // Encrypt unencrypted files + foreach ( $found['plain'] as $plainFilePath ) { - } else { - - return false; + // Fetch data from file + $plainData = $this->view->file_get_contents( $plainFilePath ); + + // Encrypt data, generate catfile + $encrypted = Crypt::keyEncryptKeyfile( $plainData, $publicKey ); + + // Save catfile + Keymanager::setFileKey( $this->view, $plainFilePath, $this->userId, $encrypted['key'] ); + + // Overwrite the existing file with the encrypted one + $this->view->file_put_contents( $plainFilePath, $encrypted['data'] ); + } + + // FIXME: Legacy recrypting here isn't finished yet + // Encrypt legacy encrypted files + foreach ( $found['legacy'] as $legacyFilePath ) { + + // Fetch data from file + $legacyData = $this->view->file_get_contents( $legacyFilePath ); + + // Recrypt data, generate catfile + $recrypted = Crypt::legacyKeyRecryptKeyfile( $legacyData, $legacyPassphrase, $publicKey, $newPassphrase ); + + // Save catfile + Keymanager::setFileKey( $this->view, $plainFilePath, $this->userId, $recrypted['key'] ); + + // Overwrite the existing file with the encrypted one + $this->view->file_put_contents( $plainFilePath, $recrypted['data'] ); + + } + } } diff --git a/apps/files_encryption/templates/settings-personal.php b/apps/files_encryption/templates/settings-personal.php index 1274bd3bb5..c8144da563 100644 --- a/apps/files_encryption/templates/settings-personal.php +++ b/apps/files_encryption/templates/settings-personal.php @@ -9,17 +9,6 @@ value="" > - - /> - t('Client side encryption (most secure but makes it impossible to access your data from the web interface)'); ?> -
- /> - t('Server side encryption (allows you to access your files from the web interface and the desktop client)'); ?> + t('Server side encryption (allows you to access your files from the web interface)'); ?>
- - t('Choose encryption mode:'); ?> - - -

- - t('Important: Once you selected an encryption mode there is no way to change it back'); ?> - -

- -

- - /> - - t("Client side encryption (most secure but makes it impossible to access your data from the web interface)"); ?> -
- - - /> - - t('Server side encryption (allows you to access your files from the web interface and the desktop client)'); ?> -
- - - /> - - t('User specific (let the user decide)'); ?> -
- - - /> - - t('None (no encryption at all)'); ?> -
- -

t('Encryption'); ?> - t("Exclude the following file types from encryption"); ?> + t("Exclude the following file types from encryption:"); ?> +