diff options
author | Sam Tuke <samtuke@owncloud.com> | 2012-08-23 16:43:10 +0100 |
---|---|---|
committer | Sam Tuke <samtuke@owncloud.com> | 2012-08-23 16:43:10 +0100 |
commit | 32ee3de9188a2373d5629ae2e00dcbe2cbe33e29 (patch) | |
tree | 966c92560fc7ef2a47797fd957407516243ab814 | |
parent | 293a0f4d3229ab6737b3625d7ceb0718ef6dea00 (diff) | |
download | nextcloud-server-32ee3de9188a2373d5629ae2e00dcbe2cbe33e29.tar.gz nextcloud-server-32ee3de9188a2373d5629ae2e00dcbe2cbe33e29.zip |
Extensive work on crypto stream wrapper implementation
-rw-r--r-- | apps/files_encryption/lib/crypt.php | 35 | ||||
-rw-r--r-- | apps/files_encryption/lib/keymanager.php | 42 | ||||
-rw-r--r-- | apps/files_encryption/lib/stream.php | 115 | ||||
-rw-r--r-- | apps/files_encryption/tests/crypt.php | 144 | ||||
-rw-r--r-- | apps/files_encryption/tests/keymanager.php | 21 |
5 files changed, 259 insertions, 98 deletions
diff --git a/apps/files_encryption/lib/crypt.php b/apps/files_encryption/lib/crypt.php index fa7287a736b..25ba906deb4 100644 --- a/apps/files_encryption/lib/crypt.php +++ b/apps/files_encryption/lib/crypt.php @@ -177,6 +177,14 @@ class Crypt { }
+ public static function concatIv ( $content, $iv ) {
+
+ $combined = $content . '00iv00' . $iv;
+
+ return $combined;
+
+ }
+
/**
* @brief Symmetrically encrypts a string and returns keyfile content
* @param $plainContent content to be encrypted in keyfile
@@ -197,7 +205,7 @@ class Crypt { if ( $encryptedContent = self::encrypt( $plainContent, $iv, $passphrase ) ) {
// Combine content to encrypt with IV identifier and actual IV
- $combinedKeyfile = $encryptedContent . '00iv00' . $iv;
+ $combinedKeyfile = self::concatIv( $encryptedContent, $iv );
return $combinedKeyfile;
@@ -398,13 +406,17 @@ class Crypt { $crypted = '';
- while( strlen( $plainContent ) ) {
+ $remaining = $plainContent;
+
+ while( strlen( $remaining ) ) {
// Encrypt a chunk of unencrypted data and add it to the rest
- $crypted .= self::symmetricEncryptFileContent( substr( $plainContent, 0, 8192 ), $key );
+ $block = self::symmetricEncryptFileContent( substr( $remaining, 0, 8192 ), $key );
+
+ $crypted .= $block;
// Remove the data already encrypted from remaining unencrypted data
- $plainContent = substr( $plainContent, 8192 );
+ $remaining = substr( $remaining, 8192 );
}
@@ -418,17 +430,24 @@ class Crypt { */
public static function symmetricBlockDecryptFileContent( $crypted, $key ) {
+ //echo "\n\n\nfags \$crypted = $crypted\n\n\n";
+
$decrypted = '';
- while( strlen( $crypted ) ) {
+ $remaining = $crypted;
+
+ while( strlen( $remaining ) ) {
- $decrypted .= self::symmetricDecryptFileContent( substr( $crypted, 0, 8192 ), $key );
+ // 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 );
- $crypted = substr( $crypted, 8192 );
+ // Remove the data already encrypted from remaining unencrypted data
+ $remaining = substr( $remaining, 10946 );
}
- return rtrim( $decrypted, "\0" );
+ return $decrypted;
}
diff --git a/apps/files_encryption/lib/keymanager.php b/apps/files_encryption/lib/keymanager.php index 26101b8356c..525943f13d6 100644 --- a/apps/files_encryption/lib/keymanager.php +++ b/apps/files_encryption/lib/keymanager.php @@ -197,45 +197,57 @@ class Keymanager { */
public static function setFileKey( $path, $key, $view = Null, $dbClassName = '\OC_DB') {
- $targetpath = ltrim( $path, '/' );
+ $targetPath = ltrim( $path, '/' );
$user = \OCP\User::getUser();
// update $keytarget and $user if key belongs to a file shared by someone else
$query = $dbClassName::prepare( "SELECT uid_owner, source, target FROM `*PREFIX*sharing` WHERE target = ? AND uid_shared_with = ?" );
- $result = $query->execute( array ( '/'.$user.'/files/'.$targetpath, $user ) );
+ $result = $query->execute( array ( '/'.$user.'/files/'.$targetPath, $user ) );
if ( $row = $result->fetchRow( ) ) {
- $targetpath = $row['source'];
+ $targetPath = $row['source'];
- $targetpath_parts=explode( '/',$targetpath );
+ $targetPath_parts = explode( '/', $targetPath );
- $user = $targetpath_parts[1];
+ $user = $targetPath_parts[1];
- $rootview = new \OC_FilesystemView( '/');
- if (!$rootview->is_writable($targetpath)) {
- \OC_Log::write( 'Encryption library', "File Key not updated because you don't have write access for the corresponding file" , \OC_Log::ERROR );
+ $rootview = new \OC_FilesystemView( '/' );
+
+ if ( ! $rootview->is_writable( $targetPath ) ) {
+
+ \OC_Log::write( 'Encryption library', "File Key not updated because you don't have write access for the corresponding file", \OC_Log::ERROR );
+
return false;
- }
+
+ }
- $targetpath = str_replace( '/'.$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
}
- $path_parts = pathinfo( $targetpath );
-
- if (!$view) {
+ $path_parts = pathinfo( $targetPath );
+
+ if ( !$view ) {
+
$view = new \OC_FilesystemView( '/' );
+
}
$view->chroot( '/' . $user . '/files_encryption/keyfiles' );
- if ( !$view->file_exists( $path_parts['dirname'] ) ) $view->mkdir( $path_parts['dirname'] );
+ // If the file resides within a subdirectory, create it
+ if ( ! $view->file_exists( $path_parts['dirname'] ) ) {
+
+ $view->mkdir( $path_parts['dirname'] );
+
+ }
- return $view->file_put_contents( '/' . $targetpath . '.key', $key );
+ // Save the keyfile in parallel directory
+ return $view->file_put_contents( '/' . $targetPath . '.key', $key );
}
diff --git a/apps/files_encryption/lib/stream.php b/apps/files_encryption/lib/stream.php index bdcfdfd73aa..d1ab25a0192 100644 --- a/apps/files_encryption/lib/stream.php +++ b/apps/files_encryption/lib/stream.php @@ -34,11 +34,13 @@ class Stream { public static $sourceStreams = array(); private $source; private $path; + private $rawPath; // The raw path received by stream_open private $readBuffer; // For streams that dont support seeking private $meta = array(); // Header / meta for source stream private $count; private $writeCache; private $size; + private $keyfile; private static $view; public function stream_open( $path, $mode, $options, &$opened_path ) { @@ -52,7 +54,9 @@ class Stream { // Get the bare file path $path = str_replace( 'crypt://', '', $path ); - + + $this->rawPath = $path; + if ( dirname( $path ) == 'streams' and isset( self::$sourceStreams[basename( $path )] ) @@ -115,33 +119,77 @@ class Stream { return ftell($this->source); } - public function stream_read($count) { - //$count will always be 8192 https://bugs.php.net/bug.php?id=21641 - //This makes this function a lot simpler but will breake everything the moment it's fixed - $this->writeCache=''; - if ($count!=8192) { - OCP\Util::writeLog('files_encryption','php bug 21641 no longer holds, decryption will not work',OCP\Util::FATAL); + public function stream_read( $count ) { + + $this->writeCache = ''; + + if ( $count != 8192 ) { + + // $count will always be 8192 https://bugs.php.net/bug.php?id=21641 + // This makes this function a lot simpler, but will break this class if the above 'bug' gets 'fixed' + \OCP\Util::writeLog( 'files_encryption', 'PHP "bug" 21641 no longer holds, decryption system requires refactoring', OCP\Util::FATAL ); + die(); + } - $pos=ftell($this->source); - $data=fread($this->source,8192); - if (strlen($data)) { - $result=Crypt::decrypt($data); - }else{ - $result=''; + + $pos = ftell( $this->source ); + + $data = fread( $this->source, 8192 ); + + if ( strlen( $data ) ) { + + $result = Crypt::symmetricDecryptFileContent( $data, $this->keyfile ); + + } else { + + $result = ''; + } - $length=$this->size-$pos; - if ($length<8192) { - $result=substr($result,0,$length); + + $length = $this->size - $pos; + + if ( $length < 8192 ) { + + $result = substr( $result, 0, $length ); + } + return $result; + + } + + /** + * @brief Get the keyfile for the current file, generate one if necessary + */ + public function getKey() { + + # TODO: Move this user call out of here - it belongs elsewhere + $user = \OCP\User::getUser(); + + if ( self::$view->file_exists( $this->rawPath . $user ) ) { + + // If the data is to be written to an existing file, fetch its keyfile + $this->keyfile = Keymanager::getFileKey( $this->rawPath . $user ); + + } else { + + // If the data is to be written to a new file, generate a new keyfile + $this->keyfile = Crypt::generateKey(); + + } + } /** * @brief */ public function stream_write( $data ) { - + + # TODO: Find a way to get path of file in order to know where to save its parallel keyfile + + \OC_FileProxy::$enabled = false; + $length = strlen( $data ); $written = 0; @@ -151,15 +199,13 @@ class Stream { # TODO: Move this user call out of here - it belongs elsewhere $user = \OCP\User::getUser(); - if ( self::$view->file_exists( $this->path . $user ) ) { + // Set keyfile property for file in question + $this->getKey(); - $key = Keymanager::getFileKey( $this->path . $user ); + if ( ! self::$view->file_exists( $this->rawPath . $user ) ) { - } else { - - $key = Crypt::generateKey(); - - Keymanager::setFileKey( $path, $key, new \OC_FilesystemView ); + // Save keyfile in parallel directory structure + Keymanager::setFileKey( $this->rawPath, $this->keyfile, new \OC_FilesystemView( '/' ) ); } @@ -180,7 +226,7 @@ class Stream { fseek( $this->source, - ( $currentPos % 8192 ), SEEK_CUR ); - $block = Crypt::symmetricDecryptFileContent( $encryptedBlock, $key ); + $block = Crypt::symmetricDecryptFileContent( $encryptedBlock, $this->keyfile ); $data = substr( $block, 0, $currentPos % 8192 ) . $data; @@ -190,9 +236,9 @@ class Stream { $currentPos = ftell( $this->source ); - while( $remainingLength = strlen( $data )>0 ) { + while( $remainingLength = strlen( $data ) > 0 ) { - if ( $remainingLength<8192 ) { + if ( $remainingLength < 8192 ) { $this->writeCache = $data; @@ -200,9 +246,11 @@ class Stream { } else { - $encrypted = Crypt::symmetricBlockEncryptFileContent( $data, $key ); + $encrypted = Crypt::symmetricBlockEncryptFileContent( $data, $this->keyfile ); + + //$encrypted = $data; - fwrite( $this->source . $user, $encrypted ); + fwrite( $this->source, $encrypted ); $data = substr( $data,8192 ); @@ -255,10 +303,17 @@ class Stream { } public function stream_close() { + $this->flush(); + if ($this->meta['mode']!='r' and $this->meta['mode']!='rb') { - OC_FileCache::put($this->path,array('encrypted'=>true,'size'=>$this->size),''); + + \OC_FileCache::put($this->path,array('encrypted'=>true,'size'=>$this->size),''); + } + return fclose($this->source); + } + } diff --git a/apps/files_encryption/tests/crypt.php b/apps/files_encryption/tests/crypt.php index ef453ab90d6..78f5f74fbf4 100644 --- a/apps/files_encryption/tests/crypt.php +++ b/apps/files_encryption/tests/crypt.php @@ -17,9 +17,13 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { function setUp() { // set content for encrypting / decrypting in tests - $this->data = realpath( dirname(__FILE__).'/../lib/crypt.php' ); + $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->view = new \OC_FilesystemView( '/' ); //stream_wrapper_register( 'crypt', 'OCA_Encryption\Stream' ); @@ -51,9 +55,9 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { $iv = substr( base64_encode( $random ), 0, -4 ); // i.e. E5IG033j+mRNKrht - $crypted = Crypt::encrypt( $this->data, $iv, 'hat' ); + $crypted = Crypt::encrypt( $this->dataUrl, $iv, 'hat' ); - $this->assertNotEquals( $this->data, $crypted ); + $this->assertNotEquals( $this->dataUrl, $crypted ); } @@ -63,11 +67,11 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { $iv = substr( base64_encode( $random ), 0, -4 ); // i.e. E5IG033j+mRNKrht - $crypted = Crypt::encrypt( $this->data, $iv, 'hat' ); + $crypted = Crypt::encrypt( $this->dataUrl, $iv, 'hat' ); $decrypt = Crypt::decrypt( $crypted, $iv, 'hat' ); - $this->assertEquals( $this->data, $decrypt ); + $this->assertEquals( $this->dataUrl, $decrypt ); } @@ -75,81 +79,133 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { # TODO: search in keyfile for actual content as IV will ensure this test always passes - $crypted = Crypt::symmetricEncryptFileContent( $this->data, 'hat' ); + $crypted = Crypt::symmetricEncryptFileContent( $this->dataUrl, 'hat' ); - $this->assertNotEquals( $this->data, $crypted ); + $this->assertNotEquals( $this->dataUrl, $crypted ); $decrypt = Crypt::symmetricDecryptFileContent( $crypted, 'hat' ); - $this->assertEquals( $this->data, $decrypt ); + $this->assertEquals( $this->dataUrl, $decrypt ); } - function testSymmetricBlockEncryptFileContent() { + function testSymmetricBlockEncryptShortFileContent() { + + $key = file_get_contents( '/home/samtuke/owncloud/git/oc3/data/admin/files_encryption/keyfiles/sscceEncrypt-1345649062.key' ); + + $crypted = Crypt::symmetricBlockEncryptFileContent( $this->dataShort, $key ); - $crypted = Crypt::symmetricBlockEncryptFileContent( $this->data, 'hat' ); + $this->assertNotEquals( $this->dataShort, $crypted ); + + + $decrypt = Crypt::symmetricBlockDecryptFileContent( $crypted, $key ); - $this->assertNotEquals( $this->data, $crypted ); + $this->assertEquals( $this->dataShort, $decrypt ); + + } + + function testSymmetricBlockEncryptLongFileContent() { + + $key = file_get_contents( '/home/samtuke/owncloud/git/oc3/data/admin/files_encryption/keyfiles/sscceEncrypt-1345649062.key' ); + + $crypted = Crypt::symmetricBlockEncryptFileContent( substr( $this->dataLong, 0, 6500 ), $key ); + $this->assertNotEquals( $this->dataLong, $crypted ); + + //echo "\n\nCAT ".substr( $this->dataLong, 0, 7000 ); - $decrypt = Crypt::symmetricBlockDecryptFileContent( $crypted, 'hat' ); + $decrypt = Crypt::symmetricBlockDecryptFileContent( $crypted, $key ); - $this->assertEquals( $this->data, $decrypt ); + $this->assertEquals( substr( $this->dataLong, 0, 6500 + + ), $decrypt ); } // function testSymmetricBlockStreamEncryptFileContent() { // -// $crypted = Crypt::symmetricBlockEncryptFileContent( $this->data, 'hat' ); +// \OC_User::setUserId( 'admin' ); +// +// // Disable encryption proxy to prevent unwanted en/decryption +// \OC_FileProxy::$enabled = false; // -// $cryptedFile = file_put_contents( 'crypt://' . '/blockEncrypt', $crypted ); +// $cryptedFile = file_put_contents( 'crypt://' . '/blockEncrypt', $this->dataUrl ); // // // Test that data was successfully written -// $this->assertTrue( $cryptedFile ); +// $this->assertTrue( is_int( $cryptedFile ) ); +// +// // Disable encryption proxy to prevent unwanted en/decryption +// \OC_FileProxy::$enabled = false; +// +// +// +// // Get file contents without using any wrapper to get it's actual contents on disk +// $retreivedCryptedFile = $this->view->file_get_contents( '/blockEncrypt' ); +// +// echo "\n\n\$retreivedCryptedFile = !! $retreivedCryptedFile !!"; +// +// $key = file_get_contents( '/home/samtuke/owncloud/git/oc3/data/files_encryption/keyfiles/tmp/testSetFileKey.key' ); +// +// echo "\n\n\$key = !! $key !!"; // -// $retreivedCryptedFile = file_get_contents( '/blockEncrypt' ); +// $manualDecrypt = Crypt::symmetricDecryptFileContent( $retreivedCryptedFile, $key ); // -// $this->assertNotEquals( $this->data, $retreivedCryptedFile ); +// echo "\n\n\$manualDecrypt = !! $manualDecrypt !!"; +// +// // Check that the file was encrypted before being written to disk +// $this->assertNotEquals( $this->dataUrl, $retreivedCryptedFile ); +// +// $decrypt = Crypt::symmetricBlockDecryptFileContent( $retreivedCryptedFile, $key); +// +// $this->assertEquals( $this->dataUrl, $decrypt ); // // } - function testSymmetricBlockStreamDecryptFileContent() { - - \OC_User::setUserId( 'admin' ); - - $crypted = Crypt::symmetricBlockEncryptFileContent( $this->data, 'hat' ); - - $cryptedFile = file_put_contents( 'crypt://' . '/blockEncrypt', $crypted ); - - $retreivedCryptedFile = file_get_contents( 'crypt://' . '/blockEncrypt' ); - - $this->assertEquals( $this->data, $retreivedCryptedFile ); - - } +// function 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 = Crypt::symmetricEncryptFileContentKeyfile( $this->data ); + $crypted = Crypt::symmetricEncryptFileContentKeyfile( $this->dataUrl ); - $this->assertNotEquals( $this->data, $crypted['encrypted'] ); + $this->assertNotEquals( $this->dataUrl, $crypted['encrypted'] ); $decrypt = Crypt::symmetricDecryptFileContent( $crypted['encrypted'], $crypted['key'] ); - $this->assertEquals( $this->data, $decrypt ); + $this->assertEquals( $this->dataUrl, $decrypt ); } function testIsEncryptedContent() { - $this->assertFalse( Crypt::isEncryptedContent( $this->data ) ); + $this->assertFalse( Crypt::isEncryptedContent( $this->dataUrl ) ); $this->assertFalse( Crypt::isEncryptedContent( $this->legacyEncryptedData ) ); - $keyfileContent = Crypt::symmetricEncryptFileContent( $this->data, 'hat' ); + $keyfileContent = Crypt::symmetricEncryptFileContent( $this->dataUrl, 'hat' ); $this->assertTrue( Crypt::isEncryptedContent( $keyfileContent ) ); @@ -168,14 +224,14 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { $this->assertTrue( strlen( $pair1['privateKey'] ) > 1 ); - $crypted = Crypt::multiKeyEncrypt( $this->data, array( $pair1['publicKey'] ) ); + $crypted = Crypt::multiKeyEncrypt( $this->dataUrl, array( $pair1['publicKey'] ) ); - $this->assertNotEquals( $this->data, $crypted['encrypted'] ); + $this->assertNotEquals( $this->dataUrl, $crypted['encrypted'] ); $decrypt = Crypt::multiKeyDecrypt( $crypted['encrypted'], $crypted['keys'][0], $pair1['privateKey'] ); - $this->assertEquals( $this->data, $decrypt ); + $this->assertEquals( $this->dataUrl, $decrypt ); } @@ -185,14 +241,14 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { $pair1 = Crypt::createKeypair(); // Encrypt data - $crypted = Crypt::keyEncrypt( $this->data, $pair1['publicKey'] ); + $crypted = Crypt::keyEncrypt( $this->dataUrl, $pair1['publicKey'] ); - $this->assertNotEquals( $this->data, $crypted ); + $this->assertNotEquals( $this->dataUrl, $crypted ); // Decrypt data $decrypt = Crypt::keyDecrypt( $crypted, $pair1['privateKey'] ); - $this->assertEquals( $this->data, $decrypt ); + $this->assertEquals( $this->dataUrl, $decrypt ); } @@ -204,7 +260,7 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { $pair1 = Crypt::createKeypair(); // Encrypt plain data, generate keyfile & encrypted file - $cryptedData = Crypt::symmetricEncryptFileContentKeyfile( $this->data ); + $cryptedData = Crypt::symmetricEncryptFileContentKeyfile( $this->dataUrl ); // Encrypt keyfile $cryptedKey = Crypt::keyEncrypt( $cryptedData['key'], $pair1['publicKey'] ); @@ -215,7 +271,7 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { // Decrypt encrypted file $decryptData = Crypt::symmetricDecryptFileContent( $cryptedData['encrypted'], $decryptKey ); - $this->assertEquals( $this->data, $decryptData ); + $this->assertEquals( $this->dataUrl, $decryptData ); } diff --git a/apps/files_encryption/tests/keymanager.php b/apps/files_encryption/tests/keymanager.php index e0ce7a1d6ad..a6b425bafa2 100644 --- a/apps/files_encryption/tests/keymanager.php +++ b/apps/files_encryption/tests/keymanager.php @@ -15,7 +15,8 @@ class Test_Keymanager extends \PHPUnit_Framework_TestCase { function setUp() { - // set content for encrypting / decrypting in tests + // Set data for use in tests + $this->data = realpath( dirname(__FILE__).'/../lib/crypt.php' ); $this->user = 'admin'; $this->passphrase = 'admin'; $this->view = new \OC_FilesystemView( '' ); @@ -39,6 +40,22 @@ class Test_Keymanager extends \PHPUnit_Framework_TestCase { } + 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' ); +// +// Keymanager::setFileKey( $tmpPath, $key['key'], $view ); + + } + function testGetDecryptedPrivateKey() { $key = Keymanager::getPrivateKey( $this->user, $this->view ); @@ -52,4 +69,6 @@ class Test_Keymanager extends \PHPUnit_Framework_TestCase { } + + } |