diff --git a/apps/files_encryption/ajax/changeRecoveryPassword.php b/apps/files_encryption/ajax/changeRecoveryPassword.php index 0cb010d3b5..99cc7b3cdd 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 f7d20c486c..a14c9fe507 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 943e7dfcf5..a62d0d413c 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); + // Encrypt private key with new password + $encryptedKey = \OCA\Encryption\Crypt::symmetricEncryptFileContent($keypair['privateKey'], $newUserPassword, Helper::getCipher()); + if ($encryptedKey) { + Keymanager::setPrivateKey($encryptedKey, $user); - // 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); + 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 18f0224391..8ca96899f8 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 a409b0f0fb..3ea27faf40 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 fed0788028..5894c40fe9 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 da84e975a0..931469f4b7 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; @@ -279,6 +283,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 * diff --git a/apps/files_encryption/lib/session.php b/apps/files_encryption/lib/session.php index 4b28f0ce67..accefa559a 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 341114214d..f74812a725 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 , 2011 Robin Appelman - * + * @author Bjoern Schiessle, Robin Appelman + * @copyright 2014 Bjoern Schiessle + * 2012 Sam Tuke , + * 2011 Robin Appelman * * 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; @@ -317,6 +357,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 @@ -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 e44a8bd3dd..495f22c401 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) { @@ -438,6 +445,30 @@ class Util { return $result; } + /** + * 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 diff --git a/config/config.sample.php b/config/config.sample.php index 24c1579bad..1cf2c22866 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,