Skip to content

Commit fd93aa8

Browse files
authored
Merge pull request #28373 from nextcloud/enh/noid/fix-encrypted-versions-detect-unencrypted
Fix encrypted version to 0 when finding unencrypted file
2 parents 25e4135 + 9c6bbfa commit fd93aa8

3 files changed

Lines changed: 69 additions & 2 deletions

File tree

apps/encryption/lib/Command/FixEncryptedVersion.php

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
namespace OCA\Encryption\Command;
2424

2525
use OC\Files\View;
26+
use OC\ServerNotAvailableException;
2627
use OCA\Encryption\Util;
2728
use OCP\Files\IRootFolder;
2829
use OCP\HintException;
@@ -53,6 +54,9 @@ class FixEncryptedVersion extends Command {
5354
/** @var View */
5455
private $view;
5556

57+
/** @var bool */
58+
private $supportLegacy;
59+
5660
public function __construct(
5761
IConfig $config,
5862
ILogger $logger,
@@ -67,6 +71,8 @@ public function __construct(
6771
$this->userManager = $userManager;
6872
$this->util = $util;
6973
$this->view = $view;
74+
$this->supportLegacy = false;
75+
7076
parent::__construct();
7177
}
7278

@@ -95,6 +101,7 @@ protected function configure(): void {
95101
*/
96102
protected function execute(InputInterface $input, OutputInterface $output): int {
97103
$skipSignatureCheck = $this->config->getSystemValue('encryption_skip_signature_check', false);
104+
$this->supportLegacy = $this->config->getSystemValueBool('encryption.legacy_format_support', false);
98105

99106
if ($skipSignatureCheck) {
100107
$output->writeln("<error>Repairing is not possible when \"encryption_skip_signature_check\" is set. Please disable this flag in the configuration.</error>\n");
@@ -187,6 +194,14 @@ private function verifyFileContent($path, OutputInterface $output, $ignoreCorrec
187194
\fclose($handle);
188195

189196
return true;
197+
} catch (ServerNotAvailableException $e) {
198+
// not a "bad signature" error and likely "legacy cipher" exception
199+
// this could mean that the file is maybe not encrypted but the encrypted version is set
200+
if (!$this->supportLegacy && $ignoreCorrectEncVersionCall === true) {
201+
$output->writeln("<info>Attempting to fix the path: \"$path\"</info>");
202+
return $this->correctEncryptedVersion($path, $output, true);
203+
}
204+
return false;
190205
} catch (HintException $e) {
191206
$this->logger->warning("Issue: " . $e->getMessage());
192207
//If allowOnce is set to false, this becomes recursive.
@@ -202,9 +217,10 @@ private function verifyFileContent($path, OutputInterface $output, $ignoreCorrec
202217
/**
203218
* @param string $path
204219
* @param OutputInterface $output
220+
* @param bool $includeZero whether to try zero version for unencrypted file
205221
* @return bool
206222
*/
207-
private function correctEncryptedVersion($path, OutputInterface $output): bool {
223+
private function correctEncryptedVersion($path, OutputInterface $output, bool $includeZero = false): bool {
208224
$fileInfo = $this->view->getFileInfo($path);
209225
if (!$fileInfo) {
210226
$output->writeln("<warning>File info not found for file: \"$path\"</warning>");
@@ -231,6 +247,17 @@ private function correctEncryptedVersion($path, OutputInterface $output): bool {
231247
// Save original encrypted version so we can restore it if decryption fails with all version
232248
$originalEncryptedVersion = $encryptedVersion;
233249
if ($encryptedVersion >= 0) {
250+
if ($includeZero) {
251+
// try with zero first
252+
$cacheInfo = ['encryptedVersion' => 0, 'encrypted' => 0];
253+
$cache->put($fileCache->getPath(), $cacheInfo);
254+
$output->writeln("<info>Set the encrypted version to 0 (unencrypted)</info>");
255+
if ($this->verifyFileContent($path, $output, false) === true) {
256+
$output->writeln("<info>Fixed the file: \"$path\" with version 0 (unencrypted)</info>");
257+
return true;
258+
}
259+
}
260+
234261
//test by decrementing the value till 1 and if nothing works try incrementing
235262
$encryptedVersion--;
236263
while ($encryptedVersion > 0) {

apps/encryption/tests/Command/FixEncryptedVersionTest.php

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,46 @@ public function testVersionIsRestoredToOriginalIfNoFixIsFound() {
244244
$this->assertEquals(15, $encryptedVersion);
245245
}
246246

247+
public function testRepairUnencryptedFileWhenVersionIsSet() {
248+
$this->util->expects($this->once())->method('isMasterKeyEnabled')
249+
->willReturn(true);
250+
251+
$view = new View("/" . $this->userId . "/files");
252+
253+
// create a file, it's encrypted and also the version is set in the database
254+
$view->touch('hello.txt');
255+
256+
$fileInfo1 = $view->getFileInfo('hello.txt');
257+
258+
$storage1 = $fileInfo1->getStorage();
259+
$cache1 = $storage1->getCache();
260+
$fileCache1 = $cache1->get($fileInfo1->getId());
261+
262+
// Now change the encrypted version
263+
$cacheInfo = ['encryptedVersion' => 1, 'encrypted' => 1];
264+
$cache1->put($fileCache1->getPath(), $cacheInfo);
265+
266+
$absPath = $view->getLocalFolder(''). '/hello.txt';
267+
268+
// create unencrypted file on disk, the version stays
269+
file_put_contents($absPath, 'hello contents');
270+
271+
$this->commandTester->execute([
272+
'user' => $this->userId
273+
]);
274+
275+
$output = $this->commandTester->getDisplay();
276+
277+
$this->assertStringContainsString("Verifying the content of file \"/$this->userId/files/hello.txt\"
278+
Attempting to fix the path: \"/$this->userId/files/hello.txt\"
279+
Set the encrypted version to 0 (unencrypted)
280+
The file \"/$this->userId/files/hello.txt\" is: OK
281+
Fixed the file: \"/$this->userId/files/hello.txt\" with version 0 (unencrypted)", $output);
282+
283+
// the file can be decrypted
284+
$this->assertEquals('hello contents', $view->file_get_contents('hello.txt'));
285+
}
286+
247287
/**
248288
* Test commands with a file path
249289
*/

lib/private/Files/Stream/Encryption.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ protected static function wrapSource($source, $context = [], $protocol = null, $
212212
} else {
213213
$wrapped = fopen($protocol . '://', $mode, false, $context);
214214
}
215-
} catch (\BadMethodCallException $e) {
215+
} catch (\Exception $e) {
216216
stream_wrapper_unregister($protocol);
217217
throw $e;
218218
}

0 commit comments

Comments
 (0)