Fixed stream wrapper bugs
Switched encryptAll() to use stream-based instead of file-at-a-time encryption Development snapshot
This commit is contained in:
parent
f2b86d0227
commit
c1f1fbda08
5 changed files with 99 additions and 59 deletions
|
@ -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;
|
||||
|
|
|
@ -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, '' );
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 ), '' );
|
||||
|
|
|
@ -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 ) );
|
||||
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue