diff --git a/apps/files_encryption/lib/crypt.php b/apps/files_encryption/lib/crypt.php index 098074c228d88150147f08ed86c68ec6c9918c6a..668101014a2e0f8194912d103b1bfe9ecdf10cfa 100644 --- a/apps/files_encryption/lib/crypt.php +++ b/apps/files_encryption/lib/crypt.php @@ -50,6 +50,66 @@ class Crypt { } + /** + * @brief Check if a file's contents contains an IV and is symmetrically encrypted + * @return true / false + */ + public static function isEncryptedContent( $content ) { + + if ( !$content ) { + + return false; + + } + + // Fetch encryption metadata from end of file + $meta = substr( $content, -22 ); + + // Fetch IV from end of file + $iv = substr( $meta, -16 ); + + // Fetch identifier from start of metadata + $identifier = substr( $meta, 0, 6 ); + + if ( $identifier == '00iv00') { + + return true; + + } else { + + return false; + + } + + } + + /** + * @brief Check if a file is encrypted via legacy system + * @return true / false + */ + public static function isLegacyEncryptedContent( $content, $path ) { + + // Fetch all file metadata from DB + $metadata = \OC_FileCache_Cached::get( $content, '' ); + + // If a file is flagged with encryption in DB, but isn't a valid content + IV combination, it's probably using the legacy encryption system + if ( + $content + and isset( $metadata['encrypted'] ) + and $metadata['encrypted'] === true + and !self::isEncryptedContent( $content ) + ) { + + return true; + + } else { + + return false; + + } + + } + /** * @brief Symmetrically encrypt a string * @returns encrypted file @@ -106,13 +166,12 @@ class Crypt { } - $random = openssl_random_pseudo_bytes( 13 ); - - $iv = substr( base64_encode( $random ), 0, -4 ); + $iv = self::generateIv(); if ( $encryptedContent = self::encrypt( $plainContent, $iv, $passphrase ) ) { - $combinedKeyfile = $encryptedContent .= $iv; + // Combine content to encrypt with IV identifier and actual IV + $combinedKeyfile = $encryptedContent . '00iv00' . $iv; return $combinedKeyfile; @@ -143,9 +202,11 @@ class Crypt { } + // Fetch IV from end of file $iv = substr( $keyfileContent, -16 ); - $encryptedContent = substr( $keyfileContent, 0, -16 ); + // Remove IV and IV identifier text to expose encrypted content + $encryptedContent = substr( $keyfileContent, 0, -22 ); if ( $plainContent = self::decrypt( $encryptedContent, $iv, $passphrase ) ) { @@ -269,6 +330,33 @@ class Crypt { } + /** + * @brief Generate a pseudo random 1024kb ASCII key + * @returns $key Generated key + */ + public static function generateIv() { + + if ( $random = openssl_random_pseudo_bytes( 13, $strong ) ) { + + if ( !$strong ) { + + // If OpenSSL indicates randomness is insecure, log error + \OC_Log::write( 'Encryption library', 'Insecure symmetric key was generated using openssl_random_pseudo_bytes()' , \OC_Log::WARN ); + + } + + $iv = substr( base64_encode( $random ), 0, -4 ); + + return $iv; + + } else { + + return false; + + } + + } + /** * @brief Generate a pseudo random 1024kb ASCII key * @returns $key Generated key diff --git a/apps/files_encryption/lib/util.php b/apps/files_encryption/lib/util.php index 62b435583e30b7f45eec9a1da940ffca11139633..5185ad351d68f69182fc016195ee23263c2414eb 100644 --- a/apps/files_encryption/lib/util.php +++ b/apps/files_encryption/lib/util.php @@ -37,6 +37,23 @@ namespace OCA_Encryption; class Util { + # DONE: add method to check if file is encrypted using new system + # DONE: add method to check if file is encrypted using old system + # TODO: add method to encrypt all user files using new system + # TODO: add method to decrypt all user files using new system + # TODO: add method to encrypt all user files using old system + # TODO: add method to decrypt all user files using old system + # TODO: fix / test the crypt stream proxy class + # TODO: add support for optional recovery user in case of lost passphrase / keys + # TODO: add admin optional required long passphrase for users + # TODO: implement flag system to allow user to specify encryption by folder, subfolder, etc. + # TODO: add UI buttons for encrypt / decrypt everything? + + # TODO: test new encryption with webdav + # TODO: test new encryption with versioning + # TODO: test new encryption with sharing + # TODO: test new encryption with proxies + private $view; // OC_FilesystemView object for filesystem operations private $pwd; // User Password private $client; // Client side encryption mode flag @@ -73,6 +90,10 @@ class Util { } + /** + * @brief Sets up encryption folders and keys for a user + * @param $passphrase passphrase to encrypt server-stored private key with + */ public function setup( $passphrase = null ) { $publicKeyFileName = 'encryption.public.key'; @@ -129,5 +150,102 @@ class Util { } } + + /** + * @brief Fetch the legacy encryption key from user files + * @param string $login used to locate the legacy key + * @param string $passphrase used to decrypt the legacy key + * @return true / false + * + * if the key is left out, the default handeler will be used + */ + public function getLegacyKey( $login, $passphrase ) { + + OC_FileProxy::$enabled = false; + + if ( + $login + and $passphrase + and $key = $this->view->file_get_contents( '/' . $login . '/encryption.key' ) + ) { + + OC_FileProxy::$enabled = true; + + return $this->legacyDecrypt( $key, $passphrase ); + + } else { + + OC_FileProxy::$enabled = true; + + return false; + + } + + } + + /** + * @brief Get the blowfish encryption handeler for a key + * @param $key string (optional) + * @return Crypt_Blowfish blowfish object + * + * if the key is left out, the default handeler will be used + */ + public function getBlowfish( $key = '' ) { + + if( $key ){ + + return new Crypt_Blowfish($key); + + } else { + + return false; + + } + + } + + /** + * @brief encrypts content using legacy blowfish system + * @param $content the cleartext message you want to encrypt + * @param $key the encryption key (optional) + * @returns encrypted content + * + * This function encrypts an content + */ + public static function legacyEncrypt( $content, $key='') { + $bf = self::getBlowfish($key); + return $bf->encrypt($content); + } + + /** + * @brief decryption of an content + * @param $content the cleartext message you want to decrypt + * @param $key the encryption key (optional) + * @returns cleartext content + * + * This function decrypts an content + */ + public static function legacyDecrypt( $content, $key = '' ) { + + $bf = $this->getBlowfish( $key ); + + $data = $bf->decrypt( $content ); + + return $data; + + } + + /** + * @brief Re-encryptes a legacy blowfish encrypted file using AES with integrated IV + * @param $legacyContent the legacy encrypted content to re-encrypt + * @returns cleartext content + * + * This function decrypts an content + */ + public function legacyRecrypt( $legacyContent ) { + + # TODO: write me + + } } diff --git a/apps/files_encryption/tests/encryption.php b/apps/files_encryption/tests/encryption.php index 600e00fd3e4dc82dd06ee8c847ca9858253c4e95..9246e7152622d56c6b19f5d3cdbfc837472ad5f7 100644 --- a/apps/files_encryption/tests/encryption.php +++ b/apps/files_encryption/tests/encryption.php @@ -1,19 +1,22 @@ <?php /** - * Copyright (c) 2012 Robin Appelman <icewind@owncloud.com> + * Copyright (c) 2012 Sam Tuke <samtuke@owncloud.com>, and + * Robin Appelman <icewind@owncloud.com> * This file is licensed under the Affero General Public License version 3 or * later. * See the COPYING-README file. */ require realpath( dirname(__FILE__).'/../lib/crypt.php' ); +//require realpath( dirname(__FILE__).'/../../../lib/filecache.php' ); class Test_Encryption extends UnitTestCase { - + function setUp() { // set content for encrypting / decrypting in tests $this->data = realpath( dirname(__FILE__).'/../lib/crypt.php' ); + $this->legacyEncryptedData = realpath( dirname(__FILE__).'/legacy-encrypted-text.txt' ); } @@ -25,10 +28,22 @@ class Test_Encryption extends UnitTestCase { $key = OCA_Encryption\Crypt::generateKey(); + $this->assertTrue( $key ); + $this->assertTrue( strlen( $key ) > 1000 ); } + function testGenerateIv() { + + $iv = OCA_Encryption\Crypt::generateIv(); + + $this->assertTrue( $iv ); + + $this->assertTrue( strlen( $iv ) == 16 ); + + } + function testEncrypt() { $random = openssl_random_pseudo_bytes( 13 ); @@ -85,6 +100,31 @@ class Test_Encryption extends UnitTestCase { } + function testIsEncryptedContent() { + + $this->assertFalse( OCA_Encryption\Crypt::isEncryptedContent( $this->data ) ); + + $this->assertFalse( OCA_Encryption\Crypt::isEncryptedContent( $this->legacyEncryptedData ) ); + + $keyfileContent = OCA_Encryption\Crypt::symmetricEncryptFileContent( $this->data, 'hat' ); + + $this->assertTrue( OCA_Encryption\Crypt::isEncryptedContent( $keyfileContent ) ); + + } + +// // Cannot use this test for now due to hidden dependencies in OC_FileCache +// function testIsLegacyEncryptedContent() { +// +// $keyfileContent = OCA_Encryption\Crypt::symmetricEncryptFileContent( $this->legacyEncryptedData, 'hat' ); +// +// $this->assertFalse( OCA_Encryption\Crypt::isLegacyEncryptedContent( $keyfileContent, '/files/admin/test.txt' ) ); +// +// OC_FileCache::put( '/admin/files/legacy-encrypted-test.txt', $this->legacyEncryptedData ); +// +// $this->assertTrue( OCA_Encryption\Crypt::isLegacyEncryptedContent( $this->legacyEncryptedData, '/files/admin/test.txt' ) ); +// +// } + function testMultiKeyEncrypt() { # TODO: search in keyfile for actual content as IV will ensure this test always passes diff --git a/apps/files_encryption/tests/legacy-encrypted-text.txt b/apps/files_encryption/tests/legacy-encrypted-text.txt new file mode 100644 index 0000000000000000000000000000000000000000..cb5bf50550d91842c8a0bd214edf9569daeadc48 Binary files /dev/null and b/apps/files_encryption/tests/legacy-encrypted-text.txt differ diff --git a/lib/filecache.php b/lib/filecache.php index d956f34dc48790be0611be8729751c0102786922..8894c8a3ebb2a26a67584f8da29180ad1e28fe5e 100644 --- a/lib/filecache.php +++ b/lib/filecache.php @@ -23,10 +23,16 @@ * provide caching for filesystem info in the database * * not used by OC_Filesystem for reading filesystem info, - * instread apps should use OC_FileCache::get where possible + * instead apps should use OC_FileCache::get where possible + * + * It will try to keep the data up to date but changes from outside + * ownCloud can invalidate the cache + * + * Methods that take $path and $root params expect $path to be relative, like + * /admin/files/file.txt, if $root is false * - * It will try to keep the data up to date but changes from outside ownCloud can invalidate the cache */ + class OC_FileCache{ /** * get the filesystem info from the cache