Development snapshot, lots of fixes
Web UI based encryption working Crypt and Util unit tests passing
This commit is contained in:
parent
6df35eeddc
commit
637891b771
|
@ -37,7 +37,7 @@ class Hooks {
|
|||
|
||||
public static function login( $params ) {
|
||||
|
||||
if ( Crypt::mode( $params['uid'] ) == 'server' ) {
|
||||
// if ( Crypt::mode( $params['uid'] ) == 'server' ) {
|
||||
|
||||
# TODO: use lots of dependency injection here
|
||||
|
||||
|
@ -47,6 +47,8 @@ class Hooks {
|
|||
|
||||
if ( ! $util->ready() ) {
|
||||
|
||||
\OC_Log::write( 'Encryption library', 'User account "' . $params['uid'] . '" is not ready for encryption; configuration started' , \OC_Log::DEBUG );
|
||||
|
||||
return $util->setupServerSide( $params['password'] );
|
||||
|
||||
}
|
||||
|
@ -59,9 +61,15 @@ class Hooks {
|
|||
|
||||
# TODO: dont manually encrypt the private keyfile - use the config options of openssl_pkey_export instead for better mobile compatibility
|
||||
|
||||
//trigger_error( "\$encryptedKey = ".var_export($encryptedKey)." \n\n\$params['password'] = ".var_export($params['password'] ) );
|
||||
|
||||
// trigger_error( "\$params['password'] = {$params['password']}" );
|
||||
|
||||
$_SESSION['enckey'] = Crypt::symmetricDecryptFileContent( $encryptedKey, $params['password'] );
|
||||
|
||||
}
|
||||
// trigger_error( "\$_SESSION['enckey'] = {$_SESSION['enckey']}" );
|
||||
|
||||
// }
|
||||
|
||||
return true;
|
||||
|
||||
|
|
|
@ -45,23 +45,26 @@ class Crypt {
|
|||
*/
|
||||
public static function mode( $user = null ) {
|
||||
|
||||
$mode = \OC_Appconfig::getValue( 'files_encryption', 'mode', 'none' );
|
||||
// $mode = \OC_Appconfig::getValue( 'files_encryption', 'mode', 'none' );
|
||||
//
|
||||
// if ( $mode == 'user') {
|
||||
// if ( !$user ) {
|
||||
// $user = \OCP\User::getUser();
|
||||
// }
|
||||
// $mode = 'none';
|
||||
// if ( $user ) {
|
||||
// $query = \OC_DB::prepare( "SELECT mode FROM *PREFIX*encryption WHERE uid = ?" );
|
||||
// $result = $query->execute(array($user));
|
||||
// if ($row = $result->fetchRow()){
|
||||
// $mode = $row['mode'];
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// return $mode;
|
||||
|
||||
if ( $mode == 'user') {
|
||||
if ( !$user ) {
|
||||
$user = \OCP\User::getUser();
|
||||
}
|
||||
$mode = 'none';
|
||||
if ( $user ) {
|
||||
$query = \OC_DB::prepare( "SELECT mode FROM *PREFIX*encryption WHERE uid = ?" );
|
||||
$result = $query->execute(array($user));
|
||||
if ($row = $result->fetchRow()){
|
||||
$mode = $row['mode'];
|
||||
}
|
||||
}
|
||||
}
|
||||
return 'server';
|
||||
|
||||
return $mode;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -101,7 +104,7 @@ class Crypt {
|
|||
/**
|
||||
* @brief Remove arbitrary padding to encrypted data
|
||||
* @param string $padded padded data to remove padding from
|
||||
* @return padded data on success, false on error
|
||||
* @return unpadded data on success, false on error
|
||||
*/
|
||||
public static function removePadding( $padded ) {
|
||||
|
||||
|
@ -220,7 +223,7 @@ class Crypt {
|
|||
|
||||
} else {
|
||||
|
||||
\OC_Log::write( 'Encryption library', 'Decryption (symmetric) of content failed' , \OC_Log::ERROR );
|
||||
throw new \Exception( 'Encryption library: Decryption (symmetric) of content failed' );
|
||||
|
||||
return false;
|
||||
|
||||
|
@ -317,7 +320,7 @@ class Crypt {
|
|||
|
||||
if ( !$keyfileContent ) {
|
||||
|
||||
return false;
|
||||
throw new \Exception( 'Encryption library: no data provided for decryption' );
|
||||
|
||||
}
|
||||
|
||||
|
@ -330,16 +333,12 @@ class Crypt {
|
|||
// Remove IV and IV identifier text to expose encrypted content
|
||||
$encryptedContent = substr( $noPadding, 0, -22 );
|
||||
|
||||
//trigger_error( "\n\n\$noPadding = ".var_export($noPadding)."\n\n\$iv = ".var_export($iv )."\n\n\$encryptedContent = ".var_export($encryptedContent) );
|
||||
|
||||
if ( $plainContent = self::decrypt( $encryptedContent, $iv, $passphrase ) ) {
|
||||
|
||||
return $plainContent;
|
||||
|
||||
} else {
|
||||
|
||||
\OC_Log::write( 'Encryption library', 'Decryption (symmetric) of keyfile content failed' , \OC_Log::ERROR );
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -230,7 +230,7 @@ class Keymanager {
|
|||
* @return bool true/false
|
||||
*/
|
||||
public static function setFileKey( $path, $key, $view = Null, $dbClassName = '\OC_DB') {
|
||||
|
||||
var_dump($path);
|
||||
$targetPath = ltrim( $path, '/' );
|
||||
$user = \OCP\User::getUser();
|
||||
|
||||
|
@ -274,7 +274,10 @@ class Keymanager {
|
|||
$view->chroot( '/' . $user . '/files_encryption/keyfiles' );
|
||||
|
||||
// If the file resides within a subdirectory, create it
|
||||
if ( ! $view->file_exists( $path_parts['dirname'] ) ) {
|
||||
if (
|
||||
isset( $path_parts['dirname'] )
|
||||
&& ! $view->file_exists( $path_parts['dirname'] )
|
||||
) {
|
||||
|
||||
$view->mkdir( $path_parts['dirname'] );
|
||||
|
||||
|
|
|
@ -46,7 +46,18 @@ class Proxy extends \OC_FileProxy {
|
|||
|
||||
if ( is_null( self::$enableEncryption ) ) {
|
||||
|
||||
self::$enableEncryption = ( \OCP\Config::getAppValue( 'files_encryption', 'enable_encryption', 'true' ) == 'true' && Crypt::mode() == 'server' );
|
||||
if (
|
||||
\OCP\Config::getAppValue( 'files_encryption', 'enable_encryption', 'true' ) == 'true'
|
||||
&& Crypt::mode() == 'server'
|
||||
) {
|
||||
|
||||
self::$enableEncryption = true;
|
||||
|
||||
} else {
|
||||
|
||||
self::$enableEncryption = false;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -132,6 +143,7 @@ class Proxy extends \OC_FileProxy {
|
|||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function postFile_get_contents( $path, $data ) {
|
||||
|
|
|
@ -31,9 +31,18 @@ namespace OCA\Encryption;
|
|||
|
||||
/**
|
||||
* @brief Provides 'crypt://' stream wrapper protocol.
|
||||
* @note We use a stream wrapper because it is the most secure way to handle 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, due to limitations of OC_FilesystemView. crypt:///home/user/owncloud/data <- will put keyfiles in [owncloud]/data/user/files_encryption/keyfiles/home/user/owncloud/data and will not be accessible by other functions.
|
||||
* @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 a 8192 encrypted block size.
|
||||
* @note We use a stream wrapper because it is the most secure way to handle
|
||||
* 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:
|
||||
* crypt://filename, or crypt://subdirectory/filename, NOT
|
||||
* crypt:///home/user/owncloud/data. Otherwise keyfiles will be put keyfiles in
|
||||
* [owncloud]/data/user/files_encryption/keyfiles/home/user/owncloud/data and
|
||||
* will not be accessible to other functions.
|
||||
* @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
|
||||
* a 8192 encrypted block size.
|
||||
*/
|
||||
class Stream {
|
||||
|
||||
|
@ -41,6 +50,8 @@ class Stream {
|
|||
|
||||
# TODO: make all below properties private again once unit testing is configured correctly
|
||||
public $rawPath; // The raw path received by stream_open
|
||||
public $path_f; // The raw path formatted to include username and data directory
|
||||
private $userId;
|
||||
private $handle; // Resource returned by fopen
|
||||
private $path;
|
||||
private $readBuffer; // For streams that dont support seeking
|
||||
|
@ -53,20 +64,24 @@ class Stream {
|
|||
|
||||
public function stream_open( $path, $mode, $options, &$opened_path ) {
|
||||
|
||||
file_put_contents('/home/samtuke/newtmp.txt', 'stream_open('.$path.')' );
|
||||
//file_put_contents('/home/samtuke/newtmp.txt', 'stream_open('.$path.')' );
|
||||
|
||||
// Get access to filesystem via filesystemview object
|
||||
if ( !self::$view ) {
|
||||
|
||||
self::$view = new \OC_FilesystemView( '' );
|
||||
self::$view = new \OC_FilesystemView( $this->userId . '/' );
|
||||
|
||||
}
|
||||
|
||||
$this->userId = \OCP\User::getUser();
|
||||
|
||||
// Get the bare file path
|
||||
$path = str_replace( 'crypt://', '', $path );
|
||||
|
||||
$this->rawPath = $path;
|
||||
|
||||
$this->path_f = $this->userId . '/files/' . $path;
|
||||
|
||||
if (
|
||||
dirname( $path ) == 'streams'
|
||||
and isset( self::$sourceStreams[basename( $path )] )
|
||||
|
@ -95,7 +110,7 @@ class Stream {
|
|||
|
||||
|
||||
|
||||
$this->size = self::$view->filesize( $path, $mode );
|
||||
$this->size = self::$view->filesize( $this->path_f, $mode );
|
||||
|
||||
//$this->size = filesize( $path );
|
||||
|
||||
|
@ -106,7 +121,7 @@ class Stream {
|
|||
|
||||
//$this->handle = fopen( $path, $mode );
|
||||
|
||||
$this->handle = self::$view->fopen( $path, $mode );
|
||||
$this->handle = self::$view->fopen( $this->path_f, $mode );
|
||||
|
||||
//file_put_contents('/home/samtuke/newtmp.txt', 'fucking hopeless = '.$path );
|
||||
|
||||
|
@ -165,7 +180,7 @@ class Stream {
|
|||
|
||||
//echo "\n\nPRE DECRYPTION = $data\n\n";
|
||||
//
|
||||
// if ( strlen( $data ) ) {
|
||||
if ( strlen( $data ) ) {
|
||||
|
||||
$this->getKey();
|
||||
|
||||
|
@ -187,13 +202,11 @@ class Stream {
|
|||
|
||||
//trigger_error("CAT $result");
|
||||
|
||||
} else {
|
||||
|
||||
$result = '';
|
||||
|
||||
// } else {
|
||||
//
|
||||
// $result = '';
|
||||
//
|
||||
// }
|
||||
}
|
||||
|
||||
// $length = $this->size - $pos;
|
||||
//
|
||||
|
@ -235,13 +248,10 @@ class Stream {
|
|||
*/
|
||||
public function getKey( $generate = true ) {
|
||||
|
||||
# TODO: Move this user call out of here - it belongs elsewhere
|
||||
$user = \OCP\User::getUser();
|
||||
|
||||
//echo "\n\$this->rawPath = {$this->rawPath}";
|
||||
|
||||
// If a keyfile already exists for a file named identically to file to be written
|
||||
if ( self::$view->file_exists( $user . '/'. 'files_encryption' . '/' . 'keyfiles' . '/' . $this->rawPath . '.key' ) ) {
|
||||
if ( self::$view->file_exists( $this->userId . '/'. 'files_encryption' . '/' . 'keyfiles' . '/' . $this->rawPath . '.key' ) ) {
|
||||
|
||||
# TODO: add error handling for when file exists but no keyfile
|
||||
|
||||
|
@ -276,6 +286,8 @@ class Stream {
|
|||
*/
|
||||
public function stream_write( $data ) {
|
||||
|
||||
|
||||
|
||||
// file_put_contents('/home/samtuke/newtmp.txt', 'stream_write('.$data.')' );
|
||||
|
||||
// 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
|
||||
|
|
|
@ -65,6 +65,11 @@ class Util {
|
|||
private $view; // OC_FilesystemView object for filesystem operations
|
||||
private $pwd; // User Password
|
||||
private $client; // Client side encryption mode flag
|
||||
private $publicKeyDir; // Directory containing all public user keys
|
||||
private $encryptionDir; // Directory containing user's files_encryption
|
||||
private $keyfilesPath; // Directory containing user's keyfiles
|
||||
private $publicKeyPath; // Path to user's public key
|
||||
private $privateKeyPath; // Path to user's private key
|
||||
|
||||
public function __construct( \OC_FilesystemView $view, $userId, $client = false ) {
|
||||
|
||||
|
@ -103,11 +108,6 @@ class Util {
|
|||
*/
|
||||
public function setupServerSide( $passphrase = null ) {
|
||||
|
||||
// Log changes to user's filesystem
|
||||
$this->appInfo = \OC_APP::getAppInfo( 'files_encryption' );
|
||||
|
||||
\OC_Log::write( $this->appInfo['name'], 'File encryption for user "' . $this->userId . '" will be set up' , \OC_Log::INFO );
|
||||
|
||||
// Create shared public key directory
|
||||
if( !$this->view->file_exists( $this->publicKeyDir ) ) {
|
||||
|
||||
|
@ -153,6 +153,8 @@ class Util {
|
|||
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
public function findFiles( $directory, $type = 'plain' ) {
|
||||
|
@ -358,4 +360,42 @@ class Util {
|
|||
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -34,7 +34,9 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase {
|
|||
|
||||
$this->view = new \OC_FilesystemView( '/' );
|
||||
|
||||
\OC_User::setUserId( 'admin' );
|
||||
$this->userId = 'admin';
|
||||
|
||||
\OC_User::setUserId( $this->userId );
|
||||
|
||||
}
|
||||
|
||||
|
@ -109,6 +111,29 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase {
|
|||
|
||||
}
|
||||
|
||||
function testAddPadding() {
|
||||
|
||||
$padded = Encryption\Crypt::addPadding( $this->dataLong );
|
||||
|
||||
$padding = substr( $padded, -2 );
|
||||
|
||||
$this->assertEquals( 'xx' , $padding );
|
||||
|
||||
return $padded;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testAddPadding
|
||||
*/
|
||||
function testRemovePadding( $padded ) {
|
||||
|
||||
$noPadding = Encryption\Crypt::RemovePadding( $padded );
|
||||
|
||||
$this->assertEquals( $this->dataLong, $noPadding );
|
||||
|
||||
}
|
||||
|
||||
function testEncrypt() {
|
||||
|
||||
$random = openssl_random_pseudo_bytes( 13 );
|
||||
|
@ -188,7 +213,7 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase {
|
|||
|
||||
|
||||
// Get file contents without using any wrapper to get it's actual contents on disk
|
||||
$retreivedCryptedFile = $this->view->file_get_contents( $filename );
|
||||
$retreivedCryptedFile = $this->view->file_get_contents( $this->userId . '/files/' . $filename );
|
||||
|
||||
//echo "\n\n\$retreivedCryptedFile = $retreivedCryptedFile";
|
||||
|
||||
|
@ -222,7 +247,7 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase {
|
|||
$this->assertTrue( is_int( $cryptedFile ) );
|
||||
|
||||
// Get file contents without using any wrapper to get it's actual contents on disk
|
||||
$retreivedCryptedFile = $this->view->file_get_contents( $filename );
|
||||
$retreivedCryptedFile = $this->view->file_get_contents( $this->userId . '/files/' . $filename );
|
||||
|
||||
// echo "\n\n\$retreivedCryptedFile = $retreivedCryptedFile\n\n";
|
||||
|
||||
|
@ -261,17 +286,16 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase {
|
|||
|
||||
$this->assertEquals( $this->dataLong.$this->dataLong, $decrypt );
|
||||
|
||||
// Teadown
|
||||
// Teardown
|
||||
|
||||
$this->view->unlink( $filename );
|
||||
|
||||
Keymanager::deleteFileKey( $filename );
|
||||
Encryption\Keymanager::deleteFileKey( $filename );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Test that data that is read by the crypto stream wrapper
|
||||
* @depends testSymmetricStreamEncryptLongFileContent
|
||||
*/
|
||||
function testSymmetricStreamDecryptShortFileContent() {
|
||||
|
||||
|
@ -285,7 +309,7 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase {
|
|||
|
||||
|
||||
// Get file contents without using any wrapper to get it's actual contents on disk
|
||||
$retreivedCryptedFile = $this->view->file_get_contents( $filename );
|
||||
$retreivedCryptedFile = $this->view->file_get_contents( $this->userId . '/files/' . $filename );
|
||||
|
||||
$decrypt = file_get_contents( 'crypt://' . $filename );
|
||||
|
||||
|
@ -305,7 +329,7 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase {
|
|||
|
||||
|
||||
// Get file contents without using any wrapper to get it's actual contents on disk
|
||||
$retreivedCryptedFile = $this->view->file_get_contents( '/admin/files/' . $filename );
|
||||
$retreivedCryptedFile = $this->view->file_get_contents( $this->userId . '/files/' . $filename );
|
||||
|
||||
$decrypt = file_get_contents( 'crypt://' . $filename );
|
||||
|
||||
|
|
|
@ -6,11 +6,11 @@
|
|||
* See the COPYING-README file.
|
||||
*/
|
||||
|
||||
namespace OCA\Encryption;
|
||||
|
||||
require_once "PHPUnit/Framework/TestCase.php";
|
||||
require_once realpath( dirname(__FILE__).'/../../../lib/base.php' );
|
||||
|
||||
use OCA\Encryption;
|
||||
|
||||
class Test_Keymanager extends \PHPUnit_Framework_TestCase {
|
||||
|
||||
function setUp() {
|
||||
|
@ -34,7 +34,7 @@ class Test_Keymanager extends \PHPUnit_Framework_TestCase {
|
|||
|
||||
function testGetEncryptedPrivateKey() {
|
||||
|
||||
$key = Keymanager::getPrivateKey( $this->user, $this->view );
|
||||
$key = Encryption\Keymanager::getPrivateKey( $this->user, $this->view );
|
||||
|
||||
$this->assertEquals( 2302, strlen( $key ) );
|
||||
|
||||
|
@ -52,16 +52,18 @@ class Test_Keymanager extends \PHPUnit_Framework_TestCase {
|
|||
//
|
||||
// //$view = new \OC_FilesystemView( '/' . $this->user . '/files_encryption/keyfiles' );
|
||||
//
|
||||
// Keymanager::setFileKey( $tmpPath, $key['key'], $view );
|
||||
// Encryption\Keymanager::setFileKey( $tmpPath, $key['key'], $view );
|
||||
|
||||
}
|
||||
|
||||
function testGetDecryptedPrivateKey() {
|
||||
|
||||
$key = Keymanager::getPrivateKey( $this->user, $this->view );
|
||||
$key = Encryption\Keymanager::getPrivateKey( $this->user, $this->view );
|
||||
|
||||
# TODO: replace call to Crypt with a mock object?
|
||||
$decrypted = Crypt::symmetricDecryptFileContent( $key, $this->passphrase );
|
||||
$decrypted = Encryption\Crypt::symmetricDecryptFileContent( $key, $this->passphrase );
|
||||
|
||||
var_dump($decrypted);
|
||||
|
||||
$this->assertEquals( 1708, strlen( $decrypted ) );
|
||||
|
||||
|
|
Loading…
Reference in New Issue