summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lib/private/encryption/util.php29
-rw-r--r--lib/private/files/storage/wrapper/encryption.php109
-rw-r--r--tests/lib/encryption/utiltest.php13
-rw-r--r--tests/lib/files/storage/wrapper/encryption.php120
4 files changed, 209 insertions, 62 deletions
diff --git a/lib/private/encryption/util.php b/lib/private/encryption/util.php
index 8bff65428d3..d0733941a35 100644
--- a/lib/private/encryption/util.php
+++ b/lib/private/encryption/util.php
@@ -128,35 +128,6 @@ class Util {
}
/**
- * read header into array
- *
- * @param string $header
- * @return array
- */
- public function readHeader($header) {
-
- $result = array();
-
- if (substr($header, 0, strlen(self::HEADER_START)) === self::HEADER_START) {
- $endAt = strpos($header, self::HEADER_END);
- if ($endAt !== false) {
- $header = substr($header, 0, $endAt + strlen(self::HEADER_END));
-
- // +1 to not start with an ':' which would result in empty element at the beginning
- $exploded = explode(':', substr($header, strlen(self::HEADER_START)+1));
-
- $element = array_shift($exploded);
- while ($element !== self::HEADER_END) {
- $result[$element] = array_shift($exploded);
- $element = array_shift($exploded);
- }
- }
- }
-
- return $result;
- }
-
- /**
* create header for encrypted file
*
* @param array $headerData
diff --git a/lib/private/files/storage/wrapper/encryption.php b/lib/private/files/storage/wrapper/encryption.php
index 8818b822fa7..ebd6062b1da 100644
--- a/lib/private/files/storage/wrapper/encryption.php
+++ b/lib/private/files/storage/wrapper/encryption.php
@@ -31,6 +31,7 @@ use OC\Encryption\Util;
use OC\Files\Filesystem;
use OC\Files\Mount\Manager;
use OC\Files\Storage\LocalTempFileTrait;
+use OCP\Encryption\Exceptions\GenericEncryptionException;
use OCP\Encryption\IFile;
use OCP\Encryption\IManager;
use OCP\Encryption\Keys\IStorage;
@@ -174,9 +175,8 @@ class Encryption extends Wrapper {
public function file_get_contents($path) {
$encryptionModule = $this->getEncryptionModule($path);
- $info = $this->getCache()->get($path);
- if ($encryptionModule || $info['encrypted'] === true) {
+ if ($encryptionModule) {
$handle = $this->fopen($path, "r");
if (!$handle) {
return false;
@@ -338,14 +338,15 @@ class Encryption extends Wrapper {
* @param string $path
* @param string $mode
* @return resource
+ * @throws GenericEncryptionException
+ * @throws ModuleDoesNotExistsException
*/
public function fopen($path, $mode) {
$encryptionEnabled = $this->encryptionManager->isEnabled();
$shouldEncrypt = false;
$encryptionModule = null;
- $rawHeader = $this->getHeader($path);
- $header = $this->util->readHeader($rawHeader);
+ $header = $this->getHeader($path);
$fullPath = $this->getFullPath($path);
$encryptionModuleId = $this->util->getEncryptionModuleId($header);
@@ -380,6 +381,10 @@ class Encryption extends Wrapper {
|| $mode === 'wb'
|| $mode === 'wb+'
) {
+ // don't overwrite encrypted files if encyption is not enabled
+ if ($targetIsEncrypted && $encryptionEnabled === false) {
+ throw new GenericEncryptionException('Tried to access encrypted file but encryption is not enabled');
+ }
if ($encryptionEnabled) {
// if $encryptionModuleId is empty, the default module will be used
$encryptionModule = $this->encryptionManager->getEncryptionModule($encryptionModuleId);
@@ -416,7 +421,7 @@ class Encryption extends Wrapper {
$source = $this->storage->fopen($path, $mode);
$handle = \OC\Files\Stream\Encryption::wrap($source, $path, $fullPath, $header,
$this->uid, $encryptionModule, $this->storage, $this, $this->util, $this->fileHelper, $mode,
- $size, $unencryptedSize, strlen($rawHeader));
+ $size, $unencryptedSize, $this->getHeaderSize($path));
return $handle;
}
@@ -606,27 +611,101 @@ class Encryption extends Wrapper {
}
/**
+ * read first block of encrypted file, typically this will contain the
+ * encryption header
+ *
+ * @param string $path
+ * @return string
+ */
+ protected function readFirstBlock($path) {
+ $firstBlock = '';
+ if ($this->storage->file_exists($path)) {
+ $handle = $this->storage->fopen($path, 'r');
+ $firstBlock = fread($handle, $this->util->getHeaderSize());
+ fclose($handle);
+ }
+ return $firstBlock;
+ }
+
+ /**
+ * return header size of given file
+ *
+ * @param string $path
+ * @return int
+ */
+ protected function getHeaderSize($path) {
+ $headerSize = 0;
+ $realFile = $this->util->stripPartialFileExtension($path);
+ if ($this->storage->file_exists($realFile)) {
+ $path = $realFile;
+ }
+ $firstBlock = $this->readFirstBlock($path);
+
+ if (substr($firstBlock, 0, strlen(Util::HEADER_START)) === Util::HEADER_START) {
+ $headerSize = strlen($firstBlock);
+ }
+
+ return $headerSize;
+ }
+
+ /**
+ * parse raw header to array
+ *
+ * @param string $rawHeader
+ * @return array
+ */
+ protected function parseRawHeader($rawHeader) {
+ $result = array();
+ if (substr($rawHeader, 0, strlen(Util::HEADER_START)) === Util::HEADER_START) {
+ $header = $rawHeader;
+ $endAt = strpos($header, Util::HEADER_END);
+ if ($endAt !== false) {
+ $header = substr($header, 0, $endAt + strlen(Util::HEADER_END));
+
+ // +1 to not start with an ':' which would result in empty element at the beginning
+ $exploded = explode(':', substr($header, strlen(Util::HEADER_START)+1));
+
+ $element = array_shift($exploded);
+ while ($element !== Util::HEADER_END) {
+ $result[$element] = array_shift($exploded);
+ $element = array_shift($exploded);
+ }
+ }
+ }
+
+ return $result;
+ }
+
+ /**
* read header from file
*
* @param string $path
* @return array
*/
protected function getHeader($path) {
- $header = '';
$realFile = $this->util->stripPartialFileExtension($path);
if ($this->storage->file_exists($realFile)) {
$path = $realFile;
}
- if ($this->storage->file_exists($path)) {
- $handle = $this->storage->fopen($path, 'r');
- $firstBlock = fread($handle, $this->util->getHeaderSize());
- fclose($handle);
- if (substr($firstBlock, 0, strlen(Util::HEADER_START)) === Util::HEADER_START) {
- $header = $firstBlock;
+ $firstBlock = $this->readFirstBlock($path);
+ $result = $this->parseRawHeader($firstBlock);
+
+ // if the header doesn't contain a encryption module we check if it is a
+ // legacy file. If true, we add the default encryption module
+ if (!isset($result[Util::HEADER_ENCRYPTION_MODULE_KEY])) {
+ if (!empty($result)) {
+ $result[Util::HEADER_ENCRYPTION_MODULE_KEY] = 'OC_DEFAULT_MODULE';
+ } else {
+ // if the header was empty we have to check first if it is a encrypted file at all
+ $info = $this->getCache()->get($path);
+ if (isset($info['encrypted']) && $info['encrypted'] === true) {
+ $result[Util::HEADER_ENCRYPTION_MODULE_KEY] = 'OC_DEFAULT_MODULE';
+ }
}
}
- return $header;
+
+ return $result;
}
/**
@@ -639,8 +718,7 @@ class Encryption extends Wrapper {
*/
protected function getEncryptionModule($path) {
$encryptionModule = null;
- $rawHeader = $this->getHeader($path);
- $header = $this->util->readHeader($rawHeader);
+ $header = $this->getHeader($path);
$encryptionModuleId = $this->util->getEncryptionModuleId($header);
if (!empty($encryptionModuleId)) {
try {
@@ -675,4 +753,5 @@ class Encryption extends Wrapper {
return false;
}
+
}
diff --git a/tests/lib/encryption/utiltest.php b/tests/lib/encryption/utiltest.php
index d5f5ce4c2e9..5aadb4e857f 100644
--- a/tests/lib/encryption/utiltest.php
+++ b/tests/lib/encryption/utiltest.php
@@ -75,19 +75,6 @@ class UtilTest extends TestCase {
/**
* @dataProvider providesHeaders
*/
- public function testReadHeader($header, $expected, $moduleId) {
- $expected['oc_encryption_module'] = $moduleId;
- $result = $this->util->readHeader($header);
- $this->assertSameSize($expected, $result);
- foreach ($expected as $key => $value) {
- $this->assertArrayHasKey($key, $result);
- $this->assertSame($value, $result[$key]);
- }
- }
-
- /**
- * @dataProvider providesHeaders
- */
public function testCreateHeader($expected, $header, $moduleId) {
$em = $this->getMock('\OCP\Encryption\IEncryptionModule');
diff --git a/tests/lib/files/storage/wrapper/encryption.php b/tests/lib/files/storage/wrapper/encryption.php
index a10e95a3f8b..677bbffc3d2 100644
--- a/tests/lib/files/storage/wrapper/encryption.php
+++ b/tests/lib/files/storage/wrapper/encryption.php
@@ -2,12 +2,20 @@
namespace Test\Files\Storage\Wrapper;
+use OC\Encryption\Util;
use OC\Files\Storage\Temporary;
use OC\Files\View;
class Encryption extends \Test\Files\Storage\Storage {
/**
+ * block size will always be 8192 for a PHP stream
+ * @see https://bugs.php.net/bug.php?id=21641
+ * @var integer
+ */
+ protected $headerSize = 8192;
+
+ /**
* @var Temporary
*/
private $sourceStorage;
@@ -407,18 +415,26 @@ class Encryption extends \Test\Files\Storage\Storage {
$this->encryptionManager, $util, $this->logger, $this->file, null, $this->keyStore, $this->update, $this->mountManager
]
)
+ ->setMethods(['readFirstBlock', 'parseRawHeader'])
->getMock();
+ $instance->expects($this->once())->method(('parseRawHeader'))
+ ->willReturn([Util::HEADER_ENCRYPTION_MODULE_KEY => 'OC_DEFAULT_MODULE']);
+
+ if ($strippedPathExists) {
+ $instance->expects($this->once())->method('readFirstBlock')
+ ->with($strippedPath)->willReturn('');
+ } else {
+ $instance->expects($this->once())->method('readFirstBlock')
+ ->with($path)->willReturn('');
+ }
+
$util->expects($this->once())->method('stripPartialFileExtension')
->with($path)->willReturn($strippedPath);
- $sourceStorage->expects($this->at(0))
+ $sourceStorage->expects($this->once())
->method('file_exists')
->with($strippedPath)
->willReturn($strippedPathExists);
- $sourceStorage->expects($this->at(1))
- ->method('file_exists')
- ->with($strippedPathExists ? $strippedPath : $path)
- ->willReturn(false);
$this->invokePrivate($instance, 'getHeader', [$path]);
}
@@ -432,4 +448,98 @@ class Encryption extends \Test\Files\Storage\Storage {
array('/foo/bar.txt.ocTransferId7437493.part', true, '/foo/bar.txt'),
);
}
+
+ /**
+ * test if getHeader adds the default module correctly to the header for
+ * legacy files
+ *
+ * @dataProvider dataTestGetHeaderAddLegacyModule
+ */
+ public function testGetHeaderAddLegacyModule($header, $isEncrypted, $expected) {
+
+ $sourceStorage = $this->getMockBuilder('\OC\Files\Storage\Storage')
+ ->disableOriginalConstructor()->getMock();
+
+ $util = $this->getMockBuilder('\OC\Encryption\Util')
+ ->setConstructorArgs([new View(), new \OC\User\Manager(), $this->groupManager, $this->config])
+ ->getMock();
+
+ $cache = $this->getMockBuilder('\OC\Files\Cache\Cache')
+ ->disableOriginalConstructor()->getMock();
+ $cache->expects($this->any())
+ ->method('get')
+ ->willReturnCallback(function($path) use ($isEncrypted) {return ['encrypted' => $isEncrypted, 'path' => $path];});
+
+ $instance = $this->getMockBuilder('\OC\Files\Storage\Wrapper\Encryption')
+ ->setConstructorArgs(
+ [
+ [
+ 'storage' => $sourceStorage,
+ 'root' => 'foo',
+ 'mountPoint' => '/',
+ 'mount' => $this->mount
+ ],
+ $this->encryptionManager, $util, $this->logger, $this->file, null, $this->keyStore, $this->update, $this->mountManager
+ ]
+ )
+ ->setMethods(['readFirstBlock', 'parseRawHeader', 'getCache'])
+ ->getMock();
+
+ $instance->expects($this->once())->method(('parseRawHeader'))->willReturn($header);
+ $instance->expects($this->any())->method('getCache')->willReturn($cache);
+
+ $result = $this->invokePrivate($instance, 'getHeader', ['test.txt']);
+ $this->assertSameSize($expected, $result);
+ foreach ($result as $key => $value) {
+ $this->assertArrayHasKey($key, $expected);
+ $this->assertSame($expected[$key], $value);
+ }
+ }
+
+ public function dataTestGetHeaderAddLegacyModule() {
+ return [
+ [['cipher' => 'AES-128'], true, ['cipher' => 'AES-128', Util::HEADER_ENCRYPTION_MODULE_KEY => 'OC_DEFAULT_MODULE']],
+ [[], true, [Util::HEADER_ENCRYPTION_MODULE_KEY => 'OC_DEFAULT_MODULE']],
+ [[], false, []],
+ ];
+ }
+
+ /**
+ * @dataProvider dataTestParseRawHeader
+ */
+ public function testParseRawHeader($rawHeader, $expected) {
+ $instance = new \OC\Files\Storage\Wrapper\Encryption(
+ [
+ 'storage' => $this->sourceStorage,
+ 'root' => 'foo',
+ 'mountPoint' => '/',
+ 'mount' => $this->mount
+ ],
+ $this->encryptionManager, $this->util, $this->logger, $this->file, null, $this->keyStore, $this->update, $this->mountManager
+
+ );
+
+ $result = $this->invokePrivate($instance, 'parseRawHeader', [$rawHeader]);
+ $this->assertSameSize($expected, $result);
+ foreach ($result as $key => $value) {
+ $this->assertArrayHasKey($key, $expected);
+ $this->assertSame($expected[$key], $value);
+ }
+ }
+
+ public function dataTestParseRawHeader() {
+ return [
+ [str_pad('HBEGIN:oc_encryption_module:0:HEND', $this->headerSize, '-', STR_PAD_RIGHT)
+ , [Util::HEADER_ENCRYPTION_MODULE_KEY => '0']],
+ [str_pad('HBEGIN:oc_encryption_module:0:custom_header:foo:HEND', $this->headerSize, '-', STR_PAD_RIGHT)
+ , ['custom_header' => 'foo', Util::HEADER_ENCRYPTION_MODULE_KEY => '0']],
+ [str_pad('HelloWorld', $this->headerSize, '-', STR_PAD_RIGHT), []],
+ ['', []],
+ [str_pad('HBEGIN:oc_encryption_module:0', $this->headerSize, '-', STR_PAD_RIGHT)
+ , []],
+ [str_pad('oc_encryption_module:0:HEND', $this->headerSize, '-', STR_PAD_RIGHT)
+ , []],
+ ];
+ }
+
}