2012-07-11 16:51:27 +00: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 18:11:29 +00: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 15:37:03 +00:00
# Sharing files to other users currently broken (due to merge + ongoing implementation of support for lost password recovery)
# 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)
# getOwner() currently returns false in all circumstances, unsure what code is returning this...
2013-02-26 18:11:29 +00:00
# Missing features
# ----------------
# Make sure user knows if large files weren't encrypted
2013-04-10 15:37:03 +00:00
# Support for resharing encrypted files
2013-03-20 18:26:59 +00:00
# Test
# ----
# Test that writing files works when recovery is enabled, and sharing API is disabled
# Test trashbin support
2013-02-26 18:11:29 +00:00
// Old Todo:
2012-07-11 16:51:27 +00:00
// - Crypt/decrypt button in the userinterface
// - Setting if crypto should be on by default
2013-01-24 18:37:34 +00:00
// - Add a setting "Don´ t encrypt files larger than xx because of performance
// reasons"
2012-07-11 16:51:27 +00:00
2012-10-17 15:35:19 +00:00
namespace OCA\Encryption ;
2012-07-11 16:51:27 +00:00
/**
2012-07-25 14:33:25 +00:00
* @ brief Class for utilities relating to encrypted file storage system
2013-01-31 19:40:51 +00: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 14:33:25 +00:00
* unused , likely to become obsolete shortly
2012-07-11 16:51:27 +00:00
*/
class Util {
2012-12-04 19:53:13 +00:00
2013-01-24 18:37:34 +00:00
// Web UI:
2012-12-04 19:53:13 +00:00
2013-01-24 18:37:34 +00: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 19:53:13 +00:00
2013-01-24 18:37:34 +00:00
// WebDAV:
2012-12-04 19:53:13 +00:00
2013-01-24 18:37:34 +00: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 19:53:13 +00:00
2013-01-24 18:37:34 +00:00
// Legacy support:
2012-12-04 19:53:13 +00:00
2013-01-24 18:37:34 +00: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 19:53:13 +00:00
2013-01-24 18:37:34 +00:00
// Admin UI:
2012-12-04 19:53:13 +00:00
2013-01-24 18:37:34 +00:00
//// DONE: changing user password also changes encryption passphrase
2012-12-11 17:12:46 +00:00
2013-01-24 18:37:34 +00: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 19:53:13 +00:00
2013-01-24 18:37:34 +00:00
// Sharing:
2012-12-11 17:12:46 +00:00
2013-01-24 18:37:34 +00:00
//// TODO: add support for encrypting to multiple public keys
//// TODO: add support for decrypting to multiple private keys
2012-12-11 17:12:46 +00:00
2013-01-24 18:37:34 +00:00
// Integration testing:
2012-12-04 19:53:13 +00:00
2013-01-24 18:37:34 +00:00
//// TODO: test new encryption with versioning
//// TODO: test new encryption with sharing
//// TODO: test new encryption with proxies
2012-12-04 19:53:13 +00:00
2012-11-22 14:08:19 +00:00
2012-07-11 16:51:27 +00:00
private $view ; // OC_FilesystemView object for filesystem operations
2013-01-23 19:24:26 +00:00
private $userId ; // ID of the currently logged-in user
2012-07-11 16:51:27 +00:00
private $pwd ; // User Password
private $client ; // Client side encryption mode flag
2013-01-14 15:39:04 +00: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 18:31:37 +00:00
private $publicKeyPath ; // Path to user's public key
private $privateKeyPath ; // Path to user's private key
2012-07-11 16:51:27 +00:00
2012-07-25 14:33:25 +00:00
public function __construct ( \OC_FilesystemView $view , $userId , $client = false ) {
2012-07-11 16:51:27 +00:00
$this -> view = $view ;
2012-07-25 14:33:25 +00:00
$this -> userId = $userId ;
2012-07-11 16:51:27 +00:00
$this -> client = $client ;
2013-01-23 19:24:26 +00:00
$this -> userDir = '/' . $this -> userId ;
2013-04-10 15:37:03 +00:00
$this -> fileFolderName = 'files' ;
$this -> userFilesDir = '/' . $this -> userId . '/' . $this -> fileFolderName ; // TODO: Does this need to be user configurable?
2012-07-25 14:33:25 +00:00
$this -> publicKeyDir = '/' . 'public-keys' ;
$this -> encryptionDir = '/' . $this -> userId . '/' . 'files_encryption' ;
$this -> keyfilesPath = $this -> encryptionDir . '/' . 'keyfiles' ;
2013-01-14 15:39:04 +00:00
$this -> shareKeysPath = $this -> encryptionDir . '/' . 'share-keys' ;
2012-07-25 14:33:25 +00: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 16:51:27 +00:00
}
public function ready () {
if (
2013-01-23 19:24:26 +00:00
! $this -> view -> file_exists ( $this -> encryptionDir )
or ! $this -> view -> file_exists ( $this -> keyfilesPath )
or ! $this -> view -> file_exists ( $this -> shareKeysPath )
2012-07-25 14:33:25 +00:00
or ! $this -> view -> file_exists ( $this -> publicKeyPath )
or ! $this -> view -> file_exists ( $this -> privateKeyPath )
2012-07-11 16:51:27 +00:00
) {
return false ;
} else {
return true ;
}
}
2012-07-24 16:53:12 +00:00
/**
2012-07-25 14:33:25 +00:00
* @ brief Sets up user folders and keys for serverside encryption
2012-07-24 16:53:12 +00:00
* @ param $passphrase passphrase to encrypt server - stored private key with
*/
2012-07-25 14:33:25 +00:00
public function setupServerSide ( $passphrase = null ) {
2012-07-11 16:51:27 +00:00
2013-03-19 18:53:15 +00:00
// Set directories to check / create
$setUpDirs = array (
$this -> userDir
, $this -> userFilesDir
, $this -> publicKeyDir
, $this -> encryptionDir
, $this -> keyfilesPath
, $this -> shareKeysPath
);
2013-01-23 19:24:26 +00:00
2013-03-19 18:53:15 +00:00
// Check / create all necessary dirs
foreach ( $setUpDirs as $dirPath ) {
2013-01-23 19:24:26 +00:00
2013-03-19 18:53:15 +00:00
if ( ! $this -> view -> file_exists ( $dirPath ) ) {
$this -> view -> mkdir ( $dirPath );
}
2013-01-14 15:39:04 +00:00
}
2012-07-11 16:51:27 +00:00
// Create user keypair
if (
2013-01-24 18:37:34 +00:00
! $this -> view -> file_exists ( $this -> publicKeyPath )
or ! $this -> view -> file_exists ( $this -> privateKeyPath )
2012-07-11 16:51:27 +00:00
) {
// Generate keypair
$keypair = Crypt :: createKeypair ();
2013-01-23 19:24:26 +00:00
2012-07-25 14:33:25 +00:00
\OC_FileProxy :: $enabled = false ;
2012-07-11 16:51:27 +00:00
// Save public key
2012-07-25 14:33:25 +00:00
$this -> view -> file_put_contents ( $this -> publicKeyPath , $keypair [ 'publicKey' ] );
2012-07-11 16:51:27 +00:00
2012-07-25 14:33:25 +00:00
// Encrypt private key with user pwd as passphrase
$encryptedPrivateKey = Crypt :: symmetricEncryptFileContent ( $keypair [ 'privateKey' ], $passphrase );
2012-07-11 16:51:27 +00:00
2012-07-25 14:33:25 +00:00
// Save private key
$this -> view -> file_put_contents ( $this -> privateKeyPath , $encryptedPrivateKey );
2012-07-11 16:51:27 +00:00
2012-07-25 14:33:25 +00:00
\OC_FileProxy :: $enabled = true ;
2012-07-11 16:51:27 +00:00
}
2012-11-16 18:31:37 +00:00
return true ;
2012-07-11 16:51:27 +00:00
}
2012-07-24 16:53:12 +00:00
2013-03-20 18:26:59 +00: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 18:53:15 +00:00
2013-03-20 18:26:59 +00:00
$sql = ' SELECT
recovery
FROM
`*PREFIX*encryption`
WHERE
uid = ? ' ;
$args = array ( $this -> userId );
2013-03-19 18:53:15 +00:00
2013-03-20 18:26:59 +00: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 18:53:15 +00:00
2013-03-20 18:26:59 +00: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 18:53:15 +00:00
}
2013-01-23 19:24:26 +00: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 19:54:40 +00: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 19:24:26 +00:00
*/
2013-02-20 19:18:00 +00:00
public function findEncFiles ( $directory ) {
2013-01-23 19:24:26 +00: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 18:37:34 +00:00
$this -> view -> is_dir ( $directory )
&& $handle = $this -> view -> opendir ( $directory )
2013-01-23 19:24:26 +00:00
) {
2012-07-31 18:28:11 +00:00
while ( false !== ( $file = readdir ( $handle ) ) ) {
2013-01-23 19:24:26 +00:00
2012-07-31 18:28:11 +00:00
if (
$file != " . "
&& $file != " .. "
) {
2013-01-23 19:24:26 +00:00
$filePath = $directory . '/' . $this -> view -> getRelativePath ( '/' . $file );
2013-02-05 15:59:28 +00:00
$relPath = $this -> stripUserFilesPath ( $filePath );
2012-07-31 18:28:11 +00:00
2013-01-23 19:24:26 +00:00
// If the path is a directory, search
// its contents
2012-07-31 18:28:11 +00:00
if ( $this -> view -> is_dir ( $filePath ) ) {
2013-02-20 19:18:00 +00:00
$this -> findEncFiles ( $filePath );
2012-07-31 18:28:11 +00:00
2013-01-23 19:24:26 +00:00
// If the path is a file, determine
// its encryption status
} elseif ( $this -> view -> is_file ( $filePath ) ) {
2012-07-31 18:28:11 +00:00
2013-01-23 19:24:26 +00:00
// Disable proxies again, some-
2013-01-31 19:40:51 +00:00
// where they got re-enabled :/
2013-01-23 19:24:26 +00:00
\OC_FileProxy :: $enabled = false ;
2012-07-31 18:28:11 +00:00
2013-01-31 19:40:51 +00:00
$data = $this -> view -> file_get_contents ( $filePath );
2013-01-23 19:24:26 +00:00
// If the file is encrypted
2013-01-31 19:40:51 +00:00
// NOTE: If the userId is
// empty or not set, file will
// detected as plain
2013-02-01 19:31:15 +00:00
// NOTE: This is inefficient;
// scanning every file like this
// will eat server resources :(
2013-01-31 19:40:51 +00:00
if (
Keymanager :: getFileKey ( $this -> view , $this -> userId , $file )
2013-03-09 18:18:34 +00:00
&& Crypt :: isCatfileContent ( $data )
2013-01-31 19:40:51 +00:00
) {
2012-07-31 18:28:11 +00:00
2013-01-23 19:24:26 +00:00
$found [ 'encrypted' ][] = array ( 'name' => $file , 'path' => $filePath );
// If the file uses old
// encryption system
2013-03-09 18:18:34 +00:00
} elseif ( Crypt :: isLegacyEncryptedContent ( $this -> tail ( $filePath , 3 ), $relPath ) ) {
2012-07-31 18:28:11 +00:00
2013-01-23 19:24:26 +00:00
$found [ 'legacy' ][] = array ( 'name' => $file , 'path' => $filePath );
2012-07-31 18:28:11 +00:00
2013-01-23 19:24:26 +00:00
// If the file is not encrypted
} else {
2013-04-12 12:30:02 +00:00
$found [ 'plain' ][] = array ( 'name' => $file , 'path' => $relPath );
2012-07-31 18:28:11 +00:00
}
}
}
}
2013-01-23 19:24:26 +00:00
\OC_FileProxy :: $enabled = true ;
2012-07-31 18:28:11 +00:00
2013-01-23 19:24:26 +00:00
if ( empty ( $found ) ) {
return false ;
2012-07-31 18:28:11 +00:00
} else {
2013-01-29 19:54:40 +00:00
2013-01-23 19:24:26 +00:00
return $found ;
2012-07-31 18:28:11 +00:00
}
}
2013-01-23 19:24:26 +00:00
\OC_FileProxy :: $enabled = true ;
2012-07-31 18:28:11 +00:00
return false ;
}
2013-03-09 18:18:34 +00: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 ;
}
2012-10-17 15:35:19 +00:00
/**
* @ brief Check if a given path identifies an encrypted file
* @ return true / false
*/
public function isEncryptedPath ( $path ) {
2012-12-04 19:53:13 +00:00
// Disable encryption proxy so data retreived is in its
// original form
\OC_FileProxy :: $enabled = false ;
2012-10-17 15:35:19 +00:00
$data = $this -> view -> file_get_contents ( $path );
2012-12-04 19:53:13 +00:00
\OC_FileProxy :: $enabled = true ;
2013-03-09 18:18:34 +00:00
return Crypt :: isCatfileContent ( $data );
2012-10-17 15:35:19 +00:00
}
2013-02-05 15:59:28 +00: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 19:18:00 +00: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 19:24:26 +00: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 18:37:34 +00:00
public function encryptAll ( $publicKey , $dirPath , $legacyPassphrase = null , $newPassphrase = null ) {
2012-07-31 18:28:11 +00:00
2013-02-20 19:18:00 +00:00
if ( $found = $this -> findEncFiles ( $dirPath ) ) {
2012-07-31 18:28:11 +00:00
2013-01-29 19:54:40 +00:00
// Disable proxy to prevent file being encrypted twice
\OC_FileProxy :: $enabled = false ;
2013-01-23 19:24:26 +00:00
// Encrypt unencrypted files
2013-01-29 19:54:40 +00:00
foreach ( $found [ 'plain' ] as $plainFile ) {
2013-03-09 18:18:34 +00:00
// Open plain file handle
// Open enc file handle
2013-01-29 19:54:40 +00:00
2013-03-09 18:18:34 +00:00
// Read plain file in chunks
2013-01-23 19:24:26 +00:00
2013-04-12 13:18:19 +00:00
//relative to data/<user>/file
$relPath = $plainFile [ 'path' ];
//relative to /data
$rawPath = $this -> userId . '/files/' . $plainFile [ 'path' ];
2013-03-09 18:18:34 +00:00
// Open handle with for binary reading
2013-04-12 13:18:19 +00:00
$plainHandle = $this -> view -> fopen ( $rawPath , 'rb' );
2013-03-09 18:18:34 +00:00
// Open handle with for binary writing
2013-04-12 12:13:38 +00:00
2013-04-12 13:18:19 +00:00
$encHandle = fopen ( 'crypt://' . $relPath . '.tmp' , 'wb' );
2013-01-23 19:24:26 +00:00
// Overwrite the existing file with the encrypted one
2013-03-09 18:18:34 +00:00
//$this->view->file_put_contents( $plainFile['path'], $encrypted['data'] );
$size = stream_copy_to_stream ( $plainHandle , $encHandle );
2013-04-12 12:13:38 +00:00
2013-04-12 13:18:19 +00:00
$this -> view -> rename ( $rawPath . '.tmp' , $rawPath );
2013-04-12 12:13:38 +00:00
2013-03-09 18:18:34 +00:00
// Fetch the key that has just been set/updated by the stream
2013-04-12 12:13:38 +00:00
//$encKey = Keymanager::getFileKey( $this->view, $this->userId, $relPath );
2013-01-31 19:40:51 +00:00
2013-03-09 18:18:34 +00:00
// Save keyfile
2013-04-12 12:13:38 +00:00
//Keymanager::setFileKey( $this->view, $relPath, $this->userId, $encKey );
2013-01-31 19:40:51 +00:00
// Add the file to the cache
\OC\Files\Filesystem :: putFileInfo ( $plainFile [ 'path' ], array ( 'encrypted' => true , 'size' => $size ), '' );
2013-01-23 19:24:26 +00:00
}
2012-07-31 18:28:11 +00:00
2013-01-23 19:24:26 +00:00
// Encrypt legacy encrypted files
2013-01-24 18:37:34 +00:00
if (
! empty ( $legacyPassphrase )
&& ! empty ( $newPassphrase )
) {
2013-01-23 19:24:26 +00:00
2013-02-05 15:35:29 +00:00
foreach ( $found [ 'legacy' ] as $legacyFile ) {
2013-01-23 19:24:26 +00:00
2013-01-24 18:37:34 +00:00
// Fetch data from file
2013-02-05 15:35:29 +00:00
$legacyData = $this -> view -> file_get_contents ( $legacyFile [ 'path' ] );
2013-01-24 18:37:34 +00:00
// Recrypt data, generate catfile
$recrypted = Crypt :: legacyKeyRecryptKeyfile ( $legacyData , $legacyPassphrase , $publicKey , $newPassphrase );
2013-04-12 13:18:19 +00:00
$relPath = $legacyFile [ 'path' ];
$rawPath = $this -> userId . '/files/' . $plainFile [ 'path' ];
2013-02-05 15:35:29 +00:00
2013-01-31 19:40:51 +00:00
// Save keyfile
2013-02-05 15:35:29 +00:00
Keymanager :: setFileKey ( $this -> view , $relPath , $this -> userId , $recrypted [ 'key' ] );
2013-01-24 18:37:34 +00:00
// Overwrite the existing file with the encrypted one
2013-04-12 13:18:19 +00:00
$this -> view -> file_put_contents ( $rawPath , $recrypted [ 'data' ] );
2013-01-31 19:40:51 +00:00
$size = strlen ( $recrypted [ 'data' ] );
// Add the file to the cache
2013-04-12 13:18:19 +00:00
\OC\Files\Filesystem :: putFileInfo ( $rawPath , array ( 'encrypted' => true , 'size' => $size ), '' );
2013-01-24 18:37:34 +00:00
}
2013-01-23 19:24:26 +00:00
}
2013-01-29 19:54:40 +00:00
\OC_FileProxy :: $enabled = true ;
// If files were found, return true
return true ;
2013-01-23 19:24:26 +00:00
2013-01-29 19:54:40 +00:00
} else {
// If no files were found, return false
return false ;
2012-07-31 18:28:11 +00:00
}
}
2013-02-06 14:30:40 +00: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 18:31:37 +00: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 15:48:04 +00:00
/**
* @ brief get path of a file .
* @ param $fileId id of the file
* @ return path of the file
*/
2013-02-20 19:18:00 +00:00
public static function fileIdToPath ( $fileId ) {
$query = \OC_DB :: prepare ( 'SELECT `path`'
2013-02-12 15:48:04 +00:00
. ' FROM `*PREFIX*filecache`'
2013-02-20 19:18:00 +00:00
. ' WHERE `fileid` = ?' );
$result = $query -> execute ( array ( $fileId ) );
2013-02-12 15:48:04 +00:00
$row = $result -> fetchRow ();
2013-02-20 19:18:00 +00:00
return substr ( $row [ 'path' ], 5 );
2013-02-12 15:48:04 +00:00
}
2013-02-19 19:16:50 +00: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-29 20:11:29 +00:00
* @ return multi - dimensional array . keys : ready , unready
2013-02-19 19:16:50 +00:00
*/
public function filterShareReadyUsers ( $unfilteredUsers ) {
// This array will collect the filtered IDs
$userIds = array ();
// Loop through users and create array of UIDs that need new keyfiles
foreach ( $unfilteredUsers as $user ) {
$util = new Util ( $this -> view , $user );
2013-03-29 20:11:29 +00:00
$readyIds = $unreadyIds = array ();
2013-02-19 19:16:50 +00:00
// Check that the user is encryption capable, or is the
// public system user 'ownCloud' (for public shares)
if (
$util -> ready ()
or $user == 'ownCloud'
) {
2013-03-29 20:11:29 +00:00
// Construct array of ready UIDs for Keymanager{}
$readyIds [] = $user ;
2013-02-19 19:16:50 +00:00
} else {
2013-03-29 20:11:29 +00:00
// Construct array of unready UIDs for Keymanager{}
$unreadyIds [] = $user ;
2013-02-19 19:16:50 +00: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-29 20:11:29 +00:00
return array (
'ready' => $userIds
, 'unready' => $unreadyIds
);
2013-02-19 19:16:50 +00:00
}
2013-02-20 19:18:00 +00:00
/**
* @ brief Expand given path to all sub files & folders
* @ param string $path path which needs to be updated
2013-02-26 18:33:31 +00:00
* @ return array $pathsArray all found file paths
* @ note Paths of directories excluded , only * file * paths are returned
2013-02-20 19:18:00 +00:00
*/
public function getPaths ( $path ) {
// Default return value is success
$result = true ;
// Make path include 'files' dir for OC_FSV operations
$fPath = 'files' . $path ;
// If we're handling a single file
if ( ! $this -> view -> is_dir ( $fPath ) ) {
$pathsArray [] = $path ;
// If we're handling a folder (recursively)
} else {
$subFiles = $this -> view -> getDirectoryContent ( $fPath );
foreach ( $subFiles as $file ) {
$filePath = substr ( $file [ 'path' ], 5 );
// If this is a nested file
if ( ! $this -> view -> is_dir ( $fPath ) ) {
// Add the file path to array
$pathsArray [] = $path ;
} else {
// If this is a nested folder
$dirPaths = $this -> getPaths ( $filePath );
// Add all subfiles & folders to the array
$pathsArray = array_merge ( $dirPaths , $pathsArray );
}
}
}
return $pathsArray ;
}
/**
2013-02-27 18:46:44 +00: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 15:37:03 +00: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 19:18:00 +00:00
*/
2013-02-27 18:46:44 +00:00
public function decryptUnknownKeyfile ( $filePath , $fileOwner , $privateKey ) {
2013-02-20 19:18:00 +00: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 18:46:44 +00: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-29 20:11:29 +00: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 18:46:44 +00:00
// Get public keys for each user, ready for generating sharekeys
2013-03-29 20:11:29 +00:00
$userPubKeys = Keymanager :: getPublicKeys ( $this -> view , $filteredUids [ 'ready' ] ); // TODO: check this includes the owner's public key
2013-02-27 18:46:44 +00: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 18:53:15 +00:00
// Decrypt keyfile
2013-02-27 18:46:44 +00:00
$plainKeyfile = $this -> decryptUnknownKeyfile ( $filePath , $fileOwner , $privateKey );
2013-02-20 19:18:00 +00:00
// Re-enc keyfile to (additional) sharekeys
2013-02-26 18:11:29 +00: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 18:50:57 +00:00
// TODO: Reuse the keyfile, it it exists, instead of making a new one
2013-02-26 18:11:29 +00:00
if (
! Keymanager :: setFileKey ( $this -> view , $filePath , $fileOwner , $multiEncKey [ 'data' ] )
|| ! Keymanager :: setShareKeys ( $this -> view , $filePath , $multiEncKey [ 'keys' ] )
) {
2013-02-20 19:18:00 +00: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-29 20:11:29 +00:00
/**
* @ brief Returns the users who are sharing a file , including the file owner
* @ param $path Relative path of the file , like files / file . txt
* @ return $users array of UIDs
* @ note This wraps the OCP\Share method , but includes the owner even if
* the file isn ' t registered in sharing API
*/
public function getUsersSharingFile ( $path ) {
$users = \OCP\Share :: getUsersSharingFile ( $path , true , true );
// FIXME: this is returning empty :/
$owner = \OC\Files\Filesystem :: getOwner ( $path );
// trigger_error( var_export( $owner, 1));
$users [] = $owner ;
return array_unique ( $users );
}
2013-04-16 16:29:22 +00:00
/**
* @ brief Find , sanitise and format users sharing a file
* @ note This wraps other methods into a portable bundle
*/
public function getSharingUsersArray ( $sharingEnabled , $filePath ) {
2012-07-11 16:51:27 +00:00
2013-04-16 16:29:22 +00:00
// Check if key recovery is enabled
$recoveryEnabled = $this -> recoveryEnabled ();
// Make sure that a share key is generated for the owner too
$userIds = array ( $this -> userId );
if ( $sharingEnabled ) {
// Find out who, if anyone, is sharing the file
$shareUids = \OCP\Share :: getUsersSharingFile ( $filePath , true , true , true );
$userIds = array_merge ( $userIds , $shareUids );
}
// 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 ;
}
// Remove duplicate UIDs
$uniqueUserIds = array_unique ( $userIds );
return $uniqueUserIds ;
}
2013-03-29 20:11:29 +00:00
/**
2013-03-26 11:39:55 +00:00
* @ brief get uid of the owners of the file and the path to the file
2013-04-10 15:37:03 +00:00
* @ param $shareFilePath Path of the file to check
* @ note $shareFilePath must be relative to data / UID / files . Files
* relative to / Shared are also acceptable
2013-03-26 11:39:55 +00:00
* @ return array
*/
2013-04-10 15:37:03 +00:00
public function getUidAndFilename ( $shareFilePath ) {
$fileOwnerUid = \OC\Files\Filesystem :: getOwner ( $shareFilePath );
// Check that UID is valid
if ( ! \OCP\User :: userExists ( $fileOwnerUid ) ) {
throw new \Exception ( 'Could not find owner (UID = "' . var_export ( $fileOwnerUid , 1 ) . '") of file "' . $shareFilePath . '"' );
}
// 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
$filename = $shareFilePath ;
} else {
$info = \OC\Files\Filesystem :: getFileInfo ( $shareFilePath );
$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 11:39:55 +00:00
}
2013-04-10 15:37:03 +00: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 ) ) {
return array ( $fileOwnerUid , $relpath );
} else {
throw new \Exception ( 'Supplied path could not be resolved "' . $shareFilePath . '"' );
}
2013-03-26 11:39:55 +00:00
}
2012-07-11 16:51:27 +00:00
}