Merge pull request #21557 from owncloud/use-hmac-over-encryption
Add integrity protection using Encrypt-Then-MAC to default encryption module
This commit is contained in:
commit
53d57bffed
18 changed files with 499 additions and 137 deletions
|
@ -131,7 +131,8 @@ class Application extends \OCP\AppFramework\App {
|
|||
$server = $c->getServer();
|
||||
return new Crypt($server->getLogger(),
|
||||
$server->getUserSession(),
|
||||
$server->getConfig());
|
||||
$server->getConfig(),
|
||||
$server->getL10N($c->getAppName()));
|
||||
});
|
||||
|
||||
$container->registerService('Session',
|
||||
|
|
|
@ -25,11 +25,12 @@ use Symfony\Component\Console\Helper\QuestionHelper;
|
|||
$userManager = OC::$server->getUserManager();
|
||||
$view = new \OC\Files\View();
|
||||
$config = \OC::$server->getConfig();
|
||||
$l = \OC::$server->getL10N('encryption');
|
||||
$userSession = \OC::$server->getUserSession();
|
||||
$connection = \OC::$server->getDatabaseConnection();
|
||||
$logger = \OC::$server->getLogger();
|
||||
$questionHelper = new QuestionHelper();
|
||||
$crypt = new \OCA\Encryption\Crypto\Crypt($logger, $userSession, $config);
|
||||
$crypt = new \OCA\Encryption\Crypto\Crypt($logger, $userSession, $config, $l);
|
||||
$util = new \OCA\Encryption\Util($view, $crypt, $logger, $userSession, $config, $userManager);
|
||||
|
||||
$application->add(new MigrateKeys($userManager, $view, $connection, $config, $logger));
|
||||
|
|
|
@ -29,16 +29,32 @@ namespace OCA\Encryption\Crypto;
|
|||
|
||||
use OC\Encryption\Exceptions\DecryptionFailedException;
|
||||
use OC\Encryption\Exceptions\EncryptionFailedException;
|
||||
use OC\HintException;
|
||||
use OCA\Encryption\Exceptions\MultiKeyDecryptException;
|
||||
use OCA\Encryption\Exceptions\MultiKeyEncryptException;
|
||||
use OCP\Encryption\Exceptions\GenericEncryptionException;
|
||||
use OCP\IConfig;
|
||||
use OCP\IL10N;
|
||||
use OCP\ILogger;
|
||||
use OCP\IUserSession;
|
||||
|
||||
/**
|
||||
* Class Crypt provides the encryption implementation of the default ownCloud
|
||||
* encryption module. As default AES-256-CTR is used, it does however offer support
|
||||
* for the following modes:
|
||||
*
|
||||
* - AES-256-CTR
|
||||
* - AES-128-CTR
|
||||
* - AES-256-CFB
|
||||
* - AES-128-CFB
|
||||
*
|
||||
* For integrity protection Encrypt-Then-MAC using HMAC-SHA256 is used.
|
||||
*
|
||||
* @package OCA\Encryption\Crypto
|
||||
*/
|
||||
class Crypt {
|
||||
|
||||
const DEFAULT_CIPHER = 'AES-256-CFB';
|
||||
const DEFAULT_CIPHER = 'AES-256-CTR';
|
||||
// default cipher from old ownCloud versions
|
||||
const LEGACY_CIPHER = 'AES-128-CFB';
|
||||
|
||||
|
@ -48,33 +64,41 @@ class Crypt {
|
|||
|
||||
const HEADER_START = 'HBEGIN';
|
||||
const HEADER_END = 'HEND';
|
||||
/**
|
||||
* @var ILogger
|
||||
*/
|
||||
|
||||
/** @var ILogger */
|
||||
private $logger;
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
|
||||
/** @var string */
|
||||
private $user;
|
||||
/**
|
||||
* @var IConfig
|
||||
*/
|
||||
|
||||
/** @var IConfig */
|
||||
private $config;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
/** @var array */
|
||||
private $supportedKeyFormats;
|
||||
|
||||
/** @var IL10N */
|
||||
private $l;
|
||||
|
||||
/** @var array */
|
||||
private $supportedCiphersAndKeySize = [
|
||||
'AES-256-CTR' => 32,
|
||||
'AES-128-CTR' => 16,
|
||||
'AES-256-CFB' => 32,
|
||||
'AES-128-CFB' => 16,
|
||||
];
|
||||
|
||||
/**
|
||||
* @param ILogger $logger
|
||||
* @param IUserSession $userSession
|
||||
* @param IConfig $config
|
||||
* @param IL10N $l
|
||||
*/
|
||||
public function __construct(ILogger $logger, IUserSession $userSession, IConfig $config) {
|
||||
public function __construct(ILogger $logger, IUserSession $userSession, IConfig $config, IL10N $l) {
|
||||
$this->logger = $logger;
|
||||
$this->user = $userSession && $userSession->isLoggedIn() ? $userSession->getUser()->getUID() : '"no user given"';
|
||||
$this->config = $config;
|
||||
$this->l = $l;
|
||||
$this->supportedKeyFormats = ['hash', 'password'];
|
||||
}
|
||||
|
||||
|
@ -145,10 +169,12 @@ class Crypt {
|
|||
/**
|
||||
* @param string $plainContent
|
||||
* @param string $passPhrase
|
||||
* @param int $version
|
||||
* @param int $position
|
||||
* @return false|string
|
||||
* @throws GenericEncryptionException
|
||||
* @throws EncryptionFailedException
|
||||
*/
|
||||
public function symmetricEncryptFileContent($plainContent, $passPhrase) {
|
||||
public function symmetricEncryptFileContent($plainContent, $passPhrase, $version, $position) {
|
||||
|
||||
if (!$plainContent) {
|
||||
$this->logger->error('Encryption Library, symmetrical encryption failed no content given',
|
||||
|
@ -162,8 +188,13 @@ class Crypt {
|
|||
$iv,
|
||||
$passPhrase,
|
||||
$this->getCipher());
|
||||
|
||||
// Create a signature based on the key as well as the current version
|
||||
$sig = $this->createSignature($encryptedContent, $passPhrase.$version.$position);
|
||||
|
||||
// combine content to encrypt the IV identifier and actual IV
|
||||
$catFile = $this->concatIV($encryptedContent, $iv);
|
||||
$catFile = $this->concatSig($catFile, $sig);
|
||||
$padded = $this->addPadding($catFile);
|
||||
|
||||
return $padded;
|
||||
|
@ -225,8 +256,13 @@ class Crypt {
|
|||
*/
|
||||
public function getCipher() {
|
||||
$cipher = $this->config->getSystemValue('cipher', self::DEFAULT_CIPHER);
|
||||
if ($cipher !== 'AES-256-CFB' && $cipher !== 'AES-128-CFB') {
|
||||
$this->logger->warning('Wrong cipher defined in config.php only AES-128-CFB and AES-256-CFB are supported. Fall back' . self::DEFAULT_CIPHER,
|
||||
if (!isset($this->supportedCiphersAndKeySize[$cipher])) {
|
||||
$this->logger->warning(
|
||||
sprintf(
|
||||
'Unsupported cipher (%s) defined in config.php supported. Falling back to %s',
|
||||
$cipher,
|
||||
self::DEFAULT_CIPHER
|
||||
),
|
||||
['app' => 'encryption']);
|
||||
$cipher = self::DEFAULT_CIPHER;
|
||||
}
|
||||
|
@ -237,19 +273,20 @@ class Crypt {
|
|||
/**
|
||||
* get key size depending on the cipher
|
||||
*
|
||||
* @param string $cipher supported ('AES-256-CFB' and 'AES-128-CFB')
|
||||
* @param string $cipher
|
||||
* @return int
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
protected function getKeySize($cipher) {
|
||||
if ($cipher === 'AES-256-CFB') {
|
||||
return 32;
|
||||
} else if ($cipher === 'AES-128-CFB') {
|
||||
return 16;
|
||||
if(isset($this->supportedCiphersAndKeySize[$cipher])) {
|
||||
return $this->supportedCiphersAndKeySize[$cipher];
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException(
|
||||
'Wrong cipher defined only AES-128-CFB and AES-256-CFB are supported.'
|
||||
sprintf(
|
||||
'Unsupported cipher (%s) defined.',
|
||||
$cipher
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -272,11 +309,24 @@ class Crypt {
|
|||
}
|
||||
|
||||
/**
|
||||
* @param string $encryptedContent
|
||||
* @param string $signature
|
||||
* @return string
|
||||
*/
|
||||
private function concatSig($encryptedContent, $signature) {
|
||||
return $encryptedContent . '00sig00' . $signature;
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: This is _NOT_ a padding used for encryption purposes. It is solely
|
||||
* used to achieve the PHP stream size. It has _NOTHING_ to do with the
|
||||
* encrypted content and is not used in any crypto primitive.
|
||||
*
|
||||
* @param string $data
|
||||
* @return string
|
||||
*/
|
||||
private function addPadding($data) {
|
||||
return $data . 'xx';
|
||||
return $data . 'xxx';
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -318,7 +368,9 @@ class Crypt {
|
|||
$hash = $this->generatePasswordHash($password, $cipher, $uid);
|
||||
$encryptedKey = $this->symmetricEncryptFileContent(
|
||||
$privateKey,
|
||||
$hash
|
||||
$hash,
|
||||
0,
|
||||
0
|
||||
);
|
||||
|
||||
return $encryptedKey;
|
||||
|
@ -357,9 +409,12 @@ class Crypt {
|
|||
self::HEADER_END) + strlen(self::HEADER_END));
|
||||
}
|
||||
|
||||
$plainKey = $this->symmetricDecryptFileContent($privateKey,
|
||||
$plainKey = $this->symmetricDecryptFileContent(
|
||||
$privateKey,
|
||||
$password,
|
||||
$cipher);
|
||||
$cipher,
|
||||
0
|
||||
);
|
||||
|
||||
if ($this->isValidPrivateKey($plainKey) === false) {
|
||||
return false;
|
||||
|
@ -390,14 +445,17 @@ class Crypt {
|
|||
* @param string $keyFileContents
|
||||
* @param string $passPhrase
|
||||
* @param string $cipher
|
||||
* @param int $version
|
||||
* @param int $position
|
||||
* @return string
|
||||
* @throws DecryptionFailedException
|
||||
*/
|
||||
public function symmetricDecryptFileContent($keyFileContents, $passPhrase, $cipher = self::DEFAULT_CIPHER) {
|
||||
// Remove Padding
|
||||
$noPadding = $this->removePadding($keyFileContents);
|
||||
public function symmetricDecryptFileContent($keyFileContents, $passPhrase, $cipher = self::DEFAULT_CIPHER, $version = 0, $position = 0) {
|
||||
$catFile = $this->splitMetaData($keyFileContents, $cipher);
|
||||
|
||||
$catFile = $this->splitIv($noPadding);
|
||||
if ($catFile['signature'] !== false) {
|
||||
$this->checkSignature($catFile['encrypted'], $passPhrase.$version.$position, $catFile['signature']);
|
||||
}
|
||||
|
||||
return $this->decrypt($catFile['encrypted'],
|
||||
$catFile['iv'],
|
||||
|
@ -405,42 +463,103 @@ class Crypt {
|
|||
$cipher);
|
||||
}
|
||||
|
||||
/**
|
||||
* check for valid signature
|
||||
*
|
||||
* @param string $data
|
||||
* @param string $passPhrase
|
||||
* @param string $expectedSignature
|
||||
* @throws HintException
|
||||
*/
|
||||
private function checkSignature($data, $passPhrase, $expectedSignature) {
|
||||
$signature = $this->createSignature($data, $passPhrase);
|
||||
if (!hash_equals($expectedSignature, $signature)) {
|
||||
throw new HintException('Bad Signature', $this->l->t('Bad Signature'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* create signature
|
||||
*
|
||||
* @param string $data
|
||||
* @param string $passPhrase
|
||||
* @return string
|
||||
*/
|
||||
private function createSignature($data, $passPhrase) {
|
||||
$passPhrase = hash('sha512', $passPhrase . 'a', true);
|
||||
$signature = hash_hmac('sha256', $data, $passPhrase);
|
||||
return $signature;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* remove padding
|
||||
*
|
||||
* @param $padded
|
||||
* @param string $padded
|
||||
* @param bool $hasSignature did the block contain a signature, in this case we use a different padding
|
||||
* @return string|false
|
||||
*/
|
||||
private function removePadding($padded) {
|
||||
if (substr($padded, -2) === 'xx') {
|
||||
private function removePadding($padded, $hasSignature = false) {
|
||||
if ($hasSignature === false && substr($padded, -2) === 'xx') {
|
||||
return substr($padded, 0, -2);
|
||||
} elseif ($hasSignature === true && substr($padded, -3) === 'xxx') {
|
||||
return substr($padded, 0, -3);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* split iv from encrypted content
|
||||
* split meta data from encrypted file
|
||||
* Note: for now, we assume that the meta data always start with the iv
|
||||
* followed by the signature, if available
|
||||
*
|
||||
* @param string|false $catFile
|
||||
* @return string
|
||||
* @param string $catFile
|
||||
* @param string $cipher
|
||||
* @return array
|
||||
*/
|
||||
private function splitIv($catFile) {
|
||||
// Fetch encryption metadata from end of file
|
||||
$meta = substr($catFile, -22);
|
||||
|
||||
// Fetch IV from end of file
|
||||
$iv = substr($meta, -16);
|
||||
|
||||
// Remove IV and IV Identifier text to expose encrypted content
|
||||
|
||||
$encrypted = substr($catFile, 0, -22);
|
||||
private function splitMetaData($catFile, $cipher) {
|
||||
if ($this->hasSignature($catFile, $cipher)) {
|
||||
$catFile = $this->removePadding($catFile, true);
|
||||
$meta = substr($catFile, -93);
|
||||
$iv = substr($meta, strlen('00iv00'), 16);
|
||||
$sig = substr($meta, 22 + strlen('00sig00'));
|
||||
$encrypted = substr($catFile, 0, -93);
|
||||
} else {
|
||||
$catFile = $this->removePadding($catFile);
|
||||
$meta = substr($catFile, -22);
|
||||
$iv = substr($meta, -16);
|
||||
$sig = false;
|
||||
$encrypted = substr($catFile, 0, -22);
|
||||
}
|
||||
|
||||
return [
|
||||
'encrypted' => $encrypted,
|
||||
'iv' => $iv
|
||||
'iv' => $iv,
|
||||
'signature' => $sig
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* check if encrypted block is signed
|
||||
*
|
||||
* @param string $catFile
|
||||
* @param string $cipher
|
||||
* @return bool
|
||||
* @throws HintException
|
||||
*/
|
||||
private function hasSignature($catFile, $cipher) {
|
||||
$meta = substr($catFile, -93);
|
||||
$signaturePosition = strpos($meta, '00sig00');
|
||||
|
||||
// enforce signature for the new 'CTR' ciphers
|
||||
if ($signaturePosition === false && strpos(strtolower($cipher), 'ctr') !== false) {
|
||||
throw new HintException('Missing Signature', $this->l->t('Missing Signature'));
|
||||
}
|
||||
|
||||
return ($signaturePosition !== false);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $encryptedContent
|
||||
* @param string $iv
|
||||
|
@ -496,40 +615,18 @@ class Crypt {
|
|||
* @throws GenericEncryptionException
|
||||
*/
|
||||
private function generateIv() {
|
||||
$random = openssl_random_pseudo_bytes(12, $strong);
|
||||
if ($random) {
|
||||
if (!$strong) {
|
||||
// If OpenSSL indicates randomness is insecure log error
|
||||
$this->logger->error('Encryption Library: Insecure symmetric key was generated using openssl_random_psudo_bytes()',
|
||||
['app' => 'encryption']);
|
||||
}
|
||||
|
||||
/*
|
||||
* We encode the iv purely for string manipulation
|
||||
* purposes -it gets decoded before use
|
||||
*/
|
||||
return base64_encode($random);
|
||||
}
|
||||
// If we ever get here we've failed anyway no need for an else
|
||||
throw new GenericEncryptionException('Generating IV Failed');
|
||||
return random_bytes(16);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a cryptographically secure pseudo-random base64 encoded 256-bit
|
||||
* ASCII key, used as file key
|
||||
* Generate a cryptographically secure pseudo-random 256-bit ASCII key, used
|
||||
* as file key
|
||||
*
|
||||
* @return string
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function generateFileKey() {
|
||||
// Generate key
|
||||
$key = base64_encode(openssl_random_pseudo_bytes(32, $strong));
|
||||
if (!$key || !$strong) {
|
||||
// If OpenSSL indicates randomness is insecure, log error
|
||||
throw new \Exception('Encryption library, Insecure symmetric key was generated using openssl_random_pseudo_bytes()');
|
||||
}
|
||||
|
||||
return $key;
|
||||
return random_bytes(32);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -29,6 +29,7 @@ namespace OCA\Encryption\Crypto;
|
|||
|
||||
|
||||
use OC\Encryption\Exceptions\DecryptionFailedException;
|
||||
use OC\Files\View;
|
||||
use OCA\Encryption\Exceptions\PublicKeyMissingException;
|
||||
use OCA\Encryption\Session;
|
||||
use OCA\Encryption\Util;
|
||||
|
@ -55,6 +56,9 @@ class Encryption implements IEncryptionModule {
|
|||
/** @var string */
|
||||
private $path;
|
||||
|
||||
/** @var string */
|
||||
private $realPath;
|
||||
|
||||
/** @var string */
|
||||
private $user;
|
||||
|
||||
|
@ -94,6 +98,16 @@ class Encryption implements IEncryptionModule {
|
|||
/** @var DecryptAll */
|
||||
private $decryptAll;
|
||||
|
||||
/** @var int unencrypted block size if block contains signature */
|
||||
private $unencryptedBlockSizeSigned = 6072;
|
||||
|
||||
/** @var int unencrypted block size */
|
||||
private $unencryptedBlockSize = 6126;
|
||||
|
||||
/** @var int Current version of the file */
|
||||
private $version = 0;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @param Crypt $crypt
|
||||
|
@ -156,8 +170,8 @@ class Encryption implements IEncryptionModule {
|
|||
* 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);
|
||||
$this->realPath = $path;
|
||||
$this->accessList = $accessList;
|
||||
$this->user = $user;
|
||||
$this->isWriteOperation = false;
|
||||
|
@ -173,6 +187,8 @@ class Encryption implements IEncryptionModule {
|
|||
$this->fileKey = $this->keyManager->getFileKey($this->path, $this->user);
|
||||
}
|
||||
|
||||
$this->version = (int)$this->keyManager->getVersion($this->realPath, new View());
|
||||
|
||||
if (
|
||||
$mode === 'w'
|
||||
|| $mode === 'w+'
|
||||
|
@ -185,17 +201,17 @@ class Encryption implements IEncryptionModule {
|
|||
}
|
||||
}
|
||||
|
||||
if (isset($header['cipher'])) {
|
||||
$this->cipher = $header['cipher'];
|
||||
} elseif ($this->isWriteOperation) {
|
||||
if ($this->isWriteOperation) {
|
||||
$this->cipher = $this->crypt->getCipher();
|
||||
} elseif (isset($header['cipher'])) {
|
||||
$this->cipher = $header['cipher'];
|
||||
} else {
|
||||
// if we read a file without a header we fall-back to the legacy cipher
|
||||
// which was used in <=oC6
|
||||
$this->cipher = $this->crypt->getLegacyCipher();
|
||||
}
|
||||
|
||||
return array('cipher' => $this->cipher);
|
||||
return array('cipher' => $this->cipher, 'signed' => 'true');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -204,17 +220,25 @@ class Encryption implements IEncryptionModule {
|
|||
* buffer.
|
||||
*
|
||||
* @param string $path to the file
|
||||
* @param int $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
|
||||
*/
|
||||
public function end($path) {
|
||||
public function end($path, $position = 0) {
|
||||
$result = '';
|
||||
if ($this->isWriteOperation) {
|
||||
// Partial files do not increase the version
|
||||
if(\OC\Files\Cache\Scanner::isPartialFile($path)) {
|
||||
$version = $this->version;
|
||||
} else {
|
||||
$version = $this->version + 1;
|
||||
}
|
||||
$this->keyManager->setVersion($this->path, $this->version+1, new View());
|
||||
if (!empty($this->writeCache)) {
|
||||
$result = $this->crypt->symmetricEncryptFileContent($this->writeCache, $this->fileKey);
|
||||
$result = $this->crypt->symmetricEncryptFileContent($this->writeCache, $this->fileKey, $version, $position);
|
||||
$this->writeCache = '';
|
||||
}
|
||||
$publicKeys = array();
|
||||
|
@ -248,12 +272,12 @@ class Encryption implements IEncryptionModule {
|
|||
* encrypt data
|
||||
*
|
||||
* @param string $data you want to encrypt
|
||||
* @param int $position
|
||||
* @return string encrypted data
|
||||
*/
|
||||
public function encrypt($data) {
|
||||
|
||||
public function encrypt($data, $position = 0) {
|
||||
// If extra data is left over from the last round, make sure it
|
||||
// is integrated into the next 6126 / 8192 block
|
||||
// is integrated into the next block
|
||||
if ($this->writeCache) {
|
||||
|
||||
// Concat writeCache to start of $data
|
||||
|
@ -275,7 +299,7 @@ class Encryption implements IEncryptionModule {
|
|||
|
||||
// If data remaining to be written is less than the
|
||||
// size of 1 6126 byte block
|
||||
if ($remainingLength < 6126) {
|
||||
if ($remainingLength < $this->unencryptedBlockSizeSigned) {
|
||||
|
||||
// Set writeCache to contents of $data
|
||||
// The writeCache will be carried over to the
|
||||
|
@ -293,14 +317,20 @@ class Encryption implements IEncryptionModule {
|
|||
} else {
|
||||
|
||||
// Read the chunk from the start of $data
|
||||
$chunk = substr($data, 0, 6126);
|
||||
$chunk = substr($data, 0, $this->unencryptedBlockSizeSigned);
|
||||
|
||||
$encrypted .= $this->crypt->symmetricEncryptFileContent($chunk, $this->fileKey);
|
||||
// Partial files do not increase the version
|
||||
if(\OC\Files\Cache\Scanner::isPartialFile($this->path)) {
|
||||
$version = $this->version;
|
||||
} else {
|
||||
$version = $this->version + 1;
|
||||
}
|
||||
$encrypted .= $this->crypt->symmetricEncryptFileContent($chunk, $this->fileKey, $version, $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, 6126);
|
||||
$data = substr($data, $this->unencryptedBlockSizeSigned);
|
||||
|
||||
}
|
||||
|
||||
|
@ -313,10 +343,11 @@ class Encryption implements IEncryptionModule {
|
|||
* decrypt data
|
||||
*
|
||||
* @param string $data you want to decrypt
|
||||
* @param int $position
|
||||
* @return string decrypted data
|
||||
* @throws DecryptionFailedException
|
||||
*/
|
||||
public function decrypt($data) {
|
||||
public function decrypt($data, $position = 0) {
|
||||
if (empty($this->fileKey)) {
|
||||
$msg = 'Can not decrypt this file, probably this is a shared file. Please ask the file owner to reshare the file with you.';
|
||||
$hint = $this->l->t('Can not decrypt this file, probably this is a shared file. Please ask the file owner to reshare the file with you.');
|
||||
|
@ -325,11 +356,7 @@ class Encryption implements IEncryptionModule {
|
|||
throw new DecryptionFailedException($msg, $hint);
|
||||
}
|
||||
|
||||
$result = '';
|
||||
if (!empty($data)) {
|
||||
$result = $this->crypt->symmetricDecryptFileContent($data, $this->fileKey, $this->cipher);
|
||||
}
|
||||
return $result;
|
||||
return $this->crypt->symmetricDecryptFileContent($data, $this->fileKey, $this->cipher, $this->version, $position);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -342,6 +369,10 @@ class Encryption implements IEncryptionModule {
|
|||
*/
|
||||
public function update($path, $uid, array $accessList) {
|
||||
$fileKey = $this->keyManager->getFileKey($path, $uid);
|
||||
if(empty($this->realPath)) {
|
||||
$this->realPath = $path;
|
||||
}
|
||||
$version = $this->keyManager->getVersion($this->realPath, new View());
|
||||
|
||||
if (!empty($fileKey)) {
|
||||
|
||||
|
@ -362,6 +393,8 @@ class Encryption implements IEncryptionModule {
|
|||
|
||||
$this->keyManager->setAllFileKeys($path, $encryptedFileKey);
|
||||
|
||||
$this->keyManager->setVersion($path, $version, new View());
|
||||
|
||||
} else {
|
||||
$this->logger->debug('no file key found, we assume that the file "{file}" is not encrypted',
|
||||
array('file' => $path, 'app' => 'encryption'));
|
||||
|
@ -407,10 +440,15 @@ class Encryption implements IEncryptionModule {
|
|||
* get size of the unencrypted payload per block.
|
||||
* ownCloud read/write files with a block size of 8192 byte
|
||||
*
|
||||
* @return integer
|
||||
* @param bool $signed
|
||||
* @return int
|
||||
*/
|
||||
public function getUnencryptedBlockSize() {
|
||||
return 6126;
|
||||
public function getUnencryptedBlockSize($signed = false) {
|
||||
if ($signed === false) {
|
||||
return $this->unencryptedBlockSize;
|
||||
}
|
||||
|
||||
return $this->unencryptedBlockSizeSigned;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -25,12 +25,14 @@
|
|||
namespace OCA\Encryption;
|
||||
|
||||
use OC\Encryption\Exceptions\DecryptionFailedException;
|
||||
use OC\Files\View;
|
||||
use OCA\Encryption\Crypto\Encryption;
|
||||
use OCA\Encryption\Exceptions\PrivateKeyMissingException;
|
||||
use OCA\Encryption\Exceptions\PublicKeyMissingException;
|
||||
use OCA\Encryption\Crypto\Crypt;
|
||||
use OCP\Encryption\Keys\IStorage;
|
||||
use OCP\IConfig;
|
||||
use OCP\IDBConnection;
|
||||
use OCP\ILogger;
|
||||
use OCP\IUserSession;
|
||||
|
||||
|
@ -412,6 +414,37 @@ class KeyManager {
|
|||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current version of a file
|
||||
*
|
||||
* @param string $path
|
||||
* @param View $view
|
||||
* @return int
|
||||
*/
|
||||
public function getVersion($path, View $view) {
|
||||
$fileInfo = $view->getFileInfo($path);
|
||||
if($fileInfo === false) {
|
||||
return 0;
|
||||
}
|
||||
return $fileInfo->getEncryptedVersion();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current version of a file
|
||||
*
|
||||
* @param string $path
|
||||
* @param int $version
|
||||
* @param View $view
|
||||
*/
|
||||
public function setVersion($path, $version, View $view) {
|
||||
$fileInfo= $view->getFileInfo($path);
|
||||
|
||||
if($fileInfo !== false) {
|
||||
$cache = $fileInfo->getStorage()->getCache();
|
||||
$cache->update($fileInfo->getId(), ['encrypted' => $version, 'encryptedVersion' => $version]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get the encrypted file key
|
||||
*
|
||||
|
@ -546,6 +579,7 @@ class KeyManager {
|
|||
|
||||
/**
|
||||
* @param string $path
|
||||
* @return bool
|
||||
*/
|
||||
public function deleteAllFileKeys($path) {
|
||||
return $this->keyStorage->deleteAllFileKeys($path);
|
||||
|
|
|
@ -29,7 +29,8 @@ $tmpl = new OCP\Template('encryption', 'settings-admin');
|
|||
$crypt = new \OCA\Encryption\Crypto\Crypt(
|
||||
\OC::$server->getLogger(),
|
||||
\OC::$server->getUserSession(),
|
||||
\OC::$server->getConfig());
|
||||
\OC::$server->getConfig(),
|
||||
\OC::$server->getL10N('encryption'));
|
||||
|
||||
$util = new \OCA\Encryption\Util(
|
||||
new \OC\Files\View(),
|
||||
|
|
|
@ -28,7 +28,8 @@ $template = new OCP\Template('encryption', 'settings-personal');
|
|||
$crypt = new \OCA\Encryption\Crypto\Crypt(
|
||||
\OC::$server->getLogger(),
|
||||
$userSession,
|
||||
\OC::$server->getConfig());
|
||||
\OC::$server->getConfig(),
|
||||
\OC::$server->getL10N('encryption'));
|
||||
|
||||
$util = new \OCA\Encryption\Util(
|
||||
new \OC\Files\View(),
|
||||
|
|
|
@ -579,4 +579,71 @@ class KeyManagerTest extends TestCase {
|
|||
];
|
||||
}
|
||||
|
||||
public function testGetVersionWithoutFileInfo() {
|
||||
$view = $this->getMockBuilder('\\OC\\Files\\View')
|
||||
->disableOriginalConstructor()->getMock();
|
||||
$view->expects($this->once())
|
||||
->method('getFileInfo')
|
||||
->with('/admin/files/myfile.txt')
|
||||
->willReturn(false);
|
||||
|
||||
$this->assertSame(0, $this->instance->getVersion('/admin/files/myfile.txt', $view));
|
||||
}
|
||||
|
||||
public function testGetVersionWithFileInfo() {
|
||||
$view = $this->getMockBuilder('\\OC\\Files\\View')
|
||||
->disableOriginalConstructor()->getMock();
|
||||
$fileInfo = $this->getMockBuilder('\\OC\\Files\\FileInfo')
|
||||
->disableOriginalConstructor()->getMock();
|
||||
$fileInfo->expects($this->once())
|
||||
->method('getEncryptedVersion')
|
||||
->willReturn(1337);
|
||||
$view->expects($this->once())
|
||||
->method('getFileInfo')
|
||||
->with('/admin/files/myfile.txt')
|
||||
->willReturn($fileInfo);
|
||||
|
||||
$this->assertSame(1337, $this->instance->getVersion('/admin/files/myfile.txt', $view));
|
||||
}
|
||||
|
||||
public function testSetVersionWithFileInfo() {
|
||||
$view = $this->getMockBuilder('\\OC\\Files\\View')
|
||||
->disableOriginalConstructor()->getMock();
|
||||
$cache = $this->getMockBuilder('\\OCP\\Files\\Cache\\ICache')
|
||||
->disableOriginalConstructor()->getMock();
|
||||
$cache->expects($this->once())
|
||||
->method('update')
|
||||
->with(123, ['encrypted' => 5, 'encryptedVersion' => 5]);
|
||||
$storage = $this->getMockBuilder('\\OCP\\Files\\Storage')
|
||||
->disableOriginalConstructor()->getMock();
|
||||
$storage->expects($this->once())
|
||||
->method('getCache')
|
||||
->willReturn($cache);
|
||||
$fileInfo = $this->getMockBuilder('\\OC\\Files\\FileInfo')
|
||||
->disableOriginalConstructor()->getMock();
|
||||
$fileInfo->expects($this->once())
|
||||
->method('getStorage')
|
||||
->willReturn($storage);
|
||||
$fileInfo->expects($this->once())
|
||||
->method('getId')
|
||||
->willReturn(123);
|
||||
$view->expects($this->once())
|
||||
->method('getFileInfo')
|
||||
->with('/admin/files/myfile.txt')
|
||||
->willReturn($fileInfo);
|
||||
|
||||
$this->instance->setVersion('/admin/files/myfile.txt', 5, $view);
|
||||
}
|
||||
|
||||
public function testSetVersionWithoutFileInfo() {
|
||||
$view = $this->getMockBuilder('\\OC\\Files\\View')
|
||||
->disableOriginalConstructor()->getMock();
|
||||
$view->expects($this->once())
|
||||
->method('getFileInfo')
|
||||
->with('/admin/files/myfile.txt')
|
||||
->willReturn(false);
|
||||
|
||||
$this->instance->setVersion('/admin/files/myfile.txt', 5, $view);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -39,6 +39,10 @@ class cryptTest extends TestCase {
|
|||
/** @var \PHPUnit_Framework_MockObject_MockObject */
|
||||
private $config;
|
||||
|
||||
|
||||
/** @var \PHPUnit_Framework_MockObject_MockObject */
|
||||
private $l;
|
||||
|
||||
/** @var Crypt */
|
||||
private $crypt;
|
||||
|
||||
|
@ -57,8 +61,9 @@ class cryptTest extends TestCase {
|
|||
$this->config = $this->getMockBuilder('OCP\IConfig')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$this->l = $this->getMock('OCP\IL10N');
|
||||
|
||||
$this->crypt = new Crypt($this->logger, $this->userSession, $this->config);
|
||||
$this->crypt = new Crypt($this->logger, $this->userSession, $this->config, $this->l);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -105,7 +110,7 @@ class cryptTest extends TestCase {
|
|||
|
||||
$this->config->expects($this->once())
|
||||
->method('getSystemValue')
|
||||
->with($this->equalTo('cipher'), $this->equalTo('AES-256-CFB'))
|
||||
->with($this->equalTo('cipher'), $this->equalTo('AES-256-CTR'))
|
||||
->willReturn('AES-128-CFB');
|
||||
|
||||
if ($keyFormat) {
|
||||
|
@ -126,6 +131,9 @@ class cryptTest extends TestCase {
|
|||
$this->crypt->generateHeader('unknown');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function dataTestGenerateHeader() {
|
||||
return [
|
||||
[null, 'HBEGIN:cipher:AES-128-CFB:keyFormat:hash:HEND'],
|
||||
|
@ -134,16 +142,28 @@ class cryptTest extends TestCase {
|
|||
];
|
||||
}
|
||||
|
||||
public function testGetCipherWithInvalidCipher() {
|
||||
$this->config->expects($this->once())
|
||||
->method('getSystemValue')
|
||||
->with($this->equalTo('cipher'), $this->equalTo('AES-256-CTR'))
|
||||
->willReturn('Not-Existing-Cipher');
|
||||
$this->logger
|
||||
->expects($this->once())
|
||||
->method('warning')
|
||||
->with('Unsupported cipher (Not-Existing-Cipher) defined in config.php supported. Falling back to AES-256-CTR');
|
||||
|
||||
$this->assertSame('AES-256-CTR', $this->crypt->getCipher());
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider dataProviderGetCipher
|
||||
* @param string $configValue
|
||||
* @param string $expected
|
||||
*/
|
||||
public function testGetCipher($configValue, $expected) {
|
||||
|
||||
$this->config->expects($this->once())
|
||||
->method('getSystemValue')
|
||||
->with($this->equalTo('cipher'), $this->equalTo('AES-256-CFB'))
|
||||
->with($this->equalTo('cipher'), $this->equalTo('AES-256-CTR'))
|
||||
->willReturn($configValue);
|
||||
|
||||
$this->assertSame($expected,
|
||||
|
@ -161,7 +181,10 @@ class cryptTest extends TestCase {
|
|||
return array(
|
||||
array('AES-128-CFB', 'AES-128-CFB'),
|
||||
array('AES-256-CFB', 'AES-256-CFB'),
|
||||
array('unknown', 'AES-256-CFB')
|
||||
array('AES-128-CTR', 'AES-128-CTR'),
|
||||
array('AES-256-CTR', 'AES-256-CTR'),
|
||||
|
||||
array('unknown', 'AES-256-CTR')
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -181,17 +204,61 @@ class cryptTest extends TestCase {
|
|||
}
|
||||
|
||||
/**
|
||||
* test splitIV()
|
||||
* @dataProvider dataTestSplitMetaData
|
||||
*/
|
||||
public function testSplitIV() {
|
||||
$data = 'encryptedContent00iv001234567890123456';
|
||||
$result = self::invokePrivate($this->crypt, 'splitIV', array($data));
|
||||
public function testSplitMetaData($data, $expected) {
|
||||
$result = self::invokePrivate($this->crypt, 'splitMetaData', array($data, 'AES-256-CFB'));
|
||||
$this->assertTrue(is_array($result));
|
||||
$this->assertSame(2, count($result));
|
||||
$this->assertSame(3, count($result));
|
||||
$this->assertArrayHasKey('encrypted', $result);
|
||||
$this->assertArrayHasKey('iv', $result);
|
||||
$this->assertSame('encryptedContent', $result['encrypted']);
|
||||
$this->assertSame('1234567890123456', $result['iv']);
|
||||
$this->assertArrayHasKey('signature', $result);
|
||||
$this->assertSame($expected['encrypted'], $result['encrypted']);
|
||||
$this->assertSame($expected['iv'], $result['iv']);
|
||||
$this->assertSame($expected['signature'], $result['signature']);
|
||||
}
|
||||
|
||||
public function dataTestSplitMetaData() {
|
||||
return [
|
||||
['encryptedContent00iv001234567890123456xx',
|
||||
['encrypted' => 'encryptedContent', 'iv' => '1234567890123456', 'signature' => false]],
|
||||
['encryptedContent00iv00123456789012345600sig00e1992521e437f6915f9173b190a512cfc38a00ac24502db44e0ba10c2bb0cc86xxx',
|
||||
['encrypted' => 'encryptedContent', 'iv' => '1234567890123456', 'signature' => 'e1992521e437f6915f9173b190a512cfc38a00ac24502db44e0ba10c2bb0cc86']],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider dataTestHasSignature
|
||||
*/
|
||||
public function testHasSignature($data, $expected) {
|
||||
$this->assertSame($expected,
|
||||
$this->invokePrivate($this->crypt, 'hasSignature', array($data, 'AES-256-CFB'))
|
||||
);
|
||||
}
|
||||
|
||||
public function dataTestHasSignature() {
|
||||
return [
|
||||
['encryptedContent00iv001234567890123456xx', false],
|
||||
['encryptedContent00iv00123456789012345600sig00e1992521e437f6915f9173b190a512cfc38a00ac24502db44e0ba10c2bb0cc86xxx', true]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider dataTestHasSignatureFail
|
||||
* @expectedException \OC\HintException
|
||||
*/
|
||||
public function testHasSignatureFail($cipher) {
|
||||
$data = 'encryptedContent00iv001234567890123456xx';
|
||||
$this->invokePrivate($this->crypt, 'hasSignature', array($data, $cipher));
|
||||
}
|
||||
|
||||
public function dataTestHasSignatureFail() {
|
||||
return [
|
||||
['AES-256-CTR'],
|
||||
['aes-256-ctr'],
|
||||
['AES-128-CTR'],
|
||||
['ctr-256-ctr']
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -199,7 +266,7 @@ class cryptTest extends TestCase {
|
|||
*/
|
||||
public function testAddPadding() {
|
||||
$result = self::invokePrivate($this->crypt, 'addPadding', array('data'));
|
||||
$this->assertSame('dataxx', $result);
|
||||
$this->assertSame('dataxxx', $result);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -303,10 +370,15 @@ class cryptTest extends TestCase {
|
|||
$this->invokePrivate($this->crypt, 'getKeySize', ['foo']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function dataTestGetKeySize() {
|
||||
return [
|
||||
['AES-256-CFB', 32],
|
||||
['AES-128-CFB', 16],
|
||||
['AES-256-CTR', 32],
|
||||
['AES-128-CTR', 16],
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -320,7 +392,8 @@ class cryptTest extends TestCase {
|
|||
[
|
||||
$this->logger,
|
||||
$this->userSession,
|
||||
$this->config
|
||||
$this->config,
|
||||
$this->l
|
||||
]
|
||||
)
|
||||
->setMethods(
|
||||
|
@ -351,6 +424,9 @@ class cryptTest extends TestCase {
|
|||
$this->assertSame($expected, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function dataTestDecryptPrivateKey() {
|
||||
return [
|
||||
[['cipher' => 'AES-128-CFB', 'keyFormat' => 'password'], 'HBEGIN:HENDprivateKey', 'AES-128-CFB', true, 'key'],
|
||||
|
|
|
@ -229,7 +229,7 @@ class EncryptionTest extends TestCase {
|
|||
|
||||
public function dataTestBegin() {
|
||||
return array(
|
||||
array('w', ['cipher' => 'myCipher'], 'legacyCipher', 'defaultCipher', 'fileKey', 'myCipher'),
|
||||
array('w', ['cipher' => 'myCipher'], 'legacyCipher', 'defaultCipher', 'fileKey', 'defaultCipher'),
|
||||
array('r', ['cipher' => 'myCipher'], 'legacyCipher', 'defaultCipher', 'fileKey', 'myCipher'),
|
||||
array('w', [], 'legacyCipher', 'defaultCipher', '', 'defaultCipher'),
|
||||
array('r', [], 'legacyCipher', 'defaultCipher', 'file_key', 'legacyCipher'),
|
||||
|
|
|
@ -165,7 +165,15 @@ class Storage {
|
|||
$mtime = $users_view->filemtime('files/' . $filename);
|
||||
$users_view->copy('files/' . $filename, 'files_versions/' . $filename . '.v' . $mtime);
|
||||
// call getFileInfo to enforce a file cache entry for the new version
|
||||
$users_view->getFileInfo('files_versions/' . $filename . '.v' . $mtime);
|
||||
$newFileInfo = $users_view->getFileInfo('files_versions/' . $filename . '.v' . $mtime);
|
||||
|
||||
// Keep the "encrypted" value of the original file
|
||||
$oldVersion = $files_view->getFileInfo($filename)->getEncryptedVersion();
|
||||
$qb = \OC::$server->getDatabaseConnection()->getQueryBuilder();
|
||||
$qb->update('filecache')
|
||||
->set('encrypted', $qb->createNamedParameter($oldVersion))
|
||||
->where($qb->expr()->eq('fileid', $qb->createNamedParameter($newFileInfo->getId())))
|
||||
->execute();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
9
lib/private/files/cache/cache.php
vendored
9
lib/private/files/cache/cache.php
vendored
|
@ -145,6 +145,7 @@ class Cache implements ICache {
|
|||
$data['size'] = 0 + $data['size'];
|
||||
$data['mtime'] = (int)$data['mtime'];
|
||||
$data['storage_mtime'] = (int)$data['storage_mtime'];
|
||||
$data['encryptedVersion'] = (int)$data['encrypted'];
|
||||
$data['encrypted'] = (bool)$data['encrypted'];
|
||||
$data['storage'] = $this->storageId;
|
||||
$data['mimetype'] = $this->mimetypeLoader->getMimetypeById($data['mimetype']);
|
||||
|
@ -345,8 +346,12 @@ class Cache implements ICache {
|
|||
$queryParts[] = '`mtime`';
|
||||
}
|
||||
} elseif ($name === 'encrypted') {
|
||||
// Boolean to integer conversion
|
||||
$value = $value ? 1 : 0;
|
||||
if(isset($data['encryptedVersion'])) {
|
||||
$value = $data['encryptedVersion'];
|
||||
} else {
|
||||
// Boolean to integer conversion
|
||||
$value = $value ? 1 : 0;
|
||||
}
|
||||
}
|
||||
$params[] = $value;
|
||||
$queryParts[] = '`' . $name . '`';
|
||||
|
|
|
@ -193,6 +193,15 @@ class FileInfo implements \OCP\Files\FileInfo, \ArrayAccess {
|
|||
return $this->data['encrypted'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the currently version used for the HMAC in the encryption app
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getEncryptedVersion() {
|
||||
return isset($this->data['encryptedVersion']) ? (int) $this->data['encryptedVersion'] : 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
|
|
|
@ -39,6 +39,7 @@ use OCP\Encryption\Keys\IStorage;
|
|||
use OCP\Files\Mount\IMountPoint;
|
||||
use OCP\Files\Storage;
|
||||
use OCP\ILogger;
|
||||
use OCP\Files\Cache\ICacheEntry;
|
||||
|
||||
class Encryption extends Wrapper {
|
||||
|
||||
|
@ -129,13 +130,16 @@ class Encryption extends Wrapper {
|
|||
if (isset($this->unencryptedSize[$fullPath])) {
|
||||
$size = $this->unencryptedSize[$fullPath];
|
||||
// update file cache
|
||||
if ($info) {
|
||||
if ($info instanceof ICacheEntry) {
|
||||
$info = $info->getData();
|
||||
$info['encrypted'] = $info['encryptedVersion'];
|
||||
} else {
|
||||
$info = [];
|
||||
if (!is_array($info)) {
|
||||
$info = [];
|
||||
}
|
||||
$info['encrypted'] = true;
|
||||
}
|
||||
|
||||
$info['encrypted'] = true;
|
||||
$info['size'] = $size;
|
||||
$this->getCache()->put($path, $info);
|
||||
|
||||
|
@ -343,6 +347,7 @@ class Encryption extends Wrapper {
|
|||
$shouldEncrypt = false;
|
||||
$encryptionModule = null;
|
||||
$header = $this->getHeader($path);
|
||||
$signed = (isset($header['signed']) && $header['signed'] === 'true') ? true : false;
|
||||
$fullPath = $this->getFullPath($path);
|
||||
$encryptionModuleId = $this->util->getEncryptionModuleId($header);
|
||||
|
||||
|
@ -377,7 +382,7 @@ class Encryption extends Wrapper {
|
|||
|| $mode === 'wb'
|
||||
|| $mode === 'wb+'
|
||||
) {
|
||||
// don't overwrite encrypted files if encyption is not enabled
|
||||
// don't overwrite encrypted files if encryption is not enabled
|
||||
if ($targetIsEncrypted && $encryptionEnabled === false) {
|
||||
throw new GenericEncryptionException('Tried to access encrypted file but encryption is not enabled');
|
||||
}
|
||||
|
@ -385,6 +390,7 @@ class Encryption extends Wrapper {
|
|||
// if $encryptionModuleId is empty, the default module will be used
|
||||
$encryptionModule = $this->encryptionManager->getEncryptionModule($encryptionModuleId);
|
||||
$shouldEncrypt = $encryptionModule->shouldEncrypt($fullPath);
|
||||
$signed = true;
|
||||
}
|
||||
} else {
|
||||
$info = $this->getCache()->get($path);
|
||||
|
@ -422,7 +428,7 @@ class Encryption extends Wrapper {
|
|||
}
|
||||
$handle = \OC\Files\Stream\Encryption::wrap($source, $path, $fullPath, $header,
|
||||
$this->uid, $encryptionModule, $this->storage, $this, $this->util, $this->fileHelper, $mode,
|
||||
$size, $unencryptedSize, $headerSize);
|
||||
$size, $unencryptedSize, $headerSize, $signed);
|
||||
return $handle;
|
||||
}
|
||||
|
||||
|
|
|
@ -72,6 +72,9 @@ class Encryption extends Wrapper {
|
|||
/** @var string */
|
||||
protected $fullPath;
|
||||
|
||||
/** @var bool */
|
||||
protected $signed;
|
||||
|
||||
/**
|
||||
* header data returned by the encryption module, will be written to the file
|
||||
* in case of a write operation
|
||||
|
@ -110,7 +113,8 @@ class Encryption extends Wrapper {
|
|||
'size',
|
||||
'unencryptedSize',
|
||||
'encryptionStorage',
|
||||
'headerSize'
|
||||
'headerSize',
|
||||
'signed'
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -132,6 +136,7 @@ class Encryption extends Wrapper {
|
|||
* @param int $size
|
||||
* @param int $unencryptedSize
|
||||
* @param int $headerSize
|
||||
* @param bool $signed
|
||||
* @param string $wrapper stream wrapper class
|
||||
* @return resource
|
||||
*
|
||||
|
@ -148,6 +153,7 @@ class Encryption extends Wrapper {
|
|||
$size,
|
||||
$unencryptedSize,
|
||||
$headerSize,
|
||||
$signed,
|
||||
$wrapper = 'OC\Files\Stream\Encryption') {
|
||||
|
||||
$context = stream_context_create(array(
|
||||
|
@ -164,7 +170,8 @@ class Encryption extends Wrapper {
|
|||
'size' => $size,
|
||||
'unencryptedSize' => $unencryptedSize,
|
||||
'encryptionStorage' => $encStorage,
|
||||
'headerSize' => $headerSize
|
||||
'headerSize' => $headerSize,
|
||||
'signed' => $signed
|
||||
)
|
||||
));
|
||||
|
||||
|
@ -225,7 +232,7 @@ class Encryption extends Wrapper {
|
|||
$this->position = 0;
|
||||
$this->cache = '';
|
||||
$this->writeFlag = false;
|
||||
$this->unencryptedBlockSize = $this->encryptionModule->getUnencryptedBlockSize();
|
||||
$this->unencryptedBlockSize = $this->encryptionModule->getUnencryptedBlockSize($this->signed);
|
||||
|
||||
if (
|
||||
$mode === 'w'
|
||||
|
@ -392,8 +399,9 @@ class Encryption extends Wrapper {
|
|||
}
|
||||
|
||||
public function stream_close() {
|
||||
$this->flush();
|
||||
$remainingData = $this->encryptionModule->end($this->fullPath);
|
||||
$this->flush('end');
|
||||
$position = (int)floor($this->position/$this->unencryptedBlockSize);
|
||||
$remainingData = $this->encryptionModule->end($this->fullPath, $position . 'end');
|
||||
if ($this->readOnly === false) {
|
||||
if(!empty($remainingData)) {
|
||||
parent::stream_write($remainingData);
|
||||
|
@ -405,15 +413,17 @@ class Encryption extends Wrapper {
|
|||
|
||||
/**
|
||||
* write block to file
|
||||
* @param string $positionPrefix
|
||||
*/
|
||||
protected function flush() {
|
||||
protected function flush($positionPrefix = '') {
|
||||
// write to disk only when writeFlag was set to 1
|
||||
if ($this->writeFlag) {
|
||||
// Disable the file proxies so that encryption is not
|
||||
// automatically attempted when the file is written to disk -
|
||||
// we are handling that separately here and we don't want to
|
||||
// get into an infinite loop
|
||||
$encrypted = $this->encryptionModule->encrypt($this->cache);
|
||||
$position = (int)floor($this->position/$this->unencryptedBlockSize);
|
||||
$encrypted = $this->encryptionModule->encrypt($this->cache, $position . $positionPrefix);
|
||||
$bytesWritten = parent::stream_write($encrypted);
|
||||
$this->writeFlag = false;
|
||||
// Check whether the write concerns the last block
|
||||
|
@ -440,7 +450,12 @@ class Encryption extends Wrapper {
|
|||
if ($this->cache === '' && !($this->position === $this->unencryptedSize && ($this->position % $this->unencryptedBlockSize) === 0)) {
|
||||
// Get the data from the file handle
|
||||
$data = parent::stream_read($this->util->getBlockSize());
|
||||
$this->cache = $this->encryptionModule->decrypt($data);
|
||||
$position = (int)floor($this->position/$this->unencryptedBlockSize);
|
||||
$numberOfChunks = (int)($this->unencryptedSize / $this->unencryptedBlockSize);
|
||||
if($numberOfChunks === $position) {
|
||||
$position .= 'end';
|
||||
}
|
||||
$this->cache = $this->encryptionModule->decrypt($data, $position);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -119,10 +119,11 @@ interface IEncryptionModule {
|
|||
* get size of the unencrypted payload per block.
|
||||
* ownCloud read/write files with a block size of 8192 byte
|
||||
*
|
||||
* @return integer
|
||||
* @since 8.1.0
|
||||
* @param bool $signed
|
||||
* @return int
|
||||
* @since 8.1.0 optional parameter $signed was added in 9.0.0
|
||||
*/
|
||||
public function getUnencryptedBlockSize();
|
||||
public function getUnencryptedBlockSize($signed = false);
|
||||
|
||||
/**
|
||||
* check if the encryption module is able to read the file,
|
||||
|
|
|
@ -89,7 +89,8 @@ class Controller {
|
|||
$crypt = new \OCA\Encryption\Crypto\Crypt(
|
||||
\OC::$server->getLogger(),
|
||||
\OC::$server->getUserSession(),
|
||||
\OC::$server->getConfig());
|
||||
\OC::$server->getConfig(),
|
||||
\OC::$server->getL10N('encryption'));
|
||||
$keyStorage = \OC::$server->getEncryptionKeyStorage();
|
||||
$util = new \OCA\Encryption\Util(
|
||||
new \OC\Files\View(),
|
||||
|
|
|
@ -117,6 +117,7 @@ class Encryption extends \Test\TestCase {
|
|||
$header->setAccessible(true);
|
||||
$header->setValue($streamWrapper, array());
|
||||
$header->setAccessible(false);
|
||||
$this->invokePrivate($streamWrapper, 'signed', [true]);
|
||||
|
||||
// call stream_open, that's the method we want to test
|
||||
$dummyVar = 'foo';
|
||||
|
|
Loading…
Reference in a new issue