Fixed stream wrapper bugs

Switched encryptAll() to use stream-based instead of file-at-a-time encryption
Development snapshot
This commit is contained in:
Sam Tuke 2013-03-09 19:18:34 +01:00
parent f2b86d0227
commit c1f1fbda08
5 changed files with 99 additions and 59 deletions

View file

@ -114,7 +114,7 @@ class Crypt {
* @return true / false
* @note see also OCA\Encryption\Util->isEncryptedPath()
*/
public static function isCatfile( $content ) {
public static function isCatfileContent( $content ) {
if ( !$content ) {
@ -179,7 +179,7 @@ class Crypt {
if (
isset( $metadata['encrypted'] )
and $metadata['encrypted'] === true
and ! self::isCatfile( $data )
and ! self::isCatfileContent( $data )
) {
return true;

View file

@ -74,7 +74,7 @@ class Proxy extends \OC_FileProxy {
}
if ( Crypt::isCatfile( $path ) ) {
if ( Crypt::isCatfileContent( $path ) ) {
return true;
@ -209,7 +209,7 @@ class Proxy extends \OC_FileProxy {
// If data is a catfile
if (
Crypt::mode() == 'server'
&& Crypt::isCatfile( $data )
&& Crypt::isCatfileContent( $data )
) {
// TODO use get owner to find correct location of key files for shared files
@ -439,7 +439,7 @@ class Proxy extends \OC_FileProxy {
public function postGetMimeType( $path, $mime ) {
if ( Crypt::isCatfile( $path ) ) {
if ( Crypt::isCatfileContent( $path ) ) {
$mime = \OCP\Files::getMimeType( 'crypt://' . $path, 'w' );
@ -451,7 +451,7 @@ class Proxy extends \OC_FileProxy {
public function postStat( $path, $data ) {
if ( Crypt::isCatfile( $path ) ) {
if ( Crypt::isCatfileContent( $path ) ) {
$cached = \OC\Files\Filesystem::getFileInfo( $path, '' );
@ -464,7 +464,7 @@ class Proxy extends \OC_FileProxy {
public function postFileSize( $path, $size ) {
if ( Crypt::isCatfile( $path ) ) {
if ( Crypt::isCatfileContent( $path ) ) {
$cached = \OC\Files\Filesystem::getFileInfo( $path, '' );

View file

@ -68,42 +68,33 @@ class Stream {
private $rootView; // a fsview object set to '/'
public function stream_open( $path, $mode, $options, &$opened_path ) {
$this->userId = \OCP\User::getUser();
// Get access to filesystem via filesystemview object
if ( !self::$view ) {
if ( ! isset( $this->rootView ) ) {
self::$view = new \OC_FilesystemView( $this->userId . '/' );
}
// Set rootview object if necessary
if ( ! $this->rootView ) {
$this->rootView = new \OC_FilesystemView( $this->userId . '/' );
$this->rootView = new \OC_FilesystemView( '/' );
}
// Get the bare file path
$path = str_replace( 'crypt://', '', $path );
// Strip identifier text from path
$this->rawPath = str_replace( 'crypt://', '', $path );
$this->rawPath = $path;
$this->path_f = $this->userId . '/files/' . $path;
// Set file path relative to user files dir
$this->relPath = $this->userId . '/files/' . $this->rawPath;
if (
dirname( $path ) == 'streams'
and isset( self::$sourceStreams[basename( $path )] )
dirname( $this->rawPath ) == 'streams'
and isset( self::$sourceStreams[basename( $this->rawPath )] )
) {
// Is this just for unit testing purposes?
$this->handle = self::$sourceStreams[basename( $path )]['stream'];
$this->handle = self::$sourceStreams[basename( $this->rawPath )]['stream'];
$this->path = self::$sourceStreams[basename( $path )]['path'];
$this->path = self::$sourceStreams[basename( $this->rawPath )]['path'];
$this->size = self::$sourceStreams[basename( $path )]['size'];
$this->size = self::$sourceStreams[basename( $this->rawPath )]['size'];
} else {
@ -114,41 +105,38 @@ class Stream {
or $mode == 'wb+'
) {
// We're writing a new file so start write counter with 0 bytes
$this->size = 0;
} else {
$this->size = $this->rootView->filesize( $this->relPath, $mode );
$this->size = self::$view->filesize( $this->path_f, $mode );
//$this->size = filesize( $path );
//$this->size = filesize( $this->rawPath );
}
// Disable fileproxies so we can open the source file without recursive encryption
\OC_FileProxy::$enabled = false;
//$this->handle = fopen( $path, $mode );
//$this->handle = fopen( $this->rawPath, $mode );
$this->handle = self::$view->fopen( $this->path_f, $mode );
$this->handle = $this->rootView->fopen( $this->relPath, $mode );
\OC_FileProxy::$enabled = true;
if ( !is_resource( $this->handle ) ) {
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 file "'.$this->rootView . '"', \OCP\Util::ERROR );
} else {
$this->meta = stream_get_meta_data( $this->handle );
}
}
if ( is_resource( $this->handle ) ) {
$this->meta = stream_get_meta_data( $this->handle );
}
return is_resource( $this->handle );
}
@ -238,7 +226,7 @@ class Stream {
// If a keyfile already exists for a file named identically to
// file to be written
if ( self::$view->file_exists( $this->userId . '/'. 'files_encryption' . '/' . 'keyfiles' . '/' . $this->rawPath . '.key' ) ) {
if ( $this->rootView->file_exists( $this->userId . '/'. 'files_encryption' . '/' . 'keyfiles' . '/' . $this->rawPath . '.key' ) ) {
// TODO: add error handling for when file exists but no
// keyfile

View file

@ -24,13 +24,12 @@
# Bugs
# ----
# Sharing a file to a user without encryption set up will not provide them with access but won't notify the sharer
# When encryption app is disabled files become unreadable
# Timeouts on first login due to encryption of very large files
# Missing features
# ----------------
# Re-use existing keyfiles so they don't need version control
# Re-use existing keyfiles so they don't need version control (part implemented, stream{} and util{} remain)
# Make sure user knows if large files weren't encrypted
# Trashbin support
@ -280,14 +279,14 @@ class Util {
// will eat server resources :(
if (
Keymanager::getFileKey( $this->view, $this->userId, $file )
&& Crypt::isCatfile( $data )
&& Crypt::isCatfileContent( $data )
) {
$found['encrypted'][] = array( 'name' => $file, 'path' => $filePath );
// If the file uses old
// encryption system
} elseif ( Crypt::isLegacyEncryptedContent( $this->view->file_get_contents( $filePath ), $relPath ) ) {
} elseif ( Crypt::isLegacyEncryptedContent( $this->tail( $filePath, 3 ), $relPath ) ) {
$found['legacy'][] = array( 'name' => $file, 'path' => $filePath );
@ -324,6 +323,49 @@ class Util {
}
/**
* @brief Fetch the last lines of a file efficiently
* @note Safe to use on large files; does not read entire file to memory
* @note Derivative of http://tekkie.flashbit.net/php/tail-functionality-in-php
*/
public function tail( $filename, $numLines ) {
\OC_FileProxy::$enabled = false;
$text = '';
$pos = -1;
$handle = $this->view->fopen( $filename, 'r' );
while ( $numLines > 0 ) {
--$pos;
if( fseek( $handle, $pos, SEEK_END ) !== 0 ) {
rewind( $handle );
$numLines = 0;
} elseif ( fgetc( $handle ) === "\n" ) {
--$numLines;
}
$block_size = ( -$pos ) % 8192;
if ( $block_size === 0 || $numLines === 0 ) {
$text = fread( $handle, ( $block_size === 0 ? 8192 : $block_size ) ) . $text;
}
}
fclose( $handle );
\OC_FileProxy::$enabled = true;
return $text;
}
/**
* @brief Check if a given path identifies an encrypted file
* @return true / false
@ -338,7 +380,7 @@ class Util {
\OC_FileProxy::$enabled = true;
return Crypt::isCatfile( $data );
return Crypt::isCatfileContent( $data );
}
@ -403,22 +445,32 @@ class Util {
// Encrypt unencrypted files
foreach ( $found['plain'] as $plainFile ) {
// Open plain file handle
// Fetch data from file
$plainData = $this->view->file_get_contents( $plainFile['path'] );
// Encrypt data, generate catfile
$encrypted = Crypt::keyEncryptKeyfile( $plainData, $publicKey );
// Open enc file handle
// Read plain file in chunks
$relPath = $this->stripUserFilesPath( $plainFile['path'] );
// Save keyfile
Keymanager::setFileKey( $this->view, $relPath, $this->userId, $encrypted['key'] );
// Open handle with for binary reading
$plainHandle = $this->view->fopen( $plainFile['path'], 'rb' );
// Open handle with for binary writing
$encHandle = fopen( 'crypt://' . 'var/www/oc6/data/' . $plainFile['path'] . '.tmp', 'ab' );
// Overwrite the existing file with the encrypted one
$this->view->file_put_contents( $plainFile['path'], $encrypted['data'] );
//$this->view->file_put_contents( $plainFile['path'], $encrypted['data'] );
$size = stream_copy_to_stream( $plainHandle, $encHandle );
$size = strlen( $encrypted['data'] );
// Fetch the key that has just been set/updated by the stream
$encKey = Keymanager::getFileKey( $relPath );
// Save keyfile
Keymanager::setFileKey( $this->view, $relPath, $this->userId, $encKey );
// Add the file to the cache
\OC\Files\Filesystem::putFileInfo( $plainFile['path'], array( 'encrypted'=>true, 'size' => $size ), '' );

View file

@ -416,13 +416,13 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase {
function testIsEncryptedContent() {
$this->assertFalse( Encryption\Crypt::isCatfile( $this->dataUrl ) );
$this->assertFalse( Encryption\Crypt::isCatfileContent( $this->dataUrl ) );
$this->assertFalse( Encryption\Crypt::isCatfile( $this->legacyEncryptedData ) );
$this->assertFalse( Encryption\Crypt::isCatfileContent( $this->legacyEncryptedData ) );
$keyfileContent = Encryption\Crypt::symmetricEncryptFileContent( $this->dataUrl, 'hat' );
$this->assertTrue( Encryption\Crypt::isCatfile( $keyfileContent ) );
$this->assertTrue( Encryption\Crypt::isCatfileContent( $keyfileContent ) );
}