diff options
author | Sam Tuke <samtuke@owncloud.com> | 2012-08-16 19:18:18 +0100 |
---|---|---|
committer | Sam Tuke <samtuke@owncloud.com> | 2012-08-16 19:18:18 +0100 |
commit | 293a0f4d3229ab6737b3625d7ceb0718ef6dea00 (patch) | |
tree | a5c339f4c0732c663fb1ef112e2e480a652bf2ba /apps | |
parent | f11f524dfa17071dbabb2f950680966867f262a6 (diff) | |
download | nextcloud-server-293a0f4d3229ab6737b3625d7ceb0718ef6dea00.tar.gz nextcloud-server-293a0f4d3229ab6737b3625d7ceb0718ef6dea00.zip |
Started rewrite of cryptstream class (renamed to stream)
Added unit tests
Fixed decryption of user private key at login
Added functionality to keymanager
Diffstat (limited to 'apps')
-rw-r--r-- | apps/files_encryption/appinfo/app.php | 4 | ||||
-rw-r--r-- | apps/files_encryption/lib/crypt.php | 41 | ||||
-rw-r--r-- | apps/files_encryption/lib/cryptstream.php | 208 | ||||
-rw-r--r-- | apps/files_encryption/lib/keymanager.php | 9 | ||||
-rw-r--r-- | apps/files_encryption/lib/stream.php | 264 | ||||
-rw-r--r-- | apps/files_encryption/lib/util.php | 2 | ||||
-rw-r--r-- | apps/files_encryption/tests/crypt.php | 50 |
7 files changed, 361 insertions, 217 deletions
diff --git a/apps/files_encryption/appinfo/app.php b/apps/files_encryption/appinfo/app.php index 4fd9c37ed30..dd95a1f0944 100644 --- a/apps/files_encryption/appinfo/app.php +++ b/apps/files_encryption/appinfo/app.php @@ -4,7 +4,7 @@ OC::$CLASSPATH['OCA_Encryption\Crypt'] = 'apps/files_encryption/lib/crypt.php'; OC::$CLASSPATH['OCA_Encryption\Hooks'] = 'apps/files_encryption/hooks/hooks.php'; OC::$CLASSPATH['OCA_Encryption\Util'] = 'apps/files_encryption/lib/util.php'; OC::$CLASSPATH['OCA_Encryption\Keymanager'] = 'apps/files_encryption/lib/keymanager.php'; -OC::$CLASSPATH['OC_CryptStream'] = 'apps/files_encryption/lib/cryptstream.php'; +OC::$CLASSPATH['OCA_Encryption\Stream'] = 'apps/files_encryption/lib/stream.php'; OC::$CLASSPATH['OCA_Encryption\Proxy'] = 'apps/files_encryption/lib/proxy.php'; OC_FileProxy::register(new OCA_Encryption\Proxy()); @@ -12,7 +12,7 @@ OC_FileProxy::register(new OCA_Encryption\Proxy()); OCP\Util::connectHook('OC_User','post_login','OCA_Encryption\Hooks','login'); OCP\Util::connectHook('OC_Webdav_Properties', 'update', 'OCA_Encryption\Hooks', 'updateKeyfile'); -stream_wrapper_register('crypt','OC_CryptStream'); +stream_wrapper_register( 'crypt', 'OCA_Encryption\Stream'); if( !isset( $_SESSION['enckey'] ) && OCP\User::isLoggedIn() && OCA_Encryption\Crypt::mode() == 'server' ) { diff --git a/apps/files_encryption/lib/crypt.php b/apps/files_encryption/lib/crypt.php index 07230fe8a24..fa7287a736b 100644 --- a/apps/files_encryption/lib/crypt.php +++ b/apps/files_encryption/lib/crypt.php @@ -391,6 +391,47 @@ class Crypt { }
+ /**
+ * @brief Symmetrically encrypt a file by combining encrypted component data blocks
+ */
+ public static function symmetricBlockEncryptFileContent( $plainContent, $key ) {
+
+ $crypted = '';
+
+ while( strlen( $plainContent ) ) {
+
+ // Encrypt a chunk of unencrypted data and add it to the rest
+ $crypted .= self::symmetricEncryptFileContent( substr( $plainContent, 0, 8192 ), $key );
+
+ // Remove the data already encrypted from remaining unencrypted data
+ $plainContent = substr( $plainContent, 8192 );
+
+ }
+
+ return $crypted;
+
+ }
+
+
+ /**
+ * @brief Symmetrically decrypt a file by combining encrypted component data blocks
+ */
+ public static function symmetricBlockDecryptFileContent( $crypted, $key ) {
+
+ $decrypted = '';
+
+ while( strlen( $crypted ) ) {
+
+ $decrypted .= self::symmetricDecryptFileContent( substr( $crypted, 0, 8192 ), $key );
+
+ $crypted = substr( $crypted, 8192 );
+
+ }
+
+ return rtrim( $decrypted, "\0" );
+
+ }
+
/**
* @brief Generate a pseudo random 1024kb ASCII key
* @returns $key Generated key
diff --git a/apps/files_encryption/lib/cryptstream.php b/apps/files_encryption/lib/cryptstream.php deleted file mode 100644 index 8c61c933cf8..00000000000 --- a/apps/files_encryption/lib/cryptstream.php +++ /dev/null @@ -1,208 +0,0 @@ -<?php -/** - * ownCloud - * - * @author Robin Appelman - * @copyright 2011 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/>. - * - */ - -/** - * transparently encrypted filestream - * - * you can use it as wrapper around an existing stream by setting OC_CryptStream::$sourceStreams['foo']=array('path'=>$path,'stream'=>$stream) - * and then fopen('crypt://streams/foo'); - */ - -class OC_CryptStream{ - public static $sourceStreams = array(); - private $source; - private $path; - private $readBuffer; // For streams that dont support seeking - private $meta = array(); // Header / meta for source stream - private $count; - private $writeCache; - private $size; - private static $rootView; - - public function stream_open($path, $mode, $options, &$opened_path){ - if(!self::$rootView){ - self::$rootView=new OC_FilesystemView(''); - } - $path=str_replace('crypt://','',$path); - if(dirname($path)=='streams' and isset(self::$sourceStreams[basename($path)])){ - $this->source=self::$sourceStreams[basename($path)]['stream']; - $this->path=self::$sourceStreams[basename($path)]['path']; - $this->size=self::$sourceStreams[basename($path)]['size']; - }else{ - $this->path=$path; - if($mode=='w' or $mode=='w+' or $mode=='wb' or $mode=='wb+'){ - $this->size=0; - }else{ - $this->size=self::$rootView->filesize($path,$mode); - } - OC_FileProxy::$enabled=false;//disable fileproxies so we can open the source file - $this->source=self::$rootView->fopen($path,$mode); - OC_FileProxy::$enabled=true; - if(!is_resource($this->source)){ - OCP\Util::writeLog('files_encryption','failed to open '.$path,OCP\Util::ERROR); - } - } - if(is_resource($this->source)){ - $this->meta=stream_get_meta_data($this->source); - } - return is_resource($this->source); - } - - public function stream_seek($offset, $whence=SEEK_SET){ - $this->flush(); - fseek($this->source,$offset,$whence); - } - - public function stream_tell(){ - return ftell($this->source); - } - - public function stream_read($count){ - //$count will always be 8192 https://bugs.php.net/bug.php?id=21641 - //This makes this function a lot simpler but will breake everything the moment it's fixed - $this->writeCache=''; - if($count!=8192){ - OCP\Util::writeLog('files_encryption','php bug 21641 no longer holds, decryption will not work',OCP\Util::FATAL); - die(); - } - $pos=ftell($this->source); - $data=fread($this->source,8192); - if(strlen($data)){ - $result=OC_Crypt::decrypt($data); - }else{ - $result=''; - } - $length=$this->size-$pos; - if($length<8192){ - $result=substr($result,0,$length); - } - return $result; - } - - public function stream_write( $data ){ - - $length = strlen( $data ); - - $written = 0; - - $currentPos = ftell( $this->source ); - - if( $this->writeCache ){ - - $data = $this->writeCache.$data; - - $this->writeCache = ''; - - } - - if( $currentPos%8192 != 0 ){ - - //make sure we always start on a block start - - fseek( $this->source,-( $currentPos%8192 ),SEEK_CUR ); - - $encryptedBlock = fread( $this->source,8192 ); - - fseek( $this->source,-( $currentPos%8192 ),SEEK_CUR ); - - $block = OC_Crypt::decrypt( $encryptedBlock ); - - $data = substr( $block,0,$currentPos%8192 ).$data; - - fseek( $this->source,-( $currentPos%8192 ),SEEK_CUR ); - - } - - $currentPos = ftell( $this->source ); - - while( $remainingLength = strlen( $data )>0 ){ - - if( $remainingLength<8192 ){ - - $this->writeCache = $data; - - $data = ''; - - }else{ - - $encrypted = OC_Crypt::encrypt( substr( $data,0,8192 ) ); - - fwrite( $this->source,$encrypted ); - - $data = substr( $data,8192 ); - - } - - } - - $this->size = max( $this->size,$currentPos+$length ); - - return $length; - - } - - - public function stream_set_option($option,$arg1,$arg2){ - switch($option){ - case STREAM_OPTION_BLOCKING: - stream_set_blocking($this->source,$arg1); - break; - case STREAM_OPTION_READ_TIMEOUT: - stream_set_timeout($this->source,$arg1,$arg2); - break; - case STREAM_OPTION_WRITE_BUFFER: - stream_set_write_buffer($this->source,$arg1,$arg2); - } - } - - public function stream_stat(){ - return fstat($this->source); - } - - public function stream_lock($mode){ - flock($this->source,$mode); - } - - public function stream_flush(){ - return fflush($this->source); - } - - public function stream_eof(){ - return feof($this->source); - } - - private function flush(){ - if($this->writeCache){ - $encrypted=OC_Crypt::encrypt($this->writeCache); - fwrite($this->source,$encrypted); - $this->writeCache=''; - } - } - - public function stream_close(){ - $this->flush(); - if($this->meta['mode']!='r' and $this->meta['mode']!='rb'){ - OC_FileCache::put($this->path,array('encrypted'=>true,'size'=>$this->size),''); - } - return fclose($this->source); - } -} diff --git a/apps/files_encryption/lib/keymanager.php b/apps/files_encryption/lib/keymanager.php index b06226397e8..26101b8356c 100644 --- a/apps/files_encryption/lib/keymanager.php +++ b/apps/files_encryption/lib/keymanager.php @@ -120,10 +120,10 @@ class Keymanager { * @param string file name
* @return string file key or false
*/
- public static function getFileKey( $path ) {
+ public static function getFileKey( $path, $staticUserClass = 'OCP\User' ) {
$keypath = ltrim( $path, '/' );
- $user = \OCP\User::getUser();
+ $user = $staticUserClass::getUser();
// update $keypath and $user if path point to a file shared by someone else
$query = \OC_DB::prepare( "SELECT uid_owner, source, target FROM `*PREFIX*sharing` WHERE target = ? AND uid_shared_with = ?" );
@@ -140,6 +140,7 @@ class Keymanager { }
$view = new \OC_FilesystemView('/'.$user.'/files_encryption/keyfiles/');
+
return $view->file_get_contents( $keypath . '.key' );
}
@@ -227,9 +228,11 @@ class Keymanager { $path_parts = pathinfo( $targetpath );
if (!$view) {
- $view = new \OC_FilesystemView( '/' . $user . '/files_encryption/keyfiles' );
+ $view = new \OC_FilesystemView( '/' );
}
+ $view->chroot( '/' . $user . '/files_encryption/keyfiles' );
+
if ( !$view->file_exists( $path_parts['dirname'] ) ) $view->mkdir( $path_parts['dirname'] );
return $view->file_put_contents( '/' . $targetpath . '.key', $key );
diff --git a/apps/files_encryption/lib/stream.php b/apps/files_encryption/lib/stream.php new file mode 100644 index 00000000000..bdcfdfd73aa --- /dev/null +++ b/apps/files_encryption/lib/stream.php @@ -0,0 +1,264 @@ +<?php +/** + * ownCloud + * + * @author Robin Appelman + * @copyright 2011 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/>. + * + */ + +/** + * transparently encrypted filestream + * + * you can use it as wrapper around an existing stream by setting CryptStream::$sourceStreams['foo']=array('path'=>$path,'stream'=>$stream) + * and then fopen('crypt://streams/foo'); + */ + +namespace OCA_Encryption; + +class Stream { + + public static $sourceStreams = array(); + private $source; + private $path; + private $readBuffer; // For streams that dont support seeking + private $meta = array(); // Header / meta for source stream + private $count; + private $writeCache; + private $size; + private static $view; + + public function stream_open( $path, $mode, $options, &$opened_path ) { + + // Get access to filesystem via filesystemview object + if ( !self::$view ) { + + self::$view = new \OC_FilesystemView( '' ); + + } + + // Get the bare file path + $path = str_replace( 'crypt://', '', $path ); + + if ( + dirname( $path ) == 'streams' + and isset( self::$sourceStreams[basename( $path )] ) + ) { + + $this->source = self::$sourceStreams[basename( $path )]['stream']; + + $this->path = self::$sourceStreams[basename( $path )]['path']; + + $this->size = self::$sourceStreams[basename( $path )]['size']; + + } else { + + if ( + $mode == 'w' + or $mode == 'w+' + or $mode == 'wb' + or $mode == 'wb+' + ) { + + $this->size = 0; + + } else { + + $this->size = self::$view->filesize( $path, $mode ); + + } + + // Disable fileproxies so we can open the source file without recursive encryption + \OC_FileProxy::$enabled = false; + + $this->source = self::$view->fopen( $path, $mode ); + + \OC_FileProxy::$enabled = true; + + if ( !is_resource( $this->source ) ) { + + \OCP\Util::writeLog( 'files_encryption','failed to open '.$path,OCP\Util::ERROR ); + + } + + } + + if ( is_resource( $this->source ) ) { + + $this->meta = stream_get_meta_data( $this->source ); + + } + + return is_resource( $this->source ); + + } + + public function stream_seek($offset, $whence=SEEK_SET) { + $this->flush(); + fseek($this->source,$offset,$whence); + } + + public function stream_tell() { + return ftell($this->source); + } + + public function stream_read($count) { + //$count will always be 8192 https://bugs.php.net/bug.php?id=21641 + //This makes this function a lot simpler but will breake everything the moment it's fixed + $this->writeCache=''; + if ($count!=8192) { + OCP\Util::writeLog('files_encryption','php bug 21641 no longer holds, decryption will not work',OCP\Util::FATAL); + die(); + } + $pos=ftell($this->source); + $data=fread($this->source,8192); + if (strlen($data)) { + $result=Crypt::decrypt($data); + }else{ + $result=''; + } + $length=$this->size-$pos; + if ($length<8192) { + $result=substr($result,0,$length); + } + return $result; + } + + /** + * @brief + */ + public function stream_write( $data ) { + + $length = strlen( $data ); + + $written = 0; + + $currentPos = ftell( $this->source ); + + # TODO: Move this user call out of here - it belongs elsewhere + $user = \OCP\User::getUser(); + + if ( self::$view->file_exists( $this->path . $user ) ) { + + $key = Keymanager::getFileKey( $this->path . $user ); + + } else { + + $key = Crypt::generateKey(); + + Keymanager::setFileKey( $path, $key, new \OC_FilesystemView ); + + } + + if ( $this->writeCache ) { + + $data = $this->writeCache . $data; + + $this->writeCache = ''; + + } + + // Make sure we always start on a block start + if ( $currentPos % 8192 != 0 ) { + + fseek( $this->source, - ( $currentPos % 8192 ), SEEK_CUR ); + + $encryptedBlock = fread( $this->source, 8192 ); + + fseek( $this->source, - ( $currentPos % 8192 ), SEEK_CUR ); + + $block = Crypt::symmetricDecryptFileContent( $encryptedBlock, $key ); + + $data = substr( $block, 0, $currentPos % 8192 ) . $data; + + fseek( $this->source, - ( $currentPos % 8192 ), SEEK_CUR ); + + } + + $currentPos = ftell( $this->source ); + + while( $remainingLength = strlen( $data )>0 ) { + + if ( $remainingLength<8192 ) { + + $this->writeCache = $data; + + $data = ''; + + } else { + + $encrypted = Crypt::symmetricBlockEncryptFileContent( $data, $key ); + + fwrite( $this->source . $user, $encrypted ); + + $data = substr( $data,8192 ); + + } + + } + + $this->size = max( $this->size, $currentPos + $length ); + + return $length; + + } + + + public function stream_set_option($option,$arg1,$arg2) { + switch($option) { + case STREAM_OPTION_BLOCKING: + stream_set_blocking($this->source,$arg1); + break; + case STREAM_OPTION_READ_TIMEOUT: + stream_set_timeout($this->source,$arg1,$arg2); + break; + case STREAM_OPTION_WRITE_BUFFER: + stream_set_write_buffer($this->source,$arg1,$arg2); + } + } + + public function stream_stat() { + return fstat($this->source); + } + + public function stream_lock($mode) { + flock($this->source,$mode); + } + + public function stream_flush() { + return fflush($this->source); + } + + public function stream_eof() { + return feof($this->source); + } + + private function flush() { + if ($this->writeCache) { + $encrypted=Crypt::encrypt($this->writeCache); + fwrite($this->source,$encrypted); + $this->writeCache=''; + } + } + + public function stream_close() { + $this->flush(); + if ($this->meta['mode']!='r' and $this->meta['mode']!='rb') { + OC_FileCache::put($this->path,array('encrypted'=>true,'size'=>$this->size),''); + } + return fclose($this->source); + } +} diff --git a/apps/files_encryption/lib/util.php b/apps/files_encryption/lib/util.php index b919c56a2eb..eab5b5edf5b 100644 --- a/apps/files_encryption/lib/util.php +++ b/apps/files_encryption/lib/util.php @@ -46,7 +46,7 @@ class Util { # DONE: add method to decrypt legacy encrypted data # DONE: fix / test the crypt stream proxy class - # TODO: replace cryptstream wrapper with stream_socket_enable_crypto, or fix it to use new crypt class methods + # TODO: replace cryptstream wrapper new AES based system # TODO: add support for optional recovery user 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. diff --git a/apps/files_encryption/tests/crypt.php b/apps/files_encryption/tests/crypt.php index 2802f32a58d..ef453ab90d6 100644 --- a/apps/files_encryption/tests/crypt.php +++ b/apps/files_encryption/tests/crypt.php @@ -21,6 +21,8 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { $this->legacyData = realpath( dirname(__FILE__).'/legacy-text.txt' ); $this->legacyEncryptedData = realpath( dirname(__FILE__).'/legacy-encrypted-text.txt' ); + //stream_wrapper_register( 'crypt', 'OCA_Encryption\Stream' ); + } function tearDown(){} @@ -73,16 +75,58 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { # TODO: search in keyfile for actual content as IV will ensure this test always passes - $keyfileContent = Crypt::symmetricEncryptFileContent( $this->data, 'hat' ); + $crypted = Crypt::symmetricEncryptFileContent( $this->data, 'hat' ); - $this->assertNotEquals( $this->data, $keyfileContent ); + $this->assertNotEquals( $this->data, $crypted ); - $decrypt = Crypt::symmetricDecryptFileContent( $keyfileContent, 'hat' ); + $decrypt = Crypt::symmetricDecryptFileContent( $crypted, 'hat' ); $this->assertEquals( $this->data, $decrypt ); } + + function testSymmetricBlockEncryptFileContent() { + + $crypted = Crypt::symmetricBlockEncryptFileContent( $this->data, 'hat' ); + + $this->assertNotEquals( $this->data, $crypted ); + + + $decrypt = Crypt::symmetricBlockDecryptFileContent( $crypted, 'hat' ); + + $this->assertEquals( $this->data, $decrypt ); + + } + +// function testSymmetricBlockStreamEncryptFileContent() { +// +// $crypted = Crypt::symmetricBlockEncryptFileContent( $this->data, 'hat' ); +// +// $cryptedFile = file_put_contents( 'crypt://' . '/blockEncrypt', $crypted ); +// +// // Test that data was successfully written +// $this->assertTrue( $cryptedFile ); +// +// $retreivedCryptedFile = file_get_contents( '/blockEncrypt' ); +// +// $this->assertNotEquals( $this->data, $retreivedCryptedFile ); +// +// } + + function testSymmetricBlockStreamDecryptFileContent() { + + \OC_User::setUserId( 'admin' ); + + $crypted = Crypt::symmetricBlockEncryptFileContent( $this->data, 'hat' ); + + $cryptedFile = file_put_contents( 'crypt://' . '/blockEncrypt', $crypted ); + + $retreivedCryptedFile = file_get_contents( 'crypt://' . '/blockEncrypt' ); + + $this->assertEquals( $this->data, $retreivedCryptedFile ); + + } function testSymmetricEncryptFileContentKeyfile() { |