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