Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/encryption/appinfo/info.xml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ Please read the documentation to know all implications before you decide to enab
<command>OCA\Encryption\Command\ScanLegacyFormat</command>
<command>OCA\Encryption\Command\FixEncryptedVersion</command>
<command>OCA\Encryption\Command\FixKeyLocation</command>
<command>OCA\Encryption\Command\LocateKey</command>
</commands>

<settings>
Expand Down
17 changes: 2 additions & 15 deletions apps/encryption/composer/autoload.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,8 @@
// autoload.php @generated by Composer

if (PHP_VERSION_ID < 50600) {
if (!headers_sent()) {
header('HTTP/1.1 500 Internal Server Error');
}
$err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
if (!ini_get('display_errors')) {
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
fwrite(STDERR, $err);
} elseif (!headers_sent()) {
echo $err;
}
}
trigger_error(
$err,
E_USER_ERROR
);
echo 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
exit(1);
}

require_once __DIR__ . '/composer/autoload_real.php';
Expand Down
37 changes: 14 additions & 23 deletions apps/encryption/composer/composer/ClassLoader.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,6 @@
*/
class ClassLoader
{
/** @var \Closure(string):void */
private static $includeFile;

/** @var ?string */
private $vendorDir;

Expand Down Expand Up @@ -109,7 +106,6 @@ class ClassLoader
public function __construct($vendorDir = null)
{
$this->vendorDir = $vendorDir;
self::initializeIncludeClosure();
}

/**
Expand Down Expand Up @@ -429,7 +425,7 @@ public function unregister()
public function loadClass($class)
{
if ($file = $this->findFile($class)) {
(self::$includeFile)($file);
includeFile($file);

return true;
}
Expand Down Expand Up @@ -559,23 +555,18 @@ private function findFileWithExtension($class, $ext)

return false;
}
}

private static function initializeIncludeClosure(): void
{
if (self::$includeFile !== null) {
return;
}

/**
* Scope isolated include.
*
* Prevents access to $this/self from included files.
*
* @param string $file
* @return void
*/
self::$includeFile = static function($file) {
include $file;
};
}
/**
* Scope isolated include.
*
* Prevents access to $this/self from included files.
*
* @param string $file
* @return void
* @private
*/
function includeFile($file)
{
include $file;
}
2 changes: 2 additions & 0 deletions apps/encryption/composer/composer/autoload_classmap.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
'OCA\\Encryption\\Command\\EnableMasterKey' => $baseDir . '/../lib/Command/EnableMasterKey.php',
'OCA\\Encryption\\Command\\FixEncryptedVersion' => $baseDir . '/../lib/Command/FixEncryptedVersion.php',
'OCA\\Encryption\\Command\\FixKeyLocation' => $baseDir . '/../lib/Command/FixKeyLocation.php',
'OCA\\Encryption\\Command\\LocateKey' => $baseDir . '/../lib/Command/LocateKey.php',
'OCA\\Encryption\\Command\\RecoverUser' => $baseDir . '/../lib/Command/RecoverUser.php',
'OCA\\Encryption\\Command\\ScanLegacyFormat' => $baseDir . '/../lib/Command/ScanLegacyFormat.php',
'OCA\\Encryption\\Controller\\RecoveryController' => $baseDir . '/../lib/Controller/RecoveryController.php',
Expand All @@ -31,6 +32,7 @@
'OCA\\Encryption\\KeyManager' => $baseDir . '/../lib/KeyManager.php',
'OCA\\Encryption\\Migration\\SetMasterKeyStatus' => $baseDir . '/../lib/Migration/SetMasterKeyStatus.php',
'OCA\\Encryption\\Recovery' => $baseDir . '/../lib/Recovery.php',
'OCA\\Encryption\\Repair' => $baseDir . '/../lib/Repair.php',
'OCA\\Encryption\\Session' => $baseDir . '/../lib/Session.php',
'OCA\\Encryption\\Settings\\Admin' => $baseDir . '/../lib/Settings/Admin.php',
'OCA\\Encryption\\Settings\\Personal' => $baseDir . '/../lib/Settings/Personal.php',
Expand Down
2 changes: 2 additions & 0 deletions apps/encryption/composer/composer/autoload_static.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class ComposerStaticInitEncryption
'OCA\\Encryption\\Command\\EnableMasterKey' => __DIR__ . '/..' . '/../lib/Command/EnableMasterKey.php',
'OCA\\Encryption\\Command\\FixEncryptedVersion' => __DIR__ . '/..' . '/../lib/Command/FixEncryptedVersion.php',
'OCA\\Encryption\\Command\\FixKeyLocation' => __DIR__ . '/..' . '/../lib/Command/FixKeyLocation.php',
'OCA\\Encryption\\Command\\LocateKey' => __DIR__ . '/..' . '/../lib/Command/LocateKey.php',
'OCA\\Encryption\\Command\\RecoverUser' => __DIR__ . '/..' . '/../lib/Command/RecoverUser.php',
'OCA\\Encryption\\Command\\ScanLegacyFormat' => __DIR__ . '/..' . '/../lib/Command/ScanLegacyFormat.php',
'OCA\\Encryption\\Controller\\RecoveryController' => __DIR__ . '/..' . '/../lib/Controller/RecoveryController.php',
Expand All @@ -46,6 +47,7 @@ class ComposerStaticInitEncryption
'OCA\\Encryption\\KeyManager' => __DIR__ . '/..' . '/../lib/KeyManager.php',
'OCA\\Encryption\\Migration\\SetMasterKeyStatus' => __DIR__ . '/..' . '/../lib/Migration/SetMasterKeyStatus.php',
'OCA\\Encryption\\Recovery' => __DIR__ . '/..' . '/../lib/Recovery.php',
'OCA\\Encryption\\Repair' => __DIR__ . '/..' . '/../lib/Repair.php',
'OCA\\Encryption\\Session' => __DIR__ . '/..' . '/../lib/Session.php',
'OCA\\Encryption\\Settings\\Admin' => __DIR__ . '/..' . '/../lib/Settings/Admin.php',
'OCA\\Encryption\\Settings\\Personal' => __DIR__ . '/..' . '/../lib/Settings/Personal.php',
Expand Down
158 changes: 111 additions & 47 deletions apps/encryption/lib/Command/FixKeyLocation.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,14 @@
namespace OCA\Encryption\Command;

