From f1cbb9effc7e0672dd9dd6fa810aba36c5749898 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Thu, 24 Nov 2011 01:44:54 +0100 Subject: [PATCH] initial integration of encryption --- apps/files_encryption/appinfo/info.xml | 11 +++ {lib => apps/files_encryption/lib}/crypt.php | 89 +++++++++++-------- apps/files_encryption/lib/cryptstream.php | 49 +++++++++-- apps/files_encryption/lib/proxy.php | 91 ++++++++++++++++---- lib/user.php | 3 +- 5 files changed, 178 insertions(+), 65 deletions(-) create mode 100644 apps/files_encryption/appinfo/info.xml rename {lib => apps/files_encryption/lib}/crypt.php (68%) diff --git a/apps/files_encryption/appinfo/info.xml b/apps/files_encryption/appinfo/info.xml new file mode 100644 index 0000000000..a8db06aa3d --- /dev/null +++ b/apps/files_encryption/appinfo/info.xml @@ -0,0 +1,11 @@ + + + files_encryption + Encryption + Server side encryption of files + 0.1 + AGPL + Robin Appelman + 3 + + diff --git a/lib/crypt.php b/apps/files_encryption/lib/crypt.php similarity index 68% rename from lib/crypt.php rename to apps/files_encryption/lib/crypt.php index 3e6fa05b85..4e7739b1d0 100644 --- a/lib/crypt.php +++ b/apps/files_encryption/lib/crypt.php @@ -37,20 +37,42 @@ require_once('Crypt_Blowfish/Blowfish.php'); * This class is for crypting and decrypting */ class OC_Crypt { + static private $bf = null; - static $encription_extension='.encrypted'; + public static function loginListener($params){ + self::init($params['uid'],$params['password']); + } public static function init($login,$password) { - $_SESSION['user_password'] = $password; // save the password as passcode for the encryption if(OC_User::isLoggedIn()){ - // does key exist? - if(!file_exists(OC_Config::getValue( "datadirectory").'/'.$login.'/encryption.key')){ - OC_Crypt::createkey($_SESSION['user_password']); + $view=new OC_FilesystemView('/'.$login); + if(!$view->file_exists('/encryption.key')){// does key exist? + OC_Crypt::createkey($password); } + $key=$view->file_get_contents('/encryption.key'); + $_SESSION['enckey']=OC_Crypt::decrypt($key, $password); } } - + /** + * get the blowfish encryption handeler for a key + * @param string $key (optional) + * + * if the key is left out, the default handeler will be used + */ + public static function getBlowfish($key=''){ + if($key){ + return new Crypt_Blowfish($key); + }else{ + if(!isset($_SESSION['enckey'])){ + return false; + } + if(!self::$bf){ + self::$bf=new Crypt_Blowfish($_SESSION['enckey']); + } + return self::$bf; + } + } public static function createkey($passcode) { if(OC_User::isLoggedIn()){ @@ -61,57 +83,58 @@ class OC_Crypt { $enckey=OC_Crypt::encrypt($key,$passcode); // Write the file - $username=OC_USER::getUser(); - @file_put_contents(OC_Config::getValue( "datadirectory").'/'.$username.'/encryption.key', $enckey ); + $username=OC_USER::getUser(); + OC_FileProxy::$enabled=false; + $view=new OC_FilesystemView('/'.$username); + $view->file_put_contents('/encryption.key',$enckey); + OC_FileProxy::$enabled=true; } } - public static function changekeypasscode( $newpasscode) { + public static function changekeypasscode($oldPassword, $newPassword) { if(OC_User::isLoggedIn()){ - $username=OC_USER::getUser(); + $username=OC_USER::getUser(); + $view=new OC_FilesystemView('/'.$username); // read old key - $key=file_get_contents(OC_Config::getValue( "datadirectory").'/'.$username.'/encryption.key'); + $key=$view->file_get_contents('/encryption.key'); // decrypt key with old passcode - $key=OC_Crypt::decrypt($key, $_SESSION['user_password']); + $key=OC_Crypt::decrypt($key, $oldPassword); // encrypt again with new passcode - $key=OC_Crypt::encrypt($key,$newpassword); + $key=OC_Crypt::encrypt($key, $newPassword); // store the new key - file_put_contents(OC_Config::getValue( "datadirectory").'/'.$username.'/encryption.key', $key ); - - $_SESSION['user_password']=$newpasscode; + $view->file_put_contents('/encryption.key', $key ); } } /** * @brief encrypts an content * @param $content the cleartext message you want to encrypt - * @param $key the encryption key + * @param $key the encryption key (optional) * @returns encrypted content * * This function encrypts an content */ - public static function encrypt( $content, $key) { - $bf = new Crypt_Blowfish($key); + public static function encrypt( $content, $key='') { + $bf = self::getBlowfish($key); return($bf->encrypt($content)); } - - /** - * @brief decryption of an content - * @param $content the cleartext message you want to decrypt - * @param $key the encryption key - * @returns cleartext content - * - * This function decrypts an content - */ - public static function decrypt( $content, $key) { - $bf = new Crypt_Blowfish($key); + /** + * @brief decryption of an content + * @param $content the cleartext message you want to decrypt + * @param $key the encryption key (optional) + * @returns cleartext content + * + * This function decrypts an content + */ + public static function decrypt( $content, $key='') { + $bf = self::getBlowfish($key); return($bf->encrypt($contents)); - } + } /** * @brief encryption of a file @@ -181,8 +204,4 @@ class OC_Crypt { } return $result; } - - - - } diff --git a/apps/files_encryption/lib/cryptstream.php b/apps/files_encryption/lib/cryptstream.php index 7fbfeaa7a8..00dda7352b 100644 --- a/apps/files_encryption/lib/cryptstream.php +++ b/apps/files_encryption/lib/cryptstream.php @@ -22,19 +22,35 @@ /** * 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 public function stream_open($path, $mode, $options, &$opened_path){ $path=str_replace('crypt://','',$path); - OC_Log::write('files_encryption','open encrypted '.$path. ' in '.$mode,OC_Log::DEBUG); - OC_FileProxy::$enabled=false;//disable fileproxies so we can open the source file - $this->source=OC_FileSystem::fopen($path,$mode); - OC_FileProxy::$enabled=true; - if(!is_resource($this->source)){ - OC_Log::write('files_encryption','failed to open '.$path,OC_Log::ERROR); + if(dirname($path)=='streams' and isset(self::$sourceStreams[basename($path)])){ + $this->source=self::$sourceStreams[basename($path)]['stream']; + $this->path=self::$sourceStreams[basename($path)]['path']; + }else{ + $this->path=$path; + OC_Log::write('files_encryption','open encrypted '.$path. ' in '.$mode,OC_Log::DEBUG); + OC_FileProxy::$enabled=false;//disable fileproxies so we can open the source file + $this->source=OC_FileSystem::fopen($path,$mode); + OC_FileProxy::$enabled=true; + if(!is_resource($this->source)){ + OC_Log::write('files_encryption','failed to open '.$path,OC_Log::ERROR); + } + } + if(is_resource($this->source)){ + $this->meta=stream_get_meta_data($this->source); } return is_resource($this->source); } @@ -51,14 +67,26 @@ class OC_CryptStream{ $pos=0; $currentPos=ftell($this->source); $offset=$currentPos%8192; - fseek($this->source,-$offset,SEEK_CUR); $result=''; + if($offset>0){ + if($this->meta['seekable']){ + fseek($this->source,-$offset,SEEK_CUR);//if seeking isnt supported the internal read buffer will be used + }else{ + $pos=strlen($this->readBuffer); + $result=$this->readBuffer; + } + } while($count>$pos){ $data=fread($this->source,8192); $pos+=8192; - $result.=OC_Crypt::decrypt($data); + if(strlen($data)){ + $result.=OC_Crypt::decrypt($data); + } } - return substr($result,$offset,$count); + if(!$this->meta['seekable']){ + $this->readBuffer=substr($result,$count); + } + return substr($result,0,$count); } public function stream_write($data){ @@ -119,6 +147,9 @@ class OC_CryptStream{ } public function stream_close(){ + if(OC_FileCache::inCache($this->path)){ + OC_FileCache::put($this->path,array('encrypted'=>true)); + } return fclose($this->source); } } \ No newline at end of file diff --git a/apps/files_encryption/lib/proxy.php b/apps/files_encryption/lib/proxy.php index 053ac786c3..173aea785e 100644 --- a/apps/files_encryption/lib/proxy.php +++ b/apps/files_encryption/lib/proxy.php @@ -26,38 +26,98 @@ */ class OC_FileProxy_Encryption extends OC_FileProxy{ + private static $blackList=null; //mimetypes blacklisted from encryption + private static $metaData=array(); //metadata cache + + /** + * check if a file should be encrypted during write + * @param string $path + * @return bool + */ + private static function shouldEncrypt($path){ + if(is_null(self::$blackList)){ + self::$blackList=explode(',',OC_Appconfig::getValue('files_encryption','type_blacklist','jpg,png,jpeg,avi,mpg,mpeg,mkv,mp3,oga,ogv,ogg')); + } + if(isset(self::$metaData[$path])){ + $metadata=self::$metaData[$path]; + }else{ + $metadata=OC_FileCache::get($path); + self::$metaData[$path]=$metadata; + } + if($metadata['encrypted']){ + return true; + } + $extention=substr($path,strrpos($path,'.')+1); + if(array_search($extention,self::$blackList)===false){ + return true; + } + } + + /** + * check if a file is encrypted + * @param string $path + * @return bool + */ + private static function isEncrypted($path){ + if(isset(self::$metaData[$path])){ + $metadata=self::$metaData[$path]; + }else{ + $metadata=OC_FileCache::get($path); + self::$metaData[$path]=$metadata; + } + return (bool)$metadata['encrypted']; + } + public function preFile_put_contents($path,&$data){ - if(substr($path,-4)=='.enc'){ + if(self::shouldEncrypt($path)){ + $exists=OC_Filesystem::file_exists($path); + $target=fopen('crypt://'.$path,'w'); if (is_resource($data)) { - $newData=''; while(!feof($data)){ - $block=fread($data,8192); - $newData.=OC_Crypt::encrypt($block); + fwrite($target,fread($data,8192)); } - $data=$newData; }else{ - $data=OC_Crypt::blockEncrypt($data); + fwrite($target,$data); } + //fake the normal hooks + OC_Hook::emit( 'OC_Filesystem', 'post_create', array( 'path' => $path)); + OC_Hook::emit( 'OC_Filesystem', 'post_write', array( 'path' => $path)); + return false; } } public function postFile_get_contents($path,$data){ - if(substr($path,-4)=='.enc'){ - return OC_Crypt::blockDecrypt($data); + if(self::isEncrypted($path)){ + $data=OC_Crypt::blockDecrypt($data); } + return $data; } public function postFopen($path,&$result){ - if(substr($path,-4)=='.enc'){ - $meta=stream_get_meta_data($result); + if(!$result){ + return $result; + } + $meta=stream_get_meta_data($result); + if(self::isEncrypted($path)){ fclose($result); - OC_log::write('file_encryption','mode: '.$meta['mode']); + $result=fopen('crypt://'.$path,$meta['mode']); + }elseif(self::shouldEncrypt($path) and $meta['mode']!='r'){ + if(OC_Filesystem::file_exists($path)){ + //first encrypt the target file so we don't end up with a half encrypted file + OC_Log::write('files_encryption','Decrypting '.$path.' before writing',OC_Log::DEBUG); + if($result){ + fclose($result); + } + $tmpFile=OC_Filesystem::toTmpFile($path); + OC_Filesystem::fromTmpFile($tmpFile,$path); + } $result=fopen('crypt://'.$path,$meta['mode']); } + return $result; } public function preReadFile($path){ - if(substr($path,-4)=='.enc'){ + if(self::isEncrypted($path)){ $stream=fopen('crypt://'.$path,'r'); while(!feof($stream)){ print(fread($stream,8192)); @@ -65,11 +125,4 @@ class OC_FileProxy_Encryption extends OC_FileProxy{ return false;//cancel the original request } } - - public function postGetMimeType($path,$result){ - if(substr($path,-4)=='.enc'){ - return 'text/plain'; - } - return $result; - } } diff --git a/lib/user.php b/lib/user.php index 34f44f572e..aa828de52f 100644 --- a/lib/user.php +++ b/lib/user.php @@ -195,8 +195,8 @@ class OC_User { if( $run ){ $uid=self::checkPassword( $uid, $password ); if($uid){ - OC_Crypt::init($uid,$password); return self::setUserId($uid); + OC_Hook::emit( "OC_User", "post_login", array( "uid" => $uid, 'password'=>$password )); } } return false; @@ -209,7 +209,6 @@ class OC_User { */ public static function setUserId($uid) { $_SESSION['user_id'] = $uid; - OC_Hook::emit( "OC_User", "post_login", array( "uid" => $uid )); return true; }