From 92162898567c5b19e0119b63484e3beb228c5490 Mon Sep 17 00:00:00 2001
From: Sam Tuke <samtuke@owncloud.com>
Date: Tue, 24 Jul 2012 17:53:12 +0100
Subject: [PATCH] Wrote new methods for testing if a file is encrypted using
 AES or Blowfish Added more unit tests for crypt class Added new method for
 generating 16 character pseudo-random initialisation vectors Started writing
 new methods for handling legacy keys and en/de/re cryption Added comments to
 lib/filecache.php explaining expected $path type

---
 apps/files_encryption/lib/crypt.php           |  98 ++++++++++++++-
 apps/files_encryption/lib/util.php            | 118 ++++++++++++++++++
 apps/files_encryption/tests/encryption.php    |  44 ++++++-
 .../tests/legacy-encrypted-text.txt           | Bin 0 -> 3360 bytes
 lib/filecache.php                             |  10 +-
 5 files changed, 261 insertions(+), 9 deletions(-)
 create mode 100644 apps/files_encryption/tests/legacy-encrypted-text.txt

diff --git a/apps/files_encryption/lib/crypt.php b/apps/files_encryption/lib/crypt.php
index 098074c228..668101014a 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 62b435583e..5185ad351d 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 600e00fd3e..9246e71526 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
GIT binary patch
literal 3360
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

literal 0
HcmV?d00001

diff --git a/lib/filecache.php b/lib/filecache.php
index d956f34dc4..8894c8a3eb 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
-- 
GitLab