use OC\Encryption\Util;
use OC\Files\Storage\Wrapper\Encryption;
use OC\Files\View;
use OCA\Encryption\Repair;
use OCP\Files\Config\ICachedMountInfo;
use OCP\Files\Config\IUserMountCache;
use OCP\Files\Folder;
use OCP\Files\File;
use OCP\Files\IRootFolder;
use OCP\Files\Node;
use OCP\IUser;
use OCP\IUserManager;
use Symfony\Component\Console\Command\Command;
Expand All @@ -42,18 +43,21 @@
class FixKeyLocation extends Command {
private IUserManager $userManager;
private IUserMountCache $userMountCache;
private Util $encryptionUtil;
private IRootFolder $rootFolder;
private string $keyRootDirectory;
private View $rootView;

public function __construct(IUserManager $userManager, IUserMountCache $userMountCache, Util $encryptionUtil, IRootFolder $rootFolder) {
private Repair $repair;

public function __construct(
IUserManager $userManager,
IUserMountCache $userMountCache,
IRootFolder $rootFolder,
Repair $repair
) {
$this->userManager = $userManager;
$this->userMountCache = $userMountCache;
$this->encryptionUtil = $encryptionUtil;
$this->rootFolder = $rootFolder;
$this->keyRootDirectory = rtrim($this->encryptionUtil->getKeyStorageRoot(), '/');
$this->rootView = new View();
$this->repair = $repair;

parent::__construct();
}
Expand Down Expand Up @@ -88,20 +92,71 @@ protected function execute(InputInterface $input, OutputInterface $output): int
continue;
}

$files = $this->getAllFiles($mountRootFolder);
$files = $this->getAllEncryptedFiles($mountRootFolder);
foreach ($files as $file) {
if ($this->isKeyStoredForUser($user, $file)) {
/** @var File $file */
$hasSystemKey = $this->repair->hasSystemKey($file);
$hasUserKey = $this->repair->hasUserKey($user, $file);
if (!$hasSystemKey && $hasUserKey) {
// key was stored incorrectly as user key, migrate

if ($dryRun) {
$output->writeln("<info>" . $file->getPath() . "</info> needs migration");
} else {
$output->write("Migrating key for <info>" . $file->getPath() . "</info> ");
if ($this->copyKeyAndValidate($user, $file)) {
if ($this->copyUserKeyToSystemAndValidate($user, $file)) {
$output->writeln("<info>✓</info>");
} else {
$output->writeln("<fg=red>❌</>");
$output->writeln(" Failed to validate key for <error>" . $file->getPath() . "</error>, key will not be migrated");
}
}
} elseif (!$hasSystemKey && !$hasUserKey) {
// no matching key, probably from a broken cross-storage move

$shouldBeEncrypted = $file->getStorage()->instanceOfStorage(Encryption::class);
$isActuallyEncrypted = $this->repair->isDataEncrypted($file);
if ($isActuallyEncrypted) {
if ($dryRun) {
if ($shouldBeEncrypted) {
$output->write("<info>" . $file->getPath() . "</info> needs migration");
} else {
$output->write("<info>" . $file->getPath() . "</info> needs decryption");
}
$foundKey = $this->findUserKeyForSystemFileByName($user, $file);
if ($foundKey) {
$output->writeln(", valid key found at <info>" . $foundKey . "</info>");
} else {
$output->writeln(" <error>❌ No key found</error>");
}
} else {
if ($shouldBeEncrypted) {
$output->write("<info>Migrating key for " . $file->getPath() . "</info>");
} else {
$output->write("<info>Decrypting " . $file->getPath() . "</info>");
}
$foundKey = $this->findUserKeyForSystemFileByName($user, $file);
if ($foundKey) {
if ($shouldBeEncrypted) {
$systemKeyPath = $this->repair->getSystemKeyPath($file);
$this->rootView->copy($foundKey, $systemKeyPath);
$output->writeln(" Migrated key from <info>" . $foundKey . "</info>");
} else {
$this->repair->decryptWithSystemKey($file, $foundKey);
$output->writeln(" Decrypted with key from <info>" . $foundKey . "</info>");
}
} else {
$output->writeln(" <error>❌ No key found</error>");
}
}
} else {
if ($dryRun) {
$output->writeln("<info>" . $file->getPath() . " needs to be marked as not encrypted</info>");
} else {
$this->repair->markAsUnEncrypted($file);
$output->writeln("<info>" . $file->getPath() . " marked as not encrypted</info>");
}
}
}
}
}
Expand All @@ -114,73 +169,82 @@ protected function execute(InputInterface $input, OutputInterface $output): int
* @return ICachedMountInfo[]
*/
private function getSystemMountsForUser(IUser $user): array {
return array_filter($this->userMountCache->getMountsForUser($user), function(ICachedMountInfo $mount) use ($user) {
$mountPoint = substr($mount->getMountPoint(), strlen($user->getUID() . '/'));
return $this->encryptionUtil->isSystemWideMountPoint($mountPoint, $user->getUID());
return array_filter($this->userMountCache->getMountsForUser($user), function (ICachedMountInfo $mount){
return $this->repair->needsSystemKey($mount->getMountPoint());
});
}

/**
* Get all files in a folder which are marked as encrypted
*
* @param Folder $folder
* @return \Generator<File>
*/
private function getAllFiles(Folder $folder) {
private function getAllEncryptedFiles(Folder $folder) {
foreach ($folder->getDirectoryListing() as $child) {
if ($child instanceof Folder) {
yield from $this->getAllFiles($child);
yield from $this->getAllEncryptedFiles($child);
} else {
yield $child;
if (substr($child->getName(), -4) !== '.bak' && $child->isEncrypted()) {
yield $child;
}
}
}
}

/**
* Check if the key for a file is stored in the user's keystore and not the system one
*
* @param IUser $user
* @param Node $node
* @return bool
*/
private function isKeyStoredForUser(IUser $user, Node $node): bool {
$path = trim(substr($node->getPath(), strlen($user->getUID()) + 1), '/');
$systemKeyPath = $this->keyRootDirectory . '/files_encryption/keys/' . $path . '/';
$userKeyPath = $this->keyRootDirectory . '/' . $user->getUID() . '/files_encryption/keys/' . $path . '/';

// this uses View instead of the RootFolder because the keys might not be in the cache
$systemKeyExists = $this->rootView->file_exists($systemKeyPath);
$userKeyExists = $this->rootView->file_exists($userKeyPath);
return $userKeyExists && !$systemKeyExists;
}

/**
* Check that the user key stored for a file can decrypt the file
*
* @param IUser $user
* @param File $node
* @return bool
*/
private function copyKeyAndValidate(IUser $user, File $node): bool {
private function copyUserKeyToSystemAndValidate(IUser $user, File $node): bool {
$path = trim(substr($node->getPath(), strlen($user->getUID()) + 1), '/');
$systemKeyPath = $this->keyRootDirectory . '/files_encryption/keys/' . $path . '/';
$userKeyPath = $this->keyRootDirectory . '/' . $user->getUID() . '/files_encryption/keys/' . $path . '/';
$systemKeyPath = $this->repair->getSystemKeyRoot() . '/' . $path . '/';
$userKeyPath = $this->repair->getUserKeyRoot($user) . '/' . $path . '/';

$this->rootView->copy($userKeyPath, $systemKeyPath);
try {
// check that the copied key is valid
$fh = $node->fopen('r');
// read a single chunk
$data = fread($fh, 8192);
if ($data === false) {
throw new \Exception("Read failed");
}

if ($this->repair->tryReadFile($node)) {
// cleanup wrong key location
$this->rootView->rmdir($userKeyPath);
return true;
} catch (\Exception $e) {
} else {
// remove the copied key if we know it's invalid
$this->rootView->rmdir($systemKeyPath);
return false;
}
}

/**
* Attempt to find a key (stored for user) for a file (that needs a system key) even when it's not stored in the expected location
*
* @param File $node
* @return string
*/
public function findUserKeyForSystemFileByName(IUser $user, File $node): ?string {
$userKeyPath = $this->repair->getUserKeyRoot($user);
$possibleKeys = $this->findKeysByFileName($userKeyPath, $node->getName());
foreach ($possibleKeys as $possibleKey) {
if ($this->repair->testSystemKey($possibleKey, $node)) {
return $possibleKey;
}
}
return null;
}

/**
* Attempt to find a key for a file even when it's not stored in the expected location
*
* @param string $basePath
* @param string $name
* @return \Iterator<mixed, string>
*/
public function findKeysByFileName(string $basePath, string $name) {
$allKeys = $this->repair->findAllKeysInDirectory($basePath);
return new \CallbackFilterIterator($allKeys, function($path) use ($name) {
$parts = explode('/', $path);
return array_pop($parts) === $name;
});
}
}
Loading