From bfd47cd2dfad9c613d51fa9b4e5391f25ab57a87 Mon Sep 17 00:00:00 2001 From: Sam Tuke Date: Wed, 28 Nov 2012 18:39:19 +0000 Subject: [PATCH] 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(-) 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