Merge pull request #17540 from owncloud/enc_detect_legacy_files2
make sure that we always detect legacy files correctly
This commit is contained in:
commit
f363fc2d4a
|
@ -127,35 +127,6 @@ class Util {
|
||||||
return $id;
|
return $id;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
* create header for encrypted file
|
||||||
*
|
*
|
||||||
|
|
|
@ -31,6 +31,7 @@ use OC\Encryption\Util;
|
||||||
use OC\Files\Filesystem;
|
use OC\Files\Filesystem;
|
||||||
use OC\Files\Mount\Manager;
|
use OC\Files\Mount\Manager;
|
||||||
use OC\Files\Storage\LocalTempFileTrait;
|
use OC\Files\Storage\LocalTempFileTrait;
|
||||||
|
use OCP\Encryption\Exceptions\GenericEncryptionException;
|
||||||
use OCP\Encryption\IFile;
|
use OCP\Encryption\IFile;
|
||||||
use OCP\Encryption\IManager;
|
use OCP\Encryption\IManager;
|
||||||
use OCP\Encryption\Keys\IStorage;
|
use OCP\Encryption\Keys\IStorage;
|
||||||
|
@ -174,9 +175,8 @@ class Encryption extends Wrapper {
|
||||||
public function file_get_contents($path) {
|
public function file_get_contents($path) {
|
||||||
|
|
||||||
$encryptionModule = $this->getEncryptionModule($path);
|
$encryptionModule = $this->getEncryptionModule($path);
|
||||||
$info = $this->getCache()->get($path);
|
|
||||||
|
|
||||||
if ($encryptionModule || $info['encrypted'] === true) {
|
if ($encryptionModule) {
|
||||||
$handle = $this->fopen($path, "r");
|
$handle = $this->fopen($path, "r");
|
||||||
if (!$handle) {
|
if (!$handle) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -338,14 +338,15 @@ class Encryption extends Wrapper {
|
||||||
* @param string $path
|
* @param string $path
|
||||||
* @param string $mode
|
* @param string $mode
|
||||||
* @return resource
|
* @return resource
|
||||||
|
* @throws GenericEncryptionException
|
||||||
|
* @throws ModuleDoesNotExistsException
|
||||||
*/
|
*/
|
||||||
public function fopen($path, $mode) {
|
public function fopen($path, $mode) {
|
||||||
|
|
||||||
$encryptionEnabled = $this->encryptionManager->isEnabled();
|
$encryptionEnabled = $this->encryptionManager->isEnabled();
|
||||||
$shouldEncrypt = false;
|
$shouldEncrypt = false;
|
||||||
$encryptionModule = null;
|
$encryptionModule = null;
|
||||||
$rawHeader = $this->getHeader($path);
|
$header = $this->getHeader($path);
|
||||||
$header = $this->util->readHeader($rawHeader);
|
|
||||||
$fullPath = $this->getFullPath($path);
|
$fullPath = $this->getFullPath($path);
|
||||||
$encryptionModuleId = $this->util->getEncryptionModuleId($header);
|
$encryptionModuleId = $this->util->getEncryptionModuleId($header);
|
||||||
|
|
||||||
|
@ -380,6 +381,10 @@ class Encryption extends Wrapper {
|
||||||
|| $mode === 'wb'
|
|| $mode === 'wb'
|
||||||
|| $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 ($encryptionEnabled) {
|
||||||
// if $encryptionModuleId is empty, the default module will be used
|
// if $encryptionModuleId is empty, the default module will be used
|
||||||
$encryptionModule = $this->encryptionManager->getEncryptionModule($encryptionModuleId);
|
$encryptionModule = $this->encryptionManager->getEncryptionModule($encryptionModuleId);
|
||||||
|
@ -398,6 +403,7 @@ class Encryption extends Wrapper {
|
||||||
// OC_DEFAULT_MODULE to read the file
|
// OC_DEFAULT_MODULE to read the file
|
||||||
$encryptionModule = $this->encryptionManager->getEncryptionModule('OC_DEFAULT_MODULE');
|
$encryptionModule = $this->encryptionManager->getEncryptionModule('OC_DEFAULT_MODULE');
|
||||||
$shouldEncrypt = true;
|
$shouldEncrypt = true;
|
||||||
|
$targetIsEncrypted = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (ModuleDoesNotExistsException $e) {
|
} catch (ModuleDoesNotExistsException $e) {
|
||||||
|
@ -416,7 +422,7 @@ class Encryption extends Wrapper {
|
||||||
$source = $this->storage->fopen($path, $mode);
|
$source = $this->storage->fopen($path, $mode);
|
||||||
$handle = \OC\Files\Stream\Encryption::wrap($source, $path, $fullPath, $header,
|
$handle = \OC\Files\Stream\Encryption::wrap($source, $path, $fullPath, $header,
|
||||||
$this->uid, $encryptionModule, $this->storage, $this, $this->util, $this->fileHelper, $mode,
|
$this->uid, $encryptionModule, $this->storage, $this, $this->util, $this->fileHelper, $mode,
|
||||||
$size, $unencryptedSize, strlen($rawHeader));
|
$size, $unencryptedSize, $this->getHeaderSize($path));
|
||||||
return $handle;
|
return $handle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -605,6 +611,72 @@ class Encryption extends Wrapper {
|
||||||
return Filesystem::normalizePath($this->mountPoint . '/' . $path);
|
return Filesystem::normalizePath($this->mountPoint . '/' . $path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
* read header from file
|
||||||
*
|
*
|
||||||
|
@ -612,21 +684,29 @@ class Encryption extends Wrapper {
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
protected function getHeader($path) {
|
protected function getHeader($path) {
|
||||||
$header = '';
|
|
||||||
$realFile = $this->util->stripPartialFileExtension($path);
|
$realFile = $this->util->stripPartialFileExtension($path);
|
||||||
if ($this->storage->file_exists($realFile)) {
|
if ($this->storage->file_exists($realFile)) {
|
||||||
$path = $realFile;
|
$path = $realFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->storage->file_exists($path)) {
|
$firstBlock = $this->readFirstBlock($path);
|
||||||
$handle = $this->storage->fopen($path, 'r');
|
$result = $this->parseRawHeader($firstBlock);
|
||||||
$firstBlock = fread($handle, $this->util->getHeaderSize());
|
|
||||||
fclose($handle);
|
// if the header doesn't contain a encryption module we check if it is a
|
||||||
if (substr($firstBlock, 0, strlen(Util::HEADER_START)) === Util::HEADER_START) {
|
// legacy file. If true, we add the default encryption module
|
||||||
$header = $firstBlock;
|
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 +719,7 @@ class Encryption extends Wrapper {
|
||||||
*/
|
*/
|
||||||
protected function getEncryptionModule($path) {
|
protected function getEncryptionModule($path) {
|
||||||
$encryptionModule = null;
|
$encryptionModule = null;
|
||||||
$rawHeader = $this->getHeader($path);
|
$header = $this->getHeader($path);
|
||||||
$header = $this->util->readHeader($rawHeader);
|
|
||||||
$encryptionModuleId = $this->util->getEncryptionModuleId($header);
|
$encryptionModuleId = $this->util->getEncryptionModuleId($header);
|
||||||
if (!empty($encryptionModuleId)) {
|
if (!empty($encryptionModuleId)) {
|
||||||
try {
|
try {
|
||||||
|
@ -675,4 +754,5 @@ class Encryption extends Wrapper {
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,19 +72,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
|
* @dataProvider providesHeaders
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -2,11 +2,19 @@
|
||||||
|
|
||||||
namespace Test\Files\Storage\Wrapper;
|
namespace Test\Files\Storage\Wrapper;
|
||||||
|
|
||||||
|
use OC\Encryption\Util;
|
||||||
use OC\Files\Storage\Temporary;
|
use OC\Files\Storage\Temporary;
|
||||||
use OC\Files\View;
|
use OC\Files\View;
|
||||||
|
|
||||||
class Encryption extends \Test\Files\Storage\Storage {
|
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
|
* @var Temporary
|
||||||
*/
|
*/
|
||||||
|
@ -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
|
$this->encryptionManager, $util, $this->logger, $this->file, null, $this->keyStore, $this->update, $this->mountManager
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
->setMethods(['readFirstBlock', 'parseRawHeader'])
|
||||||
->getMock();
|
->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')
|
$util->expects($this->once())->method('stripPartialFileExtension')
|
||||||
->with($path)->willReturn($strippedPath);
|
->with($path)->willReturn($strippedPath);
|
||||||
$sourceStorage->expects($this->at(0))
|
$sourceStorage->expects($this->once())
|
||||||
->method('file_exists')
|
->method('file_exists')
|
||||||
->with($strippedPath)
|
->with($strippedPath)
|
||||||
->willReturn($strippedPathExists);
|
->willReturn($strippedPathExists);
|
||||||
$sourceStorage->expects($this->at(1))
|
|
||||||
->method('file_exists')
|
|
||||||
->with($strippedPathExists ? $strippedPath : $path)
|
|
||||||
->willReturn(false);
|
|
||||||
|
|
||||||
$this->invokePrivate($instance, 'getHeader', [$path]);
|
$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'),
|
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)
|
||||||
|
, []],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue