From 9bab06537c8d455c1a93b167193ec7cdebe89ffe Mon Sep 17 00:00:00 2001 From: Bjoern Schiessle Date: Tue, 31 Jul 2012 15:03:28 +0200 Subject: update file encryption key over webdav properties for client side encryption --- apps/files_encryption/appinfo/app.php | 1 + apps/files_encryption/hooks/hooks.php | 12 ++++++++++++ lib/connector/sabre/node.php | 30 +++++++++++++++++++++++++++++- lib/ocs.php | 4 +--- 4 files changed, 43 insertions(+), 4 deletions(-) diff --git a/apps/files_encryption/appinfo/app.php b/apps/files_encryption/appinfo/app.php index 1a4021e9395..2047bdbb1fb 100644 --- a/apps/files_encryption/appinfo/app.php +++ b/apps/files_encryption/appinfo/app.php @@ -10,6 +10,7 @@ OC::$CLASSPATH['OCA_Encryption\Proxy'] = 'apps/files_encryption/lib/proxy.php'; OC_FileProxy::register(new OCA_Encryption\Proxy()); OCP\Util::connectHook('OC_User','post_login','OCA_Encryption\Hooks','login'); +OCP\Util::connectHook('OC_Webdav_Properties', 'update', 'OCA_Encryption\Hooks', 'updateKeyfile'); stream_wrapper_register('crypt','OC_CryptStream'); diff --git a/apps/files_encryption/hooks/hooks.php b/apps/files_encryption/hooks/hooks.php index 80daf50a24d..35e14e28106 100644 --- a/apps/files_encryption/hooks/hooks.php +++ b/apps/files_encryption/hooks/hooks.php @@ -58,6 +58,18 @@ class Hooks { } + + /** + * @brief update the encryption key of the file uploaded by the client + */ + public static function updateKeyfile( $params ) { + if (Crypt::mode(\OCP\User::getUser()) == 'client') + if (isset($params['properties']['key'])) { + Keymanager::setFileKey(\OCP\User::getUser(), $params['path'], $params['properties']['key']); + } else { + error_log("Client side encryption is enabled but the client doesn't provide a encryption key for the file!"); + } + } } ?> \ No newline at end of file diff --git a/lib/connector/sabre/node.php b/lib/connector/sabre/node.php index be315a0ffd9..90f88566a4a 100644 --- a/lib/connector/sabre/node.php +++ b/lib/connector/sabre/node.php @@ -22,6 +22,7 @@ */ abstract class OC_Connector_Sabre_Node implements Sabre_DAV_INode, Sabre_DAV_IProperties { + const GETETAG_PROPERTYNAME = '{DAV:}getetag'; /** * The path to the current node @@ -140,7 +141,9 @@ abstract class OC_Connector_Sabre_Node implements Sabre_DAV_INode, Sabre_DAV_IPr */ public function updateProperties($properties) { $existing = $this->getProperties(array()); + OC_Hook::emit('OC_Webdav_Properties', 'update', array('properties' => $properties, 'path' => $this->path)); foreach($properties as $propertyName => $propertyValue) { + $propertyName = preg_replace("/^{.*}/", "", $propertyName); // remove leading namespace from property name // If it was null, we need to delete the property if (is_null($propertyValue)) { if(array_key_exists( $propertyName, $existing )){ @@ -178,7 +181,7 @@ abstract class OC_Connector_Sabre_Node implements Sabre_DAV_INode, Sabre_DAV_IPr * @param array $properties * @return void */ - function getProperties($properties) { + public function getProperties($properties) { if (is_null($this->property_cache)) { $query = OC_DB::prepare( 'SELECT * FROM *PREFIX*properties WHERE userid = ? AND propertypath = ?' ); $result = $query->execute( array( OC_User::getUser(), $this->path )); @@ -200,4 +203,29 @@ abstract class OC_Connector_Sabre_Node implements Sabre_DAV_INode, Sabre_DAV_IPr } return $props; } + + /** + * Returns the ETag surrounded by double-quotes for this path. + * @param string $path Path of the file + * @return string|null Returns null if the ETag can not effectively be determined + */ + static public function getETagPropertyForFile($path) { + $tag = OC_Filesystem::hash('md5', $path); + if (empty($tag)) { + return null; + } + $etag = '"'.$tag.'"'; + $query = OC_DB::prepare( 'INSERT INTO *PREFIX*properties (userid,propertypath,propertyname,propertyvalue) VALUES(?,?,?,?)' ); + $query->execute( array( OC_User::getUser(), $path, self::GETETAG_PROPERTYNAME, $etag )); + return $etag; + } + + /** + * Remove the ETag from the cache. + * @param string $path Path of the file + */ + static public function removeETagPropertyForFile($path) { + $query = OC_DB::prepare( 'DELETE FROM *PREFIX*properties WHERE userid = ? AND propertypath = ? AND propertyname = ?' ); + $query->execute( array( OC_User::getUser(), $path, self::GETETAG_PROPERTYNAME )); + } } diff --git a/lib/ocs.php b/lib/ocs.php index cf4248395f3..17ae649deb6 100644 --- a/lib/ocs.php +++ b/lib/ocs.php @@ -808,8 +808,7 @@ class OC_OCS { $login=OC_OCS::checkpassword(); if(($login==$user)) { if(OC_App::isEnabled('files_encryption') && OCA_Encryption\Crypt::mode($user) === 'client') { - if (($key = OCA_Encryption\Keymanager::setFileKey($user, $file, $key))) { - // TODO: emit hook to move file from tmp location to the right place + if (($key = OCA_Encryption\Keymanager::setFileKey($user, $file, $key))) { echo self::generateXml('', 'ok', 100, ''); return true; } else { @@ -821,7 +820,6 @@ class OC_OCS { }else{ echo self::generateXml('', 'fail', 300, 'You don“t have permission to access this ressource.'); } - //TODO: emit signal to remove file from tmp location return false; } -- cgit v1.2.3 From e4e6574e425b666170ac56238a3bb6c84a2d50e2 Mon Sep 17 00:00:00 2001 From: Bjoern Schiessle Date: Tue, 31 Jul 2012 16:37:37 +0200 Subject: allow admin to choose between client and server side encryption --- apps/files_encryption/js/settings.js | 11 +++++++++++ apps/files_encryption/templates/settings.php | 11 ++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/apps/files_encryption/js/settings.js b/apps/files_encryption/js/settings.js index 8cc433246cb..49dcf2bfca3 100644 --- a/apps/files_encryption/js/settings.js +++ b/apps/files_encryption/js/settings.js @@ -21,4 +21,15 @@ $(document).ready(function(){ var checked=$('#enable_encryption').is(':checked'); OC.AppConfig.setValue('files_encryption','enable_encryption',(checked)?'true':'false'); }) + $('input[name=encryption_mode]').change(function(){ + var client=$('input[value="client"]:checked').val() + ,server=$('input[value="server"]:checked').val() + ,none=$('input[value="none"]:checked').val() + if (client) + OC.AppConfig.setValue('files_encryption','mode','client'); + if (server) + OC.AppConfig.setValue('files_encryption','mode','server'); + if (none) + OC.AppConfig.setValue('files_encryption','mode','none'); + }) }) \ No newline at end of file diff --git a/apps/files_encryption/templates/settings.php b/apps/files_encryption/templates/settings.php index 79780d694cf..80b3da84caa 100644 --- a/apps/files_encryption/templates/settings.php +++ b/apps/files_encryption/templates/settings.php @@ -1,5 +1,14 @@
+ + Choose encryption mode: + +

+ Client side encryption (most secure but makes it impossible to access your data from the web interface)
+ Server side encryption (allows you to access your files from the web interface and the desktop client)
+ None (no encryption at all)
+

+

t('Encryption'); ?> t("Exclude the following file types from encryption"); ?> - > +

-- cgit v1.2.3 From f6863f9e51ca1523e43c92a1ecbfe6f70c090494 Mon Sep 17 00:00:00 2001 From: Bjoern Schiessle Date: Tue, 31 Jul 2012 16:52:21 +0200 Subject: get encryption mode from the settings --- apps/files_encryption/lib/crypt.php | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/apps/files_encryption/lib/crypt.php b/apps/files_encryption/lib/crypt.php index 090b1db0611..fdace3e61dd 100644 --- a/apps/files_encryption/lib/crypt.php +++ b/apps/files_encryption/lib/crypt.php @@ -32,13 +32,19 @@ class Crypt { /** * @brief return encryption mode client or server side encryption - * @param string user name + * @param string user name (use system wide setting if name=null) * @return string 'client' or 'server' */ - public static function mode($user) { - //TODO: allow user to set encryption mode and check the selection of the user - // for the moment I just return 'client' for test purposes - return 'client'; + public static function mode($user=null) { + + $mode = \OC_Appconfig::getValue('files_encryption', 'mode', 'unknown'); + + if ($mode == 'unknown') { + error_log('no encryption mode configured'); + return false; + } + + return $mode; } /** -- cgit v1.2.3 From 84fd62b13047cb756d9f39c192e17fd5f2179f83 Mon Sep 17 00:00:00 2001 From: Sam Tuke Date: Tue, 31 Jul 2012 19:35:36 +0100 Subject: Implemented writing of keyfiles and directory hierarchy in proxy class Added crypt::findFiles() method for finding different types of files, ready for batch encrypting / decrypting Added comments to postFopen in proxy class --- apps/files_encryption/hooks/hooks.php | 4 +- apps/files_encryption/lib/crypt.php | 843 +++++++++++++++--------------- apps/files_encryption/lib/cryptstream.php | 83 ++- apps/files_encryption/lib/keymanager.php | 26 +- apps/files_encryption/lib/proxy.php | 86 ++- apps/files_encryption/lib/util.php | 96 +++- 6 files changed, 653 insertions(+), 485 deletions(-) diff --git a/apps/files_encryption/hooks/hooks.php b/apps/files_encryption/hooks/hooks.php index 35e14e28106..d06e9a0d2d3 100644 --- a/apps/files_encryption/hooks/hooks.php +++ b/apps/files_encryption/hooks/hooks.php @@ -37,14 +37,14 @@ class Hooks { public static function login( $params ) { - if (Crypt::mode($params['uid'])=='server') { + if ( Crypt::mode( $params['uid'] ) == 'server' ) { $view = new \OC_FilesystemView( '/' ); $util = new Util( $view, $params['uid'] ); if ( !$util->ready()) { - + return $util->setupServerSide( $params['password'] ); } diff --git a/apps/files_encryption/lib/crypt.php b/apps/files_encryption/lib/crypt.php index fdace3e61dd..7e50c900fa1 100644 --- a/apps/files_encryption/lib/crypt.php +++ b/apps/files_encryption/lib/crypt.php @@ -1,426 +1,429 @@ -. - * - */ - -namespace OCA_Encryption; - -/** - * Class for common cryptography functionality - */ - -class Crypt { - +. + * + */ + +namespace OCA_Encryption; + +/** + * Class for common cryptography functionality + */ + +class Crypt { + /** - * @brief return encryption mode client or server side encryption + * @brief return encryption mode client or server side encryption * @param string user name (use system wide setting if name=null) * @return string 'client' or 'server' */ - public static function mode($user=null) { - - $mode = \OC_Appconfig::getValue('files_encryption', 'mode', 'unknown'); - - if ($mode == 'unknown') { - error_log('no encryption mode configured'); - return false; - } - + public static function mode( $user = null ) { + + $mode = \OC_Appconfig::getValue( 'files_encryption', 'mode', 'unknown' ); + + if ( $mode == 'unknown' ) { + + error_log('no encryption mode configured'); + + return false; + + } + return $mode; - } - - /** - * @brief Create a new encryption keypair - * @return array publicKey, privatekey - */ - public static function createKeypair() { - - $res = openssl_pkey_new(); - - // Get private key - openssl_pkey_export( $res, $privateKey ); - - // Get public key - $publicKey = openssl_pkey_get_details( $res ); - - $publicKey = $publicKey['key']; - - return( array( 'publicKey' => $publicKey, 'privateKey' => $privateKey ) ); - - } - - /** - * @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 - */ - public static function encrypt( $plainContent, $iv, $passphrase = '' ) { - - if ( $encryptedContent = openssl_encrypt( $plainContent, 'AES-128-CFB', $passphrase, false, $iv ) ) { - - return $encryptedContent; - - } else { - - \OC_Log::write( 'Encryption library', 'Encryption (symmetric) of content failed' , \OC_Log::ERROR ); - - return false; - - } - - } - - /** - * @brief Symmetrically decrypt a string - * @returns decrypted file - */ - public static function decrypt( $encryptedContent, $iv, $passphrase ) { - - if ( $plainContent = openssl_decrypt( $encryptedContent, 'AES-128-CFB', $passphrase, false, $iv ) ) { - - return $plainContent; - - - } else { - - \OC_Log::write( 'Encryption library', 'Decryption (symmetric) of content failed' , \OC_Log::ERROR ); - - return false; - - } - - } - - /** - * @brief Symmetrically encrypts a string and returns keyfile content - * @param $plainContent content to be encrypted in keyfile - * @returns encrypted content combined with IV - * @note IV need not be specified, as it will be stored in the returned keyfile - * and remain accessible therein. - */ - public static function symmetricEncryptFileContent( $plainContent, $passphrase = '' ) { - - if ( !$plainContent ) { - - return false; - - } - - $iv = self::generateIv(); - - if ( $encryptedContent = self::encrypt( $plainContent, $iv, $passphrase ) ) { - - // Combine content to encrypt with IV identifier and actual IV - $combinedKeyfile = $encryptedContent . '00iv00' . $iv; - - return $combinedKeyfile; - - } else { - - \OC_Log::write( 'Encryption library', 'Encryption (symmetric) of keyfile content failed' , \OC_Log::ERROR ); - - return false; - - } - - } - - - /** - * @brief Symmetrically decrypts keyfile content - * @param string $source - * @param string $target - * @param string $key the decryption key - * - * This function decrypts a file - */ - public static function symmetricDecryptFileContent( $keyfileContent, $passphrase = '' ) { - - if ( !$keyfileContent ) { - - return false; - - } - - // Fetch IV from end of file - $iv = substr( $keyfileContent, -16 ); - - // Remove IV and IV identifier text to expose encrypted content - $encryptedContent = substr( $keyfileContent, 0, -22 ); - - 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; - - } - - } - - /** - * @brief Creates symmetric keyfile content using a generated key - * @param string $plainContent content to be encrypted - * @returns array keys: key, encrypted - * @note symmetricDecryptFileContent() can be used to decrypt files created using this method - * - * This function decrypts a file - */ - public static function symmetricEncryptFileContentKeyfile( $plainContent ) { - - $key = self::generateKey(); - - if( $encryptedContent = self::symmetricEncryptFileContent( $plainContent, $key ) ) { - - return array( - 'key' => $key - , 'encrypted' => $encryptedContent - ); - - } else { - - return false; - - } - - } - - /** - * @brief Create asymmetrically encrypted keyfile content using a generated key - * @param string $plainContent content to be encrypted - * @returns array keys: key, encrypted - * @note symmetricDecryptFileContent() can be used to decrypt files created using this method - * - * This function decrypts a file - */ - public static function multiKeyEncrypt( $plainContent, array $publicKeys ) { - - $envKeys = array(); - - if( openssl_seal( $plainContent, $sealed, $envKeys, $publicKeys ) ) { - - return array( - 'keys' => $envKeys - , 'encrypted' => $sealed - ); - - } else { - - return false; - - } - - } - - /** - * @brief Asymmetrically encrypt a file using multiple public keys - * @param string $plainContent content to be encrypted - * @returns array keys: key, encrypted - * @note symmetricDecryptFileContent() can be used to decrypt files created using this method - * - * This function decrypts a file - */ - public static function multiKeyDecrypt( $encryptedContent, $envKey, $privateKey ) { - - if ( !$encryptedContent ) { - - return false; - - } - - if ( openssl_open( $encryptedContent, $plainContent, $envKey, $privateKey ) ) { - - return $plainContent; - - } else { - - \OC_Log::write( 'Encryption library', 'Decryption (asymmetric) of sealed content failed' , \OC_Log::ERROR ); - - return false; - - } - - } - - /** - * @brief Asymetrically encrypt a string using a public key - * @returns encrypted file - */ - public static function keyEncrypt( $plainContent, $publicKey ) { - - openssl_public_encrypt( $plainContent, $encryptedContent, $publicKey ); - - return $encryptedContent; - - } - - /** - * @brief Asymetrically decrypt a file using a private key - * @returns decrypted file - */ - public static function keyDecrypt( $encryptedContent, $privatekey ) { - - openssl_private_decrypt( $encryptedContent, $plainContent, $privatekey ); - - return $plainContent; - - } - - /** - * @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 - */ - public static function generateKey() { - - // $key = mt_rand( 10000, 99999 ) . mt_rand( 10000, 99999 ) . mt_rand( 10000, 99999 ) . mt_rand( 10000, 99999 ); - - // Generate key - if ( $key = base64_encode( openssl_random_pseudo_bytes( 768000, $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 ); - - } - - return $key; - - } else { - - return false; - - } - - } - - public static function changekeypasscode($oldPassword, $newPassword) { - if(OCP\User::isLoggedIn()){ - $username=OCP\USER::getUser(); - $view=new OC_FilesystemView('/'.$username); - - // read old key - $key=$view->file_get_contents('/encryption.key'); - - // decrypt key with old passcode - $key=OC_Crypt::decrypt($key, $oldPassword); - - // encrypt again with new passcode - $key=OC_Crypt::encrypt($key, $newPassword); - - // store the new key - $view->file_put_contents('/encryption.key', $key ); - } - } - -} - + } + + /** + * @brief Create a new encryption keypair + * @return array publicKey, privatekey + */ + public static function createKeypair() { + + $res = openssl_pkey_new(); + + // Get private key + openssl_pkey_export( $res, $privateKey ); + + // Get public key + $publicKey = openssl_pkey_get_details( $res ); + + $publicKey = $publicKey['key']; + + return( array( 'publicKey' => $publicKey, 'privateKey' => $privateKey ) ); + + } + + /** + * @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 + */ + public static function encrypt( $plainContent, $iv, $passphrase = '' ) { + + if ( $encryptedContent = openssl_encrypt( $plainContent, 'AES-128-CFB', $passphrase, false, $iv ) ) { + + return $encryptedContent; + + } else { + + \OC_Log::write( 'Encryption library', 'Encryption (symmetric) of content failed' , \OC_Log::ERROR ); + + return false; + + } + + } + + /** + * @brief Symmetrically decrypt a string + * @returns decrypted file + */ + public static function decrypt( $encryptedContent, $iv, $passphrase ) { + + if ( $plainContent = openssl_decrypt( $encryptedContent, 'AES-128-CFB', $passphrase, false, $iv ) ) { + + return $plainContent; + + + } else { + + \OC_Log::write( 'Encryption library', 'Decryption (symmetric) of content failed' , \OC_Log::ERROR ); + + return false; + + } + + } + + /** + * @brief Symmetrically encrypts a string and returns keyfile content + * @param $plainContent content to be encrypted in keyfile + * @returns encrypted content combined with IV + * @note IV need not be specified, as it will be stored in the returned keyfile + * and remain accessible therein. + */ + public static function symmetricEncryptFileContent( $plainContent, $passphrase = '' ) { + + if ( !$plainContent ) { + + return false; + + } + + $iv = self::generateIv(); + + if ( $encryptedContent = self::encrypt( $plainContent, $iv, $passphrase ) ) { + + // Combine content to encrypt with IV identifier and actual IV + $combinedKeyfile = $encryptedContent . '00iv00' . $iv; + + return $combinedKeyfile; + + } else { + + \OC_Log::write( 'Encryption library', 'Encryption (symmetric) of keyfile content failed' , \OC_Log::ERROR ); + + return false; + + } + + } + + + /** + * @brief Symmetrically decrypts keyfile content + * @param string $source + * @param string $target + * @param string $key the decryption key + * + * This function decrypts a file + */ + public static function symmetricDecryptFileContent( $keyfileContent, $passphrase = '' ) { + + if ( !$keyfileContent ) { + + return false; + + } + + // Fetch IV from end of file + $iv = substr( $keyfileContent, -16 ); + + // Remove IV and IV identifier text to expose encrypted content + $encryptedContent = substr( $keyfileContent, 0, -22 ); + + 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; + + } + + } + + /** + * @brief Creates symmetric keyfile content using a generated key + * @param string $plainContent content to be encrypted + * @returns array keys: key, encrypted + * @note symmetricDecryptFileContent() can be used to decrypt files created using this method + * + * This function decrypts a file + */ + public static function symmetricEncryptFileContentKeyfile( $plainContent ) { + + $key = self::generateKey(); + + if( $encryptedContent = self::symmetricEncryptFileContent( $plainContent, $key ) ) { + + return array( + 'key' => $key + , 'encrypted' => $encryptedContent + ); + + } else { + + return false; + + } + + } + + /** + * @brief Create asymmetrically encrypted keyfile content using a generated key + * @param string $plainContent content to be encrypted + * @returns array keys: key, encrypted + * @note symmetricDecryptFileContent() can be used to decrypt files created using this method + * + * This function decrypts a file + */ + public static function multiKeyEncrypt( $plainContent, array $publicKeys ) { + + $envKeys = array(); + + if( openssl_seal( $plainContent, $sealed, $envKeys, $publicKeys ) ) { + + return array( + 'keys' => $envKeys + , 'encrypted' => $sealed + ); + + } else { + + return false; + + } + + } + + /** + * @brief Asymmetrically encrypt a file using multiple public keys + * @param string $plainContent content to be encrypted + * @returns array keys: key, encrypted + * @note symmetricDecryptFileContent() can be used to decrypt files created using this method + * + * This function decrypts a file + */ + public static function multiKeyDecrypt( $encryptedContent, $envKey, $privateKey ) { + + if ( !$encryptedContent ) { + + return false; + + } + + if ( openssl_open( $encryptedContent, $plainContent, $envKey, $privateKey ) ) { + + return $plainContent; + + } else { + + \OC_Log::write( 'Encryption library', 'Decryption (asymmetric) of sealed content failed' , \OC_Log::ERROR ); + + return false; + + } + + } + + /** + * @brief Asymetrically encrypt a string using a public key + * @returns encrypted file + */ + public static function keyEncrypt( $plainContent, $publicKey ) { + + openssl_public_encrypt( $plainContent, $encryptedContent, $publicKey ); + + return $encryptedContent; + + } + + /** + * @brief Asymetrically decrypt a file using a private key + * @returns decrypted file + */ + public static function keyDecrypt( $encryptedContent, $privatekey ) { + + openssl_private_decrypt( $encryptedContent, $plainContent, $privatekey ); + + return $plainContent; + + } + + /** + * @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 + */ + public static function generateKey() { + + // $key = mt_rand( 10000, 99999 ) . mt_rand( 10000, 99999 ) . mt_rand( 10000, 99999 ) . mt_rand( 10000, 99999 ); + + // Generate key + if ( $key = base64_encode( openssl_random_pseudo_bytes( 768000, $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 ); + + } + + return $key; + + } else { + + return false; + + } + + } + + public static function changekeypasscode($oldPassword, $newPassword) { + if(OCP\User::isLoggedIn()){ + $username=OCP\USER::getUser(); + $view=new OC_FilesystemView('/'.$username); + + // read old key + $key=$view->file_get_contents('/encryption.key'); + + // decrypt key with old passcode + $key=OC_Crypt::decrypt($key, $oldPassword); + + // encrypt again with new passcode + $key=OC_Crypt::encrypt($key, $newPassword); + + // store the new key + $view->file_put_contents('/encryption.key', $key ); + } + } + +} + ?> \ No newline at end of file diff --git a/apps/files_encryption/lib/cryptstream.php b/apps/files_encryption/lib/cryptstream.php index e0020537563..8c61c933cf8 100644 --- a/apps/files_encryption/lib/cryptstream.php +++ b/apps/files_encryption/lib/cryptstream.php @@ -28,11 +28,11 @@ */ class OC_CryptStream{ - public static $sourceStreams=array(); + public static $sourceStreams = array(); private $source; private $path; - private $readBuffer;//for streams that dont support seeking - private $meta=array();//header/meta for source stream + private $readBuffer; // For streams that dont support seeking + private $meta = array(); // Header / meta for source stream private $count; private $writeCache; private $size; @@ -98,38 +98,69 @@ class OC_CryptStream{ return $result; } - public function stream_write($data){ - $length=strlen($data); - $written=0; - $currentPos=ftell($this->source); - if($this->writeCache){ - $data=$this->writeCache.$data; - $this->writeCache=''; + public function stream_write( $data ){ + + $length = strlen( $data ); + + $written = 0; + + $currentPos = ftell( $this->source ); + + if( $this->writeCache ){ + + $data = $this->writeCache.$data; + + $this->writeCache = ''; + } - if($currentPos%8192!=0){ + + if( $currentPos%8192 != 0 ){ + //make sure we always start on a block start - fseek($this->source,-($currentPos%8192),SEEK_CUR); - $encryptedBlock=fread($this->source,8192); - fseek($this->source,-($currentPos%8192),SEEK_CUR); - $block=OC_Crypt::decrypt($encryptedBlock); - $data=substr($block,0,$currentPos%8192).$data; - fseek($this->source,-($currentPos%8192),SEEK_CUR); + + fseek( $this->source,-( $currentPos%8192 ),SEEK_CUR ); + + $encryptedBlock = fread( $this->source,8192 ); + + fseek( $this->source,-( $currentPos%8192 ),SEEK_CUR ); + + $block = OC_Crypt::decrypt( $encryptedBlock ); + + $data = substr( $block,0,$currentPos%8192 ).$data; + + fseek( $this->source,-( $currentPos%8192 ),SEEK_CUR ); + } - $currentPos=ftell($this->source); - while($remainingLength=strlen($data)>0){ - if($remainingLength<8192){ - $this->writeCache=$data; - $data=''; + + $currentPos = ftell( $this->source ); + + while( $remainingLength = strlen( $data )>0 ){ + + if( $remainingLength<8192 ){ + + $this->writeCache = $data; + + $data = ''; + }else{ - $encrypted=OC_Crypt::encrypt(substr($data,0,8192)); - fwrite($this->source,$encrypted); - $data=substr($data,8192); + + $encrypted = OC_Crypt::encrypt( substr( $data,0,8192 ) ); + + fwrite( $this->source,$encrypted ); + + $data = substr( $data,8192 ); + } + } - $this->size=max($this->size,$currentPos+$length); + + $this->size = max( $this->size,$currentPos+$length ); + return $length; + } + public function stream_set_option($option,$arg1,$arg2){ switch($option){ case STREAM_OPTION_BLOCKING: diff --git a/apps/files_encryption/lib/keymanager.php b/apps/files_encryption/lib/keymanager.php index bafe8f1a5f0..0c76bf27a52 100644 --- a/apps/files_encryption/lib/keymanager.php +++ b/apps/files_encryption/lib/keymanager.php @@ -27,7 +27,7 @@ namespace OCA_Encryption; */ class Keymanager { - # TODO: Try and get rid of username dependencies as these methods need to be used in a proxy class that doesn't have username access + # TODO: make all dependencies explicit, such as ocfsview objects, by adding them as method arguments (dependency injection) /** * @brief retrieve private key from a user @@ -60,9 +60,9 @@ class Keymanager { * @param string user name of the file owner * @return string file key or false */ - public static function getFileKey($userId, $path) { + public static function getFileKey( $userId, $path ) { - $keypath = ltrim($path, '/'); + $keypath = ltrim( $path, '/' ); $user = $userId; // update $keypath and $user if path point to a file shared by someone else @@ -127,29 +127,33 @@ class Keymanager { * @param string $path relative path of the file, including filename * @param string $key * @return bool true/false - */ + */ public static function setFileKey( $userId, $path, $key ) { \OC_FileProxy::$enabled = false; - $targetpath = ltrim($path, '/'); + $targetpath = ltrim( $path, '/' ); $user = $userId; // update $keytarget and $user if key belongs to a file shared by someone else $query = \OC_DB::prepare( "SELECT uid_owner, source, target FROM `*PREFIX*sharing` WHERE target = ? AND uid_shared_with = ?" ); - $result = $query->execute( array ('/'.$userId.'/files/'.$targetpath, $userId)); - if ($row = $result->fetchRow()){ + + $result = $query->execute( array ( '/'.$userId.'/files/'.$targetpath, $userId ) ); + + if ( $row = $result->fetchRow( ) ) { $targetpath = $row['source']; - $targetpath_parts=explode('/',$targetpath); + $targetpath_parts=explode( '/',$targetpath ); $user = $targetpath_parts[1]; - $targetpath = str_replace('/'.$user.'/files/', '', $targetpath); + $targetpath = str_replace( '/'.$user.'/files/', '', $targetpath ); //TODO: check for write permission on shared file once the new sharing API is in place } $view = new \OC_FilesystemView( '/' . $user . '/files_encryption/keyfiles' ); - $path_parts = pathinfo($targetpath); - if (!$view->file_exists($path_parts['dirname'])) $view->mkdir($path_parts['dirname']); + $path_parts = pathinfo( $targetpath ); + + if ( !$view->file_exists( $path_parts['dirname'] ) ) $view->mkdir( $path_parts['dirname'] ); + $result = $view->file_put_contents( '/' . $targetpath . '.key', $key ); \OC_FileProxy::$enabled = true; diff --git a/apps/files_encryption/lib/proxy.php b/apps/files_encryption/lib/proxy.php index 53ed05d2c3b..c1956ad0216 100644 --- a/apps/files_encryption/lib/proxy.php +++ b/apps/files_encryption/lib/proxy.php @@ -109,10 +109,14 @@ class Proxy extends \OC_FileProxy { // Replace plain content with encrypted content by reference $data = $encrypted['encrypted']; - # TODO: check if file is in subdirectories, and if so, create those parent directories. Or else monitor creation of directories using hooks to ensure path will always exist (what about existing directories when encryption is enabled?) + $filePath = explode( '/', $path ); - // Save keyfile for newly encrypted file in parallel directory - Keymanager::setFileKey( \OCP\USER::getUser(), $path, $encrypted['key'] ); + $filePath = array_slice( $filePath, 3 ); + + $filePath = '/' . implode( '/', $filePath ); + + // Save keyfile for newly encrypted file in parallel directory tree + Keymanager::setFileKey( \OCP\USER::getUser(), $filePath, $encrypted['key'] ); // Update the file cache with file info \OC_FileCache::put( $path, array( 'encrypted'=>true, 'size' => $size ), '' ); @@ -124,38 +128,80 @@ class Proxy extends \OC_FileProxy { public function postFile_get_contents( $path, $data ) { if ( Crypt::isEncryptedContent( $data ) ) { - trigger_error('best'); + + $filePath = explode( '/', $path ); + + $filePath = array_slice( $filePath, 3 ); + + $filePath = '/' . implode( '/', $filePath ); + + trigger_error( "CAT " . $filePath); + $cached = \OC_FileCache_Cached::get( $path, '' ); - $data = Crypt::symmetricDecryptFileContent( $data, $_SESSION['enckey'] ); + // Get keyfile for encrypted file + $keyFile = Keymanager::getFileKey( \OCP\USER::getUser(), $filePath ); + + $data = Crypt::symmetricDecryptFileContent( $data, $keyFile ); } return $data; + } - public function postFopen($path,&$result){ + public function postFopen( $path, &$result ){ - if(!$result){ + if ( !$result ) { + return $result; + } - $meta=stream_get_meta_data($result); - if(Crypt::isEncryptedContent($path)){ - fclose($result); - $result=fopen('crypt://'.$path,$meta['mode']); - }elseif(self::shouldEncrypt($path) and $meta['mode']!='r' and $meta['mode']!='rb'){ - if( \OC_Filesystem::file_exists( $path ) and \OC_Filesystem::filesize($path)>0){ + + $meta = stream_get_meta_data( $result ); + + // If file is encrypted, decrypt using crypto protocol + if ( Crypt::isEncryptedContent( $path ) ) { + + fclose ( $result ); + + $result = fopen( 'crypt://'.$path, $meta['mode'] ); + + } elseif ( + self::shouldEncrypt( $path ) + and $meta ['mode'] != 'r' + and $meta['mode'] != 'rb' + ) { + + # TODO: figure out what this does + + if ( + \OC_Filesystem::file_exists( $path ) + and \OC_Filesystem::filesize( $path ) > 0 + ) { + //first encrypt the target file so we don't end up with a half encrypted file - \OCP\Util::writeLog('files_encryption','Decrypting '.$path.' before writing', \OCP\Util::DEBUG); - $tmp=fopen('php://temp'); - \OCP\Files::streamCopy($result,$tmp); - fclose($result); - \OC_Filesystem::file_put_contents($path,$tmp); - fclose($tmp); + \OCP\Util::writeLog( 'files_encryption', 'Decrypting '.$path.' before writing', \OCP\Util::DEBUG ); + + $tmp = fopen( 'php://temp' ); + + \OCP\Files::streamCopy( $result, $tmp ); + + // Close the original stream, we'll return another one + fclose( $result ); + + \OC_Filesystem::file_put_contents( $path, $tmp ); + + fclose( $tmp ); + } - $result=fopen('crypt://'.$path,$meta['mode']); + + $result = fopen( 'crypt://'.$path, $meta['mode'] ); + } + return $result; + } public function postGetMimeType($path,$mime){ diff --git a/apps/files_encryption/lib/util.php b/apps/files_encryption/lib/util.php index ab58b4aa721..609f7871241 100644 --- a/apps/files_encryption/lib/util.php +++ b/apps/files_encryption/lib/util.php @@ -44,18 +44,19 @@ class Util { # DONE: add method to check if file is encrypted using old system # DONE: add method to fetch legacy key # DONE: add method to decrypt legacy encrypted data + # DONE: fix / test the crypt stream proxy class - # 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: replace cryptstream wrapper with stream_socket_enable_crypto, or fix it to use new crypt class methods # 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: 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: test new encryption with webdav # TODO: test new encryption with versioning # TODO: test new encryption with sharing @@ -154,6 +155,89 @@ class Util { } + public function findFiles( $directory, $type = 'plain' ) { + + # TODO: test finding non plain content + + if ( $handle = $this->view->opendir( $directory ) ) { + + while ( false !== ( $file = readdir( $handle ) ) ) { + + if ( + $file != "." + && $file != ".." + ) { + + $filePath = $directory . '/' . $this->view->getRelativePath( '/' . $file ); + + var_dump($filePath); + + if ( $this->view->is_dir( $filePath ) ) { + + $this->findFiles( $filePath ); + + } elseif ( $this->view->is_file( $filePath ) ) { + + if ( $type == 'plain' ) { + + $this->files[] = array( 'name' => $file, 'path' => $filePath ); + + } elseif ( $type == 'encrypted' ) { + + if ( Crypt::isEncryptedContent( $this->view->file_get_contents( $filePath ) ) ) { + + $this->files[] = array( 'name' => $file, 'path' => $filePath ); + + } + + } elseif ( $type == 'legacy' ) { + + if ( Crypt::isLegacyEncryptedContent( $this->view->file_get_contents( $filePath ) ) ) { + + $this->files[] = array( 'name' => $file, 'path' => $filePath ); + + } + + } + + } + + } + + } + + if ( !empty( $this->files ) ) { + + return $this->files; + + } else { + + return false; + + } + + } + + return false; + + } + + public function encryptAll( OC_FilesystemView $view ) { + + $plainFiles = $this->findPlainFiles( $view ); + + if ( $this->encryptFiles( $plainFiles ) ) { + + return true; + + } else { + + return false; + + } + + } + /** * @brief Get the blowfish encryption handeler for a key * @param $key string (optional) -- cgit v1.2.3