2012-07-11 20:51:27 +04:00
< ? php
/**
* ownCloud
*
* @ author Sam Tuke , Frank Karlitschek
* @ copyright 2012 Sam Tuke samtuke @ owncloud . com ,
* Frank Karlitschek frank @ owncloud . org
*
* 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 />.
*
*/
2013-02-26 22:11:29 +04:00
# Bugs
# ----
# Sharing a file to a user without encryption set up will not provide them with access but won't notify the sharer
2013-04-10 19:37:03 +04:00
# Timeouts on first login due to encryption of very large files (fix in progress, as a result streaming is currently broken)
# Sharing all files to admin for recovery purposes still in progress
# Possibly public links are broken (not tested since last merge of master)
2013-04-17 19:20:37 +04:00
# encryptAll during login mangles paths: /files/files/
# encryptAll is accessing files via encryption proxy - perhaps proxies should be disabled?
2013-02-26 22:11:29 +04:00
# Missing features
# ----------------
# Make sure user knows if large files weren't encrypted
2013-03-20 22:26:59 +04:00
# Test
# ----
# Test that writing files works when recovery is enabled, and sharing API is disabled
# Test trashbin support
2013-02-26 22:11:29 +04:00
// Old Todo:
2012-07-11 20:51:27 +04:00
// - Crypt/decrypt button in the userinterface
// - Setting if crypto should be on by default
2013-01-24 22:37:34 +04:00
// - Add a setting "Don´ t encrypt files larger than xx because of performance
// reasons"
2012-07-11 20:51:27 +04:00
2012-10-17 19:35:19 +04:00
namespace OCA\Encryption ;
2012-07-11 20:51:27 +04:00
/**
2012-07-25 18:33:25 +04:00
* @ brief Class for utilities relating to encrypted file storage system
2013-01-31 23:40:51 +04:00
* @ param OC_FilesystemView $view expected to have OC '/' as root path
* @ param string $userId ID of the logged in user
* @ param int $client indicating status of client side encryption . Currently
2012-07-25 18:33:25 +04:00
* unused , likely to become obsolete shortly
2012-07-11 20:51:27 +04:00
*/
class Util {
2012-12-04 23:53:13 +04:00
2013-01-24 22:37:34 +04:00
// Web UI:
2012-12-04 23:53:13 +04:00
2013-01-24 22:37:34 +04:00
//// DONE: files created via web ui are encrypted
//// DONE: file created & encrypted via web ui are readable in web ui
//// DONE: file created & encrypted via web ui are readable via webdav
2012-12-04 23:53:13 +04:00
2013-01-24 22:37:34 +04:00
// WebDAV:
2012-12-04 23:53:13 +04:00
2013-01-24 22:37:34 +04:00
//// DONE: new data filled files added via webdav get encrypted
//// DONE: new data filled files added via webdav are readable via webdav
//// DONE: reading unencrypted files when encryption is enabled works via
//// webdav
//// DONE: files created & encrypted via web ui are readable via webdav
2012-12-04 23:53:13 +04:00
2013-01-24 22:37:34 +04:00
// Legacy support:
2012-12-04 23:53:13 +04:00
2013-01-24 22:37:34 +04:00
//// DONE: add method to check if file is encrypted using new system
//// DONE: add method to check if file is encrypted using old system
//// DONE: add method to fetch legacy key
//// DONE: add method to decrypt legacy encrypted data
2012-12-04 23:53:13 +04:00
2013-01-24 22:37:34 +04:00
// Admin UI:
2012-12-04 23:53:13 +04:00
2013-01-24 22:37:34 +04:00
//// DONE: changing user password also changes encryption passphrase
2012-12-11 21:12:46 +04:00
2013-01-24 22:37:34 +04:00
//// TODO: add support for optional recovery in case of lost passphrase / keys
//// TODO: add admin optional required long passphrase for users
//// TODO: add UI buttons for encrypt / decrypt everything
//// TODO: implement flag system to allow user to specify encryption by folder, subfolder, etc.
2012-12-04 23:53:13 +04:00
2013-01-24 22:37:34 +04:00
// Sharing:
2012-12-11 21:12:46 +04:00
2013-01-24 22:37:34 +04:00
//// TODO: add support for encrypting to multiple public keys
//// TODO: add support for decrypting to multiple private keys
2012-12-11 21:12:46 +04:00
2013-01-24 22:37:34 +04:00
// Integration testing:
2012-12-04 23:53:13 +04:00
2013-01-24 22:37:34 +04:00
//// TODO: test new encryption with versioning
//// TODO: test new encryption with sharing
//// TODO: test new encryption with proxies
2012-12-04 23:53:13 +04:00
2012-11-22 18:08:19 +04:00
2012-07-11 20:51:27 +04:00
private $view ; // OC_FilesystemView object for filesystem operations
2013-01-23 23:24:26 +04:00
private $userId ; // ID of the currently logged-in user
2012-07-11 20:51:27 +04:00
private $pwd ; // User Password
private $client ; // Client side encryption mode flag
2013-01-14 19:39:04 +04:00
private $publicKeyDir ; // Dir containing all public user keys
private $encryptionDir ; // Dir containing user's files_encryption
private $keyfilesPath ; // Dir containing user's keyfiles
private $shareKeysPath ; // Dir containing env keys for shared files
2012-11-16 22:31:37 +04:00
private $publicKeyPath ; // Path to user's public key
private $privateKeyPath ; // Path to user's private key
2012-07-11 20:51:27 +04:00
2012-07-25 18:33:25 +04:00
public function __construct ( \OC_FilesystemView $view , $userId , $client = false ) {
2012-07-11 20:51:27 +04:00
$this -> view = $view ;
2012-07-25 18:33:25 +04:00
$this -> userId = $userId ;
2012-07-11 20:51:27 +04:00
$this -> client = $client ;
2013-01-23 23:24:26 +04:00
$this -> userDir = '/' . $this -> userId ;
2013-04-10 19:37:03 +04:00
$this -> fileFolderName = 'files' ;
$this -> userFilesDir = '/' . $this -> userId . '/' . $this -> fileFolderName ; // TODO: Does this need to be user configurable?
2012-07-25 18:33:25 +04:00
$this -> publicKeyDir = '/' . 'public-keys' ;
$this -> encryptionDir = '/' . $this -> userId . '/' . 'files_encryption' ;
$this -> keyfilesPath = $this -> encryptionDir . '/' . 'keyfiles' ;
2013-01-14 19:39:04 +04:00
$this -> shareKeysPath = $this -> encryptionDir . '/' . 'share-keys' ;
2012-07-25 18:33:25 +04:00
$this -> publicKeyPath = $this -> publicKeyDir . '/' . $this -> userId . '.public.key' ; // e.g. data/public-keys/admin.public.key
$this -> privateKeyPath = $this -> encryptionDir . '/' . $this -> userId . '.private.key' ; // e.g. data/admin/admin.private.key
2012-07-11 20:51:27 +04:00
}
public function ready () {
if (
2013-01-23 23:24:26 +04:00
! $this -> view -> file_exists ( $this -> encryptionDir )
or ! $this -> view -> file_exists ( $this -> keyfilesPath )
or ! $this -> view -> file_exists ( $this -> shareKeysPath )
2012-07-25 18:33:25 +04:00
or ! $this -> view -> file_exists ( $this -> publicKeyPath )
or ! $this -> view -> file_exists ( $this -> privateKeyPath )
2012-07-11 20:51:27 +04:00
) {
return false ;
} else {
return true ;
}
}
2012-07-24 20:53:12 +04:00
/**
2012-07-25 18:33:25 +04:00
* @ brief Sets up user folders and keys for serverside encryption
2012-07-24 20:53:12 +04:00
* @ param $passphrase passphrase to encrypt server - stored private key with
*/
2012-07-25 18:33:25 +04:00
public function setupServerSide ( $passphrase = null ) {
2012-07-11 20:51:27 +04:00
2013-03-19 22:53:15 +04:00
// Set directories to check / create
$setUpDirs = array (
$this -> userDir
, $this -> userFilesDir
, $this -> publicKeyDir
, $this -> encryptionDir
, $this -> keyfilesPath
, $this -> shareKeysPath
);
2013-01-23 23:24:26 +04:00
2013-03-19 22:53:15 +04:00
// Check / create all necessary dirs
foreach ( $setUpDirs as $dirPath ) {
2013-01-23 23:24:26 +04:00
2013-03-19 22:53:15 +04:00
if ( ! $this -> view -> file_exists ( $dirPath ) ) {
$this -> view -> mkdir ( $dirPath );
}
2013-01-14 19:39:04 +04:00
}
2012-07-11 20:51:27 +04:00
// Create user keypair
if (
2013-01-24 22:37:34 +04:00
! $this -> view -> file_exists ( $this -> publicKeyPath )
or ! $this -> view -> file_exists ( $this -> privateKeyPath )
2012-07-11 20:51:27 +04:00
) {
// Generate keypair
$keypair = Crypt :: createKeypair ();
2013-01-23 23:24:26 +04:00
2012-07-25 18:33:25 +04:00
\OC_FileProxy :: $enabled = false ;
2012-07-11 20:51:27 +04:00
// Save public key
2012-07-25 18:33:25 +04:00
$this -> view -> file_put_contents ( $this -> publicKeyPath , $keypair [ 'publicKey' ] );
2012-07-11 20:51:27 +04:00
2012-07-25 18:33:25 +04:00
// Encrypt private key with user pwd as passphrase
$encryptedPrivateKey = Crypt :: symmetricEncryptFileContent ( $keypair [ 'privateKey' ], $passphrase );
2012-07-11 20:51:27 +04:00
2012-07-25 18:33:25 +04:00
// Save private key
$this -> view -> file_put_contents ( $this -> privateKeyPath , $encryptedPrivateKey );
2012-07-11 20:51:27 +04:00
2012-07-25 18:33:25 +04:00
\OC_FileProxy :: $enabled = true ;
2012-07-11 20:51:27 +04:00
}
2012-11-16 22:31:37 +04:00
return true ;
2012-07-11 20:51:27 +04:00
}
2012-07-24 20:53:12 +04:00
2013-03-20 22:26:59 +04:00
/**
* @ brief Check whether pwd recovery is enabled for a given user
* @ return bool
* @ note If records are not being returned , check for a hidden space
* at the start of the uid in db
*/
public function recoveryEnabled () {
2013-03-19 22:53:15 +04:00
2013-03-20 22:26:59 +04:00
$sql = ' SELECT
recovery
FROM
`*PREFIX*encryption`
WHERE
uid = ? ' ;
$args = array ( $this -> userId );
2013-03-19 22:53:15 +04:00
2013-03-20 22:26:59 +04:00
$query = \OCP\DB :: prepare ( $sql );
$result = $query -> execute ( $args );
// Set default in case no records found
$recoveryEnabled = 0 ;
while ( $row = $result -> fetchRow () ) {
$recoveryEnabled = $row [ 'recovery' ];
}
return $recoveryEnabled ;
}
2013-03-19 22:53:15 +04:00
2013-03-20 22:26:59 +04:00
/**
* @ brief Enable / disable pwd recovery for a given user
* @ param bool $enabled Whether to enable or disable recovery
* @ return bool
*/
public function setRecovery ( $enabled ) {
$sql = ' UPDATE
* PREFIX * encryption
SET
recovery = ?
WHERE
uid = ? ' ;
// Ensure value is an integer
$enabled = intval ( $enabled );
$args = array ( $enabled , $this -> userId );
$query = \OCP\DB :: prepare ( $sql );
if ( $query -> execute ( $args ) ) {
return true ;
} else {
return false ;
}
2013-03-19 22:53:15 +04:00
}
2013-01-23 23:24:26 +04:00
/**
* @ brief Find all files and their encryption status within a directory
* @ param string $directory The path of the parent directory to search
* @ return mixed false if 0 found , array on success . Keys : name , path
2013-01-29 23:54:40 +04:00
* @ note $directory needs to be a path relative to OC data dir . e . g .
* / admin / files NOT / backup OR / home / www / oc / data / admin / files
2013-01-23 23:24:26 +04:00
*/
2013-02-20 23:18:00 +04:00
public function findEncFiles ( $directory ) {
2013-01-23 23:24:26 +04:00
// Disable proxy - we don't want files to be decrypted before
// we handle them
\OC_FileProxy :: $enabled = false ;
$found = array ( 'plain' => array (), 'encrypted' => array (), 'legacy' => array () );
if (
2013-01-24 22:37:34 +04:00
$this -> view -> is_dir ( $directory )
&& $handle = $this -> view -> opendir ( $directory )
2013-01-23 23:24:26 +04:00
) {
2012-07-31 22:28:11 +04:00
while ( false !== ( $file = readdir ( $handle ) ) ) {
2013-01-23 23:24:26 +04:00
2012-07-31 22:28:11 +04:00
if (
$file != " . "
&& $file != " .. "
) {
2013-01-23 23:24:26 +04:00
$filePath = $directory . '/' . $this -> view -> getRelativePath ( '/' . $file );
2013-02-05 19:59:28 +04:00
$relPath = $this -> stripUserFilesPath ( $filePath );
2012-07-31 22:28:11 +04:00
2013-01-23 23:24:26 +04:00
// If the path is a directory, search
// its contents
2012-07-31 22:28:11 +04:00
if ( $this -> view -> is_dir ( $filePath ) ) {
2013-02-20 23:18:00 +04:00
$this -> findEncFiles ( $filePath );
2012-07-31 22:28:11 +04:00
2013-01-23 23:24:26 +04:00
// If the path is a file, determine
// its encryption status
} elseif ( $this -> view -> is_file ( $filePath ) ) {
2012-07-31 22:28:11 +04:00
2013-01-23 23:24:26 +04:00
// Disable proxies again, some-
2013-01-31 23:40:51 +04:00
// where they got re-enabled :/
2013-01-23 23:24:26 +04:00
\OC_FileProxy :: $enabled = false ;
2012-07-31 22:28:11 +04:00
2013-01-31 23:40:51 +04:00
$data = $this -> view -> file_get_contents ( $filePath );
2013-01-23 23:24:26 +04:00
// If the file is encrypted
2013-01-31 23:40:51 +04:00
// NOTE: If the userId is
// empty or not set, file will
// detected as plain
2013-02-01 23:31:15 +04:00
// NOTE: This is inefficient;
// scanning every file like this
// will eat server resources :(
2013-01-31 23:40:51 +04:00
if (
2013-04-22 20:50:59 +04:00
Keymanager :: getFileKey ( $this -> view , $this -> userId , $relPath )
2013-03-09 22:18:34 +04:00
&& Crypt :: isCatfileContent ( $data )
2013-01-31 23:40:51 +04:00
) {
2012-07-31 22:28:11 +04:00
2013-01-23 23:24:26 +04:00
$found [ 'encrypted' ][] = array ( 'name' => $file , 'path' => $filePath );
// If the file uses old
// encryption system
2013-03-09 22:18:34 +04:00
} elseif ( Crypt :: isLegacyEncryptedContent ( $this -> tail ( $filePath , 3 ), $relPath ) ) {
2012-07-31 22:28:11 +04:00
2013-01-23 23:24:26 +04:00
$found [ 'legacy' ][] = array ( 'name' => $file , 'path' => $filePath );
2012-07-31 22:28:11 +04:00
2013-01-23 23:24:26 +04:00
// If the file is not encrypted
} else {
2013-04-12 16:30:02 +04:00
$found [ 'plain' ][] = array ( 'name' => $file , 'path' => $relPath );
2012-07-31 22:28:11 +04:00
}
}
}
}
2013-01-23 23:24:26 +04:00
\OC_FileProxy :: $enabled = true ;
2012-07-31 22:28:11 +04:00
2013-01-23 23:24:26 +04:00
if ( empty ( $found ) ) {
return false ;
2012-07-31 22:28:11 +04:00
} else {
2013-01-29 23:54:40 +04:00
2013-01-23 23:24:26 +04:00
return $found ;
2012-07-31 22:28:11 +04:00
}
}
2013-01-23 23:24:26 +04:00
\OC_FileProxy :: $enabled = true ;
2012-07-31 22:28:11 +04:00
return false ;
}
2013-03-09 22:18:34 +04:00
/**
* @ brief Fetch the last lines of a file efficiently
* @ note Safe to use on large files ; does not read entire file to memory
* @ note Derivative of http :// tekkie . flashbit . net / php / tail - functionality - in - php
*/
public function tail ( $filename , $numLines ) {
\OC_FileProxy :: $enabled = false ;
$text = '' ;
$pos = - 1 ;
$handle = $this -> view -> fopen ( $filename , 'r' );
while ( $numLines > 0 ) {
-- $pos ;
if ( fseek ( $handle , $pos , SEEK_END ) !== 0 ) {
rewind ( $handle );
$numLines = 0 ;
} elseif ( fgetc ( $handle ) === " \n " ) {
-- $numLines ;
}
$block_size = ( - $pos ) % 8192 ;
if ( $block_size === 0 || $numLines === 0 ) {
$text = fread ( $handle , ( $block_size === 0 ? 8192 : $block_size ) ) . $text ;
}
}
fclose ( $handle );
\OC_FileProxy :: $enabled = true ;
return $text ;
}
2013-04-27 02:05:20 +04:00
/**
* @ brief Check if a given path identifies an encrypted file
* @ return true / false
*/
2012-10-17 19:35:19 +04:00
public function isEncryptedPath ( $path ) {
2012-12-04 23:53:13 +04:00
// Disable encryption proxy so data retreived is in its
// original form
\OC_FileProxy :: $enabled = false ;
2012-10-17 19:35:19 +04:00
$data = $this -> view -> file_get_contents ( $path );
2012-12-04 23:53:13 +04:00
\OC_FileProxy :: $enabled = true ;
2013-03-09 22:18:34 +04:00
return Crypt :: isCatfileContent ( $data );
2012-10-17 19:35:19 +04:00
}
2013-04-27 02:05:20 +04:00
/**
2013-04-28 01:34:25 +04:00
* @ brief get the file size of the unencrypted file
2013-04-27 02:05:20 +04:00
*
* @ param $path absolute path
* @ return true / false if file is encrypted
*/
2013-04-28 01:34:25 +04:00
public function getFileSize ( $path ) {
$result = 0 ;
2013-04-27 02:05:20 +04:00
// Disable encryption proxy to prevent recursive calls
$proxyStatus = \OC_FileProxy :: $enabled ;
\OC_FileProxy :: $enabled = false ;
2013-04-28 01:34:25 +04:00
// Reformat path for use with OC_FSV
$pathSplit = explode ( '/' , $path );
$pathRelative = implode ( '/' , array_slice ( $pathSplit , 3 ) );
2013-04-27 02:05:20 +04:00
2013-04-28 01:34:25 +04:00
if ( $pathSplit [ 2 ] == 'files' && $this -> view -> file_exists ( $path ) && $this -> isEncryptedPath ( $path )) {
2013-04-27 02:05:20 +04:00
// get the size from filesystem
2013-04-28 01:34:25 +04:00
$fullPath = $this -> view -> getLocalFile ( $path );
$size = filesize ( $fullPath );
2013-04-27 02:05:20 +04:00
// calculate last chunk nr
$lastChunckNr = floor ( $size / 8192 );
// open stream
2013-04-28 01:34:25 +04:00
$stream = fopen ( 'crypt://' . $pathRelative , " r " );
2013-04-27 02:05:20 +04:00
2013-04-28 01:34:25 +04:00
if ( is_resource ( $stream )) {
2013-04-27 02:05:20 +04:00
// calculate last chunk position
$lastChunckPos = ( $lastChunckNr * 8192 );
// seek to end
2013-04-28 01:34:25 +04:00
fseek ( $stream , $lastChunckPos );
2013-04-27 02:05:20 +04:00
// get the content of the last chunk
2013-04-28 01:34:25 +04:00
$lastChunkContent = fread ( $stream , 8192 );
2013-04-27 02:05:20 +04:00
// calc the real file size with the size of the last chunk
$realSize = (( $lastChunckNr * 6126 ) + strlen ( $lastChunkContent ));
2013-04-28 01:34:25 +04:00
// store file size
$result = $realSize ;
2013-04-27 02:05:20 +04:00
}
2013-04-28 01:34:25 +04:00
}
\OC_FileProxy :: $enabled = $proxyStatus ;
return $result ;
}
/**
* @ brief fix the file size of the encrypted file
*
* @ param $path absolute path
* @ return true / false if file is encrypted
*/
public function fixFileSize ( $path ) {
$result = false ;
// Disable encryption proxy to prevent recursive calls
$proxyStatus = \OC_FileProxy :: $enabled ;
\OC_FileProxy :: $enabled = false ;
$realSize = $this -> getFileSize ( $path );
if ( $realSize > 0 ) {
$cached = $this -> view -> getFileInfo ( $path );
$cached [ 'encrypted' ] = 1 ;
// set the size
$cached [ 'unencrypted_size' ] = $realSize ;
2013-04-27 02:05:20 +04:00
// put file info
$this -> view -> putFileInfo ( $path , $cached );
$result = true ;
}
\OC_FileProxy :: $enabled = $proxyStatus ;
return $result ;
}
2013-02-05 19:59:28 +04:00
/**
* @ brief Format a path to be relative to the / user / files / directory
*/
public function stripUserFilesPath ( $path ) {
$trimmed = ltrim ( $path , '/' );
$split = explode ( '/' , $trimmed );
$sliced = array_slice ( $split , 2 );
$relPath = implode ( '/' , $sliced );
return $relPath ;
}
2013-02-20 23:18:00 +04:00
/**
* @ brief Format a shared path to be relative to the / user / files / directory
* @ note Expects a path like / uid / files / Shared / filepath
*/
public function stripSharedFilePath ( $path ) {
$trimmed = ltrim ( $path , '/' );
$split = explode ( '/' , $trimmed );
$sliced = array_slice ( $split , 3 );
$relPath = implode ( '/' , $sliced );
return $relPath ;
}
public function isSharedPath ( $path ) {
$trimmed = ltrim ( $path , '/' );
$split = explode ( '/' , $trimmed );
if ( $split [ 2 ] == " Shared " ) {
return true ;
} else {
return false ;
}
}
2013-01-23 23:24:26 +04:00
/**
* @ brief Encrypt all files in a directory
* @ param string $publicKey the public key to encrypt files with
* @ param string $dirPath the directory whose files will be encrypted
* @ note Encryption is recursive
*/
2013-01-24 22:37:34 +04:00
public function encryptAll ( $publicKey , $dirPath , $legacyPassphrase = null , $newPassphrase = null ) {
2013-04-23 19:36:35 +04:00
2013-02-20 23:18:00 +04:00
if ( $found = $this -> findEncFiles ( $dirPath ) ) {
2012-07-31 22:28:11 +04:00
2013-01-29 23:54:40 +04:00
// Disable proxy to prevent file being encrypted twice
\OC_FileProxy :: $enabled = false ;
2013-01-23 23:24:26 +04:00
// Encrypt unencrypted files
2013-01-29 23:54:40 +04:00
foreach ( $found [ 'plain' ] as $plainFile ) {
2013-01-23 23:24:26 +04:00
2013-04-12 17:18:19 +04:00
//relative to data/<user>/file
$relPath = $plainFile [ 'path' ];
2013-04-23 20:41:01 +04:00
2013-04-12 17:18:19 +04:00
//relative to /data
$rawPath = $this -> userId . '/files/' . $plainFile [ 'path' ];
2013-01-23 23:24:26 +04:00
2013-04-23 20:41:01 +04:00
// Open plain file handle for binary reading
$plainHandle1 = $this -> view -> fopen ( $rawPath , 'rb' );
// 2nd handle for moving plain file - view->rename() doesn't work, this is a workaround
$plainHandle2 = $this -> view -> fopen ( $rawPath . '.plaintmp' , 'wb' );
// Move plain file to a temporary location
stream_copy_to_stream ( $plainHandle1 , $plainHandle2 );
// Close access to original file
// $this->view->fclose( $plainHandle1 ); // not implemented in view{}
// Delete original plain file so we can rename enc file later
$this -> view -> unlink ( $rawPath );
// Open enc file handle for binary writing, with same filename as original plain file
$encHandle = fopen ( 'crypt://' . $relPath , 'wb' );
// Save data from plain stream to new encrypted file via enc stream
// NOTE: Stream{} will be invoked for handling
// the encryption, and should handle all keys
// and their generation etc. automatically
$size = stream_copy_to_stream ( $plainHandle2 , $encHandle );
2013-01-31 23:40:51 +04:00
2013-04-23 20:41:01 +04:00
// Delete temporary plain copy of file
$this -> view -> unlink ( $rawPath . '.plaintmp' );
2013-01-31 23:40:51 +04:00
// Add the file to the cache
\OC\Files\Filesystem :: putFileInfo ( $plainFile [ 'path' ], array ( 'encrypted' => true , 'size' => $size ), '' );
2013-01-23 23:24:26 +04:00
}
2012-07-31 22:28:11 +04:00
2013-01-23 23:24:26 +04:00
// Encrypt legacy encrypted files
2013-01-24 22:37:34 +04:00
if (
! empty ( $legacyPassphrase )
&& ! empty ( $newPassphrase )
) {
2013-01-23 23:24:26 +04:00
2013-02-05 19:35:29 +04:00
foreach ( $found [ 'legacy' ] as $legacyFile ) {
2013-01-23 23:24:26 +04:00
2013-01-24 22:37:34 +04:00
// Fetch data from file
2013-02-05 19:35:29 +04:00
$legacyData = $this -> view -> file_get_contents ( $legacyFile [ 'path' ] );
2013-01-24 22:37:34 +04:00
// Recrypt data, generate catfile
$recrypted = Crypt :: legacyKeyRecryptKeyfile ( $legacyData , $legacyPassphrase , $publicKey , $newPassphrase );
2013-04-12 17:18:19 +04:00
$relPath = $legacyFile [ 'path' ];
$rawPath = $this -> userId . '/files/' . $plainFile [ 'path' ];
2013-02-05 19:35:29 +04:00
2013-01-31 23:40:51 +04:00
// Save keyfile
2013-02-05 19:35:29 +04:00
Keymanager :: setFileKey ( $this -> view , $relPath , $this -> userId , $recrypted [ 'key' ] );
2013-01-24 22:37:34 +04:00
// Overwrite the existing file with the encrypted one
2013-04-12 17:18:19 +04:00
$this -> view -> file_put_contents ( $rawPath , $recrypted [ 'data' ] );
2013-01-31 23:40:51 +04:00
$size = strlen ( $recrypted [ 'data' ] );
// Add the file to the cache
2013-04-12 17:18:19 +04:00
\OC\Files\Filesystem :: putFileInfo ( $rawPath , array ( 'encrypted' => true , 'size' => $size ), '' );
2013-01-24 22:37:34 +04:00
}
2013-01-23 23:24:26 +04:00
}
2013-01-29 23:54:40 +04:00
\OC_FileProxy :: $enabled = true ;
// If files were found, return true
return true ;
2013-01-23 23:24:26 +04:00
2013-01-29 23:54:40 +04:00
} else {
// If no files were found, return false
return false ;
2012-07-31 22:28:11 +04:00
}
}
2013-02-06 18:30:40 +04:00
/**
* @ brief Return important encryption related paths
* @ param string $pathName Name of the directory to return the path of
* @ return string path
*/
2012-11-16 22:31:37 +04:00
public function getPath ( $pathName ) {
switch ( $pathName ) {
case 'publicKeyDir' :
return $this -> publicKeyDir ;
break ;
case 'encryptionDir' :
return $this -> encryptionDir ;
break ;
case 'keyfilesPath' :
return $this -> keyfilesPath ;
break ;
case 'publicKeyPath' :
return $this -> publicKeyPath ;
break ;
case 'privateKeyPath' :
return $this -> privateKeyPath ;
break ;
}
}
2013-02-12 19:48:04 +04:00
/**
* @ brief get path of a file .
* @ param $fileId id of the file
* @ return path of the file
*/
2013-02-20 23:18:00 +04:00
public static function fileIdToPath ( $fileId ) {
$query = \OC_DB :: prepare ( 'SELECT `path`'
2013-02-12 19:48:04 +04:00
. ' FROM `*PREFIX*filecache`'
2013-02-20 23:18:00 +04:00
. ' WHERE `fileid` = ?' );
$result = $query -> execute ( array ( $fileId ) );
2013-02-12 19:48:04 +04:00
$row = $result -> fetchRow ();
2013-02-20 23:18:00 +04:00
return substr ( $row [ 'path' ], 5 );
2013-02-12 19:48:04 +04:00
}
2013-02-19 23:16:50 +04:00
/**
* @ brief Filter an array of UIDs to return only ones ready for sharing
* @ param array $unfilteredUsers users to be checked for sharing readiness
2013-03-30 00:11:29 +04:00
* @ return multi - dimensional array . keys : ready , unready
2013-02-19 23:16:50 +04:00
*/
public function filterShareReadyUsers ( $unfilteredUsers ) {
// This array will collect the filtered IDs
2013-04-18 22:02:27 +04:00
$readyIds = $unreadyIds = array ();
2013-02-19 23:16:50 +04:00
// Loop through users and create array of UIDs that need new keyfiles
foreach ( $unfilteredUsers as $user ) {
$util = new Util ( $this -> view , $user );
// Check that the user is encryption capable, or is the
// public system user 'ownCloud' (for public shares)
if (
$util -> ready ()
2013-04-22 16:30:10 +04:00
or $user == 'owncloud'
2013-02-19 23:16:50 +04:00
) {
2013-03-30 00:11:29 +04:00
// Construct array of ready UIDs for Keymanager{}
$readyIds [] = $user ;
2013-02-19 23:16:50 +04:00
} else {
2013-03-30 00:11:29 +04:00
// Construct array of unready UIDs for Keymanager{}
$unreadyIds [] = $user ;
2013-02-19 23:16:50 +04:00
// Log warning; we can't do necessary setup here
// because we don't have the user passphrase
\OC_Log :: write ( 'Encryption library' , '"' . $user . '" is not setup for encryption' , \OC_Log :: WARN );
}
}
2013-03-30 00:11:29 +04:00
return array (
2013-04-18 22:02:27 +04:00
'ready' => $readyIds
2013-03-30 00:11:29 +04:00
, 'unready' => $unreadyIds
);
2013-02-19 23:16:50 +04:00
}
2013-02-20 23:18:00 +04:00
/**
2013-02-27 22:46:44 +04:00
* @ brief Decrypt a keyfile without knowing how it was encrypted
* @ param string $filePath
* @ param string $fileOwner
* @ param string $privateKey
* @ note Checks whether file was encrypted with openssl_seal or
* openssl_encrypt , and decrypts accrdingly
2013-04-10 19:37:03 +04:00
* @ note This was used when 2 types of encryption for keyfiles was used ,
* but now we ' ve switched to exclusively using openssl_seal ()
2013-02-20 23:18:00 +04:00
*/
2013-02-27 22:46:44 +04:00
public function decryptUnknownKeyfile ( $filePath , $fileOwner , $privateKey ) {
2013-02-20 23:18:00 +04:00
// Get the encrypted keyfile
// NOTE: the keyfile format depends on how it was encrypted! At
// this stage we don't know how it was encrypted
$encKeyfile = Keymanager :: getFileKey ( $this -> view , $this -> userId , $filePath );
// We need to decrypt the keyfile
// Has the file been shared yet?
if (
$this -> userId == $fileOwner
&& ! Keymanager :: getShareKey ( $this -> view , $this -> userId , $filePath ) // NOTE: we can't use isShared() here because it's a post share hook so it always returns true
) {
// The file has no shareKey, and its keyfile must be
// decrypted conventionally
$plainKeyfile = Crypt :: keyDecrypt ( $encKeyfile , $privateKey );
} else {
// The file has a shareKey and must use it for decryption
$shareKey = Keymanager :: getShareKey ( $this -> view , $this -> userId , $filePath );
$plainKeyfile = Crypt :: multiKeyDecrypt ( $encKeyfile , $shareKey , $privateKey );
}
2013-02-27 22:46:44 +04:00
return $plainKeyfile ;
}
/**
* @ brief Encrypt keyfile to multiple users
* @ param array $users list of users which should be able to access the file
* @ param string $filePath path of the file to be shared
*/
public function setSharedFileKeyfiles ( Session $session , array $users , $filePath ) {
// Make sure users are capable of sharing
$filteredUids = $this -> filterShareReadyUsers ( $users );
2013-03-30 00:11:29 +04:00
// trigger_error( print_r($filteredUids, 1) );
if ( ! empty ( $filteredUids [ 'unready' ] ) ) {
// Notify user of unready userDir
// TODO: Move this out of here; it belongs somewhere else
\OCP\JSON :: error ();
}
2013-02-27 22:46:44 +04:00
// Get public keys for each user, ready for generating sharekeys
2013-04-18 22:02:27 +04:00
$userPubKeys = Keymanager :: getPublicKeys ( $this -> view , $filteredUids [ 'ready' ] );
2013-02-27 22:46:44 +04:00
\OC_FileProxy :: $enabled = false ;
// Get the current users's private key for decrypting existing keyfile
$privateKey = $session -> getPrivateKey ();
$fileOwner = \OC\Files\Filesystem :: getOwner ( $filePath );
2013-03-19 22:53:15 +04:00
// Decrypt keyfile
2013-02-27 22:46:44 +04:00
$plainKeyfile = $this -> decryptUnknownKeyfile ( $filePath , $fileOwner , $privateKey );
2013-02-20 23:18:00 +04:00
// Re-enc keyfile to (additional) sharekeys
2013-02-26 22:11:29 +04:00
$multiEncKey = Crypt :: multiKeyEncrypt ( $plainKeyfile , $userPubKeys );
// Save the recrypted key to it's owner's keyfiles directory
// Save new sharekeys to all necessary user directory
2013-02-27 22:50:57 +04:00
// TODO: Reuse the keyfile, it it exists, instead of making a new one
2013-02-26 22:11:29 +04:00
if (
! Keymanager :: setFileKey ( $this -> view , $filePath , $fileOwner , $multiEncKey [ 'data' ] )
|| ! Keymanager :: setShareKeys ( $this -> view , $filePath , $multiEncKey [ 'keys' ] )
) {
2013-02-20 23:18:00 +04:00
trigger_error ( " SET Share keys failed " );
}
// Delete existing keyfile
// Do this last to ensure file is recoverable in case of error
// Keymanager::deleteFileKey( $this->view, $this->userId, $params['fileTarget'] );
\OC_FileProxy :: $enabled = true ;
return true ;
}
2013-03-30 00:11:29 +04:00
2013-04-16 20:29:22 +04:00
/**
* @ brief Find , sanitise and format users sharing a file
* @ note This wraps other methods into a portable bundle
*/
2013-04-22 14:25:55 +04:00
public function getSharingUsersArray ( $sharingEnabled , $filePath , $currentUserId = false ) {
2012-07-11 20:51:27 +04:00
2013-04-16 20:29:22 +04:00
// Check if key recovery is enabled
$recoveryEnabled = $this -> recoveryEnabled ();
// Make sure that a share key is generated for the owner too
2013-04-18 19:53:59 +04:00
list ( $owner , $ownerPath ) = $this -> getUidAndFilename ( $filePath );
2013-04-16 20:29:22 +04:00
if ( $sharingEnabled ) {
// Find out who, if anyone, is sharing the file
2013-04-22 16:14:28 +04:00
$userIds = \OCP\Share :: getUsersSharingFile ( $ownerPath , $owner , true , true , true );
2013-04-16 20:29:22 +04:00
}
// If recovery is enabled, add the
// Admin UID to list of users to share to
if ( $recoveryEnabled ) {
// FIXME: Create a separate admin user purely for recovery, and create method in util for fetching this id from DB?
$adminUid = 'recoveryAdmin' ;
$userIds [] = $adminUid ;
}
2013-04-22 14:25:55 +04:00
// add current user if given
if ( $currentUserId != false ) {
$userIds [] = $currentUserId ;
}
2013-04-16 20:29:22 +04:00
// Remove duplicate UIDs
$uniqueUserIds = array_unique ( $userIds );
return $uniqueUserIds ;
}
2013-03-30 00:11:29 +04:00
/**
2013-03-26 15:39:55 +04:00
* @ brief get uid of the owners of the file and the path to the file
2013-04-18 18:37:49 +04:00
* @ param $path Path of the file to check
2013-04-10 19:37:03 +04:00
* @ note $shareFilePath must be relative to data / UID / files . Files
* relative to / Shared are also acceptable
2013-03-26 15:39:55 +04:00
* @ return array
*/
2013-04-18 18:37:49 +04:00
public function getUidAndFilename ( $path ) {
$fileOwnerUid = \OC\Files\Filesystem :: getOwner ( $path );
2013-04-10 19:37:03 +04:00
// Check that UID is valid
if ( ! \OCP\User :: userExists ( $fileOwnerUid ) ) {
2013-04-18 18:37:49 +04:00
throw new \Exception ( 'Could not find owner (UID = "' . var_export ( $fileOwnerUid , 1 ) . '") of file "' . $path . '"' );
2013-04-10 19:37:03 +04:00
}
// NOTE: Bah, this dependency should be elsewhere
\OC\Files\Filesystem :: initMountPoints ( $fileOwnerUid );
// If the file owner is the currently logged in user
if ( $fileOwnerUid == $this -> userId ) {
// Assume the path supplied is correct
2013-04-18 18:37:49 +04:00
$filename = $path ;
2013-04-10 19:37:03 +04:00
} else {
2013-04-18 18:37:49 +04:00
$info = \OC\Files\Filesystem :: getFileInfo ( $path );
2013-04-10 19:37:03 +04:00
$ownerView = new \OC\Files\View ( '/' . $fileOwnerUid . '/files' );
// Fetch real file path from DB
$filename = $ownerView -> getPath ( $info [ 'fileid' ] ); // TODO: Check that this returns a path without including the user data dir
2013-03-26 15:39:55 +04:00
}
2013-04-10 19:37:03 +04:00
// Make path relative for use by $view
$relpath = $fileOwnerUid . '/' . $this -> fileFolderName . '/' . $filename ;
// Check that the filename we're using is working
if ( $this -> view -> file_exists ( $relpath ) ) {
2013-04-18 15:41:21 +04:00
return array ( $fileOwnerUid , $filename );
2013-04-10 19:37:03 +04:00
} else {
2013-04-25 22:23:54 +04:00
return false ;
2013-04-10 19:37:03 +04:00
}
2013-03-26 15:39:55 +04:00
}
2013-04-22 13:58:39 +04:00
/**
2013-04-22 16:14:28 +04:00
* @ brief geo recursively through a dir and collect all files and sub files .
2013-04-22 13:58:39 +04:00
* @ param type $dir relative to the users files folder
* @ return array with list of files relative to the users files folder
*/
public function getAllFiles ( $dir ) {
$result = array ();
2013-04-22 14:32:38 +04:00
$content = $this -> view -> getDirectoryContent ( $this -> userFilesDir . $dir );
2013-04-22 13:58:39 +04:00
foreach ( $content as $c ) {
if ( $c [ 'type' ] === " dir " ) {
$result = array_merge ( $result , $this -> getAllFiles ( substr ( $c [ 'path' ], 5 )));
} else {
$result [] = substr ( $c [ 'path' ], 5 );
}
}
return $result ;
}
2012-07-11 20:51:27 +04:00
}