2012-08-16 22:18:18 +04:00
< ? php
/**
2015-03-26 13:44:34 +03:00
* @ author Björn Schießle < schiessle @ owncloud . com >
* @ author Florin Peter < github @ florin - peter . de >
* @ author jknockaert < jasper @ knockaert . nl >
* @ author Joas Schilling < nickvergessen @ owncloud . com >
* @ author Morris Jobke < hey @ morrisjobke . de >
* @ author Robin McCorkell < rmccorkell @ karoshi . org . uk >
* @ author Sam Tuke < mail @ samtuke . com >
* @ author Vincent Petry < pvince81 @ owncloud . com >
2012-08-16 22:18:18 +04:00
*
2015-03-26 13:44:34 +03:00
* @ copyright Copyright ( c ) 2015 , ownCloud , Inc .
* @ license AGPL - 3.0
2014-11-05 16:42:36 +03:00
*
2015-03-26 13:44:34 +03:00
* This code is free software : you can redistribute it and / or modify
* it under the terms of the GNU Affero General Public License , version 3 ,
* as published by the Free Software Foundation .
2012-08-16 22:18:18 +04:00
*
2015-03-26 13:44:34 +03:00
* This program is distributed in the hope that it will be useful ,
2012-08-16 22:18:18 +04:00
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
2015-03-26 13:44:34 +03:00
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU Affero General Public License for more details .
2012-08-16 22:18:18 +04:00
*
2015-03-26 13:44:34 +03:00
* You should have received a copy of the GNU Affero General Public License , version 3 ,
* along with this program . If not , see < http :// www . gnu . org / licenses />
2012-08-16 22:18:18 +04:00
*
*/
/**
* 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
2015-02-19 18:10:41 +03:00
private $cache ; // Current block unencrypted
private $position ; // Current pointer position in the unencrypted stream
private $writeFlag ; // Flag to write current block when leaving it
2013-05-20 03:24:36 +04:00
private $size ;
2015-02-19 18:10:41 +03:00
private $headerSize = 0 ; // Size of header
2013-05-20 03:24:36 +04:00
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 $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 );
2015-01-12 19:51:09 +03:00
$user = \OC :: $server -> getUserSession () -> getUser ();
$this -> userId = $user ? $user -> getUID () : Helper :: getUserFromPath ( $this -> rawPath );
2015-01-08 22:57:49 +03:00
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 ( '/' ));
2015-01-12 19:51:09 +03:00
$user = \OC :: $server -> getUserSession () -> getUser ();
$this -> userId = $user ? $user -> getUID () : Helper :: getUserFromPath ( $this -> rawPath );
2015-01-08 22:57:49 +03:00
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 ;
2015-02-19 18:10:41 +03:00
$this -> position = 0 ;
$this -> cache = '' ;
$this -> writeFlag = 0 ;
// Setting handle so it can be used for reading the header
if ( $this -> isLocalTmpFile ) {
$this -> handle = fopen ( $this -> localTmpFile , $mode );
} else {
$this -> handle = $this -> rootView -> fopen ( $this -> rawPath , $mode );
}
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 );
2015-02-19 18:10:41 +03:00
\OC_FileProxy :: $enabled = true ;
$this -> unencryptedSize = $this -> rootView -> filesize ( $this -> rawPath );
\OC_FileProxy :: $enabled = false ;
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-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 () {
2015-02-19 18:10:41 +03:00
if ( is_resource ( $this -> handle )) {
$data = fread ( $this -> handle , Crypt :: BLOCKSIZE );
2014-07-21 15:02:28 +04:00
$header = Crypt :: parseHeader ( $data );
$this -> cipher = Crypt :: getCipher ( $header );
// remeber that we found a header
if ( ! empty ( $header )) {
$this -> containHeader = true ;
2015-02-19 18:10:41 +03:00
$this -> headerSize = Crypt :: BLOCKSIZE ;
// if there's no header then decrypt the block and store it in the cache
} else {
if ( ! $this -> getKey ()) {
throw new \Exception ( 'Encryption key not found for "' . $this -> rawPath . '" during attempted read via stream' );
} else {
$this -> cache = Crypt :: symmetricDecryptFileContent ( $data , $this -> plainKey , $this -> cipher );
}
2014-07-21 15:02:28 +04:00
}
}
}
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 () {
2015-02-19 18:10:41 +03:00
return $this -> position ;
2013-12-18 18:40:43 +04:00
}
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
*/
2015-02-19 18:10:41 +03:00
// seeking the stream tries to move the pointer on the encrypted stream to the beginning of the target block
// if that works, it flushes the current block and changes the position in the unencrypted stream
2013-05-27 19:26:58 +04:00
public function stream_seek ( $offset , $whence = SEEK_SET ) {
2015-02-19 18:10:41 +03:00
// this wrapper needs to return "true" for success.
// the fseek call itself returns 0 on succeess
2013-05-20 03:24:36 +04:00
2015-02-19 18:10:41 +03:00
$return = false ;
2013-05-20 03:24:36 +04:00
2015-02-19 18:10:41 +03:00
switch ( $whence ) {
case SEEK_SET :
if ( $offset < $this -> unencryptedSize && $offset >= 0 ) {
$newPosition = $offset ;
}
break ;
case SEEK_CUR :
if ( $offset >= 0 ) {
$newPosition = $offset + $this -> position ;
}
break ;
case SEEK_END :
if ( $this -> unencryptedSize + $offset >= 0 ) {
$newPosition = $this -> unencryptedSize + $offset ;
}
break ;
default :
return $return ;
2014-07-21 15:02:28 +04:00
}
2015-02-19 18:10:41 +03:00
$newFilePosition = floor ( $newPosition / 6126 ) * Crypt :: BLOCKSIZE + $this -> headerSize ;
if ( fseek ( $this -> handle , $newFilePosition ) === 0 ) {
$this -> flush ();
$this -> position = $newPosition ;
$return = true ;
}
return $return ;
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
2015-02-19 18:10:41 +03:00
$result = '' ;
2012-08-23 19:43:10 +04:00
2015-02-19 18:10:41 +03:00
// limit to the end of the unencrypted file; otherwise getFileSize will fail and it is good practise anyway
$count = min ( $count , $this -> unencryptedSize - $this -> position );
2013-05-20 03:24:36 +04:00
2015-02-19 18:10:41 +03:00
// loop over the 6126 sized unencrypted blocks
while ( $count > 0 ) {
2013-05-20 03:24:36 +04:00
2015-02-19 18:10:41 +03:00
$remainingLength = $count ;
2013-05-20 03:24:36 +04:00
2015-02-19 18:10:41 +03:00
// update the cache of the current block
$this -> readCache ();
// determine the relative position in the current block
$blockPosition = ( $this -> position % 6126 );
2013-05-20 03:24:36 +04:00
2015-02-19 18:10:41 +03:00
// if entire read inside current block then only position needs to be updated
if ( $remainingLength < ( 6126 - $blockPosition )) {
$result .= substr ( $this -> cache , $blockPosition , $remainingLength );
$this -> position += $remainingLength ;
$count = 0 ;
// otherwise remainder of current block is fetched, the block is flushed and the position updated
2013-05-31 15:58:58 +04:00
} else {
2015-02-19 18:10:41 +03:00
$result .= substr ( $this -> cache , $blockPosition );
$this -> flush ();
$this -> position += ( 6126 - $blockPosition );
$count -= ( 6126 - $blockPosition );
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
2013-04-26 00:49:47 +04:00
// Fetch and decrypt keyfile
2013-05-20 03:24:36 +04:00
// Fetch existing keyfile
2015-02-19 18:10:41 +03:00
$this -> encKeyfile = Keymanager :: getFileKey ( $this -> rootView , $this -> 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
2015-02-19 18:10:41 +03:00
$shareKey = Keymanager :: getShareKey ( $this -> rootView , $this -> keyId , $this -> 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 ;
2015-02-19 18:10:41 +03:00
$this -> containHeader = true ;
$this -> headerSize = Crypt :: BLOCKSIZE ;
$this -> size += $this -> headerSize ;
2014-07-21 15:02:28 +04:00
}
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
2015-02-19 18:10:41 +03:00
* @ note Because the encryption process used increases the length of $data , a cache is used to carry over data which would not fit in the required block size
2012-10-16 18:02:51 +04:00
* @ 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 );
}
2015-02-19 18:10:41 +03:00
if ( $this -> size === 0 ) {
2014-07-21 15:02:28 +04:00
$this -> writeHeader ();
}
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
2015-02-19 18:10:41 +03:00
$length = 0 ;
2012-10-16 18:02:51 +04:00
2015-02-19 18:10:41 +03:00
// loop over $data to fit it in 6126 sized unencrypted blocks
2013-05-27 19:26:58 +04:00
while ( strlen ( $data ) > 0 ) {
2013-05-20 03:24:36 +04:00
2013-05-27 19:26:58 +04:00
$remainingLength = strlen ( $data );
2013-05-20 03:24:36 +04:00
2015-02-19 18:10:41 +03:00
// set the cache to the current 6126 block
$this -> readCache ();
// only allow writes on seekable streams, or at the end of the encrypted stream
// for seekable streams the pointer is moved back to the beginning of the encrypted block
// flush will start writing there when the position moves to another block
if (( fseek ( $this -> handle , floor ( $this -> position / 6126 ) * Crypt :: BLOCKSIZE + $this -> headerSize ) === 0 ) || ( floor ( $this -> position / 6126 ) * Crypt :: BLOCKSIZE + $this -> headerSize === $this -> size )) {
// switch the writeFlag so flush() will write the block
$this -> writeFlag = 1 ;
// determine the relative position in the current block
$blockPosition = ( $this -> position % 6126 );
// check if $data fits in current block
// if so, overwrite existing data (if any)
// update position and liberate $data
if ( $remainingLength < ( 6126 - $blockPosition )) {
$this -> cache = substr ( $this -> cache , 0 , $blockPosition ) . $data . substr ( $this -> cache , $blockPosition + $remainingLength );
$this -> position += $remainingLength ;
$length += $remainingLength ;
$data = '' ;
// if $data doens't fit the current block, the fill the current block and reiterate
// after the block is filled, it is flushed and $data is updated
} else {
$this -> cache = substr ( $this -> cache , 0 , $blockPosition ) . substr ( $data , 0 , 6126 - $blockPosition );
$this -> flush ();
$this -> position += ( 6126 - $blockPosition );
$length += ( 6126 - $blockPosition );
$data = substr ( $data , 6126 - $blockPosition );
}
2013-05-20 03:24:36 +04:00
2012-10-16 18:02:51 +04:00
} else {
2015-02-19 18:10:41 +03:00
$data = '' ;
2012-10-16 18:02:51 +04:00
}
2012-10-10 21:40:59 +04:00
}
2013-04-25 17:20:06 +04:00
2015-02-19 18:10:41 +03:00
$this -> unencryptedSize = max ( $this -> unencryptedSize , $this -> position );
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
2015-02-19 18:10:41 +03:00
$this -> flush ();
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 () {
2015-02-19 18:10:41 +03:00
return ( $this -> position >= $this -> unencryptedSize );
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
2015-02-19 18:10:41 +03:00
// write to disk only when writeFlag was set to 1
if ( $this -> writeFlag === 1 ) {
// 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
// get into an infinite loop
$proxyStatus = \OC_FileProxy :: $enabled ;
\OC_FileProxy :: $enabled = false ;
2012-09-11 16:40:45 +04:00
// Set keyfile property for file in question
$this -> getKey ();
2015-02-19 18:10:41 +03:00
$encrypted = $this -> preWriteEncrypt ( $this -> cache , $this -> plainKey );
2013-05-27 19:26:58 +04:00
fwrite ( $this -> handle , $encrypted );
2015-02-19 18:10:41 +03:00
$this -> writeFlag = 0 ;
$this -> size = max ( $this -> size , ftell ( $this -> handle ));
\OC_FileProxy :: $enabled = $proxyStatus ;
}
// always empty the cache (otherwise readCache() will not fill it with the new block)
$this -> cache = '' ;
}
2013-05-20 03:24:36 +04:00
2015-02-19 18:10:41 +03:00
private function readCache () {
// cache should always be empty string when this function is called
// don't try to fill the cache when trying to write at the end of the unencrypted file when it coincides with new block
if ( $this -> cache === '' && ! ( $this -> position === $this -> unencryptedSize && ( $this -> position % 6126 ) === 0 )) {
// Get the data from the file handle
$data = fread ( $this -> handle , Crypt :: BLOCKSIZE );
$result = '' ;
if ( strlen ( $data )) {
if ( ! $this -> getKey ()) {
// Error! We don't have a key to decrypt the file with
throw new \Exception ( 'Encryption key not found for "' . $this -> rawPath . '" during attempted read via stream' );
} else {
// Decrypt data
$result = Crypt :: symmetricDecryptFileContent ( $data , $this -> plainKey , $this -> cipher );
}
}
$this -> cache = $result ;
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
2015-02-19 18:10:41 +03:00
if ( $this -> rootView -> file_exists ( $this -> rawPath ) && $this -> size === $this -> headerSize ) {
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 &&
2015-02-19 18:10:41 +03:00
$this -> size > $this -> headerSize &&
2013-11-20 19:20:21 +04:00
$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
}