[Pkg-owncloud-commits] [owncloud] 60/121: support aes 256

David Prévot taffit at moszumanska.debian.org
Thu Aug 21 16:44:32 UTC 2014


This is an automated email from the git hooks/post-receive script.

taffit pushed a commit to branch master
in repository owncloud.

commit 09ed6b5ad42313ff1e7fb0a714e7de73c0829cae
Author: Bjoern Schiessle <schiessle at owncloud.com>
Date:   Mon Jul 21 13:02:28 2014 +0200

    support aes 256
---
 .../ajax/changeRecoveryPassword.php                |  11 +-
 .../ajax/updatePrivateKeyPassword.php              |  14 +--
 apps/files_encryption/hooks/hooks.php              |  29 +++--
 apps/files_encryption/lib/crypt.php                | 131 +++++++++++++++++----
 apps/files_encryption/lib/exceptions.php           |   9 ++
 apps/files_encryption/lib/helper.php               |  39 ++++--
 apps/files_encryption/lib/keymanager.php           |  37 +++++-
 apps/files_encryption/lib/session.php              |  12 +-
 apps/files_encryption/lib/stream.php               |  91 +++++++++++---
 apps/files_encryption/lib/util.php                 |  45 +++++--
 config/config.sample.php                           |   3 +
 11 files changed, 337 insertions(+), 84 deletions(-)

diff --git a/apps/files_encryption/ajax/changeRecoveryPassword.php b/apps/files_encryption/ajax/changeRecoveryPassword.php
index 0cb010d..99cc7b3 100644
--- a/apps/files_encryption/ajax/changeRecoveryPassword.php
+++ b/apps/files_encryption/ajax/changeRecoveryPassword.php
@@ -35,11 +35,12 @@ $encryptedRecoveryKey = $view->file_get_contents($keyPath);
 $decryptedRecoveryKey = \OCA\Encryption\Crypt::decryptPrivateKey($encryptedRecoveryKey, $oldPassword);
 
 if ($decryptedRecoveryKey) {
-
-	$encryptedRecoveryKey = \OCA\Encryption\Crypt::symmetricEncryptFileContent($decryptedRecoveryKey, $newPassword);
-	$view->file_put_contents($keyPath, $encryptedRecoveryKey);
-
-	$return = true;
+	$cipher = \OCA\Encryption\Helper::getCipher();
+	$encryptedKey = \OCA\Encryption\Crypt::symmetricEncryptFileContent($decryptedRecoveryKey, $newPassword, $cipher);
+	if ($encryptedKey) {
+		\OCA\Encryption\Keymanager::setPrivateSystemKey($encryptedKey, $keyId . '.private.key');
+		$return = true;
+	}
 }
 
 \OC_FileProxy::$enabled = $proxyStatus;
diff --git a/apps/files_encryption/ajax/updatePrivateKeyPassword.php b/apps/files_encryption/ajax/updatePrivateKeyPassword.php
index f7d20c4..a14c9fe 100644
--- a/apps/files_encryption/ajax/updatePrivateKeyPassword.php
+++ b/apps/files_encryption/ajax/updatePrivateKeyPassword.php
@@ -35,13 +35,13 @@ $encryptedKey = $view->file_get_contents($keyPath);
 $decryptedKey = \OCA\Encryption\Crypt::decryptPrivateKey($encryptedKey, $oldPassword);
 
 if ($decryptedKey) {
-
-	$encryptedKey = \OCA\Encryption\Crypt::symmetricEncryptFileContent($decryptedKey, $newPassword);
-	$view->file_put_contents($keyPath, $encryptedKey);
-
-	$session->setPrivateKey($decryptedKey);
-
-	$return = true;
+	$cipher = \OCA\Encryption\Helper::getCipher();
+	$encryptedKey = \OCA\Encryption\Crypt::symmetricEncryptFileContent($decryptedKey, $newPassword, $cipher);
+	if ($encryptedKey) {
+		\OCA\Encryption\Keymanager::setPrivateKey($encryptedKey, $user);
+		$session->setPrivateKey($decryptedKey);
+		$return = true;
+	}
 }
 
 \OC_FileProxy::$enabled = $proxyStatus;
diff --git a/apps/files_encryption/hooks/hooks.php b/apps/files_encryption/hooks/hooks.php
index abfcb6d..d514bce 100644
--- a/apps/files_encryption/hooks/hooks.php
+++ b/apps/files_encryption/hooks/hooks.php
@@ -214,10 +214,14 @@ class Hooks {
 				$privateKey = $session->getPrivateKey();
 
 				// Encrypt private key with new user pwd as passphrase
-				$encryptedPrivateKey = Crypt::symmetricEncryptFileContent($privateKey, $params['password']);
+				$encryptedPrivateKey = Crypt::symmetricEncryptFileContent($privateKey, $params['password'], Helper::getCipher());
 
 				// Save private key
-				Keymanager::setPrivateKey($encryptedPrivateKey);
+				if ($encryptedPrivateKey) {
+					Keymanager::setPrivateKey($encryptedPrivateKey, \OCP\User::getUser());
+				} else {
+					\OCP\Util::writeLog('files_encryption', 'Could not update users encryption password', \OCP\Util::ERROR);
+				}
 
 				// NOTE: Session does not need to be updated as the
 				// private key has not changed, only the passphrase
@@ -252,16 +256,17 @@ class Hooks {
 					// Save public key
 					$view->file_put_contents('/public-keys/' . $user . '.public.key', $keypair['publicKey']);
 
-					// Encrypt private key empty passphrase
-					$encryptedPrivateKey = Crypt::symmetricEncryptFileContent($keypair['privateKey'], $newUserPassword);
-
-					// Save private key
-					$view->file_put_contents(
-							'/' . $user . '/files_encryption/' . $user . '.private.key', $encryptedPrivateKey);
-
-					if ($recoveryPassword) { // if recovery key is set we can re-encrypt the key files
-						$util = new Util($view, $user);
-						$util->recoverUsersFiles($recoveryPassword);
+					// Encrypt private key with new password
+					$encryptedKey = \OCA\Encryption\Crypt::symmetricEncryptFileContent($keypair['privateKey'], $newUserPassword, Helper::getCipher());
+					if ($encryptedKey) {
+						Keymanager::setPrivateKey($encryptedKey, $user);
+
+						if ($recoveryPassword) { // if recovery key is set we can re-encrypt the key files
+							$util = new Util($view, $user);
+							$util->recoverUsersFiles($recoveryPassword);
+						}
+					} else {
+						\OCP\Util::writeLog('files_encryption', 'Could not update users encryption password', \OCP\Util::ERROR);
 					}
 
 					\OC_FileProxy::$enabled = $proxyStatus;
diff --git a/apps/files_encryption/lib/crypt.php b/apps/files_encryption/lib/crypt.php
index 69ccce0..d8e41fc 100755
--- a/apps/files_encryption/lib/crypt.php
+++ b/apps/files_encryption/lib/crypt.php
@@ -38,6 +38,11 @@ class Crypt {
 	const ENCRYPTION_PRIVATE_KEY_NOT_VALID_ERROR = 2;
 	const ENCRYPTION_NO_SHARE_KEY_FOUND = 3;
 
+	const BLOCKSIZE = 8192; // block size will always be 8192 for a PHP stream https://bugs.php.net/bug.php?id=21641
+	const DEFAULT_CIPHER = 'AES-256-CFB';
+
+	const HEADERSTART = 'HBEGIN';
+	const HEADEREND = 'HEND';
 
 	/**
 	 * return encryption mode client or server side encryption
@@ -213,19 +218,22 @@ class Crypt {
 	 * @param string $plainContent
 	 * @param string $iv
 	 * @param string $passphrase
+	 * @param string $cypher used for encryption, currently we support AES-128-CFB and AES-256-CFB
 	 * @return string encrypted file content
+	 * @throws \OCA\Encryption\Exceptions\EncryptionException
 	 */
-	private static function encrypt($plainContent, $iv, $passphrase = '') {
+	private static function encrypt($plainContent, $iv, $passphrase = '', $cipher = Crypt::DEFAULT_CIPHER) {
 
-		if ($encryptedContent = openssl_encrypt($plainContent, 'AES-128-CFB', $passphrase, false, $iv)) {
-			return $encryptedContent;
-		} else {
-			\OCP\Util::writeLog('Encryption library', 'Encryption (symmetric) of content failed', \OCP\Util::ERROR);
-			\OCP\Util::writeLog('Encryption library', openssl_error_string(), \OCP\Util::ERROR);
-			return false;
+		$encryptedContent = openssl_encrypt($plainContent, $cipher, $passphrase, false, $iv);
 
+		if (!$encryptedContent) {
+			$error = "Encryption (symmetric) of content failed: " . openssl_error_string();
+			\OCP\Util::writeLog('Encryption library', $error, \OCP\Util::ERROR);
+			throw new Exceptions\EncryptionException($error, 50);
 		}
 
+		return $encryptedContent;
+
 	}
 
 	/**
@@ -233,19 +241,18 @@ class Crypt {
 	 * @param string $encryptedContent
 	 * @param string $iv
 	 * @param string $passphrase
+	 * @param string $cipher cipher user for decryption, currently we support aes128 and aes256
 	 * @throws \Exception
 	 * @return string decrypted file content
 	 */
-	private static function decrypt($encryptedContent, $iv, $passphrase) {
+	private static function decrypt($encryptedContent, $iv, $passphrase, $cipher = Crypt::DEFAULT_CIPHER) {
 
-		if ($plainContent = openssl_decrypt($encryptedContent, 'AES-128-CFB', $passphrase, false, $iv)) {
+		$plainContent = openssl_decrypt($encryptedContent, $cipher, $passphrase, false, $iv);
 
+		if ($plainContent) {
 			return $plainContent;
-
 		} else {
-
 			throw new \Exception('Encryption library: Decryption (symmetric) of content failed');
-
 		}
 
 	}
@@ -293,11 +300,12 @@ class Crypt {
 	 * Symmetrically encrypts a string and returns keyfile content
 	 * @param string $plainContent content to be encrypted in keyfile
 	 * @param string $passphrase
+	 * @param string $cypher used for encryption, currently we support AES-128-CFB and AES-256-CFB
 	 * @return false|string 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 = '') {
+	public static function symmetricEncryptFileContent($plainContent, $passphrase = '', $cipher = Crypt::DEFAULT_CIPHER) {
 
 		if (!$plainContent) {
 			\OCP\Util::writeLog('Encryption library', 'symmetrically encryption failed, no content given.', \OCP\Util::ERROR);
@@ -306,15 +314,16 @@ class Crypt {
 
 		$iv = self::generateIv();
 
-		if ($encryptedContent = self::encrypt($plainContent, $iv, $passphrase)) {
+		try {
+			$encryptedContent = self::encrypt($plainContent, $iv, $passphrase, $cipher);
 			// Combine content to encrypt with IV identifier and actual IV
 			$catfile = self::concatIv($encryptedContent, $iv);
 			$padded = self::addPadding($catfile);
 
 			return $padded;
-
-		} else {
-			\OCP\Util::writeLog('Encryption library', 'Encryption (symmetric) of keyfile content failed', \OCP\Util::ERROR);
+		} catch (OCA\Encryption\Exceptions\EncryptionException $e) {
+			$message = 'Could not encrypt file content (code: ' . $e->getCode . '): ';
+			\OCP\Util::writeLog('files_encryption', $message . $e->getMessage, \OCP\Util::ERROR);
 			return false;
 		}
 
@@ -325,6 +334,7 @@ class Crypt {
 	 * Symmetrically decrypts keyfile content
 	 * @param string $keyfileContent
 	 * @param string $passphrase
+	 * @param string $cipher cipher used for decryption, currently aes128 and aes256 is supported.
 	 * @throws \Exception
 	 * @return string|false
 	 * @internal param string $source
@@ -334,7 +344,7 @@ class Crypt {
 	 *
 	 * This function decrypts a file
 	 */
-	public static function symmetricDecryptFileContent($keyfileContent, $passphrase = '') {
+	public static function symmetricDecryptFileContent($keyfileContent, $passphrase = '', $cipher = 'AES-128-CFB') {
 
 		if (!$keyfileContent) {
 
@@ -348,7 +358,7 @@ class Crypt {
 		// Split into enc data and catfile
 		$catfile = self::splitIv($noPadding);
 
-		if ($plainContent = self::decrypt($catfile['encrypted'], $catfile['iv'], $passphrase)) {
+		if ($plainContent = self::decrypt($catfile['encrypted'], $catfile['iv'], $passphrase, $cipher)) {
 
 			return $plainContent;
 
@@ -360,6 +370,7 @@ class Crypt {
 
 	/**
 	 * Decrypt private key and check if the result is a valid keyfile
+	 *
 	 * @param string $encryptedKey encrypted keyfile
 	 * @param string $passphrase to decrypt keyfile
 	 * @return string|false encrypted private key or false
@@ -368,7 +379,15 @@ class Crypt {
 	 */
 	public static function decryptPrivateKey($encryptedKey, $passphrase) {
 
-		$plainKey = self::symmetricDecryptFileContent($encryptedKey, $passphrase);
+		$header = self::parseHeader($encryptedKey);
+		$cipher = self::getCipher($header);
+
+		// if we found a header we need to remove it from the key we want to decrypt
+		if (!empty($header)) {
+			$encryptedKey = substr($encryptedKey, strpos($encryptedKey, self::HEADEREND) + strlen(self::HEADEREND));
+		}
+
+		$plainKey = self::symmetricDecryptFileContent($encryptedKey, $passphrase, $cipher);
 
 		// check if this a valid private key
 		$res = openssl_pkey_get_private($plainKey);
@@ -571,4 +590,76 @@ class Crypt {
 		}
 	}
 
+	/**
+	 * read header into array
+	 *
+	 * @param string $data
+	 * @return array
+	 */
+	public static function parseHeader($data) {
+
+		$result = array();
+
+		if (substr($data, 0, strlen(self::HEADERSTART)) === self::HEADERSTART) {
+			$endAt = strpos($data, self::HEADEREND);
+			$header = substr($data, 0, $endAt + strlen(self::HEADEREND));
+
+			// +1 to not start with an ':' which would result in empty element at the beginning
+			$exploded = explode(':', substr($header, strlen(self::HEADERSTART)+1));
+
+			$element = array_shift($exploded);
+			while ($element !== self::HEADEREND) {
+
+				$result[$element] = array_shift($exploded);
+
+				$element = array_shift($exploded);
+
+			}
+		}
+
+		return $result;
+	}
+
+	/**
+	 * check if data block is the header
+	 *
+	 * @param string $data
+	 * @return boolean
+	 */
+	public static function isHeader($data) {
+
+		if (substr($data, 0, strlen(self::HEADERSTART)) === self::HEADERSTART) {
+			return true;
+		}
+
+		return false;
+	}
+
+	/**
+	 * get chiper from header
+	 *
+	 * @param array $header
+	 * @throws \OCA\Encryption\Exceptions\EncryptionException
+	 */
+	public static function getCipher($header) {
+		$cipher = isset($header['cipher']) ? $header['cipher'] : 'AES-128-CFB';
+
+		if ($cipher !== 'AES-256-CFB' && $cipher !== 'AES-128-CFB') {
+
+			throw new \OCA\Encryption\Exceptions\EncryptionException('file header broken, no supported cipher defined', 40);
+		}
+
+		return $cipher;
+	}
+
+	/**
+	 * generate header for encrypted file
+	 */
+	public static function generateHeader() {
+		$cipher = Helper::getCipher();
+		$header = self::HEADERSTART . ':cipher:' . $cipher . ':' . self::HEADEREND;
+
+		return $header;
+	}
+
 }
diff --git a/apps/files_encryption/lib/exceptions.php b/apps/files_encryption/lib/exceptions.php
index a409b0f..3ea27fa 100644
--- a/apps/files_encryption/lib/exceptions.php
+++ b/apps/files_encryption/lib/exceptions.php
@@ -22,6 +22,15 @@
 
 namespace OCA\Encryption\Exceptions;
 
+/**
+ * General encryption exception
+ * Possible Error Codes:
+ * 10 - unexpected end of encryption header
+ * 20 - unexpected blog size
+ * 30 - encryption header to large
+ * 40 - unknown cipher
+ * 50 - encryption failed
+ */
 class EncryptionException extends \Exception {
 }
 
diff --git a/apps/files_encryption/lib/helper.php b/apps/files_encryption/lib/helper.php
index 214e212..d427c51 100755
--- a/apps/files_encryption/lib/helper.php
+++ b/apps/files_encryption/lib/helper.php
@@ -144,19 +144,17 @@ class Helper {
 
 			$view->file_put_contents('/public-keys/' . $recoveryKeyId . '.public.key', $keypair['publicKey']);
 
-			// Encrypt private key empty passphrase
-			$encryptedPrivateKey = \OCA\Encryption\Crypt::symmetricEncryptFileContent($keypair['privateKey'], $recoveryPassword);
-
-			// Save private key
-			$view->file_put_contents('/owncloud_private_key/' . $recoveryKeyId . '.private.key', $encryptedPrivateKey);
+			$cipher = \OCA\Encryption\Helper::getCipher();
+			$encryptedKey = \OCA\Encryption\Crypt::symmetricEncryptFileContent($keypair['privateKey'], $recoveryPassword, $cipher);
+			if ($encryptedKey) {
+				Keymanager::setPrivateSystemKey($encryptedKey, $recoveryKeyId . '.private.key');
+				// Set recoveryAdmin as enabled
+				$appConfig->setValue('files_encryption', 'recoveryAdminEnabled', 1);
+				$return = true;
+			}
 
 			\OC_FileProxy::$enabled = true;
 
-			// Set recoveryAdmin as enabled
-			$appConfig->setValue('files_encryption', 'recoveryAdminEnabled', 1);
-
-			$return = true;
-
 		} else { // get recovery key and check the password
 			$util = new \OCA\Encryption\Util(new \OC\Files\View('/'), \OCP\User::getUser());
 			$return = $util->checkRecoveryPassword($recoveryPassword);
@@ -230,7 +228,6 @@ class Helper {
 		return $return;
 	}
 
-
 	/**
 	 * checks if access is public/anonymous user
 	 * @return bool
@@ -478,5 +475,25 @@ class Helper {
 
 		return false;
 	}
+
+	/**
+	 * read the cipher used for encryption from the config.php
+	 *
+	 * @return string
+	 */
+	public static function getCipher() {
+
+		$cipher = \OCP\Config::getSystemValue('cipher', Crypt::DEFAULT_CIPHER);
+
+		if ($cipher !== 'AES-256-CFB' && $cipher !== 'AES-128-CFB') {
+			\OCP\Util::writeLog('files_encryption',
+					'wrong cipher defined in config.php, only AES-128-CFB and AES-256-CFB is supported. Fall back ' . Crypt::DEFAULT_CIPHER,
+					\OCP\Util::WARN);
+
+			$cipher = Crypt::DEFAULT_CIPHER;
+		}
+
+		return $cipher;
+	}
 }
 
diff --git a/apps/files_encryption/lib/keymanager.php b/apps/files_encryption/lib/keymanager.php
index da84e97..931469f 100755
--- a/apps/files_encryption/lib/keymanager.php
+++ b/apps/files_encryption/lib/keymanager.php
@@ -258,9 +258,13 @@ class Keymanager {
 	 * @note Encryption of the private key must be performed by client code
 	 * as no encryption takes place here
 	 */
-	public static function setPrivateKey($key) {
+	public static function setPrivateKey($key, $user = '') {
 
-		$user = \OCP\User::getUser();
+		if ($user === '') {
+			$user = \OCP\User::getUser();
+		}
+
+		$header = Crypt::generateHeader();
 
 		$view = new \OC\Files\View('/' . $user . '/files_encryption');
 
@@ -271,7 +275,7 @@ class Keymanager {
 			$view->mkdir('');
 		}
 
-		$result = $view->file_put_contents($user . '.private.key', $key);
+		$result = $view->file_put_contents($user . '.private.key', $header . $key);
 
 		\OC_FileProxy::$enabled = $proxyStatus;
 
@@ -280,6 +284,33 @@ class Keymanager {
 	}
 
 	/**
+	 * write private system key (recovery and public share key) to disk
+	 *
+	 * @param string $key encrypted key
+	 * @param string $keyName name of the key file
+	 * @return boolean
+	 */
+	public static function setPrivateSystemKey($key, $keyName) {
+
+		$header = Crypt::generateHeader();
+
+		$view = new \OC\Files\View('/owncloud_private_key');
+
+		$proxyStatus = \OC_FileProxy::$enabled;
+		\OC_FileProxy::$enabled = false;
+
+		if (!$view->file_exists('')) {
+			$view->mkdir('');
+		}
+
+		$result = $view->file_put_contents($keyName, $header . $key);
+
+		\OC_FileProxy::$enabled = $proxyStatus;
+
+		return $result;
+	}
+
+	/**
 	 * store share key
 	 *
 	 * @param \OC\Files\View $view
diff --git a/apps/files_encryption/lib/session.php b/apps/files_encryption/lib/session.php
index e6d9fa5..fc15a42 100644
--- a/apps/files_encryption/lib/session.php
+++ b/apps/files_encryption/lib/session.php
@@ -80,11 +80,13 @@ class Session {
 			$this->view->file_put_contents('/public-keys/' . $publicShareKeyId . '.public.key', $keypair['publicKey']);
 
 			// Encrypt private key empty passphrase
-			$encryptedPrivateKey = Crypt::symmetricEncryptFileContent($keypair['privateKey'], '');
-
-			// Save private key
-			$this->view->file_put_contents(
-				'/owncloud_private_key/' . $publicShareKeyId . '.private.key', $encryptedPrivateKey);
+			$cipher = \OCA\Encryption\Helper::getCipher();
+			$encryptedKey = \OCA\Encryption\Crypt::symmetricEncryptFileContent($keypair['privateKey'], '', $cipher);
+			if ($encryptedKey) {
+				Keymanager::setPrivateSystemKey($encryptedKey, $publicShareKeyId . '.private.key');
+			} else {
+				\OCP\Util::writeLog('files_encryption', 'Could not create public share keys', \OCP\Util::ERROR);
+			}
 
 			\OC_FileProxy::$enabled = $proxyStatus;
 
diff --git a/apps/files_encryption/lib/stream.php b/apps/files_encryption/lib/stream.php
index 3411142..f74812a 100644
--- a/apps/files_encryption/lib/stream.php
+++ b/apps/files_encryption/lib/stream.php
@@ -2,9 +2,10 @@
 /**
  * ownCloud
  *
- * @author Robin Appelman
- * @copyright 2012 Sam Tuke <samtuke at owncloud.com>, 2011 Robin Appelman
- * <icewind1991 at gmail.com>
+ * @author Bjoern Schiessle, Robin Appelman
+ * @copyright 2014 Bjoern Schiessle <schiessle at owncloud.com>
+ *            2012 Sam Tuke <samtuke at owncloud.com>,
+ *            2011 Robin Appelman <icewind1991 at gmail.com>
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
@@ -49,9 +50,11 @@ namespace OCA\Encryption;
  * encryption proxies are used and keyfiles deleted.
  */
 class Stream {
+
+	const PADDING_CHAR = '-';
+
 	private $plainKey;
 	private $encKeyfiles;
-
 	private $rawPath; // The raw path relative to the data dir
 	private $relPath; // rel path to users file dir
 	private $userId;
@@ -66,6 +69,9 @@ class Stream {
 	private $newFile; // helper var, we only need to write the keyfile for new files
 	private $isLocalTmpFile = false; // do we operate on a local tmp file
 	private $localTmpFile; // path of local tmp file
+	private $headerWritten = false;
+	private $containHeader = false; // the file contain a header
+	private $cipher; // cipher used for encryption/decryption
 
 	/**
 	 * @var \OC\Files\View
@@ -87,6 +93,9 @@ class Stream {
 	 */
 	public function stream_open($path, $mode, $options, &$opened_path) {
 
+		// read default cipher from config
+		$this->cipher = Helper::getCipher();
+
 		// assume that the file already exist before we decide it finally in getKey()
 		$this->newFile = false;
 
@@ -150,6 +159,9 @@ class Stream {
 			}
 
 			$this->size = $this->rootView->filesize($this->rawPath);
+
+			$this->readHeader();
+
 		}
 
 		if ($this->isLocalTmpFile) {
@@ -178,6 +190,29 @@ class Stream {
 
 	}
 
+	private function readHeader() {
+
+		if ($this->isLocalTmpFile) {
+			$handle = fopen($this->localTmpFile, 'r');
+		} else {
+			$handle = $this->rootView->fopen($this->rawPath, 'r');
+		}
+
+		if (is_resource($handle)) {
+			$data = fread($handle, Crypt::BLOCKSIZE);
+
+			$header = Crypt::parseHeader($data);
+			$this->cipher = Crypt::getCipher($header);
+
+			// remeber that we found a header
+			if (!empty($header)) {
+				$this->containHeader = true;
+			}
+
+			fclose($handle);
+		}
+	}
+
 	/**
 	 * Returns the current position of the file pointer
 	 * @return int position of the file pointer
@@ -195,6 +230,11 @@ class Stream {
 
 		$this->flush();
 
+		// ignore the header and just overstep it
+		if ($this->containHeader) {
+			$offset += Crypt::BLOCKSIZE;
+		}
+
 		// this wrapper needs to return "true" for success.
 		// the fseek call itself returns 0 on succeess
 		return !fseek($this->handle, $offset, $whence);
@@ -204,25 +244,25 @@ class Stream {
 	/**
 	 * @param int $count
 	 * @return bool|string
-	 * @throws \Exception
+	 * @throws \OCA\Encryption\Exceptions\EncryptionException
 	 */
 	public function stream_read($count) {
 
 		$this->writeCache = '';
 
-		if ($count !== 8192) {
-
-			// $count will always be 8192 https://bugs.php.net/bug.php?id=21641
-			// This makes this function a lot simpler, but will break this class if the above 'bug' gets 'fixed'
+		if ($count !== Crypt::BLOCKSIZE) {
 			\OCP\Util::writeLog('Encryption library', 'PHP "bug" 21641 no longer holds, decryption system requires refactoring', \OCP\Util::FATAL);
-
-			die();
-
+			throw new \OCA\Encryption\Exceptions\EncryptionException('expected a blog size of 8192 byte', 20);
 		}
 
 		// Get the data from the file handle
 		$data = fread($this->handle, $count);
 
+		// if this block contained the header we move on to the next block
+		if (Crypt::isHeader($data)) {
+			$data = fread($this->handle, $count);
+		}
+
 		$result = null;
 
 		if (strlen($data)) {
@@ -236,7 +276,7 @@ class Stream {
 			} else {
 
 				// Decrypt data
-				$result = Crypt::symmetricDecryptFileContent($data, $this->plainKey);
+				$result = Crypt::symmetricDecryptFileContent($data, $this->plainKey, $this->cipher);
 			}
 
 		}
@@ -254,7 +294,7 @@ class Stream {
 	public function preWriteEncrypt($plainData, $key) {
 
 		// Encrypt data to 'catfile', which includes IV
-		if ($encrypted = Crypt::symmetricEncryptFileContent($plainData, $key)) {
+		if ($encrypted = Crypt::symmetricEncryptFileContent($plainData, $key, $this->cipher)) {
 
 			return $encrypted;
 
@@ -318,6 +358,25 @@ class Stream {
 	}
 
 	/**
+	 * write header at beginning of encrypted file
+	 *
+	 * @throws Exceptions\EncryptionException
+	 */
+	private function writeHeader() {
+
+		$header = Crypt::generateHeader();
+
+		if (strlen($header) > Crypt::BLOCKSIZE) {
+			throw new Exceptions\EncryptionException('max header size exceeded', 30);
+		}
+
+		$paddedHeader = str_pad($header, Crypt::BLOCKSIZE, self::PADDING_CHAR, STR_PAD_RIGHT);
+
+		fwrite($this->handle, $paddedHeader);
+		$this->headerWritten = true;
+	}
+
+	/**
 	 * Handle plain data from the stream, and write it in 8192 byte blocks
 	 * @param string $data data to be written to disk
 	 * @note the data will be written to the path stored in the stream handle, set in stream_open()
@@ -334,6 +393,10 @@ class Stream {
 			return strlen($data);
 		}
 
+		if ($this->headerWritten === false) {
+			$this->writeHeader();
+		}
+
 		// 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
diff --git a/apps/files_encryption/lib/util.php b/apps/files_encryption/lib/util.php
index e98dea5..f434b67 100644
--- a/apps/files_encryption/lib/util.php
+++ b/apps/files_encryption/lib/util.php
@@ -167,11 +167,12 @@ class Util {
 				\OC_FileProxy::$enabled = false;
 
 				// Encrypt private key with user pwd as passphrase
-				$encryptedPrivateKey = Crypt::symmetricEncryptFileContent($keypair['privateKey'], $passphrase);
+				$encryptedPrivateKey = Crypt::symmetricEncryptFileContent($keypair['privateKey'], $passphrase, Helper::getCipher());
 
 				// Save key-pair
 				if ($encryptedPrivateKey) {
-					$this->view->file_put_contents($this->privateKeyPath, $encryptedPrivateKey);
+					$header = crypt::generateHeader();
+					$this->view->file_put_contents($this->privateKeyPath, $header . $encryptedPrivateKey);
 					$this->view->file_put_contents($this->publicKeyPath, $keypair['publicKey']);
 				}
 
@@ -394,8 +395,14 @@ class Util {
 			&& $this->isEncryptedPath($path)
 		) {
 
-			// get the size from filesystem
-			$size = $this->view->filesize($path);
+			$offset = 0;
+			if ($this->containHeader($path)) {
+				$offset = Crypt::BLOCKSIZE;
+			}
+
+			// get the size from filesystem if the file contains a encryption header we
+			// we substract it
+			$size = $this->view->filesize($path) - $offset;
 
 			// fast path, else the calculation for $lastChunkNr is bogus
 			if ($size === 0) {
@@ -406,15 +413,15 @@ class Util {
 			// calculate last chunk nr
 			// next highest is end of chunks, one subtracted is last one
 			// we have to read the last chunk, we can't just calculate it (because of padding etc)
-			$lastChunkNr = ceil($size/ 8192) - 1;
-			$lastChunkSize = $size - ($lastChunkNr * 8192);
+			$lastChunkNr = ceil($size/ Crypt::BLOCKSIZE) - 1;
+			$lastChunkSize = $size - ($lastChunkNr * Crypt::BLOCKSIZE);
 
 			// open stream
 			$stream = fopen('crypt://' . $path, "r");
 
 			if (is_resource($stream)) {
 				// calculate last chunk position
-				$lastChunckPos = ($lastChunkNr * 8192);
+				$lastChunckPos = ($lastChunkNr * Crypt::BLOCKSIZE);
 
 				// seek to end
 				if (@fseek($stream, $lastChunckPos) === -1) {
@@ -449,6 +456,30 @@ class Util {
 	}
 
 	/**
+	 * check if encrypted file contain a encryption header
+	 *
+	 * @param string $path
+	 * @return boolean
+	 */
+	private function containHeader($path) {
+		// Disable encryption proxy to read the raw data
+		$proxyStatus = \OC_FileProxy::$enabled;
+		\OC_FileProxy::$enabled = false;
+
+		$isHeader = false;
+		$handle = $this->view->fopen($path, 'r');
+
+		if (is_resource($handle)) {
+			$firstBlock = fread($handle, Crypt::BLOCKSIZE);
+			$isHeader =  Crypt::isHeader($firstBlock);
+		}
+
+		\OC_FileProxy::$enabled = $proxyStatus;
+
+		return $isHeader;
+	}
+
+	/**
 	 * fix the file size of the encrypted file
 	 * @param string $path absolute path
 	 * @return boolean true / false if file is encrypted
diff --git a/config/config.sample.php b/config/config.sample.php
index 24c1579..1cf2c22 100755
--- a/config/config.sample.php
+++ b/config/config.sample.php
@@ -277,6 +277,9 @@ $CONFIG = array(
 	//'config' => '/absolute/location/of/openssl.cnf',
 ),
 
+// default cipher used for file encryption, currently we support AES-128-CFB and AES-256-CFB
+'cipher' => 'AES-256-CFB',
+
 /* whether usage of the instance should be restricted to admin users only */
 'singleuser' => false,
 

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-owncloud/owncloud.git



More information about the Pkg-owncloud-commits mailing list