Development snapshot, lots of fixes

Web UI based encryption working
Crypt and Util unit tests passing
This commit is contained in:
Sam Tuke 2012-11-16 18:31:37 +00:00
parent 6df35eeddc
commit 637891b771
8 changed files with 176 additions and 76 deletions

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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'] );

View File

@ -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 ) {

View File

@ -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

View File

@ -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;
}
}
}

View File

@ -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 );

View File

@ -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 ) );