diff options
author | Bjoern Schiessle <schiessle@owncloud.com> | 2014-07-21 13:02:28 +0200 |
---|---|---|
committer | Bjoern Schiessle <schiessle@owncloud.com> | 2014-07-23 12:14:01 +0200 |
commit | 4bbdcfbccfe1fef28d3b6feea8a742e156295a63 (patch) | |
tree | 4b920bc9e16ad4d5b4c1b2e2b8fc521ba9e0dd6e | |
parent | ddd8eae9320bcd5317f18a6305120c358f72bcdd (diff) | |
download | nextcloud-server-4bbdcfbccfe1fef28d3b6feea8a742e156295a63.tar.gz nextcloud-server-4bbdcfbccfe1fef28d3b6feea8a742e156295a63.zip |
support aes 256
-rw-r--r-- | apps/files_encryption/ajax/changeRecoveryPassword.php | 11 | ||||
-rw-r--r-- | apps/files_encryption/ajax/updatePrivateKeyPassword.php | 14 | ||||
-rw-r--r-- | apps/files_encryption/hooks/hooks.php | 29 | ||||
-rwxr-xr-x | apps/files_encryption/lib/crypt.php | 131 | ||||
-rw-r--r-- | apps/files_encryption/lib/exceptions.php | 9 | ||||
-rwxr-xr-x | apps/files_encryption/lib/helper.php | 39 | ||||
-rwxr-xr-x | apps/files_encryption/lib/keymanager.php | 37 | ||||
-rw-r--r-- | apps/files_encryption/lib/session.php | 12 | ||||
-rw-r--r-- | apps/files_encryption/lib/stream.php | 91 | ||||
-rw-r--r-- | apps/files_encryption/lib/util.php | 45 | ||||
-rwxr-xr-x | 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 0cb010d3b56..99cc7b3cdde 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 f7d20c486cf..a14c9fe5076 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 943e7dfcf50..a62d0d413c9 100644 --- a/apps/files_encryption/hooks/hooks.php +++ b/apps/files_encryption/hooks/hooks.php @@ -193,10 +193,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
@@ -231,16 +235,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 18f0224391d..8ca96899f88 100755 --- a/apps/files_encryption/lib/crypt.php +++ b/apps/files_encryption/lib/crypt.php @@ -36,6 +36,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
@@ -181,19 +186,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;
+
}
/**
@@ -201,19 +209,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');
-
}
}
@@ -261,11 +268,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);
@@ -274,15 +282,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;
}
@@ -293,6 +302,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
@@ -302,7 +312,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) {
@@ -316,7 +326,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;
@@ -328,6 +338,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
@@ -336,7 +347,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);
@@ -481,4 +500,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 a409b0f0fb2..3ea27faf406 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 fed0788028f..5894c40fe92 100755 --- a/apps/files_encryption/lib/helper.php +++ b/apps/files_encryption/lib/helper.php @@ -141,19 +141,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); @@ -227,7 +225,6 @@ class Helper { return $return; } - /** * checks if access is public/anonymous user * @return bool @@ -475,5 +472,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 da84e975a05..931469f4b74 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 4b28f0ce676..accefa559a2 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 341114214d5..f74812a7253 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@owncloud.com>, 2011 Robin Appelman - * <icewind1991@gmail.com> + * @author Bjoern Schiessle, Robin Appelman + * @copyright 2014 Bjoern Schiessle <schiessle@owncloud.com> + * 2012 Sam Tuke <samtuke@owncloud.com>, + * 2011 Robin Appelman <icewind1991@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 e44a8bd3dda..495f22c4014 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']); } @@ -384,8 +385,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) { @@ -396,15 +403,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) { @@ -439,6 +446,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 24c1579badf..1cf2c22866a 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, |