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
|
@ -114,7 +114,7 @@ class Crypt {
|
||||||
* @return true / false
|
* @return true / false
|
||||||
* @note see also OCA\Encryption\Util->isEncryptedPath()
|
* @note see also OCA\Encryption\Util->isEncryptedPath()
|
||||||
*/
|
*/
|
||||||
public static function isCatfile( $content ) {
|
public static function isCatfileContent( $content ) {
|
||||||
|
|
||||||
if ( !$content ) {
|
if ( !$content ) {
|
||||||
|
|
||||||
|
@ -179,7 +179,7 @@ class Crypt {
|
||||||
if (
|
if (
|
||||||
isset( $metadata['encrypted'] )
|
isset( $metadata['encrypted'] )
|
||||||
and $metadata['encrypted'] === true
|
and $metadata['encrypted'] === true
|
||||||
and ! self::isCatfile( $data )
|
and ! self::isCatfileContent( $data )
|
||||||
) {
|
) {
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -74,7 +74,7 @@ class Proxy extends \OC_FileProxy {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( Crypt::isCatfile( $path ) ) {
|
if ( Crypt::isCatfileContent( $path ) ) {
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
@ -209,7 +209,7 @@ class Proxy extends \OC_FileProxy {
|
||||||
// If data is a catfile
|
// If data is a catfile
|
||||||
if (
|
if (
|
||||||
Crypt::mode() == 'server'
|
Crypt::mode() == 'server'
|
||||||
&& Crypt::isCatfile( $data )
|
&& Crypt::isCatfileContent( $data )
|
||||||
) {
|
) {
|
||||||
|
|
||||||
// TODO use get owner to find correct location of key files for shared files
|
// 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 ) {
|
public function postGetMimeType( $path, $mime ) {
|
||||||
|
|
||||||
if ( Crypt::isCatfile( $path ) ) {
|
if ( Crypt::isCatfileContent( $path ) ) {
|
||||||
|
|
||||||
$mime = \OCP\Files::getMimeType( 'crypt://' . $path, 'w' );
|
$mime = \OCP\Files::getMimeType( 'crypt://' . $path, 'w' );
|
||||||
|
|
||||||
|
@ -451,7 +451,7 @@ class Proxy extends \OC_FileProxy {
|
||||||
|
|
||||||
public function postStat( $path, $data ) {
|
public function postStat( $path, $data ) {
|
||||||
|
|
||||||
if ( Crypt::isCatfile( $path ) ) {
|
if ( Crypt::isCatfileContent( $path ) ) {
|
||||||
|
|
||||||
$cached = \OC\Files\Filesystem::getFileInfo( $path, '' );
|
$cached = \OC\Files\Filesystem::getFileInfo( $path, '' );
|
||||||
|
|
||||||
|
@ -464,7 +464,7 @@ class Proxy extends \OC_FileProxy {
|
||||||
|
|
||||||
public function postFileSize( $path, $size ) {
|
public function postFileSize( $path, $size ) {
|
||||||
|
|
||||||
if ( Crypt::isCatfile( $path ) ) {
|
if ( Crypt::isCatfileContent( $path ) ) {
|
||||||
|
|
||||||
$cached = \OC\Files\Filesystem::getFileInfo( $path, '' );
|
$cached = \OC\Files\Filesystem::getFileInfo( $path, '' );
|
||||||
|
|
||||||
|
|
|
@ -71,39 +71,30 @@ class Stream {
|
||||||
|
|
||||||
$this->userId = \OCP\User::getUser();
|
$this->userId = \OCP\User::getUser();
|
||||||
|
|
||||||
// Get access to filesystem via filesystemview object
|
if ( ! isset( $this->rootView ) ) {
|
||||||
if ( !self::$view ) {
|
|
||||||
|
|
||||||
self::$view = new \OC_FilesystemView( $this->userId . '/' );
|
$this->rootView = new \OC_FilesystemView( '/' );
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set rootview object if necessary
|
// Strip identifier text from path
|
||||||
if ( ! $this->rootView ) {
|
$this->rawPath = str_replace( 'crypt://', '', $path );
|
||||||
|
|
||||||
$this->rootView = new \OC_FilesystemView( $this->userId . '/' );
|
// Set file path relative to user files dir
|
||||||
|
$this->relPath = $this->userId . '/files/' . $this->rawPath;
|
||||||
}
|
|
||||||
|
|
||||||
// Get the bare file path
|
|
||||||
$path = str_replace( 'crypt://', '', $path );
|
|
||||||
|
|
||||||
$this->rawPath = $path;
|
|
||||||
|
|
||||||
$this->path_f = $this->userId . '/files/' . $path;
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
dirname( $path ) == 'streams'
|
dirname( $this->rawPath ) == 'streams'
|
||||||
and isset( self::$sourceStreams[basename( $path )] )
|
and isset( self::$sourceStreams[basename( $this->rawPath )] )
|
||||||
) {
|
) {
|
||||||
|
|
||||||
// Is this just for unit testing purposes?
|
// 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 {
|
} else {
|
||||||
|
|
||||||
|
@ -114,41 +105,38 @@ class Stream {
|
||||||
or $mode == 'wb+'
|
or $mode == 'wb+'
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
// We're writing a new file so start write counter with 0 bytes
|
||||||
$this->size = 0;
|
$this->size = 0;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
|
$this->size = $this->rootView->filesize( $this->relPath, $mode );
|
||||||
|
|
||||||
|
//$this->size = filesize( $this->rawPath );
|
||||||
$this->size = self::$view->filesize( $this->path_f, $mode );
|
|
||||||
|
|
||||||
//$this->size = filesize( $path );
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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( $this->rawPath, $mode );
|
||||||
|
|
||||||
$this->handle = self::$view->fopen( $this->path_f, $mode );
|
$this->handle = $this->rootView->fopen( $this->relPath, $mode );
|
||||||
|
|
||||||
\OC_FileProxy::$enabled = true;
|
\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 );
|
return is_resource( $this->handle );
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -238,7 +226,7 @@ class Stream {
|
||||||
|
|
||||||
// If a keyfile already exists for a file named identically to
|
// If a keyfile already exists for a file named identically to
|
||||||
// file to be written
|
// 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
|
// TODO: add error handling for when file exists but no
|
||||||
// keyfile
|
// keyfile
|
||||||
|
|
|
@ -24,13 +24,12 @@
|
||||||
# Bugs
|
# Bugs
|
||||||
# ----
|
# ----
|
||||||
# Sharing a file to a user without encryption set up will not provide them with access but won't notify the sharer
|
# 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
|
# Timeouts on first login due to encryption of very large files
|
||||||
|
|
||||||
|
|
||||||
# Missing features
|
# 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
|
# Make sure user knows if large files weren't encrypted
|
||||||
# Trashbin support
|
# Trashbin support
|
||||||
|
|
||||||
|
@ -280,14 +279,14 @@ class Util {
|
||||||
// will eat server resources :(
|
// will eat server resources :(
|
||||||
if (
|
if (
|
||||||
Keymanager::getFileKey( $this->view, $this->userId, $file )
|
Keymanager::getFileKey( $this->view, $this->userId, $file )
|
||||||
&& Crypt::isCatfile( $data )
|
&& Crypt::isCatfileContent( $data )
|
||||||
) {
|
) {
|
||||||
|
|
||||||
$found['encrypted'][] = array( 'name' => $file, 'path' => $filePath );
|
$found['encrypted'][] = array( 'name' => $file, 'path' => $filePath );
|
||||||
|
|
||||||
// If the file uses old
|
// If the file uses old
|
||||||
// encryption system
|
// 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 );
|
$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
|
* @brief Check if a given path identifies an encrypted file
|
||||||
* @return true / false
|
* @return true / false
|
||||||
|
@ -338,7 +380,7 @@ class Util {
|
||||||
|
|
||||||
\OC_FileProxy::$enabled = true;
|
\OC_FileProxy::$enabled = true;
|
||||||
|
|
||||||
return Crypt::isCatfile( $data );
|
return Crypt::isCatfileContent( $data );
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -404,21 +446,31 @@ class Util {
|
||||||
// Encrypt unencrypted files
|
// Encrypt unencrypted files
|
||||||
foreach ( $found['plain'] as $plainFile ) {
|
foreach ( $found['plain'] as $plainFile ) {
|
||||||
|
|
||||||
// Fetch data from file
|
// Open plain file handle
|
||||||
$plainData = $this->view->file_get_contents( $plainFile['path'] );
|
|
||||||
|
|
||||||
|
// Open enc file handle
|
||||||
|
|
||||||
|
|
||||||
|
// Read plain file in chunks
|
||||||
|
|
||||||
// Encrypt data, generate catfile
|
|
||||||
$encrypted = Crypt::keyEncryptKeyfile( $plainData, $publicKey );
|
|
||||||
|
|
||||||
$relPath = $this->stripUserFilesPath( $plainFile['path'] );
|
$relPath = $this->stripUserFilesPath( $plainFile['path'] );
|
||||||
|
|
||||||
// Save keyfile
|
// Open handle with for binary reading
|
||||||
Keymanager::setFileKey( $this->view, $relPath, $this->userId, $encrypted['key'] );
|
$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
|
// 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
|
// Add the file to the cache
|
||||||
\OC\Files\Filesystem::putFileInfo( $plainFile['path'], array( 'encrypted'=>true, 'size' => $size ), '' );
|
\OC\Files\Filesystem::putFileInfo( $plainFile['path'], array( 'encrypted'=>true, 'size' => $size ), '' );
|
||||||
|
|
|
@ -416,13 +416,13 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase {
|
||||||
|
|
||||||
function testIsEncryptedContent() {
|
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' );
|
$keyfileContent = Encryption\Crypt::symmetricEncryptFileContent( $this->dataUrl, 'hat' );
|
||||||
|
|
||||||
$this->assertTrue( Encryption\Crypt::isCatfile( $keyfileContent ) );
|
$this->assertTrue( Encryption\Crypt::isCatfileContent( $keyfileContent ) );
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue