aboutsummaryrefslogtreecommitdiffstats
path: root/apps/encryption/lib/Crypto/Encryption.php
diff options
context:
space:
mode:
Diffstat (limited to 'apps/encryption/lib/Crypto/Encryption.php')
-rw-r--r--apps/encryption/lib/Crypto/Encryption.php229
1 files changed, 92 insertions, 137 deletions
diff --git a/apps/encryption/lib/Crypto/Encryption.php b/apps/encryption/lib/Crypto/Encryption.php
index 58cefcbe087..6d388624e48 100644
--- a/apps/encryption/lib/Crypto/Encryption.php
+++ b/apps/encryption/lib/Crypto/Encryption.php
@@ -1,48 +1,23 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Bjoern Schiessle <bjoern@schiessle.org>
- * @author Björn Schießle <bjoern@schiessle.org>
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Clark Tomlinson <fallen013@gmail.com>
- * @author Jan-Christoph Borchardt <hey@jancborchardt.net>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Julius Härtl <jus@bitgrid.net>
- * @author Lukas Reschke <lukas@statuscode.ch>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Robin Appelman <robin@icewind.nl>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- * @author Thomas Müller <thomas.mueller@tmit.eu>
- * @author Valdnet <47037905+Valdnet@users.noreply.github.com>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OCA\Encryption\Crypto;
use OC\Encryption\Exceptions\DecryptionFailedException;
use OC\Files\Cache\Scanner;
use OC\Files\View;
+use OCA\Encryption\Exceptions\MultiKeyEncryptException;
use OCA\Encryption\Exceptions\PublicKeyMissingException;
use OCA\Encryption\KeyManager;
use OCA\Encryption\Session;
use OCA\Encryption\Util;
use OCP\Encryption\IEncryptionModule;
use OCP\IL10N;
-use OCP\ILogger;
+use Psr\Log\LoggerInterface;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
@@ -50,11 +25,6 @@ class Encryption implements IEncryptionModule {
public const ID = 'OC_DEFAULT_MODULE';
public const DISPLAY_NAME = 'Default encryption module';
- /**
- * @var Crypt
- */
- private $crypt;
-
/** @var string */
private $cipher;
@@ -64,8 +34,7 @@ class Encryption implements IEncryptionModule {
/** @var string */
private $user;
- /** @var array */
- private $owner;
+ private array $owner;
/** @var string */
private $fileKey;
@@ -73,78 +42,34 @@ class Encryption implements IEncryptionModule {
/** @var string */
private $writeCache;
- /** @var KeyManager */
- private $keyManager;
-
/** @var array */
private $accessList;
/** @var boolean */
private $isWriteOperation;
- /** @var Util */
- private $util;
-
- /** @var Session */
- private $session;
-
- /** @var ILogger */
- private $logger;
-
- /** @var IL10N */
- private $l;
-
- /** @var EncryptAll */
- private $encryptAll;
-
- /** @var bool */
- private $useMasterPassword;
-
- /** @var DecryptAll */
- private $decryptAll;
+ private bool $useMasterPassword;
- /** @var int unencrypted block size if block contains signature */
- private $unencryptedBlockSizeSigned = 6072;
-
- /** @var int unencrypted block size */
- private $unencryptedBlockSize = 6126;
+ private bool $useLegacyBase64Encoding = false;
/** @var int Current version of the file */
- private $version = 0;
+ private int $version = 0;
/** @var array remember encryption signature version */
private static $rememberVersion = [];
-
- /**
- *
- * @param Crypt $crypt
- * @param KeyManager $keyManager
- * @param Util $util
- * @param Session $session
- * @param EncryptAll $encryptAll
- * @param DecryptAll $decryptAll
- * @param ILogger $logger
- * @param IL10N $il10n
- */
- public function __construct(Crypt $crypt,
- KeyManager $keyManager,
- Util $util,
- Session $session,
- EncryptAll $encryptAll,
- DecryptAll $decryptAll,
- ILogger $logger,
- IL10N $il10n) {
- $this->crypt = $crypt;
- $this->keyManager = $keyManager;
- $this->util = $util;
- $this->session = $session;
- $this->encryptAll = $encryptAll;
- $this->decryptAll = $decryptAll;
- $this->logger = $logger;
- $this->l = $il10n;
+ public function __construct(
+ private Crypt $crypt,
+ private KeyManager $keyManager,
+ private Util $util,
+ private Session $session,
+ private EncryptAll $encryptAll,
+ private DecryptAll $decryptAll,
+ private LoggerInterface $logger,
+ private IL10N $l,
+ ) {
$this->owner = [];
- $this->useMasterPassword = $util->isMasterKeyEnabled();
+ $this->useMasterPassword = $this->util->isMasterKeyEnabled();
}
/**
@@ -175,8 +100,8 @@ class Encryption implements IEncryptionModule {
* @param array $accessList who has access to the file contains the key 'users' and 'public'
*
* @return array $header contain data as key-value pairs which should be
- * written to the header, in case of a write operation
- * or if no additional data is needed return a empty array
+ * written to the header, in case of a write operation
+ * or if no additional data is needed return a empty array
*/
public function begin($path, $user, $mode, array $header, array $accessList) {
$this->path = $this->getPathToRealFile($path);
@@ -184,6 +109,12 @@ class Encryption implements IEncryptionModule {
$this->user = $user;
$this->isWriteOperation = false;
$this->writeCache = '';
+ $this->useLegacyBase64Encoding = true;
+
+
+ if (isset($header['encoding'])) {
+ $this->useLegacyBase64Encoding = $header['encoding'] !== Crypt::BINARY_ENCODING_FORMAT;
+ }
if ($this->session->isReady() === false) {
// if the master key is enabled we can initialize encryption
@@ -193,15 +124,10 @@ class Encryption implements IEncryptionModule {
}
}
- if ($this->session->decryptAllModeActivated()) {
- $encryptedFileKey = $this->keyManager->getEncryptedFileKey($this->path);
- $shareKey = $this->keyManager->getShareKey($this->path, $this->session->getDecryptAllUid());
- $this->fileKey = $this->crypt->multiKeyDecrypt($encryptedFileKey,
- $shareKey,
- $this->session->getDecryptAllKey());
- } else {
- $this->fileKey = $this->keyManager->getFileKey($this->path, $this->user);
- }
+ /* If useLegacyFileKey is not specified in header, auto-detect, to be safe */
+ $useLegacyFileKey = (($header['useLegacyFileKey'] ?? '') == 'false' ? false : null);
+
+ $this->fileKey = $this->keyManager->getFileKey($this->path, $this->user, $useLegacyFileKey, $this->session->decryptAllModeActivated());
// always use the version from the original file, also part files
// need to have a correct version number if they get moved over to the
@@ -229,6 +155,7 @@ class Encryption implements IEncryptionModule {
if ($this->isWriteOperation) {
$this->cipher = $this->crypt->getCipher();
+ $this->useLegacyBase64Encoding = $this->crypt->useLegacyBase64Encoding();
} elseif (isset($header['cipher'])) {
$this->cipher = $header['cipher'];
} else {
@@ -237,7 +164,17 @@ class Encryption implements IEncryptionModule {
$this->cipher = $this->crypt->getLegacyCipher();
}
- return ['cipher' => $this->cipher, 'signed' => 'true'];
+ $result = [
+ 'cipher' => $this->cipher,
+ 'signed' => 'true',
+ 'useLegacyFileKey' => 'false',
+ ];
+
+ if ($this->useLegacyBase64Encoding !== true) {
+ $result['encoding'] = Crypt::BINARY_ENCODING_FORMAT;
+ }
+
+ return $result;
}
/**
@@ -246,14 +183,14 @@ class Encryption implements IEncryptionModule {
* buffer.
*
* @param string $path to the file
- * @param int $position
+ * @param string $position
* @return string remained data which should be written to the file in case
* of a write operation
* @throws PublicKeyMissingException
* @throws \Exception
- * @throws \OCA\Encryption\Exceptions\MultiKeyEncryptException
+ * @throws MultiKeyEncryptException
*/
- public function end($path, $position = 0) {
+ public function end($path, $position = '0') {
$result = '';
if ($this->isWriteOperation) {
// in case of a part file we remember the new signature versions
@@ -288,10 +225,18 @@ class Encryption implements IEncryptionModule {
}
$publicKeys = $this->keyManager->addSystemKeys($this->accessList, $publicKeys, $this->getOwner($path));
- $encryptedKeyfiles = $this->crypt->multiKeyEncrypt($this->fileKey, $publicKeys);
- $this->keyManager->setAllFileKeys($this->path, $encryptedKeyfiles);
+ $shareKeys = $this->crypt->multiKeyEncrypt($this->fileKey, $publicKeys);
+ if (!$this->keyManager->deleteLegacyFileKey($this->path)) {
+ $this->logger->warning(
+ 'Failed to delete legacy filekey for {path}',
+ ['app' => 'encryption', 'path' => $path]
+ );
+ }
+ foreach ($shareKeys as $uid => $keyFile) {
+ $this->keyManager->setShareKey($this->path, $uid, $keyFile);
+ }
}
- return $result;
+ return $result ?: '';
}
@@ -307,7 +252,6 @@ class Encryption implements IEncryptionModule {
// If extra data is left over from the last round, make sure it
// is integrated into the next block
if ($this->writeCache) {
-
// Concat writeCache to start of $data
$data = $this->writeCache . $data;
@@ -319,15 +263,13 @@ class Encryption implements IEncryptionModule {
$encrypted = '';
// While there still remains some data to be processed & written
while (strlen($data) > 0) {
-
// Remaining length for this iteration, not of the
// entire file (may be greater than 8192 bytes)
$remainingLength = strlen($data);
// If data remaining to be written is less than the
- // size of 1 6126 byte block
- if ($remainingLength < $this->unencryptedBlockSizeSigned) {
-
+ // size of 1 unencrypted block
+ if ($remainingLength < $this->getUnencryptedBlockSize(true)) {
// Set writeCache to contents of $data
// The writeCache will be carried over to the
// next write round, and added to the start of
@@ -341,16 +283,15 @@ class Encryption implements IEncryptionModule {
// Clear $data ready for next round
$data = '';
} else {
-
// Read the chunk from the start of $data
- $chunk = substr($data, 0, $this->unencryptedBlockSizeSigned);
+ $chunk = substr($data, 0, $this->getUnencryptedBlockSize(true));
- $encrypted .= $this->crypt->symmetricEncryptFileContent($chunk, $this->fileKey, $this->version + 1, $position);
+ $encrypted .= $this->crypt->symmetricEncryptFileContent($chunk, $this->fileKey, $this->version + 1, (string)$position);
// Remove the chunk we just processed from
// $data, leaving only unprocessed data in $data
// var, for handling on the next round
- $data = substr($data, $this->unencryptedBlockSizeSigned);
+ $data = substr($data, $this->getUnencryptedBlockSize(true));
}
}
@@ -374,7 +315,7 @@ class Encryption implements IEncryptionModule {
throw new DecryptionFailedException($msg, $hint);
}
- return $this->crypt->symmetricDecryptFileContent($data, $this->fileKey, $this->cipher, $this->version, $position);
+ return $this->crypt->symmetricDecryptFileContent($data, $this->fileKey, $this->cipher, $this->version, $position, !$this->useLegacyBase64Encoding);
}
/**
@@ -383,7 +324,7 @@ class Encryption implements IEncryptionModule {
* @param string $path path to the file which should be updated
* @param string $uid of the user who performs the operation
* @param array $accessList who has access to the file contains the key 'users' and 'public'
- * @return boolean
+ * @return bool
*/
public function update($path, $uid, array $accessList) {
if (empty($accessList)) {
@@ -391,10 +332,10 @@ class Encryption implements IEncryptionModule {
$this->keyManager->setVersion($path, self::$rememberVersion[$path], new View());
unset(self::$rememberVersion[$path]);
}
- return;
+ return false;
}
- $fileKey = $this->keyManager->getFileKey($path, $uid);
+ $fileKey = $this->keyManager->getFileKey($path, $uid, null);
if (!empty($fileKey)) {
$publicKeys = [];
@@ -412,11 +353,13 @@ class Encryption implements IEncryptionModule {
$publicKeys = $this->keyManager->addSystemKeys($accessList, $publicKeys, $this->getOwner($path));
- $encryptedFileKey = $this->crypt->multiKeyEncrypt($fileKey, $publicKeys);
+ $shareKeys = $this->crypt->multiKeyEncrypt($fileKey, $publicKeys);
$this->keyManager->deleteAllFileKeys($path);
- $this->keyManager->setAllFileKeys($path, $encryptedFileKey);
+ foreach ($shareKeys as $uid => $keyFile) {
+ $this->keyManager->setShareKey($path, $uid, $keyFile);
+ }
} else {
$this->logger->debug('no file key found, we assume that the file "{file}" is not encrypted',
['file' => $path, 'app' => 'encryption']);
@@ -462,15 +405,27 @@ class Encryption implements IEncryptionModule {
* get size of the unencrypted payload per block.
* Nextcloud read/write files with a block size of 8192 byte
*
+ * Encrypted blocks have a 22-byte IV and 2 bytes of padding, encrypted and
+ * signed blocks have also a 71-byte signature and 1 more byte of padding,
+ * resulting respectively in:
+ *
+ * 8192 - 22 - 2 = 8168 bytes in each unsigned unencrypted block
+ * 8192 - 22 - 2 - 71 - 1 = 8096 bytes in each signed unencrypted block
+ *
+ * Legacy base64 encoding then reduces the available size by a 3/4 factor:
+ *
+ * 8168 * (3/4) = 6126 bytes in each base64-encoded unsigned unencrypted block
+ * 8096 * (3/4) = 6072 bytes in each base64-encoded signed unencrypted block
+ *
* @param bool $signed
* @return int
*/
public function getUnencryptedBlockSize($signed = false) {
- if ($signed === false) {
- return $this->unencryptedBlockSize;
+ if ($this->useLegacyBase64Encoding) {
+ return $signed ? 6072 : 6126;
+ } else {
+ return $signed ? 8096 : 8168;
}
-
- return $this->unencryptedBlockSizeSigned;
}
/**
@@ -478,12 +433,12 @@ class Encryption implements IEncryptionModule {
* e.g. if all encryption keys exists
*
* @param string $path
- * @param string $uid user for whom we want to check if he can read the file
+ * @param string $uid user for whom we want to check if they can read the file
* @return bool
* @throws DecryptionFailedException
*/
public function isReadable($path, $uid) {
- $fileKey = $this->keyManager->getFileKey($path, $uid);
+ $fileKey = $this->keyManager->getFileKey($path, $uid, null);
if (empty($fileKey)) {
$owner = $this->util->getOwner($path);
if ($owner !== $uid) {
@@ -491,8 +446,8 @@ class Encryption implements IEncryptionModule {
// error message because in this case it means that the file was
// shared with the user at a point where the user didn't had a
// valid private/public key
- $msg = 'Encryption module "' . $this->getDisplayName() .
- '" is not able to read ' . $path;
+ $msg = 'Encryption module "' . $this->getDisplayName()
+ . '" is not able to read ' . $path;
$hint = $this->l->t('Cannot read this file, probably this is a shared file. Please ask the file owner to reshare the file with you.');
$this->logger->warning($msg);
throw new DecryptionFailedException($msg, $hint);