Development snapshot; encryption stream wrapper now successfully writes files, and passes unit tests

This commit is contained in:
Sam Tuke 2012-10-10 18:40:59 +01:00
parent ed980674a6
commit 4da67b0a08
5 changed files with 255 additions and 77 deletions

View File

@ -442,8 +442,6 @@ class Crypt {
*/ */
public static function symmetricBlockDecryptFileContent( $crypted, $key ) { public static function symmetricBlockDecryptFileContent( $crypted, $key ) {
echo "\n\n\nfags \$crypted = $crypted\n\n\n";
$decrypted = ''; $decrypted = '';
$remaining = $crypted; $remaining = $crypted;
@ -463,9 +461,7 @@ class Crypt {
} }
echo "nags "; //print_r($testarray);
print_r($testarray);
return $decrypted; return $decrypted;

View File

@ -145,6 +145,37 @@ class Keymanager {
} }
/**
* @brief retrieve file encryption key
*
* @param string file name
* @return string file key or false
*/
public static function deleteFileKey( $path, $staticUserClass = 'OCP\User' ) {
$keypath = ltrim( $path, '/' );
$user = $staticUserClass::getUser();
// update $keypath and $user if path point to a file shared by someone else
// $query = \OC_DB::prepare( "SELECT uid_owner, source, target FROM `*PREFIX*sharing` WHERE target = ? AND uid_shared_with = ?" );
//
// $result = $query->execute( array ('/'.$user.'/files/'.$keypath, $user));
//
// if ($row = $result->fetchRow()) {
//
// $keypath = $row['source'];
// $keypath_parts = explode( '/', $keypath );
// $user = $keypath_parts[1];
// $keypath = str_replace( '/' . $user . '/files/', '', $keypath );
//
// }
$view = new \OC_FilesystemView('/'.$user.'/files_encryption/keyfiles/');
return $view->unlink( $keypath . '.key' );
}
/** /**
* @brief store private key from the user * @brief store private key from the user
* *

View File

@ -3,7 +3,7 @@
* ownCloud * ownCloud
* *
* @author Robin Appelman * @author Robin Appelman
* @copyright 2011 Robin Appelman icewind1991@gmail.com * @copyright 2012 Sam Tuke samtuke@owncloud.com, 2011 Robin Appelman icewind1991@gmail.com
* *
* This library is free software; you can redistribute it and/or * This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
@ -29,6 +29,11 @@
namespace OCA_Encryption; namespace OCA_Encryption;
/**
* @brief Provides 'crypt://' stream wrapper protocol.
* @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.
*/
class Stream { class Stream {
public static $sourceStreams = array(); public static $sourceStreams = array();
@ -94,9 +99,9 @@ class Stream {
// Disable fileproxies so we can open the source file without recursive encryption // Disable fileproxies so we can open the source file without recursive encryption
\OC_FileProxy::$enabled = false; \OC_FileProxy::$enabled = false;
$this->handle = fopen( $path, $mode ); //$this->handle = fopen( $path, $mode );
//$this->handle = self::$view->fopen( $path, $mode ); $this->handle = self::$view->fopen( \OCP\USER::getUser() . '/' . 'files' . '/' . $path, $mode );
\OC_FileProxy::$enabled = true; \OC_FileProxy::$enabled = true;
@ -156,7 +161,7 @@ class Stream {
echo "\n\nGROWL {$this->keyfile}\n\n"; echo "\n\nGROWL {$this->keyfile}\n\n";
$key = file_get_contents( '/home/samtuke/owncloud/git/oc3/data/admin/files_encryption/keyfiles/tmp-1346255589.key' ); //$key = file_get_contents( '/home/samtuke/owncloud/git/oc3/data/admin/files_encryption/keyfiles/tmp-1346255589.key' );
$result = Crypt::symmetricDecryptFileContent( $data, $this->keyfile ); $result = Crypt::symmetricDecryptFileContent( $data, $this->keyfile );
@ -201,11 +206,14 @@ class Stream {
# TODO: Move this user call out of here - it belongs elsewhere # TODO: Move this user call out of here - it belongs elsewhere
$user = \OCP\User::getUser(); $user = \OCP\User::getUser();
if ( self::$view->file_exists( $this->rawPath ) ) { //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' ) ) {
# TODO: add error handling for when file exists but no keyfile # TODO: add error handling for when file exists but no keyfile
// If the data is to be written to an existing file, fetch its keyfile // Fetch existing keyfile
$this->keyfile = Keymanager::getFileKey( $this->rawPath ); $this->keyfile = Keymanager::getFileKey( $this->rawPath );
} else { } else {
@ -223,6 +231,9 @@ class Stream {
/** /**
* @brief Take plain data destined to be written, encrypt it, and write it block by block * @brief Take plain data destined to be written, encrypt it, and write it block by block
* @param string $data data to be written to disk
* @note the data will be written to the path stored in the stream handle, set in stream_open()
* @note $data is only ever x bytes long. stream_write() is called multiple times on data larger than x to process it x byte chunks.
*/ */
public function stream_write( $data ) { public function stream_write( $data ) {
@ -232,7 +243,11 @@ class Stream {
$written = 0; $written = 0;
$currentPos = ftell( $this->handle ); $pointer = ftell( $this->handle );
echo "\n\n\$length = $length\n";
echo "\$pointer = $pointer\n";
# TODO: Move this user call out of here - it belongs elsewhere # TODO: Move this user call out of here - it belongs elsewhere
$user = \OCP\User::getUser(); $user = \OCP\User::getUser();
@ -247,10 +262,10 @@ class Stream {
} }
// // If data exists in the writeCache // If data exists in the writeCache
// if ( $this->writeCache ) { // if ( $this->writeCache ) {
// //
// trigger_error("write cache is set"); // //trigger_error("write cache is set");
// //
// // Concat writeCache to start of $data // // Concat writeCache to start of $data
// $data = $this->writeCache . $data; // $data = $this->writeCache . $data;
@ -260,15 +275,30 @@ class Stream {
// } // }
// //
// // Make sure we always start on a block start // // Make sure we always start on a block start
// if ( 0 != ( $currentPos % 8192 ) ) { // If we're not at the end of file yet (in the final chunk), if there will be no bytes left to read after the current chunk if ( 0 != ( $pointer % 8192 ) ) { // if the current positoin of file indicator is not aligned to a 8192 byte block, fix it so that it is
//
// echo "\n\nNOT ON BLOCK START ";
// echo $pointer % 8192;
//
// echo "\n\n1. $currentPos\n\n";
// //
// echo "ftell() = ".ftell($this->handle)."\n";
// fseek( $this->handle, - ( $pointer % 8192 ), SEEK_CUR );
//
// $pointer = ftell( $this->handle );
// echo "ftell() = ".ftell($this->handle)."\n";
//
// $unencryptedNewBlock = fread( $this->handle, 8192 );
//
// echo "\n\n2. $currentPos\n\n";
// //
// fseek( $this->handle, - ( $currentPos % 8192 ), SEEK_CUR ); // fseek( $this->handle, - ( $currentPos % 8192 ), SEEK_CUR );
// //
// $encryptedBlock = fread( $this->handle, 8192 ); // echo "\n\n3. $currentPos\n\n";
// //
// fseek( $this->handle, - ( $currentPos % 8192 ), SEEK_CUR ); // $block = Crypt::symmetricDecryptFileContent( $unencryptedNewBlock, $this->keyfile );
//
// $block = Crypt::symmetricDecryptFileContent( $encryptedBlock, $this->keyfile );
// //
// $x = substr( $block, 0, $currentPos % 8192 ); // $x = substr( $block, 0, $currentPos % 8192 );
// //
@ -276,13 +306,14 @@ class Stream {
// //
// fseek( $this->handle, - ( $currentPos % 8192 ), SEEK_CUR ); // fseek( $this->handle, - ( $currentPos % 8192 ), SEEK_CUR );
// //
// } }
/*
$currentPos = ftell( $this->handle );*/
// // While there still remains somed data to be written // $currentPos = ftell( $this->handle );
// while( strlen( $data ) > 0 ) {
// // While there still remains somed data to be processed & written
while( strlen( $data ) > 0 ) {
// //
// // Remaining length for this iteration, not of the entire file (may be greater than 8192 bytes)
// $remainingLength = strlen( $data ); // $remainingLength = strlen( $data );
// //
// // If data remaining to be written is less than the size of 1 block // // If data remaining to be written is less than the size of 1 block
@ -294,24 +325,55 @@ class Stream {
// $this->writeCache = $data; // $this->writeCache = $data;
// //
// $data = ''; // $data = '';
// // //
// } else { // } else {
$encrypted = Crypt::symmetricEncryptFileContent( $data, $this->keyfile ); //echo "\n\nbefore before ".strlen($data)."\n";
file_put_contents('/home/samtuke/tmp.txt', $encrypted); // Read the chunk from the start of $data
$chunk = substr( $data, 0, 6126 );
//echo "\n\nFRESHLY ENCRYPTED = $encrypted\n\n"; //echo "before ".strlen($data)."\n";
fwrite( $this->handle, $encrypted ); //echo "\n\$this->keyfile 1 = {$this->keyfile}";
$data = substr( $data, 8192 ); $encrypted = Crypt::symmetricEncryptFileContent( $chunk, $this->keyfile );
//echo "\n\n\$rawEnc = $encrypted\n\n";
//echo "\$encrypted = ".strlen($encrypted)."\n";
$padded = $encrypted . 'xx';
//echo "\$padded = ".strlen($padded)."\n";
//echo "after ".strlen($encrypted)."\n\n";
//file_put_contents('/home/samtuke/tmp.txt', $encrypted);
fwrite( $this->handle, $padded );
$bef = ftell( $this->handle );
//echo "ftell before = $bef\n";
$writtenLen = strlen( $padded );
//fseek( $this->handle, $writtenLen, SEEK_CUR );
// $aft = ftell( $this->handle );
// echo "ftell after = $aft\n";
// echo "ftell sum = ";
// echo $aft - $bef."\n";
// Remove the chunk we just processed from $data, leaving only unprocessed data in $data var
$data = substr( $data, 6126 );
// } // }
// //
// } }
$this->size = max( $this->size, $currentPos + $length ); $this->size = max( $this->size, $pointer + $length );
echo "\$this->size = $this->size\n\n";
return $length; return $length;

View File

@ -124,7 +124,130 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase {
\OC_User::setUserId( 'admin' ); \OC_User::setUserId( 'admin' );
$filename = 'flockEncrypt'; $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( '/admin/files/' . $filename );
// Manually remove padding from end of each chunk
$retreivedCryptedFile = substr( $retreivedCryptedFile, 0, -2 );
// Check that the file was encrypted before being written to disk
$this->assertNotEquals( $this->dataShort, $retreivedCryptedFile );
$key = Keymanager::getFileKey( $filename );
$manualDecrypt = Crypt::symmetricBlockDecryptFileContent( $retreivedCryptedFile, $key );
$this->assertEquals( $this->dataShort, $manualDecrypt );
}
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( '/admin/files/' . $filename );
// 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);
//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] );
// 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 );
// Set var for reassembling decrypted content
$decrypt = '';
// Manually decrypt chunk
foreach ($f as $f) {
// echo "\n\$encryptMe = $f";
$chunkDecrypt = Crypt::symmetricDecryptFileContent( $f, $keyfile );
// Assemble decrypted chunks
$decrypt .= $chunkDecrypt;
// echo "\n\$chunkDecrypt = $chunkDecrypt";
}
$this->assertEquals( $this->dataLong.$this->dataLong, $decrypt );
// Teadown
$this->view->unlink( '/admin/files/' . $filename );
Keymanager::deleteFileKey( $filename );
// Fetch the saved encrypted file using stream wrapper to decrypt it
// $autoDecrypted = file_get_contents( 'crypt:////home/samtuke/owncloud/git/oc3/data/' . $filename );
//
// //file_get_contents('crypt:///home/samtuke/tmp-1346255589');
//
// // Check that the retreived decrypted contents match the original
// $this->assertEquals( $this->dataLong.$this->dataLong, $autoDecrypted );
// $key = file_get_contents( '/home/samtuke/owncloud/git/oc3/data/admin/files_encryption/keyfiles/' . $filename . '.key' );
//
// $manualDecrypt = Crypt::symmetricBlockDecryptFileContent( $retreivedCryptedFile, $key );
//
// echo "\n\n\n\n\n\n\n\n\n\n\$manualDecrypt = $manualDecrypt\n\n";
//
// $this->assertEquals( $this->dataLong, $manualDecrypt );
}
function testSymmetricStreamDecryptShortFileContent() {
\OC_User::setUserId( 'admin' );
$filename = 'tmp-'.time();
$cryptedFile = file_put_contents( 'crypt://' . '/' . $filename, $this->dataShort ); $cryptedFile = file_put_contents( 'crypt://' . '/' . $filename, $this->dataShort );
@ -147,44 +270,7 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase {
} }
function testSymmetricStreamEncryptLongFileContent() { // Is this test still necessary?
$filename = 'tmp-'.time();
echo "\n\n\$filename = $filename\n\n";
$cryptedFile = file_put_contents( 'crypt://' . '/' . '/home/samtuke/owncloud/git/oc3/data/' . $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( '/' . $filename );
//echo "\n\nsock $retreivedCryptedFile\n\n";
// Check that the file was encrypted before being written to disk
$this->assertNotEquals( $this->dataLong.$this->dataLong, $retreivedCryptedFile );
$autoDecrypted = file_get_contents( 'crypt:////home/samtuke/owncloud/git/oc3/data/' . $filename );
//file_get_contents('crypt:///home/samtuke/tmp-1346255589');
$this->assertEquals( $this->dataLong.$this->dataLong, $autoDecrypted );
// $key = file_get_contents( '/home/samtuke/owncloud/git/oc3/data/admin/files_encryption/keyfiles/' . $filename . '.key' );
//
// $manualDecrypt = Crypt::symmetricBlockDecryptFileContent( $retreivedCryptedFile, $key );
//
// echo "\n\n\n\n\n\n\n\n\n\n\$manualDecrypt = $manualDecrypt\n\n";
//
// $this->assertEquals( $this->dataLong, $manualDecrypt );
}
// function testSymmetricBlockStreamDecryptFileContent() { // function testSymmetricBlockStreamDecryptFileContent() {
// //
// \OC_User::setUserId( 'admin' ); // \OC_User::setUserId( 'admin' );

View File

@ -38,6 +38,9 @@
* OC_Filestorage object * OC_Filestorage object
*/ */
/**
* @note default root (if $root is empty or '/') is /data/[user]/
*/
class OC_FilesystemView { class OC_FilesystemView {
private $fakeRoot=''; private $fakeRoot='';
private $internal_path_cache=array(); private $internal_path_cache=array();