2012-08-16 22:18:18 +04:00
< ? php
/**
* ownCloud
*
2014-11-05 16:42:36 +03:00
* @ copyright ( C ) 2014 ownCloud , Inc .
*
* @ author Bjoern Schiessle < schiessle @ owncloud . com >
* @ author Robin Appelman < icewind @ owncloud . com >
* @ author Sam Tuke < samtuke @ owncloud . com >
2012-08-16 22:18:18 +04:00
*
* This library is free software ; you can redistribute it and / or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation ; either
* version 3 of the License , or any later version .
*
* This library is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU AFFERO GENERAL PUBLIC LICENSE for more details .
*
* You should have received a copy of the GNU Affero General Public
* License along with this library . If not , see < http :// www . gnu . org / licenses />.
*
*/
/**
* transparently encrypted filestream
*
* you can use it as wrapper around an existing stream by setting CryptStream :: $sourceStreams [ 'foo' ] = array ( 'path' => $path , 'stream' => $stream )
2012-12-12 21:39:43 +04:00
* and then fopen ( 'crypt://streams/foo' );
2012-08-16 22:18:18 +04:00
*/
2014-12-03 12:57:16 +03:00
namespace OCA\Files_Encryption ;
2014-12-03 18:52:44 +03:00
use OCA\Files_Encryption\Exception\EncryptionException ;
2012-08-16 22:18:18 +04:00
2012-10-10 21:40:59 +04:00
/**
2014-05-19 19:50:53 +04:00
* Provides 'crypt://' stream wrapper protocol .
2013-05-20 03:24:36 +04:00
* @ note We use a stream wrapper because it is the most secure way to handle
2012-11-16 22:31:37 +04:00
* decrypted content transfers . There is no safe way to decrypt the entire file
* somewhere on the server , so we have to encrypt and decrypt blocks on the fly .
* @ note Paths used with this protocol MUST BE RELATIVE . Use URLs like :
2013-05-20 03:24:36 +04:00
* crypt :// filename , or crypt :// subdirectory / filename , NOT
* crypt :/// home / user / owncloud / data . Otherwise keyfiles will be put in
* [ owncloud ] / data / user / files_encryption / keyfiles / home / user / owncloud / data and
2012-12-12 21:39:43 +04:00
* will not be accessible to other methods .
2013-05-20 03:24:36 +04:00
* @ 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
2012-11-16 22:31:37 +04:00
* a 8192 encrypted block size .
2013-05-20 03:24:36 +04:00
* @ note When files are deleted via webdav , or when they are updated and the
* previous version deleted , this is handled by OC\Files\View , and thus the
2013-04-16 20:29:22 +04:00
* encryption proxies are used and keyfiles deleted .
2012-10-10 21:40:59 +04:00
*/
2013-05-27 19:26:58 +04:00
class Stream {
2014-07-21 15:02:28 +04:00
const PADDING_CHAR = '-' ;
2013-05-20 03:24:36 +04:00
private $plainKey ;
private $encKeyfiles ;
private $rawPath ; // The raw path relative to the data dir
private $relPath ; // rel path to users file dir
2012-11-16 22:31:37 +04:00
private $userId ;
2013-11-21 13:09:07 +04:00
private $keyId ;
2012-09-11 16:40:45 +04:00
private $handle ; // Resource returned by fopen
2012-08-16 22:18:18 +04:00
private $meta = array (); // Header / meta for source stream
private $writeCache ;
2013-05-20 03:24:36 +04:00
private $size ;
private $unencryptedSize ;
2012-12-11 19:10:56 +04:00
private $publicKey ;
private $encKeyfile ;
2013-07-30 12:43:16 +04:00
private $newFile ; // helper var, we only need to write the keyfile for new files
2013-12-17 21:13:46 +04:00
private $isLocalTmpFile = false ; // do we operate on a local tmp file
private $localTmpFile ; // path of local tmp file
2014-07-21 15:02:28 +04:00
private $headerWritten = false ;
private $containHeader = false ; // the file contain a header
private $cipher ; // cipher used for encryption/decryption
2015-01-08 22:57:49 +03:00
/** @var \OCA\Files_Encryption\Util */
private $util ;
2013-12-17 21:13:46 +04:00
2013-05-31 18:52:33 +04:00
/**
* @ var \OC\Files\View
*/
2013-01-05 21:12:23 +04:00
private $rootView ; // a fsview object set to '/'
2013-11-12 18:55:59 +04:00
2013-05-31 18:52:33 +04:00
/**
2014-12-03 18:52:44 +03:00
* @ var \OCA\Files_Encryption\Session
2013-05-31 18:52:33 +04:00
*/
private $session ;
private $privateKey ;
2012-08-16 22:18:18 +04:00
2013-05-20 03:24:36 +04:00
/**
2014-05-13 15:29:25 +04:00
* @ param string $path raw path relative to data /
* @ param string $mode
* @ param int $options
* @ param string $opened_path
2013-05-20 03:24:36 +04:00
* @ return bool
2014-12-03 18:52:44 +03:00
* @ throw \OCA\Files_Encryption\Exception\EncryptionException
2013-05-20 03:24:36 +04:00
*/
2013-05-27 19:26:58 +04:00
public function stream_open ( $path , $mode , $options , & $opened_path ) {
2013-08-30 12:17:50 +04:00
2014-07-21 15:02:28 +04:00
// read default cipher from config
$this -> cipher = Helper :: getCipher ();
2013-07-30 12:43:16 +04:00
// assume that the file already exist before we decide it finally in getKey()
$this -> newFile = false ;
2013-01-05 21:12:23 +04:00
2015-01-08 22:57:49 +03:00
$this -> rootView = new \OC\Files\View ( '/' );
2013-03-04 20:58:56 +04:00
2014-12-03 18:52:44 +03:00
$this -> session = new Session ( $this -> rootView );
2013-05-31 18:52:33 +04:00
2013-11-21 02:23:23 +04:00
$this -> privateKey = $this -> session -> getPrivateKey ();
2014-11-04 19:17:29 +03:00
if ( $this -> privateKey === false ) {
throw new EncryptionException ( 'Session does not contain a private key, maybe your login password changed?' ,
2014-11-05 16:42:36 +03:00
EncryptionException :: PRIVATE_KEY_MISSING );
2014-11-04 19:17:29 +03:00
}
2013-05-31 18:52:33 +04:00
2013-12-17 21:13:46 +04:00
$normalizedPath = \OC\Files\Filesystem :: normalizePath ( str_replace ( 'crypt://' , '' , $path ));
2015-01-08 22:57:49 +03:00
$originalFile = Helper :: getPathFromTmpFile ( $normalizedPath );
if ( $originalFile ) {
2013-12-17 21:13:46 +04:00
$this -> rawPath = $originalFile ;
$this -> isLocalTmpFile = true ;
$this -> localTmpFile = $normalizedPath ;
} else {
$this -> rawPath = $normalizedPath ;
}
2013-05-13 23:24:59 +04:00
2015-01-08 22:57:49 +03:00
$this -> util = new Util ( $this -> rootView , Helper :: getUser ( $this -> rawPath ));
2013-11-21 02:23:23 +04:00
2013-11-27 14:46:24 +04:00
// get the key ID which we want to use, can be the users key or the
2013-11-21 13:09:07 +04:00
// public share key
2015-01-08 22:57:49 +03:00
$this -> keyId = $this -> util -> getKeyId ();
2013-05-20 03:24:36 +04:00
2015-01-08 22:57:49 +03:00
$fileType = Helper :: detectFileType ( $this -> rawPath );
2013-08-30 12:17:50 +04:00
2015-01-08 22:57:49 +03:00
switch ( $fileType ) {
case Util :: FILE_TYPE_FILE :
$this -> relPath = Helper :: stripUserFilesPath ( $this -> rawPath );
$this -> userId = \OC :: $server -> getUserSession () -> getUser () -> getUID ();
break ;
case Util :: FILE_TYPE_VERSION :
$this -> relPath = Helper :: getPathFromVersion ( $this -> rawPath );
$this -> userId = Helper :: getUserFromPath ( $this -> rawPath );
break ;
case Util :: FILE_TYPE_CACHE :
$this -> relPath = Helper :: getPathFromCachedFile ( $this -> rawPath );
Helper :: mkdirr ( $this -> rawPath , new \OC\Files\View ( '/' ));
$this -> userId = \OC :: $server -> getUserSession () -> getUser () -> getUID ();
break ;
default :
\OCP\Util :: writeLog ( 'Encryption library' , 'failed to open file "' . $this -> rawPath . '" expecting a path to "files", "files_versions" or "cache"' , \OCP\Util :: ERROR );
return false ;
2013-07-30 17:27:59 +04:00
}
2013-08-30 12:17:50 +04:00
2013-05-22 02:53:07 +04:00
// Disable fileproxies so we can get the file size and open the source file without recursive encryption
$proxyStatus = \OC_FileProxy :: $enabled ;
\OC_FileProxy :: $enabled = false ;
2013-04-26 00:49:47 +04:00
if (
2013-05-27 22:44:38 +04:00
$mode === 'w'
or $mode === 'w+'
or $mode === 'wb'
or $mode === 'wb+'
2012-08-16 22:18:18 +04:00
) {
2013-05-22 02:53:07 +04:00
// We're writing a new file so start write counter with 0 bytes
$this -> size = 0 ;
$this -> unencryptedSize = 0 ;
2012-08-16 22:18:18 +04:00
} else {
2013-12-17 21:13:46 +04:00
$this -> size = $this -> rootView -> filesize ( $this -> rawPath );
2014-07-21 15:02:28 +04:00
$this -> readHeader ();
2013-05-22 02:53:07 +04:00
}
2013-05-20 03:24:36 +04:00
2013-12-17 21:13:46 +04:00
if ( $this -> isLocalTmpFile ) {
$this -> handle = fopen ( $this -> localTmpFile , $mode );
} else {
$this -> handle = $this -> rootView -> fopen ( $this -> rawPath , $mode );
}
2013-05-20 03:24:36 +04:00
2013-05-22 02:53:07 +04:00
\OC_FileProxy :: $enabled = $proxyStatus ;
2013-05-20 03:24:36 +04:00
2013-05-27 19:26:58 +04:00
if ( ! is_resource ( $this -> handle )) {
2012-08-16 22:18:18 +04:00
2013-05-31 15:58:58 +04:00
\OCP\Util :: writeLog ( 'Encryption library' , 'failed to open file "' . $this -> rawPath . '"' , \OCP\Util :: ERROR );
2012-08-16 22:18:18 +04:00
2013-05-22 02:53:07 +04:00
} else {
2012-08-16 22:18:18 +04:00
2013-05-27 19:26:58 +04:00
$this -> meta = stream_get_meta_data ( $this -> handle );
2014-02-27 16:58:51 +04:00
// sometimes fopen changes the mode, e.g. for a url "r" convert to "r+"
// but we need to remember the original access type
$this -> meta [ 'mode' ] = $mode ;
2013-05-20 03:24:36 +04:00
2013-05-22 02:53:07 +04:00
}
2013-05-20 03:24:36 +04:00
2012-08-16 22:18:18 +04:00
2013-05-27 19:26:58 +04:00
return is_resource ( $this -> handle );
2012-08-16 22:18:18 +04:00
}
2013-05-20 03:24:36 +04:00
2014-07-21 15:02:28 +04:00
private function readHeader () {
if ( $this -> isLocalTmpFile ) {
$handle = fopen ( $this -> localTmpFile , 'r' );
} else {
$handle = $this -> rootView -> fopen ( $this -> rawPath , 'r' );
}
if ( is_resource ( $handle )) {
$data = fread ( $handle , Crypt :: BLOCKSIZE );
$header = Crypt :: parseHeader ( $data );
$this -> cipher = Crypt :: getCipher ( $header );
// remeber that we found a header
if ( ! empty ( $header )) {
$this -> containHeader = true ;
}
fclose ( $handle );
}
}
2013-12-18 18:40:43 +04:00
/**
2014-05-19 19:50:53 +04:00
* Returns the current position of the file pointer
2014-05-16 00:47:28 +04:00
* @ return int position of the file pointer
2013-12-18 18:40:43 +04:00
*/
public function stream_tell () {
return ftell ( $this -> handle );
}
2013-05-20 03:24:36 +04:00
/**
2014-05-13 15:29:25 +04:00
* @ param int $offset
2013-05-20 03:24:36 +04:00
* @ param int $whence
2013-12-18 18:40:43 +04:00
* @ return bool true if fseek was successful , otherwise false
2013-05-20 03:24:36 +04:00
*/
2013-05-27 19:26:58 +04:00
public function stream_seek ( $offset , $whence = SEEK_SET ) {
2013-05-20 03:24:36 +04:00
2012-08-16 22:18:18 +04:00
$this -> flush ();
2013-05-20 03:24:36 +04:00
2014-07-21 15:02:28 +04:00
// ignore the header and just overstep it
if ( $this -> containHeader ) {
$offset += Crypt :: BLOCKSIZE ;
}
2013-12-18 18:40:43 +04:00
// this wrapper needs to return "true" for success.
// the fseek call itself returns 0 on succeess
return ! fseek ( $this -> handle , $offset , $whence );
2013-05-20 03:24:36 +04:00
2012-08-16 22:18:18 +04:00
}
2013-05-20 03:24:36 +04:00
/**
2014-05-13 15:29:25 +04:00
* @ param int $count
2013-05-20 03:24:36 +04:00
* @ return bool | string
2014-12-03 18:52:44 +03:00
* @ throws \OCA\Files_Encryption\Exception\EncryptionException
2013-05-20 03:24:36 +04:00
*/
2013-05-27 19:26:58 +04:00
public function stream_read ( $count ) {
2013-05-20 03:24:36 +04:00
2012-08-23 19:43:10 +04:00
$this -> writeCache = '' ;
2014-07-21 15:02:28 +04:00
if ( $count !== Crypt :: BLOCKSIZE ) {
2013-05-31 15:58:58 +04:00
\OCP\Util :: writeLog ( 'Encryption library' , 'PHP "bug" 21641 no longer holds, decryption system requires refactoring' , \OCP\Util :: FATAL );
2014-11-05 16:42:36 +03:00
throw new EncryptionException ( 'expected a blog size of 8192 byte' , EncryptionException :: UNEXPECTED_BLOG_SIZE );
2012-08-16 22:18:18 +04:00
}
2012-08-23 19:43:10 +04:00
2012-10-17 19:35:19 +04:00
// Get the data from the file handle
2013-09-04 23:15:06 +04:00
$data = fread ( $this -> handle , $count );
2013-05-20 03:24:36 +04:00
2014-07-21 15:02:28 +04:00
// if this block contained the header we move on to the next block
if ( Crypt :: isHeader ( $data )) {
$data = fread ( $this -> handle , $count );
}
2013-05-31 15:58:58 +04:00
$result = null ;
2013-05-20 03:24:36 +04:00
2013-05-27 19:26:58 +04:00
if ( strlen ( $data )) {
2013-05-20 03:24:36 +04:00
2013-05-27 19:26:58 +04:00
if ( ! $this -> getKey ()) {
2013-05-20 03:24:36 +04:00
2013-04-16 20:29:22 +04:00
// Error! We don't have a key to decrypt the file with
2013-05-27 19:26:58 +04:00
throw new \Exception (
'Encryption key not found for "' . $this -> rawPath . '" during attempted read via stream' );
2013-05-20 03:24:36 +04:00
2013-05-31 15:58:58 +04:00
} else {
2013-05-20 03:24:36 +04:00
2013-05-31 15:58:58 +04:00
// Decrypt data
2014-07-21 15:02:28 +04:00
$result = Crypt :: symmetricDecryptFileContent ( $data , $this -> plainKey , $this -> cipher );
2013-05-31 15:58:58 +04:00
}
2012-08-23 19:43:10 +04:00
2013-05-20 03:24:36 +04:00
}
2012-08-23 19:43:10 +04:00
2012-08-16 22:18:18 +04:00
return $result ;
2012-08-23 19:43:10 +04:00
}
2013-05-20 03:24:36 +04:00
2012-10-16 18:02:51 +04:00
/**
2014-05-19 19:50:53 +04:00
* Encrypt and pad data ready for writing to disk
2012-10-16 18:02:51 +04:00
* @ param string $plainData data to be encrypted
* @ param string $key key to use for encryption
2013-05-20 03:24:36 +04:00
* @ return string encrypted data on success , false on failure
2012-10-16 18:02:51 +04:00
*/
2013-05-27 19:26:58 +04:00
public function preWriteEncrypt ( $plainData , $key ) {
2013-05-20 03:24:36 +04:00
2012-10-16 18:02:51 +04:00
// Encrypt data to 'catfile', which includes IV
2014-07-21 15:02:28 +04:00
if ( $encrypted = Crypt :: symmetricEncryptFileContent ( $plainData , $key , $this -> cipher )) {
2013-05-20 03:24:36 +04:00
return $encrypted ;
2012-10-16 18:02:51 +04:00
} else {
2013-05-20 03:24:36 +04:00
2012-10-16 18:02:51 +04:00
return false ;
2013-05-20 03:24:36 +04:00
2012-10-16 18:02:51 +04:00
}
2013-05-20 03:24:36 +04:00
2012-10-16 18:02:51 +04:00
}
2013-05-20 03:24:36 +04:00
2012-08-23 19:43:10 +04:00
/**
2014-05-19 19:50:53 +04:00
* Fetch the plain encryption key for the file and set it as plainKey property
2013-05-20 03:24:36 +04:00
* @ internal param bool $generate if true , a new key will be generated if none can be found
2012-10-16 18:02:51 +04:00
* @ return bool true on key found and set , false on key not found and new key generated and set
2012-08-23 19:43:10 +04:00
*/
2013-05-24 01:56:31 +04:00
public function getKey () {
2013-05-20 03:24:36 +04:00
2013-04-16 20:29:22 +04:00
// Check if key is already set
2013-05-27 19:26:58 +04:00
if ( isset ( $this -> plainKey ) && isset ( $this -> encKeyfile )) {
2013-05-20 03:24:36 +04:00
2013-04-16 20:29:22 +04:00
return true ;
2013-05-20 03:24:36 +04:00
2013-04-16 20:29:22 +04:00
}
2013-05-20 03:24:36 +04:00
2015-01-08 22:57:49 +03:00
$util = new Util ( $this -> rootView , $this -> userId );
2013-04-26 00:49:47 +04:00
// Fetch and decrypt keyfile
2013-05-20 03:24:36 +04:00
// Fetch existing keyfile
2013-11-20 21:10:56 +04:00
$this -> encKeyfile = Keymanager :: getFileKey ( $this -> rootView , $util , $this -> relPath );
2013-04-22 06:40:49 +04:00
2013-04-16 20:29:22 +04:00
// If a keyfile already exists
2013-05-27 19:26:58 +04:00
if ( $this -> encKeyfile ) {
2013-05-20 03:24:36 +04:00
2013-11-21 13:09:07 +04:00
$shareKey = Keymanager :: getShareKey ( $this -> rootView , $this -> keyId , $util , $this -> relPath );
2013-10-11 16:20:46 +04:00
2013-05-31 15:58:58 +04:00
// if there is no valid private key return false
2013-05-31 18:52:33 +04:00
if ( $this -> privateKey === false ) {
2013-06-04 01:41:57 +04:00
// if private key is not valid redirect user to a error page
2014-12-03 18:52:44 +03:00
Helper :: redirectToErrorPage ( $this -> session );
2013-05-31 15:58:58 +04:00
return false ;
}
2013-10-11 16:20:46 +04:00
if ( $shareKey === false ) {
// if no share key is available redirect user to a error page
2014-12-03 18:52:44 +03:00
Helper :: redirectToErrorPage ( $this -> session , Crypt :: ENCRYPTION_NO_SHARE_KEY_FOUND );
2013-10-11 16:20:46 +04:00
return false ;
}
2013-05-20 03:24:36 +04:00
2013-05-31 18:52:33 +04:00
$this -> plainKey = Crypt :: multiKeyDecrypt ( $this -> encKeyfile , $shareKey , $this -> privateKey );
2013-05-20 03:24:36 +04:00
2012-10-16 18:02:51 +04:00
return true ;
2013-05-20 03:24:36 +04:00
2012-08-23 19:43:10 +04:00
} else {
2013-05-20 03:24:36 +04:00
2013-07-30 12:43:16 +04:00
$this -> newFile = true ;
2013-08-30 12:17:50 +04:00
2012-12-11 19:10:56 +04:00
return false ;
2013-05-20 03:24:36 +04:00
2012-12-11 19:10:56 +04:00
}
2013-05-20 03:24:36 +04:00
2012-12-11 19:10:56 +04:00
}
2013-05-20 03:24:36 +04:00
2014-07-21 15:02:28 +04:00
/**
* write header at beginning of encrypted file
*
2014-12-03 18:52:44 +03:00
* @ throws \OCA\Files_Encryption\Exception\EncryptionException
2014-07-21 15:02:28 +04:00
*/
private function writeHeader () {
$header = Crypt :: generateHeader ();
if ( strlen ( $header ) > Crypt :: BLOCKSIZE ) {
2014-11-05 16:42:36 +03:00
throw new EncryptionException ( 'max header size exceeded' , EncryptionException :: ENCRYPTION_HEADER_TO_LARGE );
2014-07-21 15:02:28 +04:00
}
$paddedHeader = str_pad ( $header , Crypt :: BLOCKSIZE , self :: PADDING_CHAR , STR_PAD_RIGHT );
fwrite ( $this -> handle , $paddedHeader );
$this -> headerWritten = true ;
}
2012-08-16 22:18:18 +04:00
/**
2014-05-19 19:50:53 +04:00
* Handle plain data from the stream , and write it in 8192 byte blocks
2012-10-10 21:40:59 +04:00
* @ 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 ()
2012-10-16 18:02:51 +04:00
* @ note $data is only ever be a maximum of 8192 bytes long . This is set by PHP internally . stream_write () is called multiple times in a loop on data larger than 8192 bytes
* @ note Because the encryption process used increases the length of $data , a writeCache is used to carry over data which would not fit in the required block size
* @ note Padding is added to each encrypted block to ensure that the resulting block is exactly 8192 bytes . This is removed during stream_read
* @ note PHP automatically updates the file pointer after writing data to reflect it ' s length . There is generally no need to update the poitner manually using fseek
2012-08-16 22:18:18 +04:00
*/
2013-05-27 19:26:58 +04:00
public function stream_write ( $data ) {
2013-05-20 03:24:36 +04:00
2013-05-31 18:52:33 +04:00
// if there is no valid private key return false
if ( $this -> privateKey === false ) {
$this -> size = 0 ;
return strlen ( $data );
}
2014-07-21 15:02:28 +04:00
if ( $this -> headerWritten === false ) {
$this -> writeHeader ();
}
2013-08-30 12:17:50 +04:00
// Disable the file proxies so that encryption is not
// automatically attempted when the file is written to disk -
// we are handling that separately here and we don't want to
2013-01-24 22:37:34 +04:00
// get into an infinite loop
2013-05-20 03:24:36 +04:00
$proxyStatus = \OC_FileProxy :: $enabled ;
2013-04-29 03:43:59 +04:00
\OC_FileProxy :: $enabled = false ;
2013-05-20 03:24:36 +04:00
2012-10-16 18:02:51 +04:00
// Get the length of the unencrypted data that we are handling
2013-05-27 19:26:58 +04:00
$length = strlen ( $data );
2013-05-20 03:24:36 +04:00
2013-05-22 02:53:07 +04:00
// Find out where we are up to in the writing of data to the
2013-01-24 22:37:34 +04:00
// file
2013-05-27 19:26:58 +04:00
$pointer = ftell ( $this -> handle );
2013-05-20 03:24:36 +04:00
2012-10-16 18:02:51 +04:00
// Get / generate the keyfile for the file we're handling
2013-08-30 12:17:50 +04:00
// If we're writing a new file (not overwriting an existing
2013-01-14 23:07:28 +04:00
// one), save the newly generated keyfile
2013-05-27 19:26:58 +04:00
if ( ! $this -> getKey ()) {
2013-05-20 03:24:36 +04:00
2013-04-16 20:29:22 +04:00
$this -> plainKey = Crypt :: generateKey ();
2013-05-20 03:24:36 +04:00
2012-08-16 22:18:18 +04:00
}
2013-04-22 06:40:49 +04:00
2013-08-30 12:17:50 +04:00
// If extra data is left over from the last round, make sure it
2013-01-24 22:37:34 +04:00
// is integrated into the next 6126 / 8192 block
2013-05-27 19:26:58 +04:00
if ( $this -> writeCache ) {
2013-05-20 03:24:36 +04:00
2012-10-16 18:02:51 +04:00
// Concat writeCache to start of $data
$data = $this -> writeCache . $data ;
2013-05-20 03:24:36 +04:00
2013-05-24 01:56:31 +04:00
// Clear the write cache, ready for reuse - it has been
2013-01-24 22:37:34 +04:00
// flushed and its old contents processed
2012-10-16 18:02:51 +04:00
$this -> writeCache = '' ;
}
2012-10-10 21:40:59 +04:00
2013-05-24 01:56:31 +04:00
// While there still remains some data to be processed & written
2013-05-27 19:26:58 +04:00
while ( strlen ( $data ) > 0 ) {
2013-05-20 03:24:36 +04:00
2013-05-24 01:56:31 +04:00
// Remaining length for this iteration, not of the
2013-05-20 03:24:36 +04:00
// entire file (may be greater than 8192 bytes)
2013-05-27 19:26:58 +04:00
$remainingLength = strlen ( $data );
2013-05-20 03:24:36 +04:00
2013-05-24 01:56:31 +04:00
// If data remaining to be written is less than the
2013-05-20 03:24:36 +04:00
// size of 1 6126 byte block
2013-05-27 19:26:58 +04:00
if ( $remainingLength < 6126 ) {
2013-05-20 03:24:36 +04:00
2012-10-16 18:02:51 +04:00
// Set writeCache to contents of $data
2013-08-30 12:17:50 +04:00
// The writeCache will be carried over to the
// next write round, and added to the start of
// $data to ensure that written blocks are
// always the correct length. If there is still
// data in writeCache after the writing round
// has finished, then the data will be written
2013-01-24 22:37:34 +04:00
// to disk by $this->flush().
2012-10-16 18:02:51 +04:00
$this -> writeCache = $data ;
// Clear $data ready for next round
$data = '' ;
2013-05-20 03:24:36 +04:00
2012-10-16 18:02:51 +04:00
} else {
2013-05-20 03:24:36 +04:00
2012-10-10 21:40:59 +04:00
// Read the chunk from the start of $data
2013-05-27 19:26:58 +04:00
$chunk = substr ( $data , 0 , 6126 );
2013-05-20 03:24:36 +04:00
2013-05-27 19:26:58 +04:00
$encrypted = $this -> preWriteEncrypt ( $chunk , $this -> plainKey );
2013-05-20 03:24:36 +04:00
2013-08-30 12:17:50 +04:00
// Write the data chunk to disk. This will be
2013-02-09 21:01:38 +04:00
// attended to the last data chunk if the file
2013-01-24 22:37:34 +04:00
// being handled totals more than 6126 bytes
2013-05-27 19:26:58 +04:00
fwrite ( $this -> handle , $encrypted );
2013-05-20 03:24:36 +04:00
2013-05-22 02:53:07 +04:00
// Remove the chunk we just processed from
2013-01-24 22:37:34 +04:00
// $data, leaving only unprocessed data in $data
// var, for handling on the next round
2013-05-27 19:26:58 +04:00
$data = substr ( $data , 6126 );
2012-08-16 22:18:18 +04:00
2012-10-16 18:02:51 +04:00
}
2013-05-20 03:24:36 +04:00
2012-10-10 21:40:59 +04:00
}
2013-04-25 17:20:06 +04:00
2013-05-27 19:26:58 +04:00
$this -> size = max ( $this -> size , $pointer + $length );
2013-05-20 03:24:36 +04:00
$this -> unencryptedSize += $length ;
\OC_FileProxy :: $enabled = $proxyStatus ;
2013-04-29 03:43:59 +04:00
2012-08-16 22:18:18 +04:00
return $length ;
}
2013-05-20 03:24:36 +04:00
/**
2014-05-13 15:29:25 +04:00
* @ param int $option
* @ param int $arg1
* @ param int | null $arg2
2013-05-20 03:24:36 +04:00
*/
2013-05-27 19:26:58 +04:00
public function stream_set_option ( $option , $arg1 , $arg2 ) {
2013-05-22 02:53:07 +04:00
$return = false ;
2013-05-27 19:26:58 +04:00
switch ( $option ) {
2012-08-16 22:18:18 +04:00
case STREAM_OPTION_BLOCKING :
2013-05-27 19:26:58 +04:00
$return = stream_set_blocking ( $this -> handle , $arg1 );
2012-08-16 22:18:18 +04:00
break ;
case STREAM_OPTION_READ_TIMEOUT :
2013-05-27 19:26:58 +04:00
$return = stream_set_timeout ( $this -> handle , $arg1 , $arg2 );
2012-08-16 22:18:18 +04:00
break ;
case STREAM_OPTION_WRITE_BUFFER :
2013-05-27 19:26:58 +04:00
$return = stream_set_write_buffer ( $this -> handle , $arg1 );
2012-08-16 22:18:18 +04:00
}
2013-05-22 02:53:07 +04:00
return $return ;
2012-08-16 22:18:18 +04:00
}
2013-05-20 03:24:36 +04:00
/**
* @ return array
*/
2013-05-24 01:56:31 +04:00
public function stream_stat () {
2013-05-27 19:26:58 +04:00
return fstat ( $this -> handle );
2012-08-16 22:18:18 +04:00
}
2013-05-20 03:24:36 +04:00
/**
2014-05-13 15:29:25 +04:00
* @ param int $mode
2013-05-20 03:24:36 +04:00
*/
2013-05-27 19:26:58 +04:00
public function stream_lock ( $mode ) {
return flock ( $this -> handle , $mode );
2012-08-16 22:18:18 +04:00
}
2013-05-20 03:24:36 +04:00
/**
* @ return bool
*/
2013-05-24 01:56:31 +04:00
public function stream_flush () {
2013-05-20 03:24:36 +04:00
2013-05-27 19:26:58 +04:00
return fflush ( $this -> handle );
2013-01-24 22:37:34 +04:00
// Not a typo: http://php.net/manual/en/function.fflush.php
2013-05-20 03:24:36 +04:00
2012-08-16 22:18:18 +04:00
}
2013-05-20 03:24:36 +04:00
/**
* @ return bool
*/
2013-05-24 01:56:31 +04:00
public function stream_eof () {
2013-05-27 19:26:58 +04:00
return feof ( $this -> handle );
2012-08-16 22:18:18 +04:00
}
2013-05-24 01:56:31 +04:00
private function flush () {
2013-05-20 03:24:36 +04:00
2013-05-27 19:26:58 +04:00
if ( $this -> writeCache ) {
2013-05-20 03:24:36 +04:00
2012-09-11 16:40:45 +04:00
// Set keyfile property for file in question
$this -> getKey ();
2013-05-20 03:24:36 +04:00
2013-05-27 19:26:58 +04:00
$encrypted = $this -> preWriteEncrypt ( $this -> writeCache , $this -> plainKey );
2013-05-20 03:24:36 +04:00
2013-05-27 19:26:58 +04:00
fwrite ( $this -> handle , $encrypted );
2013-05-20 03:24:36 +04:00
2012-09-11 16:40:45 +04:00
$this -> writeCache = '' ;
2012-08-16 22:18:18 +04:00
}
}
2013-05-20 03:24:36 +04:00
/**
* @ return bool
*/
2013-05-24 01:56:31 +04:00
public function stream_close () {
2013-05-20 03:24:36 +04:00
$this -> flush ();
2013-04-29 03:43:59 +04:00
2013-05-31 17:57:18 +04:00
// if there is no valid private key return false
2013-05-31 18:52:33 +04:00
if ( $this -> privateKey === false ) {
2013-05-31 17:57:18 +04:00
2013-07-30 12:43:16 +04:00
// cleanup
2013-12-17 21:13:46 +04:00
if ( $this -> meta [ 'mode' ] !== 'r' && $this -> meta [ 'mode' ] !== 'rb' && ! $this -> isLocalTmpFile ) {
2013-05-31 18:52:33 +04:00
2013-07-30 12:43:16 +04:00
// Disable encryption proxy to prevent recursive calls
$proxyStatus = \OC_FileProxy :: $enabled ;
\OC_FileProxy :: $enabled = false ;
2013-05-31 18:52:33 +04:00
2013-07-30 12:43:16 +04:00
if ( $this -> rootView -> file_exists ( $this -> rawPath ) && $this -> size === 0 ) {
2014-11-06 18:53:35 +03:00
fclose ( $this -> handle );
2013-07-30 12:43:16 +04:00
$this -> rootView -> unlink ( $this -> rawPath );
2013-05-31 18:52:33 +04:00
}
2013-07-30 12:43:16 +04:00
// Re-enable proxy - our work is done
\OC_FileProxy :: $enabled = $proxyStatus ;
}
2013-06-04 01:41:57 +04:00
// if private key is not valid redirect user to a error page
2014-12-03 18:52:44 +03:00
Helper :: redirectToErrorPage ( $this -> session );
2013-05-31 17:57:18 +04:00
}
2013-05-20 03:24:36 +04:00
if (
2013-07-30 12:43:16 +04:00
$this -> meta [ 'mode' ] !== 'r' &&
$this -> meta [ 'mode' ] !== 'rb' &&
2013-12-17 21:13:46 +04:00
$this -> isLocalTmpFile === false &&
2013-11-20 19:20:21 +04:00
$this -> size > 0 &&
$this -> unencryptedSize > 0
2013-01-06 22:38:35 +04:00
) {
2013-09-04 23:15:06 +04:00
2013-07-30 12:43:16 +04:00
// only write keyfiles if it was a new file
if ( $this -> newFile === true ) {
2013-04-29 03:43:59 +04:00
2013-07-30 12:43:16 +04:00
// Disable encryption proxy to prevent recursive calls
$proxyStatus = \OC_FileProxy :: $enabled ;
\OC_FileProxy :: $enabled = false ;
2013-04-29 03:43:59 +04:00
2013-07-30 12:43:16 +04:00
// Fetch user's public key
2013-11-21 13:09:07 +04:00
$this -> publicKey = Keymanager :: getPublicKey ( $this -> rootView , $this -> keyId );
2013-04-29 03:43:59 +04:00
2013-07-30 12:43:16 +04:00
// Check if OC sharing api is enabled
$sharingEnabled = \OCP\Share :: isEnabled ();
2013-04-29 03:43:59 +04:00
2013-07-30 12:43:16 +04:00
// Get all users sharing the file includes current user
2015-01-08 22:57:49 +03:00
$uniqueUserIds = $this -> util -> getSharingUsersArray ( $sharingEnabled , $this -> relPath );
$checkedUserIds = $this -> util -> filterShareReadyUsers ( $uniqueUserIds );
2013-04-29 03:43:59 +04:00
2013-07-30 12:43:16 +04:00
// Fetch public keys for all sharing users
2013-10-09 17:56:21 +04:00
$publicKeys = Keymanager :: getPublicKeys ( $this -> rootView , $checkedUserIds [ 'ready' ]);
2013-04-29 03:43:59 +04:00
2013-07-30 12:43:16 +04:00
// Encrypt enc key for all sharing users
$this -> encKeyfiles = Crypt :: multiKeyEncrypt ( $this -> plainKey , $publicKeys );
2013-04-29 03:43:59 +04:00
2013-07-30 12:43:16 +04:00
// Save the new encrypted file key
2015-01-08 22:57:49 +03:00
Keymanager :: setFileKey ( $this -> rootView , $this -> util , $this -> relPath , $this -> encKeyfiles [ 'data' ]);
2013-07-30 12:43:16 +04:00
// Save the sharekeys
2015-01-08 22:57:49 +03:00
Keymanager :: setShareKeys ( $this -> rootView , $this -> util , $this -> relPath , $this -> encKeyfiles [ 'keys' ]);
2013-07-30 12:43:16 +04:00
// Re-enable proxy - our work is done
\OC_FileProxy :: $enabled = $proxyStatus ;
}
2013-04-29 03:43:59 +04:00
2013-11-12 18:55:59 +04:00
// we need to update the file info for the real file, not for the
// part file.
2013-11-12 21:48:31 +04:00
$path = Helper :: stripPartialFileExtension ( $this -> rawPath );
2013-11-12 18:55:59 +04:00
2014-03-31 14:43:38 +04:00
$fileInfo = array (
2014-10-27 14:51:52 +03:00
'mimetype' => $this -> rootView -> getMimeType ( $this -> rawPath ),
2014-03-31 14:43:38 +04:00
'encrypted' => true ,
'unencrypted_size' => $this -> unencryptedSize ,
);
2014-10-27 14:51:52 +03:00
// if we write a part file we also store the unencrypted size for
// the part file so that it can be re-used later
$this -> rootView -> putFileInfo ( $this -> rawPath , $fileInfo );
if ( $path !== $this -> rawPath ) {
$this -> rootView -> putFileInfo ( $path , $fileInfo );
}
2013-05-06 23:15:25 +04:00
2012-08-16 22:18:18 +04:00
}
2012-08-23 19:43:10 +04:00
2014-03-31 14:43:38 +04:00
$result = fclose ( $this -> handle );
if ( $result === false ) {
\OCP\Util :: writeLog ( 'Encryption library' , 'Could not close stream, file could be corrupted' , \OCP\Util :: FATAL );
}
return $result ;
2012-08-16 22:18:18 +04:00
}
2012-08-23 19:43:10 +04:00
2012-08-16 22:18:18 +04:00
}