2012-08-16 18:18:18 +00:00
< ? php
/**
* ownCloud
*
2014-07-21 11:02:28 +00:00
* @ author Bjoern Schiessle , Robin Appelman
* @ copyright 2014 Bjoern Schiessle < schiessle @ owncloud . com >
* 2012 Sam Tuke < samtuke @ owncloud . com > ,
* 2011 Robin Appelman < icewind1991 @ gmail . com >
2012-08-16 18:18:18 +00: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 17:39:43 +00:00
* and then fopen ( 'crypt://streams/foo' );
2012-08-16 18:18:18 +00:00
*/
2012-10-17 15:35:19 +00:00
namespace OCA\Encryption ;
2012-08-16 18:18:18 +00:00
2012-10-10 17:40:59 +00:00
/**
2014-05-19 15:50:53 +00:00
* Provides 'crypt://' stream wrapper protocol .
2013-05-19 23:24:36 +00:00
* @ note We use a stream wrapper because it is the most secure way to handle
2012-11-16 18:31:37 +00: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-19 23:24:36 +00: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 17:39:43 +00:00
* will not be accessible to other methods .
2013-05-19 23:24:36 +00: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 18:31:37 +00:00
* a 8192 encrypted block size .
2013-05-19 23:24:36 +00: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 16:29:22 +00:00
* encryption proxies are used and keyfiles deleted .
2012-10-10 17:40:59 +00:00
*/
2013-05-27 15:26:58 +00:00
class Stream {
2014-07-21 11:02:28 +00:00
const PADDING_CHAR = '-' ;
2013-05-19 23:24:36 +00: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 18:31:37 +00:00
private $userId ;
2013-11-21 09:09:07 +00:00
private $keyId ;
2012-09-11 12:40:45 +00:00
private $handle ; // Resource returned by fopen
2012-08-16 18:18:18 +00:00
private $meta = array (); // Header / meta for source stream
private $writeCache ;
2013-05-19 23:24:36 +00:00
private $size ;
private $unencryptedSize ;
2012-12-11 15:10:56 +00:00
private $publicKey ;
private $encKeyfile ;
2013-07-30 08:43:16 +00:00
private $newFile ; // helper var, we only need to write the keyfile for new files
2013-12-17 17:13:46 +00:00
private $isLocalTmpFile = false ; // do we operate on a local tmp file
private $localTmpFile ; // path of local tmp file
2014-07-21 11:02:28 +00:00
private $headerWritten = false ;
private $containHeader = false ; // the file contain a header
private $cipher ; // cipher used for encryption/decryption
2013-12-17 17:13:46 +00:00
2013-05-31 14:52:33 +00:00
/**
* @ var \OC\Files\View
*/
2013-01-05 17:12:23 +00:00
private $rootView ; // a fsview object set to '/'
2013-11-12 14:55:59 +00:00
2013-05-31 14:52:33 +00:00
/**
* @ var \OCA\Encryption\Session
*/
private $session ;
private $privateKey ;
2012-08-16 18:18:18 +00:00
2013-05-19 23:24:36 +00:00
/**
2014-05-13 11:29:25 +00:00
* @ param string $path raw path relative to data /
* @ param string $mode
* @ param int $options
* @ param string $opened_path
2013-05-19 23:24:36 +00:00
* @ return bool
*/
2013-05-27 15:26:58 +00:00
public function stream_open ( $path , $mode , $options , & $opened_path ) {
2013-08-30 08:17:50 +00:00
2014-07-21 11:02:28 +00:00
// read default cipher from config
$this -> cipher = Helper :: getCipher ();
2013-07-30 08:43:16 +00:00
// assume that the file already exist before we decide it finally in getKey()
$this -> newFile = false ;
2013-01-05 17:12:23 +00:00
2013-05-27 15:26:58 +00:00
if ( ! isset ( $this -> rootView )) {
2014-05-12 14:30:39 +00:00
$this -> rootView = new \OC\Files\View ( '/' );
2013-01-05 17:12:23 +00:00
}
2013-03-04 16:58:56 +00:00
2013-05-31 14:52:33 +00:00
$this -> session = new \OCA\Encryption\Session ( $this -> rootView );
2013-11-20 22:23:23 +00:00
$this -> privateKey = $this -> session -> getPrivateKey ();
2013-05-31 14:52:33 +00:00
2013-12-17 17:13:46 +00:00
$normalizedPath = \OC\Files\Filesystem :: normalizePath ( str_replace ( 'crypt://' , '' , $path ));
if ( $originalFile = Helper :: getPathFromTmpFile ( $normalizedPath )) {
$this -> rawPath = $originalFile ;
$this -> isLocalTmpFile = true ;
$this -> localTmpFile = $normalizedPath ;
} else {
$this -> rawPath = $normalizedPath ;
}
2013-05-13 19:24:59 +00:00
2013-11-21 09:09:07 +00:00
$this -> userId = Helper :: getUser ( $this -> rawPath );
2013-05-13 19:24:59 +00:00
2013-11-21 09:09:07 +00:00
$util = new Util ( $this -> rootView , $this -> userId );
2013-11-20 22:23:23 +00:00
2013-11-27 10:46:24 +00:00
// get the key ID which we want to use, can be the users key or the
2013-11-21 09:09:07 +00:00
// public share key
$this -> keyId = $util -> getKeyId ();
2013-05-19 23:24:36 +00:00
2013-06-10 09:03:07 +00:00
// Strip identifier text from path, this gives us the path relative to data/<user>/files
$this -> relPath = Helper :: stripUserFilesPath ( $this -> rawPath );
// if raw path doesn't point to a real file, check if it is a version or a file in the trash bin
if ( $this -> relPath === false ) {
$this -> relPath = Helper :: getPathToRealFile ( $this -> rawPath );
}
2013-08-30 08:17:50 +00:00
2013-07-30 13:27:59 +00:00
if ( $this -> relPath === false ) {
2013-11-14 16:32:21 +00:00
\OCP\Util :: writeLog ( 'Encryption library' , 'failed to open file "' . $this -> rawPath . '" expecting a path to "files", "files_versions" or "cache"' , \OCP\Util :: ERROR );
2013-07-30 13:27:59 +00:00
return false ;
}
2013-08-30 08:17:50 +00:00
2013-05-21 22:53:07 +00: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-25 20:49:47 +00:00
if (
2013-05-27 18:44:38 +00:00
$mode === 'w'
or $mode === 'w+'
or $mode === 'wb'
or $mode === 'wb+'
2012-08-16 18:18:18 +00:00
) {
2013-05-19 23:24:36 +00:00
2013-05-21 22:53:07 +00:00
// We're writing a new file so start write counter with 0 bytes
$this -> size = 0 ;
$this -> unencryptedSize = 0 ;
2012-08-16 18:18:18 +00:00
} else {
2013-09-06 11:16:48 +00:00
2013-06-03 21:41:57 +00:00
if ( $this -> privateKey === false ) {
// if private key is not valid redirect user to a error page
2013-09-02 09:26:11 +00:00
\OCA\Encryption\Helper :: redirectToErrorPage ( $this -> session );
2013-06-03 21:41:57 +00:00
}
2013-12-17 17:13:46 +00:00
$this -> size = $this -> rootView -> filesize ( $this -> rawPath );
2014-07-21 11:02:28 +00:00
$this -> readHeader ();
2013-05-21 22:53:07 +00:00
}
2013-05-19 23:24:36 +00:00
2013-12-17 17:13:46 +00:00
if ( $this -> isLocalTmpFile ) {
$this -> handle = fopen ( $this -> localTmpFile , $mode );
} else {
$this -> handle = $this -> rootView -> fopen ( $this -> rawPath , $mode );
}
2013-05-19 23:24:36 +00:00
2013-05-21 22:53:07 +00:00
\OC_FileProxy :: $enabled = $proxyStatus ;
2013-05-19 23:24:36 +00:00
2013-05-27 15:26:58 +00:00
if ( ! is_resource ( $this -> handle )) {
2012-08-16 18:18:18 +00:00
2013-05-31 11:58:58 +00:00
\OCP\Util :: writeLog ( 'Encryption library' , 'failed to open file "' . $this -> rawPath . '"' , \OCP\Util :: ERROR );
2012-08-16 18:18:18 +00:00
2013-05-21 22:53:07 +00:00
} else {
2012-08-16 18:18:18 +00:00
2013-05-27 15:26:58 +00:00
$this -> meta = stream_get_meta_data ( $this -> handle );
2014-02-27 12:58:51 +00: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-19 23:24:36 +00:00
2013-05-21 22:53:07 +00:00
}
2013-05-19 23:24:36 +00:00
2012-08-16 18:18:18 +00:00
2013-05-27 15:26:58 +00:00
return is_resource ( $this -> handle );
2012-08-16 18:18:18 +00:00
}
2013-05-19 23:24:36 +00:00
2014-07-21 11:02:28 +00: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 14:40:43 +00:00
/**
2014-05-19 15:50:53 +00:00
* Returns the current position of the file pointer
2014-05-15 20:47:28 +00:00
* @ return int position of the file pointer
2013-12-18 14:40:43 +00:00
*/
public function stream_tell () {
return ftell ( $this -> handle );
}
2013-05-19 23:24:36 +00:00
/**
2014-05-13 11:29:25 +00:00
* @ param int $offset
2013-05-19 23:24:36 +00:00
* @ param int $whence
2013-12-18 14:40:43 +00:00
* @ return bool true if fseek was successful , otherwise false
2013-05-19 23:24:36 +00:00
*/
2013-05-27 15:26:58 +00:00
public function stream_seek ( $offset , $whence = SEEK_SET ) {
2013-05-19 23:24:36 +00:00
2012-08-16 18:18:18 +00:00
$this -> flush ();
2013-05-19 23:24:36 +00:00
2014-07-21 11:02:28 +00:00
// ignore the header and just overstep it
if ( $this -> containHeader ) {
$offset += Crypt :: BLOCKSIZE ;
}
2013-12-18 14:40:43 +00: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-19 23:24:36 +00:00
2012-08-16 18:18:18 +00:00
}
2013-05-19 23:24:36 +00:00
/**
2014-05-13 11:29:25 +00:00
* @ param int $count
2013-05-19 23:24:36 +00:00
* @ return bool | string
2014-07-21 11:02:28 +00:00
* @ throws \OCA\Encryption\Exceptions\EncryptionException
2013-05-19 23:24:36 +00:00
*/
2013-05-27 15:26:58 +00:00
public function stream_read ( $count ) {
2013-05-19 23:24:36 +00:00
2012-08-23 15:43:10 +00:00
$this -> writeCache = '' ;
2014-07-21 11:02:28 +00:00
if ( $count !== Crypt :: BLOCKSIZE ) {
2013-05-31 11:58:58 +00:00
\OCP\Util :: writeLog ( 'Encryption library' , 'PHP "bug" 21641 no longer holds, decryption system requires refactoring' , \OCP\Util :: FATAL );
2014-07-21 11:02:28 +00:00
throw new \OCA\Encryption\Exceptions\EncryptionException ( 'expected a blog size of 8192 byte' , 20 );
2012-08-16 18:18:18 +00:00
}
2012-08-23 15:43:10 +00:00
2012-10-17 15:35:19 +00:00
// Get the data from the file handle
2013-09-04 19:15:06 +00:00
$data = fread ( $this -> handle , $count );
2013-05-19 23:24:36 +00:00
2014-07-21 11:02:28 +00: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 11:58:58 +00:00
$result = null ;
2013-05-19 23:24:36 +00:00
2013-05-27 15:26:58 +00:00
if ( strlen ( $data )) {
2013-05-19 23:24:36 +00:00
2013-05-27 15:26:58 +00:00
if ( ! $this -> getKey ()) {
2013-05-19 23:24:36 +00:00
2013-04-16 16:29:22 +00:00
// Error! We don't have a key to decrypt the file with
2013-05-27 15:26:58 +00:00
throw new \Exception (
'Encryption key not found for "' . $this -> rawPath . '" during attempted read via stream' );
2013-05-19 23:24:36 +00:00
2013-05-31 11:58:58 +00:00
} else {
2013-05-19 23:24:36 +00:00
2013-05-31 11:58:58 +00:00
// Decrypt data
2014-07-21 11:02:28 +00:00
$result = Crypt :: symmetricDecryptFileContent ( $data , $this -> plainKey , $this -> cipher );
2013-05-31 11:58:58 +00:00
}
2012-08-23 15:43:10 +00:00
2013-05-19 23:24:36 +00:00
}
2012-08-23 15:43:10 +00:00
2012-08-16 18:18:18 +00:00
return $result ;
2012-08-23 15:43:10 +00:00
}
2013-05-19 23:24:36 +00:00
2012-10-16 14:02:51 +00:00
/**
2014-05-19 15:50:53 +00:00
* Encrypt and pad data ready for writing to disk
2012-10-16 14:02:51 +00:00
* @ param string $plainData data to be encrypted
* @ param string $key key to use for encryption
2013-05-19 23:24:36 +00:00
* @ return string encrypted data on success , false on failure
2012-10-16 14:02:51 +00:00
*/
2013-05-27 15:26:58 +00:00
public function preWriteEncrypt ( $plainData , $key ) {
2013-05-19 23:24:36 +00:00
2012-10-16 14:02:51 +00:00
// Encrypt data to 'catfile', which includes IV
2014-07-21 11:02:28 +00:00
if ( $encrypted = Crypt :: symmetricEncryptFileContent ( $plainData , $key , $this -> cipher )) {
2013-05-19 23:24:36 +00:00
return $encrypted ;
2012-10-16 14:02:51 +00:00
} else {
2013-05-19 23:24:36 +00:00
2012-10-16 14:02:51 +00:00
return false ;
2013-05-19 23:24:36 +00:00
2012-10-16 14:02:51 +00:00
}
2013-05-19 23:24:36 +00:00
2012-10-16 14:02:51 +00:00
}
2013-05-19 23:24:36 +00:00
2012-08-23 15:43:10 +00:00
/**
2014-05-19 15:50:53 +00:00
* Fetch the plain encryption key for the file and set it as plainKey property
2013-05-19 23:24:36 +00:00
* @ internal param bool $generate if true , a new key will be generated if none can be found
2012-10-16 14:02:51 +00:00
* @ return bool true on key found and set , false on key not found and new key generated and set
2012-08-23 15:43:10 +00:00
*/
2013-05-23 21:56:31 +00:00
public function getKey () {
2013-05-19 23:24:36 +00:00
2013-04-16 16:29:22 +00:00
// Check if key is already set
2013-05-27 15:26:58 +00:00
if ( isset ( $this -> plainKey ) && isset ( $this -> encKeyfile )) {
2013-05-19 23:24:36 +00:00
2013-04-16 16:29:22 +00:00
return true ;
2013-05-19 23:24:36 +00:00
2013-04-16 16:29:22 +00:00
}
2013-05-19 23:24:36 +00:00
2013-04-25 20:49:47 +00:00
// Fetch and decrypt keyfile
2013-05-19 23:24:36 +00:00
// Fetch existing keyfile
2013-11-21 09:09:07 +00:00
$util = new \OCA\Encryption\Util ( $this -> rootView , $this -> userId );
2013-11-20 17:10:56 +00:00
$this -> encKeyfile = Keymanager :: getFileKey ( $this -> rootView , $util , $this -> relPath );
2013-04-22 02:40:49 +00:00
2013-04-16 16:29:22 +00:00
// If a keyfile already exists
2013-05-27 15:26:58 +00:00
if ( $this -> encKeyfile ) {
2013-05-19 23:24:36 +00:00
2013-11-21 09:09:07 +00:00
$shareKey = Keymanager :: getShareKey ( $this -> rootView , $this -> keyId , $util , $this -> relPath );
2013-10-11 12:20:46 +00:00
2013-05-31 11:58:58 +00:00
// if there is no valid private key return false
2013-05-31 14:52:33 +00:00
if ( $this -> privateKey === false ) {
2013-06-03 21:41:57 +00:00
// if private key is not valid redirect user to a error page
2013-10-11 12:20:46 +00:00
\OCA\Encryption\Helper :: redirectToErrorPage ( $this -> session );
2013-05-31 11:58:58 +00:00
return false ;
}
2013-10-11 12:20:46 +00:00
if ( $shareKey === false ) {
// if no share key is available redirect user to a error page
\OCA\Encryption\Helper :: redirectToErrorPage ( $this -> session , \OCA\Encryption\Crypt :: ENCRYPTION_NO_SHARE_KEY_FOUND );
return false ;
}
2013-05-19 23:24:36 +00:00
2013-05-31 14:52:33 +00:00
$this -> plainKey = Crypt :: multiKeyDecrypt ( $this -> encKeyfile , $shareKey , $this -> privateKey );
2013-05-19 23:24:36 +00:00
2012-10-16 14:02:51 +00:00
return true ;
2013-05-19 23:24:36 +00:00
2012-08-23 15:43:10 +00:00
} else {
2013-05-19 23:24:36 +00:00
2013-07-30 08:43:16 +00:00
$this -> newFile = true ;
2013-08-30 08:17:50 +00:00
2012-12-11 15:10:56 +00:00
return false ;
2013-05-19 23:24:36 +00:00
2012-12-11 15:10:56 +00:00
}
2013-05-19 23:24:36 +00:00
2012-12-11 15:10:56 +00:00
}
2013-05-19 23:24:36 +00:00
2014-07-21 11:02:28 +00:00
/**
* write header at beginning of encrypted file
*
* @ throws Exceptions\EncryptionException
*/
private function writeHeader () {
$header = Crypt :: generateHeader ();
if ( strlen ( $header ) > Crypt :: BLOCKSIZE ) {
throw new Exceptions\EncryptionException ( 'max header size exceeded' , 30 );
}
$paddedHeader = str_pad ( $header , Crypt :: BLOCKSIZE , self :: PADDING_CHAR , STR_PAD_RIGHT );
fwrite ( $this -> handle , $paddedHeader );
$this -> headerWritten = true ;
}
2012-08-16 18:18:18 +00:00
/**
2014-05-19 15:50:53 +00:00
* Handle plain data from the stream , and write it in 8192 byte blocks
2012-10-10 17:40:59 +00: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 14:02:51 +00: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 18:18:18 +00:00
*/
2013-05-27 15:26:58 +00:00
public function stream_write ( $data ) {
2013-05-19 23:24:36 +00:00
2013-05-31 14:52:33 +00:00
// if there is no valid private key return false
if ( $this -> privateKey === false ) {
$this -> size = 0 ;
return strlen ( $data );
}
2014-07-21 11:02:28 +00:00
if ( $this -> headerWritten === false ) {
$this -> writeHeader ();
}
2013-08-30 08:17:50 +00: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 18:37:34 +00:00
// get into an infinite loop
2013-05-19 23:24:36 +00:00
$proxyStatus = \OC_FileProxy :: $enabled ;
2013-04-28 23:43:59 +00:00
\OC_FileProxy :: $enabled = false ;
2013-05-19 23:24:36 +00:00
2012-10-16 14:02:51 +00:00
// Get the length of the unencrypted data that we are handling
2013-05-27 15:26:58 +00:00
$length = strlen ( $data );
2013-05-19 23:24:36 +00:00
2013-05-21 22:53:07 +00:00
// Find out where we are up to in the writing of data to the
2013-01-24 18:37:34 +00:00
// file
2013-05-27 15:26:58 +00:00
$pointer = ftell ( $this -> handle );
2013-05-19 23:24:36 +00:00
2012-10-16 14:02:51 +00:00
// Get / generate the keyfile for the file we're handling
2013-08-30 08:17:50 +00:00
// If we're writing a new file (not overwriting an existing
2013-01-14 19:07:28 +00:00
// one), save the newly generated keyfile
2013-05-27 15:26:58 +00:00
if ( ! $this -> getKey ()) {
2013-05-19 23:24:36 +00:00
2013-04-16 16:29:22 +00:00
$this -> plainKey = Crypt :: generateKey ();
2013-05-19 23:24:36 +00:00
2012-08-16 18:18:18 +00:00
}
2013-04-22 02:40:49 +00:00
2013-08-30 08:17:50 +00:00
// If extra data is left over from the last round, make sure it
2013-01-24 18:37:34 +00:00
// is integrated into the next 6126 / 8192 block
2013-05-27 15:26:58 +00:00
if ( $this -> writeCache ) {
2013-05-19 23:24:36 +00:00
2012-10-16 14:02:51 +00:00
// Concat writeCache to start of $data
$data = $this -> writeCache . $data ;
2013-05-19 23:24:36 +00:00
2013-05-23 21:56:31 +00:00
// Clear the write cache, ready for reuse - it has been
2013-01-24 18:37:34 +00:00
// flushed and its old contents processed
2012-10-16 14:02:51 +00:00
$this -> writeCache = '' ;
}
2012-10-10 17:40:59 +00:00
2013-05-23 21:56:31 +00:00
// While there still remains some data to be processed & written
2013-05-27 15:26:58 +00:00
while ( strlen ( $data ) > 0 ) {
2013-05-19 23:24:36 +00:00
2013-05-23 21:56:31 +00:00
// Remaining length for this iteration, not of the
2013-05-19 23:24:36 +00:00
// entire file (may be greater than 8192 bytes)
2013-05-27 15:26:58 +00:00
$remainingLength = strlen ( $data );
2013-05-19 23:24:36 +00:00
2013-05-23 21:56:31 +00:00
// If data remaining to be written is less than the
2013-05-19 23:24:36 +00:00
// size of 1 6126 byte block
2013-05-27 15:26:58 +00:00
if ( $remainingLength < 6126 ) {
2013-05-19 23:24:36 +00:00
2012-10-16 14:02:51 +00:00
// Set writeCache to contents of $data
2013-08-30 08:17:50 +00: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 18:37:34 +00:00
// to disk by $this->flush().
2012-10-16 14:02:51 +00:00
$this -> writeCache = $data ;
// Clear $data ready for next round
$data = '' ;
2013-05-19 23:24:36 +00:00
2012-10-16 14:02:51 +00:00
} else {
2013-05-19 23:24:36 +00:00
2012-10-10 17:40:59 +00:00
// Read the chunk from the start of $data
2013-05-27 15:26:58 +00:00
$chunk = substr ( $data , 0 , 6126 );
2013-05-19 23:24:36 +00:00
2013-05-27 15:26:58 +00:00
$encrypted = $this -> preWriteEncrypt ( $chunk , $this -> plainKey );
2013-05-19 23:24:36 +00:00
2013-08-30 08:17:50 +00:00
// Write the data chunk to disk. This will be
2013-02-09 17:01:38 +00:00
// attended to the last data chunk if the file
2013-01-24 18:37:34 +00:00
// being handled totals more than 6126 bytes
2013-05-27 15:26:58 +00:00
fwrite ( $this -> handle , $encrypted );
2013-05-19 23:24:36 +00:00
2013-05-21 22:53:07 +00:00
// Remove the chunk we just processed from
2013-01-24 18:37:34 +00:00
// $data, leaving only unprocessed data in $data
// var, for handling on the next round
2013-05-27 15:26:58 +00:00
$data = substr ( $data , 6126 );
2012-08-16 18:18:18 +00:00
2012-10-16 14:02:51 +00:00
}
2013-05-19 23:24:36 +00:00
2012-10-10 17:40:59 +00:00
}
2013-04-25 13:20:06 +00:00
2013-05-27 15:26:58 +00:00
$this -> size = max ( $this -> size , $pointer + $length );
2013-05-19 23:24:36 +00:00
$this -> unencryptedSize += $length ;
\OC_FileProxy :: $enabled = $proxyStatus ;
2013-04-28 23:43:59 +00:00
2012-08-16 18:18:18 +00:00
return $length ;
}
2013-05-19 23:24:36 +00:00
/**
2014-05-13 11:29:25 +00:00
* @ param int $option
* @ param int $arg1
* @ param int | null $arg2
2013-05-19 23:24:36 +00:00
*/
2013-05-27 15:26:58 +00:00
public function stream_set_option ( $option , $arg1 , $arg2 ) {
2013-05-21 22:53:07 +00:00
$return = false ;
2013-05-27 15:26:58 +00:00
switch ( $option ) {
2012-08-16 18:18:18 +00:00
case STREAM_OPTION_BLOCKING :
2013-05-27 15:26:58 +00:00
$return = stream_set_blocking ( $this -> handle , $arg1 );
2012-08-16 18:18:18 +00:00
break ;
case STREAM_OPTION_READ_TIMEOUT :
2013-05-27 15:26:58 +00:00
$return = stream_set_timeout ( $this -> handle , $arg1 , $arg2 );
2012-08-16 18:18:18 +00:00
break ;
case STREAM_OPTION_WRITE_BUFFER :
2013-05-27 15:26:58 +00:00
$return = stream_set_write_buffer ( $this -> handle , $arg1 );
2012-08-16 18:18:18 +00:00
}
2013-05-21 22:53:07 +00:00
return $return ;
2012-08-16 18:18:18 +00:00
}
2013-05-19 23:24:36 +00:00
/**
* @ return array
*/
2013-05-23 21:56:31 +00:00
public function stream_stat () {
2013-05-27 15:26:58 +00:00
return fstat ( $this -> handle );
2012-08-16 18:18:18 +00:00
}
2013-05-19 23:24:36 +00:00
/**
2014-05-13 11:29:25 +00:00
* @ param int $mode
2013-05-19 23:24:36 +00:00
*/
2013-05-27 15:26:58 +00:00
public function stream_lock ( $mode ) {
return flock ( $this -> handle , $mode );
2012-08-16 18:18:18 +00:00
}
2013-05-19 23:24:36 +00:00
/**
* @ return bool
*/
2013-05-23 21:56:31 +00:00
public function stream_flush () {
2013-05-19 23:24:36 +00:00
2013-05-27 15:26:58 +00:00
return fflush ( $this -> handle );
2013-01-24 18:37:34 +00:00
// Not a typo: http://php.net/manual/en/function.fflush.php
2013-05-19 23:24:36 +00:00
2012-08-16 18:18:18 +00:00
}
2013-05-19 23:24:36 +00:00
/**
* @ return bool
*/
2013-05-23 21:56:31 +00:00
public function stream_eof () {
2013-05-27 15:26:58 +00:00
return feof ( $this -> handle );
2012-08-16 18:18:18 +00:00
}
2013-05-23 21:56:31 +00:00
private function flush () {
2013-05-19 23:24:36 +00:00
2013-05-27 15:26:58 +00:00
if ( $this -> writeCache ) {
2013-05-19 23:24:36 +00:00
2012-09-11 12:40:45 +00:00
// Set keyfile property for file in question
$this -> getKey ();
2013-05-19 23:24:36 +00:00
2013-05-27 15:26:58 +00:00
$encrypted = $this -> preWriteEncrypt ( $this -> writeCache , $this -> plainKey );
2013-05-19 23:24:36 +00:00
2013-05-27 15:26:58 +00:00
fwrite ( $this -> handle , $encrypted );
2013-05-19 23:24:36 +00:00
2012-09-11 12:40:45 +00:00
$this -> writeCache = '' ;
2012-08-16 18:18:18 +00:00
}
}
2013-05-19 23:24:36 +00:00
/**
* @ return bool
*/
2013-05-23 21:56:31 +00:00
public function stream_close () {
2013-05-19 23:24:36 +00:00
$this -> flush ();
2013-04-28 23:43:59 +00:00
2013-05-31 13:57:18 +00:00
// if there is no valid private key return false
2013-05-31 14:52:33 +00:00
if ( $this -> privateKey === false ) {
2013-05-31 13:57:18 +00:00
2013-07-30 08:43:16 +00:00
// cleanup
2013-12-17 17:13:46 +00:00
if ( $this -> meta [ 'mode' ] !== 'r' && $this -> meta [ 'mode' ] !== 'rb' && ! $this -> isLocalTmpFile ) {
2013-05-31 14:52:33 +00:00
2013-07-30 08:43:16 +00:00
// Disable encryption proxy to prevent recursive calls
$proxyStatus = \OC_FileProxy :: $enabled ;
\OC_FileProxy :: $enabled = false ;
2013-05-31 14:52:33 +00:00
2013-07-30 08:43:16 +00:00
if ( $this -> rootView -> file_exists ( $this -> rawPath ) && $this -> size === 0 ) {
2014-11-06 15:53:35 +00:00
fclose ( $this -> handle );
2013-07-30 08:43:16 +00:00
$this -> rootView -> unlink ( $this -> rawPath );
2013-05-31 14:52:33 +00:00
}
2013-07-30 08:43:16 +00:00
// Re-enable proxy - our work is done
\OC_FileProxy :: $enabled = $proxyStatus ;
}
2013-06-03 21:41:57 +00:00
// if private key is not valid redirect user to a error page
2013-11-17 12:23:07 +00:00
\OCA\Encryption\Helper :: redirectToErrorPage ( $this -> session );
2013-05-31 13:57:18 +00:00
}
2013-05-19 23:24:36 +00:00
if (
2013-07-30 08:43:16 +00:00
$this -> meta [ 'mode' ] !== 'r' &&
$this -> meta [ 'mode' ] !== 'rb' &&
2013-12-17 17:13:46 +00:00
$this -> isLocalTmpFile === false &&
2013-11-20 15:20:21 +00:00
$this -> size > 0 &&
$this -> unencryptedSize > 0
2013-01-06 18:38:35 +00:00
) {
2013-09-04 19:15:06 +00:00
2013-07-30 08:43:16 +00:00
// only write keyfiles if it was a new file
if ( $this -> newFile === true ) {
2013-04-28 23:43:59 +00:00
2013-07-30 08:43:16 +00:00
// Disable encryption proxy to prevent recursive calls
$proxyStatus = \OC_FileProxy :: $enabled ;
\OC_FileProxy :: $enabled = false ;
2013-04-28 23:43:59 +00:00
2013-07-30 08:43:16 +00:00
// Fetch user's public key
2013-11-21 09:09:07 +00:00
$this -> publicKey = Keymanager :: getPublicKey ( $this -> rootView , $this -> keyId );
2013-04-28 23:43:59 +00:00
2013-07-30 08:43:16 +00:00
// Check if OC sharing api is enabled
$sharingEnabled = \OCP\Share :: isEnabled ();
2013-04-28 23:43:59 +00:00
2013-11-21 09:09:07 +00:00
$util = new Util ( $this -> rootView , $this -> userId );
2013-04-28 23:43:59 +00:00
2013-07-30 08:43:16 +00:00
// Get all users sharing the file includes current user
2014-03-31 16:09:46 +00:00
$uniqueUserIds = $util -> getSharingUsersArray ( $sharingEnabled , $this -> relPath );
2013-10-09 13:56:21 +00:00
$checkedUserIds = $util -> filterShareReadyUsers ( $uniqueUserIds );
2013-04-28 23:43:59 +00:00
2013-07-30 08:43:16 +00:00
// Fetch public keys for all sharing users
2013-10-09 13:56:21 +00:00
$publicKeys = Keymanager :: getPublicKeys ( $this -> rootView , $checkedUserIds [ 'ready' ]);
2013-04-28 23:43:59 +00:00
2013-07-30 08:43:16 +00:00
// Encrypt enc key for all sharing users
$this -> encKeyfiles = Crypt :: multiKeyEncrypt ( $this -> plainKey , $publicKeys );
2013-04-28 23:43:59 +00:00
2013-07-30 08:43:16 +00:00
// Save the new encrypted file key
2013-11-27 14:08:09 +00:00
Keymanager :: setFileKey ( $this -> rootView , $util , $this -> relPath , $this -> encKeyfiles [ 'data' ]);
2013-07-30 08:43:16 +00:00
// Save the sharekeys
2013-11-20 23:23:38 +00:00
Keymanager :: setShareKeys ( $this -> rootView , $util , $this -> relPath , $this -> encKeyfiles [ 'keys' ]);
2013-07-30 08:43:16 +00:00
// Re-enable proxy - our work is done
\OC_FileProxy :: $enabled = $proxyStatus ;
}
2013-04-28 23:43:59 +00:00
2013-11-12 14:55:59 +00:00
// we need to update the file info for the real file, not for the
// part file.
2013-11-12 17:48:31 +00:00
$path = Helper :: stripPartialFileExtension ( $this -> rawPath );
2013-11-12 14:55:59 +00:00
2014-03-31 10:43:38 +00:00
$fileInfo = array (
'encrypted' => true ,
'size' => $this -> size ,
'unencrypted_size' => $this -> unencryptedSize ,
);
// set fileinfo
$this -> rootView -> putFileInfo ( $path , $fileInfo );
2013-05-06 19:15:25 +00:00
2012-08-16 18:18:18 +00:00
}
2012-08-23 15:43:10 +00:00
2014-03-31 10:43:38 +00: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 18:18:18 +00:00
}
2012-08-23 15:43:10 +00:00
2012-08-16 18:18:18 +00:00
}