From edaa4363453351641356058a63495f85cda77e02 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Wed, 17 Jan 2024 17:47:01 +0100 Subject: [PATCH] add key location to info:file output Signed-off-by: Robin Appelman --- core/Command/Info/File.php | 12 +++++++ lib/private/Encryption/Keys/Storage.php | 30 +++------------- lib/private/Encryption/Util.php | 21 +++++++++++ tests/lib/Encryption/Keys/StorageTest.php | 34 +----------------- tests/lib/Encryption/UtilTest.php | 44 +++++++++++++++++++++++ 5 files changed, 83 insertions(+), 58 deletions(-) diff --git a/core/Command/Info/File.php b/core/Command/Info/File.php index d62feeb5a17..4afda280370 100644 --- a/core/Command/Info/File.php +++ b/core/Command/Info/File.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace OC\Core\Command\Info; use OC\Files\ObjectStore\ObjectStoreStorage; +use OC\Files\View; use OCA\Files_External\Config\ExternalMountPoint; use OCA\GroupFolders\Mount\GroupMountPoint; use OCP\Files\Folder; @@ -23,13 +24,16 @@ use Symfony\Component\Console\Output\OutputInterface; class File extends Command { private IL10N $l10n; + private View $rootView; public function __construct( IFactory $l10nFactory, private FileUtils $fileUtils, + private \OC\Encryption\Util $encryptionUtil ) { $this->l10n = $l10nFactory->get("core"); parent::__construct(); + $this->rootView = new View(); } protected function configure(): void { @@ -54,6 +58,14 @@ class File extends Command { $output->writeln(" mimetype: " . $node->getMimetype()); $output->writeln(" modified: " . (string)$this->l10n->l("datetime", $node->getMTime())); $output->writeln(" " . ($node->isEncrypted() ? "encrypted" : "not encrypted")); + if ($node->isEncrypted()) { + $keyPath = $this->encryptionUtil->getFileKeyDir('', $node->getPath()); + if ($this->rootView->file_exists($keyPath)) { + $output->writeln(" encryption key at: " . $keyPath); + } else { + $output->writeln(" encryption key not found should be located at: " . $keyPath); + } + } $output->writeln(" size: " . Util::humanFileSize($node->getSize())); $output->writeln(" etag: " . $node->getEtag()); if ($node instanceof Folder) { diff --git a/lib/private/Encryption/Keys/Storage.php b/lib/private/Encryption/Keys/Storage.php index e88c305eeec..cc7ed2f1f7b 100644 --- a/lib/private/Encryption/Keys/Storage.php +++ b/lib/private/Encryption/Keys/Storage.php @@ -98,14 +98,14 @@ class Storage implements IStorage { */ public function getFileKey($path, $keyId, $encryptionModuleId) { $realFile = $this->util->stripPartialFileExtension($path); - $keyDir = $this->getFileKeyDir($encryptionModuleId, $realFile); + $keyDir = $this->util->getFileKeyDir($encryptionModuleId, $realFile); $key = $this->getKey($keyDir . $keyId)['key']; if ($key === '' && $realFile !== $path) { // Check if the part file has keys and use them, if no normal keys // exist. This is required to fix copyBetweenStorage() when we // rename a .part file over storage borders. - $keyDir = $this->getFileKeyDir($encryptionModuleId, $path); + $keyDir = $this->util->getFileKeyDir($encryptionModuleId, $path); $key = $this->getKey($keyDir . $keyId)['key']; } @@ -135,7 +135,7 @@ class Storage implements IStorage { * @inheritdoc */ public function setFileKey($path, $keyId, $key, $encryptionModuleId) { - $keyDir = $this->getFileKeyDir($encryptionModuleId, $path); + $keyDir = $this->util->getFileKeyDir($encryptionModuleId, $path); return $this->setKey($keyDir . $keyId, [ 'key' => base64_encode($key), ]); @@ -177,7 +177,7 @@ class Storage implements IStorage { * @inheritdoc */ public function deleteFileKey($path, $keyId, $encryptionModuleId) { - $keyDir = $this->getFileKeyDir($encryptionModuleId, $path); + $keyDir = $this->util->getFileKeyDir($encryptionModuleId, $path); return !$this->view->file_exists($keyDir . $keyId) || $this->view->unlink($keyDir . $keyId); } @@ -185,7 +185,7 @@ class Storage implements IStorage { * @inheritdoc */ public function deleteAllFileKeys($path) { - $keyDir = $this->getFileKeyDir('', $path); + $keyDir = $this->util->getFileKeyDir('', $path); return !$this->view->file_exists($keyDir) || $this->view->deleteAll($keyDir); } @@ -355,26 +355,6 @@ class Storage implements IStorage { return false; } - /** - * get path to key folder for a given file - * - * @param string $encryptionModuleId - * @param string $path path to the file, relative to data/ - * @return string - */ - private function getFileKeyDir($encryptionModuleId, $path) { - [$owner, $filename] = $this->util->getUidAndFilename($path); - - // in case of system wide mount points the keys are stored directly in the data directory - if ($this->util->isSystemWideMountPoint($filename, $owner)) { - $keyPath = $this->root_dir . '/' . $this->keys_base_dir . $filename . '/'; - } else { - $keyPath = $this->root_dir . '/' . $owner . $this->keys_base_dir . $filename . '/'; - } - - return Filesystem::normalizePath($keyPath . $encryptionModuleId . '/', false); - } - /** * move keys if a file was renamed * diff --git a/lib/private/Encryption/Util.php b/lib/private/Encryption/Util.php index a828483265b..bd27d71c40e 100644 --- a/lib/private/Encryption/Util.php +++ b/lib/private/Encryption/Util.php @@ -385,4 +385,25 @@ class Util { return $result; } + + /** + * get path to key folder for a given file + * + * @param string $encryptionModuleId + * @param string $path path to the file, relative to data/ + * @return string + */ + public function getFileKeyDir(string $encryptionModuleId, string $path): string { + [$owner, $filename] = $this->getUidAndFilename($path); + $root = $this->getKeyStorageRoot(); + + // in case of system-wide mount points the keys are stored directly in the data directory + if ($this->isSystemWideMountPoint($filename, $owner)) { + $keyPath = $root . '/' . '/files_encryption/keys' . $filename . '/'; + } else { + $keyPath = $root . '/' . $owner . '/files_encryption/keys' . $filename . '/'; + } + + return Filesystem::normalizePath($keyPath . $encryptionModuleId . '/', false); + } } diff --git a/tests/lib/Encryption/Keys/StorageTest.php b/tests/lib/Encryption/Keys/StorageTest.php index a47edb3fdd6..eaef7fbaa54 100644 --- a/tests/lib/Encryption/Keys/StorageTest.php +++ b/tests/lib/Encryption/Keys/StorageTest.php @@ -53,6 +53,7 @@ class StorageTest extends TestCase { $this->util = $this->getMockBuilder('OC\Encryption\Util') ->disableOriginalConstructor() + ->setMethodsExcept(['getFileKeyDir']) ->getMock(); $this->view = $this->getMockBuilder(View::class) @@ -583,39 +584,6 @@ class StorageTest extends TestCase { $this->assertSame($expected, $args[0]); } - /** - * @dataProvider dataTestGetFileKeyDir - * - * @param bool $isSystemWideMountPoint - * @param string $storageRoot - * @param string $expected - */ - public function testGetFileKeyDir($isSystemWideMountPoint, $storageRoot, $expected) { - $path = '/user1/files/foo/bar.txt'; - $owner = 'user1'; - $relativePath = '/foo/bar.txt'; - - $this->invokePrivate($this->storage, 'root_dir', [$storageRoot]); - - $this->util->expects($this->once())->method('isSystemWideMountPoint') - ->willReturn($isSystemWideMountPoint); - $this->util->expects($this->once())->method('getUidAndFilename') - ->with($path)->willReturn([$owner, $relativePath]); - - $this->assertSame($expected, - $this->invokePrivate($this->storage, 'getFileKeyDir', ['OC_DEFAULT_MODULE', $path]) - ); - } - - public function dataTestGetFileKeyDir() { - return [ - [false, '', '/user1/files_encryption/keys/foo/bar.txt/OC_DEFAULT_MODULE/'], - [true, '', '/files_encryption/keys/foo/bar.txt/OC_DEFAULT_MODULE/'], - [false, 'newStorageRoot', '/newStorageRoot/user1/files_encryption/keys/foo/bar.txt/OC_DEFAULT_MODULE/'], - [true, 'newStorageRoot', '/newStorageRoot/files_encryption/keys/foo/bar.txt/OC_DEFAULT_MODULE/'], - ]; - } - /** * @dataProvider dataTestBackupUserKeys diff --git a/tests/lib/Encryption/UtilTest.php b/tests/lib/Encryption/UtilTest.php index e236ef54c26..7f5b05d6967 100644 --- a/tests/lib/Encryption/UtilTest.php +++ b/tests/lib/Encryption/UtilTest.php @@ -13,6 +13,7 @@ use Test\TestCase; class UtilTest extends TestCase { /** * block size will always be 8192 for a PHP stream + * * @see https://bugs.php.net/bug.php?id=21641 */ protected int $headerSize = 8192; @@ -205,4 +206,47 @@ class UtilTest extends TestCase { , []], ]; } + + /** + * @dataProvider dataTestGetFileKeyDir + * + * @param bool $isSystemWideMountPoint + * @param string $storageRoot + * @param string $expected + */ + public function testGetFileKeyDir($isSystemWideMountPoint, $storageRoot, $expected) { + $path = '/user1/files/foo/bar.txt'; + $owner = 'user1'; + $relativePath = '/foo/bar.txt'; + + $util = $this->getMockBuilder(Util::class) + ->onlyMethods(['isSystemWideMountPoint', 'getUidAndFilename', 'getKeyStorageRoot']) + ->setConstructorArgs([ + $this->view, + $this->userManager, + $this->groupManager, + $this->config + ]) + ->getMock(); + + $util->expects($this->once())->method('getKeyStorageRoot') + ->willReturn($storageRoot); + $util->expects($this->once())->method('isSystemWideMountPoint') + ->willReturn($isSystemWideMountPoint); + $util->expects($this->once())->method('getUidAndFilename') + ->with($path)->willReturn([$owner, $relativePath]); + + $this->assertSame($expected, + $util->getFileKeyDir('OC_DEFAULT_MODULE', $path) + ); + } + + public function dataTestGetFileKeyDir() { + return [ + [false, '', '/user1/files_encryption/keys/foo/bar.txt/OC_DEFAULT_MODULE/'], + [true, '', '/files_encryption/keys/foo/bar.txt/OC_DEFAULT_MODULE/'], + [false, 'newStorageRoot', '/newStorageRoot/user1/files_encryption/keys/foo/bar.txt/OC_DEFAULT_MODULE/'], + [true, 'newStorageRoot', '/newStorageRoot/files_encryption/keys/foo/bar.txt/OC_DEFAULT_MODULE/'], + ]; + } }