From 6af99f3a099a7192c1f4864d5e9472cb69726060 Mon Sep 17 00:00:00 2001 From: Sam Tuke Date: Wed, 11 Jul 2012 17:51:27 +0100 Subject: Development snapshot: Rewrote crtpt class as Util, Hooks, and Crypt Switched blowfish for openssl with AES Added setup() method for creating user keys and directory structure Many other changes complete and in progress --- apps/files_encryption/lib/proxy.php | 79 +++++++++++++++++++++++++++---------- 1 file changed, 59 insertions(+), 20 deletions(-) (limited to 'apps/files_encryption/lib/proxy.php') diff --git a/apps/files_encryption/lib/proxy.php b/apps/files_encryption/lib/proxy.php index f25e4a662f6..e06242e29d4 100644 --- a/apps/files_encryption/lib/proxy.php +++ b/apps/files_encryption/lib/proxy.php @@ -21,6 +21,14 @@ * */ + +class OC_FileProxy_Encryption extends OC_FileProxy { + + + +} + + /** * transparent encryption */ @@ -30,45 +38,76 @@ class OC_FileProxy_Encryption extends OC_FileProxy{ private static $enableEncryption=null; /** - * check if a file should be encrypted during write + * Check if a file requires encryption * @param string $path * @return bool + * + * Tests if encryption is enabled, and file is allowed by blacklists */ - private static function shouldEncrypt($path){ - if(is_null(self::$enableEncryption)){ - self::$enableEncryption=(OCP\Config::getAppValue('files_encryption','enable_encryption','true')=='true'); + private static function shouldEncrypt( $path ) { + + if ( is_null( self::$enableEncryption ) ) { + + self::$enableEncryption = ( OCP\Config::getAppValue( 'files_encryption', 'enable_encryption', 'true' ) == 'true' ); + } - if(!self::$enableEncryption){ + + 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','jpg,png,jpeg,avi,mpg,mpeg,mkv,mp3,oga,ogv,ogg' ) ); + } - if(self::isEncrypted($path)){ + + if( self::isEncrypted( $path ) ) { + return true; + } - $extension=substr($path,strrpos($path,'.')+1); - if(array_search($extension,self::$blackList)===false){ + + $extension = substr( $path, strrpos( $path,'.' ) +1 ); + + if ( array_search( $extension, self::$blackList ) === false ){ + return true; + } + + return false; } /** - * check if a file is encrypted + * Check if a file is encrypted according to database file cache * @param string $path * @return bool */ - private static function isEncrypted($path){ - $metadata=OC_FileCache_Cached::get($path,''); - return isset($metadata['encrypted']) and (bool)$metadata['encrypted']; + private static function isEncrypted( $path ){ + + // Fetch all file metadata from DB + $metadata = OC_FileCache_Cached::get( $path, '' ); + + // Return encryption status + return isset( $metadata['encrypted'] ) and ( bool )$metadata['encrypted']; + } - public function preFile_put_contents($path,&$data){ - if(self::shouldEncrypt($path)){ - if (!is_resource($data)) {//stream put contents should have been converter to fopen - $size=strlen($data); - $data=OC_Crypt::blockEncrypt($data); - OC_FileCache::put($path,array('encrypted'=>true,'size'=>$size),''); + public function preFile_put_contents( $path, &$data ) { + + if ( self::shouldEncrypt( $path ) ) { + + if ( !is_resource( $data ) ) {//stream put contents should have been converter to fopen + + $size = strlen( $data ); + + $data = Crypt::blockEncrypt( $data ); + + OC_FileCache::put( $path, array( 'encrypted'=>true, 'size' => $size ), '' ); + } } } -- cgit v1.2.3 From d294e7772156dc27b6d69df405f7dcf7d7f4326f Mon Sep 17 00:00:00 2001 From: Sam Tuke Date: Wed, 18 Jul 2012 18:52:00 +0100 Subject: Development snapshot: - Added methods for sealing data with multiple keys - Added method for encrypting data, generating iv and keyfile, and returning both - Added 6 unit test cases (containing 12 tests) for Crypt class - Commented out old unit tests for now --- apps/files_encryption/lib/crypt.php | 124 ++++++++++++++-- apps/files_encryption/lib/proxy.php | 7 - apps/files_encryption/lib/util.php | 2 +- apps/files_encryption/tests/encryption.php | 214 ++++++++++++++++++++-------- apps/files_encryption/tests/proxy.php | 218 ++++++++++++++--------------- apps/files_encryption/tests/stream.php | 154 ++++++++++---------- 6 files changed, 458 insertions(+), 261 deletions(-) (limited to 'apps/files_encryption/lib/proxy.php') diff --git a/apps/files_encryption/lib/crypt.php b/apps/files_encryption/lib/crypt.php index e5bc3adcbc5..098074c228d 100644 --- a/apps/files_encryption/lib/crypt.php +++ b/apps/files_encryption/lib/crypt.php @@ -51,7 +51,7 @@ class Crypt { } /** - * @brief Symmetrically encrypt a file + * @brief Symmetrically encrypt a string * @returns encrypted file */ public static function encrypt( $plainContent, $iv, $passphrase = '' ) { @@ -62,7 +62,7 @@ class Crypt { } else { - \OC_Log::write( 'Encrypted storage', 'Encryption (symmetric) of content failed' , \OC_Log::ERROR ); + \OC_Log::write( 'Encryption library', 'Encryption (symmetric) of content failed' , \OC_Log::ERROR ); return false; @@ -71,7 +71,7 @@ class Crypt { } /** - * @brief Symmetrically decrypt a file + * @brief Symmetrically decrypt a string * @returns decrypted file */ public static function decrypt( $encryptedContent, $iv, $passphrase ) { @@ -83,7 +83,7 @@ class Crypt { } else { - \OC_Log::write( 'Encrypted storage', 'Decryption (symmetric) of content failed' , \OC_Log::ERROR ); + \OC_Log::write( 'Encryption library', 'Decryption (symmetric) of content failed' , \OC_Log::ERROR ); return false; @@ -92,7 +92,7 @@ class Crypt { } /** - * @brief Creates symmetric keyfile content + * @brief Symmetrically encrypts a string and returns keyfile content * @param $plainContent content to be encrypted in keyfile * @returns encrypted content combined with IV * @note IV need not be specified, as it will be stored in the returned keyfile @@ -118,7 +118,7 @@ class Crypt { } else { - \OC_Log::write( 'Encrypted storage', 'Encryption (symmetric) of keyfile content failed' , \OC_Log::ERROR ); + \OC_Log::write( 'Encryption library', 'Encryption (symmetric) of keyfile content failed' , \OC_Log::ERROR ); return false; @@ -128,7 +128,7 @@ class Crypt { /** - * @brief Decrypts keyfile content + * @brief Symmetrically decrypts keyfile content * @param string $source * @param string $target * @param string $key the decryption key @@ -153,7 +153,91 @@ class Crypt { } else { - \OC_Log::write( 'Encrypted storage', 'Decryption (symmetric) of keyfile content failed' , \OC_Log::ERROR ); + \OC_Log::write( 'Encryption library', 'Decryption (symmetric) of keyfile content failed' , \OC_Log::ERROR ); + + return false; + + } + + } + + /** + * @brief Creates symmetric keyfile content using a generated key + * @param string $plainContent content to be encrypted + * @returns array keys: key, encrypted + * @note symmetricDecryptFileContent() can be used to decrypt files created using this method + * + * This function decrypts a file + */ + public static function symmetricEncryptFileContentKeyfile( $plainContent ) { + + $key = self::generateKey(); + + if( $encryptedContent = self::symmetricEncryptFileContent( $plainContent, $key ) ) { + + return array( + 'key' => $key + , 'encrypted' => $encryptedContent + ); + + } else { + + return false; + + } + + } + + /** + * @brief Create asymmetrically encrypted keyfile content using a generated key + * @param string $plainContent content to be encrypted + * @returns array keys: key, encrypted + * @note symmetricDecryptFileContent() can be used to decrypt files created using this method + * + * This function decrypts a file + */ + public static function multiKeyEncrypt( $plainContent, array $publicKeys ) { + + $envKeys = array(); + + if( openssl_seal( $plainContent, $sealed, $envKeys, $publicKeys ) ) { + + return array( + 'keys' => $envKeys + , 'encrypted' => $sealed + ); + + } else { + + return false; + + } + + } + + /** + * @brief Asymmetrically encrypt a file using multiple public keys + * @param string $plainContent content to be encrypted + * @returns array keys: key, encrypted + * @note symmetricDecryptFileContent() can be used to decrypt files created using this method + * + * This function decrypts a file + */ + public static function multiKeyDecrypt( $encryptedContent, $envKey, $privateKey ) { + + if ( !$encryptedContent ) { + + return false; + + } + + if ( openssl_open( $encryptedContent, $plainContent, $envKey, $privateKey ) ) { + + return $plainContent; + + } else { + + \OC_Log::write( 'Encryption library', 'Decryption (asymmetric) of sealed content failed' , \OC_Log::ERROR ); return false; @@ -162,7 +246,7 @@ class Crypt { } /** - * @brief Asymetrically encrypt a file using a public key + * @brief Asymetrically encrypt a string using a public key * @returns encrypted file */ public static function keyEncrypt( $plainContent, $publicKey ) { @@ -186,14 +270,30 @@ class Crypt { } /** - * @brief Generate a random key for symmetric encryption + * @brief Generate a pseudo random 1024kb ASCII key * @returns $key Generated key */ public static function generateKey() { - $key = mt_rand( 10000, 99999 ) . mt_rand( 10000, 99999 ) . mt_rand( 10000, 99999 ) . mt_rand( 10000, 99999 ); + // $key = mt_rand( 10000, 99999 ) . mt_rand( 10000, 99999 ) . mt_rand( 10000, 99999 ) . mt_rand( 10000, 99999 ); + + // Generate key + if ( $key = base64_encode( openssl_random_pseudo_bytes( 768000, $strong ) ) ) { + + if ( !$strong ) { + + // If OpenSSL indicates randomness is insecure, log error + \OC_Log::write( 'Encryption library', 'Insecure symmetric key was generated using openssl_random_pseudo_bytes()' , \OC_Log::WARN ); + + } + + return $key; + + } else { - return $key; + return false; + + } } diff --git a/apps/files_encryption/lib/proxy.php b/apps/files_encryption/lib/proxy.php index e06242e29d4..3f9b86b988b 100644 --- a/apps/files_encryption/lib/proxy.php +++ b/apps/files_encryption/lib/proxy.php @@ -22,13 +22,6 @@ */ -class OC_FileProxy_Encryption extends OC_FileProxy { - - - -} - - /** * transparent encryption */ diff --git a/apps/files_encryption/lib/util.php b/apps/files_encryption/lib/util.php index 9c0f71fe395..62b435583e3 100644 --- a/apps/files_encryption/lib/util.php +++ b/apps/files_encryption/lib/util.php @@ -114,7 +114,7 @@ class Util { # TODO: Use proper IV in encryption // Encrypt private key with user pwd as passphrase - $encryptedPrivateKey = Crypt::createSymmetricKeyfile( $keypair['privateKey'], $passphrase ); + $encryptedPrivateKey = Crypt::symmetricEncryptFileContent( $keypair['privateKey'], $passphrase ); // $iv = openssl_random_pseudo_bytes(16); $this->view->file_put_contents( '/'. 'keypair'. '/' . $privateKeyFileName, $encryptedPrivateKey ); diff --git a/apps/files_encryption/tests/encryption.php b/apps/files_encryption/tests/encryption.php index 286770a69f5..600e00fd3e4 100644 --- a/apps/files_encryption/tests/encryption.php +++ b/apps/files_encryption/tests/encryption.php @@ -6,67 +6,171 @@ * See the COPYING-README file. */ +require realpath( dirname(__FILE__).'/../lib/crypt.php' ); + class Test_Encryption extends UnitTestCase { - function testEncryption(){ - $key=uniqid(); - $file=OC::$SERVERROOT.'/3rdparty/MDB2.php'; - $source=file_get_contents($file); //nice large text file - $encrypted=OC_Crypt::encrypt($source,$key); - $decrypted=OC_Crypt::decrypt($encrypted,$key); - $decrypted=rtrim($decrypted, "\0"); - $this->assertNotEqual($encrypted,$source); - $this->assertEqual($decrypted,$source); - - $chunk=substr($source,0,8192); - $encrypted=OC_Crypt::encrypt($chunk,$key); - $this->assertEqual(strlen($chunk),strlen($encrypted)); - $decrypted=OC_Crypt::decrypt($encrypted,$key); - $decrypted=rtrim($decrypted, "\0"); - $this->assertEqual($decrypted,$chunk); - - $encrypted=OC_Crypt::blockEncrypt($source,$key); - $decrypted=OC_Crypt::blockDecrypt($encrypted,$key); - $this->assertNotEqual($encrypted,$source); - $this->assertEqual($decrypted,$source); - - $tmpFileEncrypted=OCP\Files::tmpFile(); - OC_Crypt::encryptfile($file,$tmpFileEncrypted,$key); - $encrypted=file_get_contents($tmpFileEncrypted); - $decrypted=OC_Crypt::blockDecrypt($encrypted,$key); - $this->assertNotEqual($encrypted,$source); - $this->assertEqual($decrypted,$source); - - $tmpFileDecrypted=OCP\Files::tmpFile(); - OC_Crypt::decryptfile($tmpFileEncrypted,$tmpFileDecrypted,$key); - $decrypted=file_get_contents($tmpFileDecrypted); - $this->assertEqual($decrypted,$source); - - $file=OC::$SERVERROOT.'/core/img/weather-clear.png'; - $source=file_get_contents($file); //binary file - $encrypted=OC_Crypt::encrypt($source,$key); - $decrypted=OC_Crypt::decrypt($encrypted,$key); - $decrypted=rtrim($decrypted, "\0"); - $this->assertEqual($decrypted,$source); - - $encrypted=OC_Crypt::blockEncrypt($source,$key); - $decrypted=OC_Crypt::blockDecrypt($encrypted,$key); - $this->assertEqual($decrypted,$source); + function setUp() { + + // set content for encrypting / decrypting in tests + $this->data = realpath( dirname(__FILE__).'/../lib/crypt.php' ); + + } + + function tearDown(){} + + function testGenerateKey() { + + # TODO: use more accurate (larger) string length for test confirmation + + $key = OCA_Encryption\Crypt::generateKey(); + + $this->assertTrue( strlen( $key ) > 1000 ); + + } + + function testEncrypt() { + + $random = openssl_random_pseudo_bytes( 13 ); + + $iv = substr( base64_encode( $random ), 0, -4 ); // i.e. E5IG033j+mRNKrht + + $crypted = OCA_Encryption\Crypt::encrypt( $this->data, $iv, 'hat' ); + + $this->assertNotEqual( $this->data, $crypted ); + + } + + function testDecrypt() { + + $random = openssl_random_pseudo_bytes( 13 ); + + $iv = substr( base64_encode( $random ), 0, -4 ); // i.e. E5IG033j+mRNKrht + + $crypted = OCA_Encryption\Crypt::encrypt( $this->data, $iv, 'hat' ); + + $decrypt = OCA_Encryption\Crypt::decrypt( $crypted, $iv, 'hat' ); + + $this->assertEqual( $this->data, $decrypt ); + + } + + function testSymmetricEncryptFileContent() { + + # TODO: search in keyfile for actual content as IV will ensure this test always passes + + $keyfileContent = OCA_Encryption\Crypt::symmetricEncryptFileContent( $this->data, 'hat' ); + + $this->assertNotEqual( $this->data, $keyfileContent ); + + + $decrypt = OCA_Encryption\Crypt::symmetricDecryptFileContent( $keyfileContent, 'hat' ); + + $this->assertEqual( $this->data, $decrypt ); + } - function testBinary(){ - $key=uniqid(); + function testSymmetricEncryptFileContentKeyfile() { - $file=__DIR__.'/binary'; - $source=file_get_contents($file); //binary file - $encrypted=OC_Crypt::encrypt($source,$key); - $decrypted=OC_Crypt::decrypt($encrypted,$key); + # TODO: search in keyfile for actual content as IV will ensure this test always passes + + $crypted = OCA_Encryption\Crypt::symmetricEncryptFileContentKeyfile( $this->data ); + + $this->assertNotEqual( $this->data, $crypted['encrypted'] ); + + + $decrypt = OCA_Encryption\Crypt::symmetricDecryptFileContent( $crypted['encrypted'], $crypted['key'] ); + + $this->assertEqual( $this->data, $decrypt ); + + } + + function testMultiKeyEncrypt() { + + # TODO: search in keyfile for actual content as IV will ensure this test always passes + + $pair1 = OCA_Encryption\Crypt::createKeypair(); + + $this->assertEqual( 2, count( $pair1 ) ); + + $this->assertTrue( strlen( $pair1['publicKey'] ) > 1 ); + + $this->assertTrue( strlen( $pair1['privateKey'] ) > 1 ); + - $decrypted=rtrim($decrypted, "\0"); - $this->assertEqual($decrypted,$source); + $crypted = OCA_Encryption\Crypt::multiKeyEncrypt( $this->data, array( $pair1['publicKey'] ) ); + + $this->assertNotEqual( $this->data, $crypted['encrypted'] ); + - $encrypted=OC_Crypt::blockEncrypt($source,$key); - $decrypted=OC_Crypt::blockDecrypt($encrypted,$key,strlen($source)); - $this->assertEqual($decrypted,$source); + $decrypt = OCA_Encryption\Crypt::multiKeyDecrypt( $crypted['encrypted'], $crypted['keys'][0], $pair1['privateKey'] ); + + $this->assertEqual( $this->data, $decrypt ); + } + +// function testEncryption(){ +// +// $key=uniqid(); +// $file=OC::$SERVERROOT.'/3rdparty/MDB2.php'; +// $source=file_get_contents($file); //nice large text file +// $encrypted=OC_Crypt::encrypt($source,$key); +// $decrypted=OC_Crypt::decrypt($encrypted,$key); +// $decrypted=rtrim($decrypted, "\0"); +// $this->assertNotEqual($encrypted,$source); +// $this->assertEqual($decrypted,$source); +// +// $chunk=substr($source,0,8192); +// $encrypted=OC_Crypt::encrypt($chunk,$key); +// $this->assertEqual(strlen($chunk),strlen($encrypted)); +// $decrypted=OC_Crypt::decrypt($encrypted,$key); +// $decrypted=rtrim($decrypted, "\0"); +// $this->assertEqual($decrypted,$chunk); +// +// $encrypted=OC_Crypt::blockEncrypt($source,$key); +// $decrypted=OC_Crypt::blockDecrypt($encrypted,$key); +// $this->assertNotEqual($encrypted,$source); +// $this->assertEqual($decrypted,$source); +// +// $tmpFileEncrypted=OCP\Files::tmpFile(); +// OC_Crypt::encryptfile($file,$tmpFileEncrypted,$key); +// $encrypted=file_get_contents($tmpFileEncrypted); +// $decrypted=OC_Crypt::blockDecrypt($encrypted,$key); +// $this->assertNotEqual($encrypted,$source); +// $this->assertEqual($decrypted,$source); +// +// $tmpFileDecrypted=OCP\Files::tmpFile(); +// OC_Crypt::decryptfile($tmpFileEncrypted,$tmpFileDecrypted,$key); +// $decrypted=file_get_contents($tmpFileDecrypted); +// $this->assertEqual($decrypted,$source); +// +// $file=OC::$SERVERROOT.'/core/img/weather-clear.png'; +// $source=file_get_contents($file); //binary file +// $encrypted=OC_Crypt::encrypt($source,$key); +// $decrypted=OC_Crypt::decrypt($encrypted,$key); +// $decrypted=rtrim($decrypted, "\0"); +// $this->assertEqual($decrypted,$source); +// +// $encrypted=OC_Crypt::blockEncrypt($source,$key); +// $decrypted=OC_Crypt::blockDecrypt($encrypted,$key); +// $this->assertEqual($decrypted,$source); +// +// } +// +// function testBinary(){ +// $key=uniqid(); +// +// $file=__DIR__.'/binary'; +// $source=file_get_contents($file); //binary file +// $encrypted=OC_Crypt::encrypt($source,$key); +// $decrypted=OC_Crypt::decrypt($encrypted,$key); +// +// $decrypted=rtrim($decrypted, "\0"); +// $this->assertEqual($decrypted,$source); +// +// $encrypted=OC_Crypt::blockEncrypt($source,$key); +// $decrypted=OC_Crypt::blockDecrypt($encrypted,$key,strlen($source)); +// $this->assertEqual($decrypted,$source); +// } + } diff --git a/apps/files_encryption/tests/proxy.php b/apps/files_encryption/tests/proxy.php index 5463836a209..253a32164ec 100644 --- a/apps/files_encryption/tests/proxy.php +++ b/apps/files_encryption/tests/proxy.php @@ -6,112 +6,112 @@ * See the COPYING-README file. */ -class Test_CryptProxy extends UnitTestCase { - private $oldConfig; - private $oldKey; - - public function setUp(){ - $user=OC_User::getUser(); - - $this->oldConfig=OCP\Config::getAppValue('files_encryption','enable_encryption','true'); - OCP\Config::setAppValue('files_encryption','enable_encryption','true'); - $this->oldKey=isset($_SESSION['enckey'])?$_SESSION['enckey']:null; - - - //set testing key - $_SESSION['enckey']=md5(time()); - - //clear all proxies and hooks so we can do clean testing - OC_FileProxy::clearProxies(); - OC_Hook::clear('OC_Filesystem'); - - //enable only the encryption hook - OC_FileProxy::register(new OC_FileProxy_Encryption()); - - //set up temporary storage - OC_Filesystem::clearMounts(); - OC_Filesystem::mount('OC_Filestorage_Temporary',array(),'/'); - - OC_Filesystem::init('/'.$user.'/files'); - - //set up the users home folder in the temp storage - $rootView=new OC_FilesystemView(''); - $rootView->mkdir('/'.$user); - $rootView->mkdir('/'.$user.'/files'); - } - - public function tearDown(){ - OCP\Config::setAppValue('files_encryption','enable_encryption',$this->oldConfig); - if(!is_null($this->oldKey)){ - $_SESSION['enckey']=$this->oldKey; - } - } - - public function testSimple(){ - $file=OC::$SERVERROOT.'/3rdparty/MDB2.php'; - $original=file_get_contents($file); - - OC_Filesystem::file_put_contents('/file',$original); - - OC_FileProxy::$enabled=false; - $stored=OC_Filesystem::file_get_contents('/file'); - OC_FileProxy::$enabled=true; - - $fromFile=OC_Filesystem::file_get_contents('/file'); - $this->assertNotEqual($original,$stored); - $this->assertEqual(strlen($original),strlen($fromFile)); - $this->assertEqual($original,$fromFile); - - } - - public function testView(){ - $file=OC::$SERVERROOT.'/3rdparty/MDB2.php'; - $original=file_get_contents($file); - - $rootView=new OC_FilesystemView(''); - $view=new OC_FilesystemView('/'.OC_User::getUser()); - $userDir='/'.OC_User::getUser().'/files'; - - $rootView->file_put_contents($userDir.'/file',$original); - - OC_FileProxy::$enabled=false; - $stored=$rootView->file_get_contents($userDir.'/file'); - OC_FileProxy::$enabled=true; - - $this->assertNotEqual($original,$stored); - $fromFile=$rootView->file_get_contents($userDir.'/file'); - $this->assertEqual($original,$fromFile); - - $fromFile=$view->file_get_contents('files/file'); - $this->assertEqual($original,$fromFile); - } - - public function testBinary(){ - $file=__DIR__.'/binary'; - $original=file_get_contents($file); - - OC_Filesystem::file_put_contents('/file',$original); - - OC_FileProxy::$enabled=false; - $stored=OC_Filesystem::file_get_contents('/file'); - OC_FileProxy::$enabled=true; - - $fromFile=OC_Filesystem::file_get_contents('/file'); - $this->assertNotEqual($original,$stored); - $this->assertEqual(strlen($original),strlen($fromFile)); - $this->assertEqual($original,$fromFile); - - $file=__DIR__.'/zeros'; - $original=file_get_contents($file); - - OC_Filesystem::file_put_contents('/file',$original); - - OC_FileProxy::$enabled=false; - $stored=OC_Filesystem::file_get_contents('/file'); - OC_FileProxy::$enabled=true; - - $fromFile=OC_Filesystem::file_get_contents('/file'); - $this->assertNotEqual($original,$stored); - $this->assertEqual(strlen($original),strlen($fromFile)); - } -} +// class Test_CryptProxy extends UnitTestCase { +// private $oldConfig; +// private $oldKey; +// +// public function setUp(){ +// $user=OC_User::getUser(); +// +// $this->oldConfig=OCP\Config::getAppValue('files_encryption','enable_encryption','true'); +// OCP\Config::setAppValue('files_encryption','enable_encryption','true'); +// $this->oldKey=isset($_SESSION['enckey'])?$_SESSION['enckey']:null; +// +// +// //set testing key +// $_SESSION['enckey']=md5(time()); +// +// //clear all proxies and hooks so we can do clean testing +// OC_FileProxy::clearProxies(); +// OC_Hook::clear('OC_Filesystem'); +// +// //enable only the encryption hook +// OC_FileProxy::register(new OC_FileProxy_Encryption()); +// +// //set up temporary storage +// OC_Filesystem::clearMounts(); +// OC_Filesystem::mount('OC_Filestorage_Temporary',array(),'/'); +// +// OC_Filesystem::init('/'.$user.'/files'); +// +// //set up the users home folder in the temp storage +// $rootView=new OC_FilesystemView(''); +// $rootView->mkdir('/'.$user); +// $rootView->mkdir('/'.$user.'/files'); +// } +// +// public function tearDown(){ +// OCP\Config::setAppValue('files_encryption','enable_encryption',$this->oldConfig); +// if(!is_null($this->oldKey)){ +// $_SESSION['enckey']=$this->oldKey; +// } +// } +// +// public function testSimple(){ +// $file=OC::$SERVERROOT.'/3rdparty/MDB2.php'; +// $original=file_get_contents($file); +// +// OC_Filesystem::file_put_contents('/file',$original); +// +// OC_FileProxy::$enabled=false; +// $stored=OC_Filesystem::file_get_contents('/file'); +// OC_FileProxy::$enabled=true; +// +// $fromFile=OC_Filesystem::file_get_contents('/file'); +// $this->assertNotEqual($original,$stored); +// $this->assertEqual(strlen($original),strlen($fromFile)); +// $this->assertEqual($original,$fromFile); +// +// } +// +// public function testView(){ +// $file=OC::$SERVERROOT.'/3rdparty/MDB2.php'; +// $original=file_get_contents($file); +// +// $rootView=new OC_FilesystemView(''); +// $view=new OC_FilesystemView('/'.OC_User::getUser()); +// $userDir='/'.OC_User::getUser().'/files'; +// +// $rootView->file_put_contents($userDir.'/file',$original); +// +// OC_FileProxy::$enabled=false; +// $stored=$rootView->file_get_contents($userDir.'/file'); +// OC_FileProxy::$enabled=true; +// +// $this->assertNotEqual($original,$stored); +// $fromFile=$rootView->file_get_contents($userDir.'/file'); +// $this->assertEqual($original,$fromFile); +// +// $fromFile=$view->file_get_contents('files/file'); +// $this->assertEqual($original,$fromFile); +// } +// +// public function testBinary(){ +// $file=__DIR__.'/binary'; +// $original=file_get_contents($file); +// +// OC_Filesystem::file_put_contents('/file',$original); +// +// OC_FileProxy::$enabled=false; +// $stored=OC_Filesystem::file_get_contents('/file'); +// OC_FileProxy::$enabled=true; +// +// $fromFile=OC_Filesystem::file_get_contents('/file'); +// $this->assertNotEqual($original,$stored); +// $this->assertEqual(strlen($original),strlen($fromFile)); +// $this->assertEqual($original,$fromFile); +// +// $file=__DIR__.'/zeros'; +// $original=file_get_contents($file); +// +// OC_Filesystem::file_put_contents('/file',$original); +// +// OC_FileProxy::$enabled=false; +// $stored=OC_Filesystem::file_get_contents('/file'); +// OC_FileProxy::$enabled=true; +// +// $fromFile=OC_Filesystem::file_get_contents('/file'); +// $this->assertNotEqual($original,$stored); +// $this->assertEqual(strlen($original),strlen($fromFile)); +// } +// } diff --git a/apps/files_encryption/tests/stream.php b/apps/files_encryption/tests/stream.php index d95ea792f72..4c78b2d7b0f 100644 --- a/apps/files_encryption/tests/stream.php +++ b/apps/files_encryption/tests/stream.php @@ -6,80 +6,80 @@ * See the COPYING-README file. */ -class Test_CryptStream extends UnitTestCase { - private $tmpFiles=array(); - - function testStream(){ - $stream=$this->getStream('test1','w',strlen('foobar')); - fwrite($stream,'foobar'); - fclose($stream); - - $stream=$this->getStream('test1','r',strlen('foobar')); - $data=fread($stream,6); - fclose($stream); - $this->assertEqual('foobar',$data); - - $file=OC::$SERVERROOT.'/3rdparty/MDB2.php'; - $source=fopen($file,'r'); - $target=$this->getStream('test2','w',0); - OCP\Files::streamCopy($source,$target); - fclose($target); - fclose($source); - - $stream=$this->getStream('test2','r',filesize($file)); - $data=stream_get_contents($stream); - $original=file_get_contents($file); - $this->assertEqual(strlen($original),strlen($data)); - $this->assertEqual($original,$data); - } - - /** - * get a cryptstream to a temporary file - * @param string $id - * @param string $mode - * @param int size - * @return resource - */ - function getStream($id,$mode,$size){ - if($id===''){ - $id=uniqid(); - } - if(!isset($this->tmpFiles[$id])){ - $file=OCP\Files::tmpFile(); - $this->tmpFiles[$id]=$file; - }else{ - $file=$this->tmpFiles[$id]; - } - $stream=fopen($file,$mode); - OC_CryptStream::$sourceStreams[$id]=array('path'=>'dummy'.$id,'stream'=>$stream,'size'=>$size); - return fopen('crypt://streams/'.$id,$mode); - } - - function testBinary(){ - $file=__DIR__.'/binary'; - $source=file_get_contents($file); - - $stream=$this->getStream('test','w',strlen($source)); - fwrite($stream,$source); - fclose($stream); - - $stream=$this->getStream('test','r',strlen($source)); - $data=stream_get_contents($stream); - fclose($stream); - $this->assertEqual(strlen($data),strlen($source)); - $this->assertEqual($source,$data); - - $file=__DIR__.'/zeros'; - $source=file_get_contents($file); - - $stream=$this->getStream('test2','w',strlen($source)); - fwrite($stream,$source); - fclose($stream); - - $stream=$this->getStream('test2','r',strlen($source)); - $data=stream_get_contents($stream); - fclose($stream); - $this->assertEqual(strlen($data),strlen($source)); - $this->assertEqual($source,$data); - } -} +// class Test_CryptStream extends UnitTestCase { +// private $tmpFiles=array(); +// +// function testStream(){ +// $stream=$this->getStream('test1','w',strlen('foobar')); +// fwrite($stream,'foobar'); +// fclose($stream); +// +// $stream=$this->getStream('test1','r',strlen('foobar')); +// $data=fread($stream,6); +// fclose($stream); +// $this->assertEqual('foobar',$data); +// +// $file=OC::$SERVERROOT.'/3rdparty/MDB2.php'; +// $source=fopen($file,'r'); +// $target=$this->getStream('test2','w',0); +// OCP\Files::streamCopy($source,$target); +// fclose($target); +// fclose($source); +// +// $stream=$this->getStream('test2','r',filesize($file)); +// $data=stream_get_contents($stream); +// $original=file_get_contents($file); +// $this->assertEqual(strlen($original),strlen($data)); +// $this->assertEqual($original,$data); +// } +// +// /** +// * get a cryptstream to a temporary file +// * @param string $id +// * @param string $mode +// * @param int size +// * @return resource +// */ +// function getStream($id,$mode,$size){ +// if($id===''){ +// $id=uniqid(); +// } +// if(!isset($this->tmpFiles[$id])){ +// $file=OCP\Files::tmpFile(); +// $this->tmpFiles[$id]=$file; +// }else{ +// $file=$this->tmpFiles[$id]; +// } +// $stream=fopen($file,$mode); +// OC_CryptStream::$sourceStreams[$id]=array('path'=>'dummy'.$id,'stream'=>$stream,'size'=>$size); +// return fopen('crypt://streams/'.$id,$mode); +// } +// +// function testBinary(){ +// $file=__DIR__.'/binary'; +// $source=file_get_contents($file); +// +// $stream=$this->getStream('test','w',strlen($source)); +// fwrite($stream,$source); +// fclose($stream); +// +// $stream=$this->getStream('test','r',strlen($source)); +// $data=stream_get_contents($stream); +// fclose($stream); +// $this->assertEqual(strlen($data),strlen($source)); +// $this->assertEqual($source,$data); +// +// $file=__DIR__.'/zeros'; +// $source=file_get_contents($file); +// +// $stream=$this->getStream('test2','w',strlen($source)); +// fwrite($stream,$source); +// fclose($stream); +// +// $stream=$this->getStream('test2','r',strlen($source)); +// $data=stream_get_contents($stream); +// fclose($stream); +// $this->assertEqual(strlen($data),strlen($source)); +// $this->assertEqual($source,$data); +// } +// } -- cgit v1.2.3 From 66b461629be6d1585ae0171b9128ad19d2c85bfb Mon Sep 17 00:00:00 2001 From: Sam Tuke Date: Wed, 25 Jul 2012 16:25:24 +0100 Subject: Started implementation of new encyryption classes into the encryption proxy --- apps/files_encryption/hooks/hooks.php | 14 ++++++++------ apps/files_encryption/lib/proxy.php | 23 +++++++++++++++-------- 2 files changed, 23 insertions(+), 14 deletions(-) (limited to 'apps/files_encryption/lib/proxy.php') diff --git a/apps/files_encryption/hooks/hooks.php b/apps/files_encryption/hooks/hooks.php index 89d526b7044..a8304261e47 100644 --- a/apps/files_encryption/hooks/hooks.php +++ b/apps/files_encryption/hooks/hooks.php @@ -30,19 +30,21 @@ class Hooks { public static function login( $params ){ - $view = new \OC_FilesystemView( '/' . $params['uid'] ); + $view = new \OC_FilesystemView( '/' ); - $storage = new Storage( $view ); + $storage = new Storage( $view, $params['uid'] ); if ( !$storage->ready() ) { return $storage->setup( $params['password'] ); - } else { - - return true; - } + + $_SESSION['enckey'] = OC_Crypt::decrypt($key, $password); + + return true; + + } } diff --git a/apps/files_encryption/lib/proxy.php b/apps/files_encryption/lib/proxy.php index 3f9b86b988b..080fd04cd7c 100644 --- a/apps/files_encryption/lib/proxy.php +++ b/apps/files_encryption/lib/proxy.php @@ -26,9 +26,11 @@ * transparent encryption */ -class OC_FileProxy_Encryption extends OC_FileProxy{ - private static $blackList=null; //mimetypes blacklisted from encryption - private static $enableEncryption=null; +class OC_FileProxy_Encryption extends OC_FileProxy { + + private static $blackList = null; //mimetypes blacklisted from encryption + + private static $enableEncryption = null; /** * Check if a file requires encryption @@ -97,7 +99,7 @@ class OC_FileProxy_Encryption extends OC_FileProxy{ $size = strlen( $data ); - $data = Crypt::blockEncrypt( $data ); + $data = OCA_Encryption\Crypt::symmetricEncryptFileContent( $data, '', $cached['size'] ); OC_FileCache::put( $path, array( 'encrypted'=>true, 'size' => $size ), '' ); @@ -105,11 +107,16 @@ class OC_FileProxy_Encryption extends OC_FileProxy{ } } - public function postFile_get_contents($path,$data){ - if(self::isEncrypted($path)){ - $cached=OC_FileCache_Cached::get($path,''); - $data=OC_Crypt::blockDecrypt($data,'',$cached['size']); + public function postFile_get_contents( $path, $data ) { + + if ( self::isEncrypted( $path ) ) { + + $cached = OC_FileCache_Cached::get( $path, '' ); + + $data = OCA_Encryption\Crypt::symmetricDecryptFileContent( $data, '' ); + } + return $data; } -- cgit v1.2.3 From 9f51841c57ca96eb7ce518dde9c6d35c905110c6 Mon Sep 17 00:00:00 2001 From: Sam Tuke Date: Wed, 25 Jul 2012 18:28:56 +0100 Subject: Mainly work on implementing new encryption system (+ keyfile handling) into proxy classs --- apps/files_encryption/appinfo/app.php | 16 ++++++-- apps/files_encryption/hooks/hooks.php | 1 - apps/files_encryption/lib/keymanager.php | 24 +++++++----- apps/files_encryption/lib/proxy.php | 65 ++++++++++++++++++++------------ apps/files_encryption/lib/util.php | 9 +++-- 5 files changed, 73 insertions(+), 42 deletions(-) (limited to 'apps/files_encryption/lib/proxy.php') diff --git a/apps/files_encryption/appinfo/app.php b/apps/files_encryption/appinfo/app.php index 969c824cfda..1a4021e9395 100644 --- a/apps/files_encryption/appinfo/app.php +++ b/apps/files_encryption/appinfo/app.php @@ -5,18 +5,26 @@ 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['OC_FileProxy_Encryption'] = 'apps/files_encryption/lib/proxy.php'; +OC::$CLASSPATH['OCA_Encryption\Proxy'] = 'apps/files_encryption/lib/proxy.php'; -//OC_FileProxy::register(new OC_FileProxy_Encryption()); +OC_FileProxy::register(new OCA_Encryption\Proxy()); OCP\Util::connectHook('OC_User','post_login','OCA_Encryption\Hooks','login'); stream_wrapper_register('crypt','OC_CryptStream'); -if( !isset($_SESSION['enckey']) and OCP\User::isLoggedIn() ){//force the user to re-loggin if the encryption key isn't unlocked (happens when a user is logged in before the encryption app is enabled) +if( +!isset( $_SESSION['enckey'] ) +and OCP\User::isLoggedIn() +) { + + // Force the user to re-log in if the encryption key isn't unlocked (happens when a user is logged in before the encryption app is enabled) OCP\User::logout(); + header("Location: ".OC::$WEBROOT.'/'); + exit(); + } -OCP\App::registerAdmin('files_encryption', 'settings'); +OCP\App::registerAdmin('files_encryption', 'settings'); \ No newline at end of file diff --git a/apps/files_encryption/hooks/hooks.php b/apps/files_encryption/hooks/hooks.php index 70bbbcf4789..654686208fa 100644 --- a/apps/files_encryption/hooks/hooks.php +++ b/apps/files_encryption/hooks/hooks.php @@ -52,7 +52,6 @@ class Hooks { return true; - } } diff --git a/apps/files_encryption/lib/keymanager.php b/apps/files_encryption/lib/keymanager.php index d78db132b16..e4462796f0d 100644 --- a/apps/files_encryption/lib/keymanager.php +++ b/apps/files_encryption/lib/keymanager.php @@ -27,6 +27,7 @@ namespace OCA_Encryption; */ class Keymanager { + # TODO: Try and get rid of username dependencies as these methods need to be used in a proxy class that doesn't have username access /** * @brief retrieve private key from a user @@ -36,9 +37,9 @@ class Keymanager { */ public static function getPrivateKey( $user ) { - $view = new \OC_FilesystemView( '/' . $user . '/' . 'files_encryption' . '/' ); + $view = new \OC_FilesystemView( '/' . $user . '/' . 'files_encryption' ); - return $view->file_get_contents( $user.'.private.key' ); + return $view->file_get_contents( '/' . $user.'.private.key' ); } /** @@ -96,15 +97,20 @@ class Keymanager { /** * @brief store file encryption key * - * @param string user name of the file owner - * @param string file name - * @param string key + * @param string $userId name of the file owner + * @param string $path relative path of the file, including filename + * @param string $key * @return bool true/false */ - public static function setFileKey($user, $file, $key) { - $fileKeyStorage = \OCP\Config::getSystemValue('datadirectory').'/'.$user.'/files_encryption/keyfiles/'; - $view = new \OC_FilesystemView($fileKeyStorage); - return $view->file_put_contents($file.'.key', $key); + public static function setFileKey( $userId, $path, $key ) { + + \OC_FileProxy::$enabled = false; + + $view = new \OC_FilesystemView( '/' . $userId . '/' . 'files_encryption' ); + + return $view->file_put_contents( '/' . $path . '.key', $key ); + + \OC_FileProxy::$enabled = true; } diff --git a/apps/files_encryption/lib/proxy.php b/apps/files_encryption/lib/proxy.php index 080fd04cd7c..53ed05d2c3b 100644 --- a/apps/files_encryption/lib/proxy.php +++ b/apps/files_encryption/lib/proxy.php @@ -3,8 +3,9 @@ /** * ownCloud * -* @author Robin Appelman -* @copyright 2011 Robin Appelman icewind1991@gmail.com +* @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 @@ -26,7 +27,9 @@ * transparent encryption */ -class OC_FileProxy_Encryption extends OC_FileProxy { +namespace OCA_Encryption; + +class Proxy extends \OC_FileProxy { private static $blackList = null; //mimetypes blacklisted from encryption @@ -43,7 +46,7 @@ class OC_FileProxy_Encryption extends OC_FileProxy { if ( is_null( self::$enableEncryption ) ) { - self::$enableEncryption = ( OCP\Config::getAppValue( 'files_encryption', 'enable_encryption', 'true' ) == 'true' ); + self::$enableEncryption = ( \OCP\Config::getAppValue( 'files_encryption', 'enable_encryption', 'true' ) == 'true' ); } @@ -55,11 +58,11 @@ class OC_FileProxy_Encryption extends OC_FileProxy { 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' ) ); + self::$blackList = explode(',', \OCP\Config::getAppValue( 'files_encryption','type_blacklist','jpg,png,jpeg,avi,mpg,mpeg,mkv,mp3,oga,ogv,ogg' ) ); } - if( self::isEncrypted( $path ) ) { + if( Crypt::isEncryptedContent( $path ) ) { return true; @@ -84,7 +87,7 @@ class OC_FileProxy_Encryption extends OC_FileProxy { private static function isEncrypted( $path ){ // Fetch all file metadata from DB - $metadata = OC_FileCache_Cached::get( $path, '' ); + $metadata = \OC_FileCache_Cached::get( $path, '' ); // Return encryption status return isset( $metadata['encrypted'] ) and ( bool )$metadata['encrypted']; @@ -95,13 +98,24 @@ class OC_FileProxy_Encryption extends OC_FileProxy { if ( self::shouldEncrypt( $path ) ) { - if ( !is_resource( $data ) ) {//stream put contents should have been converter to fopen + if ( !is_resource( $data ) ) { //stream put contents should have been converter to fopen + // Set the filesize for userland, before encrypting $size = strlen( $data ); - $data = OCA_Encryption\Crypt::symmetricEncryptFileContent( $data, '', $cached['size'] ); + // Encrypt plain data and fetch key + $encrypted = Crypt::symmetricEncryptFileContentKeyfile( $data, $_SESSION['enckey'] ); + + // Replace plain content with encrypted content by reference + $data = $encrypted['encrypted']; + + # TODO: check if file is in subdirectories, and if so, create those parent directories. Or else monitor creation of directories using hooks to ensure path will always exist (what about existing directories when encryption is enabled?) - OC_FileCache::put( $path, array( 'encrypted'=>true, 'size' => $size ), '' ); + // Save keyfile for newly encrypted file in parallel directory + Keymanager::setFileKey( \OCP\USER::getUser(), $path, $encrypted['key'] ); + + // Update the file cache with file info + \OC_FileCache::put( $path, array( 'encrypted'=>true, 'size' => $size ), '' ); } } @@ -109,11 +123,11 @@ class OC_FileProxy_Encryption extends OC_FileProxy { public function postFile_get_contents( $path, $data ) { - if ( self::isEncrypted( $path ) ) { - - $cached = OC_FileCache_Cached::get( $path, '' ); + if ( Crypt::isEncryptedContent( $data ) ) { + trigger_error('best'); + $cached = \OC_FileCache_Cached::get( $path, '' ); - $data = OCA_Encryption\Crypt::symmetricDecryptFileContent( $data, '' ); + $data = Crypt::symmetricDecryptFileContent( $data, $_SESSION['enckey'] ); } @@ -121,21 +135,22 @@ class OC_FileProxy_Encryption extends OC_FileProxy { } public function postFopen($path,&$result){ + if(!$result){ return $result; } $meta=stream_get_meta_data($result); - if(self::isEncrypted($path)){ + if(Crypt::isEncryptedContent($path)){ fclose($result); $result=fopen('crypt://'.$path,$meta['mode']); }elseif(self::shouldEncrypt($path) and $meta['mode']!='r' and $meta['mode']!='rb'){ - if(OC_Filesystem::file_exists($path) and OC_Filesystem::filesize($path)>0){ + if( \OC_Filesystem::file_exists( $path ) and \OC_Filesystem::filesize($path)>0){ //first encrypt the target file so we don't end up with a half encrypted file - OCP\Util::writeLog('files_encryption','Decrypting '.$path.' before writing',OCP\Util::DEBUG); + \OCP\Util::writeLog('files_encryption','Decrypting '.$path.' before writing', \OCP\Util::DEBUG); $tmp=fopen('php://temp'); - OCP\Files::streamCopy($result,$tmp); + \OCP\Files::streamCopy($result,$tmp); fclose($result); - OC_Filesystem::file_put_contents($path,$tmp); + \OC_Filesystem::file_put_contents($path,$tmp); fclose($tmp); } $result=fopen('crypt://'.$path,$meta['mode']); @@ -144,23 +159,23 @@ class OC_FileProxy_Encryption extends OC_FileProxy { } public function postGetMimeType($path,$mime){ - if(self::isEncrypted($path)){ - $mime=OCP\Files::getMimeType('crypt://'.$path,'w'); + if(Crypt::isEncryptedContent($path)){ + $mime = \OCP\Files::getMimeType('crypt://'.$path,'w'); } return $mime; } public function postStat($path,$data){ - if(self::isEncrypted($path)){ - $cached=OC_FileCache_Cached::get($path,''); + if(Crypt::isEncryptedContent($path)){ + $cached= \OC_FileCache_Cached::get($path,''); $data['size']=$cached['size']; } return $data; } public function postFileSize($path,$size){ - if(self::isEncrypted($path)){ - $cached=OC_FileCache_Cached::get($path,''); + if(Crypt::isEncryptedContent($path)){ + $cached = \OC_FileCache_Cached::get($path,''); return $cached['size']; }else{ return $size; diff --git a/apps/files_encryption/lib/util.php b/apps/files_encryption/lib/util.php index e876e886c42..ab58b4aa721 100644 --- a/apps/files_encryption/lib/util.php +++ b/apps/files_encryption/lib/util.php @@ -184,15 +184,16 @@ class Util { * if the key is left out, the default handeler will be used */ public function getLegacyKey( $passphrase ) { - - //OC_FileProxy::$enabled = false; + + // Disable proxies to prevent attempt to automatically decrypt key + OC_FileProxy::$enabled = false; if ( $passphrase and $key = $this->view->file_get_contents( '/encryption.key' ) ) { - //OC_FileProxy::$enabled = true; + OC_FileProxy::$enabled = true; if ( $this->legacyKey = $this->legacyDecrypt( $key, $passphrase ) ) { @@ -206,6 +207,8 @@ class Util { } else { + OC_FileProxy::$enabled = true; + return false; } -- cgit v1.2.3 From eebf76d34457df616d2b739582d9630f58df60b1 Mon Sep 17 00:00:00 2001 From: Sam Tuke Date: Tue, 31 Jul 2012 19:28:11 +0100 Subject: Implemented writing of keyfiles and directory hierarchy in proxy class Added crypt::findFiles() method for finding different types of files, ready for batch encrypting / decrypting Added comments to postFopen in proxy class --- apps/files_encryption/hooks/hooks.php | 4 +- apps/files_encryption/lib/crypt.php | 830 +++++++++++++++--------------- apps/files_encryption/lib/cryptstream.php | 83 ++- apps/files_encryption/lib/keymanager.php | 26 +- apps/files_encryption/lib/proxy.php | 86 +++- apps/files_encryption/lib/util.php | 96 +++- 6 files changed, 646 insertions(+), 479 deletions(-) (limited to 'apps/files_encryption/lib/proxy.php') diff --git a/apps/files_encryption/hooks/hooks.php b/apps/files_encryption/hooks/hooks.php index 80daf50a24d..57d379b9365 100644 --- a/apps/files_encryption/hooks/hooks.php +++ b/apps/files_encryption/hooks/hooks.php @@ -37,14 +37,14 @@ class Hooks { public static function login( $params ) { - if (Crypt::mode($params['uid'])=='server') { + if ( Crypt::mode( $params['uid'] ) == 'server' ) { $view = new \OC_FilesystemView( '/' ); $util = new Util( $view, $params['uid'] ); if ( !$util->ready()) { - + return $util->setupServerSide( $params['password'] ); } diff --git a/apps/files_encryption/lib/crypt.php b/apps/files_encryption/lib/crypt.php index 090b1db0611..cd658601845 100644 --- a/apps/files_encryption/lib/crypt.php +++ b/apps/files_encryption/lib/crypt.php @@ -1,420 +1,422 @@ -. - * - */ - -namespace OCA_Encryption; - -/** - * Class for common cryptography functionality - */ - -class Crypt { - +. + * + */ + +namespace OCA_Encryption; + +/** + * Class for common cryptography functionality + */ + +class Crypt { + /** - * @brief return encryption mode client or server side encryption + * @brief return encryption mode client or server side encryption * @param string user name * @return string 'client' or 'server' */ - public static function mode($user) { - //TODO: allow user to set encryption mode and check the selection of the user + public static function mode( $user ) { + + //TODO: allow user to set encryption mode and check the selection of the user // for the moment I just return 'client' for test purposes - return 'client'; - } - - /** - * @brief Create a new encryption keypair - * @return array publicKey, privatekey - */ - public static function createKeypair() { - - $res = openssl_pkey_new(); - - // Get private key - openssl_pkey_export( $res, $privateKey ); - - // Get public key - $publicKey = openssl_pkey_get_details( $res ); - - $publicKey = $publicKey['key']; - - return( array( 'publicKey' => $publicKey, 'privateKey' => $privateKey ) ); - - } - - /** - * @brief Check if a file's contents contains an IV and is symmetrically encrypted - * @return true / false - */ - public static function isEncryptedContent( $content ) { - - if ( !$content ) { - - return false; - - } - - // Fetch encryption metadata from end of file - $meta = substr( $content, -22 ); - - // Fetch IV from end of file - $iv = substr( $meta, -16 ); - - // Fetch identifier from start of metadata - $identifier = substr( $meta, 0, 6 ); - - if ( $identifier == '00iv00') { - - return true; - - } else { - - return false; - - } - - } - - /** - * @brief Check if a file is encrypted via legacy system - * @return true / false - */ - public static function isLegacyEncryptedContent( $content, $path ) { - - // Fetch all file metadata from DB - $metadata = \OC_FileCache_Cached::get( $content, '' ); - - // If a file is flagged with encryption in DB, but isn't a valid content + IV combination, it's probably using the legacy encryption system - if ( - $content - and isset( $metadata['encrypted'] ) - and $metadata['encrypted'] === true - and !self::isEncryptedContent( $content ) - ) { - - return true; - - } else { - - return false; - - } - - } - - /** - * @brief Symmetrically encrypt a string - * @returns encrypted file - */ - public static function encrypt( $plainContent, $iv, $passphrase = '' ) { - - if ( $encryptedContent = openssl_encrypt( $plainContent, 'AES-128-CFB', $passphrase, false, $iv ) ) { - - return $encryptedContent; - - } else { - - \OC_Log::write( 'Encryption library', 'Encryption (symmetric) of content failed' , \OC_Log::ERROR ); - - return false; - - } - - } - - /** - * @brief Symmetrically decrypt a string - * @returns decrypted file - */ - public static function decrypt( $encryptedContent, $iv, $passphrase ) { - - if ( $plainContent = openssl_decrypt( $encryptedContent, 'AES-128-CFB', $passphrase, false, $iv ) ) { - - return $plainContent; - - - } else { - - \OC_Log::write( 'Encryption library', 'Decryption (symmetric) of content failed' , \OC_Log::ERROR ); - - return false; - - } - - } - - /** - * @brief Symmetrically encrypts a string and returns keyfile content - * @param $plainContent content to be encrypted in keyfile - * @returns encrypted content combined with IV - * @note IV need not be specified, as it will be stored in the returned keyfile - * and remain accessible therein. - */ - public static function symmetricEncryptFileContent( $plainContent, $passphrase = '' ) { - - if ( !$plainContent ) { - - return false; - - } - - $iv = self::generateIv(); - - if ( $encryptedContent = self::encrypt( $plainContent, $iv, $passphrase ) ) { - - // Combine content to encrypt with IV identifier and actual IV - $combinedKeyfile = $encryptedContent . '00iv00' . $iv; - - return $combinedKeyfile; - - } else { - - \OC_Log::write( 'Encryption library', 'Encryption (symmetric) of keyfile content failed' , \OC_Log::ERROR ); - - return false; - - } - - } - - - /** - * @brief Symmetrically decrypts keyfile content - * @param string $source - * @param string $target - * @param string $key the decryption key - * - * This function decrypts a file - */ - public static function symmetricDecryptFileContent( $keyfileContent, $passphrase = '' ) { - - if ( !$keyfileContent ) { - - return false; - - } - - // Fetch IV from end of file - $iv = substr( $keyfileContent, -16 ); - - // Remove IV and IV identifier text to expose encrypted content - $encryptedContent = substr( $keyfileContent, 0, -22 ); - - if ( $plainContent = self::decrypt( $encryptedContent, $iv, $passphrase ) ) { - - return $plainContent; - - } else { - - \OC_Log::write( 'Encryption library', 'Decryption (symmetric) of keyfile content failed' , \OC_Log::ERROR ); - - return false; - - } - - } - - /** - * @brief Creates symmetric keyfile content using a generated key - * @param string $plainContent content to be encrypted - * @returns array keys: key, encrypted - * @note symmetricDecryptFileContent() can be used to decrypt files created using this method - * - * This function decrypts a file - */ - public static function symmetricEncryptFileContentKeyfile( $plainContent ) { - - $key = self::generateKey(); - - if( $encryptedContent = self::symmetricEncryptFileContent( $plainContent, $key ) ) { - - return array( - 'key' => $key - , 'encrypted' => $encryptedContent - ); - - } else { - - return false; - - } - - } - - /** - * @brief Create asymmetrically encrypted keyfile content using a generated key - * @param string $plainContent content to be encrypted - * @returns array keys: key, encrypted - * @note symmetricDecryptFileContent() can be used to decrypt files created using this method - * - * This function decrypts a file - */ - public static function multiKeyEncrypt( $plainContent, array $publicKeys ) { - - $envKeys = array(); - - if( openssl_seal( $plainContent, $sealed, $envKeys, $publicKeys ) ) { - - return array( - 'keys' => $envKeys - , 'encrypted' => $sealed - ); - - } else { - - return false; - - } - - } - - /** - * @brief Asymmetrically encrypt a file using multiple public keys - * @param string $plainContent content to be encrypted - * @returns array keys: key, encrypted - * @note symmetricDecryptFileContent() can be used to decrypt files created using this method - * - * This function decrypts a file - */ - public static function multiKeyDecrypt( $encryptedContent, $envKey, $privateKey ) { - - if ( !$encryptedContent ) { - - return false; - - } - - if ( openssl_open( $encryptedContent, $plainContent, $envKey, $privateKey ) ) { - - return $plainContent; - - } else { - - \OC_Log::write( 'Encryption library', 'Decryption (asymmetric) of sealed content failed' , \OC_Log::ERROR ); - - return false; - - } - - } - - /** - * @brief Asymetrically encrypt a string using a public key - * @returns encrypted file - */ - public static function keyEncrypt( $plainContent, $publicKey ) { - - openssl_public_encrypt( $plainContent, $encryptedContent, $publicKey ); - - return $encryptedContent; - - } - - /** - * @brief Asymetrically decrypt a file using a private key - * @returns decrypted file - */ - public static function keyDecrypt( $encryptedContent, $privatekey ) { - - openssl_private_decrypt( $encryptedContent, $plainContent, $privatekey ); - - return $plainContent; - - } - - /** - * @brief Generate a pseudo random 1024kb ASCII key - * @returns $key Generated key - */ - public static function generateIv() { - - if ( $random = openssl_random_pseudo_bytes( 13, $strong ) ) { - - if ( !$strong ) { - - // If OpenSSL indicates randomness is insecure, log error - \OC_Log::write( 'Encryption library', 'Insecure symmetric key was generated using openssl_random_pseudo_bytes()' , \OC_Log::WARN ); - - } - - $iv = substr( base64_encode( $random ), 0, -4 ); - - return $iv; - - } else { - - return false; - - } - - } - - /** - * @brief Generate a pseudo random 1024kb ASCII key - * @returns $key Generated key - */ - public static function generateKey() { - - // $key = mt_rand( 10000, 99999 ) . mt_rand( 10000, 99999 ) . mt_rand( 10000, 99999 ) . mt_rand( 10000, 99999 ); - - // Generate key - if ( $key = base64_encode( openssl_random_pseudo_bytes( 768000, $strong ) ) ) { - - if ( !$strong ) { - - // If OpenSSL indicates randomness is insecure, log error - \OC_Log::write( 'Encryption library', 'Insecure symmetric key was generated using openssl_random_pseudo_bytes()' , \OC_Log::WARN ); - - } - - return $key; - - } else { - - return false; - - } - - } - - public static function changekeypasscode($oldPassword, $newPassword) { - if(OCP\User::isLoggedIn()){ - $username=OCP\USER::getUser(); - $view=new OC_FilesystemView('/'.$username); - - // read old key - $key=$view->file_get_contents('/encryption.key'); - - // decrypt key with old passcode - $key=OC_Crypt::decrypt($key, $oldPassword); - - // encrypt again with new passcode - $key=OC_Crypt::encrypt($key, $newPassword); - - // store the new key - $view->file_put_contents('/encryption.key', $key ); - } - } - -} - + return 'server'; + + } + + /** + * @brief Create a new encryption keypair + * @return array publicKey, privatekey + */ + public static function createKeypair() { + + $res = openssl_pkey_new(); + + // Get private key + openssl_pkey_export( $res, $privateKey ); + + // Get public key + $publicKey = openssl_pkey_get_details( $res ); + + $publicKey = $publicKey['key']; + + return( array( 'publicKey' => $publicKey, 'privateKey' => $privateKey ) ); + + } + + /** + * @brief Check if a file's contents contains an IV and is symmetrically encrypted + * @return true / false + */ + public static function isEncryptedContent( $content ) { + + if ( !$content ) { + + return false; + + } + + // Fetch encryption metadata from end of file + $meta = substr( $content, -22 ); + + // Fetch IV from end of file + $iv = substr( $meta, -16 ); + + // Fetch identifier from start of metadata + $identifier = substr( $meta, 0, 6 ); + + if ( $identifier == '00iv00') { + + return true; + + } else { + + return false; + + } + + } + + /** + * @brief Check if a file is encrypted via legacy system + * @return true / false + */ + public static function isLegacyEncryptedContent( $content, $path ) { + + // Fetch all file metadata from DB + $metadata = \OC_FileCache_Cached::get( $content, '' ); + + // If a file is flagged with encryption in DB, but isn't a valid content + IV combination, it's probably using the legacy encryption system + if ( + $content + and isset( $metadata['encrypted'] ) + and $metadata['encrypted'] === true + and !self::isEncryptedContent( $content ) + ) { + + return true; + + } else { + + return false; + + } + + } + + /** + * @brief Symmetrically encrypt a string + * @returns encrypted file + */ + public static function encrypt( $plainContent, $iv, $passphrase = '' ) { + + if ( $encryptedContent = openssl_encrypt( $plainContent, 'AES-128-CFB', $passphrase, false, $iv ) ) { + + return $encryptedContent; + + } else { + + \OC_Log::write( 'Encryption library', 'Encryption (symmetric) of content failed' , \OC_Log::ERROR ); + + return false; + + } + + } + + /** + * @brief Symmetrically decrypt a string + * @returns decrypted file + */ + public static function decrypt( $encryptedContent, $iv, $passphrase ) { + + if ( $plainContent = openssl_decrypt( $encryptedContent, 'AES-128-CFB', $passphrase, false, $iv ) ) { + + return $plainContent; + + + } else { + + \OC_Log::write( 'Encryption library', 'Decryption (symmetric) of content failed' , \OC_Log::ERROR ); + + return false; + + } + + } + + /** + * @brief Symmetrically encrypts a string and returns keyfile content + * @param $plainContent content to be encrypted in keyfile + * @returns encrypted content combined with IV + * @note IV need not be specified, as it will be stored in the returned keyfile + * and remain accessible therein. + */ + public static function symmetricEncryptFileContent( $plainContent, $passphrase = '' ) { + + if ( !$plainContent ) { + + return false; + + } + + $iv = self::generateIv(); + + if ( $encryptedContent = self::encrypt( $plainContent, $iv, $passphrase ) ) { + + // Combine content to encrypt with IV identifier and actual IV + $combinedKeyfile = $encryptedContent . '00iv00' . $iv; + + return $combinedKeyfile; + + } else { + + \OC_Log::write( 'Encryption library', 'Encryption (symmetric) of keyfile content failed' , \OC_Log::ERROR ); + + return false; + + } + + } + + + /** + * @brief Symmetrically decrypts keyfile content + * @param string $source + * @param string $target + * @param string $key the decryption key + * + * This function decrypts a file + */ + public static function symmetricDecryptFileContent( $keyfileContent, $passphrase = '' ) { + + if ( !$keyfileContent ) { + + return false; + + } + + // Fetch IV from end of file + $iv = substr( $keyfileContent, -16 ); + + // Remove IV and IV identifier text to expose encrypted content + $encryptedContent = substr( $keyfileContent, 0, -22 ); + + if ( $plainContent = self::decrypt( $encryptedContent, $iv, $passphrase ) ) { + + return $plainContent; + + } else { + + \OC_Log::write( 'Encryption library', 'Decryption (symmetric) of keyfile content failed' , \OC_Log::ERROR ); + + return false; + + } + + } + + /** + * @brief Creates symmetric keyfile content using a generated key + * @param string $plainContent content to be encrypted + * @returns array keys: key, encrypted + * @note symmetricDecryptFileContent() can be used to decrypt files created using this method + * + * This function decrypts a file + */ + public static function symmetricEncryptFileContentKeyfile( $plainContent ) { + + $key = self::generateKey(); + + if( $encryptedContent = self::symmetricEncryptFileContent( $plainContent, $key ) ) { + + return array( + 'key' => $key + , 'encrypted' => $encryptedContent + ); + + } else { + + return false; + + } + + } + + /** + * @brief Create asymmetrically encrypted keyfile content using a generated key + * @param string $plainContent content to be encrypted + * @returns array keys: key, encrypted + * @note symmetricDecryptFileContent() can be used to decrypt files created using this method + * + * This function decrypts a file + */ + public static function multiKeyEncrypt( $plainContent, array $publicKeys ) { + + $envKeys = array(); + + if( openssl_seal( $plainContent, $sealed, $envKeys, $publicKeys ) ) { + + return array( + 'keys' => $envKeys + , 'encrypted' => $sealed + ); + + } else { + + return false; + + } + + } + + /** + * @brief Asymmetrically encrypt a file using multiple public keys + * @param string $plainContent content to be encrypted + * @returns array keys: key, encrypted + * @note symmetricDecryptFileContent() can be used to decrypt files created using this method + * + * This function decrypts a file + */ + public static function multiKeyDecrypt( $encryptedContent, $envKey, $privateKey ) { + + if ( !$encryptedContent ) { + + return false; + + } + + if ( openssl_open( $encryptedContent, $plainContent, $envKey, $privateKey ) ) { + + return $plainContent; + + } else { + + \OC_Log::write( 'Encryption library', 'Decryption (asymmetric) of sealed content failed' , \OC_Log::ERROR ); + + return false; + + } + + } + + /** + * @brief Asymetrically encrypt a string using a public key + * @returns encrypted file + */ + public static function keyEncrypt( $plainContent, $publicKey ) { + + openssl_public_encrypt( $plainContent, $encryptedContent, $publicKey ); + + return $encryptedContent; + + } + + /** + * @brief Asymetrically decrypt a file using a private key + * @returns decrypted file + */ + public static function keyDecrypt( $encryptedContent, $privatekey ) { + + openssl_private_decrypt( $encryptedContent, $plainContent, $privatekey ); + + return $plainContent; + + } + + /** + * @brief Generate a pseudo random 1024kb ASCII key + * @returns $key Generated key + */ + public static function generateIv() { + + if ( $random = openssl_random_pseudo_bytes( 13, $strong ) ) { + + if ( !$strong ) { + + // If OpenSSL indicates randomness is insecure, log error + \OC_Log::write( 'Encryption library', 'Insecure symmetric key was generated using openssl_random_pseudo_bytes()' , \OC_Log::WARN ); + + } + + $iv = substr( base64_encode( $random ), 0, -4 ); + + return $iv; + + } else { + + return false; + + } + + } + + /** + * @brief Generate a pseudo random 1024kb ASCII key + * @returns $key Generated key + */ + public static function generateKey() { + + // $key = mt_rand( 10000, 99999 ) . mt_rand( 10000, 99999 ) . mt_rand( 10000, 99999 ) . mt_rand( 10000, 99999 ); + + // Generate key + if ( $key = base64_encode( openssl_random_pseudo_bytes( 768000, $strong ) ) ) { + + if ( !$strong ) { + + // If OpenSSL indicates randomness is insecure, log error + \OC_Log::write( 'Encryption library', 'Insecure symmetric key was generated using openssl_random_pseudo_bytes()' , \OC_Log::WARN ); + + } + + return $key; + + } else { + + return false; + + } + + } + + public static function changekeypasscode($oldPassword, $newPassword) { + if(OCP\User::isLoggedIn()){ + $username=OCP\USER::getUser(); + $view=new OC_FilesystemView('/'.$username); + + // read old key + $key=$view->file_get_contents('/encryption.key'); + + // decrypt key with old passcode + $key=OC_Crypt::decrypt($key, $oldPassword); + + // encrypt again with new passcode + $key=OC_Crypt::encrypt($key, $newPassword); + + // store the new key + $view->file_put_contents('/encryption.key', $key ); + } + } + +} + ?> \ No newline at end of file diff --git a/apps/files_encryption/lib/cryptstream.php b/apps/files_encryption/lib/cryptstream.php index e0020537563..8c61c933cf8 100644 --- a/apps/files_encryption/lib/cryptstream.php +++ b/apps/files_encryption/lib/cryptstream.php @@ -28,11 +28,11 @@ */ class OC_CryptStream{ - public static $sourceStreams=array(); + 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 $readBuffer; // For streams that dont support seeking + private $meta = array(); // Header / meta for source stream private $count; private $writeCache; private $size; @@ -98,38 +98,69 @@ class OC_CryptStream{ 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=''; + 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){ + + 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); + + 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=''; + + $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); + + $encrypted = OC_Crypt::encrypt( substr( $data,0,8192 ) ); + + fwrite( $this->source,$encrypted ); + + $data = substr( $data,8192 ); + } + } - $this->size=max($this->size,$currentPos+$length); + + $this->size = max( $this->size,$currentPos+$length ); + return $length; + } + public function stream_set_option($option,$arg1,$arg2){ switch($option){ case STREAM_OPTION_BLOCKING: diff --git a/apps/files_encryption/lib/keymanager.php b/apps/files_encryption/lib/keymanager.php index bafe8f1a5f0..0c76bf27a52 100644 --- a/apps/files_encryption/lib/keymanager.php +++ b/apps/files_encryption/lib/keymanager.php @@ -27,7 +27,7 @@ namespace OCA_Encryption; */ class Keymanager { - # TODO: Try and get rid of username dependencies as these methods need to be used in a proxy class that doesn't have username access + # TODO: make all dependencies explicit, such as ocfsview objects, by adding them as method arguments (dependency injection) /** * @brief retrieve private key from a user @@ -60,9 +60,9 @@ class Keymanager { * @param string user name of the file owner * @return string file key or false */ - public static function getFileKey($userId, $path) { + public static function getFileKey( $userId, $path ) { - $keypath = ltrim($path, '/'); + $keypath = ltrim( $path, '/' ); $user = $userId; // update $keypath and $user if path point to a file shared by someone else @@ -127,29 +127,33 @@ class Keymanager { * @param string $path relative path of the file, including filename * @param string $key * @return bool true/false - */ + */ public static function setFileKey( $userId, $path, $key ) { \OC_FileProxy::$enabled = false; - $targetpath = ltrim($path, '/'); + $targetpath = ltrim( $path, '/' ); $user = $userId; // update $keytarget and $user if key belongs 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 = ?" ); - $result = $query->execute( array ('/'.$userId.'/files/'.$targetpath, $userId)); - if ($row = $result->fetchRow()){ + + $result = $query->execute( array ( '/'.$userId.'/files/'.$targetpath, $userId ) ); + + if ( $row = $result->fetchRow( ) ) { $targetpath = $row['source']; - $targetpath_parts=explode('/',$targetpath); + $targetpath_parts=explode( '/',$targetpath ); $user = $targetpath_parts[1]; - $targetpath = str_replace('/'.$user.'/files/', '', $targetpath); + $targetpath = str_replace( '/'.$user.'/files/', '', $targetpath ); //TODO: check for write permission on shared file once the new sharing API is in place } $view = new \OC_FilesystemView( '/' . $user . '/files_encryption/keyfiles' ); - $path_parts = pathinfo($targetpath); - if (!$view->file_exists($path_parts['dirname'])) $view->mkdir($path_parts['dirname']); + $path_parts = pathinfo( $targetpath ); + + if ( !$view->file_exists( $path_parts['dirname'] ) ) $view->mkdir( $path_parts['dirname'] ); + $result = $view->file_put_contents( '/' . $targetpath . '.key', $key ); \OC_FileProxy::$enabled = true; diff --git a/apps/files_encryption/lib/proxy.php b/apps/files_encryption/lib/proxy.php index 53ed05d2c3b..c1956ad0216 100644 --- a/apps/files_encryption/lib/proxy.php +++ b/apps/files_encryption/lib/proxy.php @@ -109,10 +109,14 @@ class Proxy extends \OC_FileProxy { // Replace plain content with encrypted content by reference $data = $encrypted['encrypted']; - # TODO: check if file is in subdirectories, and if so, create those parent directories. Or else monitor creation of directories using hooks to ensure path will always exist (what about existing directories when encryption is enabled?) + $filePath = explode( '/', $path ); - // Save keyfile for newly encrypted file in parallel directory - Keymanager::setFileKey( \OCP\USER::getUser(), $path, $encrypted['key'] ); + $filePath = array_slice( $filePath, 3 ); + + $filePath = '/' . implode( '/', $filePath ); + + // Save keyfile for newly encrypted file in parallel directory tree + Keymanager::setFileKey( \OCP\USER::getUser(), $filePath, $encrypted['key'] ); // Update the file cache with file info \OC_FileCache::put( $path, array( 'encrypted'=>true, 'size' => $size ), '' ); @@ -124,38 +128,80 @@ class Proxy extends \OC_FileProxy { public function postFile_get_contents( $path, $data ) { if ( Crypt::isEncryptedContent( $data ) ) { - trigger_error('best'); + + $filePath = explode( '/', $path ); + + $filePath = array_slice( $filePath, 3 ); + + $filePath = '/' . implode( '/', $filePath ); + + trigger_error( "CAT " . $filePath); + $cached = \OC_FileCache_Cached::get( $path, '' ); - $data = Crypt::symmetricDecryptFileContent( $data, $_SESSION['enckey'] ); + // Get keyfile for encrypted file + $keyFile = Keymanager::getFileKey( \OCP\USER::getUser(), $filePath ); + + $data = Crypt::symmetricDecryptFileContent( $data, $keyFile ); } return $data; + } - public function postFopen($path,&$result){ + public function postFopen( $path, &$result ){ - if(!$result){ + if ( !$result ) { + return $result; + } - $meta=stream_get_meta_data($result); - if(Crypt::isEncryptedContent($path)){ - fclose($result); - $result=fopen('crypt://'.$path,$meta['mode']); - }elseif(self::shouldEncrypt($path) and $meta['mode']!='r' and $meta['mode']!='rb'){ - if( \OC_Filesystem::file_exists( $path ) and \OC_Filesystem::filesize($path)>0){ + + $meta = stream_get_meta_data( $result ); + + // If file is encrypted, decrypt using crypto protocol + if ( Crypt::isEncryptedContent( $path ) ) { + + fclose ( $result ); + + $result = fopen( 'crypt://'.$path, $meta['mode'] ); + + } elseif ( + self::shouldEncrypt( $path ) + and $meta ['mode'] != 'r' + and $meta['mode'] != 'rb' + ) { + + # TODO: figure out what this does + + if ( + \OC_Filesystem::file_exists( $path ) + and \OC_Filesystem::filesize( $path ) > 0 + ) { + //first encrypt the target file so we don't end up with a half encrypted file - \OCP\Util::writeLog('files_encryption','Decrypting '.$path.' before writing', \OCP\Util::DEBUG); - $tmp=fopen('php://temp'); - \OCP\Files::streamCopy($result,$tmp); - fclose($result); - \OC_Filesystem::file_put_contents($path,$tmp); - fclose($tmp); + \OCP\Util::writeLog( 'files_encryption', 'Decrypting '.$path.' before writing', \OCP\Util::DEBUG ); + + $tmp = fopen( 'php://temp' ); + + \OCP\Files::streamCopy( $result, $tmp ); + + // Close the original stream, we'll return another one + fclose( $result ); + + \OC_Filesystem::file_put_contents( $path, $tmp ); + + fclose( $tmp ); + } - $result=fopen('crypt://'.$path,$meta['mode']); + + $result = fopen( 'crypt://'.$path, $meta['mode'] ); + } + return $result; + } public function postGetMimeType($path,$mime){ diff --git a/apps/files_encryption/lib/util.php b/apps/files_encryption/lib/util.php index ab58b4aa721..609f7871241 100644 --- a/apps/files_encryption/lib/util.php +++ b/apps/files_encryption/lib/util.php @@ -44,18 +44,19 @@ class Util { # DONE: add method to check if file is encrypted using old system # DONE: add method to fetch legacy key # DONE: add method to decrypt legacy encrypted data + # DONE: fix / test the crypt stream proxy class - # TODO: add method to encrypt all user files using new system - # TODO: add method to decrypt all user files using new system - # TODO: add method to encrypt all user files using old system - # TODO: add method to decrypt all user files using old system - - # TODO: 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: 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. # TODO: add UI buttons for encrypt / decrypt everything? + # TODO: add method to encrypt all user files using new system + # TODO: add method to decrypt all user files using new system + # TODO: add method to encrypt all user files using old system + # TODO: add method to decrypt all user files using old system + # TODO: test new encryption with webdav # TODO: test new encryption with versioning # TODO: test new encryption with sharing @@ -154,6 +155,89 @@ class Util { } + public function findFiles( $directory, $type = 'plain' ) { + + # TODO: test finding non plain content + + if ( $handle = $this->view->opendir( $directory ) ) { + + while ( false !== ( $file = readdir( $handle ) ) ) { + + if ( + $file != "." + && $file != ".." + ) { + + $filePath = $directory . '/' . $this->view->getRelativePath( '/' . $file ); + + var_dump($filePath); + + if ( $this->view->is_dir( $filePath ) ) { + + $this->findFiles( $filePath ); + + } 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 ); + + } + + } elseif ( $type == 'legacy' ) { + + if ( Crypt::isLegacyEncryptedContent( $this->view->file_get_contents( $filePath ) ) ) { + + $this->files[] = array( 'name' => $file, 'path' => $filePath ); + + } + + } + + } + + } + + } + + if ( !empty( $this->files ) ) { + + return $this->files; + + } else { + + return false; + + } + + } + + return false; + + } + + public function encryptAll( OC_FilesystemView $view ) { + + $plainFiles = $this->findPlainFiles( $view ); + + if ( $this->encryptFiles( $plainFiles ) ) { + + return true; + + } else { + + return false; + + } + + } + /** * @brief Get the blowfish encryption handeler for a key * @param $key string (optional) -- cgit v1.2.3 From c4d1ad1b7d4507e387a5833622b4831044eb9e09 Mon Sep 17 00:00:00 2001 From: Sam Tuke Date: Wed, 1 Aug 2012 14:11:41 +0100 Subject: Made dependencies of Kaymanager::setFileKey() explicit using dependency injection --- apps/files_encryption/lib/crypt.php | 822 +++++++++++++++---------------- apps/files_encryption/lib/keymanager.php | 21 +- apps/files_encryption/lib/proxy.php | 5 +- 3 files changed, 427 insertions(+), 421 deletions(-) (limited to 'apps/files_encryption/lib/proxy.php') diff --git a/apps/files_encryption/lib/crypt.php b/apps/files_encryption/lib/crypt.php index 8cd8de73bce..7e50c900fa1 100644 --- a/apps/files_encryption/lib/crypt.php +++ b/apps/files_encryption/lib/crypt.php @@ -1,37 +1,37 @@ -. - * - */ - -namespace OCA_Encryption; - -/** - * Class for common cryptography functionality - */ - -class Crypt { - +. + * + */ + +namespace OCA_Encryption; + +/** + * Class for common cryptography functionality + */ + +class Crypt { + /** - * @brief return encryption mode client or server side encryption + * @brief return encryption mode client or server side encryption * @param string user name (use system wide setting if name=null) * @return string 'client' or 'server' */ @@ -48,382 +48,382 @@ class Crypt { } return $mode; - } - - /** - * @brief Create a new encryption keypair - * @return array publicKey, privatekey - */ - public static function createKeypair() { - - $res = openssl_pkey_new(); - - // Get private key - openssl_pkey_export( $res, $privateKey ); - - // Get public key - $publicKey = openssl_pkey_get_details( $res ); - - $publicKey = $publicKey['key']; - - return( array( 'publicKey' => $publicKey, 'privateKey' => $privateKey ) ); - - } - - /** - * @brief Check if a file's contents contains an IV and is symmetrically encrypted - * @return true / false - */ - public static function isEncryptedContent( $content ) { - - if ( !$content ) { - - return false; - - } - - // Fetch encryption metadata from end of file - $meta = substr( $content, -22 ); - - // Fetch IV from end of file - $iv = substr( $meta, -16 ); - - // Fetch identifier from start of metadata - $identifier = substr( $meta, 0, 6 ); - - if ( $identifier == '00iv00') { - - return true; - - } else { - - return false; - - } - - } - - /** - * @brief Check if a file is encrypted via legacy system - * @return true / false - */ - public static function isLegacyEncryptedContent( $content, $path ) { - - // Fetch all file metadata from DB - $metadata = \OC_FileCache_Cached::get( $content, '' ); - - // If a file is flagged with encryption in DB, but isn't a valid content + IV combination, it's probably using the legacy encryption system - if ( - $content - and isset( $metadata['encrypted'] ) - and $metadata['encrypted'] === true - and !self::isEncryptedContent( $content ) - ) { - - return true; - - } else { - - return false; - - } - - } - - /** - * @brief Symmetrically encrypt a string - * @returns encrypted file - */ - public static function encrypt( $plainContent, $iv, $passphrase = '' ) { - - if ( $encryptedContent = openssl_encrypt( $plainContent, 'AES-128-CFB', $passphrase, false, $iv ) ) { - - return $encryptedContent; - - } else { - - \OC_Log::write( 'Encryption library', 'Encryption (symmetric) of content failed' , \OC_Log::ERROR ); - - return false; - - } - - } - - /** - * @brief Symmetrically decrypt a string - * @returns decrypted file - */ - public static function decrypt( $encryptedContent, $iv, $passphrase ) { - - if ( $plainContent = openssl_decrypt( $encryptedContent, 'AES-128-CFB', $passphrase, false, $iv ) ) { - - return $plainContent; - - - } else { - - \OC_Log::write( 'Encryption library', 'Decryption (symmetric) of content failed' , \OC_Log::ERROR ); - - return false; - - } - - } - - /** - * @brief Symmetrically encrypts a string and returns keyfile content - * @param $plainContent content to be encrypted in keyfile - * @returns encrypted content combined with IV - * @note IV need not be specified, as it will be stored in the returned keyfile - * and remain accessible therein. - */ - public static function symmetricEncryptFileContent( $plainContent, $passphrase = '' ) { - - if ( !$plainContent ) { - - return false; - - } - - $iv = self::generateIv(); - - if ( $encryptedContent = self::encrypt( $plainContent, $iv, $passphrase ) ) { - - // Combine content to encrypt with IV identifier and actual IV - $combinedKeyfile = $encryptedContent . '00iv00' . $iv; - - return $combinedKeyfile; - - } else { - - \OC_Log::write( 'Encryption library', 'Encryption (symmetric) of keyfile content failed' , \OC_Log::ERROR ); - - return false; - - } - - } - - - /** - * @brief Symmetrically decrypts keyfile content - * @param string $source - * @param string $target - * @param string $key the decryption key - * - * This function decrypts a file - */ - public static function symmetricDecryptFileContent( $keyfileContent, $passphrase = '' ) { - - if ( !$keyfileContent ) { - - return false; - - } - - // Fetch IV from end of file - $iv = substr( $keyfileContent, -16 ); - - // Remove IV and IV identifier text to expose encrypted content - $encryptedContent = substr( $keyfileContent, 0, -22 ); - - if ( $plainContent = self::decrypt( $encryptedContent, $iv, $passphrase ) ) { - - return $plainContent; - - } else { - - \OC_Log::write( 'Encryption library', 'Decryption (symmetric) of keyfile content failed' , \OC_Log::ERROR ); - - return false; - - } - - } - - /** - * @brief Creates symmetric keyfile content using a generated key - * @param string $plainContent content to be encrypted - * @returns array keys: key, encrypted - * @note symmetricDecryptFileContent() can be used to decrypt files created using this method - * - * This function decrypts a file - */ - public static function symmetricEncryptFileContentKeyfile( $plainContent ) { - - $key = self::generateKey(); - - if( $encryptedContent = self::symmetricEncryptFileContent( $plainContent, $key ) ) { - - return array( - 'key' => $key - , 'encrypted' => $encryptedContent - ); - - } else { - - return false; - - } - - } - - /** - * @brief Create asymmetrically encrypted keyfile content using a generated key - * @param string $plainContent content to be encrypted - * @returns array keys: key, encrypted - * @note symmetricDecryptFileContent() can be used to decrypt files created using this method - * - * This function decrypts a file - */ - public static function multiKeyEncrypt( $plainContent, array $publicKeys ) { - - $envKeys = array(); - - if( openssl_seal( $plainContent, $sealed, $envKeys, $publicKeys ) ) { - - return array( - 'keys' => $envKeys - , 'encrypted' => $sealed - ); - - } else { - - return false; - - } - - } - - /** - * @brief Asymmetrically encrypt a file using multiple public keys - * @param string $plainContent content to be encrypted - * @returns array keys: key, encrypted - * @note symmetricDecryptFileContent() can be used to decrypt files created using this method - * - * This function decrypts a file - */ - public static function multiKeyDecrypt( $encryptedContent, $envKey, $privateKey ) { - - if ( !$encryptedContent ) { - - return false; - - } - - if ( openssl_open( $encryptedContent, $plainContent, $envKey, $privateKey ) ) { - - return $plainContent; - - } else { - - \OC_Log::write( 'Encryption library', 'Decryption (asymmetric) of sealed content failed' , \OC_Log::ERROR ); - - return false; - - } - - } - - /** - * @brief Asymetrically encrypt a string using a public key - * @returns encrypted file - */ - public static function keyEncrypt( $plainContent, $publicKey ) { - - openssl_public_encrypt( $plainContent, $encryptedContent, $publicKey ); - - return $encryptedContent; - - } - - /** - * @brief Asymetrically decrypt a file using a private key - * @returns decrypted file - */ - public static function keyDecrypt( $encryptedContent, $privatekey ) { - - openssl_private_decrypt( $encryptedContent, $plainContent, $privatekey ); - - return $plainContent; - - } - - /** - * @brief Generate a pseudo random 1024kb ASCII key - * @returns $key Generated key - */ - public static function generateIv() { - - if ( $random = openssl_random_pseudo_bytes( 13, $strong ) ) { - - if ( !$strong ) { - - // If OpenSSL indicates randomness is insecure, log error - \OC_Log::write( 'Encryption library', 'Insecure symmetric key was generated using openssl_random_pseudo_bytes()' , \OC_Log::WARN ); - - } - - $iv = substr( base64_encode( $random ), 0, -4 ); - - return $iv; - - } else { - - return false; - - } - - } - - /** - * @brief Generate a pseudo random 1024kb ASCII key - * @returns $key Generated key - */ - public static function generateKey() { - - // $key = mt_rand( 10000, 99999 ) . mt_rand( 10000, 99999 ) . mt_rand( 10000, 99999 ) . mt_rand( 10000, 99999 ); - - // Generate key - if ( $key = base64_encode( openssl_random_pseudo_bytes( 768000, $strong ) ) ) { - - if ( !$strong ) { - - // If OpenSSL indicates randomness is insecure, log error - \OC_Log::write( 'Encryption library', 'Insecure symmetric key was generated using openssl_random_pseudo_bytes()' , \OC_Log::WARN ); - - } - - return $key; - - } else { - - return false; - - } - - } - - public static function changekeypasscode($oldPassword, $newPassword) { - if(OCP\User::isLoggedIn()){ - $username=OCP\USER::getUser(); - $view=new OC_FilesystemView('/'.$username); - - // read old key - $key=$view->file_get_contents('/encryption.key'); - - // decrypt key with old passcode - $key=OC_Crypt::decrypt($key, $oldPassword); - - // encrypt again with new passcode - $key=OC_Crypt::encrypt($key, $newPassword); - - // store the new key - $view->file_put_contents('/encryption.key', $key ); - } - } - -} - + } + + /** + * @brief Create a new encryption keypair + * @return array publicKey, privatekey + */ + public static function createKeypair() { + + $res = openssl_pkey_new(); + + // Get private key + openssl_pkey_export( $res, $privateKey ); + + // Get public key + $publicKey = openssl_pkey_get_details( $res ); + + $publicKey = $publicKey['key']; + + return( array( 'publicKey' => $publicKey, 'privateKey' => $privateKey ) ); + + } + + /** + * @brief Check if a file's contents contains an IV and is symmetrically encrypted + * @return true / false + */ + public static function isEncryptedContent( $content ) { + + if ( !$content ) { + + return false; + + } + + // Fetch encryption metadata from end of file + $meta = substr( $content, -22 ); + + // Fetch IV from end of file + $iv = substr( $meta, -16 ); + + // Fetch identifier from start of metadata + $identifier = substr( $meta, 0, 6 ); + + if ( $identifier == '00iv00') { + + return true; + + } else { + + return false; + + } + + } + + /** + * @brief Check if a file is encrypted via legacy system + * @return true / false + */ + public static function isLegacyEncryptedContent( $content, $path ) { + + // Fetch all file metadata from DB + $metadata = \OC_FileCache_Cached::get( $content, '' ); + + // If a file is flagged with encryption in DB, but isn't a valid content + IV combination, it's probably using the legacy encryption system + if ( + $content + and isset( $metadata['encrypted'] ) + and $metadata['encrypted'] === true + and !self::isEncryptedContent( $content ) + ) { + + return true; + + } else { + + return false; + + } + + } + + /** + * @brief Symmetrically encrypt a string + * @returns encrypted file + */ + public static function encrypt( $plainContent, $iv, $passphrase = '' ) { + + if ( $encryptedContent = openssl_encrypt( $plainContent, 'AES-128-CFB', $passphrase, false, $iv ) ) { + + return $encryptedContent; + + } else { + + \OC_Log::write( 'Encryption library', 'Encryption (symmetric) of content failed' , \OC_Log::ERROR ); + + return false; + + } + + } + + /** + * @brief Symmetrically decrypt a string + * @returns decrypted file + */ + public static function decrypt( $encryptedContent, $iv, $passphrase ) { + + if ( $plainContent = openssl_decrypt( $encryptedContent, 'AES-128-CFB', $passphrase, false, $iv ) ) { + + return $plainContent; + + + } else { + + \OC_Log::write( 'Encryption library', 'Decryption (symmetric) of content failed' , \OC_Log::ERROR ); + + return false; + + } + + } + + /** + * @brief Symmetrically encrypts a string and returns keyfile content + * @param $plainContent content to be encrypted in keyfile + * @returns encrypted content combined with IV + * @note IV need not be specified, as it will be stored in the returned keyfile + * and remain accessible therein. + */ + public static function symmetricEncryptFileContent( $plainContent, $passphrase = '' ) { + + if ( !$plainContent ) { + + return false; + + } + + $iv = self::generateIv(); + + if ( $encryptedContent = self::encrypt( $plainContent, $iv, $passphrase ) ) { + + // Combine content to encrypt with IV identifier and actual IV + $combinedKeyfile = $encryptedContent . '00iv00' . $iv; + + return $combinedKeyfile; + + } else { + + \OC_Log::write( 'Encryption library', 'Encryption (symmetric) of keyfile content failed' , \OC_Log::ERROR ); + + return false; + + } + + } + + + /** + * @brief Symmetrically decrypts keyfile content + * @param string $source + * @param string $target + * @param string $key the decryption key + * + * This function decrypts a file + */ + public static function symmetricDecryptFileContent( $keyfileContent, $passphrase = '' ) { + + if ( !$keyfileContent ) { + + return false; + + } + + // Fetch IV from end of file + $iv = substr( $keyfileContent, -16 ); + + // Remove IV and IV identifier text to expose encrypted content + $encryptedContent = substr( $keyfileContent, 0, -22 ); + + if ( $plainContent = self::decrypt( $encryptedContent, $iv, $passphrase ) ) { + + return $plainContent; + + } else { + + \OC_Log::write( 'Encryption library', 'Decryption (symmetric) of keyfile content failed' , \OC_Log::ERROR ); + + return false; + + } + + } + + /** + * @brief Creates symmetric keyfile content using a generated key + * @param string $plainContent content to be encrypted + * @returns array keys: key, encrypted + * @note symmetricDecryptFileContent() can be used to decrypt files created using this method + * + * This function decrypts a file + */ + public static function symmetricEncryptFileContentKeyfile( $plainContent ) { + + $key = self::generateKey(); + + if( $encryptedContent = self::symmetricEncryptFileContent( $plainContent, $key ) ) { + + return array( + 'key' => $key + , 'encrypted' => $encryptedContent + ); + + } else { + + return false; + + } + + } + + /** + * @brief Create asymmetrically encrypted keyfile content using a generated key + * @param string $plainContent content to be encrypted + * @returns array keys: key, encrypted + * @note symmetricDecryptFileContent() can be used to decrypt files created using this method + * + * This function decrypts a file + */ + public static function multiKeyEncrypt( $plainContent, array $publicKeys ) { + + $envKeys = array(); + + if( openssl_seal( $plainContent, $sealed, $envKeys, $publicKeys ) ) { + + return array( + 'keys' => $envKeys + , 'encrypted' => $sealed + ); + + } else { + + return false; + + } + + } + + /** + * @brief Asymmetrically encrypt a file using multiple public keys + * @param string $plainContent content to be encrypted + * @returns array keys: key, encrypted + * @note symmetricDecryptFileContent() can be used to decrypt files created using this method + * + * This function decrypts a file + */ + public static function multiKeyDecrypt( $encryptedContent, $envKey, $privateKey ) { + + if ( !$encryptedContent ) { + + return false; + + } + + if ( openssl_open( $encryptedContent, $plainContent, $envKey, $privateKey ) ) { + + return $plainContent; + + } else { + + \OC_Log::write( 'Encryption library', 'Decryption (asymmetric) of sealed content failed' , \OC_Log::ERROR ); + + return false; + + } + + } + + /** + * @brief Asymetrically encrypt a string using a public key + * @returns encrypted file + */ + public static function keyEncrypt( $plainContent, $publicKey ) { + + openssl_public_encrypt( $plainContent, $encryptedContent, $publicKey ); + + return $encryptedContent; + + } + + /** + * @brief Asymetrically decrypt a file using a private key + * @returns decrypted file + */ + public static function keyDecrypt( $encryptedContent, $privatekey ) { + + openssl_private_decrypt( $encryptedContent, $plainContent, $privatekey ); + + return $plainContent; + + } + + /** + * @brief Generate a pseudo random 1024kb ASCII key + * @returns $key Generated key + */ + public static function generateIv() { + + if ( $random = openssl_random_pseudo_bytes( 13, $strong ) ) { + + if ( !$strong ) { + + // If OpenSSL indicates randomness is insecure, log error + \OC_Log::write( 'Encryption library', 'Insecure symmetric key was generated using openssl_random_pseudo_bytes()' , \OC_Log::WARN ); + + } + + $iv = substr( base64_encode( $random ), 0, -4 ); + + return $iv; + + } else { + + return false; + + } + + } + + /** + * @brief Generate a pseudo random 1024kb ASCII key + * @returns $key Generated key + */ + public static function generateKey() { + + // $key = mt_rand( 10000, 99999 ) . mt_rand( 10000, 99999 ) . mt_rand( 10000, 99999 ) . mt_rand( 10000, 99999 ); + + // Generate key + if ( $key = base64_encode( openssl_random_pseudo_bytes( 768000, $strong ) ) ) { + + if ( !$strong ) { + + // If OpenSSL indicates randomness is insecure, log error + \OC_Log::write( 'Encryption library', 'Insecure symmetric key was generated using openssl_random_pseudo_bytes()' , \OC_Log::WARN ); + + } + + return $key; + + } else { + + return false; + + } + + } + + public static function changekeypasscode($oldPassword, $newPassword) { + if(OCP\User::isLoggedIn()){ + $username=OCP\USER::getUser(); + $view=new OC_FilesystemView('/'.$username); + + // read old key + $key=$view->file_get_contents('/encryption.key'); + + // decrypt key with old passcode + $key=OC_Crypt::decrypt($key, $oldPassword); + + // encrypt again with new passcode + $key=OC_Crypt::encrypt($key, $newPassword); + + // store the new key + $view->file_put_contents('/encryption.key', $key ); + } + } + +} + ?> \ No newline at end of file diff --git a/apps/files_encryption/lib/keymanager.php b/apps/files_encryption/lib/keymanager.php index 0c76bf27a52..7f67fc7e5ea 100644 --- a/apps/files_encryption/lib/keymanager.php +++ b/apps/files_encryption/lib/keymanager.php @@ -27,7 +27,7 @@ namespace OCA_Encryption; */ class Keymanager { - # TODO: make all dependencies explicit, such as ocfsview objects, by adding them as method arguments (dependency injection) + # TODO: make all dependencies (including static classes) explicit, such as ocfsview objects, by adding them as method arguments (dependency injection) /** * @brief retrieve private key from a user @@ -128,35 +128,38 @@ class Keymanager { * @param string $key * @return bool true/false */ - public static function setFileKey( $userId, $path, $key ) { + public static function setFileKey( $user, $path, $key, $view, $dbClassName, $fileProxyClassName ) { - \OC_FileProxy::$enabled = false; + $fileProxyClassName::$enabled = false; $targetpath = ltrim( $path, '/' ); - $user = $userId; // update $keytarget and $user if key belongs 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 = ?" ); + $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 ) ); + $result = $query->execute( array ( '/'.$user.'/files/'.$targetpath, $user ) ); if ( $row = $result->fetchRow( ) ) { + $targetpath = $row['source']; + $targetpath_parts=explode( '/',$targetpath ); + $user = $targetpath_parts[1]; + $targetpath = str_replace( '/'.$user.'/files/', '', $targetpath ); + //TODO: check for write permission on shared file once the new sharing API is in place + } - $view = new \OC_FilesystemView( '/' . $user . '/files_encryption/keyfiles' ); - $path_parts = pathinfo( $targetpath ); if ( !$view->file_exists( $path_parts['dirname'] ) ) $view->mkdir( $path_parts['dirname'] ); $result = $view->file_put_contents( '/' . $targetpath . '.key', $key ); - \OC_FileProxy::$enabled = true; + $fileProxyClassName::$enabled = true; return $result; } diff --git a/apps/files_encryption/lib/proxy.php b/apps/files_encryption/lib/proxy.php index c1956ad0216..94f427f2f22 100644 --- a/apps/files_encryption/lib/proxy.php +++ b/apps/files_encryption/lib/proxy.php @@ -115,8 +115,11 @@ class Proxy extends \OC_FileProxy { $filePath = '/' . implode( '/', $filePath ); + # TODO: make keyfile dir dynamic from app config + $view = new \OC_FilesystemView( '/' . \OCP\USER::getUser() . '/files_encryption/keyfiles' ); + // Save keyfile for newly encrypted file in parallel directory tree - Keymanager::setFileKey( \OCP\USER::getUser(), $filePath, $encrypted['key'] ); + Keymanager::setFileKey( \OCP\USER::getUser(), $filePath, $encrypted['key'], $view, '\OC_DB', '\OC_FileProxy' ); // Update the file cache with file info \OC_FileCache::put( $path, array( 'encrypted'=>true, 'size' => $size ), '' ); -- cgit v1.2.3 From 12628be38bd8a8225139c19c6725255fcebc3d93 Mon Sep 17 00:00:00 2001 From: Bjoern Schiessle Date: Fri, 10 Aug 2012 11:44:38 +0200 Subject: only call proxies for server side encryption --- apps/files_encryption/lib/keymanager.php | 31 ++++--------------------------- apps/files_encryption/lib/proxy.php | 2 +- 2 files changed, 5 insertions(+), 28 deletions(-) (limited to 'apps/files_encryption/lib/proxy.php') diff --git a/apps/files_encryption/lib/keymanager.php b/apps/files_encryption/lib/keymanager.php index 43ac67f8cba..0705205682a 100644 --- a/apps/files_encryption/lib/keymanager.php +++ b/apps/files_encryption/lib/keymanager.php @@ -35,15 +35,11 @@ class Keymanager { * @return string private key or false */ public static function getPrivateKey() { - - \OC_FileProxy::$enabled = false; - + $user = \OCP\User::getUser(); $view = new \OC_FilesystemView( '/' . $user . '/' . 'files_encryption' ); $result = $view->file_get_contents( '/' . $user.'.private.key' ); - - \OC_FileProxy::$enabled = true; - + return $result; } @@ -80,8 +76,6 @@ class Keymanager { } } - \OC_FileProxy::$enabled = false; - $view = new \OC_FilesystemView( '/public-keys/' ); $keylist = array(); @@ -89,8 +83,6 @@ class Keymanager { foreach ($users as $user) { $keylist['key'.++$count] = $view->file_get_contents($user.'.public.key'); } - - \OC_FileProxy::$enabled = true; return $keylist; @@ -117,13 +109,9 @@ class Keymanager { $keypath = str_replace('/'.$user.'/files/', '', $keypath); } - \OC_FileProxy::$enabled = false; - $view = new \OC_FilesystemView('/'.$user.'/files_encryption/keyfiles/'); $result = $view->file_get_contents($keypath.'.key'); - - \OC_FileProxy::$enabled = true; - + return $result; } @@ -134,16 +122,12 @@ class Keymanager { * @return bool true/false */ public static function setPrivateKey($key) { - - \OC_FileProxy::$enabled = false; $user = \OCP\User::getUser(); $view = new \OC_FilesystemView('/'.$user.'/files_encryption'); if (!$view->file_exists('')) $view->mkdir(''); $result = $view->file_put_contents($user.'.private.key', $key); - \OC_FileProxy::$enabled = true; - return $result; } @@ -156,14 +140,10 @@ class Keymanager { */ public static function setPublicKey($key) { - \OC_FileProxy::$enabled = false; - $view = new \OC_FilesystemView('/public-keys'); if (!$view->file_exists('')) $view->mkdir(''); $result = $view->file_put_contents(\OCP\User::getUser().'.public.key', $key); - \OC_FileProxy::$enabled = true; - return $result; } @@ -174,8 +154,7 @@ class Keymanager { * @param string $key * @return bool true/false */ - public static function setFileKey( $path, $key, $view = Null, $dbClassName = '\OC_DB', $fileProxyClassName = '\OC_FileProxy') { - $fileProxyClassName::$enabled = false; + public static function setFileKey( $path, $key, $view = Null, $dbClassName = '\OC_DB') { $targetpath = ltrim( $path, '/' ); $user = \OCP\User::getUser(); @@ -209,8 +188,6 @@ class Keymanager { $result = $view->file_put_contents( '/' . $targetpath . '.key', $key ); - $fileProxyClassName::$enabled = true; - return $result; } diff --git a/apps/files_encryption/lib/proxy.php b/apps/files_encryption/lib/proxy.php index 94f427f2f22..32b7a67e655 100644 --- a/apps/files_encryption/lib/proxy.php +++ b/apps/files_encryption/lib/proxy.php @@ -46,7 +46,7 @@ class Proxy extends \OC_FileProxy { if ( is_null( self::$enableEncryption ) ) { - self::$enableEncryption = ( \OCP\Config::getAppValue( 'files_encryption', 'enable_encryption', 'true' ) == 'true' ); + self::$enableEncryption = ( \OCP\Config::getAppValue( 'files_encryption', 'enable_encryption', 'true' ) == 'true' && Crypt::mode() == 'server' ); } -- cgit v1.2.3 From e4450d10354a23d8b20eea6657fb7bd1e57580fa Mon Sep 17 00:00:00 2001 From: Bjoern Schiessle Date: Fri, 10 Aug 2012 12:27:09 +0200 Subject: execute file hooks only if server side encryption is enabled --- apps/files_encryption/hooks/hooks.php | 2 +- apps/files_encryption/lib/keymanager.php | 21 +++++++++------------ apps/files_encryption/lib/proxy.php | 12 ++++++------ 3 files changed, 16 insertions(+), 19 deletions(-) (limited to 'apps/files_encryption/lib/proxy.php') diff --git a/apps/files_encryption/hooks/hooks.php b/apps/files_encryption/hooks/hooks.php index b7e8df9eac0..a9b3b2bcd8f 100644 --- a/apps/files_encryption/hooks/hooks.php +++ b/apps/files_encryption/hooks/hooks.php @@ -63,7 +63,7 @@ class Hooks { * @brief update the encryption key of the file uploaded by the client */ public static function updateKeyfile( $params ) { - if (Crypt::mode(\OCP\User::getUser()) == 'client') + if (Crypt::mode() == 'client') if (isset($params['properties']['key'])) { Keymanager::setFileKey($params['path'], $params['properties']['key']); } else { diff --git a/apps/files_encryption/lib/keymanager.php b/apps/files_encryption/lib/keymanager.php index 0705205682a..42aaf9b60bf 100644 --- a/apps/files_encryption/lib/keymanager.php +++ b/apps/files_encryption/lib/keymanager.php @@ -38,9 +38,8 @@ class Keymanager { $user = \OCP\User::getUser(); $view = new \OC_FilesystemView( '/' . $user . '/' . 'files_encryption' ); - $result = $view->file_get_contents( '/' . $user.'.private.key' ); - - return $result; + return $view->file_get_contents( '/' . $user.'.private.key' ); + } /** @@ -110,9 +109,8 @@ class Keymanager { } $view = new \OC_FilesystemView('/'.$user.'/files_encryption/keyfiles/'); - $result = $view->file_get_contents($keypath.'.key'); - - return $result; + return $view->file_get_contents($keypath.'.key'); + } /** @@ -126,9 +124,8 @@ class Keymanager { $user = \OCP\User::getUser(); $view = new \OC_FilesystemView('/'.$user.'/files_encryption'); if (!$view->file_exists('')) $view->mkdir(''); - $result = $view->file_put_contents($user.'.private.key', $key); + return $view->file_put_contents($user.'.private.key', $key); - return $result; } @@ -142,9 +139,8 @@ class Keymanager { $view = new \OC_FilesystemView('/public-keys'); if (!$view->file_exists('')) $view->mkdir(''); - $result = $view->file_put_contents(\OCP\User::getUser().'.public.key', $key); + return $view->file_put_contents(\OCP\User::getUser().'.public.key', $key); - return $result; } /** @@ -186,9 +182,8 @@ class Keymanager { if ( !$view->file_exists( $path_parts['dirname'] ) ) $view->mkdir( $path_parts['dirname'] ); - $result = $view->file_put_contents( '/' . $targetpath . '.key', $key ); + return $view->file_put_contents( '/' . $targetpath . '.key', $key ); - return $result; } /** @@ -199,6 +194,7 @@ class Keymanager { * @return bool true/false */ public static function changePasswd($oldpasswd, $newpasswd) { + if ( \OCP\User::checkPassword(\OCP\User::getUser(), $newpasswd) ) { $key = Keymanager::getPrivateKey(); if ( ($key = Crypt::symmetricDecryptFileContent($key,$oldpasswd)) ) { @@ -209,6 +205,7 @@ class Keymanager { } } return false; + } } \ No newline at end of file diff --git a/apps/files_encryption/lib/proxy.php b/apps/files_encryption/lib/proxy.php index 32b7a67e655..85b5c868f30 100644 --- a/apps/files_encryption/lib/proxy.php +++ b/apps/files_encryption/lib/proxy.php @@ -40,7 +40,7 @@ class Proxy extends \OC_FileProxy { * @param string $path * @return bool * - * Tests if encryption is enabled, and file is allowed by blacklists + * Tests if server side encryption is enabled, and file is allowed by blacklists */ private static function shouldEncrypt( $path ) { @@ -130,7 +130,7 @@ class Proxy extends \OC_FileProxy { public function postFile_get_contents( $path, $data ) { - if ( Crypt::isEncryptedContent( $data ) ) { + if ( Crypt::mode() == 'server' && Crypt::isEncryptedContent( $data ) ) { $filePath = explode( '/', $path ); @@ -164,7 +164,7 @@ class Proxy extends \OC_FileProxy { $meta = stream_get_meta_data( $result ); // If file is encrypted, decrypt using crypto protocol - if ( Crypt::isEncryptedContent( $path ) ) { + if ( Crypt::mode() == 'server' && Crypt::isEncryptedContent( $path ) ) { fclose ( $result ); @@ -208,14 +208,14 @@ class Proxy extends \OC_FileProxy { } public function postGetMimeType($path,$mime){ - if(Crypt::isEncryptedContent($path)){ + if( Crypt::isEncryptedContent($path)){ $mime = \OCP\Files::getMimeType('crypt://'.$path,'w'); } return $mime; } public function postStat($path,$data){ - if(Crypt::isEncryptedContent($path)){ + if( Crypt::isEncryptedContent($path)){ $cached= \OC_FileCache_Cached::get($path,''); $data['size']=$cached['size']; } @@ -223,7 +223,7 @@ class Proxy extends \OC_FileProxy { } public function postFileSize($path,$size){ - if(Crypt::isEncryptedContent($path)){ + if( Crypt::isEncryptedContent($path)){ $cached = \OC_FileCache_Cached::get($path,''); return $cached['size']; }else{ -- cgit v1.2.3 From 6ce315fe5826d058722353f3d4c0b2f025dabd43 Mon Sep 17 00:00:00 2001 From: Sam Tuke Date: Tue, 14 Aug 2012 19:06:56 +0100 Subject: added wrapper method in crypt class for encrypting asymmetric and symmetric simultaneously fixed bugs with keymanager integration added unit tests --- apps/files_encryption/hooks/hooks.php | 145 ++++++++--------- apps/files_encryption/lib/crypt.php | 51 +++++- apps/files_encryption/lib/keymanager.php | 27 +++- apps/files_encryption/lib/proxy.php | 25 ++- apps/files_encryption/tests/crypt.php | 245 +++++++++++++++++++++++++++++ apps/files_encryption/tests/encryption.php | 205 ------------------------ 6 files changed, 400 insertions(+), 298 deletions(-) create mode 100644 apps/files_encryption/tests/crypt.php delete mode 100644 apps/files_encryption/tests/encryption.php (limited to 'apps/files_encryption/lib/proxy.php') diff --git a/apps/files_encryption/hooks/hooks.php b/apps/files_encryption/hooks/hooks.php index 5215ac10624..e23e3a09d46 100644 --- a/apps/files_encryption/hooks/hooks.php +++ b/apps/files_encryption/hooks/hooks.php @@ -1,76 +1,77 @@ -. - * - */ - -namespace OCA_Encryption; - -/** - * Class for hook specific logic - */ - -class Hooks { - - # TODO: use passphrase for encrypting private key that is separate to the login password - - /** - * @brief Startup encryption backend upon user login - * @note This method should never be called for users using client side encryption - */ - - public static function login( $params ) { - - if ( Crypt::mode( $params['uid'] ) == 'server' ) { - - $view = new \OC_FilesystemView( '/' ); - - $util = new Util( $view, $params['uid'] ); - - if ( !$util->ready()) { - - return $util->setupServerSide( $params['password'] ); - - } - - $encryptedKey = Keymanager::getPrivateKey( $params['uid'] ); - - $_SESSION['enckey'] = Crypt::symmetricEncryptFileContent( $encryptedKey, $params['password'] ); - } - - return true; - - } - +. + * + */ + +namespace OCA_Encryption; + +/** + * Class for hook specific logic + */ + +class Hooks { + + # TODO: use passphrase for encrypting private key that is separate to the login password + + /** + * @brief Startup encryption backend upon user login + * @note This method should never be called for users using client side encryption + */ + + public static function login( $params ) { + + if ( Crypt::mode( $params['uid'] ) == 'server' ) { + + $view = new \OC_FilesystemView( '/' ); + + $util = new Util( $view, $params['uid'] ); + + if ( !$util->ready()) { + + return $util->setupServerSide( $params['password'] ); + + } + + $encryptedKey = Keymanager::getPrivateKey( $params['uid'] ); + + $_SESSION['enckey'] = Crypt::symmetricDecryptFileContent( $encryptedKey, $params['password'] ); + + } + + return true; + + } + /** * @brief update the encryption key of the file uploaded by the client - */ - public static function updateKeyfile( $params ) { - if (Crypt::mode() == 'client') - if (isset($params['properties']['key'])) { - Keymanager::setFileKey($params['path'], $params['properties']['key']); - } else { - \OC_Log::write( 'Encryption library', "Client side encryption is enabled but the client doesn't provide a encryption key for the file!", \OC_Log::ERROR ); - error_log("Client side encryption is enabled but the client doesn't provide a encryption key for the file!"); - } - } -} - + */ + public static function updateKeyfile( $params ) { + if (Crypt::mode() == 'client') + if (isset($params['properties']['key'])) { + Keymanager::setFileKey($params['path'], $params['properties']['key']); + } else { + \OC_Log::write( 'Encryption library', "Client side encryption is enabled but the client doesn't provide a encryption key for the file!", \OC_Log::ERROR ); + error_log("Client side encryption is enabled but the client doesn't provide a encryption key for the file!"); + } + } +} + ?> \ No newline at end of file diff --git a/apps/files_encryption/lib/crypt.php b/apps/files_encryption/lib/crypt.php index 1fa7013776a..f868028be91 100644 --- a/apps/files_encryption/lib/crypt.php +++ b/apps/files_encryption/lib/crypt.php @@ -38,19 +38,19 @@ class Crypt { public static function mode( $user = null ) { $mode = \OC_Appconfig::getValue( 'files_encryption', 'mode', 'none' ); - + if ( $mode == 'user') { if ( !$user ) { $user = \OCP\User::getUser(); - } - $mode = 'none'; - if ( $user ) { + } + $mode = 'none'; + if ( $user ) { $query = \OC_DB::prepare( "SELECT mode FROM *PREFIX*encryption WHERE uid = ?" ); $result = $query->execute(array($user)); if ($row = $result->fetchRow()){ $mode = $row['mode']; - } - } + } + } } return $mode; @@ -217,6 +217,7 @@ class Crypt { * @param string $source * @param string $target * @param string $key the decryption key + * @returns decrypted content * * This function decrypts a file */ @@ -305,7 +306,7 @@ class Crypt { /** * @brief Asymmetrically encrypt a file using multiple public keys * @param string $plainContent content to be encrypted - * @returns array keys: key, encrypted + * @returns string $plainContent decrypted string * @note symmetricDecryptFileContent() can be used to decrypt files created using this method * * This function decrypts a file @@ -355,6 +356,40 @@ class Crypt { return $plainContent; } + + /** + * @brief Encrypts content symmetrically and generated keyfile asymmetrically + * @returns array keys: data, key + * @note this method is a wrapper for combining other crypt class methods + */ + public static function keyEncryptKeyfile( $plainContent, $publicKey ) { + + // Encrypt plain data, generate keyfile & encrypted file + $cryptedData = self::symmetricEncryptFileContentKeyfile( $plainContent ); + + // Encrypt keyfile + $cryptedKey = self::keyEncrypt( $cryptedData['key'], $publicKey ); + + return array( 'data' => $cryptedData['encrypted'], 'key' => $cryptedKey ); + + } + + /** + * @brief Encrypts content symmetrically and generated keyfile asymmetrically + * @returns decrypted content + * @note this method is a wrapper for combining other crypt class methods + */ + public static function keyDecryptKeyfile( $encryptedData, $encryptedKey, $privateKey ) { + + // Decrypt keyfile + $decryptedKey = self::keyDecrypt( $encryptedKey, $privateKey ); + + // Decrypt encrypted file + $decryptedData = self::symmetricDecryptFileContent( $encryptedData, $decryptedKey ); + + return $decryptedData; + + } /** * @brief Generate a pseudo random 1024kb ASCII key @@ -392,7 +427,7 @@ class Crypt { // $key = mt_rand( 10000, 99999 ) . mt_rand( 10000, 99999 ) . mt_rand( 10000, 99999 ) . mt_rand( 10000, 99999 ); // Generate key - if ( $key = base64_encode( openssl_random_pseudo_bytes( 768000, $strong ) ) ) { + if ( $key = base64_encode( openssl_random_pseudo_bytes( 183, $strong ) ) ) { if ( !$strong ) { diff --git a/apps/files_encryption/lib/keymanager.php b/apps/files_encryption/lib/keymanager.php index 10b673f31aa..8d81c97cfa3 100644 --- a/apps/files_encryption/lib/keymanager.php +++ b/apps/files_encryption/lib/keymanager.php @@ -41,6 +41,19 @@ class Keymanager { return $view->file_get_contents( '/' . $user.'.private.key' ); } + + /** + * @brief retrieve public key for a specified user + * + * @return string public key or false + */ + public static function getPublicKey() { + + $user = \OCP\User::getUser(); + $view = new \OC_FilesystemView( '/public-keys/' ); + return $view->file_get_contents( '/' . $user . '.public.key' ); + + } /** * @brief retrieve a list of the public key from all users with access to the file @@ -94,22 +107,26 @@ class Keymanager { * @return string file key or false */ public static function getFileKey( $path ) { - + trigger_error("div ".$path); $keypath = ltrim( $path, '/' ); $user = \OCP\User::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 = ?" ); + $result = $query->execute( array ('/'.$user.'/files/'.$keypath, $user)); - if ($row = $result->fetchRow()){ + + if ($row = $result->fetchRow()) { + $keypath = $row['source']; - $keypath_parts=explode('/',$keypath); + $keypath_parts = explode( '/', $keypath ); $user = $keypath_parts[1]; - $keypath = str_replace('/'.$user.'/files/', '', $keypath); + $keypath = str_replace( '/' . $user . '/files/', '', $keypath ); + } $view = new \OC_FilesystemView('/'.$user.'/files_encryption/keyfiles/'); - return $view->file_get_contents($keypath.'.key'); + return $view->file_get_contents( $keypath . '.key' ); } diff --git a/apps/files_encryption/lib/proxy.php b/apps/files_encryption/lib/proxy.php index 85b5c868f30..51ed889d129 100644 --- a/apps/files_encryption/lib/proxy.php +++ b/apps/files_encryption/lib/proxy.php @@ -103,11 +103,14 @@ class Proxy extends \OC_FileProxy { // Set the filesize for userland, before encrypting $size = strlen( $data ); + // Disable encryption proxy to prevent recursive calls + \OC_FileProxy::$enabled = false; + // Encrypt plain data and fetch key - $encrypted = Crypt::symmetricEncryptFileContentKeyfile( $data, $_SESSION['enckey'] ); + $encrypted = Crypt::keyEncryptKeyfile( $data, Keymanager::getPublicKey() ); // Replace plain content with encrypted content by reference - $data = $encrypted['encrypted']; + $data = $encrypted['data']; $filePath = explode( '/', $path ); @@ -119,11 +122,13 @@ class Proxy extends \OC_FileProxy { $view = new \OC_FilesystemView( '/' . \OCP\USER::getUser() . '/files_encryption/keyfiles' ); // Save keyfile for newly encrypted file in parallel directory tree - Keymanager::setFileKey( \OCP\USER::getUser(), $filePath, $encrypted['key'], $view, '\OC_DB', '\OC_FileProxy' ); + Keymanager::setFileKey( $filePath, $encrypted['key'], $view, '\OC_DB' ); // Update the file cache with file info \OC_FileCache::put( $path, array( 'encrypted'=>true, 'size' => $size ), '' ); + \OC_FileProxy::$enabled = true; + } } } @@ -138,14 +143,18 @@ class Proxy extends \OC_FileProxy { $filePath = '/' . implode( '/', $filePath ); - trigger_error( "CAT " . $filePath); - $cached = \OC_FileCache_Cached::get( $path, '' ); - // Get keyfile for encrypted file - $keyFile = Keymanager::getFileKey( \OCP\USER::getUser(), $filePath ); + // Disable encryption proxy to prevent recursive calls + \OC_FileProxy::$enabled = false; + + $keyFile = Keymanager::getFileKey( $filePath ); + + $privateKey = Keymanager::getPrivateKey(); + + $data = Crypt::keyDecryptKeyfile( $data, $keyFile, $privateKey ); - $data = Crypt::symmetricDecryptFileContent( $data, $keyFile ); + \OC_FileProxy::$enabled = true; } diff --git a/apps/files_encryption/tests/crypt.php b/apps/files_encryption/tests/crypt.php new file mode 100644 index 00000000000..b6dc0f40aab --- /dev/null +++ b/apps/files_encryption/tests/crypt.php @@ -0,0 +1,245 @@ +, and + * Robin Appelman + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +require_once realpath( dirname(__FILE__).'/../lib/crypt.php' ); +require_once realpath( dirname(__FILE__).'/../lib/util.php' ); +//require realpath( dirname(__FILE__).'/../../../lib/filecache.php' ); + +class Test_Crypt extends UnitTestCase { + + function setUp() { + + // set content for encrypting / decrypting in tests + $this->data = realpath( dirname(__FILE__).'/../lib/crypt.php' ); + $this->legacyData = realpath( dirname(__FILE__).'/legacy-text.txt' ); + $this->legacyEncryptedData = realpath( dirname(__FILE__).'/legacy-encrypted-text.txt' ); + + } + + function tearDown(){} + + function testGenerateKey() { + + # TODO: use more accurate (larger) string length for test confirmation + + $key = OCA_Encryption\Crypt::generateKey(); + + $this->assertTrue( $key ); + + $this->assertTrue( strlen( $key ) > 16 ); + + } + + function testGenerateIv() { + + $iv = OCA_Encryption\Crypt::generateIv(); + + $this->assertTrue( $iv ); + + $this->assertTrue( strlen( $iv ) == 16 ); + + } + + function testEncrypt() { + + $random = openssl_random_pseudo_bytes( 13 ); + + $iv = substr( base64_encode( $random ), 0, -4 ); // i.e. E5IG033j+mRNKrht + + $crypted = OCA_Encryption\Crypt::encrypt( $this->data, $iv, 'hat' ); + + $this->assertNotEqual( $this->data, $crypted ); + + } + + function testDecrypt() { + + $random = openssl_random_pseudo_bytes( 13 ); + + $iv = substr( base64_encode( $random ), 0, -4 ); // i.e. E5IG033j+mRNKrht + + $crypted = OCA_Encryption\Crypt::encrypt( $this->data, $iv, 'hat' ); + + $decrypt = OCA_Encryption\Crypt::decrypt( $crypted, $iv, 'hat' ); + + $this->assertEqual( $this->data, $decrypt ); + + } + + function testSymmetricEncryptFileContent() { + + # TODO: search in keyfile for actual content as IV will ensure this test always passes + + $keyfileContent = OCA_Encryption\Crypt::symmetricEncryptFileContent( $this->data, 'hat' ); + + $this->assertNotEqual( $this->data, $keyfileContent ); + + + $decrypt = OCA_Encryption\Crypt::symmetricDecryptFileContent( $keyfileContent, 'hat' ); + + $this->assertEqual( $this->data, $decrypt ); + + } + + function testSymmetricEncryptFileContentKeyfile() { + + # TODO: search in keyfile for actual content as IV will ensure this test always passes + + $crypted = OCA_Encryption\Crypt::symmetricEncryptFileContentKeyfile( $this->data ); + + $this->assertNotEqual( $this->data, $crypted['encrypted'] ); + + + $decrypt = OCA_Encryption\Crypt::symmetricDecryptFileContent( $crypted['encrypted'], $crypted['key'] ); + + $this->assertEqual( $this->data, $decrypt ); + + } + + function testIsEncryptedContent() { + + $this->assertFalse( OCA_Encryption\Crypt::isEncryptedContent( $this->data ) ); + + $this->assertFalse( OCA_Encryption\Crypt::isEncryptedContent( $this->legacyEncryptedData ) ); + + $keyfileContent = OCA_Encryption\Crypt::symmetricEncryptFileContent( $this->data, 'hat' ); + + $this->assertTrue( OCA_Encryption\Crypt::isEncryptedContent( $keyfileContent ) ); + + } + + function testMultiKeyEncrypt() { + + # TODO: search in keyfile for actual content as IV will ensure this test always passes + + $pair1 = OCA_Encryption\Crypt::createKeypair(); + + $this->assertEqual( 2, count( $pair1 ) ); + + $this->assertTrue( strlen( $pair1['publicKey'] ) > 1 ); + + $this->assertTrue( strlen( $pair1['privateKey'] ) > 1 ); + + + $crypted = OCA_Encryption\Crypt::multiKeyEncrypt( $this->data, array( $pair1['publicKey'] ) ); + + $this->assertNotEqual( $this->data, $crypted['encrypted'] ); + + + $decrypt = OCA_Encryption\Crypt::multiKeyDecrypt( $crypted['encrypted'], $crypted['keys'][0], $pair1['privateKey'] ); + + $this->assertEqual( $this->data, $decrypt ); + + } + + function testKeyEncrypt() { + + // Generate keypair + $pair1 = OCA_Encryption\Crypt::createKeypair(); + + // Encrypt data + $crypted = OCA_Encryption\Crypt::keyEncrypt( $this->data, $pair1['publicKey'] ); + + $this->assertNotEqual( $this->data, $crypted ); + + // Decrypt data + $decrypt = OCA_Encryption\Crypt::keyDecrypt( $crypted, $pair1['privateKey'] ); + + $this->assertEqual( $this->data, $decrypt ); + + } + + function testKeyEncryptKeyfile() { + + # TODO: Don't repeat encryption from previous tests, use PHPUnit test interdependency instead + + // Generate keypair + $pair1 = OCA_Encryption\Crypt::createKeypair(); + + // Encrypt plain data, generate keyfile & encrypted file + $cryptedData = OCA_Encryption\Crypt::symmetricEncryptFileContentKeyfile( $this->data ); + + // Encrypt keyfile + $cryptedKey = OCA_Encryption\Crypt::keyEncrypt( $cryptedData['key'], $pair1['publicKey'] ); + + // Decrypt keyfile + $decryptKey = OCA_Encryption\Crypt::keyDecrypt( $cryptedKey, $pair1['privateKey'] ); + + // Decrypt encrypted file + $decryptData = OCA_Encryption\Crypt::symmetricDecryptFileContent( $cryptedData['encrypted'], $decryptKey ); + + $this->assertEqual( $this->data, $decryptData ); + + } + +// function testEncryption(){ +// +// $key=uniqid(); +// $file=OC::$SERVERROOT.'/3rdparty/MDB2.php'; +// $source=file_get_contents($file); //nice large text file +// $encrypted=OC_Crypt::encrypt($source,$key); +// $decrypted=OC_Crypt::decrypt($encrypted,$key); +// $decrypted=rtrim($decrypted, "\0"); +// $this->assertNotEqual($encrypted,$source); +// $this->assertEqual($decrypted,$source); +// +// $chunk=substr($source,0,8192); +// $encrypted=OC_Crypt::encrypt($chunk,$key); +// $this->assertEqual(strlen($chunk),strlen($encrypted)); +// $decrypted=OC_Crypt::decrypt($encrypted,$key); +// $decrypted=rtrim($decrypted, "\0"); +// $this->assertEqual($decrypted,$chunk); +// +// $encrypted=OC_Crypt::blockEncrypt($source,$key); +// $decrypted=OC_Crypt::blockDecrypt($encrypted,$key); +// $this->assertNotEqual($encrypted,$source); +// $this->assertEqual($decrypted,$source); +// +// $tmpFileEncrypted=OCP\Files::tmpFile(); +// OC_Crypt::encryptfile($file,$tmpFileEncrypted,$key); +// $encrypted=file_get_contents($tmpFileEncrypted); +// $decrypted=OC_Crypt::blockDecrypt($encrypted,$key); +// $this->assertNotEqual($encrypted,$source); +// $this->assertEqual($decrypted,$source); +// +// $tmpFileDecrypted=OCP\Files::tmpFile(); +// OC_Crypt::decryptfile($tmpFileEncrypted,$tmpFileDecrypted,$key); +// $decrypted=file_get_contents($tmpFileDecrypted); +// $this->assertEqual($decrypted,$source); +// +// $file=OC::$SERVERROOT.'/core/img/weather-clear.png'; +// $source=file_get_contents($file); //binary file +// $encrypted=OC_Crypt::encrypt($source,$key); +// $decrypted=OC_Crypt::decrypt($encrypted,$key); +// $decrypted=rtrim($decrypted, "\0"); +// $this->assertEqual($decrypted,$source); +// +// $encrypted=OC_Crypt::blockEncrypt($source,$key); +// $decrypted=OC_Crypt::blockDecrypt($encrypted,$key); +// $this->assertEqual($decrypted,$source); +// +// } +// +// function testBinary(){ +// $key=uniqid(); +// +// $file=__DIR__.'/binary'; +// $source=file_get_contents($file); //binary file +// $encrypted=OC_Crypt::encrypt($source,$key); +// $decrypted=OC_Crypt::decrypt($encrypted,$key); +// +// $decrypted=rtrim($decrypted, "\0"); +// $this->assertEqual($decrypted,$source); +// +// $encrypted=OC_Crypt::blockEncrypt($source,$key); +// $decrypted=OC_Crypt::blockDecrypt($encrypted,$key,strlen($source)); +// $this->assertEqual($decrypted,$source); +// } + +} diff --git a/apps/files_encryption/tests/encryption.php b/apps/files_encryption/tests/encryption.php deleted file mode 100644 index ed3b65b1797..00000000000 --- a/apps/files_encryption/tests/encryption.php +++ /dev/null @@ -1,205 +0,0 @@ -, and - * Robin Appelman - * This file is licensed under the Affero General Public License version 3 or - * later. - * See the COPYING-README file. - */ - -require realpath( dirname(__FILE__).'/../lib/crypt.php' ); -require realpath( dirname(__FILE__).'/../lib/util.php' ); -//require realpath( dirname(__FILE__).'/../../../lib/filecache.php' ); - -class Test_Encryption extends UnitTestCase { - - function setUp() { - - // set content for encrypting / decrypting in tests - $this->data = realpath( dirname(__FILE__).'/../lib/crypt.php' ); - $this->legacyData = realpath( dirname(__FILE__).'/legacy-text.txt' ); - $this->legacyEncryptedData = realpath( dirname(__FILE__).'/legacy-encrypted-text.txt' ); - - } - - function tearDown(){} - - function testGenerateKey() { - - # TODO: use more accurate (larger) string length for test confirmation - - $key = OCA_Encryption\Crypt::generateKey(); - - $this->assertTrue( $key ); - - $this->assertTrue( strlen( $key ) > 1000 ); - - } - - function testGenerateIv() { - - $iv = OCA_Encryption\Crypt::generateIv(); - - $this->assertTrue( $iv ); - - $this->assertTrue( strlen( $iv ) == 16 ); - - } - - function testEncrypt() { - - $random = openssl_random_pseudo_bytes( 13 ); - - $iv = substr( base64_encode( $random ), 0, -4 ); // i.e. E5IG033j+mRNKrht - - $crypted = OCA_Encryption\Crypt::encrypt( $this->data, $iv, 'hat' ); - - $this->assertNotEqual( $this->data, $crypted ); - - } - - function testDecrypt() { - - $random = openssl_random_pseudo_bytes( 13 ); - - $iv = substr( base64_encode( $random ), 0, -4 ); // i.e. E5IG033j+mRNKrht - - $crypted = OCA_Encryption\Crypt::encrypt( $this->data, $iv, 'hat' ); - - $decrypt = OCA_Encryption\Crypt::decrypt( $crypted, $iv, 'hat' ); - - $this->assertEqual( $this->data, $decrypt ); - - } - - function testSymmetricEncryptFileContent() { - - # TODO: search in keyfile for actual content as IV will ensure this test always passes - - $keyfileContent = OCA_Encryption\Crypt::symmetricEncryptFileContent( $this->data, 'hat' ); - - $this->assertNotEqual( $this->data, $keyfileContent ); - - - $decrypt = OCA_Encryption\Crypt::symmetricDecryptFileContent( $keyfileContent, 'hat' ); - - $this->assertEqual( $this->data, $decrypt ); - - } - - function testSymmetricEncryptFileContentKeyfile() { - - # TODO: search in keyfile for actual content as IV will ensure this test always passes - - $crypted = OCA_Encryption\Crypt::symmetricEncryptFileContentKeyfile( $this->data ); - - $this->assertNotEqual( $this->data, $crypted['encrypted'] ); - - - $decrypt = OCA_Encryption\Crypt::symmetricDecryptFileContent( $crypted['encrypted'], $crypted['key'] ); - - $this->assertEqual( $this->data, $decrypt ); - - } - - function testIsEncryptedContent() { - - $this->assertFalse( OCA_Encryption\Crypt::isEncryptedContent( $this->data ) ); - - $this->assertFalse( OCA_Encryption\Crypt::isEncryptedContent( $this->legacyEncryptedData ) ); - - $keyfileContent = OCA_Encryption\Crypt::symmetricEncryptFileContent( $this->data, 'hat' ); - - $this->assertTrue( OCA_Encryption\Crypt::isEncryptedContent( $keyfileContent ) ); - - } - - function testMultiKeyEncrypt() { - - # TODO: search in keyfile for actual content as IV will ensure this test always passes - - $pair1 = OCA_Encryption\Crypt::createKeypair(); - - $this->assertEqual( 2, count( $pair1 ) ); - - $this->assertTrue( strlen( $pair1['publicKey'] ) > 1 ); - - $this->assertTrue( strlen( $pair1['privateKey'] ) > 1 ); - - - $crypted = OCA_Encryption\Crypt::multiKeyEncrypt( $this->data, array( $pair1['publicKey'] ) ); - - $this->assertNotEqual( $this->data, $crypted['encrypted'] ); - - - $decrypt = OCA_Encryption\Crypt::multiKeyDecrypt( $crypted['encrypted'], $crypted['keys'][0], $pair1['privateKey'] ); - - $this->assertEqual( $this->data, $decrypt ); - - } - -// function testEncryption(){ -// -// $key=uniqid(); -// $file=OC::$SERVERROOT.'/3rdparty/MDB2.php'; -// $source=file_get_contents($file); //nice large text file -// $encrypted=OC_Crypt::encrypt($source,$key); -// $decrypted=OC_Crypt::decrypt($encrypted,$key); -// $decrypted=rtrim($decrypted, "\0"); -// $this->assertNotEqual($encrypted,$source); -// $this->assertEqual($decrypted,$source); -// -// $chunk=substr($source,0,8192); -// $encrypted=OC_Crypt::encrypt($chunk,$key); -// $this->assertEqual(strlen($chunk),strlen($encrypted)); -// $decrypted=OC_Crypt::decrypt($encrypted,$key); -// $decrypted=rtrim($decrypted, "\0"); -// $this->assertEqual($decrypted,$chunk); -// -// $encrypted=OC_Crypt::blockEncrypt($source,$key); -// $decrypted=OC_Crypt::blockDecrypt($encrypted,$key); -// $this->assertNotEqual($encrypted,$source); -// $this->assertEqual($decrypted,$source); -// -// $tmpFileEncrypted=OCP\Files::tmpFile(); -// OC_Crypt::encryptfile($file,$tmpFileEncrypted,$key); -// $encrypted=file_get_contents($tmpFileEncrypted); -// $decrypted=OC_Crypt::blockDecrypt($encrypted,$key); -// $this->assertNotEqual($encrypted,$source); -// $this->assertEqual($decrypted,$source); -// -// $tmpFileDecrypted=OCP\Files::tmpFile(); -// OC_Crypt::decryptfile($tmpFileEncrypted,$tmpFileDecrypted,$key); -// $decrypted=file_get_contents($tmpFileDecrypted); -// $this->assertEqual($decrypted,$source); -// -// $file=OC::$SERVERROOT.'/core/img/weather-clear.png'; -// $source=file_get_contents($file); //binary file -// $encrypted=OC_Crypt::encrypt($source,$key); -// $decrypted=OC_Crypt::decrypt($encrypted,$key); -// $decrypted=rtrim($decrypted, "\0"); -// $this->assertEqual($decrypted,$source); -// -// $encrypted=OC_Crypt::blockEncrypt($source,$key); -// $decrypted=OC_Crypt::blockDecrypt($encrypted,$key); -// $this->assertEqual($decrypted,$source); -// -// } -// -// function testBinary(){ -// $key=uniqid(); -// -// $file=__DIR__.'/binary'; -// $source=file_get_contents($file); //binary file -// $encrypted=OC_Crypt::encrypt($source,$key); -// $decrypted=OC_Crypt::decrypt($encrypted,$key); -// -// $decrypted=rtrim($decrypted, "\0"); -// $this->assertEqual($decrypted,$source); -// -// $encrypted=OC_Crypt::blockEncrypt($source,$key); -// $decrypted=OC_Crypt::blockDecrypt($encrypted,$key,strlen($source)); -// $this->assertEqual($decrypted,$source); -// } - -} -- cgit v1.2.3 From f11f524dfa17071dbabb2f950680966867f262a6 Mon Sep 17 00:00:00 2001 From: Sam Tuke Date: Wed, 15 Aug 2012 18:49:53 +0100 Subject: working on streaming decrypted content applied some dependency injection to keymanager.php --- apps/files_encryption/hooks/hooks.php | 8 +++++++- apps/files_encryption/lib/keymanager.php | 10 +++++----- apps/files_encryption/lib/proxy.php | 14 ++++++++++---- apps/files_encryption/lib/util.php | 4 ++-- apps/files_encryption/tests/keymanager.php | 1 + 5 files changed, 25 insertions(+), 12 deletions(-) (limited to 'apps/files_encryption/lib/proxy.php') diff --git a/apps/files_encryption/hooks/hooks.php b/apps/files_encryption/hooks/hooks.php index e23e3a09d46..b37c974b9c1 100644 --- a/apps/files_encryption/hooks/hooks.php +++ b/apps/files_encryption/hooks/hooks.php @@ -39,6 +39,8 @@ class Hooks { if ( Crypt::mode( $params['uid'] ) == 'server' ) { + # TODO: use lots of dependency injection here + $view = new \OC_FilesystemView( '/' ); $util = new Util( $view, $params['uid'] ); @@ -49,8 +51,12 @@ class Hooks { } - $encryptedKey = Keymanager::getPrivateKey( $params['uid'] ); + \OC_FileProxy::$enabled = false; + + $encryptedKey = Keymanager::getPrivateKey( $params['uid'], $view ); + \OC_FileProxy::$enabled = true; + $_SESSION['enckey'] = Crypt::symmetricDecryptFileContent( $encryptedKey, $params['password'] ); } diff --git a/apps/files_encryption/lib/keymanager.php b/apps/files_encryption/lib/keymanager.php index ea6e4872d4b..b06226397e8 100644 --- a/apps/files_encryption/lib/keymanager.php +++ b/apps/files_encryption/lib/keymanager.php @@ -30,14 +30,14 @@ class Keymanager { # TODO: make all dependencies (including static classes) explicit, such as ocfsview objects, by adding them as method arguments (dependency injection) /** - * @brief retrieve private key from a user + * @brief retrieve the ENCRYPTED private key from a user * * @return string private key or false + * @note the key returned by this method must be decrypted before use */ - public static function getPrivateKey() { + public static function getPrivateKey( $user, $view ) { - $user = \OCP\User::getUser(); - $view = new \OC_FilesystemView( '/' . $user . '/' . 'files_encryption' ); + $view->chroot( '/' . $user . '/' . 'files_encryption' ); return $view->file_get_contents( '/' . $user.'.private.key' ); } @@ -121,7 +121,7 @@ class Keymanager { * @return string file key or false */ public static function getFileKey( $path ) { - trigger_error("div ".$path); + $keypath = ltrim( $path, '/' ); $user = \OCP\User::getUser(); diff --git a/apps/files_encryption/lib/proxy.php b/apps/files_encryption/lib/proxy.php index 51ed889d129..5b0369bde9b 100644 --- a/apps/files_encryption/lib/proxy.php +++ b/apps/files_encryption/lib/proxy.php @@ -135,6 +135,8 @@ class Proxy extends \OC_FileProxy { public function postFile_get_contents( $path, $data ) { + # TODO: Use dependency injection to add required args for view and user etc. to this method + if ( Crypt::mode() == 'server' && Crypt::isEncryptedContent( $data ) ) { $filePath = explode( '/', $path ); @@ -150,9 +152,7 @@ class Proxy extends \OC_FileProxy { $keyFile = Keymanager::getFileKey( $filePath ); - $privateKey = Keymanager::getPrivateKey(); - - $data = Crypt::keyDecryptKeyfile( $data, $keyFile, $privateKey ); + $data = Crypt::keyDecryptKeyfile( $data, $keyFile, $_SESSION['enckey'] ); \OC_FileProxy::$enabled = true; @@ -175,9 +175,15 @@ class Proxy extends \OC_FileProxy { // If file is encrypted, decrypt using crypto protocol if ( Crypt::mode() == 'server' && Crypt::isEncryptedContent( $path ) ) { + $keyFile = Keymanager::getFileKey( $filePath ); + + $tmp = tmpfile(); + + file_put_contents( $tmp, Crypt::keyDecryptKeyfile( $result, $keyFile, $_SESSION['enckey'] ) ); + fclose ( $result ); - $result = fopen( 'crypt://'.$path, $meta['mode'] ); + $result = fopen( $tmp ); } elseif ( self::shouldEncrypt( $path ) diff --git a/apps/files_encryption/lib/util.php b/apps/files_encryption/lib/util.php index 609f7871241..b919c56a2eb 100644 --- a/apps/files_encryption/lib/util.php +++ b/apps/files_encryption/lib/util.php @@ -222,9 +222,9 @@ class Util { } - public function encryptAll( OC_FilesystemView $view ) { + public function encryptAll( $directory ) { - $plainFiles = $this->findPlainFiles( $view ); + $plainFiles = $this->findFiles( $this->view, 'plain' ); if ( $this->encryptFiles( $plainFiles ) ) { diff --git a/apps/files_encryption/tests/keymanager.php b/apps/files_encryption/tests/keymanager.php index 51b49c5da57..e0ce7a1d6ad 100644 --- a/apps/files_encryption/tests/keymanager.php +++ b/apps/files_encryption/tests/keymanager.php @@ -43,6 +43,7 @@ class Test_Keymanager extends \PHPUnit_Framework_TestCase { $key = Keymanager::getPrivateKey( $this->user, $this->view ); + # TODO: replace call to Crypt with a mock object? $decrypted = Crypt::symmetricDecryptFileContent( $key, $this->passphrase ); $this->assertEquals( 1708, strlen( $decrypted ) ); -- cgit v1.2.3 From 265f3654af3a8abb96a74214e63cd65a0a40f150 Mon Sep 17 00:00:00 2001 From: Sam Tuke Date: Wed, 17 Oct 2012 16:35:19 +0100 Subject: all unit files_encryption crypt unit tests now passing after merge --- apps/files_encryption/ajax/mode.php | 2 +- apps/files_encryption/appinfo/app.php | 22 +++---- apps/files_encryption/appinfo/info.xml | 6 +- apps/files_encryption/hooks/hooks.php | 2 +- apps/files_encryption/lib/crypt.php | 89 ++++++++++++++++++++++----- apps/files_encryption/lib/keymanager.php | 2 +- apps/files_encryption/lib/proxy.php | 46 ++++++++++---- apps/files_encryption/lib/stream.php | 54 +++++++++-------- apps/files_encryption/lib/util.php | 16 ++++- apps/files_encryption/tests/crypt.php | 96 +++++++++++++----------------- apps/files_encryption/tests/keymanager.php | 2 +- apps/files_encryption/tests/out.txt | 79 +++--------------------- apps/files_encryption/tests/stream.php | 2 +- apps/files_encryption/tests/util.php | 10 ++-- lib/base.php | 6 ++ lib/ocs.php | 32 +++++----- 16 files changed, 248 insertions(+), 218 deletions(-) (limited to 'apps/files_encryption/lib/proxy.php') diff --git a/apps/files_encryption/ajax/mode.php b/apps/files_encryption/ajax/mode.php index 64203b42cf5..64c5be94401 100644 --- a/apps/files_encryption/ajax/mode.php +++ b/apps/files_encryption/ajax/mode.php @@ -5,7 +5,7 @@ * See the COPYING-README file. */ -use OCA_Encryption\Keymanager; +use OCA\Encryption\Keymanager; OCP\JSON::checkAppEnabled('files_encryption'); OCP\JSON::checkLoggedIn(); diff --git a/apps/files_encryption/appinfo/app.php b/apps/files_encryption/appinfo/app.php index dd95a1f0944..12920aa8291 100644 --- a/apps/files_encryption/appinfo/app.php +++ b/apps/files_encryption/appinfo/app.php @@ -1,20 +1,20 @@ files_encryption Encryption - Server side encryption of files. DEPRECATED. This app is no longer supported and will be replaced with an improved version in ownCloud 5. Only enable this features if you want to read old encrypted data. Warning: You will lose your data if you enable this App and forget your password. Encryption is not yet compatible with LDAP. + Server side encryption of files. Warning: You will lose your data if you enable this App and forget your password. Encryption is not yet compatible with LDAP. AGPL - Robin Appelman - 4.9 + Sam Tuke + 4 true diff --git a/apps/files_encryption/hooks/hooks.php b/apps/files_encryption/hooks/hooks.php index 71b1b060808..b9758ec0df2 100644 --- a/apps/files_encryption/hooks/hooks.php +++ b/apps/files_encryption/hooks/hooks.php @@ -20,7 +20,7 @@ * */ -namespace OCA_Encryption; +namespace OCA\Encryption; /** * Class for hook specific logic diff --git a/apps/files_encryption/lib/crypt.php b/apps/files_encryption/lib/crypt.php index e805752137c..e92c534a93c 100644 --- a/apps/files_encryption/lib/crypt.php +++ b/apps/files_encryption/lib/crypt.php @@ -22,7 +22,15 @@ * */ -namespace OCA_Encryption; +namespace OCA\Encryption; + +// Todo: +// - Crypt/decrypt button in the userinterface +// - Setting if crypto should be on by default +// - Add a setting "Don´t encrypt files larger than xx because of performance reasons" +// - Transparent decrypt/encrypt in filesystem.php. Autodetect if a file is encrypted (.encrypted extension) +// - Don't use a password directly as encryption key. but a key which is stored on the server and encrypted with the user password. -> password change faster +// - IMPORTANT! Check if the block lenght of the encrypted data stays the same /** * Class for common cryptography functionality @@ -52,7 +60,7 @@ class Crypt { } } } - + return $mode; } @@ -61,7 +69,7 @@ class Crypt { * @return array publicKey, privatekey */ public static function createKeypair() { - + $res = openssl_pkey_new(); // Get private key @@ -76,9 +84,46 @@ class Crypt { } + /** + * @brief Add arbitrary padding to encrypted data + * @param string $data data to be padded + * @return padded data + * @note In order to end up with data exactly 8192 bytes long we must add two letters. Something about the encryption process always results in 8190 or 8194 byte length, hence the letters must be added manually after encryption takes place + */ + public static function addPadding( $data ) { + + $padded = $data . 'xx'; + + return $padded; + + } + + /** + * @brief Remove arbitrary padding to encrypted data + * @param string $padded padded data to remove padding from + * @return padded data on success, false on error + */ + public static function removePadding( $padded ) { + + if ( substr( $padded, -2 ) == 'xx' ) { + + $data = substr( $padded, 0, -2 ); + + return $data; + + } else { + + # TODO: log the fact that unpadded data was submitted for removal of padding + return false; + + } + + } + /** * @brief Check if a file's contents contains an IV and is symmetrically encrypted * @return true / false + * @note see also OCA\Encryption\Util->isEncryptedPath() */ public static function isEncryptedContent( $content ) { @@ -88,12 +133,18 @@ class Crypt { } + $noPadding = self::removePadding( $content ); + // Fetch encryption metadata from end of file - $meta = substr( $content, -22 ); + $meta = substr( $noPadding, -22 ); // Fetch IV from end of file $iv = substr( $meta, -16 ); +// $msg = "\$content = ".var_dump($content, 1).", \$noPadding = ".var_dump($noPadding, 1).", \$meta = ".var_dump($meta, 1).", \$iv = ".var_dump($iv, 1); +// +// file_put_contents('/home/samtuke/newtmp.txt', $msg ); + // Fetch identifier from start of metadata $identifier = substr( $meta, 0, 6 ); @@ -207,7 +258,9 @@ class Crypt { // Combine content to encrypt with IV identifier and actual IV $combinedKeyfile = self::concatIv( $encryptedContent, $iv ); - return $combinedKeyfile; + $padded = self::addPadding( $combinedKeyfile ); + + return $padded; } else { @@ -237,11 +290,14 @@ class Crypt { } + // Remove padding + $noPadding = self::removePadding( $keyfileContent ); + // Fetch IV from end of file - $iv = substr( $keyfileContent, -16 ); + $iv = substr( $noPadding, -16 ); // Remove IV and IV identifier text to expose encrypted content - $encryptedContent = substr( $keyfileContent, 0, -22 ); + $encryptedContent = substr( $noPadding, 0, -22 ); if ( $plainContent = self::decrypt( $encryptedContent, $iv, $passphrase ) ) { @@ -412,17 +468,19 @@ class Crypt { while( strlen( $remaining ) ) { - //echo "\n\n\$block = ".substr( $remaining, 0, 8192 ); + //echo "\n\n\$block = ".substr( $remaining, 0, 6126 ); // Encrypt a chunk of unencrypted data and add it to the rest - $block = self::symmetricEncryptFileContent( substr( $remaining, 0, 8192 ), $key ); + $block = self::symmetricEncryptFileContent( substr( $remaining, 0, 6126 ), $key ); + + $padded = self::addPadding( $block ); $crypted .= $block; $testarray[] = $block; // Remove the data already encrypted from remaining unencrypted data - $remaining = substr( $remaining, 8192 ); + $remaining = substr( $remaining, 6126 ); } @@ -450,18 +508,17 @@ class Crypt { while( strlen( $remaining ) ) { - $testarray[] = substr( $remaining, 0, 10946 ); + $testarray[] = substr( $remaining, 0, 8192 ); - // Encrypt a chunk of unencrypted data and add it to the rest - // 10946 is the length of a 8192 string once it has been encrypted - $decrypted .= self::symmetricDecryptFileContent( substr( $remaining, 0, 10946 ), $key ); + // Decrypt a chunk of unencrypted data and add it to the rest + $decrypted .= self::symmetricDecryptFileContent( $remaining, $key ); // Remove the data already encrypted from remaining unencrypted data - $remaining = substr( $remaining, 10946 ); + $remaining = substr( $remaining, 8192 ); } - //print_r($testarray); + //echo "\n\n\$testarray = "; print_r($testarray); return $decrypted; diff --git a/apps/files_encryption/lib/keymanager.php b/apps/files_encryption/lib/keymanager.php index 9d5e170e7fa..37669ef62c8 100644 --- a/apps/files_encryption/lib/keymanager.php +++ b/apps/files_encryption/lib/keymanager.php @@ -20,7 +20,7 @@ * */ -namespace OCA_Encryption; +namespace OCA\Encryption; /** * This class provides basic operations to read/write encryption keys from/to the filesystem diff --git a/apps/files_encryption/lib/proxy.php b/apps/files_encryption/lib/proxy.php index 5b0369bde9b..269521857b2 100644 --- a/apps/files_encryption/lib/proxy.php +++ b/apps/files_encryption/lib/proxy.php @@ -27,7 +27,7 @@ * transparent encryption */ -namespace OCA_Encryption; +namespace OCA\Encryption; class Proxy extends \OC_FileProxy { @@ -43,7 +43,7 @@ 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 ) ) { self::$enableEncryption = ( \OCP\Config::getAppValue( 'files_encryption', 'enable_encryption', 'true' ) == 'true' && Crypt::mode() == 'server' ); @@ -127,6 +127,7 @@ class Proxy extends \OC_FileProxy { // Update the file cache with file info \OC_FileCache::put( $path, array( 'encrypted'=>true, 'size' => $size ), '' ); + // Re-enable proxy - our work is done \OC_FileProxy::$enabled = true; } @@ -170,22 +171,45 @@ class Proxy extends \OC_FileProxy { } + // Disable encryption proxy to prevent recursive calls + \OC_FileProxy::$enabled = false; + $meta = stream_get_meta_data( $result ); + $view = new \OC_FilesystemView(); + + $util = new Util( $view, \OCP\USER::getUser()); + // If file is encrypted, decrypt using crypto protocol - if ( Crypt::mode() == 'server' && Crypt::isEncryptedContent( $path ) ) { + if ( Crypt::mode() == 'server' && $util->isEncryptedPath( $path ) ) { - $keyFile = Keymanager::getFileKey( $filePath ); + file_put_contents('/home/samtuke/newtmp.txt', "bar" ); - $tmp = tmpfile(); + $tmp = fopen( 'php://temp' ); - file_put_contents( $tmp, Crypt::keyDecryptKeyfile( $result, $keyFile, $_SESSION['enckey'] ) ); - - fclose ( $result ); + \OCP\Files::streamCopy( $result, $tmp ); - $result = fopen( $tmp ); + fclose( $result ); - } elseif ( + \OC_Filesystem::file_put_contents( $path, $tmp ); + + fclose( $tmp ); + + $result = fopen( 'crypt://' . $path, $meta['mode'] ); + +// file_put_contents('/home/samtuke/newtmp.txt', "mode= server" ); + +// $keyFile = Keymanager::getFileKey( $filePath ); +// +// $tmp = tmpfile(); +// +// file_put_contents( $tmp, Crypt::keyDecryptKeyfile( $result, $keyFile, $_SESSION['enckey'] ) ); +// +// fclose ( $result ); +// +// $result = fopen( $tmp ); + + } /*elseif ( self::shouldEncrypt( $path ) and $meta ['mode'] != 'r' and $meta['mode'] != 'rb' @@ -216,7 +240,7 @@ class Proxy extends \OC_FileProxy { $result = fopen( 'crypt://'.$path, $meta['mode'] ); - } + }*/ return $result; diff --git a/apps/files_encryption/lib/stream.php b/apps/files_encryption/lib/stream.php index 0a8efa41d33..8264c507bda 100644 --- a/apps/files_encryption/lib/stream.php +++ b/apps/files_encryption/lib/stream.php @@ -27,7 +27,7 @@ * and then fopen('crypt://streams/foo'); */ -namespace OCA_Encryption; +namespace OCA\Encryption; /** * @brief Provides 'crypt://' stream wrapper protocol. @@ -89,8 +89,10 @@ class Stream { $this->size = 0; } else { - - $this->size = self::$view->filesize( \OCP\USER::getUser() . '/' . 'files' . '/' . $path, $mode ); + + + + $this->size = self::$view->filesize( $path, $mode ); //$this->size = filesize( $path ); @@ -101,13 +103,15 @@ class Stream { //$this->handle = fopen( $path, $mode ); - $this->handle = self::$view->fopen( \OCP\USER::getUser() . '/' . 'files' . '/' . $path, $mode ); - + $this->handle = self::$view->fopen( $path, $mode ); + + //file_put_contents('/home/samtuke/newtmp.txt', 'fucking hopeless = '.$path ); + \OC_FileProxy::$enabled = true; if ( !is_resource( $this->handle ) ) { - \OCP\Util::writeLog( 'files_encryption','failed to open '.$path,OCP\Util::ERROR ); + \OCP\Util::writeLog( 'files_encryption', 'failed to open '.$path, \OCP\Util::ERROR ); } @@ -137,6 +141,10 @@ class Stream { public function stream_read( $count ) { + trigger_error("\$count = $count"); + + file_put_contents('/home/samtuke/newtmp.txt', "\$count = $count" ); + $this->writeCache = ''; if ( $count != 8192 ) { @@ -151,11 +159,8 @@ class Stream { // $pos = ftell( $this->handle ); // - // Get the data from the file handle, including IV and padding - $padded = fread( $this->handle, 8192 ); - - // Remove padding, leaving data and IV - $data = substr( $padded, 0, -2 ); + // Get the data from the file handle + $data = fread( $this->handle, 8192 ); //echo "\n\nPRE DECRYPTION = $data\n\n"; // @@ -167,15 +172,17 @@ class Stream { $result = Crypt::symmetricDecryptFileContent( $data, $this->keyfile ); - echo "\n\n\n\n-----------------------------\n\nNEWS"; +// file_put_contents('/home/samtuke/newtmp.txt', '$result = '.$result ); - echo "\n\n\$data = $data"; - - echo "\n\n\$key = {$this->keyfile}"; - - echo "\n\n\$result = $result"; - - echo "\n\n\n\n-----------------------------\n\n"; +// echo "\n\n\n\n-----------------------------\n\nNEWS"; +// +// echo "\n\n\$data = $data"; +// +// echo "\n\n\$key = {$this->keyfile}"; +// +// echo "\n\n\$result = $result"; +// +// echo "\n\n\n\n-----------------------------\n\n"; //trigger_error("CAT $result"); @@ -208,12 +215,9 @@ class Stream { public function preWriteEncrypt( $plainData, $key ) { // Encrypt data to 'catfile', which includes IV - if ( $encrypted = Crypt::symmetricBlockEncryptFileContent( $plainData, $key ) ) { + if ( $encrypted = Crypt::symmetricEncryptFileContent( $plainData, $key ) ) { - // Add padding. In order to end up with data exactly 8192 bytes long we must add two letters. Something about the encryption process always results in 8190 or 8194 byte length, hence the letters must be added manually after encryption takes place. They get removed in the stream read process - $padded = $encrypted . 'xx'; - - return $padded; + return $encrypted; } else { @@ -271,6 +275,8 @@ class Stream { */ public function stream_write( $data ) { + //file_put_contents('/home/samtuke/newtmp.txt', 'stream_write('.$data.')' ); + // Disable the file proxies so that encryption is not automatically attempted when the file is written to disk - we are handling that separately here and we don't want to get into an infinite loop \OC_FileProxy::$enabled = false; diff --git a/apps/files_encryption/lib/util.php b/apps/files_encryption/lib/util.php index eab5b5edf5b..0f1498885af 100644 --- a/apps/files_encryption/lib/util.php +++ b/apps/files_encryption/lib/util.php @@ -29,7 +29,7 @@ // - Don't use a password directly as encryption key. but a key which is stored on the server and encrypted with the user password. -> password change faster // - IMPORTANT! Check if the block lenght of the encrypted data stays the same -namespace OCA_Encryption; +namespace OCA\Encryption; /** * @brief Class for utilities relating to encrypted file storage system @@ -45,8 +45,8 @@ class Util { # DONE: add method to fetch legacy key # DONE: add method to decrypt legacy encrypted data # DONE: fix / test the crypt stream proxy class + # DONE: replace cryptstream wrapper new AES based system - # 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. @@ -222,6 +222,18 @@ class Util { } + /** + * @brief Check if a given path identifies an encrypted file + * @return true / false + */ + public function isEncryptedPath( $path ) { + + $data = $this->view->file_get_contents( $path ); + + return Crypt::isEncryptedContent( $data ); + + } + public function encryptAll( $directory ) { $plainFiles = $this->findFiles( $this->view, 'plain' ); diff --git a/apps/files_encryption/tests/crypt.php b/apps/files_encryption/tests/crypt.php index 30a0caf0034..7f423151490 100644 --- a/apps/files_encryption/tests/crypt.php +++ b/apps/files_encryption/tests/crypt.php @@ -7,11 +7,11 @@ * See the COPYING-README file. */ -namespace OCA_Encryption; +namespace OCA\Encryption; require_once "PHPUnit/Framework/TestCase.php"; require_once realpath( dirname(__FILE__).'/../../../lib/base.php' ); -require_once realpath( dirname(__FILE__).'/../lib/crypt.php' ); +//require_once realpath( dirname(__FILE__).'/../lib/crypt.php' ); class Test_Crypt extends \PHPUnit_Framework_TestCase { @@ -92,33 +92,34 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { } - function testSymmetricBlockEncryptShortFileContent() { - - $crypted = Crypt::symmetricBlockEncryptFileContent( $this->dataShort, $this->randomKey ); - - $this->assertNotEquals( $this->dataShort, $crypted ); - - - $decrypt = Crypt::symmetricBlockDecryptFileContent( $crypted, $this->randomKey ); - - $this->assertEquals( $this->dataShort, $decrypt ); - - } - - function testSymmetricBlockEncryptLongFileContent() { - - $crypted = Crypt::symmetricBlockEncryptFileContent( $this->dataLong, $this->randomKey ); - - $this->assertNotEquals( $this->dataLong, $crypted ); - - - $decrypt = Crypt::symmetricBlockDecryptFileContent( $crypted, $this->randomKey ); - - $this->assertEquals( $this->dataLong, $decrypt ); - - } + // These aren't used for now +// function testSymmetricBlockEncryptShortFileContent() { +// +// $crypted = Crypt::symmetricBlockEncryptFileContent( $this->dataShort, $this->randomKey ); +// +// $this->assertNotEquals( $this->dataShort, $crypted ); +// +// +// $decrypt = Crypt::symmetricBlockDecryptFileContent( $crypted, $this->randomKey ); +// +// $this->assertEquals( $this->dataShort, $decrypt ); +// +// } +// +// function testSymmetricBlockEncryptLongFileContent() { +// +// $crypted = Crypt::symmetricBlockEncryptFileContent( $this->dataLong, $this->randomKey ); +// +// $this->assertNotEquals( $this->dataLong, $crypted ); +// +// +// $decrypt = Crypt::symmetricBlockDecryptFileContent( $crypted, $this->randomKey ); +// +// $this->assertEquals( $this->dataLong, $decrypt ); +// +// } - function testSymmetricStreamEncryptShortFileContent() { + function testSymmetricStreamEncryptShortFileContent() { $filename = 'tmp-'.time(); @@ -129,10 +130,9 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { // Get file contents without using any wrapper to get it's actual contents on disk - $retreivedCryptedFile = $this->view->file_get_contents( '/admin/files/' . $filename ); + $retreivedCryptedFile = $this->view->file_get_contents( $filename ); - // Manually remove padding from end of each chunk - $retreivedCryptedFile = substr( $retreivedCryptedFile, 0, -2 ); + //echo "\n\n\$retreivedCryptedFile = $retreivedCryptedFile"; // Check that the file was encrypted before being written to disk $this->assertNotEquals( $this->dataShort, $retreivedCryptedFile ); @@ -164,37 +164,23 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { $this->assertTrue( is_int( $cryptedFile ) ); // Get file contents without using any wrapper to get it's actual contents on disk - $retreivedCryptedFile = $this->view->file_get_contents( '/admin/files/' . $filename ); + $retreivedCryptedFile = $this->view->file_get_contents( $filename ); + +// echo "\n\n\$retreivedCryptedFile = $retreivedCryptedFile\n\n"; // Check that the file was encrypted before being written to disk $this->assertNotEquals( $this->dataLong.$this->dataLong, $retreivedCryptedFile ); - // Get file contents without using any wrapper to get it's actual contents on disk - $undecrypted = file_get_contents( '/home/samtuke/owncloud/git/oc3/data/admin/files/' . $filename ); - - //echo "\n\n\$undecrypted = $undecrypted\n\n"; - // Manuallly split saved file into separate IVs and encrypted chunks - $r = preg_split('/(00iv00.{16,18})/', $undecrypted, NULL, PREG_SPLIT_DELIM_CAPTURE); + $r = preg_split('/(00iv00.{16,18})/', $retreivedCryptedFile, NULL, PREG_SPLIT_DELIM_CAPTURE); - print_r($r); + //print_r($r); // Join IVs and their respective data chunks - $e = array( $r[0].$r[1], $r[2].$r[3], $r[4].$r[5], $r[6].$r[7], $r[8].$r[9], $r[10] );//.$r[11], $r[12].$r[13], $r[14] ); + $e = array( $r[0].$r[1], $r[2].$r[3], $r[4].$r[5], $r[6].$r[7], $r[8].$r[9], $r[10].$r[11] );//.$r[11], $r[12].$r[13], $r[14] ); //print_r($e); - $f = array(); - - // Manually remove padding from end of each chunk - foreach ( $e as $e ) { - - $f[] = substr( $e, 0, -2 ); - - } - -// print_r($f); - // Manually fetch keyfile $keyfile = Keymanager::getFileKey( $filename ); @@ -202,11 +188,11 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { $decrypt = ''; // Manually decrypt chunk - foreach ($f as $f) { + foreach ($e as $e) { // echo "\n\$encryptMe = $f"; - $chunkDecrypt = Crypt::symmetricDecryptFileContent( $f, $keyfile ); + $chunkDecrypt = Crypt::symmetricDecryptFileContent( $e, $keyfile ); // Assemble decrypted chunks $decrypt .= $chunkDecrypt; @@ -219,7 +205,7 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { // Teadown - $this->view->unlink( '/admin/files/' . $filename ); + $this->view->unlink( $filename ); Keymanager::deleteFileKey( $filename ); @@ -241,7 +227,7 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { // Get file contents without using any wrapper to get it's actual contents on disk - $retreivedCryptedFile = $this->view->file_get_contents( '/admin/files/' . $filename ); + $retreivedCryptedFile = $this->view->file_get_contents( $filename ); $decrypt = file_get_contents( 'crypt://' . $filename ); diff --git a/apps/files_encryption/tests/keymanager.php b/apps/files_encryption/tests/keymanager.php index a6b425bafa2..463b8a67c38 100644 --- a/apps/files_encryption/tests/keymanager.php +++ b/apps/files_encryption/tests/keymanager.php @@ -6,7 +6,7 @@ * See the COPYING-README file. */ -namespace OCA_Encryption; +namespace OCA\Encryption; require_once "PHPUnit/Framework/TestCase.php"; require_once realpath( dirname(__FILE__).'/../../../lib/base.php' ); diff --git a/apps/files_encryption/tests/out.txt b/apps/files_encryption/tests/out.txt index 00f61adacac..f3e7abfa0be 100644 --- a/apps/files_encryption/tests/out.txt +++ b/apps/files_encryption/tests/out.txt @@ -1,78 +1,17 @@ PHPUnit 3.6.12 by Sebastian Bergmann. -. - -$filename = tmp-1350393650 - - - -$rawLength = 8192 -$pointer = 0 - - -$rawEnc = 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 - -written = 8192 -$this->size = 8192 - - - -$rawLength = 8192 -$pointer = 8192 - - -cache + data length = 10258 - - -$rawEnc = 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 - -written = 8192 -$this->size = 16384 - +...... - -$rawLength = 8192 -$pointer = 16384 - - -cache + data length = 12324 - - -$rawEnc = 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 - -written = 8192 - - -$rawEnc = 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 - -written = 8192 -$this->size = 24576 - - - -$rawLength = 2954 -$pointer = 32768 - - -cache + data length = 3026 -$this->size = 35722 - -Array +$testarray = Array ( - [0] => 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 - [1] => 00iv00M/4PgOZwLTXSOHH7xx - [2] => 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 - [3] => 00iv009zGKkK9rKWzgOhM4xx - [4] => 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 - [5] => 00iv00sbQ4oOpn7O1qZ/Wzxx - [6] => 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 - [7] => 00iv00nIRclAfQYB7zKU8mxx - [8] => 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 - [9] => 00iv00Mkl5TvkupI7ENPlrxx - [10] => + [0] => Syz/Pg==00iv002QCKh0n9d8/x9Fk7xx ) +. + +$filename = tmp-1350488042 +....... -Time: 0 seconds, Memory: 5.50Mb +Time: 1 second, Memory: 6.00Mb -OK (1 test, 3 assertions) +OK (14 tests, 29 assertions) diff --git a/apps/files_encryption/tests/stream.php b/apps/files_encryption/tests/stream.php index 0f65e49f9ec..52e85fe4850 100644 --- a/apps/files_encryption/tests/stream.php +++ b/apps/files_encryption/tests/stream.php @@ -6,7 +6,7 @@ * See the COPYING-README file. */ -namespace OCA_Encryption; +namespace OCA\Encryption; require_once "PHPUnit/Framework/TestCase.php"; require_once realpath( dirname(__FILE__).'/../../../lib/base.php' ); diff --git a/apps/files_encryption/tests/util.php b/apps/files_encryption/tests/util.php index f24b1642052..ea8fade142a 100644 --- a/apps/files_encryption/tests/util.php +++ b/apps/files_encryption/tests/util.php @@ -27,20 +27,20 @@ class Test_Encryption extends UnitTestCase { // // Cannot use this test for now due to hidden dependencies in OC_FileCache // function testIsLegacyEncryptedContent() { // -// $keyfileContent = OCA_Encryption\Crypt::symmetricEncryptFileContent( $this->legacyEncryptedData, 'hat' ); +// $keyfileContent = OCA\Encryption\Crypt::symmetricEncryptFileContent( $this->legacyEncryptedData, 'hat' ); // -// $this->assertFalse( OCA_Encryption\Crypt::isLegacyEncryptedContent( $keyfileContent, '/files/admin/test.txt' ) ); +// $this->assertFalse( OCA\Encryption\Crypt::isLegacyEncryptedContent( $keyfileContent, '/files/admin/test.txt' ) ); // // OC_FileCache::put( '/admin/files/legacy-encrypted-test.txt', $this->legacyEncryptedData ); // -// $this->assertTrue( OCA_Encryption\Crypt::isLegacyEncryptedContent( $this->legacyEncryptedData, '/files/admin/test.txt' ) ); +// $this->assertTrue( OCA\Encryption\Crypt::isLegacyEncryptedContent( $this->legacyEncryptedData, '/files/admin/test.txt' ) ); // // } // // Cannot use this test for now due to need for different root in OC_Filesystem_view class // function testGetLegacyKey() { // -// $c = new \OCA_Encryption\Util( $view, false ); +// $c = new \OCA\Encryption\Util( $view, false ); // // $bool = $c->getLegacyKey( 'admin' ); // @@ -57,7 +57,7 @@ class Test_Encryption extends UnitTestCase { // // Cannot use this test for now due to need for different root in OC_Filesystem_view class // function testLegacyDecrypt() { // -// $c = new OCA_Encryption\Util( $this->view, false ); +// $c = new OCA\Encryption\Util( $this->view, false ); // // $bool = $c->getLegacyKey( 'admin' ); // diff --git a/lib/base.php b/lib/base.php index 803d3e8bde5..3cec1449474 100644 --- a/lib/base.php +++ b/lib/base.php @@ -71,6 +71,11 @@ class OC{ * SPL autoload */ public static function autoload($className) { + + //trigger_error('seth', E_ERROR); + + //debug_print_backtrace(); + if(array_key_exists($className, OC::$CLASSPATH)) { $path = OC::$CLASSPATH[$className]; /** @TODO: Remove this when necessary @@ -106,6 +111,7 @@ class OC{ } public static function initPaths() { + // calculate the root directories OC::$SERVERROOT=str_replace("\\", '/', substr(__DIR__, 0, -4)); OC::$SUBURI= str_replace("\\", "/", substr(realpath($_SERVER["SCRIPT_FILENAME"]), strlen(OC::$SERVERROOT))); diff --git a/lib/ocs.php b/lib/ocs.php index 19fbe26d903..d04959c715d 100644 --- a/lib/ocs.php +++ b/lib/ocs.php @@ -681,8 +681,8 @@ class OC_OCS { */ private static function publicKeyGet($format, $file) { $login=OC_OCS::checkpassword(); - if(OC_App::isEnabled('files_encryption') && OCA_Encryption\Crypt::mode() === 'client') { - if (($keys = OCA_Encryption\Keymanager::getPublicKeys($file))) { + if(OC_App::isEnabled('files_encryption') && OCA\Encryption\Crypt::mode() === 'client') { + if (($keys = OCA\Encryption\Keymanager::getPublicKeys($file))) { $xml=$keys; $txt=OC_OCS::generatexml($format, 'ok', 100, '', $xml, 'cloud', '', 1, 0, 0); echo($txt); @@ -703,8 +703,8 @@ class OC_OCS { */ private static function publicKeySet($format, $key) { $login=OC_OCS::checkpassword(); - if(OC_App::isEnabled('files_encryption') && OCA_Encryption\Crypt::mode() === 'client') { - if (OCA_Encryption\Keymanager::setPublicKey($key)) { + if(OC_App::isEnabled('files_encryption') && OCA\Encryption\Crypt::mode() === 'client') { + if (OCA\Encryption\Keymanager::setPublicKey($key)) { echo self::generateXml('', 'ok', 100, ''); } else { echo self::generateXml('', 'fail', 404, 'could not add your public key to the key storage'); @@ -721,8 +721,8 @@ class OC_OCS { */ private static function privateKeyGet($format) { $login=OC_OCS::checkpassword(); - if(OC_App::isEnabled('files_encryption') && OCA_Encryption\Crypt::mode() === 'client') { - if (($key = OCA_Encryption\Keymanager::getPrivateKey())) { + if(OC_App::isEnabled('files_encryption') && OCA\Encryption\Crypt::mode() === 'client') { + if (($key = OCA\Encryption\Keymanager::getPrivateKey())) { $xml=array(); $xml['key']=$key; $txt=OC_OCS::generatexml($format, 'ok', 100, '', $xml, 'cloud', '', 1, 0, 0); @@ -743,8 +743,8 @@ class OC_OCS { */ private static function privateKeySet($format, $key) { $login=OC_OCS::checkpassword(); - if(OC_App::isEnabled('files_encryption') && OCA_Encryption\Crypt::mode() === 'client') { - if (($key = OCA_Encryption\Keymanager::setPrivateKey($key))) { + if(OC_App::isEnabled('files_encryption') && OCA\Encryption\Crypt::mode() === 'client') { + if (($key = OCA\Encryption\Keymanager::setPrivateKey($key))) { echo self::generateXml('', 'ok', 100, ''); } else { echo self::generateXml('', 'fail', 404, 'could not add your private key to the key storage'); @@ -761,8 +761,8 @@ class OC_OCS { */ private static function userKeysGet($format) { $login=OC_OCS::checkpassword(); - if(OC_App::isEnabled('files_encryption') && OCA_Encryption\Crypt::mode() === 'client') { - $keys = OCA_Encryption\Keymanager::getUserKeys(); + if(OC_App::isEnabled('files_encryption') && OCA\Encryption\Crypt::mode() === 'client') { + $keys = OCA\Encryption\Keymanager::getUserKeys(); if ($keys['privatekey'] && $keys['publickey']) { $xml=array(); $xml['privatekey']=$keys['privatekey']; @@ -786,8 +786,8 @@ class OC_OCS { */ private static function userKeysSet($format, $privatekey, $publickey) { $login=OC_OCS::checkpassword(); - if(OC_App::isEnabled('files_encryption') && OCA_Encryption\Crypt::mode() === 'client') { - if (($key = OCA_Encryption\Keymanager::setUserKeys($privatekey, $publickey))) { + if(OC_App::isEnabled('files_encryption') && OCA\Encryption\Crypt::mode() === 'client') { + if (($key = OCA\Encryption\Keymanager::setUserKeys($privatekey, $publickey))) { echo self::generateXml('', 'ok', 100, ''); } else { echo self::generateXml('', 'fail', 404, 'could not add your keys to the key storage'); @@ -805,8 +805,8 @@ class OC_OCS { */ private static function fileKeyGet($format, $file) { $login=OC_OCS::checkpassword(); - if(OC_App::isEnabled('files_encryption') && OCA_Encryption\Crypt::mode() === 'client') { - if (($key = OCA_Encryption\Keymanager::getFileKey($file))) { + if(OC_App::isEnabled('files_encryption') && OCA\Encryption\Crypt::mode() === 'client') { + if (($key = OCA\Encryption\Keymanager::getFileKey($file))) { $xml=array(); $xml['key']=$key; $txt=OC_OCS::generatexml($format, 'ok', 100, '', $xml, 'cloud', '', 1, 0, 0); @@ -828,8 +828,8 @@ class OC_OCS { */ private static function fileKeySet($format, $file, $key) { $login=OC_OCS::checkpassword(); - if(OC_App::isEnabled('files_encryption') && OCA_Encryption\Crypt::mode() === 'client') { - if (($key = OCA_Encryption\Keymanager::setFileKey($file, $key))) { + if(OC_App::isEnabled('files_encryption') && OCA\Encryption\Crypt::mode() === 'client') { + if (($key = OCA\Encryption\Keymanager::setFileKey($file, $key))) { echo self::generateXml('', 'ok', 100, ''); } else { echo self::generateXml('', 'fail', 404, 'could not write key file'); -- cgit v1.2.3 From dd987a8bd1e405c2335abcd0c3db66fcab865214 Mon Sep 17 00:00:00 2001 From: Sam Tuke Date: Wed, 14 Nov 2012 13:58:57 +0000 Subject: Added minor documentation --- apps/files_encryption/lib/proxy.php | 12 ++++++++++-- apps/files_encryption/lib/stream.php | 9 +++++---- apps/files_encryption/tests/out.txt | 17 ----------------- 3 files changed, 15 insertions(+), 23 deletions(-) delete mode 100644 apps/files_encryption/tests/out.txt (limited to 'apps/files_encryption/lib/proxy.php') diff --git a/apps/files_encryption/lib/proxy.php b/apps/files_encryption/lib/proxy.php index 269521857b2..e3e2161a141 100644 --- a/apps/files_encryption/lib/proxy.php +++ b/apps/files_encryption/lib/proxy.php @@ -183,18 +183,23 @@ class Proxy extends \OC_FileProxy { // If file is encrypted, decrypt using crypto protocol if ( Crypt::mode() == 'server' && $util->isEncryptedPath( $path ) ) { - file_put_contents('/home/samtuke/newtmp.txt', "bar" ); - $tmp = fopen( 'php://temp' ); \OCP\Files::streamCopy( $result, $tmp ); fclose( $result ); + $encrypted = $view->file_get_contents( $path ); + + //file_put_contents('/home/samtuke/newtmp.txt', "\$path = $path, \$data = $data" ); + + // Replace the contents of \OC_Filesystem::file_put_contents( $path, $tmp ); fclose( $tmp ); + //file_put_contents('/home/samtuke/newtmp.txt', file_get_contents( 'crypt://' . $path ) ); + $result = fopen( 'crypt://' . $path, $meta['mode'] ); // file_put_contents('/home/samtuke/newtmp.txt', "mode= server" ); @@ -242,6 +247,9 @@ class Proxy extends \OC_FileProxy { }*/ + // Re-enable the proxy + \OC_FileProxy::$enabled = true; + return $result; } diff --git a/apps/files_encryption/lib/stream.php b/apps/files_encryption/lib/stream.php index 8264c507bda..2e3cdaabe44 100644 --- a/apps/files_encryption/lib/stream.php +++ b/apps/files_encryption/lib/stream.php @@ -31,6 +31,7 @@ namespace OCA\Encryption; /** * @brief Provides 'crypt://' stream wrapper protocol. + * @note We use a stream wrapper because it is the most secure way to handle decrypted content transfers. There is no safe way to decrypt the entire file somewhere on the server, so we have to encrypt and decrypt blocks on the fly. * @note Paths used with this protocol MUST BE RELATIVE, due to limitations of OC_FilesystemView. crypt:///home/user/owncloud/data <- will put keyfiles in [owncloud]/data/user/files_encryption/keyfiles/home/user/owncloud/data and will not be accessible by other functions. * @note Data read and written must always be 8192 bytes long, as this is the buffer size used internally by PHP. The encryption process makes the input data longer, and input is chunked into smaller pieces in order to result in a 8192 encrypted block size. */ @@ -52,6 +53,8 @@ class Stream { public function stream_open( $path, $mode, $options, &$opened_path ) { + file_put_contents('/home/samtuke/newtmp.txt', 'stream_open('.$path.')' ); + // Get access to filesystem via filesystemview object if ( !self::$view ) { @@ -141,9 +144,7 @@ class Stream { public function stream_read( $count ) { - trigger_error("\$count = $count"); - - file_put_contents('/home/samtuke/newtmp.txt', "\$count = $count" ); +// file_put_contents('/home/samtuke/newtmp.txt', "\$count = $count" ); $this->writeCache = ''; @@ -275,7 +276,7 @@ class Stream { */ public function stream_write( $data ) { - //file_put_contents('/home/samtuke/newtmp.txt', 'stream_write('.$data.')' ); +// file_put_contents('/home/samtuke/newtmp.txt', 'stream_write('.$data.')' ); // Disable the file proxies so that encryption is not automatically attempted when the file is written to disk - we are handling that separately here and we don't want to get into an infinite loop \OC_FileProxy::$enabled = false; diff --git a/apps/files_encryption/tests/out.txt b/apps/files_encryption/tests/out.txt deleted file mode 100644 index f3e7abfa0be..00000000000 --- a/apps/files_encryption/tests/out.txt +++ /dev/null @@ -1,17 +0,0 @@ -PHPUnit 3.6.12 by Sebastian Bergmann. - -...... - -$testarray = Array -( - [0] => Syz/Pg==00iv002QCKh0n9d8/x9Fk7xx -) -. - -$filename = tmp-1350488042 - -....... - -Time: 1 second, Memory: 6.00Mb - -OK (14 tests, 29 assertions) -- cgit v1.2.3 From 637891b77120a1acf25a907971a11c36bf5e35b7 Mon Sep 17 00:00:00 2001 From: Sam Tuke Date: Fri, 16 Nov 2012 18:31:37 +0000 Subject: Development snapshot, lots of fixes Web UI based encryption working Crypt and Util unit tests passing --- apps/files_encryption/hooks/hooks.php | 24 +++++++++----- apps/files_encryption/lib/crypt.php | 47 ++++++++++++++-------------- apps/files_encryption/lib/keymanager.php | 7 +++-- apps/files_encryption/lib/proxy.php | 20 +++++++++--- apps/files_encryption/lib/stream.php | 50 ++++++++++++++++++------------ apps/files_encryption/lib/util.php | 50 +++++++++++++++++++++++++++--- apps/files_encryption/tests/crypt.php | 40 +++++++++++++++++++----- apps/files_encryption/tests/keymanager.php | 14 +++++---- 8 files changed, 176 insertions(+), 76 deletions(-) (limited to 'apps/files_encryption/lib/proxy.php') diff --git a/apps/files_encryption/hooks/hooks.php b/apps/files_encryption/hooks/hooks.php index b9758ec0df2..d2b546e8d1f 100644 --- a/apps/files_encryption/hooks/hooks.php +++ b/apps/files_encryption/hooks/hooks.php @@ -36,32 +36,40 @@ class Hooks { */ public static function login( $params ) { - - if ( Crypt::mode( $params['uid'] ) == 'server' ) { - + +// if ( Crypt::mode( $params['uid'] ) == 'server' ) { + # TODO: use lots of dependency injection here $view = new \OC_FilesystemView( '/' ); $util = new Util( $view, $params['uid'] ); - - if ( !$util->ready()) { + + if ( ! $util->ready() ) { + + \OC_Log::write( 'Encryption library', 'User account "' . $params['uid'] . '" is not ready for encryption; configuration started' , \OC_Log::DEBUG ); return $util->setupServerSide( $params['password'] ); } \OC_FileProxy::$enabled = false; - + $encryptedKey = Keymanager::getPrivateKey( $params['uid'], $view ); - + \OC_FileProxy::$enabled = true; # TODO: dont manually encrypt the private keyfile - use the config options of openssl_pkey_export instead for better mobile compatibility + //trigger_error( "\$encryptedKey = ".var_export($encryptedKey)." \n\n\$params['password'] = ".var_export($params['password'] ) ); + +// trigger_error( "\$params['password'] = {$params['password']}" ); + $_SESSION['enckey'] = Crypt::symmetricDecryptFileContent( $encryptedKey, $params['password'] ); - } +// trigger_error( "\$_SESSION['enckey'] = {$_SESSION['enckey']}" ); + +// } return true; diff --git a/apps/files_encryption/lib/crypt.php b/apps/files_encryption/lib/crypt.php index 8026ac4361d..a5278ad3308 100755 --- a/apps/files_encryption/lib/crypt.php +++ b/apps/files_encryption/lib/crypt.php @@ -45,23 +45,26 @@ class Crypt { */ public static function mode( $user = null ) { - $mode = \OC_Appconfig::getValue( 'files_encryption', 'mode', 'none' ); +// $mode = \OC_Appconfig::getValue( 'files_encryption', 'mode', 'none' ); +// +// if ( $mode == 'user') { +// if ( !$user ) { +// $user = \OCP\User::getUser(); +// } +// $mode = 'none'; +// if ( $user ) { +// $query = \OC_DB::prepare( "SELECT mode FROM *PREFIX*encryption WHERE uid = ?" ); +// $result = $query->execute(array($user)); +// if ($row = $result->fetchRow()){ +// $mode = $row['mode']; +// } +// } +// } +// +// return $mode; - if ( $mode == 'user') { - if ( !$user ) { - $user = \OCP\User::getUser(); - } - $mode = 'none'; - if ( $user ) { - $query = \OC_DB::prepare( "SELECT mode FROM *PREFIX*encryption WHERE uid = ?" ); - $result = $query->execute(array($user)); - if ($row = $result->fetchRow()){ - $mode = $row['mode']; - } - } - } + return 'server'; - return $mode; } /** @@ -101,7 +104,7 @@ class Crypt { /** * @brief Remove arbitrary padding to encrypted data * @param string $padded padded data to remove padding from - * @return padded data on success, false on error + * @return unpadded data on success, false on error */ public static function removePadding( $padded ) { @@ -220,7 +223,7 @@ class Crypt { } else { - \OC_Log::write( 'Encryption library', 'Decryption (symmetric) of content failed' , \OC_Log::ERROR ); + throw new \Exception( 'Encryption library: Decryption (symmetric) of content failed' ); return false; @@ -317,7 +320,7 @@ class Crypt { if ( !$keyfileContent ) { - return false; + throw new \Exception( 'Encryption library: no data provided for decryption' ); } @@ -330,16 +333,12 @@ class Crypt { // Remove IV and IV identifier text to expose encrypted content $encryptedContent = substr( $noPadding, 0, -22 ); + //trigger_error( "\n\n\$noPadding = ".var_export($noPadding)."\n\n\$iv = ".var_export($iv )."\n\n\$encryptedContent = ".var_export($encryptedContent) ); + if ( $plainContent = self::decrypt( $encryptedContent, $iv, $passphrase ) ) { return $plainContent; - } else { - - \OC_Log::write( 'Encryption library', 'Decryption (symmetric) of keyfile content failed' , \OC_Log::ERROR ); - - return false; - } } diff --git a/apps/files_encryption/lib/keymanager.php b/apps/files_encryption/lib/keymanager.php index dd13b62d556..0eaca463c74 100755 --- a/apps/files_encryption/lib/keymanager.php +++ b/apps/files_encryption/lib/keymanager.php @@ -230,7 +230,7 @@ class Keymanager { * @return bool true/false */ public static function setFileKey( $path, $key, $view = Null, $dbClassName = '\OC_DB') { - +var_dump($path); $targetPath = ltrim( $path, '/' ); $user = \OCP\User::getUser(); @@ -274,7 +274,10 @@ class Keymanager { $view->chroot( '/' . $user . '/files_encryption/keyfiles' ); // If the file resides within a subdirectory, create it - if ( ! $view->file_exists( $path_parts['dirname'] ) ) { + if ( + isset( $path_parts['dirname'] ) + && ! $view->file_exists( $path_parts['dirname'] ) + ) { $view->mkdir( $path_parts['dirname'] ); diff --git a/apps/files_encryption/lib/proxy.php b/apps/files_encryption/lib/proxy.php index e3e2161a141..7c179e62b74 100644 --- a/apps/files_encryption/lib/proxy.php +++ b/apps/files_encryption/lib/proxy.php @@ -46,23 +46,34 @@ class Proxy extends \OC_FileProxy { if ( is_null( self::$enableEncryption ) ) { - self::$enableEncryption = ( \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 ) { + if ( !self::$enableEncryption ) { return false; } - if( is_null(self::$blackList ) ) { + 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( Crypt::isEncryptedContent( $path ) ) { + if ( Crypt::isEncryptedContent( $path ) ) { return true; @@ -132,6 +143,7 @@ class Proxy extends \OC_FileProxy { } } + } public function postFile_get_contents( $path, $data ) { diff --git a/apps/files_encryption/lib/stream.php b/apps/files_encryption/lib/stream.php index 2e3cdaabe44..74dff1531a9 100644 --- a/apps/files_encryption/lib/stream.php +++ b/apps/files_encryption/lib/stream.php @@ -31,9 +31,18 @@ namespace OCA\Encryption; /** * @brief Provides 'crypt://' stream wrapper protocol. - * @note We use a stream wrapper because it is the most secure way to handle decrypted content transfers. There is no safe way to decrypt the entire file somewhere on the server, so we have to encrypt and decrypt blocks on the fly. - * @note Paths used with this protocol MUST BE RELATIVE, due to limitations of OC_FilesystemView. crypt:///home/user/owncloud/data <- will put keyfiles in [owncloud]/data/user/files_encryption/keyfiles/home/user/owncloud/data and will not be accessible by other functions. - * @note Data read and written must always be 8192 bytes long, as this is the buffer size used internally by PHP. The encryption process makes the input data longer, and input is chunked into smaller pieces in order to result in a 8192 encrypted block size. + * @note We use a stream wrapper because it is the most secure way to handle + * decrypted content transfers. There is no safe way to decrypt the entire file + * somewhere on the server, so we have to encrypt and decrypt blocks on the fly. + * @note Paths used with this protocol MUST BE RELATIVE. Use URLs like: + * crypt://filename, or crypt://subdirectory/filename, NOT + * crypt:///home/user/owncloud/data. Otherwise keyfiles will be put keyfiles in + * [owncloud]/data/user/files_encryption/keyfiles/home/user/owncloud/data and + * will not be accessible to other functions. + * @note Data read and written must always be 8192 bytes long, as this is the + * buffer size used internally by PHP. The encryption process makes the input + * data longer, and input is chunked into smaller pieces in order to result in + * a 8192 encrypted block size. */ class Stream { @@ -41,6 +50,8 @@ class Stream { # TODO: make all below properties private again once unit testing is configured correctly public $rawPath; // The raw path received by stream_open + public $path_f; // The raw path formatted to include username and data directory + private $userId; private $handle; // Resource returned by fopen private $path; private $readBuffer; // For streams that dont support seeking @@ -53,20 +64,24 @@ class Stream { public function stream_open( $path, $mode, $options, &$opened_path ) { - file_put_contents('/home/samtuke/newtmp.txt', 'stream_open('.$path.')' ); + //file_put_contents('/home/samtuke/newtmp.txt', 'stream_open('.$path.')' ); // Get access to filesystem via filesystemview object if ( !self::$view ) { - self::$view = new \OC_FilesystemView( '' ); + self::$view = new \OC_FilesystemView( $this->userId . '/' ); } + $this->userId = \OCP\User::getUser(); + // Get the bare file path $path = str_replace( 'crypt://', '', $path ); $this->rawPath = $path; + $this->path_f = $this->userId . '/files/' . $path; + if ( dirname( $path ) == 'streams' and isset( self::$sourceStreams[basename( $path )] ) @@ -95,7 +110,7 @@ class Stream { - $this->size = self::$view->filesize( $path, $mode ); + $this->size = self::$view->filesize( $this->path_f, $mode ); //$this->size = filesize( $path ); @@ -106,7 +121,7 @@ class Stream { //$this->handle = fopen( $path, $mode ); - $this->handle = self::$view->fopen( $path, $mode ); + $this->handle = self::$view->fopen( $this->path_f, $mode ); //file_put_contents('/home/samtuke/newtmp.txt', 'fucking hopeless = '.$path ); @@ -165,7 +180,7 @@ class Stream { //echo "\n\nPRE DECRYPTION = $data\n\n"; // -// if ( strlen( $data ) ) { + if ( strlen( $data ) ) { $this->getKey(); @@ -186,14 +201,12 @@ class Stream { // echo "\n\n\n\n-----------------------------\n\n"; //trigger_error("CAT $result"); - - -// } else { -// -// $result = ''; -// -// } + } else { + + $result = ''; + + } // $length = $this->size - $pos; // @@ -234,14 +247,11 @@ class Stream { * @return bool true on key found and set, false on key not found and new key generated and set */ public function getKey( $generate = true ) { - - # TODO: Move this user call out of here - it belongs elsewhere - $user = \OCP\User::getUser(); //echo "\n\$this->rawPath = {$this->rawPath}"; // If a keyfile already exists for a file named identically to file to be written - if ( self::$view->file_exists( $user . '/'. 'files_encryption' . '/' . 'keyfiles' . '/' . $this->rawPath . '.key' ) ) { + if ( self::$view->file_exists( $this->userId . '/'. 'files_encryption' . '/' . 'keyfiles' . '/' . $this->rawPath . '.key' ) ) { # TODO: add error handling for when file exists but no keyfile @@ -276,6 +286,8 @@ class Stream { */ public function stream_write( $data ) { + + // file_put_contents('/home/samtuke/newtmp.txt', 'stream_write('.$data.')' ); // Disable the file proxies so that encryption is not automatically attempted when the file is written to disk - we are handling that separately here and we don't want to get into an infinite loop diff --git a/apps/files_encryption/lib/util.php b/apps/files_encryption/lib/util.php index 0f1498885af..ea2791650f9 100644 --- a/apps/files_encryption/lib/util.php +++ b/apps/files_encryption/lib/util.php @@ -65,6 +65,11 @@ class Util { private $view; // OC_FilesystemView object for filesystem operations private $pwd; // User Password private $client; // Client side encryption mode flag + private $publicKeyDir; // Directory containing all public user keys + private $encryptionDir; // Directory containing user's files_encryption + private $keyfilesPath; // Directory containing user's keyfiles + private $publicKeyPath; // Path to user's public key + private $privateKeyPath; // Path to user's private key public function __construct( \OC_FilesystemView $view, $userId, $client = false ) { @@ -102,11 +107,6 @@ class Util { * @param $passphrase passphrase to encrypt server-stored private key with */ public function setupServerSide( $passphrase = null ) { - - // Log changes to user's filesystem - $this->appInfo = \OC_APP::getAppInfo( 'files_encryption' ); - - \OC_Log::write( $this->appInfo['name'], 'File encryption for user "' . $this->userId . '" will be set up' , \OC_Log::INFO ); // Create shared public key directory if( !$this->view->file_exists( $this->publicKeyDir ) ) { @@ -152,6 +152,8 @@ class Util { \OC_FileProxy::$enabled = true; } + + return true; } @@ -357,5 +359,43 @@ class Util { # TODO: write me } + + public function getPath( $pathName ) { + + switch ( $pathName ) { + + case 'publicKeyDir': + + return $this->publicKeyDir; + + break; + + case 'encryptionDir': + + return $this->encryptionDir; + + break; + + case 'keyfilesPath': + + return $this->keyfilesPath; + + break; + + case 'publicKeyPath': + + return $this->publicKeyPath; + + break; + + case 'privateKeyPath': + + return $this->privateKeyPath; + + break; + + } + + } } diff --git a/apps/files_encryption/tests/crypt.php b/apps/files_encryption/tests/crypt.php index 4315e347cb9..1ff894bc7a6 100755 --- a/apps/files_encryption/tests/crypt.php +++ b/apps/files_encryption/tests/crypt.php @@ -34,7 +34,9 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { $this->view = new \OC_FilesystemView( '/' ); - \OC_User::setUserId( 'admin' ); + $this->userId = 'admin'; + + \OC_User::setUserId( $this->userId ); } @@ -109,6 +111,29 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { } + function testAddPadding() { + + $padded = Encryption\Crypt::addPadding( $this->dataLong ); + + $padding = substr( $padded, -2 ); + + $this->assertEquals( 'xx' , $padding ); + + return $padded; + + } + + /** + * @depends testAddPadding + */ + function testRemovePadding( $padded ) { + + $noPadding = Encryption\Crypt::RemovePadding( $padded ); + + $this->assertEquals( $this->dataLong, $noPadding ); + + } + function testEncrypt() { $random = openssl_random_pseudo_bytes( 13 ); @@ -188,7 +213,7 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { // Get file contents without using any wrapper to get it's actual contents on disk - $retreivedCryptedFile = $this->view->file_get_contents( $filename ); + $retreivedCryptedFile = $this->view->file_get_contents( $this->userId . '/files/' . $filename ); //echo "\n\n\$retreivedCryptedFile = $retreivedCryptedFile"; @@ -222,7 +247,7 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { $this->assertTrue( is_int( $cryptedFile ) ); // Get file contents without using any wrapper to get it's actual contents on disk - $retreivedCryptedFile = $this->view->file_get_contents( $filename ); + $retreivedCryptedFile = $this->view->file_get_contents( $this->userId . '/files/' . $filename ); // echo "\n\n\$retreivedCryptedFile = $retreivedCryptedFile\n\n"; @@ -261,17 +286,16 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { $this->assertEquals( $this->dataLong.$this->dataLong, $decrypt ); - // Teadown + // Teardown $this->view->unlink( $filename ); - Keymanager::deleteFileKey( $filename ); + Encryption\Keymanager::deleteFileKey( $filename ); } /** * @brief Test that data that is read by the crypto stream wrapper - * @depends testSymmetricStreamEncryptLongFileContent */ function testSymmetricStreamDecryptShortFileContent() { @@ -285,7 +309,7 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { // Get file contents without using any wrapper to get it's actual contents on disk - $retreivedCryptedFile = $this->view->file_get_contents( $filename ); + $retreivedCryptedFile = $this->view->file_get_contents( $this->userId . '/files/' . $filename ); $decrypt = file_get_contents( 'crypt://' . $filename ); @@ -305,7 +329,7 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { // Get file contents without using any wrapper to get it's actual contents on disk - $retreivedCryptedFile = $this->view->file_get_contents( '/admin/files/' . $filename ); + $retreivedCryptedFile = $this->view->file_get_contents( $this->userId . '/files/' . $filename ); $decrypt = file_get_contents( 'crypt://' . $filename ); diff --git a/apps/files_encryption/tests/keymanager.php b/apps/files_encryption/tests/keymanager.php index 463b8a67c38..3c313708de3 100644 --- a/apps/files_encryption/tests/keymanager.php +++ b/apps/files_encryption/tests/keymanager.php @@ -5,12 +5,12 @@ * later. * See the COPYING-README file. */ - -namespace OCA\Encryption; require_once "PHPUnit/Framework/TestCase.php"; require_once realpath( dirname(__FILE__).'/../../../lib/base.php' ); +use OCA\Encryption; + class Test_Keymanager extends \PHPUnit_Framework_TestCase { function setUp() { @@ -34,7 +34,7 @@ class Test_Keymanager extends \PHPUnit_Framework_TestCase { function testGetEncryptedPrivateKey() { - $key = Keymanager::getPrivateKey( $this->user, $this->view ); + $key = Encryption\Keymanager::getPrivateKey( $this->user, $this->view ); $this->assertEquals( 2302, strlen( $key ) ); @@ -52,16 +52,18 @@ class Test_Keymanager extends \PHPUnit_Framework_TestCase { // // //$view = new \OC_FilesystemView( '/' . $this->user . '/files_encryption/keyfiles' ); // -// Keymanager::setFileKey( $tmpPath, $key['key'], $view ); +// Encryption\Keymanager::setFileKey( $tmpPath, $key['key'], $view ); } function testGetDecryptedPrivateKey() { - $key = Keymanager::getPrivateKey( $this->user, $this->view ); + $key = Encryption\Keymanager::getPrivateKey( $this->user, $this->view ); # TODO: replace call to Crypt with a mock object? - $decrypted = Crypt::symmetricDecryptFileContent( $key, $this->passphrase ); + $decrypted = Encryption\Crypt::symmetricDecryptFileContent( $key, $this->passphrase ); + + var_dump($decrypted); $this->assertEquals( 1708, strlen( $decrypted ) ); -- cgit v1.2.3 From 13d93fb416709f9dca5660752eefb78a7c3dc1f7 Mon Sep 17 00:00:00 2001 From: Sam Tuke Date: Thu, 22 Nov 2012 14:08:19 +0000 Subject: Development snapshot --- apps/files_encryption/lib/crypt.php | 22 +++++++++++++++++---- apps/files_encryption/lib/proxy.php | 38 ++++++++----------------------------- apps/files_encryption/lib/util.php | 7 +++++++ 3 files changed, 33 insertions(+), 34 deletions(-) (limited to 'apps/files_encryption/lib/proxy.php') diff --git a/apps/files_encryption/lib/crypt.php b/apps/files_encryption/lib/crypt.php index 5e6ebd7a86e..efbcdb4b35a 100755 --- a/apps/files_encryption/lib/crypt.php +++ b/apps/files_encryption/lib/crypt.php @@ -144,10 +144,6 @@ class Crypt { // Fetch IV from end of file $iv = substr( $meta, -16 ); -// $msg = "\$content = ".var_dump($content, 1).", \$noPadding = ".var_dump($noPadding, 1).", \$meta = ".var_dump($meta, 1).", \$iv = ".var_dump($iv, 1); -// -// file_put_contents('/home/samtuke/newtmp.txt', $msg ); - // Fetch identifier from start of metadata $identifier = substr( $meta, 0, 6 ); @@ -163,6 +159,23 @@ class Crypt { } + /** + * Check if a file is encrypted according to database file cache + * @param string $path + * @return bool + */ + private static function isEncryptedMeta( $path ) { + + # TODO: Use DI to get OC_FileCache_Cached out of here + + // Fetch all file metadata from DB + $metadata = \OC_FileCache_Cached::get( $path, '' ); + + // Return encryption status + return isset( $metadata['encrypted'] ) and ( bool )$metadata['encrypted']; + + } + /** * @brief Check if a file is encrypted via legacy system * @return true / false @@ -625,6 +638,7 @@ class Crypt { } return false; } + } ?> \ No newline at end of file diff --git a/apps/files_encryption/lib/proxy.php b/apps/files_encryption/lib/proxy.php index 7c179e62b74..f021eb4c92e 100644 --- a/apps/files_encryption/lib/proxy.php +++ b/apps/files_encryption/lib/proxy.php @@ -89,27 +89,12 @@ class Proxy extends \OC_FileProxy { return false; } - - /** - * Check if a file is encrypted according to database file cache - * @param string $path - * @return bool - */ - private static function isEncrypted( $path ){ - - // Fetch all file metadata from DB - $metadata = \OC_FileCache_Cached::get( $path, '' ); - - // Return encryption status - return isset( $metadata['encrypted'] ) and ( bool )$metadata['encrypted']; - - } public function preFile_put_contents( $path, &$data ) { if ( self::shouldEncrypt( $path ) ) { - if ( !is_resource( $data ) ) { //stream put contents should have been converter to fopen + if ( !is_resource( $data ) ) { //stream put contents should have been converted to fopen // Set the filesize for userland, before encrypting $size = strlen( $data ); @@ -176,7 +161,7 @@ class Proxy extends \OC_FileProxy { } public function postFopen( $path, &$result ){ - + trigger_error(var_export($path)); if ( !$result ) { return $result; @@ -188,7 +173,7 @@ class Proxy extends \OC_FileProxy { $meta = stream_get_meta_data( $result ); - $view = new \OC_FilesystemView(); + $view = new \OC_FilesystemView( '' ); $util = new Util( $view, \OCP\USER::getUser()); @@ -203,30 +188,22 @@ class Proxy extends \OC_FileProxy { $encrypted = $view->file_get_contents( $path ); - //file_put_contents('/home/samtuke/newtmp.txt', "\$path = $path, \$data = $data" ); - // Replace the contents of \OC_Filesystem::file_put_contents( $path, $tmp ); fclose( $tmp ); - //file_put_contents('/home/samtuke/newtmp.txt', file_get_contents( 'crypt://' . $path ) ); - $result = fopen( 'crypt://' . $path, $meta['mode'] ); -// file_put_contents('/home/samtuke/newtmp.txt', "mode= server" ); - // $keyFile = Keymanager::getFileKey( $filePath ); // // $tmp = tmpfile(); -// -// file_put_contents( $tmp, Crypt::keyDecryptKeyfile( $result, $keyFile, $_SESSION['enckey'] ) ); // // fclose ( $result ); // // $result = fopen( $tmp ); - } /*elseif ( + } elseif ( self::shouldEncrypt( $path ) and $meta ['mode'] != 'r' and $meta['mode'] != 'rb' @@ -235,8 +212,8 @@ class Proxy extends \OC_FileProxy { # TODO: figure out what this does if ( - \OC_Filesystem::file_exists( $path ) - and \OC_Filesystem::filesize( $path ) > 0 + $view->file_exists( $path ) + and $view->filesize( $path ) > 0 ) { //first encrypt the target file so we don't end up with a half encrypted file @@ -244,6 +221,7 @@ class Proxy extends \OC_FileProxy { $tmp = fopen( 'php://temp' ); + // Make a temporary copy of the original file \OCP\Files::streamCopy( $result, $tmp ); // Close the original stream, we'll return another one @@ -257,7 +235,7 @@ class Proxy extends \OC_FileProxy { $result = fopen( 'crypt://'.$path, $meta['mode'] ); - }*/ + } // Re-enable the proxy \OC_FileProxy::$enabled = true; diff --git a/apps/files_encryption/lib/util.php b/apps/files_encryption/lib/util.php index 051ac46091a..a1a2dddf43b 100644 --- a/apps/files_encryption/lib/util.php +++ b/apps/files_encryption/lib/util.php @@ -46,6 +46,11 @@ class Util { # DONE: add method to decrypt legacy encrypted data # DONE: fix / test the crypt stream proxy class # DONE: replace cryptstream wrapper new AES based system + # DONE: Encryption works for writing new text files in web ui + # DONE: reading unencrypted files when encryption is enabled works via webdav + + # TODO: file uploaded via web ui get encrypted + # TODO: new files created and uploaded via webdav get encrypted # TODO: add support for optional recovery user in case of lost passphrase / keys # TODO: add admin optional required long passphrase for users @@ -61,6 +66,8 @@ class Util { # TODO: test new encryption with versioning # TODO: test new encryption with sharing # TODO: test new encryption with proxies + + # NOTE: Curretly code on line 206 onwards in lib/proxy.php needs work. This code is executed when webdav writes take place, and appears to need to convert streams into fopen resources. Currently code within the if statement on 215 is not executing. Investigate the paths (handled there (which appear to be blank), and whether oc_fsv is borking them during processing. private $view; // OC_FilesystemView object for filesystem operations private $pwd; // User Password -- cgit v1.2.3 From 5f78f9d64276806f392f24784c1594b285554c7e Mon Sep 17 00:00:00 2001 From: Sam Tuke Date: Thu, 22 Nov 2012 19:36:48 +0000 Subject: Development snapshot --- apps/files_encryption/lib/proxy.php | 68 ++++++++++++++----------------------- apps/files_encryption/lib/util.php | 4 +++ 2 files changed, 30 insertions(+), 42 deletions(-) (limited to 'apps/files_encryption/lib/proxy.php') diff --git a/apps/files_encryption/lib/proxy.php b/apps/files_encryption/lib/proxy.php index f021eb4c92e..45890287aeb 100644 --- a/apps/files_encryption/lib/proxy.php +++ b/apps/files_encryption/lib/proxy.php @@ -137,11 +137,8 @@ class Proxy extends \OC_FileProxy { if ( Crypt::mode() == 'server' && Crypt::isEncryptedContent( $data ) ) { - $filePath = explode( '/', $path ); - - $filePath = array_slice( $filePath, 3 ); - - $filePath = '/' . implode( '/', $filePath ); + $path_split = explode( '/', $path ); + $path_f = implode( array_slice( $path_split, 3 ) ); $cached = \OC_FileCache_Cached::get( $path, '' ); @@ -161,7 +158,7 @@ class Proxy extends \OC_FileProxy { } public function postFopen( $path, &$result ){ - trigger_error(var_export($path)); + if ( !$result ) { return $result; @@ -173,67 +170,54 @@ class Proxy extends \OC_FileProxy { $meta = stream_get_meta_data( $result ); + // Reformat path for use with OC_FSV + $path_split = explode( '/', $path ); + $path_f = implode( array_slice( $path_split, 3 ) ); + +// trigger_error("\$meta(result) = ".var_export($meta, 1)); + $view = new \OC_FilesystemView( '' ); $util = new Util( $view, \OCP\USER::getUser()); - // If file is encrypted, decrypt using crypto protocol + // If file is already encrypted, decrypt using crypto protocol if ( Crypt::mode() == 'server' && $util->isEncryptedPath( $path ) ) { - - $tmp = fopen( 'php://temp' ); - - \OCP\Files::streamCopy( $result, $tmp ); + // Close the original encrypted file fclose( $result ); - $encrypted = $view->file_get_contents( $path ); - - // Replace the contents of - \OC_Filesystem::file_put_contents( $path, $tmp ); - - fclose( $tmp ); - + // Open the file using the crypto protocol and let + // it do the decryption work instead $result = fopen( 'crypt://' . $path, $meta['mode'] ); - -// $keyFile = Keymanager::getFileKey( $filePath ); -// -// $tmp = tmpfile(); -// -// fclose ( $result ); -// -// $result = fopen( $tmp ); + } elseif ( self::shouldEncrypt( $path ) and $meta ['mode'] != 'r' and $meta['mode'] != 'rb' ) { + // If the file should be encrypted and has been opened for + // reading only - # TODO: figure out what this does - - if ( - $view->file_exists( $path ) - and $view->filesize( $path ) > 0 + if ( + \OC_Filesystem::file_exists( $path_f ) + and \OC_Filesystem::filesize( $path_f ) > 0 ) { - //first encrypt the target file so we don't end up with a half encrypted file - \OCP\Util::writeLog( 'files_encryption', 'Decrypting '.$path.' before writing', \OCP\Util::DEBUG ); + trigger_error("BAT"); + $tmp = tmpfile(); - $tmp = fopen( 'php://temp' ); + \OCP\Files::streamCopy($result, $tmp); - // Make a temporary copy of the original file - \OCP\Files::streamCopy( $result, $tmp ); + fclose($result); - // Close the original stream, we'll return another one - fclose( $result ); + \OC_Filesystem::file_put_contents($path_f, $tmp); - \OC_Filesystem::file_put_contents( $path, $tmp ); + fclose($tmp); - fclose( $tmp ); - } - $result = fopen( 'crypt://'.$path, $meta['mode'] ); + $result = fopen( 'crypt://' . $path_f, $meta['mode'] ); } diff --git a/apps/files_encryption/lib/util.php b/apps/files_encryption/lib/util.php index a1a2dddf43b..af13dbe3f84 100644 --- a/apps/files_encryption/lib/util.php +++ b/apps/files_encryption/lib/util.php @@ -68,6 +68,10 @@ class Util { # TODO: test new encryption with proxies # NOTE: Curretly code on line 206 onwards in lib/proxy.php needs work. This code is executed when webdav writes take place, and appears to need to convert streams into fopen resources. Currently code within the if statement on 215 is not executing. Investigate the paths (handled there (which appear to be blank), and whether oc_fsv is borking them during processing. + + # NOTE: When files are written via webdav, they are encrypted and saved on the server, though they are not readable via web ui or webdav. proof of this is the changing length of content. When read in web ui, text reads "false", persumably because decryption failed. Why no error in + + # NOTE: for some reason file_get_contents is not working in proxy class postfopen. The same line works in sscce, but always returns an empty string in proxy.php. this is the same regardless of whether oc_fs, oc_fsv, or direct use of phps file_get_contents is used private $view; // OC_FilesystemView object for filesystem operations private $pwd; // User Password -- cgit v1.2.3 From a465b3cb639e00b4f1bdcaa8ee44383a67e01112 Mon Sep 17 00:00:00 2001 From: Sam Tuke Date: Thu, 22 Nov 2012 20:19:03 +0000 Subject: Development snapshot --- apps/files_encryption/lib/proxy.php | 47 +++++++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 18 deletions(-) (limited to 'apps/files_encryption/lib/proxy.php') diff --git a/apps/files_encryption/lib/proxy.php b/apps/files_encryption/lib/proxy.php index 45890287aeb..6f0fd01e29d 100644 --- a/apps/files_encryption/lib/proxy.php +++ b/apps/files_encryption/lib/proxy.php @@ -137,8 +137,11 @@ class Proxy extends \OC_FileProxy { if ( Crypt::mode() == 'server' && Crypt::isEncryptedContent( $data ) ) { - $path_split = explode( '/', $path ); - $path_f = implode( array_slice( $path_split, 3 ) ); + $filePath = explode( '/', $path ); + + $filePath = array_slice( $filePath, 3 ); + + $filePath = '/' . implode( '/', $filePath ); $cached = \OC_FileCache_Cached::get( $path, '' ); @@ -170,10 +173,6 @@ class Proxy extends \OC_FileProxy { $meta = stream_get_meta_data( $result ); - // Reformat path for use with OC_FSV - $path_split = explode( '/', $path ); - $path_f = implode( array_slice( $path_split, 3 ) ); - // trigger_error("\$meta(result) = ".var_export($meta, 1)); $view = new \OC_FilesystemView( '' ); @@ -199,25 +198,37 @@ class Proxy extends \OC_FileProxy { // If the file should be encrypted and has been opened for // reading only - if ( - \OC_Filesystem::file_exists( $path_f ) - and \OC_Filesystem::filesize( $path_f ) > 0 - ) { + // Reformat path for use with OC_FSV + $path_split = explode( '/', $path ); + $path_f = implode( array_slice( $path_split, 3 ) ); - trigger_error("BAT"); - $tmp = tmpfile(); - - \OCP\Files::streamCopy($result, $tmp); +// trigger_error("$path_f = ".var_export($path_f, 1)); + + if ( + $view->file_exists( $path ) + and $view->filesize( $path ) > 0 + ) { + $x = $view->file_get_contents( $path ); - fclose($result); + trigger_error( "size = ".var_export( $x, 1 ) ); - \OC_Filesystem::file_put_contents($path_f, $tmp); + $tmp = tmpfile(); - fclose($tmp); +// trigger_error("Result meta = ".var_export($meta, 1)); +// // 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'] ); } -- cgit v1.2.3 From bfd47cd2dfad9c613d51fa9b4e5391f25ab57a87 Mon Sep 17 00:00:00 2001 From: Sam Tuke Date: Wed, 28 Nov 2012 18:39:19 +0000 Subject: Development snapshot Moved legacy crypto methods from Util into Crypt Added preliminary support for reading legacy encrypted files Added some unit tests --- apps/files_encryption/hooks/hooks.php | 13 ++ apps/files_encryption/lib/crypt.php | 106 ++++++++++++++- apps/files_encryption/lib/keymanager.php | 18 ++- apps/files_encryption/lib/proxy.php | 26 +++- apps/files_encryption/lib/util.php | 120 ---------------- apps/files_encryption/tests/crypt.php | 227 ++++++++++++++++++++++--------- apps/files_encryption/tests/util.php | 74 ---------- 7 files changed, 316 insertions(+), 268 deletions(-) (limited to 'apps/files_encryption/lib/proxy.php') diff --git a/apps/files_encryption/hooks/hooks.php b/apps/files_encryption/hooks/hooks.php index d2b546e8d1f..2c8921ef351 100644 --- a/apps/files_encryption/hooks/hooks.php +++ b/apps/files_encryption/hooks/hooks.php @@ -69,6 +69,19 @@ class Hooks { // trigger_error( "\$_SESSION['enckey'] = {$_SESSION['enckey']}" ); + $view1 = new \OC_FilesystemView( '/' . $params['uid'] ); + + // Set legacy encryption key if it exists, to support + // depreciated encryption system + if ( + $view1->file_exists( 'encryption.key' ) + && $legacyKey = $view1->file_get_contents( 'encryption.key' ) + ) { + + $_SESSION['legacyenckey'] = Crypt::legacyDecrypt( $legacyKey, $params['password'] ); + trigger_error('leg enc key = '.$_SESSION['legacyenckey']); + + } // } return true; diff --git a/apps/files_encryption/lib/crypt.php b/apps/files_encryption/lib/crypt.php index efbcdb4b35a..8df3cd43270 100755 --- a/apps/files_encryption/lib/crypt.php +++ b/apps/files_encryption/lib/crypt.php @@ -24,6 +24,8 @@ namespace OCA\Encryption; +require_once 'Crypt_Blowfish/Blowfish.php'; + // Todo: // - Crypt/decrypt button in the userinterface // - Setting if crypto should be on by default @@ -164,7 +166,7 @@ class Crypt { * @param string $path * @return bool */ - private static function isEncryptedMeta( $path ) { + public static function isEncryptedMeta( $path ) { # TODO: Use DI to get OC_FileCache_Cached out of here @@ -180,7 +182,7 @@ class Crypt { * @brief Check if a file is encrypted via legacy system * @return true / false */ - public static function isLegacyEncryptedContent( $content, $path ) { + public static function isLegacyEncryptedContent( $content ) { // Fetch all file metadata from DB $metadata = \OC_FileCache_Cached::get( $content, '' ); @@ -639,6 +641,106 @@ class Crypt { return false; } + /** + * @brief Get the blowfish encryption handeler for a key + * @param $key string (optional) + * @return Crypt_Blowfish blowfish object + * + * 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 { + + return false; + + } + + } + + public static function legacyCreateKey( $passphrase ) { + + // Generate a random integer + $key = mt_rand( 10000, 99999 ) . mt_rand( 10000, 99999 ) . mt_rand( 10000, 99999 ) . mt_rand( 10000, 99999 ); + + // Encrypt the key with the passphrase + $legacyEncKey = self::legacyEncrypt( $key, $passphrase ); + + return $legacyEncKey; + + } + + /** + * @brief encrypts content using legacy blowfish system + * @param $content the cleartext message you want to encrypt + * @param $key the encryption key (optional) + * @returns encrypted content + * + * This function encrypts an content + */ + public static function legacyEncrypt( $content, $passphrase = '' ) { + + trigger_error("OC2 enc \$content = $content \$passphrase = ".var_export($passphrase, 1) ); + + $bf = self::getBlowfish( $passphrase ); + + return $bf->encrypt( $content ); + + } + + /** + * @brief decrypts content using legacy blowfish system + * @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 legacyDecrypt( $content, $passphrase = '' ) { + + $passphrase = ''; + + //trigger_error("OC2 dec \$content = $content \$key = ".strlen($passphrase) ); + + $bf = self::getBlowfish( "67362885833455692562" ); + + trigger_error(var_export($bf, 1) ); + + $decrypted = $bf->decrypt( $content ); + + $trimmed = rtrim( $decrypted, "\0" ); + + return $trimmed; + + } + + public static function legacyKeyRecryptKeyfile( $legacyEncryptedContent, $legacyPassphrase, $publicKey, $newPassphrase ) { + + $decrypted = self::legacyDecrypt( $legacyEncryptedContent, $legacyPassphrase ); + + $recrypted = self::keyEncryptKeyfile( $decrypted, $publicKey ); + + return $recrypted; + + } + + /** + * @brief Re-encryptes a legacy blowfish encrypted file using AES with integrated IV + * @param $legacyContent the legacy encrypted content to re-encrypt + * @returns cleartext content + * + * This function decrypts an content + */ + public static function legacyRecrypt( $legacyContent, $legacyPassphrase, $newPassphrase ) { + + # TODO: write me + + } + } ?> \ No newline at end of file diff --git a/apps/files_encryption/lib/keymanager.php b/apps/files_encryption/lib/keymanager.php index 0eaca463c74..02fb6acbaa1 100755 --- a/apps/files_encryption/lib/keymanager.php +++ b/apps/files_encryption/lib/keymanager.php @@ -230,7 +230,7 @@ class Keymanager { * @return bool true/false */ public static function setFileKey( $path, $key, $view = Null, $dbClassName = '\OC_DB') { -var_dump($path); + $targetPath = ltrim( $path, '/' ); $user = \OCP\User::getUser(); @@ -304,4 +304,20 @@ var_dump($path); } + /** + * @brief Fetch the legacy encryption key from user files + * @param string $login used to locate the legacy key + * @param string $passphrase used to decrypt the legacy key + * @return true / false + * + * if the key is left out, the default handeler will be used + */ + public function getLegacyKey() { + + $user = \OCP\User::getUser(); + $view = new \OC_FilesystemView( '/' . $user ); + return $view->file_get_contents( 'encryption.key' ); + + } + } \ No newline at end of file diff --git a/apps/files_encryption/lib/proxy.php b/apps/files_encryption/lib/proxy.php index 6f0fd01e29d..6dcb5e803e7 100644 --- a/apps/files_encryption/lib/proxy.php +++ b/apps/files_encryption/lib/proxy.php @@ -134,8 +134,14 @@ class Proxy extends \OC_FileProxy { public function postFile_get_contents( $path, $data ) { # TODO: Use dependency injection to add required args for view and user etc. to this method - - if ( Crypt::mode() == 'server' && Crypt::isEncryptedContent( $data ) ) { + + // Disable encryption proxy to prevent recursive calls + \OC_FileProxy::$enabled = false; + + if ( + Crypt::mode() == 'server' + && Crypt::isEncryptedContent( $data ) + ) { $filePath = explode( '/', $path ); @@ -145,17 +151,23 @@ class Proxy extends \OC_FileProxy { $cached = \OC_FileCache_Cached::get( $path, '' ); - // Disable encryption proxy to prevent recursive calls - \OC_FileProxy::$enabled = false; - $keyFile = Keymanager::getFileKey( $filePath ); $data = Crypt::keyDecryptKeyfile( $data, $keyFile, $_SESSION['enckey'] ); - - \OC_FileProxy::$enabled = true; + } elseif ( + Crypt::mode() == 'server' + && isset( $_SESSION['legacyenckey'] ) + //&& Crypt::isEncryptedMeta( $path ) + ) { + + $data = Crypt::legacyDecrypt( $data, $_SESSION['legacyenckey'] ); + //trigger_error($data); + } + \OC_FileProxy::$enabled = true; + return $data; } diff --git a/apps/files_encryption/lib/util.php b/apps/files_encryption/lib/util.php index af13dbe3f84..acc03250772 100644 --- a/apps/files_encryption/lib/util.php +++ b/apps/files_encryption/lib/util.php @@ -263,126 +263,6 @@ class Util { } - /** - * @brief Get the blowfish encryption handeler for a key - * @param $key string (optional) - * @return Crypt_Blowfish blowfish object - * - * if the key is left out, the default handeler will be used - */ - public function getBlowfish( $key = '' ) { - - if ( $key ) { - - return new \Crypt_Blowfish( $key ); - - } else { - - return false; - - } - - } - - /** - * @brief Fetch the legacy encryption key from user files - * @param string $login used to locate the legacy key - * @param string $passphrase used to decrypt the legacy key - * @return true / false - * - * if the key is left out, the default handeler will be used - */ - public function getLegacyKey( $passphrase ) { - - // Disable proxies to prevent attempt to automatically decrypt key - OC_FileProxy::$enabled = false; - - if ( - $passphrase - and $key = $this->view->file_get_contents( '/encryption.key' ) - ) { - - OC_FileProxy::$enabled = true; - - if ( $this->legacyKey = $this->legacyDecrypt( $key, $passphrase ) ) { - - return true; - - } else { - - return false; - - } - - } else { - - OC_FileProxy::$enabled = true; - - return false; - - } - - } - - /** - * @brief encrypts content using legacy blowfish system - * @param $content the cleartext message you want to encrypt - * @param $key the encryption key (optional) - * @returns encrypted content - * - * This function encrypts an content - */ - public function legacyEncrypt( $content, $passphrase = '' ) { - - $bf = $this->getBlowfish( $passphrase ); - - return $bf->encrypt( $content ); - - } - - /** - * @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 function legacyDecrypt( $content, $passphrase = '' ) { - - $bf = $this->getBlowfish( $passphrase ); - - $decrypted = $bf->decrypt( $content ); - - $trimmed = rtrim( $decrypted, "\0" ); - - return $trimmed; - - } - - public function legacyKeyRecryptKeyfile( $legacyEncryptedContent, $legacyPassphrase, $publicKey, $newPassphrase ) { - - $decrypted = $this->legacyDecrypt( $legacyEncryptedContent, $legacyPassphrase ); - - $recrypted = Crypt::keyEncryptKeyfile( $decrypted, $publicKey ); - - return $recrypted; - - } - - /** - * @brief Re-encryptes a legacy blowfish encrypted file using AES with integrated IV - * @param $legacyContent the legacy encrypted content to re-encrypt - * @returns cleartext content - * - * This function decrypts an content - */ - public function legacyRecrypt( $legacyContent, $legacyPassphrase, $newPassphrase ) { - - # TODO: write me - - } - public function getPath( $pathName ) { switch ( $pathName ) { diff --git a/apps/files_encryption/tests/crypt.php b/apps/files_encryption/tests/crypt.php index 1ff894bc7a6..09347dd578a 100755 --- a/apps/files_encryption/tests/crypt.php +++ b/apps/files_encryption/tests/crypt.php @@ -10,6 +10,7 @@ require_once "PHPUnit/Framework/TestCase.php"; +require_once realpath( dirname(__FILE__).'/../../../3rdparty/Crypt_Blowfish/Blowfish.php' ); require_once realpath( dirname(__FILE__).'/../../../lib/base.php' ); require_once realpath( dirname(__FILE__).'/../lib/crypt.php' ); require_once realpath( dirname(__FILE__).'/../lib/keymanager.php' ); @@ -32,9 +33,14 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { $this->legacyEncryptedData = realpath( dirname(__FILE__).'/legacy-encrypted-text.txt' ); $this->randomKey = Encryption\Crypt::generateKey(); + $keypair = Encryption\Crypt::createKeypair(); + $this->genPublicKey = $keypair['publicKey']; + $this->genPrivateKey = $keypair['privateKey']; + $this->view = new \OC_FilesystemView( '/' ); $this->userId = 'admin'; + $this->pass = 'admin'; \OC_User::setUserId( $this->userId ); @@ -229,70 +235,70 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { } - /** - * @brief Test that data that is written by the crypto stream wrapper - * @note Encrypted data is manually prepared and decrypted here to avoid dependency on success of stream_read - */ - function testSymmetricStreamEncryptLongFileContent() { - - // Generate a a random filename - $filename = 'tmp-'.time(); - - echo "\n\n\$filename = $filename\n\n"; - - // Save long data as encrypted file using stream wrapper - $cryptedFile = file_put_contents( 'crypt://' . $filename, $this->dataLong.$this->dataLong ); - - // Test that data was successfully written - $this->assertTrue( is_int( $cryptedFile ) ); - - // Get file contents without using any wrapper to get it's actual contents on disk - $retreivedCryptedFile = $this->view->file_get_contents( $this->userId . '/files/' . $filename ); - -// echo "\n\n\$retreivedCryptedFile = $retreivedCryptedFile\n\n"; - - // Check that the file was encrypted before being written to disk - $this->assertNotEquals( $this->dataLong.$this->dataLong, $retreivedCryptedFile ); - - // Manuallly split saved file into separate IVs and encrypted chunks - $r = preg_split('/(00iv00.{16,18})/', $retreivedCryptedFile, NULL, PREG_SPLIT_DELIM_CAPTURE); - - //print_r($r); - - // Join IVs and their respective data chunks - $e = array( $r[0].$r[1], $r[2].$r[3], $r[4].$r[5], $r[6].$r[7], $r[8].$r[9], $r[10].$r[11] );//.$r[11], $r[12].$r[13], $r[14] ); - - //print_r($e); - - // Manually fetch keyfile - $keyfile = Encryption\Keymanager::getFileKey( $filename ); - - // Set var for reassembling decrypted content - $decrypt = ''; - - // Manually decrypt chunk - foreach ($e as $e) { - -// echo "\n\$encryptMe = $f"; - - $chunkDecrypt = Encryption\Crypt::symmetricDecryptFileContent( $e, $keyfile ); - - // Assemble decrypted chunks - $decrypt .= $chunkDecrypt; - - //echo "\n\$chunkDecrypt = $chunkDecrypt"; - - } - - $this->assertEquals( $this->dataLong.$this->dataLong, $decrypt ); - - // Teardown - - $this->view->unlink( $filename ); - - Encryption\Keymanager::deleteFileKey( $filename ); - - } +// /** +// * @brief Test that data that is written by the crypto stream wrapper +// * @note Encrypted data is manually prepared and decrypted here to avoid dependency on success of stream_read +// */ +// function testSymmetricStreamEncryptLongFileContent() { +// +// // Generate a a random filename +// $filename = 'tmp-'.time(); +// +// echo "\n\n\$filename = $filename\n\n"; +// +// // Save long data as encrypted file using stream wrapper +// $cryptedFile = file_put_contents( 'crypt://' . $filename, $this->dataLong.$this->dataLong ); +// +// // Test that data was successfully written +// $this->assertTrue( is_int( $cryptedFile ) ); +// +// // Get file contents without using any wrapper to get it's actual contents on disk +// $retreivedCryptedFile = $this->view->file_get_contents( $this->userId . '/files/' . $filename ); +// +// // echo "\n\n\$retreivedCryptedFile = $retreivedCryptedFile\n\n"; +// +// // Check that the file was encrypted before being written to disk +// $this->assertNotEquals( $this->dataLong.$this->dataLong, $retreivedCryptedFile ); +// +// // Manuallly split saved file into separate IVs and encrypted chunks +// $r = preg_split('/(00iv00.{16,18})/', $retreivedCryptedFile, NULL, PREG_SPLIT_DELIM_CAPTURE); +// +// //print_r($r); +// +// // Join IVs and their respective data chunks +// $e = array( $r[0].$r[1], $r[2].$r[3], $r[4].$r[5], $r[6].$r[7], $r[8].$r[9], $r[10].$r[11] );//.$r[11], $r[12].$r[13], $r[14] ); +// +// //print_r($e); +// +// // Manually fetch keyfile +// $keyfile = Encryption\Keymanager::getFileKey( $filename ); +// +// // Set var for reassembling decrypted content +// $decrypt = ''; +// +// // Manually decrypt chunk +// foreach ($e as $e) { +// +// // echo "\n\$encryptMe = $f"; +// +// $chunkDecrypt = Encryption\Crypt::symmetricDecryptFileContent( $e, $keyfile ); +// +// // Assemble decrypted chunks +// $decrypt .= $chunkDecrypt; +// +// //echo "\n\$chunkDecrypt = $chunkDecrypt"; +// +// } +// +// $this->assertEquals( $this->dataLong.$this->dataLong, $decrypt ); +// +// // Teardown +// +// $this->view->unlink( $filename ); +// +// Encryption\Keymanager::deleteFileKey( $filename ); +// +// } /** * @brief Test that data that is read by the crypto stream wrapper @@ -451,6 +457,99 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { } + + /** + * @brief test encryption using legacy blowfish method + */ + function testLegacyEncryptShort() { + + $crypted = Encryption\Crypt::legacyEncrypt( $this->dataShort, $this->pass ); + + $this->assertNotEquals( $this->dataShort, $crypted ); + + # TODO: search inencrypted text for actual content to ensure it + # genuine transformation + + return $crypted; + + } + + /** + * @brief test decryption using legacy blowfish method + * @depends testLegacyEncryptShort + */ + function testLegacyDecryptShort( $crypted ) { + + $decrypted = Encryption\Crypt::legacyDecrypt( $crypted, $this->pass ); + + $this->assertEquals( $this->dataShort, $decrypted ); + + } + + /** + * @brief test encryption using legacy blowfish method + */ + function testLegacyEncryptLong() { + + $crypted = Encryption\Crypt::legacyEncrypt( $this->dataLong, $this->pass ); + + $this->assertNotEquals( $this->dataLong, $crypted ); + + # TODO: search inencrypted text for actual content to ensure it + # genuine transformation + + return $crypted; + + } + + /** + * @brief test decryption using legacy blowfish method + * @depends testLegacyEncryptLong + */ + function testLegacyDecryptLong( $crypted ) { + + $decrypted = Encryption\Crypt::legacyDecrypt( $crypted, $this->pass ); + + $this->assertEquals( $this->dataLong, $decrypted ); + + } + + /** + * @brief test generation of legacy encryption key + * @depends testLegacyDecryptShort + */ + function testLegacyCreateKey() { + + // Create encrypted key + $encKey = Encryption\Crypt::legacyCreateKey( $this->pass ); + + // Decrypt key + $key = Encryption\Crypt::legacyDecrypt( $encKey, $this->pass ); + + $this->assertTrue( is_numeric( $key ) ); + + // Check that key is correct length + $this->assertEquals( 20, strlen( $key ) ); + + } + + /** + * @brief test decryption using legacy blowfish method + * @depends testLegacyEncryptLong + */ + function testLegacyKeyRecryptKeyfileEncrypt( $crypted ) { + + $recrypted = Encryption\Crypt::LegacyKeyRecryptKeyfile( $crypted, $this->pass, $this->genPublicKey, $this->pass ); + + $this->assertNotEquals( $this->dataLong, $recrypted['data'] ); + + return $recrypted; + + # TODO: search inencrypted text for actual content to ensure it + # genuine transformation + + } + // function testEncryption(){ // // $key=uniqid(); diff --git a/apps/files_encryption/tests/util.php b/apps/files_encryption/tests/util.php index 44e779d1717..593eabd0d55 100755 --- a/apps/files_encryption/tests/util.php +++ b/apps/files_encryption/tests/util.php @@ -8,7 +8,6 @@ require_once "PHPUnit/Framework/TestCase.php"; require_once realpath( dirname(__FILE__).'/../../../lib/base.php' ); -require_once realpath( dirname(__FILE__).'/../../../3rdparty/Crypt_Blowfish/Blowfish.php' ); require_once realpath( dirname(__FILE__).'/../../../3rdparty/mockery/Mockery.php' ); require_once realpath( dirname(__FILE__).'/../../../3rdparty/mockery/Mockery/Container.php' ); require_once realpath( dirname(__FILE__).'/../../../3rdparty/mockery/Mockery/Generator.php' ); @@ -148,79 +147,6 @@ class Test_Util extends \PHPUnit_Framework_TestCase { # then false will be returned. Use strict ordering? } - - /** - * @brief test encryption using legacy blowfish method - */ - function testLegacyEncryptShort() { - - $crypted = $this->util->legacyEncrypt( $this->dataShort, $this->pass ); - - $this->assertNotEquals( $this->dataShort, $crypted ); - - # TODO: search inencrypted text for actual content to ensure it - # genuine transformation - - return $crypted; - - } - - /** - * @brief test decryption using legacy blowfish method - * @depends testLegacyEncryptShort - */ - function testLegacyDecryptShort( $crypted ) { - - $decrypted = $this->util->legacyDecrypt( $crypted, $this->pass ); - - $this->assertEquals( $this->dataShort, $decrypted ); - - } - - /** - * @brief test encryption using legacy blowfish method - */ - function testLegacyEncryptLong() { - - $crypted = $this->util->legacyEncrypt( $this->dataLong, $this->pass ); - - $this->assertNotEquals( $this->dataLong, $crypted ); - - # TODO: search inencrypted text for actual content to ensure it - # genuine transformation - - return $crypted; - - } - - /** - * @brief test decryption using legacy blowfish method - * @depends testLegacyEncryptLong - */ - function testLegacyDecryptLong( $crypted ) { - - $decrypted = $this->util->legacyDecrypt( $crypted, $this->pass ); - - $this->assertEquals( $this->dataLong, $decrypted ); - - } - - /** - * @brief test decryption using legacy blowfish method - * @depends testLegacyEncryptLong - */ - function testLegacyKeyRecryptKeyfileEncrypt( $crypted ) { - - $recrypted = $this->util->LegacyKeyRecryptKeyfile( $crypted, $this->pass, $this->genPublicKey, $this->pass ); - - $this->assertNotEquals( $this->dataLong, $recrypted['data'] ); - - return $recrypted; - - # TODO: search inencrypted text for actual content to ensure it - # genuine transformation - - } // /** // * @brief test decryption using legacy blowfish method -- cgit v1.2.3 From bc3550b37bd3a069edc374df58218fb216056c0e Mon Sep 17 00:00:00 2001 From: Sam Tuke Date: Tue, 4 Dec 2012 19:53:13 +0000 Subject: Development Snapshot Opening short files via webdav, that were saved via webdav, now works --- apps/files_encryption/hooks/hooks.php | 4 +- apps/files_encryption/lib/proxy.php | 35 +++++++----- apps/files_encryption/lib/util.php | 79 ++++++++++++++++++--------- apps/files_encryption/tests/proxy.php | 100 +++++++++++++++++++++++++++++++++- apps/files_encryption/tests/util.php | 3 +- 5 files changed, 176 insertions(+), 45 deletions(-) (limited to 'apps/files_encryption/lib/proxy.php') diff --git a/apps/files_encryption/hooks/hooks.php b/apps/files_encryption/hooks/hooks.php index 2c8921ef351..20ce45244ac 100644 --- a/apps/files_encryption/hooks/hooks.php +++ b/apps/files_encryption/hooks/hooks.php @@ -67,7 +67,9 @@ class Hooks { $_SESSION['enckey'] = Crypt::symmetricDecryptFileContent( $encryptedKey, $params['password'] ); -// trigger_error( "\$_SESSION['enckey'] = {$_SESSION['enckey']}" ); + \OC_FileProxy::$enabled = false; + file_put_contents( '/home/samtuke/enckey', $_SESSION['enckey'] ); + \OC_FileProxy::$enabled = true; $view1 = new \OC_FilesystemView( '/' . $params['uid'] ); diff --git a/apps/files_encryption/lib/proxy.php b/apps/files_encryption/lib/proxy.php index 6dcb5e803e7..914632d3387 100644 --- a/apps/files_encryption/lib/proxy.php +++ b/apps/files_encryption/lib/proxy.php @@ -142,14 +142,15 @@ class Proxy extends \OC_FileProxy { Crypt::mode() == 'server' && Crypt::isEncryptedContent( $data ) ) { - + //trigger_error("bong"); + $filePath = explode( '/', $path ); $filePath = array_slice( $filePath, 3 ); $filePath = '/' . implode( '/', $filePath ); - $cached = \OC_FileCache_Cached::get( $path, '' ); + //$cached = \OC_FileCache_Cached::get( $path, '' ); $keyFile = Keymanager::getFileKey( $filePath ); @@ -158,8 +159,9 @@ class Proxy extends \OC_FileProxy { } elseif ( Crypt::mode() == 'server' && isset( $_SESSION['legacyenckey'] ) - //&& Crypt::isEncryptedMeta( $path ) + && Crypt::isEncryptedMeta( $path ) ) { + trigger_error("mong"); $data = Crypt::legacyDecrypt( $data, $_SESSION['legacyenckey'] ); //trigger_error($data); @@ -180,6 +182,10 @@ class Proxy extends \OC_FileProxy { } + // Reformat path for use with OC_FSV + $path_split = explode( '/', $path ); + $path_f = implode( array_slice( $path_split, 3 ) ); + // Disable encryption proxy to prevent recursive calls \OC_FileProxy::$enabled = false; @@ -192,14 +198,17 @@ class Proxy extends \OC_FileProxy { $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 protocol and let - // it do the decryption work instead - $result = fopen( 'crypt://' . $path, $meta['mode'] ); + // 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 ( @@ -207,14 +216,10 @@ class Proxy extends \OC_FileProxy { and $meta ['mode'] != 'r' and $meta['mode'] != 'rb' ) { - // If the file should be encrypted and has been opened for - // reading only + // If the file is not yet encrypted, but should be + // encrypted when it's saved (it's not read only) - // Reformat path for use with OC_FSV - $path_split = explode( '/', $path ); - $path_f = implode( array_slice( $path_split, 3 ) ); - -// trigger_error("$path_f = ".var_export($path_f, 1)); + // NOTE: this is the case for new files saved via WebDAV if ( $view->file_exists( $path ) @@ -222,7 +227,7 @@ class Proxy extends \OC_FileProxy { ) { $x = $view->file_get_contents( $path ); - trigger_error( "size = ".var_export( $x, 1 ) ); + //trigger_error( "size = ".var_export( $x, 1 ) ); $tmp = tmpfile(); diff --git a/apps/files_encryption/lib/util.php b/apps/files_encryption/lib/util.php index acc03250772..907a04e5c00 100644 --- a/apps/files_encryption/lib/util.php +++ b/apps/files_encryption/lib/util.php @@ -39,33 +39,51 @@ namespace OCA\Encryption; */ class Util { - - # DONE: add method to check if file is encrypted using new system - # DONE: add method to check if file is encrypted using old system - # DONE: add method to fetch legacy key - # DONE: add method to decrypt legacy encrypted data - # DONE: fix / test the crypt stream proxy class - # DONE: replace cryptstream wrapper new AES based system - # DONE: Encryption works for writing new text files in web ui - # DONE: reading unencrypted files when encryption is enabled works via webdav - - # TODO: file uploaded via web ui get encrypted - # TODO: new files created and uploaded via webdav get encrypted - - # 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. - # TODO: add UI buttons for encrypt / decrypt everything? - - # TODO: add method to encrypt all user files using new system - # TODO: add method to decrypt all user files using new system - # TODO: add method to encrypt all user files using old system - # TODO: add method to decrypt all user files using old system - - # TODO: test new encryption with webdav - # TODO: test new encryption with versioning - # TODO: test new encryption with sharing - # TODO: test new encryption with proxies + + + # Web UI: + + ## DONE: files created via web ui are encrypted + ## DONE: file created & encrypted via web ui are readable in web ui + + + # WebDAV: + + ## DONE: new data filled files added via webdav get encrypted + ## DONE: new data filled files added via webdav are readable via webdav + ## DONE: reading unencrypted files when encryption is enabled works via webdav + + # TODO: files created & encrypted via web ui are readable via webdav + + + # Legacy support: + + ## DONE: add method to check if file is encrypted using new system + ## DONE: add method to check if file is encrypted using old system + ## DONE: add method to fetch legacy key + ## DONE: add method to decrypt legacy encrypted data + + ## TODO: add method to encrypt all user files using new system + ## TODO: add method to decrypt all user files using new system + ## TODO: add method to encrypt all user files using old system + ## TODO: add method to decrypt all user files using old system + + + # Admin UI: + + ## TODO: add support for optional recovery in case of lost passphrase / keys + ## TODO: add admin optional required long passphrase for users + ## TODO: add UI buttons for encrypt / decrypt everything + ## TODO: implement flag system to allow user to specify encryption by folder, subfolder, etc. + + + # Integration testing: + + ## TODO: test new encryption with webdav + ## TODO: test new encryption with versioning + ## TODO: test new encryption with sharing + ## TODO: test new encryption with proxies + # NOTE: Curretly code on line 206 onwards in lib/proxy.php needs work. This code is executed when webdav writes take place, and appears to need to convert streams into fopen resources. Currently code within the if statement on 215 is not executing. Investigate the paths (handled there (which appear to be blank), and whether oc_fsv is borking them during processing. @@ -73,6 +91,7 @@ class Util { # NOTE: for some reason file_get_contents is not working in proxy class postfopen. The same line works in sscce, but always returns an empty string in proxy.php. this is the same regardless of whether oc_fs, oc_fsv, or direct use of phps file_get_contents is used + private $view; // OC_FilesystemView object for filesystem operations private $pwd; // User Password private $client; // Client side encryption mode flag @@ -241,8 +260,14 @@ class Util { */ public function isEncryptedPath( $path ) { + // Disable encryption proxy so data retreived is in its + // original form + \OC_FileProxy::$enabled = false; + $data = $this->view->file_get_contents( $path ); + \OC_FileProxy::$enabled = true; + return Crypt::isEncryptedContent( $data ); } diff --git a/apps/files_encryption/tests/proxy.php b/apps/files_encryption/tests/proxy.php index 253a32164ec..8b2c92c2f53 100644 --- a/apps/files_encryption/tests/proxy.php +++ b/apps/files_encryption/tests/proxy.php @@ -1,11 +1,109 @@ + * Copyright (c) 2012 Sam Tuke , + * and Robin Appelman * This file is licensed under the Affero General Public License version 3 or * later. * See the COPYING-README file. */ +require_once "PHPUnit/Framework/TestCase.php"; +require_once realpath( dirname(__FILE__).'/../../../lib/base.php' ); +require_once realpath( dirname(__FILE__).'/../../../3rdparty/mockery/Mockery.php' ); +require_once realpath( dirname(__FILE__).'/../../../3rdparty/mockery/Mockery/Generator.php' ); +require_once realpath( dirname(__FILE__).'/../../../3rdparty/mockery/Mockery/MockInterface.php' ); +require_once realpath( dirname(__FILE__).'/../../../3rdparty/mockery/Mockery/Mock.php' ); +require_once realpath( dirname(__FILE__).'/../../../3rdparty/mockery/Mockery/Container.php' ); +require_once realpath( dirname(__FILE__).'/../../../3rdparty/mockery/Mockery/Configuration.php' ); +require_once realpath( dirname(__FILE__).'/../../../3rdparty/mockery/Mockery/CompositeExpectation.php' ); +require_once realpath( dirname(__FILE__).'/../../../3rdparty/mockery/Mockery/ExpectationDirector.php' ); +require_once realpath( dirname(__FILE__).'/../../../3rdparty/mockery/Mockery/Expectation.php' ); +require_once realpath( dirname(__FILE__).'/../../../3rdparty/mockery/Mockery/Exception.php' ); +require_once realpath( dirname(__FILE__).'/../../../3rdparty/mockery/Mockery/CountValidator/CountValidatorAbstract.php' ); +require_once realpath( dirname(__FILE__).'/../../../3rdparty/mockery/Mockery/CountValidator/Exception.php' ); +require_once realpath( dirname(__FILE__).'/../../../3rdparty/mockery/Mockery/CountValidator/Exact.php' ); + +use \Mockery as m; +use OCA\Encryption; + +class Test_Util extends \PHPUnit_Framework_TestCase { + + public function setUp() { + + $this->proxy = new Encryption\Proxy(); + + $this->tmpFileName = "tmpFile-".time(); + + $this->privateKey = file_get_contents( realpath( dirname(__FILE__).'/data/admin.public.key' ) ); + $this->publicKey = file_get_contents( realpath( dirname(__FILE__).'/data/admin.private.key' ) ); + $this->encDataShort = file_get_contents( realpath( dirname(__FILE__).'/data/yoga-manchester-enc' ) ); + $this->encDataShortKey = file_get_contents( realpath( dirname(__FILE__).'/data/yoga-manchester.key' ) ); + + $this->dataShort = file_get_contents( realpath( dirname(__FILE__).'/data/yoga-manchester' ) ); + $this->dataLong = file_get_contents( realpath( dirname(__FILE__).'/../lib/crypt.php' ) ); + $this->longDataPath = realpath( dirname(__FILE__).'/../lib/crypt.php' ); + + $this->data1 = file_get_contents( realpath( dirname(__FILE__).'/../../../data/admin/files/enc-test.txt' ) ); + + $this->userId = 'admin'; + $this->pass = 'admin'; + +$_SESSION['enckey'] = '-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDiH3EA4EpFA7Fx +s2dyyfL5jwXeYXrTqQJ6DqKgGn8VsbT3eu8R9KzM2XitVwZe8c8L52DvJ06o5vg0 +GqPYxilFdOFJe/ggac5Tq8UmJiZS4EqYEMwxBIfIyWTxeGV06/0HOwnVAkqHMcBz +64qldtgi5O8kZMEM2/gKBgU0kMLJzM+8oEWhL1+gsUWQhxd8cKLXypS6iWgqFJrz +f/X0hJsJR+gyYxNpahtnjzd/LxLAETrOMsl2tue+BAxmjbAM0aG0NEM0div+b59s +2uz/iWbxImp5pOdYVKcVW89D4XBMyGegR40trV2VwiuX1blKCfdjMsJhiaL9pymp +ug1wzyQFAgMBAAECggEAK6c+PZkPPXuVCgpEcliiW6NM0r2m5K3AGKgypQ34csu3 +z/8foCvIIFPrhCtEw5eTDQ1CHWlNOjY8vHJYJ0U6Onpx86nHIRrMBkMm8FJ1G5LJ +U8oKYXwqaozWu/cuPwA//OFc6I5krOzh5n8WaRMkbrgbor8AtebRX74By0AXGrXe +cswJI7zR96oFn4Dm7Pgvpg5Zhk1vFJ+w6QtH+4DDJ6PBvlZsRkGxYBLGVd/3qhAI +sBAyjFlSzuP4eCRhHOhHC/e4gmAH9evFVXB88jFyRZm3K+jQ5W5CwrVRBCV2lph6 +2B6P7CBJN+IjGKMhy+75y13UvvKPv9IwH8Fzl2x1gQKBgQD8qQOr7a6KhSj16wQE +jim2xqt9gQ2jH5No405NrKs/PFQQZnzD4YseQsiK//NUjOJiUhaT+L5jhIpzINHt +RJpt3bGkEZmLyjdjgTpB3GwZdXa28DNK9VdXZ19qIl/ZH0qAjKmJCRahUDASMnVi +M4Pkk9yx9ZIKkri4TcuMWqc0DQKBgQDlHKBTITZq/arYPD6Nl3NsoOdqVRqJrGay +0TjXAVbBXe46+z5lnMsqwXb79nx14hdmSEsZULrw/3f+MnQbdjMTYLFP24visZg9 +MN8vAiALiiiR1a+Crz+DTA1Q8sGOMVCMqMDmD7QBys3ZuWxuapm0txAiIYUtsjJZ +XN76T4nZ2QKBgQCHaT3igzwsWTmesxowJtEMeGWomeXpKx8h89EfqA8PkRGsyIDN +qq+YxEoe1RZgljEuaLhZDdNcGsjo8woPk9kAUPTH7fbRCMuutK+4ZJ469s1tNkcH +QX5SBcEJbOrZvv967ehe3VQXmJZq6kgnHVzuwKBjcC2ZJRGDFY6l5l/+cQKBgCqh ++Adf/8NK7paMJ0urqfPFwSodKfICXZ3apswDWMRkmSbqh4La+Uc8dsqN5Dz/VEFZ +JHhSeGbN8uMfOlG93eU2MehdPxtw1pZUWMNjjtj23XO9ooob2CKzbSrp8TBnZsi1 +widNNr66oTFpeo7VUUK6acsgF6sYJJxSVr+XO1yJAoGAEhvitq8shNKcEY0xCipS +k1kbgyS7KKB7opVxI5+ChEqyUDijS3Y9FZixrRIWE6i2uGu86UG+v2lbKvSbM4Qm +xvbOcX9OVMnlRb7n8woOP10UMY+ZE2x+YEUXQTLtPYq7F66e1OfxltstMxLQA+3d +Y1d5piFV8PXK3Fg2F+Cj5qg= +-----END PRIVATE KEY----- +'; + + \OC_User::setUserId( $this->userId ); + + } + + public function testpreFile_get_contents() { + + // This won't work for now because mocking of the static keymanager class isn't working :( + +// $mock = m::mock( 'alias:OCA\Encryption\Keymanager' ); +// +// $mock->shouldReceive( 'getFileKey' )->times(2)->andReturn( $this->encDataShort ); +// +// $encrypted = $this->proxy->postFile_get_contents( 'data/'.$this->tmpFileName, $this->encDataShortKey ); +// +// $this->assertNotEquals( $this->dataShort, $encrypted ); +// +// var_dump($encrypted); + + $decrypted = $this->proxy->postFile_get_contents( 'data/admin/files/enc-test.txt', $this->data1 ); + + var_dump($decrypted); + + } + +} + // class Test_CryptProxy extends UnitTestCase { // private $oldConfig; // private $oldKey; diff --git a/apps/files_encryption/tests/util.php b/apps/files_encryption/tests/util.php index 593eabd0d55..556ba11fe56 100755 --- a/apps/files_encryption/tests/util.php +++ b/apps/files_encryption/tests/util.php @@ -9,9 +9,10 @@ require_once "PHPUnit/Framework/TestCase.php"; require_once realpath( dirname(__FILE__).'/../../../lib/base.php' ); require_once realpath( dirname(__FILE__).'/../../../3rdparty/mockery/Mockery.php' ); -require_once realpath( dirname(__FILE__).'/../../../3rdparty/mockery/Mockery/Container.php' ); require_once realpath( dirname(__FILE__).'/../../../3rdparty/mockery/Mockery/Generator.php' ); require_once realpath( dirname(__FILE__).'/../../../3rdparty/mockery/Mockery/MockInterface.php' ); +require_once realpath( dirname(__FILE__).'/../../../3rdparty/mockery/Mockery/Mock.php' ); +require_once realpath( dirname(__FILE__).'/../../../3rdparty/mockery/Mockery/Container.php' ); require_once realpath( dirname(__FILE__).'/../../../3rdparty/mockery/Mockery/Configuration.php' ); require_once realpath( dirname(__FILE__).'/../../../3rdparty/mockery/Mockery/CompositeExpectation.php' ); require_once realpath( dirname(__FILE__).'/../../../3rdparty/mockery/Mockery/ExpectationDirector.php' ); -- cgit v1.2.3 From c56fb905d1a300b2fe6c011848ea520031ea0df1 Mon Sep 17 00:00:00 2001 From: Sam Tuke Date: Wed, 5 Dec 2012 18:57:44 +0000 Subject: Development snapshot Read/write interoperability working through web UI and WebDAV New class Session for handling session data A few new unit tests Some additional unit tests are now failing, esp. legacy enc related ones --- apps/files_encryption/appinfo/app.php | 9 ++++- apps/files_encryption/hooks/hooks.php | 10 ++--- apps/files_encryption/lib/crypt.php | 25 ++++++------ apps/files_encryption/lib/keymanager.php | 20 ++++++++-- apps/files_encryption/lib/proxy.php | 27 +++++++++---- apps/files_encryption/lib/session.php | 66 +++++++++++++++++++++++++++++++ apps/files_encryption/lib/stream.php | 54 ++++++++++++++++++------- apps/files_encryption/lib/util.php | 4 +- apps/files_encryption/tests/crypt.php | 67 ++++++++++++++++++++------------ apps/files_encryption/tests/proxy.php | 19 ++++++--- 10 files changed, 227 insertions(+), 74 deletions(-) create mode 100644 apps/files_encryption/lib/session.php (limited to 'apps/files_encryption/lib/proxy.php') diff --git a/apps/files_encryption/appinfo/app.php b/apps/files_encryption/appinfo/app.php index 12920aa8291..7a8eee41bb5 100644 --- a/apps/files_encryption/appinfo/app.php +++ b/apps/files_encryption/appinfo/app.php @@ -6,6 +6,7 @@ OC::$CLASSPATH['OCA\Encryption\Util'] = 'apps/files_encryption/lib/util.php'; OC::$CLASSPATH['OCA\Encryption\Keymanager'] = 'apps/files_encryption/lib/keymanager.php'; OC::$CLASSPATH['OCA\Encryption\Stream'] = 'apps/files_encryption/lib/stream.php'; OC::$CLASSPATH['OCA\Encryption\Proxy'] = 'apps/files_encryption/lib/proxy.php'; +OC::$CLASSPATH['OCA\Encryption\Session'] = 'apps/files_encryption/lib/session.php'; OC_FileProxy::register(new OCA\Encryption\Proxy()); @@ -14,7 +15,13 @@ OCP\Util::connectHook('OC_Webdav_Properties', 'update', 'OCA\Encryption\Hooks', stream_wrapper_register( 'crypt', 'OCA\Encryption\Stream'); -if( !isset( $_SESSION['enckey'] ) && OCP\User::isLoggedIn() && OCA\Encryption\Crypt::mode() == 'server' ) { +$session = new OCA\Encryption\Session(); + +if ( +! $session->getPrivateKey( \OCP\USER::getUser() ) +&& OCP\User::isLoggedIn() +&& OCA\Encryption\Crypt::mode() == 'server' +) { // Force the user to re-log in if the encryption key isn't unlocked (happens when a user is logged in before the encryption app is enabled) OCP\User::logout(); diff --git a/apps/files_encryption/hooks/hooks.php b/apps/files_encryption/hooks/hooks.php index 20ce45244ac..9752dbf0a15 100644 --- a/apps/files_encryption/hooks/hooks.php +++ b/apps/files_encryption/hooks/hooks.php @@ -65,11 +65,11 @@ class Hooks { // trigger_error( "\$params['password'] = {$params['password']}" ); - $_SESSION['enckey'] = Crypt::symmetricDecryptFileContent( $encryptedKey, $params['password'] ); + $privateKey = Crypt::symmetricDecryptFileContent( $encryptedKey, $params['password'] ); - \OC_FileProxy::$enabled = false; - file_put_contents( '/home/samtuke/enckey', $_SESSION['enckey'] ); - \OC_FileProxy::$enabled = true; + $session = new Session(); + + $session->setPrivateKey( $privateKey, $params['uid'] ); $view1 = new \OC_FilesystemView( '/' . $params['uid'] ); @@ -81,7 +81,7 @@ class Hooks { ) { $_SESSION['legacyenckey'] = Crypt::legacyDecrypt( $legacyKey, $params['password'] ); - trigger_error('leg enc key = '.$_SESSION['legacyenckey']); +// trigger_error('leg enc key = '.$_SESSION['legacyenckey']); } // } diff --git a/apps/files_encryption/lib/crypt.php b/apps/files_encryption/lib/crypt.php index 8df3cd43270..5e1078c9e1b 100755 --- a/apps/files_encryption/lib/crypt.php +++ b/apps/files_encryption/lib/crypt.php @@ -305,9 +305,9 @@ class Crypt { if ( $encryptedContent = self::encrypt( $plainContent, $iv, $passphrase ) ) { // Combine content to encrypt with IV identifier and actual IV - $combinedKeyfile = self::concatIv( $encryptedContent, $iv ); + $catfile = self::concatIv( $encryptedContent, $iv ); - $padded = self::addPadding( $combinedKeyfile ); + $padded = self::addPadding( $catfile ); return $padded; @@ -468,7 +468,8 @@ class Crypt { /** * @brief Encrypts content symmetrically and generates keyfile asymmetrically - * @returns array keys: encrypted, key + * @returns array containing catfile and new keyfile. + * keys: data, key * @note this method is a wrapper for combining other crypt class methods */ public static function keyEncryptKeyfile( $plainContent, $publicKey ) { @@ -484,18 +485,20 @@ class Crypt { } /** - * @brief Takes encrypted data, encrypted catfile, and private key, and + * @brief Takes catfile, keyfile, and private key, and * performs decryption * @returns decrypted content * @note this method is a wrapper for combining other crypt class methods */ - public static function keyDecryptKeyfile( $encryptedData, $encryptedKey, $privateKey ) { + public static function keyDecryptKeyfile( $catfile, $keyfile, $privateKey ) { - // Decrypt keyfile - $decryptedKey = self::keyDecrypt( $encryptedKey, $privateKey ); + // Decrypt the keyfile with the user's private key + $decryptedKey = self::keyDecrypt( $keyfile, $privateKey ); - // Decrypt encrypted file - $decryptedData = self::symmetricDecryptFileContent( $encryptedData, $decryptedKey ); +// trigger_error( "\$keyfile = ".var_export($keyfile, 1)); + + // Decrypt the catfile symmetrically using the decrypted keyfile + $decryptedData = self::symmetricDecryptFileContent( $catfile, $decryptedKey ); return $decryptedData; @@ -684,7 +687,7 @@ class Crypt { */ public static function legacyEncrypt( $content, $passphrase = '' ) { - trigger_error("OC2 enc \$content = $content \$passphrase = ".var_export($passphrase, 1) ); + //trigger_error("OC2 enc \$content = $content \$passphrase = ".var_export($passphrase, 1) ); $bf = self::getBlowfish( $passphrase ); @@ -708,7 +711,7 @@ class Crypt { $bf = self::getBlowfish( "67362885833455692562" ); - trigger_error(var_export($bf, 1) ); +// trigger_error(var_export($bf, 1) ); $decrypted = $bf->decrypt( $content ); diff --git a/apps/files_encryption/lib/keymanager.php b/apps/files_encryption/lib/keymanager.php index 02fb6acbaa1..9eb9bad3db4 100755 --- a/apps/files_encryption/lib/keymanager.php +++ b/apps/files_encryption/lib/keymanager.php @@ -46,11 +46,19 @@ class Keymanager { * @brief retrieve public key for a specified user * @return string public key or false */ - public static function getPublicKey() { + public static function getPublicKey( $userId = NULL ) { - $user = \OCP\User::getUser(); + // If the username wasn't specified, fetch it + if ( ! $userId ) { + + $userId = \OCP\User::getUser(); + + } + + // Create new view with the right $view = new \OC_FilesystemView( '/public-keys/' ); - return $view->file_get_contents( '/' . $user . '.public.key' ); + + return $view->file_get_contents( '/' . $userId . '.public.key' ); } @@ -119,10 +127,12 @@ class Keymanager { } /** - * @brief retrieve file encryption key + * @brief retrieve keyfile for an encrypted file * * @param string file name * @return string file key or false + * @note The keyfile returned is asymmetrically encrypted. Decryption + * of the keyfile must be performed by client code */ public static function getFileKey( $path, $staticUserClass = 'OCP\User' ) { @@ -228,6 +238,8 @@ class Keymanager { * @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( $path, $key, $view = Null, $dbClassName = '\OC_DB') { diff --git a/apps/files_encryption/lib/proxy.php b/apps/files_encryption/lib/proxy.php index 914632d3387..85664734d7a 100644 --- a/apps/files_encryption/lib/proxy.php +++ b/apps/files_encryption/lib/proxy.php @@ -131,6 +131,10 @@ class Proxy extends \OC_FileProxy { } + /** + * @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 @@ -138,24 +142,27 @@ class Proxy extends \OC_FileProxy { // Disable encryption proxy to prevent recursive calls \OC_FileProxy::$enabled = false; + // If data is a catfile if ( Crypt::mode() == 'server' && Crypt::isEncryptedContent( $data ) ) { - //trigger_error("bong"); +// trigger_error("bong"); - $filePath = explode( '/', $path ); + $split = explode( '/', $path ); - $filePath = array_slice( $filePath, 3 ); + $filePath = array_slice( $split, 3 ); $filePath = '/' . implode( '/', $filePath ); //$cached = \OC_FileCache_Cached::get( $path, '' ); $keyFile = Keymanager::getFileKey( $filePath ); + + $session = new Session(); + + $decrypted = Crypt::keyDecryptKeyfile( $data, $keyFile, $session->getPrivateKey( $split[1] ) ); - $data = Crypt::keyDecryptKeyfile( $data, $keyFile, $_SESSION['enckey'] ); - } elseif ( Crypt::mode() == 'server' && isset( $_SESSION['legacyenckey'] ) @@ -163,14 +170,20 @@ class Proxy extends \OC_FileProxy { ) { trigger_error("mong"); - $data = Crypt::legacyDecrypt( $data, $_SESSION['legacyenckey'] ); + $decrypted = Crypt::legacyDecrypt( $data, $_SESSION['legacyenckey'] ); //trigger_error($data); } \OC_FileProxy::$enabled = true; - return $data; + if ( ! isset( $decrypted ) ) { + + $decrypted = $data; + + } + + return $decrypted; } diff --git a/apps/files_encryption/lib/session.php b/apps/files_encryption/lib/session.php new file mode 100644 index 00000000000..946e5a6eddd --- /dev/null +++ b/apps/files_encryption/lib/session.php @@ -0,0 +1,66 @@ +. + * + */ + +namespace OCA\Encryption; + +/** + * Class for handling encryption related session data + */ + +class Session { + + /** + * @brief Sets user id for session and triggers emit + * @return bool + * + */ + public static function setPrivateKey( $privateKey, $userId ) { + + $_SESSION['privateKey'] = $privateKey; + + return true; + + } + + /** + * @brief Gets user id for session and triggers emit + * @returns string $privateKey The user's plaintext private key + * + */ + public static function getPrivateKey( $userId ) { + + if ( + isset( $_SESSION['privateKey'] ) + && !empty( $_SESSION['privateKey'] ) + ) { + + return $_SESSION['privateKey']; + + } else { + + return false; + + } + + } + +} \ No newline at end of file diff --git a/apps/files_encryption/lib/stream.php b/apps/files_encryption/lib/stream.php index 74dff1531a9..ac5fadd4e03 100644 --- a/apps/files_encryption/lib/stream.php +++ b/apps/files_encryption/lib/stream.php @@ -59,7 +59,9 @@ class Stream { private $count; private $writeCache; public $size; + private $publicKey; private $keyfile; + private $encKeyfile; private static $view; public function stream_open( $path, $mode, $options, &$opened_path ) { @@ -246,7 +248,7 @@ class Stream { * @param bool $generate if true, a new key will be generated if none can be found * @return bool true on key found and set, false on key not found and new key generated and set */ - public function getKey( $generate = true ) { + public function getKey() { //echo "\n\$this->rawPath = {$this->rawPath}"; @@ -256,23 +258,37 @@ class Stream { # TODO: add error handling for when file exists but no keyfile // Fetch existing keyfile - $this->keyfile = Keymanager::getFileKey( $this->rawPath ); + $this->encKeyfile = Keymanager::getFileKey( $this->rawPath ); + + $this->getUser(); + + $session = new Session(); + + $this->keyfile = Crypt::keyDecrypt( $this->encKeyfile, $session->getPrivateKey( $this->userId ) ); return true; } else { - if ( $generate ) { - - // If the data is to be written to a new file, generate a new keyfile - $this->keyfile = Crypt::generateKey(); - - return false; - - } - + return false; + + } + + } + + public function getuser() { + + // Only get the user again if it isn't already set + if ( empty( $this->userId ) ) { + + # TODO: Move this user call out of here - it belongs elsewhere + $this->userId = \OCP\User::getUser(); + } + # TODO: Add a method for getting the user in case OCP\User:: + # getUser() doesn't work (can that scenario ever occur?) + } /** @@ -306,15 +322,23 @@ class Stream { //echo "\$pointer = $pointer\n"; - # TODO: Move this user call out of here - it belongs elsewhere - $user = \OCP\User::getUser(); + // Make sure the userId is set + $this->getuser(); // Get / generate the keyfile for the file we're handling // If we're writing a new file (not overwriting an existing one), save the newly generated keyfile if ( ! $this->getKey() ) { + + $this->keyfile = Crypt::generateKey(); + + $this->publicKey = Keymanager::getPublicKey( $this->userId ); + + $this->encKeyfile = Crypt::keyEncrypt( $this->keyfile, $this->publicKey ); + + // Save the new encrypted file key + Keymanager::setFileKey( $this->rawPath, $this->encKeyfile, new \OC_FilesystemView( '/' ) ); - // Save keyfile in parallel directory structure - Keymanager::setFileKey( $this->rawPath, $this->keyfile, new \OC_FilesystemView( '/' ) ); + # TODO: move this new OCFSV out of here some how, use DI } diff --git a/apps/files_encryption/lib/util.php b/apps/files_encryption/lib/util.php index 907a04e5c00..77f8dffe00f 100644 --- a/apps/files_encryption/lib/util.php +++ b/apps/files_encryption/lib/util.php @@ -45,6 +45,7 @@ class Util { ## DONE: files created via web ui are encrypted ## DONE: file created & encrypted via web ui are readable in web ui + ## DONE: file created & encrypted via web ui are readable via webdav # WebDAV: @@ -52,8 +53,7 @@ class Util { ## DONE: new data filled files added via webdav get encrypted ## DONE: new data filled files added via webdav are readable via webdav ## DONE: reading unencrypted files when encryption is enabled works via webdav - - # TODO: files created & encrypted via web ui are readable via webdav + ## DONE: files created & encrypted via web ui are readable via webdav # Legacy support: diff --git a/apps/files_encryption/tests/crypt.php b/apps/files_encryption/tests/crypt.php index 09347dd578a..f72f15ca236 100755 --- a/apps/files_encryption/tests/crypt.php +++ b/apps/files_encryption/tests/crypt.php @@ -21,6 +21,10 @@ require_once realpath( dirname(__FILE__).'/../appinfo/app.php' ); use OCA\Encryption; +// This has to go here because otherwise session errors arise, and the private +// encryption key needs to be saved in the session +\OC_User::login( 'admin', 'admin' ); + class Test_Crypt extends \PHPUnit_Framework_TestCase { function setUp() { @@ -41,8 +45,6 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { $this->userId = 'admin'; $this->pass = 'admin'; - - \OC_User::setUserId( $this->userId ); } @@ -434,6 +436,7 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { } + // What is the point of this test? It doesn't use keyEncryptKeyfile() function testKeyEncryptKeyfile() { # TODO: Don't repeat encryption from previous tests, use PHPUnit test interdependency instead @@ -456,6 +459,22 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { $this->assertEquals( $this->dataUrl, $decryptData ); } + + /** + * @brief test functionality of keyEncryptKeyfile() and + * keyDecryptKeyfile() + */ + function testKeyDecryptKeyfile() { + + $encrypted = Encryption\Crypt::keyEncryptKeyfile( $this->dataShort, $this->genPublicKey ); + + $this->assertNotEquals( $encrypted['data'], $this->dataShort ); + + $decrypted = Encryption\Crypt::keyDecryptKeyfile( $encrypted['data'], $encrypted['key'], $this->genPrivateKey ); + + $this->assertEquals( $decrypted, $this->dataShort ); + + } /** @@ -474,17 +493,17 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { } - /** - * @brief test decryption using legacy blowfish method - * @depends testLegacyEncryptShort - */ - function testLegacyDecryptShort( $crypted ) { - - $decrypted = Encryption\Crypt::legacyDecrypt( $crypted, $this->pass ); - - $this->assertEquals( $this->dataShort, $decrypted ); - - } +// /** +// * @brief test decryption using legacy blowfish method +// * @depends testLegacyEncryptShort +// */ +// function testLegacyDecryptShort( $crypted ) { +// +// $decrypted = Encryption\Crypt::legacyDecrypt( $crypted, $this->pass ); +// +// $this->assertEquals( $this->dataShort, $decrypted ); +// +// } /** * @brief test encryption using legacy blowfish method @@ -502,17 +521,17 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { } - /** - * @brief test decryption using legacy blowfish method - * @depends testLegacyEncryptLong - */ - function testLegacyDecryptLong( $crypted ) { - - $decrypted = Encryption\Crypt::legacyDecrypt( $crypted, $this->pass ); - - $this->assertEquals( $this->dataLong, $decrypted ); - - } +// /** +// * @brief test decryption using legacy blowfish method +// * @depends testLegacyEncryptLong +// */ +// function testLegacyDecryptLong( $crypted ) { +// +// $decrypted = Encryption\Crypt::legacyDecrypt( $crypted, $this->pass ); +// +// $this->assertEquals( $this->dataLong, $decrypted ); +// +// } /** * @brief test generation of legacy encryption key diff --git a/apps/files_encryption/tests/proxy.php b/apps/files_encryption/tests/proxy.php index 8b2c92c2f53..87151234e0e 100644 --- a/apps/files_encryption/tests/proxy.php +++ b/apps/files_encryption/tests/proxy.php @@ -45,10 +45,17 @@ class Test_Util extends \PHPUnit_Framework_TestCase { $this->data1 = file_get_contents( realpath( dirname(__FILE__).'/../../../data/admin/files/enc-test.txt' ) ); + \OC_FileProxy::$enabled = false; + $this->Encdata1 = file_get_contents( realpath( dirname(__FILE__).'/../../../data/admin/files/enc-test.txt' ) ); + \OC_FileProxy::$enabled = true; + $this->userId = 'admin'; $this->pass = 'admin'; -$_SESSION['enckey'] = '-----BEGIN PRIVATE KEY----- + $this->session = new Encryption\Session(); + +$this->session->setPrivateKey( +'-----BEGIN PRIVATE KEY----- MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDiH3EA4EpFA7Fx s2dyyfL5jwXeYXrTqQJ6DqKgGn8VsbT3eu8R9KzM2XitVwZe8c8L52DvJ06o5vg0 GqPYxilFdOFJe/ggac5Tq8UmJiZS4EqYEMwxBIfIyWTxeGV06/0HOwnVAkqHMcBz @@ -76,7 +83,9 @@ k1kbgyS7KKB7opVxI5+ChEqyUDijS3Y9FZixrRIWE6i2uGu86UG+v2lbKvSbM4Qm xvbOcX9OVMnlRb7n8woOP10UMY+ZE2x+YEUXQTLtPYq7F66e1OfxltstMxLQA+3d Y1d5piFV8PXK3Fg2F+Cj5qg= -----END PRIVATE KEY----- -'; +' +, $this->userId +); \OC_User::setUserId( $this->userId ); @@ -113,11 +122,11 @@ Y1d5piFV8PXK3Fg2F+Cj5qg= // // $this->oldConfig=OCP\Config::getAppValue('files_encryption','enable_encryption','true'); // OCP\Config::setAppValue('files_encryption','enable_encryption','true'); -// $this->oldKey=isset($_SESSION['enckey'])?$_SESSION['enckey']:null; +// $this->oldKey=isset($_SESSION['privateKey'])?$_SESSION['privateKey']:null; // // // //set testing key -// $_SESSION['enckey']=md5(time()); +// $_SESSION['privateKey']=md5(time()); // // //clear all proxies and hooks so we can do clean testing // OC_FileProxy::clearProxies(); @@ -141,7 +150,7 @@ Y1d5piFV8PXK3Fg2F+Cj5qg= // public function tearDown(){ // OCP\Config::setAppValue('files_encryption','enable_encryption',$this->oldConfig); // if(!is_null($this->oldKey)){ -// $_SESSION['enckey']=$this->oldKey; +// $_SESSION['privateKey']=$this->oldKey; // } // } // -- cgit v1.2.3 From b66d38ecae3a2e7914520a90c5ef01cbc1432c10 Mon Sep 17 00:00:00 2001 From: Sam Tuke Date: Tue, 11 Dec 2012 15:10:39 +0000 Subject: Revert "Development snapshot" This reverts commit c56fb905d1a300b2fe6c011848ea520031ea0df1. --- apps/files_encryption/appinfo/app.php | 9 +---- apps/files_encryption/hooks/hooks.php | 10 ++--- apps/files_encryption/lib/crypt.php | 25 ++++++------ apps/files_encryption/lib/keymanager.php | 20 ++-------- apps/files_encryption/lib/proxy.php | 27 ++++--------- apps/files_encryption/lib/session.php | 66 ------------------------------- apps/files_encryption/lib/stream.php | 54 +++++++------------------ apps/files_encryption/lib/util.php | 4 +- apps/files_encryption/tests/crypt.php | 67 ++++++++++++-------------------- apps/files_encryption/tests/proxy.php | 19 +++------ 10 files changed, 74 insertions(+), 227 deletions(-) delete mode 100644 apps/files_encryption/lib/session.php (limited to 'apps/files_encryption/lib/proxy.php') diff --git a/apps/files_encryption/appinfo/app.php b/apps/files_encryption/appinfo/app.php index 6c082b1938c..45f43d70ff0 100644 --- a/apps/files_encryption/appinfo/app.php +++ b/apps/files_encryption/appinfo/app.php @@ -6,7 +6,6 @@ OC::$CLASSPATH['OCA\Encryption\Util'] = 'apps/files_encryption/lib/util.php'; OC::$CLASSPATH['OCA\Encryption\Keymanager'] = 'apps/files_encryption/lib/keymanager.php'; OC::$CLASSPATH['OCA\Encryption\Stream'] = 'apps/files_encryption/lib/stream.php'; OC::$CLASSPATH['OCA\Encryption\Proxy'] = 'apps/files_encryption/lib/proxy.php'; -OC::$CLASSPATH['OCA\Encryption\Session'] = 'apps/files_encryption/lib/session.php'; OC_FileProxy::register(new OCA\Encryption\Proxy()); @@ -16,13 +15,7 @@ OCP\Util::connectHook('OC_User','post_setPassword','OCA\Encryption\Hooks','setPa stream_wrapper_register( 'crypt', 'OCA\Encryption\Stream'); -$session = new OCA\Encryption\Session(); - -if ( -! $session->getPrivateKey( \OCP\USER::getUser() ) -&& OCP\User::isLoggedIn() -&& OCA\Encryption\Crypt::mode() == 'server' -) { +if( !isset( $_SESSION['enckey'] ) && OCP\User::isLoggedIn() && OCA\Encryption\Crypt::mode() == 'server' ) { // Force the user to re-log in if the encryption key isn't unlocked (happens when a user is logged in before the encryption app is enabled) OCP\User::logout(); diff --git a/apps/files_encryption/hooks/hooks.php b/apps/files_encryption/hooks/hooks.php index 8e391ca3888..5cb59dbbf82 100644 --- a/apps/files_encryption/hooks/hooks.php +++ b/apps/files_encryption/hooks/hooks.php @@ -70,11 +70,11 @@ class Hooks { // trigger_error( "\$params['password'] = {$params['password']}" ); - $privateKey = Crypt::symmetricDecryptFileContent( $encryptedKey, $params['password'] ); + $_SESSION['enckey'] = Crypt::symmetricDecryptFileContent( $encryptedKey, $params['password'] ); - $session = new Session(); - - $session->setPrivateKey( $privateKey, $params['uid'] ); + \OC_FileProxy::$enabled = false; + file_put_contents( '/home/samtuke/enckey', $_SESSION['enckey'] ); + \OC_FileProxy::$enabled = true; $view1 = new \OC_FilesystemView( '/' . $params['uid'] ); @@ -86,7 +86,7 @@ class Hooks { ) { $_SESSION['legacyenckey'] = Crypt::legacyDecrypt( $legacyKey, $params['password'] ); -// trigger_error('leg enc key = '.$_SESSION['legacyenckey']); + trigger_error('leg enc key = '.$_SESSION['legacyenckey']); } // } diff --git a/apps/files_encryption/lib/crypt.php b/apps/files_encryption/lib/crypt.php index 5e1078c9e1b..8df3cd43270 100755 --- a/apps/files_encryption/lib/crypt.php +++ b/apps/files_encryption/lib/crypt.php @@ -305,9 +305,9 @@ class Crypt { if ( $encryptedContent = self::encrypt( $plainContent, $iv, $passphrase ) ) { // Combine content to encrypt with IV identifier and actual IV - $catfile = self::concatIv( $encryptedContent, $iv ); + $combinedKeyfile = self::concatIv( $encryptedContent, $iv ); - $padded = self::addPadding( $catfile ); + $padded = self::addPadding( $combinedKeyfile ); return $padded; @@ -468,8 +468,7 @@ class Crypt { /** * @brief Encrypts content symmetrically and generates keyfile asymmetrically - * @returns array containing catfile and new keyfile. - * keys: data, key + * @returns array keys: encrypted, key * @note this method is a wrapper for combining other crypt class methods */ public static function keyEncryptKeyfile( $plainContent, $publicKey ) { @@ -485,20 +484,18 @@ class Crypt { } /** - * @brief Takes catfile, keyfile, and private key, and + * @brief Takes encrypted data, encrypted catfile, and private key, and * performs decryption * @returns decrypted content * @note this method is a wrapper for combining other crypt class methods */ - public static function keyDecryptKeyfile( $catfile, $keyfile, $privateKey ) { + public static function keyDecryptKeyfile( $encryptedData, $encryptedKey, $privateKey ) { - // Decrypt the keyfile with the user's private key - $decryptedKey = self::keyDecrypt( $keyfile, $privateKey ); + // Decrypt keyfile + $decryptedKey = self::keyDecrypt( $encryptedKey, $privateKey ); -// trigger_error( "\$keyfile = ".var_export($keyfile, 1)); - - // Decrypt the catfile symmetrically using the decrypted keyfile - $decryptedData = self::symmetricDecryptFileContent( $catfile, $decryptedKey ); + // Decrypt encrypted file + $decryptedData = self::symmetricDecryptFileContent( $encryptedData, $decryptedKey ); return $decryptedData; @@ -687,7 +684,7 @@ class Crypt { */ public static function legacyEncrypt( $content, $passphrase = '' ) { - //trigger_error("OC2 enc \$content = $content \$passphrase = ".var_export($passphrase, 1) ); + trigger_error("OC2 enc \$content = $content \$passphrase = ".var_export($passphrase, 1) ); $bf = self::getBlowfish( $passphrase ); @@ -711,7 +708,7 @@ class Crypt { $bf = self::getBlowfish( "67362885833455692562" ); -// trigger_error(var_export($bf, 1) ); + trigger_error(var_export($bf, 1) ); $decrypted = $bf->decrypt( $content ); diff --git a/apps/files_encryption/lib/keymanager.php b/apps/files_encryption/lib/keymanager.php index 55d7530c466..2f730971288 100755 --- a/apps/files_encryption/lib/keymanager.php +++ b/apps/files_encryption/lib/keymanager.php @@ -46,19 +46,11 @@ class Keymanager { * @brief retrieve public key for a specified user * @return string public key or false */ - public static function getPublicKey( $userId = NULL ) { + public static function getPublicKey() { - // If the username wasn't specified, fetch it - if ( ! $userId ) { - - $userId = \OCP\User::getUser(); - - } - - // Create new view with the right + $user = \OCP\User::getUser(); $view = new \OC_FilesystemView( '/public-keys/' ); - - return $view->file_get_contents( '/' . $userId . '.public.key' ); + return $view->file_get_contents( '/' . $user . '.public.key' ); } @@ -127,12 +119,10 @@ class Keymanager { } /** - * @brief retrieve keyfile for an encrypted file + * @brief retrieve file encryption key * * @param string file name * @return string file key or false - * @note The keyfile returned is asymmetrically encrypted. Decryption - * of the keyfile must be performed by client code */ public static function getFileKey( $path, $staticUserClass = 'OCP\User' ) { @@ -251,8 +241,6 @@ class Keymanager { * @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( $path, $key, $view = Null, $dbClassName = '\OC_DB') { diff --git a/apps/files_encryption/lib/proxy.php b/apps/files_encryption/lib/proxy.php index 85664734d7a..914632d3387 100644 --- a/apps/files_encryption/lib/proxy.php +++ b/apps/files_encryption/lib/proxy.php @@ -131,10 +131,6 @@ class Proxy extends \OC_FileProxy { } - /** - * @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 @@ -142,27 +138,24 @@ class Proxy extends \OC_FileProxy { // Disable encryption proxy to prevent recursive calls \OC_FileProxy::$enabled = false; - // If data is a catfile if ( Crypt::mode() == 'server' && Crypt::isEncryptedContent( $data ) ) { -// trigger_error("bong"); + //trigger_error("bong"); - $split = explode( '/', $path ); + $filePath = explode( '/', $path ); - $filePath = array_slice( $split, 3 ); + $filePath = array_slice( $filePath, 3 ); $filePath = '/' . implode( '/', $filePath ); //$cached = \OC_FileCache_Cached::get( $path, '' ); $keyFile = Keymanager::getFileKey( $filePath ); - - $session = new Session(); - - $decrypted = Crypt::keyDecryptKeyfile( $data, $keyFile, $session->getPrivateKey( $split[1] ) ); + $data = Crypt::keyDecryptKeyfile( $data, $keyFile, $_SESSION['enckey'] ); + } elseif ( Crypt::mode() == 'server' && isset( $_SESSION['legacyenckey'] ) @@ -170,20 +163,14 @@ class Proxy extends \OC_FileProxy { ) { trigger_error("mong"); - $decrypted = Crypt::legacyDecrypt( $data, $_SESSION['legacyenckey'] ); + $data = Crypt::legacyDecrypt( $data, $_SESSION['legacyenckey'] ); //trigger_error($data); } \OC_FileProxy::$enabled = true; - if ( ! isset( $decrypted ) ) { - - $decrypted = $data; - - } - - return $decrypted; + return $data; } diff --git a/apps/files_encryption/lib/session.php b/apps/files_encryption/lib/session.php deleted file mode 100644 index 946e5a6eddd..00000000000 --- a/apps/files_encryption/lib/session.php +++ /dev/null @@ -1,66 +0,0 @@ -. - * - */ - -namespace OCA\Encryption; - -/** - * Class for handling encryption related session data - */ - -class Session { - - /** - * @brief Sets user id for session and triggers emit - * @return bool - * - */ - public static function setPrivateKey( $privateKey, $userId ) { - - $_SESSION['privateKey'] = $privateKey; - - return true; - - } - - /** - * @brief Gets user id for session and triggers emit - * @returns string $privateKey The user's plaintext private key - * - */ - public static function getPrivateKey( $userId ) { - - if ( - isset( $_SESSION['privateKey'] ) - && !empty( $_SESSION['privateKey'] ) - ) { - - return $_SESSION['privateKey']; - - } else { - - return false; - - } - - } - -} \ No newline at end of file diff --git a/apps/files_encryption/lib/stream.php b/apps/files_encryption/lib/stream.php index ac5fadd4e03..74dff1531a9 100644 --- a/apps/files_encryption/lib/stream.php +++ b/apps/files_encryption/lib/stream.php @@ -59,9 +59,7 @@ class Stream { private $count; private $writeCache; public $size; - private $publicKey; private $keyfile; - private $encKeyfile; private static $view; public function stream_open( $path, $mode, $options, &$opened_path ) { @@ -248,7 +246,7 @@ class Stream { * @param bool $generate if true, a new key will be generated if none can be found * @return bool true on key found and set, false on key not found and new key generated and set */ - public function getKey() { + public function getKey( $generate = true ) { //echo "\n\$this->rawPath = {$this->rawPath}"; @@ -258,37 +256,23 @@ class Stream { # TODO: add error handling for when file exists but no keyfile // Fetch existing keyfile - $this->encKeyfile = Keymanager::getFileKey( $this->rawPath ); - - $this->getUser(); - - $session = new Session(); - - $this->keyfile = Crypt::keyDecrypt( $this->encKeyfile, $session->getPrivateKey( $this->userId ) ); + $this->keyfile = Keymanager::getFileKey( $this->rawPath ); return true; } else { - return false; - - } - - } - - public function getuser() { - - // Only get the user again if it isn't already set - if ( empty( $this->userId ) ) { - - # TODO: Move this user call out of here - it belongs elsewhere - $this->userId = \OCP\User::getUser(); - + if ( $generate ) { + + // If the data is to be written to a new file, generate a new keyfile + $this->keyfile = Crypt::generateKey(); + + return false; + + } + } - # TODO: Add a method for getting the user in case OCP\User:: - # getUser() doesn't work (can that scenario ever occur?) - } /** @@ -322,23 +306,15 @@ class Stream { //echo "\$pointer = $pointer\n"; - // Make sure the userId is set - $this->getuser(); + # TODO: Move this user call out of here - it belongs elsewhere + $user = \OCP\User::getUser(); // Get / generate the keyfile for the file we're handling // If we're writing a new file (not overwriting an existing one), save the newly generated keyfile if ( ! $this->getKey() ) { - - $this->keyfile = Crypt::generateKey(); - - $this->publicKey = Keymanager::getPublicKey( $this->userId ); - - $this->encKeyfile = Crypt::keyEncrypt( $this->keyfile, $this->publicKey ); - - // Save the new encrypted file key - Keymanager::setFileKey( $this->rawPath, $this->encKeyfile, new \OC_FilesystemView( '/' ) ); - # TODO: move this new OCFSV out of here some how, use DI + // Save keyfile in parallel directory structure + Keymanager::setFileKey( $this->rawPath, $this->keyfile, new \OC_FilesystemView( '/' ) ); } diff --git a/apps/files_encryption/lib/util.php b/apps/files_encryption/lib/util.php index 77f8dffe00f..907a04e5c00 100644 --- a/apps/files_encryption/lib/util.php +++ b/apps/files_encryption/lib/util.php @@ -45,7 +45,6 @@ class Util { ## DONE: files created via web ui are encrypted ## DONE: file created & encrypted via web ui are readable in web ui - ## DONE: file created & encrypted via web ui are readable via webdav # WebDAV: @@ -53,7 +52,8 @@ class Util { ## DONE: new data filled files added via webdav get encrypted ## DONE: new data filled files added via webdav are readable via webdav ## DONE: reading unencrypted files when encryption is enabled works via webdav - ## DONE: files created & encrypted via web ui are readable via webdav + + # TODO: files created & encrypted via web ui are readable via webdav # Legacy support: diff --git a/apps/files_encryption/tests/crypt.php b/apps/files_encryption/tests/crypt.php index f72f15ca236..09347dd578a 100755 --- a/apps/files_encryption/tests/crypt.php +++ b/apps/files_encryption/tests/crypt.php @@ -21,10 +21,6 @@ require_once realpath( dirname(__FILE__).'/../appinfo/app.php' ); use OCA\Encryption; -// This has to go here because otherwise session errors arise, and the private -// encryption key needs to be saved in the session -\OC_User::login( 'admin', 'admin' ); - class Test_Crypt extends \PHPUnit_Framework_TestCase { function setUp() { @@ -45,6 +41,8 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { $this->userId = 'admin'; $this->pass = 'admin'; + + \OC_User::setUserId( $this->userId ); } @@ -436,7 +434,6 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { } - // What is the point of this test? It doesn't use keyEncryptKeyfile() function testKeyEncryptKeyfile() { # TODO: Don't repeat encryption from previous tests, use PHPUnit test interdependency instead @@ -459,22 +456,6 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { $this->assertEquals( $this->dataUrl, $decryptData ); } - - /** - * @brief test functionality of keyEncryptKeyfile() and - * keyDecryptKeyfile() - */ - function testKeyDecryptKeyfile() { - - $encrypted = Encryption\Crypt::keyEncryptKeyfile( $this->dataShort, $this->genPublicKey ); - - $this->assertNotEquals( $encrypted['data'], $this->dataShort ); - - $decrypted = Encryption\Crypt::keyDecryptKeyfile( $encrypted['data'], $encrypted['key'], $this->genPrivateKey ); - - $this->assertEquals( $decrypted, $this->dataShort ); - - } /** @@ -493,17 +474,17 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { } -// /** -// * @brief test decryption using legacy blowfish method -// * @depends testLegacyEncryptShort -// */ -// function testLegacyDecryptShort( $crypted ) { -// -// $decrypted = Encryption\Crypt::legacyDecrypt( $crypted, $this->pass ); -// -// $this->assertEquals( $this->dataShort, $decrypted ); -// -// } + /** + * @brief test decryption using legacy blowfish method + * @depends testLegacyEncryptShort + */ + function testLegacyDecryptShort( $crypted ) { + + $decrypted = Encryption\Crypt::legacyDecrypt( $crypted, $this->pass ); + + $this->assertEquals( $this->dataShort, $decrypted ); + + } /** * @brief test encryption using legacy blowfish method @@ -521,17 +502,17 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { } -// /** -// * @brief test decryption using legacy blowfish method -// * @depends testLegacyEncryptLong -// */ -// function testLegacyDecryptLong( $crypted ) { -// -// $decrypted = Encryption\Crypt::legacyDecrypt( $crypted, $this->pass ); -// -// $this->assertEquals( $this->dataLong, $decrypted ); -// -// } + /** + * @brief test decryption using legacy blowfish method + * @depends testLegacyEncryptLong + */ + function testLegacyDecryptLong( $crypted ) { + + $decrypted = Encryption\Crypt::legacyDecrypt( $crypted, $this->pass ); + + $this->assertEquals( $this->dataLong, $decrypted ); + + } /** * @brief test generation of legacy encryption key diff --git a/apps/files_encryption/tests/proxy.php b/apps/files_encryption/tests/proxy.php index 87151234e0e..8b2c92c2f53 100644 --- a/apps/files_encryption/tests/proxy.php +++ b/apps/files_encryption/tests/proxy.php @@ -45,17 +45,10 @@ class Test_Util extends \PHPUnit_Framework_TestCase { $this->data1 = file_get_contents( realpath( dirname(__FILE__).'/../../../data/admin/files/enc-test.txt' ) ); - \OC_FileProxy::$enabled = false; - $this->Encdata1 = file_get_contents( realpath( dirname(__FILE__).'/../../../data/admin/files/enc-test.txt' ) ); - \OC_FileProxy::$enabled = true; - $this->userId = 'admin'; $this->pass = 'admin'; - $this->session = new Encryption\Session(); - -$this->session->setPrivateKey( -'-----BEGIN PRIVATE KEY----- +$_SESSION['enckey'] = '-----BEGIN PRIVATE KEY----- MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDiH3EA4EpFA7Fx s2dyyfL5jwXeYXrTqQJ6DqKgGn8VsbT3eu8R9KzM2XitVwZe8c8L52DvJ06o5vg0 GqPYxilFdOFJe/ggac5Tq8UmJiZS4EqYEMwxBIfIyWTxeGV06/0HOwnVAkqHMcBz @@ -83,9 +76,7 @@ k1kbgyS7KKB7opVxI5+ChEqyUDijS3Y9FZixrRIWE6i2uGu86UG+v2lbKvSbM4Qm xvbOcX9OVMnlRb7n8woOP10UMY+ZE2x+YEUXQTLtPYq7F66e1OfxltstMxLQA+3d Y1d5piFV8PXK3Fg2F+Cj5qg= -----END PRIVATE KEY----- -' -, $this->userId -); +'; \OC_User::setUserId( $this->userId ); @@ -122,11 +113,11 @@ Y1d5piFV8PXK3Fg2F+Cj5qg= // // $this->oldConfig=OCP\Config::getAppValue('files_encryption','enable_encryption','true'); // OCP\Config::setAppValue('files_encryption','enable_encryption','true'); -// $this->oldKey=isset($_SESSION['privateKey'])?$_SESSION['privateKey']:null; +// $this->oldKey=isset($_SESSION['enckey'])?$_SESSION['enckey']:null; // // // //set testing key -// $_SESSION['privateKey']=md5(time()); +// $_SESSION['enckey']=md5(time()); // // //clear all proxies and hooks so we can do clean testing // OC_FileProxy::clearProxies(); @@ -150,7 +141,7 @@ Y1d5piFV8PXK3Fg2F+Cj5qg= // public function tearDown(){ // OCP\Config::setAppValue('files_encryption','enable_encryption',$this->oldConfig); // if(!is_null($this->oldKey)){ -// $_SESSION['privateKey']=$this->oldKey; +// $_SESSION['enckey']=$this->oldKey; // } // } // -- cgit v1.2.3 From a00dd2d5d6ba908e230af4b555ed0bc902cafd15 Mon Sep 17 00:00:00 2001 From: Sam Tuke Date: Tue, 11 Dec 2012 15:10:56 +0000 Subject: Revert "Revert "Development snapshot"" This reverts commit b66d38ecae3a2e7914520a90c5ef01cbc1432c10. --- apps/files_encryption/appinfo/app.php | 9 ++++- apps/files_encryption/hooks/hooks.php | 10 ++--- apps/files_encryption/lib/crypt.php | 25 ++++++------ apps/files_encryption/lib/keymanager.php | 20 ++++++++-- apps/files_encryption/lib/proxy.php | 27 +++++++++---- apps/files_encryption/lib/session.php | 66 +++++++++++++++++++++++++++++++ apps/files_encryption/lib/stream.php | 54 ++++++++++++++++++------- apps/files_encryption/lib/util.php | 4 +- apps/files_encryption/tests/crypt.php | 67 ++++++++++++++++++++------------ apps/files_encryption/tests/proxy.php | 19 ++++++--- 10 files changed, 227 insertions(+), 74 deletions(-) create mode 100644 apps/files_encryption/lib/session.php (limited to 'apps/files_encryption/lib/proxy.php') diff --git a/apps/files_encryption/appinfo/app.php b/apps/files_encryption/appinfo/app.php index 45f43d70ff0..6c082b1938c 100644 --- a/apps/files_encryption/appinfo/app.php +++ b/apps/files_encryption/appinfo/app.php @@ -6,6 +6,7 @@ OC::$CLASSPATH['OCA\Encryption\Util'] = 'apps/files_encryption/lib/util.php'; OC::$CLASSPATH['OCA\Encryption\Keymanager'] = 'apps/files_encryption/lib/keymanager.php'; OC::$CLASSPATH['OCA\Encryption\Stream'] = 'apps/files_encryption/lib/stream.php'; OC::$CLASSPATH['OCA\Encryption\Proxy'] = 'apps/files_encryption/lib/proxy.php'; +OC::$CLASSPATH['OCA\Encryption\Session'] = 'apps/files_encryption/lib/session.php'; OC_FileProxy::register(new OCA\Encryption\Proxy()); @@ -15,7 +16,13 @@ OCP\Util::connectHook('OC_User','post_setPassword','OCA\Encryption\Hooks','setPa stream_wrapper_register( 'crypt', 'OCA\Encryption\Stream'); -if( !isset( $_SESSION['enckey'] ) && OCP\User::isLoggedIn() && OCA\Encryption\Crypt::mode() == 'server' ) { +$session = new OCA\Encryption\Session(); + +if ( +! $session->getPrivateKey( \OCP\USER::getUser() ) +&& OCP\User::isLoggedIn() +&& OCA\Encryption\Crypt::mode() == 'server' +) { // Force the user to re-log in if the encryption key isn't unlocked (happens when a user is logged in before the encryption app is enabled) OCP\User::logout(); diff --git a/apps/files_encryption/hooks/hooks.php b/apps/files_encryption/hooks/hooks.php index 5cb59dbbf82..8e391ca3888 100644 --- a/apps/files_encryption/hooks/hooks.php +++ b/apps/files_encryption/hooks/hooks.php @@ -70,11 +70,11 @@ class Hooks { // trigger_error( "\$params['password'] = {$params['password']}" ); - $_SESSION['enckey'] = Crypt::symmetricDecryptFileContent( $encryptedKey, $params['password'] ); + $privateKey = Crypt::symmetricDecryptFileContent( $encryptedKey, $params['password'] ); - \OC_FileProxy::$enabled = false; - file_put_contents( '/home/samtuke/enckey', $_SESSION['enckey'] ); - \OC_FileProxy::$enabled = true; + $session = new Session(); + + $session->setPrivateKey( $privateKey, $params['uid'] ); $view1 = new \OC_FilesystemView( '/' . $params['uid'] ); @@ -86,7 +86,7 @@ class Hooks { ) { $_SESSION['legacyenckey'] = Crypt::legacyDecrypt( $legacyKey, $params['password'] ); - trigger_error('leg enc key = '.$_SESSION['legacyenckey']); +// trigger_error('leg enc key = '.$_SESSION['legacyenckey']); } // } diff --git a/apps/files_encryption/lib/crypt.php b/apps/files_encryption/lib/crypt.php index 8df3cd43270..5e1078c9e1b 100755 --- a/apps/files_encryption/lib/crypt.php +++ b/apps/files_encryption/lib/crypt.php @@ -305,9 +305,9 @@ class Crypt { if ( $encryptedContent = self::encrypt( $plainContent, $iv, $passphrase ) ) { // Combine content to encrypt with IV identifier and actual IV - $combinedKeyfile = self::concatIv( $encryptedContent, $iv ); + $catfile = self::concatIv( $encryptedContent, $iv ); - $padded = self::addPadding( $combinedKeyfile ); + $padded = self::addPadding( $catfile ); return $padded; @@ -468,7 +468,8 @@ class Crypt { /** * @brief Encrypts content symmetrically and generates keyfile asymmetrically - * @returns array keys: encrypted, key + * @returns array containing catfile and new keyfile. + * keys: data, key * @note this method is a wrapper for combining other crypt class methods */ public static function keyEncryptKeyfile( $plainContent, $publicKey ) { @@ -484,18 +485,20 @@ class Crypt { } /** - * @brief Takes encrypted data, encrypted catfile, and private key, and + * @brief Takes catfile, keyfile, and private key, and * performs decryption * @returns decrypted content * @note this method is a wrapper for combining other crypt class methods */ - public static function keyDecryptKeyfile( $encryptedData, $encryptedKey, $privateKey ) { + public static function keyDecryptKeyfile( $catfile, $keyfile, $privateKey ) { - // Decrypt keyfile - $decryptedKey = self::keyDecrypt( $encryptedKey, $privateKey ); + // Decrypt the keyfile with the user's private key + $decryptedKey = self::keyDecrypt( $keyfile, $privateKey ); - // Decrypt encrypted file - $decryptedData = self::symmetricDecryptFileContent( $encryptedData, $decryptedKey ); +// trigger_error( "\$keyfile = ".var_export($keyfile, 1)); + + // Decrypt the catfile symmetrically using the decrypted keyfile + $decryptedData = self::symmetricDecryptFileContent( $catfile, $decryptedKey ); return $decryptedData; @@ -684,7 +687,7 @@ class Crypt { */ public static function legacyEncrypt( $content, $passphrase = '' ) { - trigger_error("OC2 enc \$content = $content \$passphrase = ".var_export($passphrase, 1) ); + //trigger_error("OC2 enc \$content = $content \$passphrase = ".var_export($passphrase, 1) ); $bf = self::getBlowfish( $passphrase ); @@ -708,7 +711,7 @@ class Crypt { $bf = self::getBlowfish( "67362885833455692562" ); - trigger_error(var_export($bf, 1) ); +// trigger_error(var_export($bf, 1) ); $decrypted = $bf->decrypt( $content ); diff --git a/apps/files_encryption/lib/keymanager.php b/apps/files_encryption/lib/keymanager.php index 2f730971288..55d7530c466 100755 --- a/apps/files_encryption/lib/keymanager.php +++ b/apps/files_encryption/lib/keymanager.php @@ -46,11 +46,19 @@ class Keymanager { * @brief retrieve public key for a specified user * @return string public key or false */ - public static function getPublicKey() { + public static function getPublicKey( $userId = NULL ) { - $user = \OCP\User::getUser(); + // If the username wasn't specified, fetch it + if ( ! $userId ) { + + $userId = \OCP\User::getUser(); + + } + + // Create new view with the right $view = new \OC_FilesystemView( '/public-keys/' ); - return $view->file_get_contents( '/' . $user . '.public.key' ); + + return $view->file_get_contents( '/' . $userId . '.public.key' ); } @@ -119,10 +127,12 @@ class Keymanager { } /** - * @brief retrieve file encryption key + * @brief retrieve keyfile for an encrypted file * * @param string file name * @return string file key or false + * @note The keyfile returned is asymmetrically encrypted. Decryption + * of the keyfile must be performed by client code */ public static function getFileKey( $path, $staticUserClass = 'OCP\User' ) { @@ -241,6 +251,8 @@ class Keymanager { * @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( $path, $key, $view = Null, $dbClassName = '\OC_DB') { diff --git a/apps/files_encryption/lib/proxy.php b/apps/files_encryption/lib/proxy.php index 914632d3387..85664734d7a 100644 --- a/apps/files_encryption/lib/proxy.php +++ b/apps/files_encryption/lib/proxy.php @@ -131,6 +131,10 @@ class Proxy extends \OC_FileProxy { } + /** + * @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 @@ -138,24 +142,27 @@ class Proxy extends \OC_FileProxy { // Disable encryption proxy to prevent recursive calls \OC_FileProxy::$enabled = false; + // If data is a catfile if ( Crypt::mode() == 'server' && Crypt::isEncryptedContent( $data ) ) { - //trigger_error("bong"); +// trigger_error("bong"); - $filePath = explode( '/', $path ); + $split = explode( '/', $path ); - $filePath = array_slice( $filePath, 3 ); + $filePath = array_slice( $split, 3 ); $filePath = '/' . implode( '/', $filePath ); //$cached = \OC_FileCache_Cached::get( $path, '' ); $keyFile = Keymanager::getFileKey( $filePath ); + + $session = new Session(); + + $decrypted = Crypt::keyDecryptKeyfile( $data, $keyFile, $session->getPrivateKey( $split[1] ) ); - $data = Crypt::keyDecryptKeyfile( $data, $keyFile, $_SESSION['enckey'] ); - } elseif ( Crypt::mode() == 'server' && isset( $_SESSION['legacyenckey'] ) @@ -163,14 +170,20 @@ class Proxy extends \OC_FileProxy { ) { trigger_error("mong"); - $data = Crypt::legacyDecrypt( $data, $_SESSION['legacyenckey'] ); + $decrypted = Crypt::legacyDecrypt( $data, $_SESSION['legacyenckey'] ); //trigger_error($data); } \OC_FileProxy::$enabled = true; - return $data; + if ( ! isset( $decrypted ) ) { + + $decrypted = $data; + + } + + return $decrypted; } diff --git a/apps/files_encryption/lib/session.php b/apps/files_encryption/lib/session.php new file mode 100644 index 00000000000..946e5a6eddd --- /dev/null +++ b/apps/files_encryption/lib/session.php @@ -0,0 +1,66 @@ +. + * + */ + +namespace OCA\Encryption; + +/** + * Class for handling encryption related session data + */ + +class Session { + + /** + * @brief Sets user id for session and triggers emit + * @return bool + * + */ + public static function setPrivateKey( $privateKey, $userId ) { + + $_SESSION['privateKey'] = $privateKey; + + return true; + + } + + /** + * @brief Gets user id for session and triggers emit + * @returns string $privateKey The user's plaintext private key + * + */ + public static function getPrivateKey( $userId ) { + + if ( + isset( $_SESSION['privateKey'] ) + && !empty( $_SESSION['privateKey'] ) + ) { + + return $_SESSION['privateKey']; + + } else { + + return false; + + } + + } + +} \ No newline at end of file diff --git a/apps/files_encryption/lib/stream.php b/apps/files_encryption/lib/stream.php index 74dff1531a9..ac5fadd4e03 100644 --- a/apps/files_encryption/lib/stream.php +++ b/apps/files_encryption/lib/stream.php @@ -59,7 +59,9 @@ class Stream { private $count; private $writeCache; public $size; + private $publicKey; private $keyfile; + private $encKeyfile; private static $view; public function stream_open( $path, $mode, $options, &$opened_path ) { @@ -246,7 +248,7 @@ class Stream { * @param bool $generate if true, a new key will be generated if none can be found * @return bool true on key found and set, false on key not found and new key generated and set */ - public function getKey( $generate = true ) { + public function getKey() { //echo "\n\$this->rawPath = {$this->rawPath}"; @@ -256,23 +258,37 @@ class Stream { # TODO: add error handling for when file exists but no keyfile // Fetch existing keyfile - $this->keyfile = Keymanager::getFileKey( $this->rawPath ); + $this->encKeyfile = Keymanager::getFileKey( $this->rawPath ); + + $this->getUser(); + + $session = new Session(); + + $this->keyfile = Crypt::keyDecrypt( $this->encKeyfile, $session->getPrivateKey( $this->userId ) ); return true; } else { - if ( $generate ) { - - // If the data is to be written to a new file, generate a new keyfile - $this->keyfile = Crypt::generateKey(); - - return false; - - } - + return false; + + } + + } + + public function getuser() { + + // Only get the user again if it isn't already set + if ( empty( $this->userId ) ) { + + # TODO: Move this user call out of here - it belongs elsewhere + $this->userId = \OCP\User::getUser(); + } + # TODO: Add a method for getting the user in case OCP\User:: + # getUser() doesn't work (can that scenario ever occur?) + } /** @@ -306,15 +322,23 @@ class Stream { //echo "\$pointer = $pointer\n"; - # TODO: Move this user call out of here - it belongs elsewhere - $user = \OCP\User::getUser(); + // Make sure the userId is set + $this->getuser(); // Get / generate the keyfile for the file we're handling // If we're writing a new file (not overwriting an existing one), save the newly generated keyfile if ( ! $this->getKey() ) { + + $this->keyfile = Crypt::generateKey(); + + $this->publicKey = Keymanager::getPublicKey( $this->userId ); + + $this->encKeyfile = Crypt::keyEncrypt( $this->keyfile, $this->publicKey ); + + // Save the new encrypted file key + Keymanager::setFileKey( $this->rawPath, $this->encKeyfile, new \OC_FilesystemView( '/' ) ); - // Save keyfile in parallel directory structure - Keymanager::setFileKey( $this->rawPath, $this->keyfile, new \OC_FilesystemView( '/' ) ); + # TODO: move this new OCFSV out of here some how, use DI } diff --git a/apps/files_encryption/lib/util.php b/apps/files_encryption/lib/util.php index 907a04e5c00..77f8dffe00f 100644 --- a/apps/files_encryption/lib/util.php +++ b/apps/files_encryption/lib/util.php @@ -45,6 +45,7 @@ class Util { ## DONE: files created via web ui are encrypted ## DONE: file created & encrypted via web ui are readable in web ui + ## DONE: file created & encrypted via web ui are readable via webdav # WebDAV: @@ -52,8 +53,7 @@ class Util { ## DONE: new data filled files added via webdav get encrypted ## DONE: new data filled files added via webdav are readable via webdav ## DONE: reading unencrypted files when encryption is enabled works via webdav - - # TODO: files created & encrypted via web ui are readable via webdav + ## DONE: files created & encrypted via web ui are readable via webdav # Legacy support: diff --git a/apps/files_encryption/tests/crypt.php b/apps/files_encryption/tests/crypt.php index 09347dd578a..f72f15ca236 100755 --- a/apps/files_encryption/tests/crypt.php +++ b/apps/files_encryption/tests/crypt.php @@ -21,6 +21,10 @@ require_once realpath( dirname(__FILE__).'/../appinfo/app.php' ); use OCA\Encryption; +// This has to go here because otherwise session errors arise, and the private +// encryption key needs to be saved in the session +\OC_User::login( 'admin', 'admin' ); + class Test_Crypt extends \PHPUnit_Framework_TestCase { function setUp() { @@ -41,8 +45,6 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { $this->userId = 'admin'; $this->pass = 'admin'; - - \OC_User::setUserId( $this->userId ); } @@ -434,6 +436,7 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { } + // What is the point of this test? It doesn't use keyEncryptKeyfile() function testKeyEncryptKeyfile() { # TODO: Don't repeat encryption from previous tests, use PHPUnit test interdependency instead @@ -456,6 +459,22 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { $this->assertEquals( $this->dataUrl, $decryptData ); } + + /** + * @brief test functionality of keyEncryptKeyfile() and + * keyDecryptKeyfile() + */ + function testKeyDecryptKeyfile() { + + $encrypted = Encryption\Crypt::keyEncryptKeyfile( $this->dataShort, $this->genPublicKey ); + + $this->assertNotEquals( $encrypted['data'], $this->dataShort ); + + $decrypted = Encryption\Crypt::keyDecryptKeyfile( $encrypted['data'], $encrypted['key'], $this->genPrivateKey ); + + $this->assertEquals( $decrypted, $this->dataShort ); + + } /** @@ -474,17 +493,17 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { } - /** - * @brief test decryption using legacy blowfish method - * @depends testLegacyEncryptShort - */ - function testLegacyDecryptShort( $crypted ) { - - $decrypted = Encryption\Crypt::legacyDecrypt( $crypted, $this->pass ); - - $this->assertEquals( $this->dataShort, $decrypted ); - - } +// /** +// * @brief test decryption using legacy blowfish method +// * @depends testLegacyEncryptShort +// */ +// function testLegacyDecryptShort( $crypted ) { +// +// $decrypted = Encryption\Crypt::legacyDecrypt( $crypted, $this->pass ); +// +// $this->assertEquals( $this->dataShort, $decrypted ); +// +// } /** * @brief test encryption using legacy blowfish method @@ -502,17 +521,17 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { } - /** - * @brief test decryption using legacy blowfish method - * @depends testLegacyEncryptLong - */ - function testLegacyDecryptLong( $crypted ) { - - $decrypted = Encryption\Crypt::legacyDecrypt( $crypted, $this->pass ); - - $this->assertEquals( $this->dataLong, $decrypted ); - - } +// /** +// * @brief test decryption using legacy blowfish method +// * @depends testLegacyEncryptLong +// */ +// function testLegacyDecryptLong( $crypted ) { +// +// $decrypted = Encryption\Crypt::legacyDecrypt( $crypted, $this->pass ); +// +// $this->assertEquals( $this->dataLong, $decrypted ); +// +// } /** * @brief test generation of legacy encryption key diff --git a/apps/files_encryption/tests/proxy.php b/apps/files_encryption/tests/proxy.php index 8b2c92c2f53..87151234e0e 100644 --- a/apps/files_encryption/tests/proxy.php +++ b/apps/files_encryption/tests/proxy.php @@ -45,10 +45,17 @@ class Test_Util extends \PHPUnit_Framework_TestCase { $this->data1 = file_get_contents( realpath( dirname(__FILE__).'/../../../data/admin/files/enc-test.txt' ) ); + \OC_FileProxy::$enabled = false; + $this->Encdata1 = file_get_contents( realpath( dirname(__FILE__).'/../../../data/admin/files/enc-test.txt' ) ); + \OC_FileProxy::$enabled = true; + $this->userId = 'admin'; $this->pass = 'admin'; -$_SESSION['enckey'] = '-----BEGIN PRIVATE KEY----- + $this->session = new Encryption\Session(); + +$this->session->setPrivateKey( +'-----BEGIN PRIVATE KEY----- MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDiH3EA4EpFA7Fx s2dyyfL5jwXeYXrTqQJ6DqKgGn8VsbT3eu8R9KzM2XitVwZe8c8L52DvJ06o5vg0 GqPYxilFdOFJe/ggac5Tq8UmJiZS4EqYEMwxBIfIyWTxeGV06/0HOwnVAkqHMcBz @@ -76,7 +83,9 @@ k1kbgyS7KKB7opVxI5+ChEqyUDijS3Y9FZixrRIWE6i2uGu86UG+v2lbKvSbM4Qm xvbOcX9OVMnlRb7n8woOP10UMY+ZE2x+YEUXQTLtPYq7F66e1OfxltstMxLQA+3d Y1d5piFV8PXK3Fg2F+Cj5qg= -----END PRIVATE KEY----- -'; +' +, $this->userId +); \OC_User::setUserId( $this->userId ); @@ -113,11 +122,11 @@ Y1d5piFV8PXK3Fg2F+Cj5qg= // // $this->oldConfig=OCP\Config::getAppValue('files_encryption','enable_encryption','true'); // OCP\Config::setAppValue('files_encryption','enable_encryption','true'); -// $this->oldKey=isset($_SESSION['enckey'])?$_SESSION['enckey']:null; +// $this->oldKey=isset($_SESSION['privateKey'])?$_SESSION['privateKey']:null; // // // //set testing key -// $_SESSION['enckey']=md5(time()); +// $_SESSION['privateKey']=md5(time()); // // //clear all proxies and hooks so we can do clean testing // OC_FileProxy::clearProxies(); @@ -141,7 +150,7 @@ Y1d5piFV8PXK3Fg2F+Cj5qg= // public function tearDown(){ // OCP\Config::setAppValue('files_encryption','enable_encryption',$this->oldConfig); // if(!is_null($this->oldKey)){ -// $_SESSION['enckey']=$this->oldKey; +// $_SESSION['privateKey']=$this->oldKey; // } // } // -- cgit v1.2.3 From 453fd66c70e0070d207be0c686baeac92be14334 Mon Sep 17 00:00:00 2001 From: Sam Tuke Date: Tue, 11 Dec 2012 17:12:46 +0000 Subject: Changing user login pwd now correctly changes encryption key passphrase All crypt unit tests are now passing --- apps/files_encryption/lib/crypt.php | 19 +--- apps/files_encryption/lib/proxy.php | 4 +- apps/files_encryption/lib/stream.php | 4 - apps/files_encryption/lib/util.php | 8 ++ apps/files_encryption/tests/crypt.php | 204 +++++++++++++++++++--------------- 5 files changed, 128 insertions(+), 111 deletions(-) (limited to 'apps/files_encryption/lib/proxy.php') diff --git a/apps/files_encryption/lib/crypt.php b/apps/files_encryption/lib/crypt.php index 5e1078c9e1b..06a34c8f4d6 100755 --- a/apps/files_encryption/lib/crypt.php +++ b/apps/files_encryption/lib/crypt.php @@ -342,15 +342,10 @@ class Crypt { // Remove padding $noPadding = self::removePadding( $keyfileContent ); - // Fetch IV from end of file - $iv = substr( $noPadding, -16 ); - - // Remove IV and IV identifier text to expose encrypted content - $encryptedContent = substr( $noPadding, 0, -22 ); + // Split into enc data and catfile + $catfile = self::splitIv( $noPadding ); - //trigger_error( "\n\n\$noPadding = ".var_export($noPadding)."\n\n\$iv = ".var_export($iv )."\n\n\$encryptedContent = ".var_export($encryptedContent) ); - - if ( $plainContent = self::decrypt( $encryptedContent, $iv, $passphrase ) ) { + if ( $plainContent = self::decrypt( $catfile['encrypted'], $catfile['iv'], $passphrase ) ) { return $plainContent; @@ -493,12 +488,12 @@ class Crypt { public static function keyDecryptKeyfile( $catfile, $keyfile, $privateKey ) { // Decrypt the keyfile with the user's private key - $decryptedKey = self::keyDecrypt( $keyfile, $privateKey ); + $decryptedKeyfile = self::keyDecrypt( $keyfile, $privateKey ); // trigger_error( "\$keyfile = ".var_export($keyfile, 1)); // Decrypt the catfile symmetrically using the decrypted keyfile - $decryptedData = self::symmetricDecryptFileContent( $catfile, $decryptedKey ); + $decryptedData = self::symmetricDecryptFileContent( $catfile, $decryptedKeyfile ); return $decryptedData; @@ -704,12 +699,10 @@ class Crypt { * This function decrypts an content */ public static function legacyDecrypt( $content, $passphrase = '' ) { - - $passphrase = ''; //trigger_error("OC2 dec \$content = $content \$key = ".strlen($passphrase) ); - $bf = self::getBlowfish( "67362885833455692562" ); + $bf = self::getBlowfish( $passphrase ); // trigger_error(var_export($bf, 1) ); diff --git a/apps/files_encryption/lib/proxy.php b/apps/files_encryption/lib/proxy.php index 85664734d7a..08e708f879b 100644 --- a/apps/files_encryption/lib/proxy.php +++ b/apps/files_encryption/lib/proxy.php @@ -157,11 +157,11 @@ class Proxy extends \OC_FileProxy { //$cached = \OC_FileCache_Cached::get( $path, '' ); - $keyFile = Keymanager::getFileKey( $filePath ); + $encryptedKeyfile = Keymanager::getFileKey( $filePath ); $session = new Session(); - $decrypted = Crypt::keyDecryptKeyfile( $data, $keyFile, $session->getPrivateKey( $split[1] ) ); + $decrypted = Crypt::keyDecryptKeyfile( $data, $encryptedKeyfile, $session->getPrivateKey( $split[1] ) ); } elseif ( Crypt::mode() == 'server' diff --git a/apps/files_encryption/lib/stream.php b/apps/files_encryption/lib/stream.php index ac5fadd4e03..42b9233f7bb 100644 --- a/apps/files_encryption/lib/stream.php +++ b/apps/files_encryption/lib/stream.php @@ -302,10 +302,6 @@ class Stream { */ public function stream_write( $data ) { - - -// file_put_contents('/home/samtuke/newtmp.txt', 'stream_write('.$data.')' ); - // Disable the file proxies so that encryption is not automatically attempted when the file is written to disk - we are handling that separately here and we don't want to get into an infinite loop \OC_FileProxy::$enabled = false; diff --git a/apps/files_encryption/lib/util.php b/apps/files_encryption/lib/util.php index 77f8dffe00f..bd8d18140ae 100644 --- a/apps/files_encryption/lib/util.php +++ b/apps/files_encryption/lib/util.php @@ -71,12 +71,20 @@ class Util { # Admin UI: + ## DONE: changing user password also changes encryption passphrase + ## TODO: add support for optional recovery in case of lost passphrase / keys ## TODO: add admin optional required long passphrase for users ## TODO: add UI buttons for encrypt / decrypt everything ## TODO: implement flag system to allow user to specify encryption by folder, subfolder, etc. + # Sharing: + + ## TODO: add support for encrypting to multiple public keys + ## TODO: add support for decrypting to multiple private keys + + # Integration testing: ## TODO: test new encryption with webdav diff --git a/apps/files_encryption/tests/crypt.php b/apps/files_encryption/tests/crypt.php index f72f15ca236..24c6cff2722 100755 --- a/apps/files_encryption/tests/crypt.php +++ b/apps/files_encryption/tests/crypt.php @@ -45,7 +45,7 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { $this->userId = 'admin'; $this->pass = 'admin'; - + } function tearDown(){} @@ -64,8 +64,6 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { $iv = Encryption\Crypt::generateIv(); - echo $iv; - $this->assertEquals( 16, strlen( $iv ) ); return $iv; @@ -223,84 +221,106 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { // Get file contents without using any wrapper to get it's actual contents on disk $retreivedCryptedFile = $this->view->file_get_contents( $this->userId . '/files/' . $filename ); - //echo "\n\n\$retreivedCryptedFile = $retreivedCryptedFile"; // Check that the file was encrypted before being written to disk $this->assertNotEquals( $this->dataShort, $retreivedCryptedFile ); + // Get private key + $encryptedPrivateKey = Encryption\Keymanager::getPrivateKey( $this->userId, $this->view ); + + $decryptedPrivateKey = Encryption\Crypt::symmetricDecryptFileContent( $encryptedPrivateKey, $this->pass ); + + + // Get keyfile + $encryptedKeyfile = Encryption\Keymanager::getFileKey( $filename ); - $key = Encryption\Keymanager::getFileKey( $filename ); + $decryptedKeyfile = Encryption\Crypt::keyDecrypt( $encryptedKeyfile, $decryptedPrivateKey ); - $manualDecrypt = Encryption\Crypt::symmetricBlockDecryptFileContent( $retreivedCryptedFile, $key ); + // Manually decrypt + $manualDecrypt = Encryption\Crypt::symmetricBlockDecryptFileContent( $retreivedCryptedFile, $decryptedKeyfile ); + + // Check that decrypted data matches $this->assertEquals( $this->dataShort, $manualDecrypt ); } -// /** -// * @brief Test that data that is written by the crypto stream wrapper -// * @note Encrypted data is manually prepared and decrypted here to avoid dependency on success of stream_read -// */ -// function testSymmetricStreamEncryptLongFileContent() { -// -// // Generate a a random filename -// $filename = 'tmp-'.time(); -// -// echo "\n\n\$filename = $filename\n\n"; -// -// // Save long data as encrypted file using stream wrapper -// $cryptedFile = file_put_contents( 'crypt://' . $filename, $this->dataLong.$this->dataLong ); -// -// // Test that data was successfully written -// $this->assertTrue( is_int( $cryptedFile ) ); -// -// // Get file contents without using any wrapper to get it's actual contents on disk -// $retreivedCryptedFile = $this->view->file_get_contents( $this->userId . '/files/' . $filename ); -// -// // echo "\n\n\$retreivedCryptedFile = $retreivedCryptedFile\n\n"; -// -// // Check that the file was encrypted before being written to disk -// $this->assertNotEquals( $this->dataLong.$this->dataLong, $retreivedCryptedFile ); -// -// // Manuallly split saved file into separate IVs and encrypted chunks -// $r = preg_split('/(00iv00.{16,18})/', $retreivedCryptedFile, NULL, PREG_SPLIT_DELIM_CAPTURE); -// -// //print_r($r); -// -// // Join IVs and their respective data chunks -// $e = array( $r[0].$r[1], $r[2].$r[3], $r[4].$r[5], $r[6].$r[7], $r[8].$r[9], $r[10].$r[11] );//.$r[11], $r[12].$r[13], $r[14] ); -// -// //print_r($e); -// -// // Manually fetch keyfile -// $keyfile = Encryption\Keymanager::getFileKey( $filename ); -// -// // Set var for reassembling decrypted content -// $decrypt = ''; -// -// // Manually decrypt chunk -// foreach ($e as $e) { -// -// // echo "\n\$encryptMe = $f"; -// -// $chunkDecrypt = Encryption\Crypt::symmetricDecryptFileContent( $e, $keyfile ); -// -// // Assemble decrypted chunks -// $decrypt .= $chunkDecrypt; -// -// //echo "\n\$chunkDecrypt = $chunkDecrypt"; -// -// } -// -// $this->assertEquals( $this->dataLong.$this->dataLong, $decrypt ); -// -// // Teardown -// -// $this->view->unlink( $filename ); -// -// Encryption\Keymanager::deleteFileKey( $filename ); -// -// } + /** + * @brief Test that data that is written by the crypto stream wrapper + * @note Encrypted data is manually prepared and decrypted here to avoid dependency on success of stream_read + * @note If this test fails with truncate content, check that enough array slices are being rejoined to form $e, as the crypt.php file may have gotten longer and broken the manual + * reassembly of its data + */ + function testSymmetricStreamEncryptLongFileContent() { + + // Generate a a random filename + $filename = 'tmp-'.time(); + + // Save long data as encrypted file using stream wrapper + $cryptedFile = file_put_contents( 'crypt://' . $filename, $this->dataLong.$this->dataLong ); + + // Test that data was successfully written + $this->assertTrue( is_int( $cryptedFile ) ); + + // Get file contents without using any wrapper to get it's actual contents on disk + $retreivedCryptedFile = $this->view->file_get_contents( $this->userId . '/files/' . $filename ); + +// echo "\n\n\$retreivedCryptedFile = $retreivedCryptedFile\n\n"; + + // Check that the file was encrypted before being written to disk + $this->assertNotEquals( $this->dataLong.$this->dataLong, $retreivedCryptedFile ); + + // Manuallly split saved file into separate IVs and encrypted chunks + $r = preg_split('/(00iv00.{16,18})/', $retreivedCryptedFile, NULL, PREG_SPLIT_DELIM_CAPTURE); + + //print_r($r); + + // Join IVs and their respective data chunks + $e = array( $r[0].$r[1], $r[2].$r[3], $r[4].$r[5], $r[6].$r[7], $r[8].$r[9], $r[10].$r[11], $r[12].$r[13] );//.$r[11], $r[12].$r[13], $r[14] ); + + //print_r($e); + + + // Get private key + $encryptedPrivateKey = Encryption\Keymanager::getPrivateKey( $this->userId, $this->view ); + + $decryptedPrivateKey = Encryption\Crypt::symmetricDecryptFileContent( $encryptedPrivateKey, $this->pass ); + + + // Get keyfile + $encryptedKeyfile = Encryption\Keymanager::getFileKey( $filename ); + + $decryptedKeyfile = Encryption\Crypt::keyDecrypt( $encryptedKeyfile, $decryptedPrivateKey ); + + + // Set var for reassembling decrypted content + $decrypt = ''; + + // Manually decrypt chunk + foreach ($e as $e) { + +// echo "\n\$e = $e"; + + $chunkDecrypt = Encryption\Crypt::symmetricDecryptFileContent( $e, $decryptedKeyfile ); + + // Assemble decrypted chunks + $decrypt .= $chunkDecrypt; + +// echo "\n\$chunkDecrypt = $chunkDecrypt"; + + } + +// echo "\n\$decrypt = $decrypt"; + + $this->assertEquals( $this->dataLong.$this->dataLong, $decrypt ); + + // Teardown + + $this->view->unlink( $filename ); + + Encryption\Keymanager::deleteFileKey( $filename ); + + } /** * @brief Test that data that is read by the crypto stream wrapper @@ -493,17 +513,17 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { } -// /** -// * @brief test decryption using legacy blowfish method -// * @depends testLegacyEncryptShort -// */ -// function testLegacyDecryptShort( $crypted ) { -// -// $decrypted = Encryption\Crypt::legacyDecrypt( $crypted, $this->pass ); -// -// $this->assertEquals( $this->dataShort, $decrypted ); -// -// } + /** + * @brief test decryption using legacy blowfish method + * @depends testLegacyEncryptShort + */ + function testLegacyDecryptShort( $crypted ) { + + $decrypted = Encryption\Crypt::legacyDecrypt( $crypted, $this->pass ); + + $this->assertEquals( $this->dataShort, $decrypted ); + + } /** * @brief test encryption using legacy blowfish method @@ -521,17 +541,17 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { } -// /** -// * @brief test decryption using legacy blowfish method -// * @depends testLegacyEncryptLong -// */ -// function testLegacyDecryptLong( $crypted ) { -// -// $decrypted = Encryption\Crypt::legacyDecrypt( $crypted, $this->pass ); -// -// $this->assertEquals( $this->dataLong, $decrypted ); -// -// } + /** + * @brief test decryption using legacy blowfish method + * @depends testLegacyEncryptLong + */ + function testLegacyDecryptLong( $crypted ) { + + $decrypted = Encryption\Crypt::legacyDecrypt( $crypted, $this->pass ); + + $this->assertEquals( $this->dataLong, $decrypted ); + + } /** * @brief test generation of legacy encryption key -- cgit v1.2.3 From 7fe92456360ea02d07e6f1d8e38f2f673ad20323 Mon Sep 17 00:00:00 2001 From: Sam Tuke Date: Sat, 5 Jan 2013 17:12:23 +0000 Subject: Development snapshot Crypt{} & Util{} unit tests now passing locally Added keymanager unit tests --- apps/files_encryption/hooks/hooks.php | 2 +- apps/files_encryption/lib/crypt.php | 2 +- apps/files_encryption/lib/keymanager.php | 65 +++++++++++------------------- apps/files_encryption/lib/proxy.php | 8 +++- apps/files_encryption/lib/stream.php | 12 +++++- apps/files_encryption/tests/crypt.php | 29 +++++-------- apps/files_encryption/tests/keymanager.php | 54 ++++++++++++++++++++----- 7 files changed, 95 insertions(+), 77 deletions(-) (limited to 'apps/files_encryption/lib/proxy.php') diff --git a/apps/files_encryption/hooks/hooks.php b/apps/files_encryption/hooks/hooks.php index 7545520fa78..59bf4921913 100644 --- a/apps/files_encryption/hooks/hooks.php +++ b/apps/files_encryption/hooks/hooks.php @@ -54,7 +54,7 @@ class Hooks { \OC_FileProxy::$enabled = false; - $encryptedKey = Keymanager::getPrivateKey( $params['uid'], $view ); + $encryptedKey = Keymanager::getPrivateKey( $view, $params['uid'] ); \OC_FileProxy::$enabled = true; diff --git a/apps/files_encryption/lib/crypt.php b/apps/files_encryption/lib/crypt.php index 7895a5dd7b0..4e2128e89f4 100755 --- a/apps/files_encryption/lib/crypt.php +++ b/apps/files_encryption/lib/crypt.php @@ -628,7 +628,7 @@ class Crypt { public static function changekeypasscode($oldPassword, $newPassword) { if(\OCP\User::isLoggedIn()){ - $key = Keymanager::getPrivateKey(); + $key = Keymanager::getPrivateKey( $user, $view ); if ( ($key = Crypt::symmetricDecryptFileContent($key,$oldpasswd)) ) { if ( ($key = Crypt::symmetricEncryptFileContent($key, $newpasswd)) ) { Keymanager::setPrivateKey($key); diff --git a/apps/files_encryption/lib/keymanager.php b/apps/files_encryption/lib/keymanager.php index d3be166add2..818cd1a154d 100755 --- a/apps/files_encryption/lib/keymanager.php +++ b/apps/files_encryption/lib/keymanager.php @@ -23,7 +23,8 @@ namespace OCA\Encryption; /** - * This class provides basic operations to read/write encryption keys from/to the filesystem + * @brief Class to manage storage and retrieval of encryption keys + * @note Where a method requires a view object, it's root must be '/' */ class Keymanager { @@ -35,60 +36,46 @@ class Keymanager { * @return string private key or false * @note the key returned by this method must be decrypted before use */ - public static function getPrivateKey( $user, $view ) { + public static function getPrivateKey( $view, $user ) { - $view->chroot( '/' . $user . '/' . 'files_encryption' ); - return $view->file_get_contents( '/' . $user.'.private.key' ); - + return $view->file_get_contents( '/' . $user . '/' . 'files_encryption' . '/' . $user.'.private.key' ); } /** * @brief retrieve public key for a specified user * @return string public key or false */ - public static function getPublicKey( $userId = NULL ) { - - // If the username wasn't specified, fetch it - if ( ! $userId ) { - - $userId = \OCP\User::getUser(); - - } + public static function getPublicKey( $view, $userId ) { - // Create new view with the right - $view = new \OC_FilesystemView( '/public-keys/' ); - - return $view->file_get_contents( '/' . $userId . '.public.key' ); + return $view->file_get_contents( '/public-keys/' . '/' . $userId . '.public.key' ); } /** * @brief retrieve both keys from a user (private and public) - * - * @return string private key or false + * @return array keys: privateKey, publicKey */ - public static function getUserKeys() { + public static function getUserKeys( $view, $userId ) { - return array( - 'privatekey' => self::getPrivateKey(), - 'publickey' => self::getPublicKey(), + return array( + 'publicKey' => self::getPublicKey( $view, $userId ) + , 'privateKey' => self::getPrivateKey( $view, $userId ) ); } /** - * @brief retrieve a list of the public key from all users with access to the file - * - * @param string path to file + * @brief Retrieve public keys of all users with access to a file + * @param string $path Path to file * @return array of public keys for the given file + * @note Checks that the sharing app is enabled should be performed + * by client code, that isn't checked here */ - public static function getPublicKeys( $path ) { - - $userId = \OCP\User::getUser(); + public static function getPublicKeys( $view, $userId, $filePath ) { $path = ltrim( $path, '/' ); - $filepath = '/'.$userId.'/files/'.$path; + $filepath = '/' . $userId . '/files/' . $filePath; // Check if sharing is enabled if ( OC_App::isEnabled( 'files_sharing' ) ) { @@ -157,34 +144,30 @@ class Keymanager { /** * @brief retrieve keyfile for an encrypted file - * * @param string file name * @return string file key or false * @note The keyfile returned is asymmetrically encrypted. Decryption * of the keyfile must be performed by client code */ - public static function getFileKey( $path, $staticUserClass = 'OCP\User' ) { + public static function getFileKey( $view, $userId, $filePath ) { - $keypath = ltrim( $path, '/' ); - $user = $staticUserClass::getUser(); + $filePath_f = ltrim( $filePath, '/' ); -// // update $keypath and $user if path point to a file shared by someone else +// // update $keypath and $userId 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 = ?" ); // -// $result = $query->execute( array ('/'.$user.'/files/'.$keypath, $user)); +// $result = $query->execute( array ('/'.$userId.'/files/'.$keypath, $userId)); // // if ($row = $result->fetchRow()) { // // $keypath = $row['source']; // $keypath_parts = explode( '/', $keypath ); -// $user = $keypath_parts[1]; -// $keypath = str_replace( '/' . $user . '/files/', '', $keypath ); +// $userId = $keypath_parts[1]; +// $keypath = str_replace( '/' . $userId . '/files/', '', $keypath ); // // } - $view = new \OC_FilesystemView('/'.$user.'/files_encryption/keyfiles/'); - - return $view->file_get_contents( $keypath . '.key' ); + return $this->view->file_get_contents( '/' . $userId . '/files_encryption/keyfiles/' . $filePath_f ); } diff --git a/apps/files_encryption/lib/proxy.php b/apps/files_encryption/lib/proxy.php index 9c7b5f01afc..272d0a5509f 100644 --- a/apps/files_encryption/lib/proxy.php +++ b/apps/files_encryption/lib/proxy.php @@ -91,6 +91,10 @@ class Proxy extends \OC_FileProxy { if ( !is_resource( $data ) ) { //stream put contents should have been converted to fopen + $userId = \OCP\USER::getUser(); + + $rootView = new \OC_FilesystemView( '/' ); + // Set the filesize for userland, before encrypting $size = strlen( $data ); @@ -98,7 +102,7 @@ class Proxy extends \OC_FileProxy { \OC_FileProxy::$enabled = false; // Encrypt plain data and fetch key - $encrypted = Crypt::keyEncryptKeyfile( $data, Keymanager::getPublicKey() ); + $encrypted = Crypt::keyEncryptKeyfile( $data, Keymanager::getPublicKey( $rootView, $userId ) ); // Replace plain content with encrypted content by reference $data = $encrypted['data']; @@ -110,7 +114,7 @@ class Proxy extends \OC_FileProxy { $filePath = '/' . implode( '/', $filePath ); # TODO: make keyfile dir dynamic from app config - $view = new \OC_FilesystemView( '/' . \OCP\USER::getUser() . '/files_encryption/keyfiles' ); + $view = new \OC_FilesystemView( '/' . $userId . '/files_encryption/keyfiles' ); // Save keyfile for newly encrypted file in parallel directory tree Keymanager::setFileKey( $filePath, $encrypted['key'], $view, '\OC_DB' ); diff --git a/apps/files_encryption/lib/stream.php b/apps/files_encryption/lib/stream.php index dcdad7ee561..fc1b9808cc5 100644 --- a/apps/files_encryption/lib/stream.php +++ b/apps/files_encryption/lib/stream.php @@ -63,7 +63,8 @@ class Stream { private $publicKey; private $keyfile; private $encKeyfile; - private static $view; + private static $view; // a fsview object set to user dir + private $rootView; // a fsview object set to '/' public function stream_open( $path, $mode, $options, &$opened_path ) { @@ -76,6 +77,13 @@ class Stream { } + // Set rootview object if necessary + if ( ! $this->rootView ) { + + $this->rootView = new \OC_FilesystemView( $this->userId . '/' ); + + } + $this->userId = \OCP\User::getUser(); // Get the bare file path @@ -332,7 +340,7 @@ class Stream { $this->keyfile = Crypt::generateKey(); - $this->publicKey = Keymanager::getPublicKey( $this->userId ); + $this->publicKey = Keymanager::getPublicKey( $this->rootView, $this->userId ); $this->encKeyfile = Crypt::keyEncrypt( $this->keyfile, $this->publicKey ); diff --git a/apps/files_encryption/tests/crypt.php b/apps/files_encryption/tests/crypt.php index e64ec15c82f..446af7cfa09 100755 --- a/apps/files_encryption/tests/crypt.php +++ b/apps/files_encryption/tests/crypt.php @@ -7,21 +7,6 @@ * See the COPYING-README file. */ -// Load mockery files -require_once 'Mockery/Loader.php'; -require_once 'Hamcrest/Hamcrest.php'; -$loader = new \Mockery\Loader; -$loader->register(); - -use \Mockery as m; - -// Overload Session{} with a mock object before it is included -$adminEncPriKey = realpath( dirname(__FILE__).'/../../../data/admin/files_encryption/admin.private.key' ); -$adminDePriKey = OCA\Encryption\Crypt::symmetricDecryptFileContent( $adminEncPriKey, 'admin' ); - -$mockSession = m::mock('overload:OCA\Encryption\Session'); -$mockSession->shouldReceive( 'getPrivateKey' )->andReturn( file_get_contents( $adminDePriKey ) ); - //require_once "PHPUnit/Framework/TestCase.php"; require_once realpath( dirname(__FILE__).'/../../../3rdparty/Crypt_Blowfish/Blowfish.php' ); require_once realpath( dirname(__FILE__).'/../../../lib/base.php' ); @@ -38,7 +23,13 @@ use OCA\Encryption; // encryption key needs to be saved in the session \OC_User::login( 'admin', 'admin' ); -//trigger_error("session = ".var_export($_SESSION, 1)); +/** + * @note It would be better to use Mockery here for mocking out the session + * handling process, and isolate calls to session class and data from the unit + * tests relating to them (stream etc.). However getting mockery to work and + * overload classes whilst also using the OC autoloader is difficult due to + * load order Pear errors. + */ class Test_Crypt extends \PHPUnit_Framework_TestCase { @@ -66,8 +57,6 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { function tearDown() { - m::close(); - } function testGenerateKey() { @@ -247,7 +236,7 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { $this->assertNotEquals( $this->dataShort, $retreivedCryptedFile ); // Get private key - $encryptedPrivateKey = Encryption\Keymanager::getPrivateKey( $this->userId, $this->view ); + $encryptedPrivateKey = Encryption\Keymanager::getPrivateKey( $this->view, $this->userId ); $decryptedPrivateKey = Encryption\Crypt::symmetricDecryptFileContent( $encryptedPrivateKey, $this->pass ); @@ -303,7 +292,7 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { // Get private key - $encryptedPrivateKey = Encryption\Keymanager::getPrivateKey( $this->userId, $this->view ); + $encryptedPrivateKey = Encryption\Keymanager::getPrivateKey( $this->view, $this->userId ); $decryptedPrivateKey = Encryption\Crypt::symmetricDecryptFileContent( $encryptedPrivateKey, $this->pass ); diff --git a/apps/files_encryption/tests/keymanager.php b/apps/files_encryption/tests/keymanager.php index c762310dcc5..e31bbe2ab27 100644 --- a/apps/files_encryption/tests/keymanager.php +++ b/apps/files_encryption/tests/keymanager.php @@ -25,10 +25,14 @@ class Test_Keymanager extends \PHPUnit_Framework_TestCase { $this->data = realpath( dirname(__FILE__).'/../lib/crypt.php' ); $this->user = 'admin'; $this->passphrase = 'admin'; + $this->filePath = '/testing'; $this->view = new \OC_FilesystemView( '' ); // Disable encryption proxy to prevent recursive calls \OC_FileProxy::$enabled = false; + + // Notify system which iser is logged in etc. + \OC_User::setUserId( 'admin' ); } @@ -38,17 +42,29 @@ class Test_Keymanager extends \PHPUnit_Framework_TestCase { } - function testGetEncryptedPrivateKey() { + function testGetPrivateKey() { - $key = Encryption\Keymanager::getPrivateKey( $this->user, $this->view ); - - $this->assertEquals( 2302, strlen( $key ) ); + $key = Encryption\Keymanager::getPrivateKey( $this->view, $this->user ); + + + // Will this length vary? Perhaps we should use a range instead + $this->assertEquals( 2296, strlen( $key ) ); } + function testGetPublicKey() { + + $key = Encryption\Keymanager::getPublicKey( $this->view, $this->user ); + + $this->assertEquals( 451, strlen( $key ) ); + + $this->assertEquals( '-----BEGIN PUBLIC KEY-----', substr( $key, 0, 26 ) ); + } + function testSetFileKey() { - # NOTE: This cannot be tested until we are able to break out of the FileSystemView data directory root + # NOTE: This cannot be tested until we are able to break out + # of the FileSystemView data directory root // $key = Crypt::symmetricEncryptFileContentKeyfile( $this->data, 'hat' ); // @@ -62,21 +78,39 @@ class Test_Keymanager extends \PHPUnit_Framework_TestCase { } - function testGetDecryptedPrivateKey() { + function testGetPrivateKey_decrypt() { - $key = Encryption\Keymanager::getPrivateKey( $this->user, $this->view ); + $key = Encryption\Keymanager::getPrivateKey( $this->view, $this->user ); # TODO: replace call to Crypt with a mock object? $decrypted = Encryption\Crypt::symmetricDecryptFileContent( $key, $this->passphrase ); - var_dump($decrypted); - - $this->assertEquals( 1708, strlen( $decrypted ) ); + $this->assertEquals( 1704, strlen( $decrypted ) ); $this->assertEquals( '-----BEGIN PRIVATE KEY-----', substr( $decrypted, 0, 27 ) ); } + function testGetUserKeys() { + + $keys = Encryption\Keymanager::getUserKeys( $this->view, $this->user ); + + $this->assertEquals( 451, strlen( $keys['publicKey'] ) ); + $this->assertEquals( '-----BEGIN PUBLIC KEY-----', substr( $keys['publicKey'], 0, 26 ) ); + $this->assertEquals( 2296, strlen( $keys['privateKey'] ) ); + } + + function testGetPublicKeys() { + + # TODO: write me + + } + + function testGetFileKey() { + +// Encryption\Keymanager::getFileKey( $this->view, $this->user, $this->filePath ); + + } } -- cgit v1.2.3 From b024db9f989098b6adea42fb5bde4a49bf7ec5de Mon Sep 17 00:00:00 2001 From: Sam Tuke Date: Sun, 6 Jan 2013 13:56:45 +0000 Subject: Made Keymanager::getFileKey() dependencies explicit, fixed client code and tests accordingly --- apps/files_encryption/lib/keymanager.php | 6 +++--- apps/files_encryption/lib/proxy.php | 6 +++++- apps/files_encryption/lib/stream.php | 2 +- apps/files_encryption/tests/crypt.php | 4 ++-- 4 files changed, 11 insertions(+), 7 deletions(-) (limited to 'apps/files_encryption/lib/proxy.php') diff --git a/apps/files_encryption/lib/keymanager.php b/apps/files_encryption/lib/keymanager.php index 818cd1a154d..e6c08ee2b7f 100755 --- a/apps/files_encryption/lib/keymanager.php +++ b/apps/files_encryption/lib/keymanager.php @@ -149,7 +149,7 @@ class Keymanager { * @note The keyfile returned is asymmetrically encrypted. Decryption * of the keyfile must be performed by client code */ - public static function getFileKey( $view, $userId, $filePath ) { + public static function getFileKey( \OC_FilesystemView $view, $userId, $filePath ) { $filePath_f = ltrim( $filePath, '/' ); @@ -166,8 +166,8 @@ class Keymanager { // $keypath = str_replace( '/' . $userId . '/files/', '', $keypath ); // // } - - return $this->view->file_get_contents( '/' . $userId . '/files_encryption/keyfiles/' . $filePath_f ); +// trigger_error(var_export($view, 1)); + return $view->file_get_contents( '/' . $userId . '/files_encryption/keyfiles/' . $filePath_f . '.key' ); } diff --git a/apps/files_encryption/lib/proxy.php b/apps/files_encryption/lib/proxy.php index 272d0a5509f..0084af94c77 100644 --- a/apps/files_encryption/lib/proxy.php +++ b/apps/files_encryption/lib/proxy.php @@ -156,7 +156,11 @@ class Proxy extends \OC_FileProxy { //$cached = \OC_FileCache_Cached::get( $path, '' ); - $encryptedKeyfile = Keymanager::getFileKey( $filePath ); + $view = new \OC_FilesystemView( '' ); + + $userId = \OCP\USER::getUser(); + + $encryptedKeyfile = Keymanager::getFileKey( $view, $userId, $filePath ); $session = new Session(); diff --git a/apps/files_encryption/lib/stream.php b/apps/files_encryption/lib/stream.php index fc1b9808cc5..a98f5bec833 100644 --- a/apps/files_encryption/lib/stream.php +++ b/apps/files_encryption/lib/stream.php @@ -267,7 +267,7 @@ class Stream { # TODO: add error handling for when file exists but no keyfile // Fetch existing keyfile - $this->encKeyfile = Keymanager::getFileKey( $this->rawPath ); + $this->encKeyfile = Keymanager::getFileKey( $this->rootView, $this->userId, $this->rawPath ); $this->getUser(); diff --git a/apps/files_encryption/tests/crypt.php b/apps/files_encryption/tests/crypt.php index 446af7cfa09..4ac53a646b1 100755 --- a/apps/files_encryption/tests/crypt.php +++ b/apps/files_encryption/tests/crypt.php @@ -242,7 +242,7 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { // Get keyfile - $encryptedKeyfile = Encryption\Keymanager::getFileKey( $filename ); + $encryptedKeyfile = Encryption\Keymanager::getFileKey( $this->view, $this->userId, $filename ); $decryptedKeyfile = Encryption\Crypt::keyDecrypt( $encryptedKeyfile, $decryptedPrivateKey ); @@ -298,7 +298,7 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { // Get keyfile - $encryptedKeyfile = Encryption\Keymanager::getFileKey( $filename ); + $encryptedKeyfile = Encryption\Keymanager::getFileKey( $this->view, $this->userId, $filename ); $decryptedKeyfile = Encryption\Crypt::keyDecrypt( $encryptedKeyfile, $decryptedPrivateKey ); -- cgit v1.2.3 From 015787fbb3152661144d21119bb9ea662a8ba0a3 Mon Sep 17 00:00:00 2001 From: Sam Tuke Date: Sun, 6 Jan 2013 18:38:35 +0000 Subject: All in-use unit tests now passing after merge --- apps/files_encryption/hooks/hooks.php | 7 - apps/files_encryption/lib/crypt.php | 10 +- apps/files_encryption/lib/keymanager.php | 14 +- apps/files_encryption/lib/proxy.php | 9 - apps/files_encryption/lib/stream.php | 35 +- apps/files_encryption/test/binary | Bin 0 -> 9734 bytes apps/files_encryption/test/crypt.php | 667 +++++++++++++++++++++ apps/files_encryption/test/keymanager.php | 132 ++++ .../test/legacy-encrypted-text.txt | Bin 0 -> 3360 bytes apps/files_encryption/test/proxy.php | 220 +++++++ apps/files_encryption/test/stream.php | 226 +++++++ apps/files_encryption/test/util.php | 210 +++++++ apps/files_encryption/test/zeros | Bin 0 -> 10238 bytes apps/files_encryption/tests/binary | Bin 9734 -> 0 bytes apps/files_encryption/tests/crypt.php | 666 -------------------- apps/files_encryption/tests/keymanager.php | 116 ---- .../tests/legacy-encrypted-text.txt | Bin 3360 -> 0 bytes apps/files_encryption/tests/proxy.php | 224 ------- apps/files_encryption/tests/stream.php | 227 ------- apps/files_encryption/tests/util.php | 208 ------- apps/files_encryption/tests/zeros | Bin 10238 -> 0 bytes 21 files changed, 1472 insertions(+), 1499 deletions(-) create mode 100644 apps/files_encryption/test/binary create mode 100755 apps/files_encryption/test/crypt.php create mode 100644 apps/files_encryption/test/keymanager.php create mode 100644 apps/files_encryption/test/legacy-encrypted-text.txt create mode 100644 apps/files_encryption/test/proxy.php create mode 100644 apps/files_encryption/test/stream.php create mode 100755 apps/files_encryption/test/util.php create mode 100644 apps/files_encryption/test/zeros delete mode 100644 apps/files_encryption/tests/binary delete mode 100755 apps/files_encryption/tests/crypt.php delete mode 100644 apps/files_encryption/tests/keymanager.php delete mode 100644 apps/files_encryption/tests/legacy-encrypted-text.txt delete mode 100644 apps/files_encryption/tests/proxy.php delete mode 100644 apps/files_encryption/tests/stream.php delete mode 100755 apps/files_encryption/tests/util.php delete mode 100644 apps/files_encryption/tests/zeros (limited to 'apps/files_encryption/lib/proxy.php') diff --git a/apps/files_encryption/hooks/hooks.php b/apps/files_encryption/hooks/hooks.php index 59bf4921913..c2f97247835 100644 --- a/apps/files_encryption/hooks/hooks.php +++ b/apps/files_encryption/hooks/hooks.php @@ -60,10 +60,6 @@ class Hooks { # TODO: dont manually encrypt the private keyfile - use the config options of openssl_pkey_export instead for better mobile compatibility - //trigger_error( "\$encryptedKey = ".var_export($encryptedKey)." \n\n\$params['password'] = ".var_export($params['password'] ) ); - -// trigger_error( "\$params['password'] = {$params['password']}" ); - $privateKey = Crypt::symmetricDecryptFileContent( $encryptedKey, $params['password'] ); $session = new Session(); @@ -80,7 +76,6 @@ class Hooks { ) { $_SESSION['legacyenckey'] = Crypt::legacyDecrypt( $legacyKey, $params['password'] ); -// trigger_error('leg enc key = '.$_SESSION['legacyenckey']); } // } @@ -103,8 +98,6 @@ class Hooks { // Get existing decrypted private key $privateKey = $_SESSION['privateKey']; - trigger_error( "\$privateKey = ". var_export($privateKey, 1)); - // Encrypt private key with new user pwd as passphrase $encryptedPrivateKey = Crypt::symmetricEncryptFileContent( $privateKey, $params['password'] ); diff --git a/apps/files_encryption/lib/crypt.php b/apps/files_encryption/lib/crypt.php index 4e2128e89f4..96176210bf1 100755 --- a/apps/files_encryption/lib/crypt.php +++ b/apps/files_encryption/lib/crypt.php @@ -454,7 +454,7 @@ class Crypt { * @returns decrypted file */ public static function keyDecrypt( $encryptedContent, $privatekey ) { - //trigger_error(var_export($privatekey, 1)); + openssl_private_decrypt( $encryptedContent, $plainContent, $privatekey ); return $plainContent; @@ -490,8 +490,6 @@ class Crypt { // Decrypt the keyfile with the user's private key $decryptedKeyfile = self::keyDecrypt( $keyfile, $privateKey ); -// trigger_error( "\$keyfile = ".var_export($keyfile, 1)); - // Decrypt the catfile symmetrically using the decrypted keyfile $decryptedData = self::symmetricDecryptFileContent( $catfile, $decryptedKeyfile ); @@ -682,8 +680,6 @@ class Crypt { */ public static function legacyEncrypt( $content, $passphrase = '' ) { - //trigger_error("OC2 enc \$content = $content \$passphrase = ".var_export($passphrase, 1) ); - $bf = self::getBlowfish( $passphrase ); return $bf->encrypt( $content ); @@ -700,12 +696,8 @@ class Crypt { */ public static function legacyDecrypt( $content, $passphrase = '' ) { - //trigger_error("OC2 dec \$content = $content \$key = ".strlen($passphrase) ); - $bf = self::getBlowfish( $passphrase ); -// trigger_error(var_export($bf, 1) ); - $decrypted = $bf->decrypt( $content ); $trimmed = rtrim( $decrypted, "\0" ); diff --git a/apps/files_encryption/lib/keymanager.php b/apps/files_encryption/lib/keymanager.php index c25c547f0d0..706e1c2661e 100755 --- a/apps/files_encryption/lib/keymanager.php +++ b/apps/files_encryption/lib/keymanager.php @@ -36,16 +36,20 @@ class Keymanager { * @return string private key or false * @note the key returned by this method must be decrypted before use */ - public static function getPrivateKey( $view, $user ) { + public static function getPrivateKey( \OC_FilesystemView $view, $user ) { - return $view->file_get_contents( '/' . $user . '/' . 'files_encryption' . '/' . $user.'.private.key' ); + $path = '/' . $user . '/' . 'files_encryption' . '/' . $user.'.private.key'; + + $key = $view->file_get_contents( $path ); + + return $key; } /** * @brief retrieve public key for a specified user * @return string public key or false */ - public static function getPublicKey( $view, $userId ) { + public static function getPublicKey( \OC_FilesystemView $view, $userId ) { return $view->file_get_contents( '/public-keys/' . '/' . $userId . '.public.key' ); @@ -55,7 +59,7 @@ class Keymanager { * @brief retrieve both keys from a user (private and public) * @return array keys: privateKey, publicKey */ - public static function getUserKeys( $view, $userId ) { + public static function getUserKeys( \OC_FilesystemView $view, $userId ) { return array( 'publicKey' => self::getPublicKey( $view, $userId ) @@ -71,7 +75,7 @@ class Keymanager { * @note Checks that the sharing app is enabled should be performed * by client code, that isn't checked here */ - public static function getPublicKeys( $view, $userId, $filePath ) { + public static function getPublicKeys( \OC_FilesystemView $view, $userId, $filePath ) { $path = ltrim( $path, '/' ); diff --git a/apps/files_encryption/lib/proxy.php b/apps/files_encryption/lib/proxy.php index 0084af94c77..52f47dba294 100644 --- a/apps/files_encryption/lib/proxy.php +++ b/apps/files_encryption/lib/proxy.php @@ -146,7 +146,6 @@ class Proxy extends \OC_FileProxy { Crypt::mode() == 'server' && Crypt::isEncryptedContent( $data ) ) { -// trigger_error("bong"); $split = explode( '/', $path ); @@ -171,10 +170,8 @@ class Proxy extends \OC_FileProxy { && isset( $_SESSION['legacyenckey'] ) && Crypt::isEncryptedMeta( $path ) ) { - trigger_error("mong"); $decrypted = Crypt::legacyDecrypt( $data, $_SESSION['legacyenckey'] ); - //trigger_error($data); } @@ -207,8 +204,6 @@ class Proxy extends \OC_FileProxy { $meta = stream_get_meta_data( $result ); -// trigger_error("\$meta(result) = ".var_export($meta, 1)); - $view = new \OC_FilesystemView( '' ); $util = new Util( $view, \OCP\USER::getUser()); @@ -243,12 +238,8 @@ class Proxy extends \OC_FileProxy { ) { $x = $view->file_get_contents( $path ); - //trigger_error( "size = ".var_export( $x, 1 ) ); - $tmp = tmpfile(); -// trigger_error("Result meta = ".var_export($meta, 1)); - // // Make a temporary copy of the original file // \OCP\Files::streamCopy( $result, $tmp ); // diff --git a/apps/files_encryption/lib/stream.php b/apps/files_encryption/lib/stream.php index a98f5bec833..076492cfe3d 100644 --- a/apps/files_encryption/lib/stream.php +++ b/apps/files_encryption/lib/stream.php @@ -134,8 +134,6 @@ class Stream { $this->handle = self::$view->fopen( $this->path_f, $mode ); - //file_put_contents('/home/samtuke/newtmp.txt', 'fucking hopeless = '.$path ); - \OC_FileProxy::$enabled = true; if ( !is_resource( $this->handle ) ) { @@ -170,8 +168,6 @@ class Stream { public function stream_read( $count ) { -// file_put_contents('/home/samtuke/newtmp.txt', "\$count = $count" ); - $this->writeCache = ''; if ( $count != 8192 ) { @@ -188,31 +184,13 @@ class Stream { // // Get the data from the file handle $data = fread( $this->handle, 8192 ); - - //echo "\n\nPRE DECRYPTION = $data\n\n"; -// + if ( strlen( $data ) ) { $this->getKey(); - //$key = file_get_contents( '/home/samtuke/owncloud/git/oc3/data/admin/files_encryption/keyfiles/tmp-1346255589.key' ); - $result = Crypt::symmetricDecryptFileContent( $data, $this->keyfile ); -// file_put_contents('/home/samtuke/newtmp.txt', '$result = '.$result ); - -// echo "\n\n\n\n-----------------------------\n\nNEWS"; -// -// echo "\n\n\$data = $data"; -// -// echo "\n\n\$key = {$this->keyfile}"; -// -// echo "\n\n\$result = $result"; -// -// echo "\n\n\n\n-----------------------------\n\n"; - - //trigger_error("CAT $result"); - } else { $result = ''; @@ -275,8 +253,6 @@ class Stream { $privateKey = $session->getPrivateKey( $this->userId ); -// trigger_error( "privateKey = '".var_export( $privateKey, 1 ) ."'" ); - $this->keyfile = Crypt::keyDecrypt( $this->encKeyfile, $privateKey ); return true; @@ -521,13 +497,16 @@ class Stream { $this->flush(); - if ($this->meta['mode']!='r' and $this->meta['mode']!='rb') { + if ( + $this->meta['mode']!='r' + and $this->meta['mode']!='rb' + ) { - \OC_FileCache::put($this->path,array('encrypted'=>true,'size'=>$this->size),''); + \OC_FileCache::put( $this->path, array( 'encrypted' => true, 'size' => $this->size ), '' ); } - return fclose($this->handle); + return fclose( $this->handle ); } diff --git a/apps/files_encryption/test/binary b/apps/files_encryption/test/binary new file mode 100644 index 00000000000..79bc99479da Binary files /dev/null and b/apps/files_encryption/test/binary differ diff --git a/apps/files_encryption/test/crypt.php b/apps/files_encryption/test/crypt.php new file mode 100755 index 00000000000..5a7820dc9da --- /dev/null +++ b/apps/files_encryption/test/crypt.php @@ -0,0 +1,667 @@ +, and + * Robin Appelman + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +//require_once "PHPUnit/Framework/TestCase.php"; +require_once realpath( dirname(__FILE__).'/../../../3rdparty/Crypt_Blowfish/Blowfish.php' ); +require_once realpath( dirname(__FILE__).'/../../../lib/base.php' ); +require_once realpath( dirname(__FILE__).'/../lib/crypt.php' ); +require_once realpath( dirname(__FILE__).'/../lib/keymanager.php' ); +require_once realpath( dirname(__FILE__).'/../lib/proxy.php' ); +require_once realpath( dirname(__FILE__).'/../lib/stream.php' ); +require_once realpath( dirname(__FILE__).'/../lib/util.php' ); +require_once realpath( dirname(__FILE__).'/../appinfo/app.php' ); + +use OCA\Encryption; + +// This has to go here because otherwise session errors arise, and the private +// encryption key needs to be saved in the session +\OC_User::login( 'admin', 'admin' ); + +/** + * @note It would be better to use Mockery here for mocking out the session + * handling process, and isolate calls to session class and data from the unit + * tests relating to them (stream etc.). However getting mockery to work and + * overload classes whilst also using the OC autoloader is difficult due to + * load order Pear errors. + */ + +class Test_Crypt extends \PHPUnit_Framework_TestCase { + + function setUp() { + + // set content for encrypting / decrypting in tests + $this->dataLong = file_get_contents( realpath( dirname(__FILE__).'/../lib/crypt.php' ) ); + $this->dataShort = 'hats'; + $this->dataUrl = realpath( dirname(__FILE__).'/../lib/crypt.php' ); + $this->legacyData = realpath( dirname(__FILE__).'/legacy-text.txt' ); + $this->legacyEncryptedData = realpath( dirname(__FILE__).'/legacy-encrypted-text.txt' ); + $this->randomKey = Encryption\Crypt::generateKey(); + + $keypair = Encryption\Crypt::createKeypair(); + $this->genPublicKey = $keypair['publicKey']; + $this->genPrivateKey = $keypair['privateKey']; + + $this->view = new \OC_FilesystemView( '/' ); + + \OC_User::setUserId( 'admin' ); + $this->userId = 'admin'; + $this->pass = 'admin'; + + \OC_Filesystem::init( '/' ); + \OC_Filesystem::mount( 'OC_Filestorage_Local', array('datadir' => \OC_User::getHome($this->userId)), '/' ); + + } + + function tearDown() { + + } + + function testGenerateKey() { + + # TODO: use more accurate (larger) string length for test confirmation + + $key = Encryption\Crypt::generateKey(); + + $this->assertTrue( strlen( $key ) > 16 ); + + } + + function testGenerateIv() { + + $iv = Encryption\Crypt::generateIv(); + + $this->assertEquals( 16, strlen( $iv ) ); + + return $iv; + + } + + /** + * @depends testGenerateIv + */ + function testConcatIv( $iv ) { + + $catFile = Encryption\Crypt::concatIv( $this->dataLong, $iv ); + + // Fetch encryption metadata from end of file + $meta = substr( $catFile, -22 ); + + $identifier = substr( $meta, 0, 6); + + // Fetch IV from end of file + $foundIv = substr( $meta, 6 ); + + $this->assertEquals( '00iv00', $identifier ); + + $this->assertEquals( $iv, $foundIv ); + + // Remove IV and IV identifier text to expose encrypted content + $data = substr( $catFile, 0, -22 ); + + $this->assertEquals( $this->dataLong, $data ); + + return array( + 'iv' => $iv + , 'catfile' => $catFile + ); + + } + + /** + * @depends testConcatIv + */ + function testSplitIv( $testConcatIv ) { + + // Split catfile into components + $splitCatfile = Encryption\Crypt::splitIv( $testConcatIv['catfile'] ); + + // Check that original IV and split IV match + $this->assertEquals( $testConcatIv['iv'], $splitCatfile['iv'] ); + + // Check that original data and split data match + $this->assertEquals( $this->dataLong, $splitCatfile['encrypted'] ); + + } + + function testAddPadding() { + + $padded = Encryption\Crypt::addPadding( $this->dataLong ); + + $padding = substr( $padded, -2 ); + + $this->assertEquals( 'xx' , $padding ); + + return $padded; + + } + + /** + * @depends testAddPadding + */ + function testRemovePadding( $padded ) { + + $noPadding = Encryption\Crypt::RemovePadding( $padded ); + + $this->assertEquals( $this->dataLong, $noPadding ); + + } + + function testEncrypt() { + + $random = openssl_random_pseudo_bytes( 13 ); + + $iv = substr( base64_encode( $random ), 0, -4 ); // i.e. E5IG033j+mRNKrht + + $crypted = Encryption\Crypt::encrypt( $this->dataUrl, $iv, 'hat' ); + + $this->assertNotEquals( $this->dataUrl, $crypted ); + + } + + function testDecrypt() { + + $random = openssl_random_pseudo_bytes( 13 ); + + $iv = substr( base64_encode( $random ), 0, -4 ); // i.e. E5IG033j+mRNKrht + + $crypted = Encryption\Crypt::encrypt( $this->dataUrl, $iv, 'hat' ); + + $decrypt = Encryption\Crypt::decrypt( $crypted, $iv, 'hat' ); + + $this->assertEquals( $this->dataUrl, $decrypt ); + + } + + function testSymmetricEncryptFileContent() { + + # TODO: search in keyfile for actual content as IV will ensure this test always passes + + $crypted = Encryption\Crypt::symmetricEncryptFileContent( $this->dataShort, 'hat' ); + + $this->assertNotEquals( $this->dataShort, $crypted ); + + + $decrypt = Encryption\Crypt::symmetricDecryptFileContent( $crypted, 'hat' ); + + $this->assertEquals( $this->dataShort, $decrypt ); + + } + + // These aren't used for now +// function testSymmetricBlockEncryptShortFileContent() { +// +// $crypted = Encryption\Crypt::symmetricBlockEncryptFileContent( $this->dataShort, $this->randomKey ); +// +// $this->assertNotEquals( $this->dataShort, $crypted ); +// +// +// $decrypt = Encryption\Crypt::symmetricBlockDecryptFileContent( $crypted, $this->randomKey ); +// +// $this->assertEquals( $this->dataShort, $decrypt ); +// +// } +// +// function testSymmetricBlockEncryptLongFileContent() { +// +// $crypted = Encryption\Crypt::symmetricBlockEncryptFileContent( $this->dataLong, $this->randomKey ); +// +// $this->assertNotEquals( $this->dataLong, $crypted ); +// +// +// $decrypt = Encryption\Crypt::symmetricBlockDecryptFileContent( $crypted, $this->randomKey ); +// +// $this->assertEquals( $this->dataLong, $decrypt ); +// +// } + + function testSymmetricStreamEncryptShortFileContent() { + + $filename = 'tmp-'.time(); + + $cryptedFile = file_put_contents( 'crypt://' . $filename, $this->dataShort ); + + // Test that data was successfully written + $this->assertTrue( is_int( $cryptedFile ) ); + + + // Get file contents without using any wrapper to get it's actual contents on disk + $retreivedCryptedFile = $this->view->file_get_contents( $this->userId . '/files/' . $filename ); + + // Check that the file was encrypted before being written to disk + $this->assertNotEquals( $this->dataShort, $retreivedCryptedFile ); + + // Get private key + $encryptedPrivateKey = Encryption\Keymanager::getPrivateKey( $this->view, $this->userId ); + + $decryptedPrivateKey = Encryption\Crypt::symmetricDecryptFileContent( $encryptedPrivateKey, $this->pass ); + + + // Get keyfile + $encryptedKeyfile = Encryption\Keymanager::getFileKey( $this->view, $this->userId, $filename ); + + $decryptedKeyfile = Encryption\Crypt::keyDecrypt( $encryptedKeyfile, $decryptedPrivateKey ); + + + // Manually decrypt + $manualDecrypt = Encryption\Crypt::symmetricBlockDecryptFileContent( $retreivedCryptedFile, $decryptedKeyfile ); + + // Check that decrypted data matches + $this->assertEquals( $this->dataShort, $manualDecrypt ); + + } + + /** + * @brief Test that data that is written by the crypto stream wrapper + * @note Encrypted data is manually prepared and decrypted here to avoid dependency on success of stream_read + * @note If this test fails with truncate content, check that enough array slices are being rejoined to form $e, as the crypt.php file may have gotten longer and broken the manual + * reassembly of its data + */ + function testSymmetricStreamEncryptLongFileContent() { + + // Generate a a random filename + $filename = 'tmp-'.time(); + + // Save long data as encrypted file using stream wrapper + $cryptedFile = file_put_contents( 'crypt://' . $filename, $this->dataLong.$this->dataLong ); + + // Test that data was successfully written + $this->assertTrue( is_int( $cryptedFile ) ); + + // Get file contents without using any wrapper to get it's actual contents on disk + $retreivedCryptedFile = $this->view->file_get_contents( $this->userId . '/files/' . $filename ); + +// echo "\n\n\$retreivedCryptedFile = $retreivedCryptedFile\n\n"; + + // Check that the file was encrypted before being written to disk + $this->assertNotEquals( $this->dataLong.$this->dataLong, $retreivedCryptedFile ); + + // Manuallly split saved file into separate IVs and encrypted chunks + $r = preg_split('/(00iv00.{16,18})/', $retreivedCryptedFile, NULL, PREG_SPLIT_DELIM_CAPTURE); + + //print_r($r); + + // Join IVs and their respective data chunks + $e = array( $r[0].$r[1], $r[2].$r[3], $r[4].$r[5], $r[6].$r[7], $r[8].$r[9], $r[10].$r[11], $r[12].$r[13] );//.$r[11], $r[12].$r[13], $r[14] ); + + //print_r($e); + + + // Get private key + $encryptedPrivateKey = Encryption\Keymanager::getPrivateKey( $this->view, $this->userId ); + + $decryptedPrivateKey = Encryption\Crypt::symmetricDecryptFileContent( $encryptedPrivateKey, $this->pass ); + + + // Get keyfile + $encryptedKeyfile = Encryption\Keymanager::getFileKey( $this->view, $this->userId, $filename ); + + $decryptedKeyfile = Encryption\Crypt::keyDecrypt( $encryptedKeyfile, $decryptedPrivateKey ); + + + // Set var for reassembling decrypted content + $decrypt = ''; + + // Manually decrypt chunk + foreach ($e as $e) { + +// echo "\n\$e = $e"; + + $chunkDecrypt = Encryption\Crypt::symmetricDecryptFileContent( $e, $decryptedKeyfile ); + + // Assemble decrypted chunks + $decrypt .= $chunkDecrypt; + +// echo "\n\$chunkDecrypt = $chunkDecrypt"; + + } + +// echo "\n\$decrypt = $decrypt"; + + $this->assertEquals( $this->dataLong.$this->dataLong, $decrypt ); + + // Teardown + + $this->view->unlink( $filename ); + + Encryption\Keymanager::deleteFileKey( $filename ); + + } + + /** + * @brief Test that data that is read by the crypto stream wrapper + */ + function testSymmetricStreamDecryptShortFileContent() { + + $filename = 'tmp-'.time(); + + // Save long data as encrypted file using stream wrapper + $cryptedFile = file_put_contents( 'crypt://' . $filename, $this->dataShort ); + + // Test that data was successfully written + $this->assertTrue( is_int( $cryptedFile ) ); + + + // Get file contents without using any wrapper to get it's actual contents on disk + $retreivedCryptedFile = $this->view->file_get_contents( $this->userId . '/files/' . $filename ); + + $decrypt = file_get_contents( 'crypt://' . $filename ); + + $this->assertEquals( $this->dataShort, $decrypt ); + + } + + function testSymmetricStreamDecryptLongFileContent() { + + $filename = 'tmp-'.time(); + + // Save long data as encrypted file using stream wrapper + $cryptedFile = file_put_contents( 'crypt://' . $filename, $this->dataLong ); + + // Test that data was successfully written + $this->assertTrue( is_int( $cryptedFile ) ); + + + // Get file contents without using any wrapper to get it's actual contents on disk + $retreivedCryptedFile = $this->view->file_get_contents( $this->userId . '/files/' . $filename ); + + $decrypt = file_get_contents( 'crypt://' . $filename ); + + $this->assertEquals( $this->dataLong, $decrypt ); + + } + + // Is this test still necessary? +// function testSymmetricBlockStreamDecryptFileContent() { +// +// \OC_User::setUserId( 'admin' ); +// +// // Disable encryption proxy to prevent unwanted en/decryption +// \OC_FileProxy::$enabled = false; +// +// $cryptedFile = file_put_contents( 'crypt://' . '/blockEncrypt', $this->dataUrl ); +// +// // Disable encryption proxy to prevent unwanted en/decryption +// \OC_FileProxy::$enabled = false; +// +// echo "\n\n\$cryptedFile = " . $this->view->file_get_contents( '/blockEncrypt' ); +// +// $retreivedCryptedFile = file_get_contents( 'crypt://' . '/blockEncrypt' ); +// +// $this->assertEquals( $this->dataUrl, $retreivedCryptedFile ); +// +// \OC_FileProxy::$enabled = false; +// +// } + + function testSymmetricEncryptFileContentKeyfile() { + + # TODO: search in keyfile for actual content as IV will ensure this test always passes + + $crypted = Encryption\Crypt::symmetricEncryptFileContentKeyfile( $this->dataUrl ); + + $this->assertNotEquals( $this->dataUrl, $crypted['encrypted'] ); + + + $decrypt = Encryption\Crypt::symmetricDecryptFileContent( $crypted['encrypted'], $crypted['key'] ); + + $this->assertEquals( $this->dataUrl, $decrypt ); + + } + + function testIsEncryptedContent() { + + $this->assertFalse( Encryption\Crypt::isEncryptedContent( $this->dataUrl ) ); + + $this->assertFalse( Encryption\Crypt::isEncryptedContent( $this->legacyEncryptedData ) ); + + $keyfileContent = Encryption\Crypt::symmetricEncryptFileContent( $this->dataUrl, 'hat' ); + + $this->assertTrue( Encryption\Crypt::isEncryptedContent( $keyfileContent ) ); + + } + + function testMultiKeyEncrypt() { + + # TODO: search in keyfile for actual content as IV will ensure this test always passes + + $pair1 = Encryption\Crypt::createKeypair(); + + $this->assertEquals( 2, count( $pair1 ) ); + + $this->assertTrue( strlen( $pair1['publicKey'] ) > 1 ); + + $this->assertTrue( strlen( $pair1['privateKey'] ) > 1 ); + + + $crypted = Encryption\Crypt::multiKeyEncrypt( $this->dataUrl, array( $pair1['publicKey'] ) ); + + $this->assertNotEquals( $this->dataUrl, $crypted['encrypted'] ); + + + $decrypt = Encryption\Crypt::multiKeyDecrypt( $crypted['encrypted'], $crypted['keys'][0], $pair1['privateKey'] ); + + $this->assertEquals( $this->dataUrl, $decrypt ); + + } + + function testKeyEncrypt() { + + // Generate keypair + $pair1 = Encryption\Crypt::createKeypair(); + + // Encrypt data + $crypted = Encryption\Crypt::keyEncrypt( $this->dataUrl, $pair1['publicKey'] ); + + $this->assertNotEquals( $this->dataUrl, $crypted ); + + // Decrypt data + $decrypt = Encryption\Crypt::keyDecrypt( $crypted, $pair1['privateKey'] ); + + $this->assertEquals( $this->dataUrl, $decrypt ); + + } + + // What is the point of this test? It doesn't use keyEncryptKeyfile() + function testKeyEncryptKeyfile() { + + # TODO: Don't repeat encryption from previous tests, use PHPUnit test interdependency instead + + // Generate keypair + $pair1 = Encryption\Crypt::createKeypair(); + + // Encrypt plain data, generate keyfile & encrypted file + $cryptedData = Encryption\Crypt::symmetricEncryptFileContentKeyfile( $this->dataUrl ); + + // Encrypt keyfile + $cryptedKey = Encryption\Crypt::keyEncrypt( $cryptedData['key'], $pair1['publicKey'] ); + + // Decrypt keyfile + $decryptKey = Encryption\Crypt::keyDecrypt( $cryptedKey, $pair1['privateKey'] ); + + // Decrypt encrypted file + $decryptData = Encryption\Crypt::symmetricDecryptFileContent( $cryptedData['encrypted'], $decryptKey ); + + $this->assertEquals( $this->dataUrl, $decryptData ); + + } + + /** + * @brief test functionality of keyEncryptKeyfile() and + * keyDecryptKeyfile() + */ + function testKeyDecryptKeyfile() { + + $encrypted = Encryption\Crypt::keyEncryptKeyfile( $this->dataShort, $this->genPublicKey ); + + $this->assertNotEquals( $encrypted['data'], $this->dataShort ); + + $decrypted = Encryption\Crypt::keyDecryptKeyfile( $encrypted['data'], $encrypted['key'], $this->genPrivateKey ); + + $this->assertEquals( $decrypted, $this->dataShort ); + + } + + + /** + * @brief test encryption using legacy blowfish method + */ + function testLegacyEncryptShort() { + + $crypted = Encryption\Crypt::legacyEncrypt( $this->dataShort, $this->pass ); + + $this->assertNotEquals( $this->dataShort, $crypted ); + + # TODO: search inencrypted text for actual content to ensure it + # genuine transformation + + return $crypted; + + } + + /** + * @brief test decryption using legacy blowfish method + * @depends testLegacyEncryptShort + */ + function testLegacyDecryptShort( $crypted ) { + + $decrypted = Encryption\Crypt::legacyDecrypt( $crypted, $this->pass ); + + $this->assertEquals( $this->dataShort, $decrypted ); + + } + + /** + * @brief test encryption using legacy blowfish method + */ + function testLegacyEncryptLong() { + + $crypted = Encryption\Crypt::legacyEncrypt( $this->dataLong, $this->pass ); + + $this->assertNotEquals( $this->dataLong, $crypted ); + + # TODO: search inencrypted text for actual content to ensure it + # genuine transformation + + return $crypted; + + } + + /** + * @brief test decryption using legacy blowfish method + * @depends testLegacyEncryptLong + */ + function testLegacyDecryptLong( $crypted ) { + + $decrypted = Encryption\Crypt::legacyDecrypt( $crypted, $this->pass ); + + $this->assertEquals( $this->dataLong, $decrypted ); + + } + + /** + * @brief test generation of legacy encryption key + * @depends testLegacyDecryptShort + */ + function testLegacyCreateKey() { + + // Create encrypted key + $encKey = Encryption\Crypt::legacyCreateKey( $this->pass ); + + // Decrypt key + $key = Encryption\Crypt::legacyDecrypt( $encKey, $this->pass ); + + $this->assertTrue( is_numeric( $key ) ); + + // Check that key is correct length + $this->assertEquals( 20, strlen( $key ) ); + + } + + /** + * @brief test decryption using legacy blowfish method + * @depends testLegacyEncryptLong + */ + function testLegacyKeyRecryptKeyfileEncrypt( $crypted ) { + + $recrypted = Encryption\Crypt::LegacyKeyRecryptKeyfile( $crypted, $this->pass, $this->genPublicKey, $this->pass ); + + $this->assertNotEquals( $this->dataLong, $recrypted['data'] ); + + return $recrypted; + + # TODO: search inencrypted text for actual content to ensure it + # genuine transformation + + } + +// function testEncryption(){ +// +// $key=uniqid(); +// $file=OC::$SERVERROOT.'/3rdparty/MDB2.php'; +// $source=file_get_contents($file); //nice large text file +// $encrypted=OC_Encryption\Crypt::encrypt($source,$key); +// $decrypted=OC_Encryption\Crypt::decrypt($encrypted,$key); +// $decrypted=rtrim($decrypted, "\0"); +// $this->assertNotEquals($encrypted,$source); +// $this->assertEqual($decrypted,$source); +// +// $chunk=substr($source,0,8192); +// $encrypted=OC_Encryption\Crypt::encrypt($chunk,$key); +// $this->assertEqual(strlen($chunk),strlen($encrypted)); +// $decrypted=OC_Encryption\Crypt::decrypt($encrypted,$key); +// $decrypted=rtrim($decrypted, "\0"); +// $this->assertEqual($decrypted,$chunk); +// +// $encrypted=OC_Encryption\Crypt::blockEncrypt($source,$key); +// $decrypted=OC_Encryption\Crypt::blockDecrypt($encrypted,$key); +// $this->assertNotEquals($encrypted,$source); +// $this->assertEqual($decrypted,$source); +// +// $tmpFileEncrypted=OCP\Files::tmpFile(); +// OC_Encryption\Crypt::encryptfile($file,$tmpFileEncrypted,$key); +// $encrypted=file_get_contents($tmpFileEncrypted); +// $decrypted=OC_Encryption\Crypt::blockDecrypt($encrypted,$key); +// $this->assertNotEquals($encrypted,$source); +// $this->assertEqual($decrypted,$source); +// +// $tmpFileDecrypted=OCP\Files::tmpFile(); +// OC_Encryption\Crypt::decryptfile($tmpFileEncrypted,$tmpFileDecrypted,$key); +// $decrypted=file_get_contents($tmpFileDecrypted); +// $this->assertEqual($decrypted,$source); +// +// $file=OC::$SERVERROOT.'/core/img/weather-clear.png'; +// $source=file_get_contents($file); //binary file +// $encrypted=OC_Encryption\Crypt::encrypt($source,$key); +// $decrypted=OC_Encryption\Crypt::decrypt($encrypted,$key); +// $decrypted=rtrim($decrypted, "\0"); +// $this->assertEqual($decrypted,$source); +// +// $encrypted=OC_Encryption\Crypt::blockEncrypt($source,$key); +// $decrypted=OC_Encryption\Crypt::blockDecrypt($encrypted,$key); +// $this->assertEqual($decrypted,$source); +// +// } +// +// function testBinary(){ +// $key=uniqid(); +// +// $file=__DIR__.'/binary'; +// $source=file_get_contents($file); //binary file +// $encrypted=OC_Encryption\Crypt::encrypt($source,$key); +// $decrypted=OC_Encryption\Crypt::decrypt($encrypted,$key); +// +// $decrypted=rtrim($decrypted, "\0"); +// $this->assertEqual($decrypted,$source); +// +// $encrypted=OC_Encryption\Crypt::blockEncrypt($source,$key); +// $decrypted=OC_Encryption\Crypt::blockDecrypt($encrypted,$key,strlen($source)); +// $this->assertEqual($decrypted,$source); +// } + +} diff --git a/apps/files_encryption/test/keymanager.php b/apps/files_encryption/test/keymanager.php new file mode 100644 index 00000000000..f02d6eb5f7a --- /dev/null +++ b/apps/files_encryption/test/keymanager.php @@ -0,0 +1,132 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +//require_once "PHPUnit/Framework/TestCase.php"; +require_once realpath( dirname(__FILE__).'/../../../lib/base.php' ); +require_once realpath( dirname(__FILE__).'/../lib/crypt.php' ); +require_once realpath( dirname(__FILE__).'/../lib/keymanager.php' ); +require_once realpath( dirname(__FILE__).'/../lib/proxy.php' ); +require_once realpath( dirname(__FILE__).'/../lib/stream.php' ); +require_once realpath( dirname(__FILE__).'/../lib/util.php' ); +require_once realpath( dirname(__FILE__).'/../appinfo/app.php' ); + +use OCA\Encryption; + +// This has to go here because otherwise session errors arise, and the private +// encryption key needs to be saved in the session +\OC_User::login( 'admin', 'admin' ); + +class Test_Keymanager extends \PHPUnit_Framework_TestCase { + + function setUp() { + + \OC_FileProxy::$enabled = false; + + // set content for encrypting / decrypting in tests + $this->dataLong = file_get_contents( realpath( dirname(__FILE__).'/../lib/crypt.php' ) ); + $this->dataShort = 'hats'; + $this->dataUrl = realpath( dirname(__FILE__).'/../lib/crypt.php' ); + $this->legacyData = realpath( dirname(__FILE__).'/legacy-text.txt' ); + $this->legacyEncryptedData = realpath( dirname(__FILE__).'/legacy-encrypted-text.txt' ); + $this->randomKey = Encryption\Crypt::generateKey(); + + $keypair = Encryption\Crypt::createKeypair(); + $this->genPublicKey = $keypair['publicKey']; + $this->genPrivateKey = $keypair['privateKey']; + + $this->view = new \OC_FilesystemView( '/' ); + + \OC_User::setUserId( 'admin' ); + $this->userId = 'admin'; + $this->pass = 'admin'; + + \OC_Filesystem::init( '/' ); + \OC_Filesystem::mount( 'OC_Filestorage_Local', array('datadir' => \OC_User::getHome($this->userId)), '/' ); + + } + + function tearDown(){ + + \OC_FileProxy::$enabled = true; + + } + + function testGetPrivateKey() { + + $key = Encryption\Keymanager::getPrivateKey( $this->view, $this->userId ); + + // Will this length vary? Perhaps we should use a range instead + $this->assertEquals( 2296, strlen( $key ) ); + + } + + function testGetPublicKey() { + + $key = Encryption\Keymanager::getPublicKey( $this->view, $this->userId ); + + $this->assertEquals( 451, strlen( $key ) ); + + $this->assertEquals( '-----BEGIN PUBLIC KEY-----', substr( $key, 0, 26 ) ); + } + + function testSetFileKey() { + + # NOTE: This cannot be tested until we are able to break out + # of the FileSystemView data directory root + +// $key = Crypt::symmetricEncryptFileContentKeyfile( $this->data, 'hat' ); +// +// $tmpPath = sys_get_temp_dir(). '/' . 'testSetFileKey'; +// +// $view = new \OC_FilesystemView( '/tmp/' ); +// +// //$view = new \OC_FilesystemView( '/' . $this->userId . '/files_encryption/keyfiles' ); +// +// Encryption\Keymanager::setFileKey( $tmpPath, $key['key'], $view ); + + } + +// /** +// * @depends testGetPrivateKey +// */ +// function testGetPrivateKey_decrypt() { +// +// $key = Encryption\Keymanager::getPrivateKey( $this->view, $this->userId ); +// +// # TODO: replace call to Crypt with a mock object? +// $decrypted = Encryption\Crypt::symmetricDecryptFileContent( $key, $this->passphrase ); +// +// $this->assertEquals( 1704, strlen( $decrypted ) ); +// +// $this->assertEquals( '-----BEGIN PRIVATE KEY-----', substr( $decrypted, 0, 27 ) ); +// +// } + + function testGetUserKeys() { + + $keys = Encryption\Keymanager::getUserKeys( $this->view, $this->userId ); + + $this->assertEquals( 451, strlen( $keys['publicKey'] ) ); + $this->assertEquals( '-----BEGIN PUBLIC KEY-----', substr( $keys['publicKey'], 0, 26 ) ); + $this->assertEquals( 2296, strlen( $keys['privateKey'] ) ); + + } + + function testGetPublicKeys() { + + # TODO: write me + + } + + function testGetFileKey() { + +// Encryption\Keymanager::getFileKey( $this->view, $this->userId, $this->filePath ); + + } + +} diff --git a/apps/files_encryption/test/legacy-encrypted-text.txt b/apps/files_encryption/test/legacy-encrypted-text.txt new file mode 100644 index 00000000000..cb5bf50550d Binary files /dev/null and b/apps/files_encryption/test/legacy-encrypted-text.txt differ diff --git a/apps/files_encryption/test/proxy.php b/apps/files_encryption/test/proxy.php new file mode 100644 index 00000000000..51e77100baa --- /dev/null +++ b/apps/files_encryption/test/proxy.php @@ -0,0 +1,220 @@ +, + * and Robin Appelman + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +// require_once "PHPUnit/Framework/TestCase.php"; +// require_once realpath( dirname(__FILE__).'/../../../lib/base.php' ); +// require_once realpath( dirname(__FILE__).'/../../../3rdparty/mockery/Mockery.php' ); +// require_once realpath( dirname(__FILE__).'/../../../3rdparty/mockery/Mockery/Generator.php' ); +// require_once realpath( dirname(__FILE__).'/../../../3rdparty/mockery/Mockery/MockInterface.php' ); +// require_once realpath( dirname(__FILE__).'/../../../3rdparty/mockery/Mockery/Mock.php' ); +// require_once realpath( dirname(__FILE__).'/../../../3rdparty/mockery/Mockery/Container.php' ); +// require_once realpath( dirname(__FILE__).'/../../../3rdparty/mockery/Mockery/Configuration.php' ); +// require_once realpath( dirname(__FILE__).'/../../../3rdparty/mockery/Mockery/CompositeExpectation.php' ); +// require_once realpath( dirname(__FILE__).'/../../../3rdparty/mockery/Mockery/ExpectationDirector.php' ); +// require_once realpath( dirname(__FILE__).'/../../../3rdparty/mockery/Mockery/Expectation.php' ); +// require_once realpath( dirname(__FILE__).'/../../../3rdparty/mockery/Mockery/Exception.php' ); +// require_once realpath( dirname(__FILE__).'/../../../3rdparty/mockery/Mockery/CountValidator/CountValidatorAbstract.php' ); +// require_once realpath( dirname(__FILE__).'/../../../3rdparty/mockery/Mockery/CountValidator/Exception.php' ); +// require_once realpath( dirname(__FILE__).'/../../../3rdparty/mockery/Mockery/CountValidator/Exact.php' ); +// +// use \Mockery as m; +// use OCA\Encryption; + +// class Test_Util extends \PHPUnit_Framework_TestCase { +// +// public function setUp() { +// +// $this->proxy = new Encryption\Proxy(); +// +// $this->tmpFileName = "tmpFile-".time(); +// +// $this->privateKey = file_get_contents( realpath( dirname(__FILE__).'/data/admin.public.key' ) ); +// $this->publicKey = file_get_contents( realpath( dirname(__FILE__).'/data/admin.private.key' ) ); +// $this->encDataShort = file_get_contents( realpath( dirname(__FILE__).'/data/yoga-manchester-enc' ) ); +// $this->encDataShortKey = file_get_contents( realpath( dirname(__FILE__).'/data/yoga-manchester.key' ) ); +// +// $this->dataShort = file_get_contents( realpath( dirname(__FILE__).'/data/yoga-manchester' ) ); +// $this->dataLong = file_get_contents( realpath( dirname(__FILE__).'/../lib/crypt.php' ) ); +// $this->longDataPath = realpath( dirname(__FILE__).'/../lib/crypt.php' ); +// +// $this->data1 = file_get_contents( realpath( dirname(__FILE__).'/../../../data/admin/files/enc-test.txt' ) ); +// +// \OC_FileProxy::$enabled = false; +// $this->Encdata1 = file_get_contents( realpath( dirname(__FILE__).'/../../../data/admin/files/enc-test.txt' ) ); +// \OC_FileProxy::$enabled = true; +// +// $this->userId = 'admin'; +// $this->pass = 'admin'; +// +// $this->session = new Encryption\Session(); +// +// $this->session->setPrivateKey( +// '-----BEGIN PRIVATE KEY----- +// MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDiH3EA4EpFA7Fx +// s2dyyfL5jwXeYXrTqQJ6DqKgGn8VsbT3eu8R9KzM2XitVwZe8c8L52DvJ06o5vg0 +// GqPYxilFdOFJe/ggac5Tq8UmJiZS4EqYEMwxBIfIyWTxeGV06/0HOwnVAkqHMcBz +// 64qldtgi5O8kZMEM2/gKBgU0kMLJzM+8oEWhL1+gsUWQhxd8cKLXypS6iWgqFJrz +// f/X0hJsJR+gyYxNpahtnjzd/LxLAETrOMsl2tue+BAxmjbAM0aG0NEM0div+b59s +// 2uz/iWbxImp5pOdYVKcVW89D4XBMyGegR40trV2VwiuX1blKCfdjMsJhiaL9pymp +// ug1wzyQFAgMBAAECggEAK6c+PZkPPXuVCgpEcliiW6NM0r2m5K3AGKgypQ34csu3 +// z/8foCvIIFPrhCtEw5eTDQ1CHWlNOjY8vHJYJ0U6Onpx86nHIRrMBkMm8FJ1G5LJ +// U8oKYXwqaozWu/cuPwA//OFc6I5krOzh5n8WaRMkbrgbor8AtebRX74By0AXGrXe +// cswJI7zR96oFn4Dm7Pgvpg5Zhk1vFJ+w6QtH+4DDJ6PBvlZsRkGxYBLGVd/3qhAI +// sBAyjFlSzuP4eCRhHOhHC/e4gmAH9evFVXB88jFyRZm3K+jQ5W5CwrVRBCV2lph6 +// 2B6P7CBJN+IjGKMhy+75y13UvvKPv9IwH8Fzl2x1gQKBgQD8qQOr7a6KhSj16wQE +// jim2xqt9gQ2jH5No405NrKs/PFQQZnzD4YseQsiK//NUjOJiUhaT+L5jhIpzINHt +// RJpt3bGkEZmLyjdjgTpB3GwZdXa28DNK9VdXZ19qIl/ZH0qAjKmJCRahUDASMnVi +// M4Pkk9yx9ZIKkri4TcuMWqc0DQKBgQDlHKBTITZq/arYPD6Nl3NsoOdqVRqJrGay +// 0TjXAVbBXe46+z5lnMsqwXb79nx14hdmSEsZULrw/3f+MnQbdjMTYLFP24visZg9 +// MN8vAiALiiiR1a+Crz+DTA1Q8sGOMVCMqMDmD7QBys3ZuWxuapm0txAiIYUtsjJZ +// XN76T4nZ2QKBgQCHaT3igzwsWTmesxowJtEMeGWomeXpKx8h89EfqA8PkRGsyIDN +// qq+YxEoe1RZgljEuaLhZDdNcGsjo8woPk9kAUPTH7fbRCMuutK+4ZJ469s1tNkcH +// QX5SBcEJbOrZvv967ehe3VQXmJZq6kgnHVzuwKBjcC2ZJRGDFY6l5l/+cQKBgCqh +// +Adf/8NK7paMJ0urqfPFwSodKfICXZ3apswDWMRkmSbqh4La+Uc8dsqN5Dz/VEFZ +// JHhSeGbN8uMfOlG93eU2MehdPxtw1pZUWMNjjtj23XO9ooob2CKzbSrp8TBnZsi1 +// widNNr66oTFpeo7VUUK6acsgF6sYJJxSVr+XO1yJAoGAEhvitq8shNKcEY0xCipS +// k1kbgyS7KKB7opVxI5+ChEqyUDijS3Y9FZixrRIWE6i2uGu86UG+v2lbKvSbM4Qm +// xvbOcX9OVMnlRb7n8woOP10UMY+ZE2x+YEUXQTLtPYq7F66e1OfxltstMxLQA+3d +// Y1d5piFV8PXK3Fg2F+Cj5qg= +// -----END PRIVATE KEY----- +// ' +// , $this->userId +// ); +// +// \OC_User::setUserId( $this->userId ); +// +// } +// +// public function testpreFile_get_contents() { +// +// // This won't work for now because mocking of the static keymanager class isn't working :( +// +// // $mock = m::mock( 'alias:OCA\Encryption\Keymanager' ); +// // +// // $mock->shouldReceive( 'getFileKey' )->times(2)->andReturn( $this->encDataShort ); +// // +// // $encrypted = $this->proxy->postFile_get_contents( 'data/'.$this->tmpFileName, $this->encDataShortKey ); +// // +// // $this->assertNotEquals( $this->dataShort, $encrypted ); +// +// $decrypted = $this->proxy->postFile_get_contents( 'data/admin/files/enc-test.txt', $this->data1 ); +// +// } +// +// } + +// class Test_CryptProxy extends UnitTestCase { +// private $oldConfig; +// private $oldKey; +// +// public function setUp(){ +// $user=OC_User::getUser(); +// +// $this->oldConfig=OCP\Config::getAppValue('files_encryption','enable_encryption','true'); +// OCP\Config::setAppValue('files_encryption','enable_encryption','true'); +// $this->oldKey=isset($_SESSION['privateKey'])?$_SESSION['privateKey']:null; +// +// +// //set testing key +// $_SESSION['privateKey']=md5(time()); +// +// //clear all proxies and hooks so we can do clean testing +// OC_FileProxy::clearProxies(); +// OC_Hook::clear('OC_Filesystem'); +// +// //enable only the encryption hook +// OC_FileProxy::register(new OC_FileProxy_Encryption()); +// +// //set up temporary storage +// OC_Filesystem::clearMounts(); +// OC_Filesystem::mount('OC_Filestorage_Temporary',array(),'/'); +// +// OC_Filesystem::init('/'.$user.'/files'); +// +// //set up the users home folder in the temp storage +// $rootView=new OC_FilesystemView(''); +// $rootView->mkdir('/'.$user); +// $rootView->mkdir('/'.$user.'/files'); +// } +// +// public function tearDown(){ +// OCP\Config::setAppValue('files_encryption','enable_encryption',$this->oldConfig); +// if(!is_null($this->oldKey)){ +// $_SESSION['privateKey']=$this->oldKey; +// } +// } +// +// public function testSimple(){ +// $file=OC::$SERVERROOT.'/3rdparty/MDB2.php'; +// $original=file_get_contents($file); +// +// OC_Filesystem::file_put_contents('/file',$original); +// +// OC_FileProxy::$enabled=false; +// $stored=OC_Filesystem::file_get_contents('/file'); +// OC_FileProxy::$enabled=true; +// +// $fromFile=OC_Filesystem::file_get_contents('/file'); +// $this->assertNotEqual($original,$stored); +// $this->assertEqual(strlen($original),strlen($fromFile)); +// $this->assertEqual($original,$fromFile); +// +// } +// +// public function testView(){ +// $file=OC::$SERVERROOT.'/3rdparty/MDB2.php'; +// $original=file_get_contents($file); +// +// $rootView=new OC_FilesystemView(''); +// $view=new OC_FilesystemView('/'.OC_User::getUser()); +// $userDir='/'.OC_User::getUser().'/files'; +// +// $rootView->file_put_contents($userDir.'/file',$original); +// +// OC_FileProxy::$enabled=false; +// $stored=$rootView->file_get_contents($userDir.'/file'); +// OC_FileProxy::$enabled=true; +// +// $this->assertNotEqual($original,$stored); +// $fromFile=$rootView->file_get_contents($userDir.'/file'); +// $this->assertEqual($original,$fromFile); +// +// $fromFile=$view->file_get_contents('files/file'); +// $this->assertEqual($original,$fromFile); +// } +// +// public function testBinary(){ +// $file=__DIR__.'/binary'; +// $original=file_get_contents($file); +// +// OC_Filesystem::file_put_contents('/file',$original); +// +// OC_FileProxy::$enabled=false; +// $stored=OC_Filesystem::file_get_contents('/file'); +// OC_FileProxy::$enabled=true; +// +// $fromFile=OC_Filesystem::file_get_contents('/file'); +// $this->assertNotEqual($original,$stored); +// $this->assertEqual(strlen($original),strlen($fromFile)); +// $this->assertEqual($original,$fromFile); +// +// $file=__DIR__.'/zeros'; +// $original=file_get_contents($file); +// +// OC_Filesystem::file_put_contents('/file',$original); +// +// OC_FileProxy::$enabled=false; +// $stored=OC_Filesystem::file_get_contents('/file'); +// OC_FileProxy::$enabled=true; +// +// $fromFile=OC_Filesystem::file_get_contents('/file'); +// $this->assertNotEqual($original,$stored); +// $this->assertEqual(strlen($original),strlen($fromFile)); +// } +// } diff --git a/apps/files_encryption/test/stream.php b/apps/files_encryption/test/stream.php new file mode 100644 index 00000000000..4211cab3104 --- /dev/null +++ b/apps/files_encryption/test/stream.php @@ -0,0 +1,226 @@ +// +// * This file is licensed under the Affero General Public License version 3 or +// * later. +// * See the COPYING-README file. +// */ +// +// namespace OCA\Encryption; +// +// class Test_Stream extends \PHPUnit_Framework_TestCase { +// +// function setUp() { +// +// \OC_Filesystem::mount( 'OC_Filestorage_Local', array(), '/' ); +// +// $this->empty = ''; +// +// $this->stream = new Stream(); +// +// $this->dataLong = file_get_contents( realpath( dirname(__FILE__).'/../lib/crypt.php' ) ); +// $this->dataShort = 'hats'; +// +// $this->emptyTmpFilePath = \OCP\Files::tmpFile(); +// +// $this->dataTmpFilePath = \OCP\Files::tmpFile(); +// +// file_put_contents( $this->dataTmpFilePath, "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a diam lectus. Sed sit amet ipsum mauris. Maecenas congue ligula ac quam viverra nec consectetur ante hendrerit. Donec et mollis dolor. Praesent et diam eget libero egestas mattis sit amet vitae augue. Nam tincidunt congue enim, ut porta lorem lacinia consectetur. Donec ut libero sed arcu vehicula ultricies a non tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean ut gravida lorem. Ut turpis felis, pulvinar a semper sed, adipiscing id dolor. Pellentesque auctor nisi id magna consequat sagittis. Curabitur dapibus enim sit amet elit pharetra tincidunt feugiat nisl imperdiet. Ut convallis libero in urna ultrices accumsan. Donec sed odio eros. Donec viverra mi quis quam pulvinar at malesuada arcu rhoncus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. In rutrum accumsan ultricies. Mauris vitae nisi at sem facilisis semper ac in est." ); +// +// } +// +// function testStreamOpen() { +// +// $stream1 = new Stream(); +// +// $handle1 = $stream1->stream_open( $this->emptyTmpFilePath, 'wb', array(), $this->empty ); +// +// // Test that resource was returned successfully +// $this->assertTrue( $handle1 ); +// +// // Test that file has correct size +// $this->assertEquals( 0, $stream1->size ); +// +// // Test that path is correct +// $this->assertEquals( $this->emptyTmpFilePath, $stream1->rawPath ); +// +// $stream2 = new Stream(); +// +// $handle2 = $stream2->stream_open( 'crypt://' . $this->emptyTmpFilePath, 'wb', array(), $this->empty ); +// +// // Test that protocol identifier is removed from path +// $this->assertEquals( $this->emptyTmpFilePath, $stream2->rawPath ); +// +// // "Stat failed error" prevents this test from executing +// // $stream3 = new Stream(); +// // +// // $handle3 = $stream3->stream_open( $this->dataTmpFilePath, 'r', array(), $this->empty ); +// // +// // $this->assertEquals( 0, $stream3->size ); +// +// } +// +// function testStreamWrite() { +// +// $stream1 = new Stream(); +// +// $handle1 = $stream1->stream_open( $this->emptyTmpFilePath, 'r+b', array(), $this->empty ); +// +// # what about the keymanager? there is no key for the newly created temporary file! +// +// $stream1->stream_write( $this->dataShort ); +// +// } +// +// // function getStream( $id, $mode, $size ) { +// // +// // if ( $id === '' ) { +// // +// // $id = uniqid(); +// // } +// // +// // +// // if ( !isset( $this->tmpFiles[$id] ) ) { +// // +// // // If tempfile with given name does not already exist, create it +// // +// // $file = OCP\Files::tmpFile(); +// // +// // $this->tmpFiles[$id] = $file; +// // +// // } else { +// // +// // $file = $this->tmpFiles[$id]; +// // +// // } +// // +// // $stream = fopen( $file, $mode ); +// // +// // Stream::$sourceStreams[$id] = array( 'path' => 'dummy' . $id, 'stream' => $stream, 'size' => $size ); +// // +// // return fopen( 'crypt://streams/'.$id, $mode ); +// // +// // } +// // +// // function testStream( ){ +// // +// // $stream = $this->getStream( 'test1', 'w', strlen( 'foobar' ) ); +// // +// // fwrite( $stream, 'foobar' ); +// // +// // fclose( $stream ); +// // +// // +// // $stream = $this->getStream( 'test1', 'r', strlen( 'foobar' ) ); +// // +// // $data = fread( $stream, 6 ); +// // +// // fclose( $stream ); +// // +// // $this->assertEqual( 'foobar', $data ); +// // +// // +// // $file = OC::$SERVERROOT.'/3rdparty/MDB2.php'; +// // +// // $source = fopen( $file, 'r' ); +// // +// // $target = $this->getStream( 'test2', 'w', 0 ); +// // +// // OCP\Files::streamCopy( $source, $target ); +// // +// // fclose( $target ); +// // +// // fclose( $source ); +// // +// // +// // $stream = $this->getStream( 'test2', 'r', filesize( $file ) ); +// // +// // $data = stream_get_contents( $stream ); +// // +// // $original = file_get_contents( $file ); +// // +// // $this->assertEqual( strlen( $original ), strlen( $data ) ); +// // +// // $this->assertEqual( $original, $data ); +// // +// // } +// +// } +// +// // class Test_CryptStream extends UnitTestCase { +// // private $tmpFiles=array(); +// // +// // function testStream(){ +// // $stream=$this->getStream('test1','w',strlen('foobar')); +// // fwrite($stream,'foobar'); +// // fclose($stream); +// // +// // $stream=$this->getStream('test1','r',strlen('foobar')); +// // $data=fread($stream,6); +// // fclose($stream); +// // $this->assertEqual('foobar',$data); +// // +// // $file=OC::$SERVERROOT.'/3rdparty/MDB2.php'; +// // $source=fopen($file,'r'); +// // $target=$this->getStream('test2','w',0); +// // OCP\Files::streamCopy($source,$target); +// // fclose($target); +// // fclose($source); +// // +// // $stream=$this->getStream('test2','r',filesize($file)); +// // $data=stream_get_contents($stream); +// // $original=file_get_contents($file); +// // $this->assertEqual(strlen($original),strlen($data)); +// // $this->assertEqual($original,$data); +// // } +// // +// // /** +// // * get a cryptstream to a temporary file +// // * @param string $id +// // * @param string $mode +// // * @param int size +// // * @return resource +// // */ +// // function getStream($id,$mode,$size){ +// // if($id===''){ +// // $id=uniqid(); +// // } +// // if(!isset($this->tmpFiles[$id])){ +// // $file=OCP\Files::tmpFile(); +// // $this->tmpFiles[$id]=$file; +// // }else{ +// // $file=$this->tmpFiles[$id]; +// // } +// // $stream=fopen($file,$mode); +// // OC_CryptStream::$sourceStreams[$id]=array('path'=>'dummy'.$id,'stream'=>$stream,'size'=>$size); +// // return fopen('crypt://streams/'.$id,$mode); +// // } +// // +// // function testBinary(){ +// // $file=__DIR__.'/binary'; +// // $source=file_get_contents($file); +// // +// // $stream=$this->getStream('test','w',strlen($source)); +// // fwrite($stream,$source); +// // fclose($stream); +// // +// // $stream=$this->getStream('test','r',strlen($source)); +// // $data=stream_get_contents($stream); +// // fclose($stream); +// // $this->assertEqual(strlen($data),strlen($source)); +// // $this->assertEqual($source,$data); +// // +// // $file=__DIR__.'/zeros'; +// // $source=file_get_contents($file); +// // +// // $stream=$this->getStream('test2','w',strlen($source)); +// // fwrite($stream,$source); +// // fclose($stream); +// // +// // $stream=$this->getStream('test2','r',strlen($source)); +// // $data=stream_get_contents($stream); +// // fclose($stream); +// // $this->assertEqual(strlen($data),strlen($source)); +// // $this->assertEqual($source,$data); +// // } +// // } diff --git a/apps/files_encryption/test/util.php b/apps/files_encryption/test/util.php new file mode 100755 index 00000000000..016787fbfba --- /dev/null +++ b/apps/files_encryption/test/util.php @@ -0,0 +1,210 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +//require_once "PHPUnit/Framework/TestCase.php"; +require_once realpath( dirname(__FILE__).'/../../../lib/base.php' ); +require_once realpath( dirname(__FILE__).'/../lib/crypt.php' ); +require_once realpath( dirname(__FILE__).'/../lib/keymanager.php' ); +require_once realpath( dirname(__FILE__).'/../lib/proxy.php' ); +require_once realpath( dirname(__FILE__).'/../lib/stream.php' ); +require_once realpath( dirname(__FILE__).'/../lib/util.php' ); +require_once realpath( dirname(__FILE__).'/../appinfo/app.php' ); + +// Load mockery files +require_once 'Mockery/Loader.php'; +require_once 'Hamcrest/Hamcrest.php'; +$loader = new \Mockery\Loader; +$loader->register(); + +use \Mockery as m; +use OCA\Encryption; + +class Test_Enc_Util extends \PHPUnit_Framework_TestCase { + + function setUp() { + + \OC_Filesystem::mount( 'OC_Filestorage_Local', array(), '/' ); + + // set content for encrypting / decrypting in tests + $this->dataUrl = realpath( dirname(__FILE__).'/../lib/crypt.php' ); + $this->dataShort = 'hats'; + $this->dataLong = file_get_contents( realpath( dirname(__FILE__).'/../lib/crypt.php' ) ); + $this->legacyData = realpath( dirname(__FILE__).'/legacy-text.txt' ); + $this->legacyEncryptedData = realpath( dirname(__FILE__).'/legacy-encrypted-text.txt' ); + + $this->userId = 'admin'; + $this->pass = 'admin'; + + $keypair = Encryption\Crypt::createKeypair(); + + $this->genPublicKey = $keypair['publicKey']; + $this->genPrivateKey = $keypair['privateKey']; + + $this->publicKeyDir = '/' . 'public-keys'; + $this->encryptionDir = '/' . $this->userId . '/' . 'files_encryption'; + $this->keyfilesPath = $this->encryptionDir . '/' . 'keyfiles'; + $this->publicKeyPath = $this->publicKeyDir . '/' . $this->userId . '.public.key'; // e.g. data/public-keys/admin.public.key + $this->privateKeyPath = $this->encryptionDir . '/' . $this->userId . '.private.key'; // e.g. data/admin/admin.private.key + + $this->view = new OC_FilesystemView( '/admin' ); + + $this->mockView = m::mock('OC_FilesystemView'); + $this->util = new Encryption\Util( $this->mockView, $this->userId ); + + } + + function tearDown(){ + + m::close(); + + } + + /** + * @brief test that paths set during User construction are correct + */ + function testKeyPaths() { + + $mockView = m::mock('OC_FilesystemView'); + + $util = new Encryption\Util( $mockView, $this->userId ); + + $this->assertEquals( $this->publicKeyDir, $util->getPath( 'publicKeyDir' ) ); + $this->assertEquals( $this->encryptionDir, $util->getPath( 'encryptionDir' ) ); + $this->assertEquals( $this->keyfilesPath, $util->getPath( 'keyfilesPath' ) ); + $this->assertEquals( $this->publicKeyPath, $util->getPath( 'publicKeyPath' ) ); + $this->assertEquals( $this->privateKeyPath, $util->getPath( 'privateKeyPath' ) ); + + } + + /** + * @brief test setup of encryption directories when they don't yet exist + */ + function testSetupServerSideNotSetup() { + + $mockView = m::mock('OC_FilesystemView'); + + $mockView->shouldReceive( 'file_exists' )->times(4)->andReturn( false ); + $mockView->shouldReceive( 'mkdir' )->times(3)->andReturn( true ); + $mockView->shouldReceive( 'file_put_contents' )->withAnyArgs(); + + $util = new Encryption\Util( $mockView, $this->userId ); + + $this->assertEquals( true, $util->setupServerSide( $this->pass ) ); + + } + + /** + * @brief test setup of encryption directories when they already exist + */ + function testSetupServerSideIsSetup() { + + $mockView = m::mock('OC_FilesystemView'); + + $mockView->shouldReceive( 'file_exists' )->times(5)->andReturn( true ); + $mockView->shouldReceive( 'file_put_contents' )->withAnyArgs(); + + $util = new Encryption\Util( $mockView, $this->userId ); + + $this->assertEquals( true, $util->setupServerSide( $this->pass ) ); + + } + + /** + * @brief test checking whether account is ready for encryption, when it isn't ready + */ + function testReadyNotReady() { + + $mockView = m::mock('OC_FilesystemView'); + + $mockView->shouldReceive( 'file_exists' )->times(1)->andReturn( false ); + + $util = new Encryption\Util( $mockView, $this->userId ); + + $this->assertEquals( false, $util->ready() ); + + # TODO: Add more tests here to check that if any of the dirs are + # then false will be returned. Use strict ordering? + + } + + /** + * @brief test checking whether account is ready for encryption, when it is ready + */ + function testReadyIsReady() { + + $mockView = m::mock('OC_FilesystemView'); + + $mockView->shouldReceive( 'file_exists' )->times(3)->andReturn( true ); + + $util = new Encryption\Util( $mockView, $this->userId ); + + $this->assertEquals( true, $util->ready() ); + + # TODO: Add more tests here to check that if any of the dirs are + # then false will be returned. Use strict ordering? + + } + +// /** +// * @brief test decryption using legacy blowfish method +// * @depends testLegacyEncryptLong +// */ +// function testLegacyKeyRecryptKeyfileDecrypt( $recrypted ) { +// +// $decrypted = Encryption\Crypt::keyDecryptKeyfile( $recrypted['data'], $recrypted['key'], $this->genPrivateKey ); +// +// $this->assertEquals( $this->dataLong, $decrypted ); +// +// } + +// // Cannot use this test for now due to hidden dependencies in OC_FileCache +// function testIsLegacyEncryptedContent() { +// +// $keyfileContent = OCA\Encryption\Crypt::symmetricEncryptFileContent( $this->legacyEncryptedData, 'hat' ); +// +// $this->assertFalse( OCA\Encryption\Crypt::isLegacyEncryptedContent( $keyfileContent, '/files/admin/test.txt' ) ); +// +// OC_FileCache::put( '/admin/files/legacy-encrypted-test.txt', $this->legacyEncryptedData ); +// +// $this->assertTrue( OCA\Encryption\Crypt::isLegacyEncryptedContent( $this->legacyEncryptedData, '/files/admin/test.txt' ) ); +// +// } + +// // Cannot use this test for now due to need for different root in OC_Filesystem_view class +// function testGetLegacyKey() { +// +// $c = new \OCA\Encryption\Util( $view, false ); +// +// $bool = $c->getLegacyKey( 'admin' ); +// +// $this->assertTrue( $bool ); +// +// $this->assertTrue( $c->legacyKey ); +// +// $this->assertTrue( is_int( $c->legacyKey ) ); +// +// $this->assertTrue( strlen( $c->legacyKey ) == 20 ); +// +// } + +// // Cannot use this test for now due to need for different root in OC_Filesystem_view class +// function testLegacyDecrypt() { +// +// $c = new OCA\Encryption\Util( $this->view, false ); +// +// $bool = $c->getLegacyKey( 'admin' ); +// +// $encrypted = $c->legacyEncrypt( $this->data, $c->legacyKey ); +// +// $decrypted = $c->legacyDecrypt( $encrypted, $c->legacyKey ); +// +// $this->assertEqual( $decrypted, $this->data ); +// +// } + +} \ No newline at end of file diff --git a/apps/files_encryption/test/zeros b/apps/files_encryption/test/zeros new file mode 100644 index 00000000000..ff982acf423 Binary files /dev/null and b/apps/files_encryption/test/zeros differ diff --git a/apps/files_encryption/tests/binary b/apps/files_encryption/tests/binary deleted file mode 100644 index 79bc99479da..00000000000 Binary files a/apps/files_encryption/tests/binary and /dev/null differ diff --git a/apps/files_encryption/tests/crypt.php b/apps/files_encryption/tests/crypt.php deleted file mode 100755 index 4ac53a646b1..00000000000 --- a/apps/files_encryption/tests/crypt.php +++ /dev/null @@ -1,666 +0,0 @@ -, and - * Robin Appelman - * This file is licensed under the Affero General Public License version 3 or - * later. - * See the COPYING-README file. - */ - -//require_once "PHPUnit/Framework/TestCase.php"; -require_once realpath( dirname(__FILE__).'/../../../3rdparty/Crypt_Blowfish/Blowfish.php' ); -require_once realpath( dirname(__FILE__).'/../../../lib/base.php' ); -require_once realpath( dirname(__FILE__).'/../lib/crypt.php' ); -require_once realpath( dirname(__FILE__).'/../lib/keymanager.php' ); -require_once realpath( dirname(__FILE__).'/../lib/proxy.php' ); -require_once realpath( dirname(__FILE__).'/../lib/stream.php' ); -require_once realpath( dirname(__FILE__).'/../lib/util.php' ); -require_once realpath( dirname(__FILE__).'/../appinfo/app.php' ); - -use OCA\Encryption; - -// This has to go here because otherwise session errors arise, and the private -// encryption key needs to be saved in the session -\OC_User::login( 'admin', 'admin' ); - -/** - * @note It would be better to use Mockery here for mocking out the session - * handling process, and isolate calls to session class and data from the unit - * tests relating to them (stream etc.). However getting mockery to work and - * overload classes whilst also using the OC autoloader is difficult due to - * load order Pear errors. - */ - -class Test_Crypt extends \PHPUnit_Framework_TestCase { - - function setUp() { - - // set content for encrypting / decrypting in tests - $this->dataLong = file_get_contents( realpath( dirname(__FILE__).'/../lib/crypt.php' ) ); - $this->dataShort = 'hats'; - $this->dataUrl = realpath( dirname(__FILE__).'/../lib/crypt.php' ); - $this->legacyData = realpath( dirname(__FILE__).'/legacy-text.txt' ); - $this->legacyEncryptedData = realpath( dirname(__FILE__).'/legacy-encrypted-text.txt' ); - $this->randomKey = Encryption\Crypt::generateKey(); - - $keypair = Encryption\Crypt::createKeypair(); - $this->genPublicKey = $keypair['publicKey']; - $this->genPrivateKey = $keypair['privateKey']; - - $this->view = new \OC_FilesystemView( '/' ); - - \OC_User::setUserId( 'admin' ); - $this->userId = 'admin'; - $this->pass = 'admin'; - - } - - function tearDown() { - - } - - function testGenerateKey() { - - # TODO: use more accurate (larger) string length for test confirmation - - $key = Encryption\Crypt::generateKey(); - - $this->assertTrue( strlen( $key ) > 16 ); - - } - - function testGenerateIv() { - - $iv = Encryption\Crypt::generateIv(); - - $this->assertEquals( 16, strlen( $iv ) ); - - return $iv; - - } - - /** - * @depends testGenerateIv - */ - function testConcatIv( $iv ) { - - $catFile = Encryption\Crypt::concatIv( $this->dataLong, $iv ); - - // Fetch encryption metadata from end of file - $meta = substr( $catFile, -22 ); - - $identifier = substr( $meta, 0, 6); - - // Fetch IV from end of file - $foundIv = substr( $meta, 6 ); - - $this->assertEquals( '00iv00', $identifier ); - - $this->assertEquals( $iv, $foundIv ); - - // Remove IV and IV identifier text to expose encrypted content - $data = substr( $catFile, 0, -22 ); - - $this->assertEquals( $this->dataLong, $data ); - - return array( - 'iv' => $iv - , 'catfile' => $catFile - ); - - } - - /** - * @depends testConcatIv - */ - function testSplitIv( $testConcatIv ) { - - // Split catfile into components - $splitCatfile = Encryption\Crypt::splitIv( $testConcatIv['catfile'] ); - - // Check that original IV and split IV match - $this->assertEquals( $testConcatIv['iv'], $splitCatfile['iv'] ); - - // Check that original data and split data match - $this->assertEquals( $this->dataLong, $splitCatfile['encrypted'] ); - - } - - function testAddPadding() { - - $padded = Encryption\Crypt::addPadding( $this->dataLong ); - - $padding = substr( $padded, -2 ); - - $this->assertEquals( 'xx' , $padding ); - - return $padded; - - } - - /** - * @depends testAddPadding - */ - function testRemovePadding( $padded ) { - - $noPadding = Encryption\Crypt::RemovePadding( $padded ); - - $this->assertEquals( $this->dataLong, $noPadding ); - - } - - function testEncrypt() { - - $random = openssl_random_pseudo_bytes( 13 ); - - $iv = substr( base64_encode( $random ), 0, -4 ); // i.e. E5IG033j+mRNKrht - - $crypted = Encryption\Crypt::encrypt( $this->dataUrl, $iv, 'hat' ); - - $this->assertNotEquals( $this->dataUrl, $crypted ); - - } - - function testDecrypt() { - - $random = openssl_random_pseudo_bytes( 13 ); - - $iv = substr( base64_encode( $random ), 0, -4 ); // i.e. E5IG033j+mRNKrht - - $crypted = Encryption\Crypt::encrypt( $this->dataUrl, $iv, 'hat' ); - - $decrypt = Encryption\Crypt::decrypt( $crypted, $iv, 'hat' ); - - $this->assertEquals( $this->dataUrl, $decrypt ); - - } - - function testSymmetricEncryptFileContent() { - - # TODO: search in keyfile for actual content as IV will ensure this test always passes - - $crypted = Encryption\Crypt::symmetricEncryptFileContent( $this->dataShort, 'hat' ); - - $this->assertNotEquals( $this->dataShort, $crypted ); - - - $decrypt = Encryption\Crypt::symmetricDecryptFileContent( $crypted, 'hat' ); - - $this->assertEquals( $this->dataShort, $decrypt ); - - } - - // These aren't used for now -// function testSymmetricBlockEncryptShortFileContent() { -// -// $crypted = Encryption\Crypt::symmetricBlockEncryptFileContent( $this->dataShort, $this->randomKey ); -// -// $this->assertNotEquals( $this->dataShort, $crypted ); -// -// -// $decrypt = Encryption\Crypt::symmetricBlockDecryptFileContent( $crypted, $this->randomKey ); -// -// $this->assertEquals( $this->dataShort, $decrypt ); -// -// } -// -// function testSymmetricBlockEncryptLongFileContent() { -// -// $crypted = Encryption\Crypt::symmetricBlockEncryptFileContent( $this->dataLong, $this->randomKey ); -// -// $this->assertNotEquals( $this->dataLong, $crypted ); -// -// -// $decrypt = Encryption\Crypt::symmetricBlockDecryptFileContent( $crypted, $this->randomKey ); -// -// $this->assertEquals( $this->dataLong, $decrypt ); -// -// } - - function testSymmetricStreamEncryptShortFileContent() { - - $filename = 'tmp-'.time(); - - $cryptedFile = file_put_contents( 'crypt://' . $filename, $this->dataShort ); - - // Test that data was successfully written - $this->assertTrue( is_int( $cryptedFile ) ); - - - // Get file contents without using any wrapper to get it's actual contents on disk - $retreivedCryptedFile = $this->view->file_get_contents( $this->userId . '/files/' . $filename ); - - //echo "$retreivedCryptedFile = ".var_export($retreivedCryptedFile, 1); - - // Check that the file was encrypted before being written to disk - $this->assertNotEquals( $this->dataShort, $retreivedCryptedFile ); - - // Get private key - $encryptedPrivateKey = Encryption\Keymanager::getPrivateKey( $this->view, $this->userId ); - - $decryptedPrivateKey = Encryption\Crypt::symmetricDecryptFileContent( $encryptedPrivateKey, $this->pass ); - - - // Get keyfile - $encryptedKeyfile = Encryption\Keymanager::getFileKey( $this->view, $this->userId, $filename ); - - $decryptedKeyfile = Encryption\Crypt::keyDecrypt( $encryptedKeyfile, $decryptedPrivateKey ); - - - // Manually decrypt - $manualDecrypt = Encryption\Crypt::symmetricBlockDecryptFileContent( $retreivedCryptedFile, $decryptedKeyfile ); - - // Check that decrypted data matches - $this->assertEquals( $this->dataShort, $manualDecrypt ); - - } - - /** - * @brief Test that data that is written by the crypto stream wrapper - * @note Encrypted data is manually prepared and decrypted here to avoid dependency on success of stream_read - * @note If this test fails with truncate content, check that enough array slices are being rejoined to form $e, as the crypt.php file may have gotten longer and broken the manual - * reassembly of its data - */ - function testSymmetricStreamEncryptLongFileContent() { - - // Generate a a random filename - $filename = 'tmp-'.time(); - - // Save long data as encrypted file using stream wrapper - $cryptedFile = file_put_contents( 'crypt://' . $filename, $this->dataLong.$this->dataLong ); - - // Test that data was successfully written - $this->assertTrue( is_int( $cryptedFile ) ); - - // Get file contents without using any wrapper to get it's actual contents on disk - $retreivedCryptedFile = $this->view->file_get_contents( $this->userId . '/files/' . $filename ); - -// echo "\n\n\$retreivedCryptedFile = $retreivedCryptedFile\n\n"; - - // Check that the file was encrypted before being written to disk - $this->assertNotEquals( $this->dataLong.$this->dataLong, $retreivedCryptedFile ); - - // Manuallly split saved file into separate IVs and encrypted chunks - $r = preg_split('/(00iv00.{16,18})/', $retreivedCryptedFile, NULL, PREG_SPLIT_DELIM_CAPTURE); - - //print_r($r); - - // Join IVs and their respective data chunks - $e = array( $r[0].$r[1], $r[2].$r[3], $r[4].$r[5], $r[6].$r[7], $r[8].$r[9], $r[10].$r[11], $r[12].$r[13] );//.$r[11], $r[12].$r[13], $r[14] ); - - //print_r($e); - - - // Get private key - $encryptedPrivateKey = Encryption\Keymanager::getPrivateKey( $this->view, $this->userId ); - - $decryptedPrivateKey = Encryption\Crypt::symmetricDecryptFileContent( $encryptedPrivateKey, $this->pass ); - - - // Get keyfile - $encryptedKeyfile = Encryption\Keymanager::getFileKey( $this->view, $this->userId, $filename ); - - $decryptedKeyfile = Encryption\Crypt::keyDecrypt( $encryptedKeyfile, $decryptedPrivateKey ); - - - // Set var for reassembling decrypted content - $decrypt = ''; - - // Manually decrypt chunk - foreach ($e as $e) { - -// echo "\n\$e = $e"; - - $chunkDecrypt = Encryption\Crypt::symmetricDecryptFileContent( $e, $decryptedKeyfile ); - - // Assemble decrypted chunks - $decrypt .= $chunkDecrypt; - -// echo "\n\$chunkDecrypt = $chunkDecrypt"; - - } - -// echo "\n\$decrypt = $decrypt"; - - $this->assertEquals( $this->dataLong.$this->dataLong, $decrypt ); - - // Teardown - - $this->view->unlink( $filename ); - - Encryption\Keymanager::deleteFileKey( $filename ); - - } - - /** - * @brief Test that data that is read by the crypto stream wrapper - */ - function testSymmetricStreamDecryptShortFileContent() { - - $filename = 'tmp-'.time(); - - // Save long data as encrypted file using stream wrapper - $cryptedFile = file_put_contents( 'crypt://' . $filename, $this->dataShort ); - - // Test that data was successfully written - $this->assertTrue( is_int( $cryptedFile ) ); - - - // Get file contents without using any wrapper to get it's actual contents on disk - $retreivedCryptedFile = $this->view->file_get_contents( $this->userId . '/files/' . $filename ); - - $decrypt = file_get_contents( 'crypt://' . $filename ); - - $this->assertEquals( $this->dataShort, $decrypt ); - - } - - function testSymmetricStreamDecryptLongFileContent() { - - $filename = 'tmp-'.time(); - - // Save long data as encrypted file using stream wrapper - $cryptedFile = file_put_contents( 'crypt://' . $filename, $this->dataLong ); - - // Test that data was successfully written - $this->assertTrue( is_int( $cryptedFile ) ); - - - // Get file contents without using any wrapper to get it's actual contents on disk - $retreivedCryptedFile = $this->view->file_get_contents( $this->userId . '/files/' . $filename ); - - $decrypt = file_get_contents( 'crypt://' . $filename ); - - $this->assertEquals( $this->dataLong, $decrypt ); - - } - - // Is this test still necessary? -// function testSymmetricBlockStreamDecryptFileContent() { -// -// \OC_User::setUserId( 'admin' ); -// -// // Disable encryption proxy to prevent unwanted en/decryption -// \OC_FileProxy::$enabled = false; -// -// $cryptedFile = file_put_contents( 'crypt://' . '/blockEncrypt', $this->dataUrl ); -// -// // Disable encryption proxy to prevent unwanted en/decryption -// \OC_FileProxy::$enabled = false; -// -// echo "\n\n\$cryptedFile = " . $this->view->file_get_contents( '/blockEncrypt' ); -// -// $retreivedCryptedFile = file_get_contents( 'crypt://' . '/blockEncrypt' ); -// -// $this->assertEquals( $this->dataUrl, $retreivedCryptedFile ); -// -// \OC_FileProxy::$enabled = false; -// -// } - - function testSymmetricEncryptFileContentKeyfile() { - - # TODO: search in keyfile for actual content as IV will ensure this test always passes - - $crypted = Encryption\Crypt::symmetricEncryptFileContentKeyfile( $this->dataUrl ); - - $this->assertNotEquals( $this->dataUrl, $crypted['encrypted'] ); - - - $decrypt = Encryption\Crypt::symmetricDecryptFileContent( $crypted['encrypted'], $crypted['key'] ); - - $this->assertEquals( $this->dataUrl, $decrypt ); - - } - - function testIsEncryptedContent() { - - $this->assertFalse( Encryption\Crypt::isEncryptedContent( $this->dataUrl ) ); - - $this->assertFalse( Encryption\Crypt::isEncryptedContent( $this->legacyEncryptedData ) ); - - $keyfileContent = Encryption\Crypt::symmetricEncryptFileContent( $this->dataUrl, 'hat' ); - - $this->assertTrue( Encryption\Crypt::isEncryptedContent( $keyfileContent ) ); - - } - - function testMultiKeyEncrypt() { - - # TODO: search in keyfile for actual content as IV will ensure this test always passes - - $pair1 = Encryption\Crypt::createKeypair(); - - $this->assertEquals( 2, count( $pair1 ) ); - - $this->assertTrue( strlen( $pair1['publicKey'] ) > 1 ); - - $this->assertTrue( strlen( $pair1['privateKey'] ) > 1 ); - - - $crypted = Encryption\Crypt::multiKeyEncrypt( $this->dataUrl, array( $pair1['publicKey'] ) ); - - $this->assertNotEquals( $this->dataUrl, $crypted['encrypted'] ); - - - $decrypt = Encryption\Crypt::multiKeyDecrypt( $crypted['encrypted'], $crypted['keys'][0], $pair1['privateKey'] ); - - $this->assertEquals( $this->dataUrl, $decrypt ); - - } - - function testKeyEncrypt() { - - // Generate keypair - $pair1 = Encryption\Crypt::createKeypair(); - - // Encrypt data - $crypted = Encryption\Crypt::keyEncrypt( $this->dataUrl, $pair1['publicKey'] ); - - $this->assertNotEquals( $this->dataUrl, $crypted ); - - // Decrypt data - $decrypt = Encryption\Crypt::keyDecrypt( $crypted, $pair1['privateKey'] ); - - $this->assertEquals( $this->dataUrl, $decrypt ); - - } - - // What is the point of this test? It doesn't use keyEncryptKeyfile() - function testKeyEncryptKeyfile() { - - # TODO: Don't repeat encryption from previous tests, use PHPUnit test interdependency instead - - // Generate keypair - $pair1 = Encryption\Crypt::createKeypair(); - - // Encrypt plain data, generate keyfile & encrypted file - $cryptedData = Encryption\Crypt::symmetricEncryptFileContentKeyfile( $this->dataUrl ); - - // Encrypt keyfile - $cryptedKey = Encryption\Crypt::keyEncrypt( $cryptedData['key'], $pair1['publicKey'] ); - - // Decrypt keyfile - $decryptKey = Encryption\Crypt::keyDecrypt( $cryptedKey, $pair1['privateKey'] ); - - // Decrypt encrypted file - $decryptData = Encryption\Crypt::symmetricDecryptFileContent( $cryptedData['encrypted'], $decryptKey ); - - $this->assertEquals( $this->dataUrl, $decryptData ); - - } - - /** - * @brief test functionality of keyEncryptKeyfile() and - * keyDecryptKeyfile() - */ - function testKeyDecryptKeyfile() { - - $encrypted = Encryption\Crypt::keyEncryptKeyfile( $this->dataShort, $this->genPublicKey ); - - $this->assertNotEquals( $encrypted['data'], $this->dataShort ); - - $decrypted = Encryption\Crypt::keyDecryptKeyfile( $encrypted['data'], $encrypted['key'], $this->genPrivateKey ); - - $this->assertEquals( $decrypted, $this->dataShort ); - - } - - - /** - * @brief test encryption using legacy blowfish method - */ - function testLegacyEncryptShort() { - - $crypted = Encryption\Crypt::legacyEncrypt( $this->dataShort, $this->pass ); - - $this->assertNotEquals( $this->dataShort, $crypted ); - - # TODO: search inencrypted text for actual content to ensure it - # genuine transformation - - return $crypted; - - } - - /** - * @brief test decryption using legacy blowfish method - * @depends testLegacyEncryptShort - */ - function testLegacyDecryptShort( $crypted ) { - - $decrypted = Encryption\Crypt::legacyDecrypt( $crypted, $this->pass ); - - $this->assertEquals( $this->dataShort, $decrypted ); - - } - - /** - * @brief test encryption using legacy blowfish method - */ - function testLegacyEncryptLong() { - - $crypted = Encryption\Crypt::legacyEncrypt( $this->dataLong, $this->pass ); - - $this->assertNotEquals( $this->dataLong, $crypted ); - - # TODO: search inencrypted text for actual content to ensure it - # genuine transformation - - return $crypted; - - } - - /** - * @brief test decryption using legacy blowfish method - * @depends testLegacyEncryptLong - */ - function testLegacyDecryptLong( $crypted ) { - - $decrypted = Encryption\Crypt::legacyDecrypt( $crypted, $this->pass ); - - $this->assertEquals( $this->dataLong, $decrypted ); - - } - - /** - * @brief test generation of legacy encryption key - * @depends testLegacyDecryptShort - */ - function testLegacyCreateKey() { - - // Create encrypted key - $encKey = Encryption\Crypt::legacyCreateKey( $this->pass ); - - // Decrypt key - $key = Encryption\Crypt::legacyDecrypt( $encKey, $this->pass ); - - $this->assertTrue( is_numeric( $key ) ); - - // Check that key is correct length - $this->assertEquals( 20, strlen( $key ) ); - - } - - /** - * @brief test decryption using legacy blowfish method - * @depends testLegacyEncryptLong - */ - function testLegacyKeyRecryptKeyfileEncrypt( $crypted ) { - - $recrypted = Encryption\Crypt::LegacyKeyRecryptKeyfile( $crypted, $this->pass, $this->genPublicKey, $this->pass ); - - $this->assertNotEquals( $this->dataLong, $recrypted['data'] ); - - return $recrypted; - - # TODO: search inencrypted text for actual content to ensure it - # genuine transformation - - } - -// function testEncryption(){ -// -// $key=uniqid(); -// $file=OC::$SERVERROOT.'/3rdparty/MDB2.php'; -// $source=file_get_contents($file); //nice large text file -// $encrypted=OC_Encryption\Crypt::encrypt($source,$key); -// $decrypted=OC_Encryption\Crypt::decrypt($encrypted,$key); -// $decrypted=rtrim($decrypted, "\0"); -// $this->assertNotEquals($encrypted,$source); -// $this->assertEqual($decrypted,$source); -// -// $chunk=substr($source,0,8192); -// $encrypted=OC_Encryption\Crypt::encrypt($chunk,$key); -// $this->assertEqual(strlen($chunk),strlen($encrypted)); -// $decrypted=OC_Encryption\Crypt::decrypt($encrypted,$key); -// $decrypted=rtrim($decrypted, "\0"); -// $this->assertEqual($decrypted,$chunk); -// -// $encrypted=OC_Encryption\Crypt::blockEncrypt($source,$key); -// $decrypted=OC_Encryption\Crypt::blockDecrypt($encrypted,$key); -// $this->assertNotEquals($encrypted,$source); -// $this->assertEqual($decrypted,$source); -// -// $tmpFileEncrypted=OCP\Files::tmpFile(); -// OC_Encryption\Crypt::encryptfile($file,$tmpFileEncrypted,$key); -// $encrypted=file_get_contents($tmpFileEncrypted); -// $decrypted=OC_Encryption\Crypt::blockDecrypt($encrypted,$key); -// $this->assertNotEquals($encrypted,$source); -// $this->assertEqual($decrypted,$source); -// -// $tmpFileDecrypted=OCP\Files::tmpFile(); -// OC_Encryption\Crypt::decryptfile($tmpFileEncrypted,$tmpFileDecrypted,$key); -// $decrypted=file_get_contents($tmpFileDecrypted); -// $this->assertEqual($decrypted,$source); -// -// $file=OC::$SERVERROOT.'/core/img/weather-clear.png'; -// $source=file_get_contents($file); //binary file -// $encrypted=OC_Encryption\Crypt::encrypt($source,$key); -// $decrypted=OC_Encryption\Crypt::decrypt($encrypted,$key); -// $decrypted=rtrim($decrypted, "\0"); -// $this->assertEqual($decrypted,$source); -// -// $encrypted=OC_Encryption\Crypt::blockEncrypt($source,$key); -// $decrypted=OC_Encryption\Crypt::blockDecrypt($encrypted,$key); -// $this->assertEqual($decrypted,$source); -// -// } -// -// function testBinary(){ -// $key=uniqid(); -// -// $file=__DIR__.'/binary'; -// $source=file_get_contents($file); //binary file -// $encrypted=OC_Encryption\Crypt::encrypt($source,$key); -// $decrypted=OC_Encryption\Crypt::decrypt($encrypted,$key); -// -// $decrypted=rtrim($decrypted, "\0"); -// $this->assertEqual($decrypted,$source); -// -// $encrypted=OC_Encryption\Crypt::blockEncrypt($source,$key); -// $decrypted=OC_Encryption\Crypt::blockDecrypt($encrypted,$key,strlen($source)); -// $this->assertEqual($decrypted,$source); -// } - -} diff --git a/apps/files_encryption/tests/keymanager.php b/apps/files_encryption/tests/keymanager.php deleted file mode 100644 index e31bbe2ab27..00000000000 --- a/apps/files_encryption/tests/keymanager.php +++ /dev/null @@ -1,116 +0,0 @@ - - * This file is licensed under the Affero General Public License version 3 or - * later. - * See the COPYING-README file. - */ - -//require_once "PHPUnit/Framework/TestCase.php"; -require_once realpath( dirname(__FILE__).'/../../../lib/base.php' ); -require_once realpath( dirname(__FILE__).'/../lib/crypt.php' ); -require_once realpath( dirname(__FILE__).'/../lib/keymanager.php' ); -require_once realpath( dirname(__FILE__).'/../lib/proxy.php' ); -require_once realpath( dirname(__FILE__).'/../lib/stream.php' ); -require_once realpath( dirname(__FILE__).'/../lib/util.php' ); -require_once realpath( dirname(__FILE__).'/../appinfo/app.php' ); - -use OCA\Encryption; - -class Test_Keymanager extends \PHPUnit_Framework_TestCase { - - function setUp() { - - // Set data for use in tests - $this->data = realpath( dirname(__FILE__).'/../lib/crypt.php' ); - $this->user = 'admin'; - $this->passphrase = 'admin'; - $this->filePath = '/testing'; - $this->view = new \OC_FilesystemView( '' ); - - // Disable encryption proxy to prevent recursive calls - \OC_FileProxy::$enabled = false; - - // Notify system which iser is logged in etc. - \OC_User::setUserId( 'admin' ); - - } - - function tearDown(){ - - \OC_FileProxy::$enabled = true; - - } - - function testGetPrivateKey() { - - $key = Encryption\Keymanager::getPrivateKey( $this->view, $this->user ); - - - // Will this length vary? Perhaps we should use a range instead - $this->assertEquals( 2296, strlen( $key ) ); - - } - - function testGetPublicKey() { - - $key = Encryption\Keymanager::getPublicKey( $this->view, $this->user ); - - $this->assertEquals( 451, strlen( $key ) ); - - $this->assertEquals( '-----BEGIN PUBLIC KEY-----', substr( $key, 0, 26 ) ); - } - - function testSetFileKey() { - - # NOTE: This cannot be tested until we are able to break out - # of the FileSystemView data directory root - -// $key = Crypt::symmetricEncryptFileContentKeyfile( $this->data, 'hat' ); -// -// $tmpPath = sys_get_temp_dir(). '/' . 'testSetFileKey'; -// -// $view = new \OC_FilesystemView( '/tmp/' ); -// -// //$view = new \OC_FilesystemView( '/' . $this->user . '/files_encryption/keyfiles' ); -// -// Encryption\Keymanager::setFileKey( $tmpPath, $key['key'], $view ); - - } - - function testGetPrivateKey_decrypt() { - - $key = Encryption\Keymanager::getPrivateKey( $this->view, $this->user ); - - # TODO: replace call to Crypt with a mock object? - $decrypted = Encryption\Crypt::symmetricDecryptFileContent( $key, $this->passphrase ); - - $this->assertEquals( 1704, strlen( $decrypted ) ); - - $this->assertEquals( '-----BEGIN PRIVATE KEY-----', substr( $decrypted, 0, 27 ) ); - - } - - function testGetUserKeys() { - - $keys = Encryption\Keymanager::getUserKeys( $this->view, $this->user ); - - $this->assertEquals( 451, strlen( $keys['publicKey'] ) ); - $this->assertEquals( '-----BEGIN PUBLIC KEY-----', substr( $keys['publicKey'], 0, 26 ) ); - $this->assertEquals( 2296, strlen( $keys['privateKey'] ) ); - - } - - function testGetPublicKeys() { - - # TODO: write me - - } - - function testGetFileKey() { - -// Encryption\Keymanager::getFileKey( $this->view, $this->user, $this->filePath ); - - } - -} diff --git a/apps/files_encryption/tests/legacy-encrypted-text.txt b/apps/files_encryption/tests/legacy-encrypted-text.txt deleted file mode 100644 index cb5bf50550d..00000000000 Binary files a/apps/files_encryption/tests/legacy-encrypted-text.txt and /dev/null differ diff --git a/apps/files_encryption/tests/proxy.php b/apps/files_encryption/tests/proxy.php deleted file mode 100644 index 87151234e0e..00000000000 --- a/apps/files_encryption/tests/proxy.php +++ /dev/null @@ -1,224 +0,0 @@ -, - * and Robin Appelman - * This file is licensed under the Affero General Public License version 3 or - * later. - * See the COPYING-README file. - */ - -require_once "PHPUnit/Framework/TestCase.php"; -require_once realpath( dirname(__FILE__).'/../../../lib/base.php' ); -require_once realpath( dirname(__FILE__).'/../../../3rdparty/mockery/Mockery.php' ); -require_once realpath( dirname(__FILE__).'/../../../3rdparty/mockery/Mockery/Generator.php' ); -require_once realpath( dirname(__FILE__).'/../../../3rdparty/mockery/Mockery/MockInterface.php' ); -require_once realpath( dirname(__FILE__).'/../../../3rdparty/mockery/Mockery/Mock.php' ); -require_once realpath( dirname(__FILE__).'/../../../3rdparty/mockery/Mockery/Container.php' ); -require_once realpath( dirname(__FILE__).'/../../../3rdparty/mockery/Mockery/Configuration.php' ); -require_once realpath( dirname(__FILE__).'/../../../3rdparty/mockery/Mockery/CompositeExpectation.php' ); -require_once realpath( dirname(__FILE__).'/../../../3rdparty/mockery/Mockery/ExpectationDirector.php' ); -require_once realpath( dirname(__FILE__).'/../../../3rdparty/mockery/Mockery/Expectation.php' ); -require_once realpath( dirname(__FILE__).'/../../../3rdparty/mockery/Mockery/Exception.php' ); -require_once realpath( dirname(__FILE__).'/../../../3rdparty/mockery/Mockery/CountValidator/CountValidatorAbstract.php' ); -require_once realpath( dirname(__FILE__).'/../../../3rdparty/mockery/Mockery/CountValidator/Exception.php' ); -require_once realpath( dirname(__FILE__).'/../../../3rdparty/mockery/Mockery/CountValidator/Exact.php' ); - -use \Mockery as m; -use OCA\Encryption; - -class Test_Util extends \PHPUnit_Framework_TestCase { - - public function setUp() { - - $this->proxy = new Encryption\Proxy(); - - $this->tmpFileName = "tmpFile-".time(); - - $this->privateKey = file_get_contents( realpath( dirname(__FILE__).'/data/admin.public.key' ) ); - $this->publicKey = file_get_contents( realpath( dirname(__FILE__).'/data/admin.private.key' ) ); - $this->encDataShort = file_get_contents( realpath( dirname(__FILE__).'/data/yoga-manchester-enc' ) ); - $this->encDataShortKey = file_get_contents( realpath( dirname(__FILE__).'/data/yoga-manchester.key' ) ); - - $this->dataShort = file_get_contents( realpath( dirname(__FILE__).'/data/yoga-manchester' ) ); - $this->dataLong = file_get_contents( realpath( dirname(__FILE__).'/../lib/crypt.php' ) ); - $this->longDataPath = realpath( dirname(__FILE__).'/../lib/crypt.php' ); - - $this->data1 = file_get_contents( realpath( dirname(__FILE__).'/../../../data/admin/files/enc-test.txt' ) ); - - \OC_FileProxy::$enabled = false; - $this->Encdata1 = file_get_contents( realpath( dirname(__FILE__).'/../../../data/admin/files/enc-test.txt' ) ); - \OC_FileProxy::$enabled = true; - - $this->userId = 'admin'; - $this->pass = 'admin'; - - $this->session = new Encryption\Session(); - -$this->session->setPrivateKey( -'-----BEGIN PRIVATE KEY----- -MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDiH3EA4EpFA7Fx -s2dyyfL5jwXeYXrTqQJ6DqKgGn8VsbT3eu8R9KzM2XitVwZe8c8L52DvJ06o5vg0 -GqPYxilFdOFJe/ggac5Tq8UmJiZS4EqYEMwxBIfIyWTxeGV06/0HOwnVAkqHMcBz -64qldtgi5O8kZMEM2/gKBgU0kMLJzM+8oEWhL1+gsUWQhxd8cKLXypS6iWgqFJrz -f/X0hJsJR+gyYxNpahtnjzd/LxLAETrOMsl2tue+BAxmjbAM0aG0NEM0div+b59s -2uz/iWbxImp5pOdYVKcVW89D4XBMyGegR40trV2VwiuX1blKCfdjMsJhiaL9pymp -ug1wzyQFAgMBAAECggEAK6c+PZkPPXuVCgpEcliiW6NM0r2m5K3AGKgypQ34csu3 -z/8foCvIIFPrhCtEw5eTDQ1CHWlNOjY8vHJYJ0U6Onpx86nHIRrMBkMm8FJ1G5LJ -U8oKYXwqaozWu/cuPwA//OFc6I5krOzh5n8WaRMkbrgbor8AtebRX74By0AXGrXe -cswJI7zR96oFn4Dm7Pgvpg5Zhk1vFJ+w6QtH+4DDJ6PBvlZsRkGxYBLGVd/3qhAI -sBAyjFlSzuP4eCRhHOhHC/e4gmAH9evFVXB88jFyRZm3K+jQ5W5CwrVRBCV2lph6 -2B6P7CBJN+IjGKMhy+75y13UvvKPv9IwH8Fzl2x1gQKBgQD8qQOr7a6KhSj16wQE -jim2xqt9gQ2jH5No405NrKs/PFQQZnzD4YseQsiK//NUjOJiUhaT+L5jhIpzINHt -RJpt3bGkEZmLyjdjgTpB3GwZdXa28DNK9VdXZ19qIl/ZH0qAjKmJCRahUDASMnVi -M4Pkk9yx9ZIKkri4TcuMWqc0DQKBgQDlHKBTITZq/arYPD6Nl3NsoOdqVRqJrGay -0TjXAVbBXe46+z5lnMsqwXb79nx14hdmSEsZULrw/3f+MnQbdjMTYLFP24visZg9 -MN8vAiALiiiR1a+Crz+DTA1Q8sGOMVCMqMDmD7QBys3ZuWxuapm0txAiIYUtsjJZ -XN76T4nZ2QKBgQCHaT3igzwsWTmesxowJtEMeGWomeXpKx8h89EfqA8PkRGsyIDN -qq+YxEoe1RZgljEuaLhZDdNcGsjo8woPk9kAUPTH7fbRCMuutK+4ZJ469s1tNkcH -QX5SBcEJbOrZvv967ehe3VQXmJZq6kgnHVzuwKBjcC2ZJRGDFY6l5l/+cQKBgCqh -+Adf/8NK7paMJ0urqfPFwSodKfICXZ3apswDWMRkmSbqh4La+Uc8dsqN5Dz/VEFZ -JHhSeGbN8uMfOlG93eU2MehdPxtw1pZUWMNjjtj23XO9ooob2CKzbSrp8TBnZsi1 -widNNr66oTFpeo7VUUK6acsgF6sYJJxSVr+XO1yJAoGAEhvitq8shNKcEY0xCipS -k1kbgyS7KKB7opVxI5+ChEqyUDijS3Y9FZixrRIWE6i2uGu86UG+v2lbKvSbM4Qm -xvbOcX9OVMnlRb7n8woOP10UMY+ZE2x+YEUXQTLtPYq7F66e1OfxltstMxLQA+3d -Y1d5piFV8PXK3Fg2F+Cj5qg= ------END PRIVATE KEY----- -' -, $this->userId -); - - \OC_User::setUserId( $this->userId ); - - } - - public function testpreFile_get_contents() { - - // This won't work for now because mocking of the static keymanager class isn't working :( - -// $mock = m::mock( 'alias:OCA\Encryption\Keymanager' ); -// -// $mock->shouldReceive( 'getFileKey' )->times(2)->andReturn( $this->encDataShort ); -// -// $encrypted = $this->proxy->postFile_get_contents( 'data/'.$this->tmpFileName, $this->encDataShortKey ); -// -// $this->assertNotEquals( $this->dataShort, $encrypted ); -// -// var_dump($encrypted); - - $decrypted = $this->proxy->postFile_get_contents( 'data/admin/files/enc-test.txt', $this->data1 ); - - var_dump($decrypted); - - } - -} - -// class Test_CryptProxy extends UnitTestCase { -// private $oldConfig; -// private $oldKey; -// -// public function setUp(){ -// $user=OC_User::getUser(); -// -// $this->oldConfig=OCP\Config::getAppValue('files_encryption','enable_encryption','true'); -// OCP\Config::setAppValue('files_encryption','enable_encryption','true'); -// $this->oldKey=isset($_SESSION['privateKey'])?$_SESSION['privateKey']:null; -// -// -// //set testing key -// $_SESSION['privateKey']=md5(time()); -// -// //clear all proxies and hooks so we can do clean testing -// OC_FileProxy::clearProxies(); -// OC_Hook::clear('OC_Filesystem'); -// -// //enable only the encryption hook -// OC_FileProxy::register(new OC_FileProxy_Encryption()); -// -// //set up temporary storage -// OC_Filesystem::clearMounts(); -// OC_Filesystem::mount('OC_Filestorage_Temporary',array(),'/'); -// -// OC_Filesystem::init('/'.$user.'/files'); -// -// //set up the users home folder in the temp storage -// $rootView=new OC_FilesystemView(''); -// $rootView->mkdir('/'.$user); -// $rootView->mkdir('/'.$user.'/files'); -// } -// -// public function tearDown(){ -// OCP\Config::setAppValue('files_encryption','enable_encryption',$this->oldConfig); -// if(!is_null($this->oldKey)){ -// $_SESSION['privateKey']=$this->oldKey; -// } -// } -// -// public function testSimple(){ -// $file=OC::$SERVERROOT.'/3rdparty/MDB2.php'; -// $original=file_get_contents($file); -// -// OC_Filesystem::file_put_contents('/file',$original); -// -// OC_FileProxy::$enabled=false; -// $stored=OC_Filesystem::file_get_contents('/file'); -// OC_FileProxy::$enabled=true; -// -// $fromFile=OC_Filesystem::file_get_contents('/file'); -// $this->assertNotEqual($original,$stored); -// $this->assertEqual(strlen($original),strlen($fromFile)); -// $this->assertEqual($original,$fromFile); -// -// } -// -// public function testView(){ -// $file=OC::$SERVERROOT.'/3rdparty/MDB2.php'; -// $original=file_get_contents($file); -// -// $rootView=new OC_FilesystemView(''); -// $view=new OC_FilesystemView('/'.OC_User::getUser()); -// $userDir='/'.OC_User::getUser().'/files'; -// -// $rootView->file_put_contents($userDir.'/file',$original); -// -// OC_FileProxy::$enabled=false; -// $stored=$rootView->file_get_contents($userDir.'/file'); -// OC_FileProxy::$enabled=true; -// -// $this->assertNotEqual($original,$stored); -// $fromFile=$rootView->file_get_contents($userDir.'/file'); -// $this->assertEqual($original,$fromFile); -// -// $fromFile=$view->file_get_contents('files/file'); -// $this->assertEqual($original,$fromFile); -// } -// -// public function testBinary(){ -// $file=__DIR__.'/binary'; -// $original=file_get_contents($file); -// -// OC_Filesystem::file_put_contents('/file',$original); -// -// OC_FileProxy::$enabled=false; -// $stored=OC_Filesystem::file_get_contents('/file'); -// OC_FileProxy::$enabled=true; -// -// $fromFile=OC_Filesystem::file_get_contents('/file'); -// $this->assertNotEqual($original,$stored); -// $this->assertEqual(strlen($original),strlen($fromFile)); -// $this->assertEqual($original,$fromFile); -// -// $file=__DIR__.'/zeros'; -// $original=file_get_contents($file); -// -// OC_Filesystem::file_put_contents('/file',$original); -// -// OC_FileProxy::$enabled=false; -// $stored=OC_Filesystem::file_get_contents('/file'); -// OC_FileProxy::$enabled=true; -// -// $fromFile=OC_Filesystem::file_get_contents('/file'); -// $this->assertNotEqual($original,$stored); -// $this->assertEqual(strlen($original),strlen($fromFile)); -// } -// } diff --git a/apps/files_encryption/tests/stream.php b/apps/files_encryption/tests/stream.php deleted file mode 100644 index 52e85fe4850..00000000000 --- a/apps/files_encryption/tests/stream.php +++ /dev/null @@ -1,227 +0,0 @@ - - * This file is licensed under the Affero General Public License version 3 or - * later. - * See the COPYING-README file. - */ - -namespace OCA\Encryption; - -require_once "PHPUnit/Framework/TestCase.php"; -require_once realpath( dirname(__FILE__).'/../../../lib/base.php' ); - -class Test_Stream extends \PHPUnit_Framework_TestCase { - - function setUp() { - - $this->empty = ''; - - $this->stream = new Stream(); - - $this->dataLong = file_get_contents( realpath( dirname(__FILE__).'/../lib/crypt.php' ) ); - $this->dataShort = 'hats'; - - $this->emptyTmpFilePath = \OCP\Files::tmpFile(); - - $this->dataTmpFilePath = \OCP\Files::tmpFile(); - - file_put_contents( $this->dataTmpFilePath, "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a diam lectus. Sed sit amet ipsum mauris. Maecenas congue ligula ac quam viverra nec consectetur ante hendrerit. Donec et mollis dolor. Praesent et diam eget libero egestas mattis sit amet vitae augue. Nam tincidunt congue enim, ut porta lorem lacinia consectetur. Donec ut libero sed arcu vehicula ultricies a non tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean ut gravida lorem. Ut turpis felis, pulvinar a semper sed, adipiscing id dolor. Pellentesque auctor nisi id magna consequat sagittis. Curabitur dapibus enim sit amet elit pharetra tincidunt feugiat nisl imperdiet. Ut convallis libero in urna ultrices accumsan. Donec sed odio eros. Donec viverra mi quis quam pulvinar at malesuada arcu rhoncus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. In rutrum accumsan ultricies. Mauris vitae nisi at sem facilisis semper ac in est." ); - - } - - function testStreamOpen() { - - $stream1 = new Stream(); - - $handle1 = $stream1->stream_open( $this->emptyTmpFilePath, 'wb', array(), $this->empty ); - - // Test that resource was returned successfully - $this->assertTrue( $handle1 ); - - // Test that file has correct size - $this->assertEquals( 0, $stream1->size ); - - // Test that path is correct - $this->assertEquals( $this->emptyTmpFilePath, $stream1->rawPath ); - - $stream2 = new Stream(); - - $handle2 = $stream2->stream_open( 'crypt://' . $this->emptyTmpFilePath, 'wb', array(), $this->empty ); - - // Test that protocol identifier is removed from path - $this->assertEquals( $this->emptyTmpFilePath, $stream2->rawPath ); - - // "Stat failed error" prevents this test from executing -// $stream3 = new Stream(); -// -// $handle3 = $stream3->stream_open( $this->dataTmpFilePath, 'r', array(), $this->empty ); -// -// $this->assertEquals( 0, $stream3->size ); - - } - - function testStreamWrite() { - - $stream1 = new Stream(); - - $handle1 = $stream1->stream_open( $this->emptyTmpFilePath, 'r+b', array(), $this->empty ); - - # what about the keymanager? there is no key for the newly created temporary file! - - $stream1->stream_write( $this->dataShort ); - - } - -// function getStream( $id, $mode, $size ) { -// -// if ( $id === '' ) { -// -// $id = uniqid(); -// } -// -// -// if ( !isset( $this->tmpFiles[$id] ) ) { -// -// // If tempfile with given name does not already exist, create it -// -// $file = OCP\Files::tmpFile(); -// -// $this->tmpFiles[$id] = $file; -// -// } else { -// -// $file = $this->tmpFiles[$id]; -// -// } -// -// $stream = fopen( $file, $mode ); -// -// Stream::$sourceStreams[$id] = array( 'path' => 'dummy' . $id, 'stream' => $stream, 'size' => $size ); -// -// return fopen( 'crypt://streams/'.$id, $mode ); -// -// } -// -// function testStream( ){ -// -// $stream = $this->getStream( 'test1', 'w', strlen( 'foobar' ) ); -// -// fwrite( $stream, 'foobar' ); -// -// fclose( $stream ); -// -// -// $stream = $this->getStream( 'test1', 'r', strlen( 'foobar' ) ); -// -// $data = fread( $stream, 6 ); -// -// fclose( $stream ); -// -// $this->assertEqual( 'foobar', $data ); -// -// -// $file = OC::$SERVERROOT.'/3rdparty/MDB2.php'; -// -// $source = fopen( $file, 'r' ); -// -// $target = $this->getStream( 'test2', 'w', 0 ); -// -// OCP\Files::streamCopy( $source, $target ); -// -// fclose( $target ); -// -// fclose( $source ); -// -// -// $stream = $this->getStream( 'test2', 'r', filesize( $file ) ); -// -// $data = stream_get_contents( $stream ); -// -// $original = file_get_contents( $file ); -// -// $this->assertEqual( strlen( $original ), strlen( $data ) ); -// -// $this->assertEqual( $original, $data ); -// -// } - -} - -// class Test_CryptStream extends UnitTestCase { -// private $tmpFiles=array(); -// -// function testStream(){ -// $stream=$this->getStream('test1','w',strlen('foobar')); -// fwrite($stream,'foobar'); -// fclose($stream); -// -// $stream=$this->getStream('test1','r',strlen('foobar')); -// $data=fread($stream,6); -// fclose($stream); -// $this->assertEqual('foobar',$data); -// -// $file=OC::$SERVERROOT.'/3rdparty/MDB2.php'; -// $source=fopen($file,'r'); -// $target=$this->getStream('test2','w',0); -// OCP\Files::streamCopy($source,$target); -// fclose($target); -// fclose($source); -// -// $stream=$this->getStream('test2','r',filesize($file)); -// $data=stream_get_contents($stream); -// $original=file_get_contents($file); -// $this->assertEqual(strlen($original),strlen($data)); -// $this->assertEqual($original,$data); -// } -// -// /** -// * get a cryptstream to a temporary file -// * @param string $id -// * @param string $mode -// * @param int size -// * @return resource -// */ -// function getStream($id,$mode,$size){ -// if($id===''){ -// $id=uniqid(); -// } -// if(!isset($this->tmpFiles[$id])){ -// $file=OCP\Files::tmpFile(); -// $this->tmpFiles[$id]=$file; -// }else{ -// $file=$this->tmpFiles[$id]; -// } -// $stream=fopen($file,$mode); -// OC_CryptStream::$sourceStreams[$id]=array('path'=>'dummy'.$id,'stream'=>$stream,'size'=>$size); -// return fopen('crypt://streams/'.$id,$mode); -// } -// -// function testBinary(){ -// $file=__DIR__.'/binary'; -// $source=file_get_contents($file); -// -// $stream=$this->getStream('test','w',strlen($source)); -// fwrite($stream,$source); -// fclose($stream); -// -// $stream=$this->getStream('test','r',strlen($source)); -// $data=stream_get_contents($stream); -// fclose($stream); -// $this->assertEqual(strlen($data),strlen($source)); -// $this->assertEqual($source,$data); -// -// $file=__DIR__.'/zeros'; -// $source=file_get_contents($file); -// -// $stream=$this->getStream('test2','w',strlen($source)); -// fwrite($stream,$source); -// fclose($stream); -// -// $stream=$this->getStream('test2','r',strlen($source)); -// $data=stream_get_contents($stream); -// fclose($stream); -// $this->assertEqual(strlen($data),strlen($source)); -// $this->assertEqual($source,$data); -// } -// } diff --git a/apps/files_encryption/tests/util.php b/apps/files_encryption/tests/util.php deleted file mode 100755 index 30ec26d3aaa..00000000000 --- a/apps/files_encryption/tests/util.php +++ /dev/null @@ -1,208 +0,0 @@ - - * This file is licensed under the Affero General Public License version 3 or - * later. - * See the COPYING-README file. - */ - -//require_once "PHPUnit/Framework/TestCase.php"; -require_once realpath( dirname(__FILE__).'/../../../lib/base.php' ); -require_once realpath( dirname(__FILE__).'/../lib/crypt.php' ); -require_once realpath( dirname(__FILE__).'/../lib/keymanager.php' ); -require_once realpath( dirname(__FILE__).'/../lib/proxy.php' ); -require_once realpath( dirname(__FILE__).'/../lib/stream.php' ); -require_once realpath( dirname(__FILE__).'/../lib/util.php' ); -require_once realpath( dirname(__FILE__).'/../appinfo/app.php' ); - -// Load mockery files -require_once 'Mockery/Loader.php'; -require_once 'Hamcrest/Hamcrest.php'; -$loader = new \Mockery\Loader; -$loader->register(); - -use \Mockery as m; -use OCA\Encryption; - -class Test_Util extends \PHPUnit_Framework_TestCase { - - function setUp() { - - // set content for encrypting / decrypting in tests - $this->dataUrl = realpath( dirname(__FILE__).'/../lib/crypt.php' ); - $this->dataShort = 'hats'; - $this->dataLong = file_get_contents( realpath( dirname(__FILE__).'/../lib/crypt.php' ) ); - $this->legacyData = realpath( dirname(__FILE__).'/legacy-text.txt' ); - $this->legacyEncryptedData = realpath( dirname(__FILE__).'/legacy-encrypted-text.txt' ); - - $this->userId = 'admin'; - $this->pass = 'admin'; - - $keypair = Encryption\Crypt::createKeypair(); - - $this->genPublicKey = $keypair['publicKey']; - $this->genPrivateKey = $keypair['privateKey']; - - $this->publicKeyDir = '/' . 'public-keys'; - $this->encryptionDir = '/' . $this->userId . '/' . 'files_encryption'; - $this->keyfilesPath = $this->encryptionDir . '/' . 'keyfiles'; - $this->publicKeyPath = $this->publicKeyDir . '/' . $this->userId . '.public.key'; // e.g. data/public-keys/admin.public.key - $this->privateKeyPath = $this->encryptionDir . '/' . $this->userId . '.private.key'; // e.g. data/admin/admin.private.key - - $this->view = new OC_FilesystemView( '/admin' ); - - $this->mockView = m::mock('OC_FilesystemView'); - $this->util = new Encryption\Util( $this->mockView, $this->userId ); - - } - - function tearDown(){ - - m::close(); - - } - - /** - * @brief test that paths set during User construction are correct - */ - function testKeyPaths() { - - $mockView = m::mock('OC_FilesystemView'); - - $util = new Encryption\Util( $mockView, $this->userId ); - - $this->assertEquals( $this->publicKeyDir, $util->getPath( 'publicKeyDir' ) ); - $this->assertEquals( $this->encryptionDir, $util->getPath( 'encryptionDir' ) ); - $this->assertEquals( $this->keyfilesPath, $util->getPath( 'keyfilesPath' ) ); - $this->assertEquals( $this->publicKeyPath, $util->getPath( 'publicKeyPath' ) ); - $this->assertEquals( $this->privateKeyPath, $util->getPath( 'privateKeyPath' ) ); - - } - - /** - * @brief test setup of encryption directories when they don't yet exist - */ - function testSetupServerSideNotSetup() { - - $mockView = m::mock('OC_FilesystemView'); - - $mockView->shouldReceive( 'file_exists' )->times(4)->andReturn( false ); - $mockView->shouldReceive( 'mkdir' )->times(3)->andReturn( true ); - $mockView->shouldReceive( 'file_put_contents' )->withAnyArgs(); - - $util = new Encryption\Util( $mockView, $this->userId ); - - $this->assertEquals( true, $util->setupServerSide( $this->pass ) ); - - } - - /** - * @brief test setup of encryption directories when they already exist - */ - function testSetupServerSideIsSetup() { - - $mockView = m::mock('OC_FilesystemView'); - - $mockView->shouldReceive( 'file_exists' )->times(5)->andReturn( true ); - $mockView->shouldReceive( 'file_put_contents' )->withAnyArgs(); - - $util = new Encryption\Util( $mockView, $this->userId ); - - $this->assertEquals( true, $util->setupServerSide( $this->pass ) ); - - } - - /** - * @brief test checking whether account is ready for encryption, when it isn't ready - */ - function testReadyNotReady() { - - $mockView = m::mock('OC_FilesystemView'); - - $mockView->shouldReceive( 'file_exists' )->times(1)->andReturn( false ); - - $util = new Encryption\Util( $mockView, $this->userId ); - - $this->assertEquals( false, $util->ready() ); - - # TODO: Add more tests here to check that if any of the dirs are - # then false will be returned. Use strict ordering? - - } - - /** - * @brief test checking whether account is ready for encryption, when it is ready - */ - function testReadyIsReady() { - - $mockView = m::mock('OC_FilesystemView'); - - $mockView->shouldReceive( 'file_exists' )->times(3)->andReturn( true ); - - $util = new Encryption\Util( $mockView, $this->userId ); - - $this->assertEquals( true, $util->ready() ); - - # TODO: Add more tests here to check that if any of the dirs are - # then false will be returned. Use strict ordering? - - } - -// /** -// * @brief test decryption using legacy blowfish method -// * @depends testLegacyEncryptLong -// */ -// function testLegacyKeyRecryptKeyfileDecrypt( $recrypted ) { -// -// $decrypted = Encryption\Crypt::keyDecryptKeyfile( $recrypted['data'], $recrypted['key'], $this->genPrivateKey ); -// -// $this->assertEquals( $this->dataLong, $decrypted ); -// -// } - -// // Cannot use this test for now due to hidden dependencies in OC_FileCache -// function testIsLegacyEncryptedContent() { -// -// $keyfileContent = OCA\Encryption\Crypt::symmetricEncryptFileContent( $this->legacyEncryptedData, 'hat' ); -// -// $this->assertFalse( OCA\Encryption\Crypt::isLegacyEncryptedContent( $keyfileContent, '/files/admin/test.txt' ) ); -// -// OC_FileCache::put( '/admin/files/legacy-encrypted-test.txt', $this->legacyEncryptedData ); -// -// $this->assertTrue( OCA\Encryption\Crypt::isLegacyEncryptedContent( $this->legacyEncryptedData, '/files/admin/test.txt' ) ); -// -// } - -// // Cannot use this test for now due to need for different root in OC_Filesystem_view class -// function testGetLegacyKey() { -// -// $c = new \OCA\Encryption\Util( $view, false ); -// -// $bool = $c->getLegacyKey( 'admin' ); -// -// $this->assertTrue( $bool ); -// -// $this->assertTrue( $c->legacyKey ); -// -// $this->assertTrue( is_int( $c->legacyKey ) ); -// -// $this->assertTrue( strlen( $c->legacyKey ) == 20 ); -// -// } - -// // Cannot use this test for now due to need for different root in OC_Filesystem_view class -// function testLegacyDecrypt() { -// -// $c = new OCA\Encryption\Util( $this->view, false ); -// -// $bool = $c->getLegacyKey( 'admin' ); -// -// $encrypted = $c->legacyEncrypt( $this->data, $c->legacyKey ); -// -// $decrypted = $c->legacyDecrypt( $encrypted, $c->legacyKey ); -// -// $this->assertEqual( $decrypted, $this->data ); -// -// } - -} \ No newline at end of file diff --git a/apps/files_encryption/tests/zeros b/apps/files_encryption/tests/zeros deleted file mode 100644 index ff982acf423..00000000000 Binary files a/apps/files_encryption/tests/zeros and /dev/null differ -- cgit v1.2.3