Skip to content

Commit

Permalink
fix: SSE files being mistaken for E2EE
Browse files Browse the repository at this point in the history
Signed-off-by: Alyx <[email protected]>
Signed-off-by: Louis Chemineau <[email protected]>
  • Loading branch information
artonge committed Mar 14, 2024
1 parent f3d39a3 commit 20bdffe
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 21 deletions.
69 changes: 49 additions & 20 deletions lib/E2EEnabledPathCache.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@
namespace OCA\EndToEndEncryption;

use OCP\Cache\CappedMemoryCache;
use OCP\Files\Cache\ICache;
use OCP\Files\Folder;
use OCP\Files\IHomeStorage;
use OCP\Files\InvalidPathException;
use OCP\Files\Node;
use OCP\Files\NotFoundException;
use OCP\Files\Storage\IStorage;

class E2EEnabledPathCache {
/**
Expand All @@ -45,53 +45,82 @@ public function __construct() {
}

/**
* Checks if the path is an E2E folder or inside an E2E folder
* Checks if the node is an E2EE folder or inside an E2EE folder.
*
* @return bool true, if the node is valid and E2EE, false otherwise
*/
public function isE2EEnabledPath(Node $node): bool {
if ($node->isEncrypted()) {
// only checking for $node->isEncrypted() will lead to false-positives for SSE files,
// as they have the encryption flag set but are not E2E, so we need to check the folder's flag,
// as folders having the encryption flag set are always E2E
if ($node instanceof Folder && $node->isEncrypted()) {
return true;
}
$storage = $node->getStorage();
$cache = $storage->getCache();
return $this->getEncryptedStates($cache, $node, $storage);

try {
$storage = $node->getStorage();
} catch (NotFoundException $e) {
return false;
}
// if not home storage, fallback to EncryptionManager
if (!$storage->instanceOfStorage(IHomeStorage::class)) {
return EncryptionManager::isEncryptedFile($node);
}

// walk path backwards while caching each node's state
return $this->getEncryptedStates((string) $storage->getCache()->getNumericStorageId(), $node);
}

/**
* Get the encryption state for the path
* Determines a node's E2E encryption state by walking up the tree. Caches each node's state on the way.
*
* @param string $storageId the node's storage id, used for caching
* @param Node $node the node to check
*
* @return bool true, if the node is valid and E2EE, false otherwise
*/
protected function getEncryptedStates(ICache $cache, Node $node, IStorage $storage): bool {
if (!$storage->instanceOfStorage(IHomeStorage::class)) {
protected function getEncryptedStates(string $storageId, Node $node): bool {
try {
$nodeId = $node->getId();
} catch (InvalidPathException|NotFoundException $e) {
return false;
}

$storageId = (string)$cache->getNumericStorageId();
// initialize array for storage id if necessary
if (!isset($this->perStorageEncryptedStateCache[$storageId])) {
$this->perStorageEncryptedStateCache[$storageId] = [];
}
if (isset($this->perStorageEncryptedStateCache[$storageId][$node->getId()])) {
return $this->perStorageEncryptedStateCache[$storageId][$node->getId()];
// return cached state if available
elseif (isset($this->perStorageEncryptedStateCache[$storageId][$nodeId])) {
return $this->perStorageEncryptedStateCache[$storageId][$nodeId];
}

if ($node->getPath() === '/') {
// root is never encrypted
$this->perStorageEncryptedStateCache[$storageId][$node->getId()] = false;
$this->perStorageEncryptedStateCache[$storageId][$nodeId] = false;
return false;
}

if ($node->isEncrypted()) {
// no need to go further down in the tree
$this->perStorageEncryptedStateCache[$storageId][$node->getId()] = true;
// checking for folder for the same reason as above
if ($node instanceof Folder && $node->isEncrypted()) {
// no need to go further up in the tree
$this->perStorageEncryptedStateCache[$storageId][$nodeId] = true;
return true;
}

try {
$parentNode = $node->getParent();
} catch (NotFoundException $e) {
$this->perStorageEncryptedStateCache[$storageId][$node->getId()] = false;
// node not encrypted and no parent that could be E2EE, so node is not E2EE
$this->perStorageEncryptedStateCache[$storageId][$nodeId] = false;
return false;
}
$encrypted = $this->getEncryptedStates($cache, $parentNode, $storage);
$this->perStorageEncryptedStateCache[$storageId][$node->getId()] = $encrypted;

// check parent's state
$encrypted = $this->getEncryptedStates($storageId, $parentNode);

// if any parent is E2EE, this node is E2EE as well
$this->perStorageEncryptedStateCache[$storageId][$nodeId] = $encrypted;
return $encrypted;
}
}
8 changes: 7 additions & 1 deletion lib/EncryptionManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,13 @@ public function removeEncryptionFlag(int $id): void {
/**
* Check if a file is in a folder marked as encrypted
*/
public function isEncryptedFile(Node $node): bool {
public static function isEncryptedFile(Node $node): bool {
// traverse up if node is not a folder to prevent false positives for SSE files
// (see E2EEnabledPathCache#isE2EEnabledPath)
if (!($node instanceof Folder)) {
$node = $node->getParent();
}

do {
if ($node->isEncrypted()) {
return true;
Expand Down

0 comments on commit 20bdffe

Please sign in to comment.