diff options
author | Bjoern Schiessle <schiessle@owncloud.com> | 2015-03-30 17:29:07 +0200 |
---|---|---|
committer | Thomas Müller <thomas.mueller@tmit.eu> | 2015-04-07 13:30:28 +0200 |
commit | e7a68d1c21c52a39ddec59579ab7701dfef82b2a (patch) | |
tree | f18553183eee730b754f89bf2b5a2a1ce5facade /apps/files_encryption/lib | |
parent | 0eee3a2618235bcb59ce1bcb98526a7592de4578 (diff) | |
download | nextcloud-server-e7a68d1c21c52a39ddec59579ab7701dfef82b2a.tar.gz nextcloud-server-e7a68d1c21c52a39ddec59579ab7701dfef82b2a.zip |
remove old encryption app
Diffstat (limited to 'apps/files_encryption/lib')
-rw-r--r-- | apps/files_encryption/lib/capabilities.php | 39 | ||||
-rw-r--r-- | apps/files_encryption/lib/crypt.php | 581 | ||||
-rw-r--r-- | apps/files_encryption/lib/helper.php | 532 | ||||
-rw-r--r-- | apps/files_encryption/lib/hooks.php | 625 | ||||
-rw-r--r-- | apps/files_encryption/lib/keymanager.php | 500 | ||||
-rw-r--r-- | apps/files_encryption/lib/migration.php | 302 | ||||
-rw-r--r-- | apps/files_encryption/lib/proxy.php | 401 | ||||
-rw-r--r-- | apps/files_encryption/lib/session.php | 203 | ||||
-rw-r--r-- | apps/files_encryption/lib/stream.php | 700 | ||||
-rw-r--r-- | apps/files_encryption/lib/util.php | 1700 |
10 files changed, 0 insertions, 5583 deletions
diff --git a/apps/files_encryption/lib/capabilities.php b/apps/files_encryption/lib/capabilities.php deleted file mode 100644 index 0ed696fc7cb..00000000000 --- a/apps/files_encryption/lib/capabilities.php +++ /dev/null @@ -1,39 +0,0 @@ -<?php -/** - * @author Christopher Schäpers <kondou@ts.unde.re> - * @author Joas Schilling <nickvergessen@owncloud.com> - * @author Morris Jobke <hey@morrisjobke.de> - * @author Tom Needham <tom@owncloud.com> - * - * @copyright Copyright (c) 2015, ownCloud, Inc. - * @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/> - * - */ - -namespace OCA\Files_Encryption; - -class Capabilities { - - public static function getCapabilities() { - return new \OC_OCS_Result(array( - 'capabilities' => array( - 'files' => array( - 'encryption' => true, - ), - ), - )); - } - -} diff --git a/apps/files_encryption/lib/crypt.php b/apps/files_encryption/lib/crypt.php deleted file mode 100644 index 784121c7ed1..00000000000 --- a/apps/files_encryption/lib/crypt.php +++ /dev/null @@ -1,581 +0,0 @@ -<?php -/** - * @author Björn Schießle <schiessle@owncloud.com> - * @author Florin Peter <github@florin-peter.de> - * @author Joas Schilling <nickvergessen@owncloud.com> - * @author Jörn Friedrich Dreyer <jfd@butonic.de> - * @author Morris Jobke <hey@morrisjobke.de> - * @author Owen Winkler <a_github@midnightcircus.com> - * @author Robin McCorkell <rmccorkell@karoshi.org.uk> - * @author Sam Tuke <mail@samtuke.com> - * @author Scott Arciszewski <scott@arciszewski.me> - * @author Thomas Müller <thomas.mueller@tmit.eu> - * - * @copyright Copyright (c) 2015, ownCloud, Inc. - * @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/> - * - */ -
-namespace OCA\Files_Encryption;
-
-/**
- * Class for common cryptography functionality
- */
-
-class Crypt {
-
- const ENCRYPTION_UNKNOWN_ERROR = -1;
- const ENCRYPTION_NOT_INITIALIZED_ERROR = 1;
- const ENCRYPTION_PRIVATE_KEY_NOT_VALID_ERROR = 2;
- const ENCRYPTION_NO_SHARE_KEY_FOUND = 3;
-
- const BLOCKSIZE = 8192; // block size will always be 8192 for a PHP stream https://bugs.php.net/bug.php?id=21641
- const DEFAULT_CIPHER = 'AES-256-CFB';
-
- const HEADERSTART = 'HBEGIN';
- const HEADEREND = 'HEND';
-
- /**
- * return encryption mode client or server side encryption
- * @param string $user name (use system wide setting if name=null)
- * @return string 'client' or 'server'
- * @note at the moment we only support server side encryption
- */
- public static function mode($user = null) {
-
- return 'server';
-
- }
-
- /**
- * Create a new encryption keypair
- * @return array publicKey, privatekey
- */
- public static function createKeypair() {
-
- $return = false;
-
- $res = Helper::getOpenSSLPkey();
-
- if ($res === false) {
- \OCP\Util::writeLog('Encryption library', 'couldn\'t generate users key-pair for ' . \OCP\User::getUser(), \OCP\Util::ERROR);
- while ($msg = openssl_error_string()) {
- \OCP\Util::writeLog('Encryption library', 'openssl_pkey_new() fails: ' . $msg, \OCP\Util::ERROR);
- }
- } elseif (openssl_pkey_export($res, $privateKey, null, Helper::getOpenSSLConfig())) {
- // Get public key
- $keyDetails = openssl_pkey_get_details($res);
- $publicKey = $keyDetails['key'];
-
- $return = array(
- 'publicKey' => $publicKey,
- 'privateKey' => $privateKey
- );
- } else {
- \OCP\Util::writeLog('Encryption library', 'couldn\'t export users private key, please check your servers openSSL configuration.' . \OCP\User::getUser(), \OCP\Util::ERROR);
- while($errMsg = openssl_error_string()) {
- \OCP\Util::writeLog('Encryption library', $errMsg, \OCP\Util::ERROR);
- }
- }
-
- return $return;
- }
-
- /**
- * Add arbitrary padding to encrypted data
- * @param string $data data to be padded
- * @return string padded data
- * @note In order to end up with data exactly 8192 bytes long we must
- * add two letters. It is impossible to achieve exactly 8192 length
- * blocks with encryption alone, hence padding is added to achieve the
- * required length.
- */
- private static function addPadding($data) {
-
- $padded = $data . 'xx';
-
- return $padded;
-
- }
-
- /**
- * Remove arbitrary padding to encrypted data
- * @param string $padded padded data to remove padding from
- * @return string unpadded data on success, false on error
- */
- private static function removePadding($padded) {
-
- if (substr($padded, -2) === 'xx') {
-
- $data = substr($padded, 0, -2);
-
- return $data;
-
- } else {
-
- // TODO: log the fact that unpadded data was submitted for removal of padding
- return false;
-
- }
-
- }
-
- /**
- * Check if a file's contents contains an IV and is symmetrically encrypted
- * @param string $content
- * @return boolean
- * @note see also \OCA\Files_Encryption\Util->isEncryptedPath()
- */
- public static function isCatfileContent($content) {
-
- if (!$content) {
-
- return false;
-
- }
-
- $noPadding = self::removePadding($content);
-
- // Fetch encryption metadata from end of file
- $meta = substr($noPadding, -22);
-
- // Fetch identifier from start of metadata
- $identifier = substr($meta, 0, 6);
-
- if ($identifier === '00iv00') {
-
- return true;
-
- } else {
-
- return false;
-
- }
-
- }
-
- /**
- * Check if a file is encrypted according to database file cache
- * @param string $path
- * @return bool
- */
- public static function isEncryptedMeta($path) {
-
- // TODO: Use DI to get \OC\Files\Filesystem out of here
-
- // Fetch all file metadata from DB
- $metadata = \OC\Files\Filesystem::getFileInfo($path);
-
- // Return encryption status
- return isset($metadata['encrypted']) && ( bool )$metadata['encrypted'];
-
- }
-
- /**
- * Symmetrically encrypt a string
- * @param string $plainContent
- * @param string $iv
- * @param string $passphrase
- * @param string $cypher used for encryption, currently we support AES-128-CFB and AES-256-CFB
- * @return string encrypted file content
- * @throws \OCA\Files_Encryption\Exception\EncryptionException
- */
- private static function encrypt($plainContent, $iv, $passphrase = '', $cipher = Crypt::DEFAULT_CIPHER) {
-
- $encryptedContent = openssl_encrypt($plainContent, $cipher, $passphrase, false, $iv);
-
- if (!$encryptedContent) {
- $error = "Encryption (symmetric) of content failed: " . openssl_error_string();
- \OCP\Util::writeLog('Encryption library', $error, \OCP\Util::ERROR);
- throw new Exception\EncryptionException($error, Exception\EncryptionException::ENCRYPTION_FAILED);
- }
-
- return $encryptedContent;
-
- }
-
- /**
- * Symmetrically decrypt a string
- * @param string $encryptedContent
- * @param string $iv
- * @param string $passphrase
- * @param string $cipher cipher user for decryption, currently we support aes128 and aes256
- * @throws \Exception
- * @return string decrypted file content
- */
- private static function decrypt($encryptedContent, $iv, $passphrase, $cipher = Crypt::DEFAULT_CIPHER) {
-
- $plainContent = openssl_decrypt($encryptedContent, $cipher, $passphrase, false, $iv);
-
- if ($plainContent) {
- return $plainContent;
- } else {
- throw new \Exception('Encryption library: Decryption (symmetric) of content failed');
- }
-
- }
-
- /**
- * Concatenate encrypted data with its IV and padding
- * @param string $content content to be concatenated
- * @param string $iv IV to be concatenated
- * @return string concatenated content
- */
- private static function concatIv($content, $iv) {
-
- $combined = $content . '00iv00' . $iv;
-
- return $combined;
-
- }
-
- /**
- * Split concatenated data and IV into respective parts
- * @param string $catFile concatenated data to be split
- * @return array keys: encrypted, iv
- */
- private static 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);
-
- $split = array(
- 'encrypted' => $encrypted,
- 'iv' => $iv
- );
-
- return $split;
-
- }
-
- /**
- * Symmetrically encrypts a string and returns keyfile content
- * @param string $plainContent content to be encrypted in keyfile
- * @param string $passphrase
- * @param string $cypher used for encryption, currently we support AES-128-CFB and AES-256-CFB
- * @return false|string encrypted content combined with IV
- * @note IV need not be specified, as it will be stored in the returned keyfile
- * and remain accessible therein.
- */
- public static function symmetricEncryptFileContent($plainContent, $passphrase = '', $cipher = Crypt::DEFAULT_CIPHER) {
-
- if (!$plainContent) {
- \OCP\Util::writeLog('Encryption library', 'symmetrically encryption failed, no content given.', \OCP\Util::ERROR);
- return false;
- }
-
- $iv = self::generateIv();
-
- try {
- $encryptedContent = self::encrypt($plainContent, $iv, $passphrase, $cipher);
- // Combine content to encrypt with IV identifier and actual IV
- $catfile = self::concatIv($encryptedContent, $iv);
- $padded = self::addPadding($catfile);
-
- return $padded;
- } catch (Exception\EncryptionException $e) {
- $message = 'Could not encrypt file content (code: ' . $e->getCode() . '): ';
- \OCP\Util::writeLog('files_encryption', $message . $e->getMessage(), \OCP\Util::ERROR);
- return false;
- }
-
- }
-
-
- /**
- * Symmetrically decrypts keyfile content
- * @param string $keyfileContent
- * @param string $passphrase
- * @param string $cipher cipher used for decryption, currently aes128 and aes256 is supported.
- * @throws \Exception
- * @return string|false
- * @internal param string $source
- * @internal param string $target
- * @internal param string $key the decryption key
- * @return string decrypted content
- *
- * This function decrypts a file
- */
- public static function symmetricDecryptFileContent($keyfileContent, $passphrase = '', $cipher = Crypt::DEFAULT_CIPHER) {
-
- if (!$keyfileContent) {
-
- throw new \Exception('Encryption library: no data provided for decryption');
-
- }
-
- // Remove padding
- $noPadding = self::removePadding($keyfileContent);
-
- // Split into enc data and catfile
- $catfile = self::splitIv($noPadding);
-
- if ($plainContent = self::decrypt($catfile['encrypted'], $catfile['iv'], $passphrase, $cipher)) {
-
- return $plainContent;
-
- } else {
- return false;
- }
-
- }
-
- /**
- * Decrypt private key and check if the result is a valid keyfile
- *
- * @param string $encryptedKey encrypted keyfile
- * @param string $passphrase to decrypt keyfile
- * @return string|false encrypted private key or false
- *
- * This function decrypts a file
- */
- public static function decryptPrivateKey($encryptedKey, $passphrase) {
-
- $header = self::parseHeader($encryptedKey);
- $cipher = self::getCipher($header);
-
- // if we found a header we need to remove it from the key we want to decrypt
- if (!empty($header)) {
- $encryptedKey = substr($encryptedKey, strpos($encryptedKey, self::HEADEREND) + strlen(self::HEADEREND));
- }
-
- $plainKey = self::symmetricDecryptFileContent($encryptedKey, $passphrase, $cipher);
-
- // check if this a valid private key
- $res = openssl_pkey_get_private($plainKey);
- if (is_resource($res)) {
- $sslInfo = openssl_pkey_get_details($res);
- if (!isset($sslInfo['key'])) {
- $plainKey = false;
- }
- } else {
- $plainKey = false;
- }
-
- return $plainKey;
-
- }
-
- /**
- * Create asymmetrically encrypted keyfile content using a generated key
- * @param string $plainContent content to be encrypted
- * @param array $publicKeys array keys must be the userId of corresponding user
- * @return array keys: keys (array, key = userId), data
- * @throws \OCA\Files_Encryption\Exception\MultiKeyEncryptException if encryption failed
- * @note symmetricDecryptFileContent() can decrypt files created using this method
- */
- public static function multiKeyEncrypt($plainContent, array $publicKeys) {
-
- // openssl_seal returns false without errors if $plainContent
- // is empty, so trigger our own error
- if (empty($plainContent)) {
- throw new Exception\MultiKeyEncryptException('Cannot multiKeyEncrypt empty plain content', Exception\MultiKeyEncryptException::EMPTY_DATA);
- }
-
- // Set empty vars to be set by openssl by reference
- $sealed = '';
- $shareKeys = array();
- $mappedShareKeys = array();
-
- if (openssl_seal($plainContent, $sealed, $shareKeys, $publicKeys)) {
-
- $i = 0;
-
- // Ensure each shareKey is labelled with its
- // corresponding userId
- foreach ($publicKeys as $userId => $publicKey) {
-
- $mappedShareKeys[$userId] = $shareKeys[$i];
- $i++;
-
- }
-
- return array(
- 'keys' => $mappedShareKeys,
- 'data' => $sealed
- );
-
- } else {
- throw new Exception\MultiKeyEncryptException('multi key encryption failed: ' . openssl_error_string(),
- Exception\MultiKeyEncryptException::OPENSSL_SEAL_FAILED);
- }
-
- }
-
- /**
- * Asymmetrically encrypt a file using multiple public keys
- * @param string $encryptedContent
- * @param string $shareKey
- * @param mixed $privateKey
- * @throws \OCA\Files_Encryption\Exception\MultiKeyDecryptException if decryption failed
- * @internal param string $plainContent contains decrypted content
- * @return string $plainContent decrypted string
- * @note symmetricDecryptFileContent() can be used to decrypt files created using this method
- *
- * This function decrypts a file
- */
- public static function multiKeyDecrypt($encryptedContent, $shareKey, $privateKey) {
-
- if (!$encryptedContent) {
- throw new Exception\MultiKeyDecryptException('Cannot mutliKeyDecrypt empty plain content',
- Exception\MultiKeyDecryptException::EMPTY_DATA);
- }
-
- if (openssl_open($encryptedContent, $plainContent, $shareKey, $privateKey)) {
-
- return $plainContent;
-
- } else {
- throw new Exception\MultiKeyDecryptException('multiKeyDecrypt with share-key' . $shareKey . 'failed: ' . openssl_error_string(),
- Exception\MultiKeyDecryptException::OPENSSL_OPEN_FAILED);
- }
-
- }
-
- /**
- * Generates a pseudo random initialisation vector
- * @return String $iv generated IV
- */
- private static function generateIv() {
-
- if ($random = openssl_random_pseudo_bytes(12, $strong)) {
-
- if (!$strong) {
-
- // If OpenSSL indicates randomness is insecure, log error
- \OCP\Util::writeLog('Encryption library', 'Insecure symmetric key was generated using openssl_random_pseudo_bytes()', \OCP\Util::WARN);
-
- }
-
- // We encode the iv purely for string manipulation
- // purposes - it gets decoded before use
- $iv = base64_encode($random);
-
- return $iv;
-
- } else {
-
- throw new \Exception('Generating IV failed');
-
- }
-
- }
-
- /**
- * Generate a pseudo random 256-bit ASCII key, used as file key
- * @return string|false Generated key
- */
- public static function generateKey() {
-
- // Generate key
- if ($key = base64_encode(openssl_random_pseudo_bytes(32, $strong))) {
-
- if (!$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;
-
- } else {
-
- return false;
-
- }
-
- }
-
- /**
- * read header into array
- *
- * @param string $data
- * @return array
- */
- public static function parseHeader($data) {
-
- $result = array();
-
- if (substr($data, 0, strlen(self::HEADERSTART)) === self::HEADERSTART) {
- $endAt = strpos($data, self::HEADEREND);
- $header = substr($data, 0, $endAt + strlen(self::HEADEREND));
-
- // +1 to not start with an ':' which would result in empty element at the beginning
- $exploded = explode(':', substr($header, strlen(self::HEADERSTART)+1));
-
- $element = array_shift($exploded);
- while ($element !== self::HEADEREND) {
-
- $result[$element] = array_shift($exploded);
-
- $element = array_shift($exploded);
-
- }
- }
-
- return $result;
- }
-
- /**
- * check if data block is the header
- *
- * @param string $data
- * @return boolean
- */
- public static function isHeader($data) {
-
- if (substr($data, 0, strlen(self::HEADERSTART)) === self::HEADERSTART) {
- return true;
- }
-
- return false;
- }
-
- /**
- * get chiper from header
- *
- * @param array $header
- * @throws \OCA\Files_Encryption\Exception\EncryptionException
- */
- public static function getCipher($header) {
- $cipher = isset($header['cipher']) ? $header['cipher'] : 'AES-128-CFB';
-
- if ($cipher !== 'AES-256-CFB' && $cipher !== 'AES-128-CFB') {
-
- throw new Exception\EncryptionException('file header broken, no supported cipher defined',
- Exception\EncryptionException::UNKNOWN_CIPHER);
- }
-
- return $cipher;
- }
-
- /**
- * generate header for encrypted file
- */
- public static function generateHeader() {
- $cipher = Helper::getCipher();
- $header = self::HEADERSTART . ':cipher:' . $cipher . ':' . self::HEADEREND;
-
- return $header;
- }
-
-}
diff --git a/apps/files_encryption/lib/helper.php b/apps/files_encryption/lib/helper.php deleted file mode 100644 index 1ae161ce99e..00000000000 --- a/apps/files_encryption/lib/helper.php +++ /dev/null @@ -1,532 +0,0 @@ -<?php -/** - * @author Björn Schießle <schiessle@owncloud.com> - * @author Florin Peter <github@florin-peter.de> - * @author Joas Schilling <nickvergessen@owncloud.com> - * @author Jörn Friedrich Dreyer <jfd@butonic.de> - * @author Lukas Reschke <lukas@owncloud.com> - * @author Morris Jobke <hey@morrisjobke.de> - * @author Owen Winkler <a_github@midnightcircus.com> - * @author Robin Appelman <icewind@owncloud.com> - * @author Robin McCorkell <rmccorkell@karoshi.org.uk> - * @author Thomas Müller <thomas.mueller@tmit.eu> - * - * @copyright Copyright (c) 2015, ownCloud, Inc. - * @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/> - * - */ - -namespace OCA\Files_Encryption; - -/** - * Class to manage registration of hooks an various helper methods - * @package OCA\Files_Encryption - */ -class Helper { - - private static $tmpFileMapping; // Map tmp files to files in data/user/files - - /** - * register share related hooks - * - */ - public static function registerShareHooks() { - - \OCP\Util::connectHook('OCP\Share', 'pre_shared', 'OCA\Files_Encryption\Hooks', 'preShared'); - \OCP\Util::connectHook('OCP\Share', 'post_shared', 'OCA\Files_Encryption\Hooks', 'postShared'); - \OCP\Util::connectHook('OCP\Share', 'post_unshare', 'OCA\Files_Encryption\Hooks', 'postUnshare'); - } - - /** - * register user related hooks - * - */ - public static function registerUserHooks() { - - \OCP\Util::connectHook('OC_User', 'post_login', 'OCA\Files_Encryption\Hooks', 'login'); - \OCP\Util::connectHook('OC_User', 'logout', 'OCA\Files_Encryption\Hooks', 'logout'); - \OCP\Util::connectHook('OC_User', 'post_setPassword', 'OCA\Files_Encryption\Hooks', 'setPassphrase'); - \OCP\Util::connectHook('OC_User', 'pre_setPassword', 'OCA\Files_Encryption\Hooks', 'preSetPassphrase'); - \OCP\Util::connectHook('OC_User', 'post_createUser', 'OCA\Files_Encryption\Hooks', 'postCreateUser'); - \OCP\Util::connectHook('OC_User', 'post_deleteUser', 'OCA\Files_Encryption\Hooks', 'postDeleteUser'); - } - - /** - * register filesystem related hooks - * - */ - public static function registerFilesystemHooks() { - - \OCP\Util::connectHook('OC_Filesystem', 'rename', 'OCA\Files_Encryption\Hooks', 'preRename'); - \OCP\Util::connectHook('OC_Filesystem', 'post_rename', 'OCA\Files_Encryption\Hooks', 'postRenameOrCopy'); - \OCP\Util::connectHook('OC_Filesystem', 'copy', 'OCA\Files_Encryption\Hooks', 'preCopy'); - \OCP\Util::connectHook('OC_Filesystem', 'post_copy', 'OCA\Files_Encryption\Hooks', 'postRenameOrCopy'); - \OCP\Util::connectHook('OC_Filesystem', 'post_delete', 'OCA\Files_Encryption\Hooks', 'postDelete'); - \OCP\Util::connectHook('OC_Filesystem', 'delete', 'OCA\Files_Encryption\Hooks', 'preDelete'); - \OCP\Util::connectHook('\OC\Core\LostPassword\Controller\LostController', 'post_passwordReset', 'OCA\Files_Encryption\Hooks', 'postPasswordReset'); - \OCP\Util::connectHook('OC_Filesystem', 'post_umount', 'OCA\Files_Encryption\Hooks', 'postUnmount'); - \OCP\Util::connectHook('OC_Filesystem', 'umount', 'OCA\Files_Encryption\Hooks', 'preUnmount'); - } - - /** - * register app management related hooks - * - */ - public static function registerAppHooks() { - - \OCP\Util::connectHook('OC_App', 'pre_disable', 'OCA\Files_Encryption\Hooks', 'preDisable'); - \OCP\Util::connectHook('OC_App', 'post_disable', 'OCA\Files_Encryption\Hooks', 'postEnable'); - } - - /** - * setup user for files_encryption - * - * @param Util $util - * @param string $password - * @return bool - */ - public static function setupUser(Util $util, $password) { - // Check files_encryption infrastructure is ready for action - if (!$util->ready()) { - - \OCP\Util::writeLog('Encryption library', 'User account "' . $util->getUserId() - . '" is not ready for encryption; configuration started', \OCP\Util::DEBUG); - - if (!$util->setupServerSide($password)) { - return false; - } - } - - return true; - } - - /** - * get recovery key id - * - * @return string|bool recovery key ID or false - */ - public static function getRecoveryKeyId() { - $appConfig = \OC::$server->getAppConfig(); - $key = $appConfig->getValue('files_encryption', 'recoveryKeyId'); - - return ($key === null) ? false : $key; - } - - public static function getPublicShareKeyId() { - $appConfig = \OC::$server->getAppConfig(); - $key = $appConfig->getValue('files_encryption', 'publicShareKeyId'); - - return ($key === null) ? false : $key; - } - - /** - * enable recovery - * - * @param string $recoveryKeyId - * @param string $recoveryPassword - * @return bool - */ - public static function adminEnableRecovery($recoveryKeyId, $recoveryPassword) { - - $view = new \OC\Files\View('/'); - $appConfig = \OC::$server->getAppConfig(); - - if ($recoveryKeyId === null) { - $recoveryKeyId = 'recovery_' . substr(md5(time()), 0, 8); - $appConfig->setValue('files_encryption', 'recoveryKeyId', $recoveryKeyId); - } - - if (!Keymanager::recoveryKeyExists($view)) { - - $keypair = Crypt::createKeypair(); - - // Save public key - Keymanager::setPublicKey($keypair['publicKey'], $recoveryKeyId); - - $cipher = Helper::getCipher(); - $encryptedKey = Crypt::symmetricEncryptFileContent($keypair['privateKey'], $recoveryPassword, $cipher); - if ($encryptedKey) { - Keymanager::setPrivateSystemKey($encryptedKey, $recoveryKeyId); - // Set recoveryAdmin as enabled - $appConfig->setValue('files_encryption', 'recoveryAdminEnabled', 1); - $return = true; - } - - } else { // get recovery key and check the password - $util = new Util(new \OC\Files\View('/'), \OCP\User::getUser()); - $return = $util->checkRecoveryPassword($recoveryPassword); - if ($return) { - $appConfig->setValue('files_encryption', 'recoveryAdminEnabled', 1); - } - } - - return $return; - } - - /** - * Check if a path is a .part file - * @param string $path Path that may identify a .part file - * @return bool - */ - public static function isPartialFilePath($path) { - - $extension = pathinfo($path, PATHINFO_EXTENSION); - if ( $extension === 'part') { - return true; - } else { - return false; - } - - } - - - /** - * Remove .path extension from a file path - * @param string $path Path that may identify a .part file - * @return string File path without .part extension - * @note this is needed for reusing keys - */ - public static function stripPartialFileExtension($path) { - $extension = pathinfo($path, PATHINFO_EXTENSION); - - if ( $extension === 'part') { - - $newLength = strlen($path) - 5; // 5 = strlen(".part") = strlen(".etmp") - $fPath = substr($path, 0, $newLength); - - // if path also contains a transaction id, we remove it too - $extension = pathinfo($fPath, PATHINFO_EXTENSION); - if(substr($extension, 0, 12) === 'ocTransferId') { // 12 = strlen("ocTransferId") - $newLength = strlen($fPath) - strlen($extension) -1; - $fPath = substr($fPath, 0, $newLength); - } - return $fPath; - - } else { - return $path; - } - } - - /** - * disable recovery - * - * @param string $recoveryPassword - * @return bool - */ - public static function adminDisableRecovery($recoveryPassword) { - $util = new Util(new \OC\Files\View('/'), \OCP\User::getUser()); - $return = $util->checkRecoveryPassword($recoveryPassword); - - if ($return) { - // Set recoveryAdmin as disabled - \OC::$server->getAppConfig()->setValue('files_encryption', 'recoveryAdminEnabled', 0); - } - - return $return; - } - - /** - * checks if access is public/anonymous user - * @return bool - */ - public static function isPublicAccess() { - if (\OCP\User::getUser() === false) { - return true; - } else { - return false; - } - } - - /** - * Format a path to be relative to the /user/files/ directory - * @param string $path the absolute path - * @return string e.g. turns '/admin/files/test.txt' into 'test.txt' - */ - public static function stripUserFilesPath($path) { - $split = self::splitPath($path); - - // it is not a file relative to data/user/files - if (count($split) < 4 || $split[2] !== 'files') { - return false; - } - - $sliced = array_slice($split, 3); - $relPath = implode('/', $sliced); - - return $relPath; - } - - /** - * try to get the user from the path if no user is logged in - * @param string $path - * @return string user - */ - public static function getUser($path) { - - $user = \OCP\User::getUser(); - - - // if we are logged in, then we return the userid - if ($user) { - return $user; - } - - // if no user is logged in we try to access a publicly shared files. - // In this case we need to try to get the user from the path - return self::getUserFromPath($path); - } - - /** - * extract user from path - * - * @param string $path - * @return string user id - * @throws Exception\EncryptionException - */ - public static function getUserFromPath($path) { - $split = self::splitPath($path); - - if (count($split) > 2 && ( - $split[2] === 'files' || $split[2] === 'files_versions' || $split[2] === 'cache' || $split[2] === 'files_trashbin')) { - - $user = $split[1]; - - if (\OCP\User::userExists($user)) { - return $user; - } - } - - throw new Exception\EncryptionException('Could not determine user', Exception\EncryptionException::GENERIC); - } - - /** - * get path to the corresponding file in data/user/files if path points - * to a file in cache - * - * @param string $path path to a file in cache - * @return string path to corresponding file relative to data/user/files - * @throws Exception\EncryptionException - */ - public static function getPathFromCachedFile($path) { - $split = self::splitPath($path); - - if (count($split) < 5) { - throw new Exception\EncryptionException('no valid cache file path', Exception\EncryptionException::GENERIC); - } - - // we skip /user/cache/transactionId - $sliced = array_slice($split, 4); - - return implode('/', $sliced); - } - - - /** - * get path to the corresponding file in data/user/files for a version - * - * @param string $path path to a version - * @return string path to corresponding file relative to data/user/files - * @throws Exception\EncryptionException - */ - public static function getPathFromVersion($path) { - $split = self::splitPath($path); - - if (count($split) < 4) { - throw new Exception\EncryptionException('no valid path to a version', Exception\EncryptionException::GENERIC); - } - - // we skip user/files_versions - $sliced = array_slice($split, 3); - $relPath = implode('/', $sliced); - //remove the last .v - $realPath = substr($relPath, 0, strrpos($relPath, '.v')); - - return $realPath; - } - - /** - * create directory recursively - * - * @param string $path - * @param \OC\Files\View $view - */ - public static function mkdirr($path, \OC\Files\View $view) { - $dirParts = self::splitPath(dirname($path)); - $dir = ""; - foreach ($dirParts as $part) { - $dir = $dir . '/' . $part; - if (!$view->file_exists($dir)) { - $view->mkdir($dir); - } - } - } - - /** - * redirect to a error page - * @param Session $session - * @param int|null $errorCode - * @throws \Exception - */ - public static function redirectToErrorPage(Session $session, $errorCode = null) { - - if ($errorCode === null) { - $init = $session->getInitialized(); - switch ($init) { - case Session::INIT_EXECUTED: - $errorCode = Crypt::ENCRYPTION_PRIVATE_KEY_NOT_VALID_ERROR; - break; - case Session::NOT_INITIALIZED: - $errorCode = Crypt::ENCRYPTION_NOT_INITIALIZED_ERROR; - break; - default: - $errorCode = Crypt::ENCRYPTION_UNKNOWN_ERROR; - } - } - - $location = \OCP\Util::linkToAbsolute('apps/files_encryption/files', 'error.php'); - $post = 0; - if(count($_POST) > 0) { - $post = 1; - } - - if(defined('PHPUNIT_RUN') and PHPUNIT_RUN) { - throw new \Exception("Encryption error: $errorCode"); - } - - header('Location: ' . $location . '?p=' . $post . '&errorCode=' . $errorCode); - exit(); - } - - /** - * check requirements for encryption app. - * @return bool true if requirements are met - */ - public static function checkRequirements() { - - //openssl extension needs to be loaded - return extension_loaded("openssl"); - - } - - /** - * check some common errors if the server isn't configured properly for encryption - * @return bool true if configuration seems to be OK - */ - public static function checkConfiguration() { - if(self::getOpenSSLPkey()) { - return true; - } else { - while ($msg = openssl_error_string()) { - \OCP\Util::writeLog('Encryption library', 'openssl_pkey_new() fails: ' . $msg, \OCP\Util::ERROR); - } - return false; - } - } - - /** - * Create an openssl pkey with config-supplied settings - * WARNING: This initializes a new private keypair, which is computationally expensive - * @return resource The pkey resource created - */ - public static function getOpenSSLPkey() { - return openssl_pkey_new(self::getOpenSSLConfig()); - } - - /** - * Return an array of OpenSSL config options, default + config - * Used for multiple OpenSSL functions - * @return array The combined defaults and config settings - */ - public static function getOpenSSLConfig() { - $config = array('private_key_bits' => 4096); - $config = array_merge(\OC::$server->getConfig()->getSystemValue('openssl', array()), $config); - return $config; - } - - /** - * remember from which file the tmp file (getLocalFile() call) was created - * @param string $tmpFile path of tmp file - * @param string $originalFile path of the original file relative to data/ - */ - public static function addTmpFileToMapper($tmpFile, $originalFile) { - self::$tmpFileMapping[$tmpFile] = $originalFile; - } - - /** - * get the path of the original file - * @param string $tmpFile path of the tmp file - * @return string|false path of the original file or false - */ - public static function getPathFromTmpFile($tmpFile) { - if (isset(self::$tmpFileMapping[$tmpFile])) { - return self::$tmpFileMapping[$tmpFile]; - } - - return false; - } - - /** - * detect file type, encryption can read/write regular files, versions - * and cached files - * - * @param string $path - * @return int - * @throws Exception\EncryptionException - */ - public static function detectFileType($path) { - $parts = self::splitPath($path); - - if (count($parts) > 2) { - switch ($parts[2]) { - case 'files': - return Util::FILE_TYPE_FILE; - case 'files_versions': - return Util::FILE_TYPE_VERSION; - case 'cache': - return Util::FILE_TYPE_CACHE; - } - } - - // thow exception if we couldn't detect a valid file type - throw new Exception\EncryptionException('Could not detect file type', Exception\EncryptionException::GENERIC); - } - - /** - * read the cipher used for encryption from the config.php - * - * @return string - */ - public static function getCipher() { - - $cipher = \OC::$server->getConfig()->getSystemValue('cipher', Crypt::DEFAULT_CIPHER); - - if ($cipher !== 'AES-256-CFB' && $cipher !== 'AES-128-CFB') { - \OCP\Util::writeLog('files_encryption', - 'wrong cipher defined in config.php, only AES-128-CFB and AES-256-CFB is supported. Fall back ' . Crypt::DEFAULT_CIPHER, - \OCP\Util::WARN); - - $cipher = Crypt::DEFAULT_CIPHER; - } - - return $cipher; - } - - public static function splitPath($path) { - $normalized = \OC\Files\Filesystem::normalizePath($path); - return explode('/', $normalized); - } - -} - diff --git a/apps/files_encryption/lib/hooks.php b/apps/files_encryption/lib/hooks.php deleted file mode 100644 index 4a29ffaaedf..00000000000 --- a/apps/files_encryption/lib/hooks.php +++ /dev/null @@ -1,625 +0,0 @@ -<?php -/** - * @author Björn Schießle <schiessle@owncloud.com> - * @author Morris Jobke <hey@morrisjobke.de> - * - * @copyright Copyright (c) 2015, ownCloud, Inc. - * @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/> - * - */ - -namespace OCA\Files_Encryption; - -/** - * Class for hook specific logic - */ -class Hooks { - - // file for which we want to rename the keys after the rename operation was successful - private static $renamedFiles = array(); - // file for which we want to delete the keys after the delete operation was successful - private static $deleteFiles = array(); - // file for which we want to delete the keys after the delete operation was successful - private static $unmountedFiles = array(); - - /** - * Startup encryption backend upon user login - * @note This method should never be called for users using client side encryption - */ - public static function login($params) { - - if (\OCP\App::isEnabled('files_encryption') === false) { - return true; - } - - - $l = new \OC_L10N('files_encryption'); - - $view = new \OC\Files\View('/'); - - // ensure filesystem is loaded - if (!\OC\Files\Filesystem::$loaded) { - \OC_Util::setupFS($params['uid']); - } - - $privateKey = Keymanager::getPrivateKey($view, $params['uid']); - - // if no private key exists, check server configuration - if (!$privateKey) { - //check if all requirements are met - if (!Helper::checkRequirements() || !Helper::checkConfiguration()) { - $error_msg = $l->t("Missing requirements."); - $hint = $l->t('Please make sure that OpenSSL together with the PHP extension is enabled and configured properly. For now, the encryption app has been disabled.'); - \OC_App::disable('files_encryption'); - \OCP\Util::writeLog('Encryption library', $error_msg . ' ' . $hint, \OCP\Util::ERROR); - \OCP\Template::printErrorPage($error_msg, $hint); - } - } - - $util = new Util($view, $params['uid']); - - // setup user, if user not ready force relogin - if (Helper::setupUser($util, $params['password']) === false) { - return false; - } - - $session = $util->initEncryption($params); - - // Check if first-run file migration has already been performed - $ready = false; - $migrationStatus = $util->getMigrationStatus(); - if ($migrationStatus === Util::MIGRATION_OPEN && $session !== false) { - $ready = $util->beginMigration(); - } elseif ($migrationStatus === Util::MIGRATION_IN_PROGRESS) { - // refuse login as long as the initial encryption is running - sleep(5); - \OCP\User::logout(); - return false; - } - - $result = true; - - // If migration not yet done - if ($ready) { - - // Encrypt existing user files - try { - $result = $util->encryptAll('/' . $params['uid'] . '/' . 'files'); - } catch (\Exception $ex) { - \OCP\Util::writeLog('Encryption library', 'Initial encryption failed! Error: ' . $ex->getMessage(), \OCP\Util::FATAL); - $result = false; - } - - if ($result) { - \OC_Log::write( - 'Encryption library', 'Encryption of existing files belonging to "' . $params['uid'] . '" completed' - , \OC_Log::INFO - ); - // Register successful migration in DB - $util->finishMigration(); - } else { - \OCP\Util::writeLog('Encryption library', 'Initial encryption failed!', \OCP\Util::FATAL); - $util->resetMigrationStatus(); - \OCP\User::logout(); - } - } - - return $result; - } - - /** - * remove keys from session during logout - */ - public static function logout() { - $session = new Session(new \OC\Files\View()); - $session->removeKeys(); - } - - /** - * setup encryption backend upon user created - * @note This method should never be called for users using client side encryption - */ - public static function postCreateUser($params) { - - if (\OCP\App::isEnabled('files_encryption')) { - $view = new \OC\Files\View('/'); - $util = new Util($view, $params['uid']); - Helper::setupUser($util, $params['password']); - } - } - - /** - * cleanup encryption backend upon user deleted - * @note This method should never be called for users using client side encryption - */ - public static function postDeleteUser($params) { - - if (\OCP\App::isEnabled('files_encryption')) { - Keymanager::deletePublicKey(new \OC\Files\View(), $params['uid']); - } - } - - /** - * If the password can't be changed within ownCloud, than update the key password in advance. - */ - public static function preSetPassphrase($params) { - if (\OCP\App::isEnabled('files_encryption')) { - if ( ! \OC_User::canUserChangePassword($params['uid']) ) { - self::setPassphrase($params); - } - } - } - - /** - * Change a user's encryption passphrase - * @param array $params keys: uid, password - */ - public static function setPassphrase($params) { - if (\OCP\App::isEnabled('files_encryption') === false) { - return true; - } - - // Only attempt to change passphrase if server-side encryption - // is in use (client-side encryption does not have access to - // the necessary keys) - if (Crypt::mode() === 'server') { - - $view = new \OC\Files\View('/'); - $session = new Session($view); - - // Get existing decrypted private key - $privateKey = $session->getPrivateKey(); - - if ($params['uid'] === \OCP\User::getUser() && $privateKey) { - - // Encrypt private key with new user pwd as passphrase - $encryptedPrivateKey = Crypt::symmetricEncryptFileContent($privateKey, $params['password'], Helper::getCipher()); - - // Save private key - if ($encryptedPrivateKey) { - Keymanager::setPrivateKey($encryptedPrivateKey, \OCP\User::getUser()); - } else { - \OCP\Util::writeLog('files_encryption', 'Could not update users encryption password', \OCP\Util::ERROR); - } - - // NOTE: Session does not need to be updated as the - // private key has not changed, only the passphrase - // used to decrypt it has changed - - - } else { // admin changed the password for a different user, create new keys and reencrypt file keys - - $user = $params['uid']; - $util = new Util($view, $user); - $recoveryPassword = isset($params['recoveryPassword']) ? $params['recoveryPassword'] : null; - - // we generate new keys if... - // ...we have a recovery password and the user enabled the recovery key - // ...encryption was activated for the first time (no keys exists) - // ...the user doesn't have any files - if (($util->recoveryEnabledForUser() && $recoveryPassword) - || !$util->userKeysExists() - || !$view->file_exists($user . '/files')) { - - // backup old keys - $util->backupAllKeys('recovery'); - - $newUserPassword = $params['password']; - - // make sure that the users home is mounted - \OC\Files\Filesystem::initMountPoints($user); - - $keypair = Crypt::createKeypair(); - - // Disable encryption proxy to prevent recursive calls - $proxyStatus = \OC_FileProxy::$enabled; - \OC_FileProxy::$enabled = false; - - // Save public key - Keymanager::setPublicKey($keypair['publicKey'], $user); - - // Encrypt private key with new password - $encryptedKey = Crypt::symmetricEncryptFileContent($keypair['privateKey'], $newUserPassword, Helper::getCipher()); - if ($encryptedKey) { - Keymanager::setPrivateKey($encryptedKey, $user); - - if ($recoveryPassword) { // if recovery key is set we can re-encrypt the key files - $util = new Util($view, $user); - $util->recoverUsersFiles($recoveryPassword); - } - } else { - \OCP\Util::writeLog('files_encryption', 'Could not update users encryption password', \OCP\Util::ERROR); - } - - \OC_FileProxy::$enabled = $proxyStatus; - } - } - } - } - - /** - * after password reset we create a new key pair for the user - * - * @param array $params - */ - public static function postPasswordReset($params) { - $uid = $params['uid']; - $password = $params['password']; - - $util = new Util(new \OC\Files\View(), $uid); - $util->replaceUserKeys($password); - } - - /* - * check if files can be encrypted to every user. - */ - /** - * @param array $params - */ - public static function preShared($params) { - - if (\OCP\App::isEnabled('files_encryption') === false) { - return true; - } - - $l = new \OC_L10N('files_encryption'); - $users = array(); - $view = new \OC\Files\View('/'); - - switch ($params['shareType']) { - case \OCP\Share::SHARE_TYPE_USER: - $users[] = $params['shareWith']; - break; - case \OCP\Share::SHARE_TYPE_GROUP: - $users = \OC_Group::usersInGroup($params['shareWith']); - break; - } - - $notConfigured = array(); - foreach ($users as $user) { - if (!Keymanager::publicKeyExists($view, $user)) { - $notConfigured[] = $user; - } - } - - if (count($notConfigured) > 0) { - $params['run'] = false; - $params['error'] = $l->t('Following users are not set up for encryption:') . ' ' . join(', ' , $notConfigured); - } - - } - - /** - * update share keys if a file was shared - */ - public static function postShared($params) { - - if (\OCP\App::isEnabled('files_encryption') === false) { - return true; - } - - if ($params['itemType'] === 'file' || $params['itemType'] === 'folder') { - - $path = \OC\Files\Filesystem::getPath($params['fileSource']); - - self::updateKeyfiles($path); - } - } - - /** - * update keyfiles and share keys recursively - * - * @param string $path to the file/folder - */ - private static function updateKeyfiles($path) { - $view = new \OC\Files\View('/'); - $userId = \OCP\User::getUser(); - $session = new Session($view); - $util = new Util($view, $userId); - $sharingEnabled = \OCP\Share::isEnabled(); - - $mountManager = \OC\Files\Filesystem::getMountManager(); - $mount = $mountManager->find('/' . $userId . '/files' . $path); - $mountPoint = $mount->getMountPoint(); - - // if a folder was shared, get a list of all (sub-)folders - if ($view->is_dir('/' . $userId . '/files' . $path)) { - $allFiles = $util->getAllFiles($path, $mountPoint); - } else { - $allFiles = array($path); - } - - foreach ($allFiles as $path) { - $usersSharing = $util->getSharingUsersArray($sharingEnabled, $path); - $util->setSharedFileKeyfiles($session, $usersSharing, $path); - } - } - - /** - * unshare file/folder from a user with whom you shared the file before - */ - public static function postUnshare($params) { - - if (\OCP\App::isEnabled('files_encryption') === false) { - return true; - } - - if ($params['itemType'] === 'file' || $params['itemType'] === 'folder') { - - $view = new \OC\Files\View('/'); - $userId = $params['uidOwner']; - $userView = new \OC\Files\View('/' . $userId . '/files'); - $util = new Util($view, $userId); - $path = $userView->getPath($params['fileSource']); - - // for group shares get a list of the group members - if ($params['shareType'] === \OCP\Share::SHARE_TYPE_GROUP) { - $userIds = \OC_Group::usersInGroup($params['shareWith']); - } else { - if ($params['shareType'] === \OCP\Share::SHARE_TYPE_LINK || $params['shareType'] === \OCP\Share::SHARE_TYPE_REMOTE) { - $userIds = array($util->getPublicShareKeyId()); - } else { - $userIds = array($params['shareWith']); - } - } - - $mountManager = \OC\Files\Filesystem::getMountManager(); - $mount = $mountManager->find('/' . $userId . '/files' . $path); - $mountPoint = $mount->getMountPoint(); - - // if we unshare a folder we need a list of all (sub-)files - if ($params['itemType'] === 'folder') { - $allFiles = $util->getAllFiles($path, $mountPoint); - } else { - $allFiles = array($path); - } - - foreach ($allFiles as $path) { - - // check if the user still has access to the file, otherwise delete share key - $sharingUsers = $util->getSharingUsersArray(true, $path); - - // Unshare every user who no longer has access to the file - $delUsers = array_diff($userIds, $sharingUsers); - $keyPath = Keymanager::getKeyPath($view, $util, $path); - - // delete share key - Keymanager::delShareKey($view, $delUsers, $keyPath, $userId, $path); - } - - } - } - - /** - * mark file as renamed so that we know the original source after the file was renamed - * @param array $params with the old path and the new path - */ - public static function preRename($params) { - self::preRenameOrCopy($params, 'rename'); - } - - /** - * mark file as copied so that we know the original source after the file was copied - * @param array $params with the old path and the new path - */ - public static function preCopy($params) { - self::preRenameOrCopy($params, 'copy'); - } - - private static function preRenameOrCopy($params, $operation) { - $user = \OCP\User::getUser(); - $view = new \OC\Files\View('/'); - $util = new Util($view, $user); - - // we only need to rename the keys if the rename happens on the same mountpoint - // otherwise we perform a stream copy, so we get a new set of keys - $oldPath = \OC\Files\Filesystem::normalizePath('/' . $user . '/files/' . $params['oldpath']); - $newPath = \OC\Files\Filesystem::normalizePath('/' . $user . '/files/' . $params['newpath']); - $mp1 = $view->getMountPoint($oldPath); - $mp2 = $view->getMountPoint($newPath); - - $oldKeysPath = Keymanager::getKeyPath($view, $util, $params['oldpath']); - - if ($mp1 === $mp2) { - self::$renamedFiles[$params['oldpath']] = array( - 'operation' => $operation, - 'oldKeysPath' => $oldKeysPath, - ); - } elseif ($mp1 !== $oldPath . '/') { - self::$renamedFiles[$params['oldpath']] = array( - 'operation' => 'cleanup', - 'oldKeysPath' => $oldKeysPath, - ); - } - } - - /** - * after a file is renamed/copied, rename/copy its keyfile and share-keys also fix the file size and fix also the sharing - * - * @param array $params array with oldpath and newpath - */ - public static function postRenameOrCopy($params) { - - if (\OCP\App::isEnabled('files_encryption') === false) { - return true; - } - - $view = new \OC\Files\View('/'); - $userId = \OCP\User::getUser(); - $util = new Util($view, $userId); - - if (isset(self::$renamedFiles[$params['oldpath']]['operation']) && - isset(self::$renamedFiles[$params['oldpath']]['oldKeysPath'])) { - $operation = self::$renamedFiles[$params['oldpath']]['operation']; - $oldKeysPath = self::$renamedFiles[$params['oldpath']]['oldKeysPath']; - unset(self::$renamedFiles[$params['oldpath']]); - if ($operation === 'cleanup') { - return $view->unlink($oldKeysPath); - } - } else { - \OCP\Util::writeLog('Encryption library', "can't get path and owner from the file before it was renamed", \OCP\Util::DEBUG); - return false; - } - - list($ownerNew, $pathNew) = $util->getUidAndFilename($params['newpath']); - - if ($util->isSystemWideMountPoint($pathNew)) { - $newKeysPath = 'files_encryption/keys/' . $pathNew; - } else { - $newKeysPath = $ownerNew . '/files_encryption/keys/' . $pathNew; - } - - // create key folders if it doesn't exists - if (!$view->file_exists(dirname($newKeysPath))) { - $view->mkdir(dirname($newKeysPath)); - } - - $view->$operation($oldKeysPath, $newKeysPath); - - // update sharing-keys - self::updateKeyfiles($params['newpath']); - } - - /** - * set migration status and the init status back to '0' so that all new files get encrypted - * if the app gets enabled again - * @param array $params contains the app ID - */ - public static function preDisable($params) { - if ($params['app'] === 'files_encryption') { - - \OC::$server->getConfig()->deleteAppFromAllUsers('files_encryption'); - - $session = new Session(new \OC\Files\View('/')); - $session->setInitialized(Session::NOT_INITIALIZED); - } - } - - /** - * set the init status to 'NOT_INITIALIZED' (0) if the app gets enabled - * @param array $params contains the app ID - */ - public static function postEnable($params) { - if ($params['app'] === 'files_encryption') { - $session = new Session(new \OC\Files\View('/')); - $session->setInitialized(Session::NOT_INITIALIZED); - } - } - - /** - * if the file was really deleted we remove the encryption keys - * @param array $params - * @return boolean|null - */ - public static function postDelete($params) { - - $path = $params[\OC\Files\Filesystem::signal_param_path]; - - if (!isset(self::$deleteFiles[$path])) { - return true; - } - - $deletedFile = self::$deleteFiles[$path]; - $keyPath = $deletedFile['keyPath']; - - // we don't need to remember the file any longer - unset(self::$deleteFiles[$path]); - - $view = new \OC\Files\View('/'); - - // return if the file still exists and wasn't deleted correctly - if ($view->file_exists('/' . \OCP\User::getUser() . '/files/' . $path)) { - return true; - } - - // Delete keyfile & shareKey so it isn't orphaned - $view->unlink($keyPath); - - } - - /** - * remember the file which should be deleted and it's owner - * @param array $params - * @return boolean|null - */ - public static function preDelete($params) { - $view = new \OC\Files\View('/'); - $path = $params[\OC\Files\Filesystem::signal_param_path]; - - // skip this method if the trash bin is enabled or if we delete a file - // outside of /data/user/files - if (\OCP\App::isEnabled('files_trashbin')) { - return true; - } - - $util = new Util($view, \OCP\USER::getUser()); - - $keysPath = Keymanager::getKeyPath($view, $util, $path); - - self::$deleteFiles[$path] = array( - 'keyPath' => $keysPath); - } - - /** - * unmount file from yourself - * remember files/folders which get unmounted - */ - public static function preUnmount($params) { - $view = new \OC\Files\View('/'); - $user = \OCP\User::getUser(); - $path = $params[\OC\Files\Filesystem::signal_param_path]; - - $util = new Util($view, $user); - list($owner, $ownerPath) = $util->getUidAndFilename($path); - - $keysPath = Keymanager::getKeyPath($view, $util, $path); - - self::$unmountedFiles[$path] = array( - 'keyPath' => $keysPath, - 'owner' => $owner, - 'ownerPath' => $ownerPath - ); - } - - /** - * unmount file from yourself - */ - public static function postUnmount($params) { - - $path = $params[\OC\Files\Filesystem::signal_param_path]; - $user = \OCP\User::getUser(); - - if (!isset(self::$unmountedFiles[$path])) { - return true; - } - - $umountedFile = self::$unmountedFiles[$path]; - $keyPath = $umountedFile['keyPath']; - $owner = $umountedFile['owner']; - $ownerPath = $umountedFile['ownerPath']; - - $view = new \OC\Files\View(); - - // we don't need to remember the file any longer - unset(self::$unmountedFiles[$path]); - - // check if the user still has access to the file, otherwise delete share key - $sharingUsers = \OCP\Share::getUsersSharingFile($path, $user); - if (!in_array($user, $sharingUsers['users'])) { - Keymanager::delShareKey($view, array($user), $keyPath, $owner, $ownerPath); - } - } - -} diff --git a/apps/files_encryption/lib/keymanager.php b/apps/files_encryption/lib/keymanager.php deleted file mode 100644 index 5e33372e9c6..00000000000 --- a/apps/files_encryption/lib/keymanager.php +++ /dev/null @@ -1,500 +0,0 @@ -<?php -/** - * @author Björn Schießle <schiessle@owncloud.com> - * @author Christopher Schäpers <kondou@ts.unde.re> - * @author Florin Peter <github@florin-peter.de> - * @author Joas Schilling <nickvergessen@owncloud.com> - * @author Jörn Friedrich Dreyer <jfd@butonic.de> - * @author Morris Jobke <hey@morrisjobke.de> - * @author Robin Appelman <icewind@owncloud.com> - * @author Robin McCorkell <rmccorkell@karoshi.org.uk> - * @author Sam Tuke <mail@samtuke.com> - * @author Thomas Müller <thomas.mueller@tmit.eu> - * @author Vincent Petry <pvince81@owncloud.com> - * - * @copyright Copyright (c) 2015, ownCloud, Inc. - * @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/> - * - */ - -namespace OCA\Files_Encryption; - -/** - * Class to manage storage and retrieval of encryption keys - * @note Where a method requires a view object, it's root must be '/' - */ -class Keymanager { - - // base dir where all the file related keys are stored - private static $keys_base_dir = '/files_encryption/keys/'; - private static $encryption_base_dir = '/files_encryption'; - private static $public_key_dir = '/files_encryption/public_keys'; - - private static $key_cache = array(); // cache keys - - /** - * read key from hard disk - * - * @param string $path to key - * @param \OC\Files\View $view - * @return string|bool either the key or false - */ - private static function getKey($path, $view) { - - $key = false; - - if (isset(self::$key_cache[$path])) { - $key = self::$key_cache[$path]; - } else { - - /** @var \OCP\Files\Storage $storage */ - list($storage, $internalPath) = $view->resolvePath($path); - - if ($storage->file_exists($internalPath)) { - $key = $storage->file_get_contents($internalPath); - self::$key_cache[$path] = $key; - } - - } - - return $key; - } - - /** - * write key to disk - * - * - * @param string $path path to key directory - * @param string $name key name - * @param string $key key - * @param \OC\Files\View $view - * @return bool - */ - private static function setKey($path, $name, $key, $view) { - self::keySetPreparation($view, $path); - - /** @var \OCP\Files\Storage $storage */ - $pathToKey = \OC\Files\Filesystem::normalizePath($path . '/' . $name); - list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($pathToKey); - $result = $storage->file_put_contents($internalPath, $key); - - if (is_int($result) && $result > 0) { - self::$key_cache[$pathToKey] = $key; - return true; - } - - return false; - } - - /** - * retrieve the ENCRYPTED private key from a user - * - * @param \OC\Files\View $view - * @param string $user - * @return string private key or false (hopefully) - * @note the key returned by this method must be decrypted before use - */ - public static function getPrivateKey(\OC\Files\View $view, $user) { - $path = '/' . $user . '/' . 'files_encryption' . '/' . $user . '.privateKey'; - return self::getKey($path, $view); - } - - /** - * retrieve public key for a specified user - * @param \OC\Files\View $view - * @param string $userId - * @return string public key or false - */ - public static function getPublicKey(\OC\Files\View $view, $userId) { - $path = self::$public_key_dir . '/' . $userId . '.publicKey'; - return self::getKey($path, $view); - } - - public static function getPublicKeyPath() { - return self::$public_key_dir; - } - - /** - * Retrieve a user's public and private key - * @param \OC\Files\View $view - * @param string $userId - * @return array keys: privateKey, publicKey - */ - public static function getUserKeys(\OC\Files\View $view, $userId) { - - return array( - 'publicKey' => self::getPublicKey($view, $userId), - 'privateKey' => self::getPrivateKey($view, $userId) - ); - - } - - /** - * Retrieve public keys for given users - * @param \OC\Files\View $view - * @param array $userIds - * @return array of public keys for the specified users - */ - public static function getPublicKeys(\OC\Files\View $view, array $userIds) { - - $keys = array(); - foreach ($userIds as $userId) { - $keys[$userId] = self::getPublicKey($view, $userId); - } - - return $keys; - - } - - /** - * store file encryption key - * - * @param \OC\Files\View $view - * @param \OCA\Files_Encryption\Util $util - * @param string $path relative path of the file, including filename - * @param string $catfile keyfile content - * @return bool true/false - * @note The keyfile is not encrypted here. Client code must - * asymmetrically encrypt the keyfile before passing it to this method - */ - public static function setFileKey(\OC\Files\View $view, $util, $path, $catfile) { - $path = self::getKeyPath($view, $util, $path); - return self::setKey($path, 'fileKey', $catfile, $view); - - } - - /** - * get path to key folder for a given file - * - * @param \OC\Files\View $view relative to data directory - * @param \OCA\Files_Encryption\Util $util - * @param string $path path to the file, relative to the users file directory - * @return string - */ - public static function getKeyPath($view, $util, $path) { - - if ($view->is_dir('/' . \OCP\User::getUser() . '/' . $path)) { - throw new Exception\EncryptionException('file was expected but directoy was given', Exception\EncryptionException::GENERIC); - } - - list($owner, $filename) = $util->getUidAndFilename($path); - $filename = Helper::stripPartialFileExtension($filename); - $filePath_f = ltrim($filename, '/'); - - // in case of system wide mount points the keys are stored directly in the data directory - if ($util->isSystemWideMountPoint($filename)) { - $keyPath = self::$keys_base_dir . $filePath_f . '/'; - } else { - $keyPath = '/' . $owner . self::$keys_base_dir . $filePath_f . '/'; - } - - return $keyPath; - } - - /** - * get path to file key for a given file - * - * @param \OC\Files\View $view relative to data directory - * @param \OCA\Files_Encryption\Util $util - * @param string $path path to the file, relative to the users file directory - * @return string - */ - public static function getFileKeyPath($view, $util, $path) { - $keyDir = self::getKeyPath($view, $util, $path); - return $keyDir . 'fileKey'; - } - - /** - * get path to share key for a given user - * - * @param \OC\Files\View $view relateive to data directory - * @param \OCA\Files_Encryption\Util $util - * @param string $path path to file relative to the users files directoy - * @param string $uid user for whom we want the share-key path - * @retrun string - */ - public static function getShareKeyPath($view, $util, $path, $uid) { - $keyDir = self::getKeyPath($view, $util, $path); - return $keyDir . $uid . '.shareKey'; - } - - /** - * delete key - * - * @param \OC\Files\View $view - * @param string $path - * @return boolean - */ - private static function deleteKey($view, $path) { - $normalizedPath = \OC\Files\Filesystem::normalizePath($path); - $result = $view->unlink($normalizedPath); - - if ($result) { - unset(self::$key_cache[$normalizedPath]); - return true; - } - - return false; - } - - /** - * delete public key from a given user - * - * @param \OC\Files\View $view - * @param string $uid user - * @return bool - */ - public static function deletePublicKey($view, $uid) { - - $result = false; - - if (!\OCP\User::userExists($uid)) { - $publicKey = self::$public_key_dir . '/' . $uid . '.publicKey'; - self::deleteKey($view, $publicKey); - } - - return $result; - } - - /** - * check if public key for user exists - * - * @param \OC\Files\View $view - * @param string $uid - */ - public static function publicKeyExists($view, $uid) { - return $view->file_exists(self::$public_key_dir . '/'. $uid . '.publicKey'); - } - - - - /** - * retrieve keyfile for an encrypted file - * @param \OC\Files\View $view - * @param \OCA\Files_Encryption\Util $util - * @param string|false $filePath - * @return string file key or false - * @note The keyfile returned is asymmetrically encrypted. Decryption - * of the keyfile must be performed by client code - */ - public static function getFileKey($view, $util, $filePath) { - $path = self::getFileKeyPath($view, $util, $filePath); - return self::getKey($path, $view); - } - - /** - * store private key from the user - * @param string $key - * @return bool - * @note Encryption of the private key must be performed by client code - * as no encryption takes place here - */ - public static function setPrivateKey($key, $user = '') { - - $user = $user === '' ? \OCP\User::getUser() : $user; - $path = '/' . $user . '/files_encryption'; - $header = Crypt::generateHeader(); - - return self::setKey($path, $user . '.privateKey', $header . $key, new \OC\Files\View()); - - } - - /** - * check if recovery key exists - * - * @param \OC\Files\View $view - * @return bool - */ - public static function recoveryKeyExists($view) { - - $result = false; - - $recoveryKeyId = Helper::getRecoveryKeyId(); - if ($recoveryKeyId) { - $result = ($view->file_exists(self::$public_key_dir . '/' . $recoveryKeyId . ".publicKey") - && $view->file_exists(self::$encryption_base_dir . '/' . $recoveryKeyId . ".privateKey")); - } - - return $result; - } - - public static function publicShareKeyExists($view) { - $result = false; - - $publicShareKeyId = Helper::getPublicShareKeyId(); - if ($publicShareKeyId) { - $result = ($view->file_exists(self::$public_key_dir . '/' . $publicShareKeyId . ".publicKey") - && $view->file_exists(self::$encryption_base_dir . '/' . $publicShareKeyId . ".privateKey")); - - } - - return $result; - } - - /** - * store public key from the user - * @param string $key - * @param string $user - * - * @return bool - */ - public static function setPublicKey($key, $user = '') { - - $user = $user === '' ? \OCP\User::getUser() : $user; - - return self::setKey(self::$public_key_dir, $user . '.publicKey', $key, new \OC\Files\View('/')); - } - - /** - * write private system key (recovery and public share key) to disk - * - * @param string $key encrypted key - * @param string $keyName name of the key - * @return boolean - */ - public static function setPrivateSystemKey($key, $keyName) { - - $keyName = $keyName . '.privateKey'; - $header = Crypt::generateHeader(); - - return self::setKey(self::$encryption_base_dir, $keyName,$header . $key, new \OC\Files\View()); - } - - /** - * read private system key (recovery and public share key) from disk - * - * @param string $keyName name of the key - * @return string|boolean private system key or false - */ - public static function getPrivateSystemKey($keyName) { - $path = $keyName . '.privateKey'; - return self::getKey($path, new \OC\Files\View(self::$encryption_base_dir)); - } - - /** - * store multiple share keys for a single file - * @param \OC\Files\View $view - * @param \OCA\Files_Encryption\Util $util - * @param string $path - * @param array $shareKeys - * @return bool - */ - public static function setShareKeys($view, $util, $path, array $shareKeys) { - - // in case of system wide mount points the keys are stored directly in the data directory - $basePath = Keymanager::getKeyPath($view, $util, $path); - - self::keySetPreparation($view, $basePath); - - $result = true; - - foreach ($shareKeys as $userId => $shareKey) { - if (!self::setKey($basePath, $userId . '.shareKey', $shareKey, $view)) { - // If any of the keys are not set, flag false - $result = false; - } - } - - // Returns false if any of the keys weren't set - return $result; - } - - /** - * retrieve shareKey for an encrypted file - * @param \OC\Files\View $view - * @param string $userId - * @param \OCA\Files_Encryption\Util $util - * @param string $filePath - * @return string file key or false - * @note The sharekey returned is encrypted. Decryption - * of the keyfile must be performed by client code - */ - public static function getShareKey($view, $userId, $util, $filePath) { - $path = self::getShareKeyPath($view, $util, $filePath, $userId); - return self::getKey($path, $view); - } - - /** - * Delete a single user's shareKey for a single file - * - * @param \OC\Files\View $view relative to data/ - * @param array $userIds list of users we want to remove - * @param string $keyPath - * @param string $owner the owner of the file - * @param string $ownerPath the owners name of the file for which we want to remove the users relative to data/user/files - */ - public static function delShareKey($view, $userIds, $keysPath, $owner, $ownerPath) { - - $key = array_search($owner, $userIds, true); - if ($key !== false && $view->file_exists('/' . $owner . '/files/' . $ownerPath)) { - unset($userIds[$key]); - } - - self::recursiveDelShareKeys($keysPath, $userIds, $view); - - } - - /** - * recursively delete share keys from given users - * - * @param string $dir directory - * @param array $userIds user ids for which the share keys should be deleted - * @param \OC\Files\View $view view relative to data/ - */ - private static function recursiveDelShareKeys($dir, $userIds, $view) { - - $dirContent = $view->opendir($dir); - - if (is_resource($dirContent)) { - while (($file = readdir($dirContent)) !== false) { - if (!\OC\Files\Filesystem::isIgnoredDir($file)) { - if ($view->is_dir($dir . '/' . $file)) { - self::recursiveDelShareKeys($dir . '/' . $file, $userIds, $view); - } else { - foreach ($userIds as $userId) { - if ($userId . '.shareKey' === $file) { - \OCP\Util::writeLog('files_encryption', 'recursiveDelShareKey: delete share key: ' . $file, \OCP\Util::DEBUG); - self::deleteKey($view, $dir . '/' . $file); - } - } - } - } - } - closedir($dirContent); - } - } - - /** - * Make preparations to vars and filesystem for saving a keyfile - * - * @param \OC\Files\View $view - * @param string $path relatvie to the views root - * @param string $basePath - */ - protected static function keySetPreparation($view, $path) { - // If the file resides within a subdirectory, create it - if (!$view->file_exists($path)) { - $sub_dirs = explode('/', $path); - $dir = ''; - foreach ($sub_dirs as $sub_dir) { - $dir .= '/' . $sub_dir; - if (!$view->is_dir($dir)) { - $view->mkdir($dir); - } - } - } - } - -} diff --git a/apps/files_encryption/lib/migration.php b/apps/files_encryption/lib/migration.php deleted file mode 100644 index 3f8ca9f4e23..00000000000 --- a/apps/files_encryption/lib/migration.php +++ /dev/null @@ -1,302 +0,0 @@ -<?php -/** - * @author Arthur Schiwon <blizzz@owncloud.com> - * @author Björn Schießle <schiessle@owncloud.com> - * @author Joas Schilling <nickvergessen@owncloud.com> - * @author Morris Jobke <hey@morrisjobke.de> - * @author Robin Appelman <icewind@owncloud.com> - * @author Thomas Müller <thomas.mueller@tmit.eu> - * @author Vincent Petry <pvince81@owncloud.com> - * - * @copyright Copyright (c) 2015, ownCloud, Inc. - * @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/> - * - */ - -namespace OCA\Files_Encryption; - - -class Migration { - - /** - * @var \OC\Files\View - */ - private $view; - private $public_share_key_id; - private $recovery_key_id; - - public function __construct() { - $this->view = new \OC\Files\View(); - $this->view->getUpdater()->disable(); - $this->public_share_key_id = Helper::getPublicShareKeyId(); - $this->recovery_key_id = Helper::getRecoveryKeyId(); - } - - public function reorganizeFolderStructure() { - $this->reorganizeSystemFolderStructure(); - - $limit = 500; - $offset = 0; - do { - $users = \OCP\User::getUsers('', $limit, $offset); - foreach ($users as $user) { - $this->reorganizeFolderStructureForUser($user); - } - $offset += $limit; - } while (count($users) >= $limit); - } - - public function reorganizeSystemFolderStructure() { - - $this->createPathForKeys('/files_encryption'); - - // backup system wide folders - $this->backupSystemWideKeys(); - - // rename public keys - $this->renamePublicKeys(); - - // rename system wide mount point - $this->renameFileKeys('', '/files_encryption/keyfiles'); - - // rename system private keys - $this->renameSystemPrivateKeys(); - - // delete old system wide folders - $this->view->deleteAll('/public-keys'); - $this->view->deleteAll('/owncloud_private_key'); - $this->view->deleteAll('/files_encryption/share-keys'); - $this->view->deleteAll('/files_encryption/keyfiles'); - $storage = $this->view->getMount('')->getStorage(); - $storage->getScanner()->scan('files_encryption'); - $storage->getCache()->remove('owncloud_private_key'); - $storage->getCache()->remove('public-keys'); - } - - - public function reorganizeFolderStructureForUser($user) { - // backup all keys - \OC_Util::tearDownFS(); - \OC_Util::setupFS($user); - if ($this->backupUserKeys($user)) { - // create new 'key' folder - $this->view->mkdir($user . '/files_encryption/keys'); - // rename users private key - $this->renameUsersPrivateKey($user); - // rename file keys - $path = $user . '/files_encryption/keyfiles'; - $this->renameFileKeys($user, $path); - $trashPath = $user . '/files_trashbin/keyfiles'; - if (\OC_App::isEnabled('files_trashbin') && $this->view->is_dir($trashPath)) { - $this->renameFileKeys($user, $trashPath, true); - $this->view->deleteAll($trashPath); - $this->view->deleteAll($user . '/files_trashbin/share-keys'); - } - // delete old folders - $this->deleteOldKeys($user); - $this->view->getMount('/' . $user)->getStorage()->getScanner()->scan('files_encryption'); - } - } - - private function backupSystemWideKeys() { - $backupDir = 'encryption_migration_backup_' . date("Y-m-d_H-i-s"); - $this->view->mkdir($backupDir); - $this->view->copy('owncloud_private_key', $backupDir . '/owncloud_private_key'); - $this->view->copy('public-keys', $backupDir . '/public-keys'); - $this->view->copy('files_encryption', $backupDir . '/files_encryption'); - } - - private function backupUserKeys($user) { - $encryptionDir = $user . '/files_encryption'; - if ($this->view->is_dir($encryptionDir)) { - $backupDir = $user . '/encryption_migration_backup_' . date("Y-m-d_H-i-s"); - $this->view->mkdir($backupDir); - $this->view->copy($encryptionDir, $backupDir); - return true; - } - return false; - } - - private function renamePublicKeys() { - $dh = $this->view->opendir('public-keys'); - - $this->createPathForKeys('files_encryption/public_keys'); - - if (is_resource($dh)) { - while (($oldPublicKey = readdir($dh)) !== false) { - if (!\OC\Files\Filesystem::isIgnoredDir($oldPublicKey)) { - $newPublicKey = substr($oldPublicKey, 0, strlen($oldPublicKey) - strlen('.public.key')) . '.publicKey'; - $this->view->rename('public-keys/' . $oldPublicKey, 'files_encryption/public_keys/' . $newPublicKey); - } - } - closedir($dh); - } - } - - private function renameSystemPrivateKeys() { - $dh = $this->view->opendir('owncloud_private_key'); - - if (is_resource($dh)) { - while (($oldPrivateKey = readdir($dh)) !== false) { - if (!\OC\Files\Filesystem::isIgnoredDir($oldPrivateKey)) { - $newPrivateKey = substr($oldPrivateKey, 0, strlen($oldPrivateKey) - strlen('.private.key')) . '.privateKey'; - $this->view->rename('owncloud_private_key/' . $oldPrivateKey, 'files_encryption/' . $newPrivateKey); - } - } - closedir($dh); - } - } - - private function renameUsersPrivateKey($user) { - $oldPrivateKey = $user . '/files_encryption/' . $user . '.private.key'; - $newPrivateKey = substr($oldPrivateKey, 0, strlen($oldPrivateKey) - strlen('.private.key')) . '.privateKey'; - - $this->view->rename($oldPrivateKey, $newPrivateKey); - } - - private function getFileName($file, $trash) { - - $extLength = strlen('.key'); - - if ($trash) { - $parts = explode('.', $file); - if ($parts[count($parts) - 1] !== 'key') { - $extLength = $extLength + strlen('.' . $parts[count($parts) - 1]); - } - } - - $filename = substr($file, 0, strlen($file) - $extLength); - - return $filename; - } - - private function getExtension($file, $trash) { - - $extension = ''; - - if ($trash) { - $parts = explode('.', $file); - if ($parts[count($parts) - 1] !== 'key') { - $extension = '.' . $parts[count($parts) - 1]; - } - } - - return $extension; - } - - private function getFilePath($path, $user, $trash) { - $offset = $trash ? strlen($user . '/files_trashbin/keyfiles') : strlen($user . '/files_encryption/keyfiles'); - return substr($path, $offset); - } - - private function getTargetDir($user, $filePath, $filename, $extension, $trash) { - if ($trash) { - $targetDir = $user . '/files_trashbin/keys/' . $filePath . '/' . $filename . $extension; - } else { - $targetDir = $user . '/files_encryption/keys/' . $filePath . '/' . $filename . $extension; - } - - return $targetDir; - } - - private function renameFileKeys($user, $path, $trash = false) { - - $dh = $this->view->opendir($path); - - if (is_resource($dh)) { - while (($file = readdir($dh)) !== false) { - if (!\OC\Files\Filesystem::isIgnoredDir($file)) { - if ($this->view->is_dir($path . '/' . $file)) { - $this->renameFileKeys($user, $path . '/' . $file, $trash); - } else { - $filename = $this->getFileName($file, $trash); - $filePath = $this->getFilePath($path, $user, $trash); - $extension = $this->getExtension($file, $trash); - $targetDir = $this->getTargetDir($user, $filePath, $filename, $extension, $trash); - $this->createPathForKeys($targetDir); - $this->view->rename($path . '/' . $file, $targetDir . '/fileKey'); - $this->renameShareKeys($user, $filePath, $filename, $targetDir, $trash); - } - } - } - closedir($dh); - } - } - - private function getOldShareKeyPath($user, $filePath, $trash) { - if ($trash) { - $oldShareKeyPath = $user . '/files_trashbin/share-keys/' . $filePath; - } else { - $oldShareKeyPath = $user . '/files_encryption/share-keys/' . $filePath; - } - - return $oldShareKeyPath; - } - - private function getUidFromShareKey($file, $filename, $trash) { - $extLength = strlen('.shareKey'); - if ($trash) { - $parts = explode('.', $file); - if ($parts[count($parts) - 1] !== 'shareKey') { - $extLength = $extLength + strlen('.' . $parts[count($parts) - 1]); - } - } - - $uid = substr($file, strlen($filename) + 1, $extLength * -1); - - return $uid; - } - - private function renameShareKeys($user, $filePath, $filename, $target, $trash) { - $oldShareKeyPath = $this->getOldShareKeyPath($user, $filePath, $trash); - $dh = $this->view->opendir($oldShareKeyPath); - - if (is_resource($dh)) { - while (($file = readdir($dh)) !== false) { - if (!\OC\Files\Filesystem::isIgnoredDir($file)) { - if ($this->view->is_dir($oldShareKeyPath . '/' . $file)) { - continue; - } else { - if (substr($file, 0, strlen($filename) + 1) === $filename . '.') { - - $uid = $this->getUidFromShareKey($file, $filename, $trash); - $this->view->rename($oldShareKeyPath . '/' . $file, $target . '/' . $uid . '.shareKey'); - } - } - - } - } - closedir($dh); - } - } - - private function deleteOldKeys($user) { - $this->view->deleteAll($user . '/files_encryption/keyfiles'); - $this->view->deleteAll($user . '/files_encryption/share-keys'); - } - - private function createPathForKeys($path) { - if (!$this->view->file_exists($path)) { - $sub_dirs = explode('/', $path); - $dir = ''; - foreach ($sub_dirs as $sub_dir) { - $dir .= '/' . $sub_dir; - if (!$this->view->is_dir($dir)) { - $this->view->mkdir($dir); - } - } - } - } -} diff --git a/apps/files_encryption/lib/proxy.php b/apps/files_encryption/lib/proxy.php deleted file mode 100644 index b452c0d4e27..00000000000 --- a/apps/files_encryption/lib/proxy.php +++ /dev/null @@ -1,401 +0,0 @@ -<?php -/** - * @author Björn Schießle <schiessle@owncloud.com> - * @author Florin Peter <github@florin-peter.de> - * @author Joas Schilling <nickvergessen@owncloud.com> - * @author Morris Jobke <hey@morrisjobke.de> - * @author Robin Appelman <icewind@owncloud.com> - * @author Robin McCorkell <rmccorkell@karoshi.org.uk> - * @author Sam Tuke <mail@samtuke.com> - * @author Thomas Müller <thomas.mueller@tmit.eu> - * @author Vincent Petry <pvince81@owncloud.com> - * - * @copyright Copyright (c) 2015, ownCloud, Inc. - * @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/> - * - */ - -/** - * Encryption proxy which handles filesystem operations before and after - * execution and encrypts, and handles keyfiles accordingly. Used for - * webui. - */ - -namespace OCA\Files_Encryption; - -/** - * Class Proxy - * @package OCA\Files_Encryption - */ -class Proxy extends \OC_FileProxy { - - private static $unencryptedSizes = array(); // remember unencrypted size - private static $fopenMode = array(); // remember the fopen mode - private static $enableEncryption = false; // Enable encryption for the given path - - - /** - * check if path is excluded from encryption - * - * @param string $path relative to data/ - * @return boolean - */ - protected function isExcludedPath($path) { - - $view = new \OC\Files\View(); - - $normalizedPath = \OC\Files\Filesystem::normalizePath($path); - - $parts = explode('/', $normalizedPath); - - // we only encrypt/decrypt files in the files and files_versions folder - if (sizeof($parts) < 3) { - /** - * Less then 3 parts means, we can't match: - * - /{$uid}/files/* nor - * - /{$uid}/files_versions/* - * So this is not a path we are looking for. - */ - return true; - } - if( - !($parts[2] === 'files' && \OCP\User::userExists($parts[1])) && - !($parts[2] === 'files_versions' && \OCP\User::userExists($parts[1]))) { - - return true; - } - - if (!$view->file_exists($normalizedPath)) { - $normalizedPath = dirname($normalizedPath); - } - - // we don't encrypt server-to-server shares - list($storage, ) = \OC\Files\Filesystem::resolvePath($normalizedPath); - /** - * @var \OCP\Files\Storage $storage - */ - if ($storage->instanceOfStorage('OCA\Files_Sharing\External\Storage')) { - return true; - } - - return false; - } - - /** - * Check if a file requires encryption - * @param string $path - * @param string $mode type of access - * @return bool - * - * Tests if server side encryption is enabled, and if we should call the - * crypt stream wrapper for the given file - */ - private function shouldEncrypt($path, $mode = 'w') { - - // don't call the crypt stream wrapper, if... - if ( - Crypt::mode() !== 'server' // we are not in server-side-encryption mode - || $this->isExcludedPath($path) // if path is excluded from encryption - || substr($path, 0, 8) === 'crypt://' // we are already in crypt mode - ) { - return false; - } - - $userId = Helper::getUser($path); - $view = new \OC\Files\View(''); - $util = new Util($view, $userId); - - // for write operation we always encrypt the files, for read operations - // we check if the existing file is encrypted or not decide if it needs to - // decrypt it. - if (($mode !== 'r' && $mode !== 'rb') || $util->isEncryptedPath($path)) { - return true; - } - - return false; - } - - /** - * @param string $path - * @param string $data - * @return bool - */ - public function preFile_put_contents($path, &$data) { - - if ($this->shouldEncrypt($path)) { - - if (!is_resource($data)) { - - // get root view - $view = new \OC\Files\View('/'); - - // get relative path - $relativePath = Helper::stripUserFilesPath($path); - - if (!isset($relativePath)) { - return true; - } - - // create random cache folder - $cacheFolder = rand(); - $path_slices = explode('/', \OC\Files\Filesystem::normalizePath($path)); - $path_slices[2] = "cache/".$cacheFolder; - $tmpPath = implode('/', $path_slices); - - $handle = fopen('crypt://' . $tmpPath, 'w'); - if (is_resource($handle)) { - - // write data to stream - fwrite($handle, $data); - - // close stream - fclose($handle); - - // disable encryption proxy to prevent recursive calls - $proxyStatus = \OC_FileProxy::$enabled; - \OC_FileProxy::$enabled = false; - - // get encrypted content - $data = $view->file_get_contents($tmpPath); - - // store new unenecrypted size so that it can be updated - // in the post proxy - $tmpFileInfo = $view->getFileInfo($tmpPath); - if ( isset($tmpFileInfo['unencrypted_size']) ) { - self::$unencryptedSizes[\OC\Files\Filesystem::normalizePath($path)] = $tmpFileInfo['unencrypted_size']; - } - - // remove our temp file - $view->deleteAll('/' . \OCP\User::getUser() . '/cache/' . $cacheFolder); - - // re-enable proxy - our work is done - \OC_FileProxy::$enabled = $proxyStatus; - } else { - return false; - } - } - } - - return true; - - } - - /** - * update file cache with the new unencrypted size after file was written - * @param string $path - * @param mixed $result - * @return mixed - */ - public function postFile_put_contents($path, $result) { - $normalizedPath = \OC\Files\Filesystem::normalizePath($path); - if ( isset(self::$unencryptedSizes[$normalizedPath]) ) { - $view = new \OC\Files\View('/'); - $view->putFileInfo($normalizedPath, - array('encrypted' => true, 'unencrypted_size' => self::$unencryptedSizes[$normalizedPath])); - unset(self::$unencryptedSizes[$normalizedPath]); - } - - return $result; - } - - /** - * @param string $path Path of file from which has been read - * @param string $data Data that has been read from file - */ - public function postFile_get_contents($path, $data) { - - $plainData = null; - - // If data is a catfile - if ( - Crypt::mode() === 'server' - && $this->shouldEncrypt($path) - && Crypt::isCatfileContent($data) - ) { - - $handle = fopen('crypt://' . $path, 'r'); - - if (is_resource($handle)) { - while (($plainDataChunk = fgets($handle, 8192)) !== false) { - $plainData .= $plainDataChunk; - } - } - - } - - if (!isset($plainData)) { - - $plainData = $data; - - } - - return $plainData; - - } - - /** - * remember initial fopen mode because sometimes it gets changed during the request - * @param string $path path - * @param string $mode type of access - */ - public function preFopen($path, $mode) { - - self::$fopenMode[$path] = $mode; - self::$enableEncryption = $this->shouldEncrypt($path, $mode); - - } - - - /** - * @param string $path - * @param resource $result - * @return resource - */ - public function postFopen($path, $result) { - - $path = \OC\Files\Filesystem::normalizePath($path); - - if (!$result || self::$enableEncryption === false) { - - return $result; - - } - - // if we remember the mode from the pre proxy we re-use it - // otherwise we fall back to stream_get_meta_data() - if (isset(self::$fopenMode[$path])) { - $mode = self::$fopenMode[$path]; - unset(self::$fopenMode[$path]); - } else { - $meta = stream_get_meta_data($result); - $mode = $meta['mode']; - } - - // Close the original encrypted file - fclose($result); - - // Open the file using the crypto stream wrapper - // protocol and let it do the decryption work instead - $result = fopen('crypt://' . $path, $mode); - - return $result; - - } - - /** - * @param string $path - * @param array $data - * @return array - */ - public function postGetFileInfo($path, $data) { - - // if path is a folder do nothing - if (\OCP\App::isEnabled('files_encryption') && $data !== false && array_key_exists('size', $data)) { - - // Disable encryption proxy to prevent recursive calls - $proxyStatus = \OC_FileProxy::$enabled; - \OC_FileProxy::$enabled = false; - - // get file size - $data['size'] = self::postFileSize($path, $data['size'], $data); - - // Re-enable the proxy - \OC_FileProxy::$enabled = $proxyStatus; - } - - return $data; - } - - /** - * @param string $path - * @param int $size - * @return int|bool - */ - public function postFileSize($path, $size, $fileInfo = null) { - - $view = new \OC\Files\View('/'); - - $userId = Helper::getUser($path); - $util = new Util($view, $userId); - - // if encryption is no longer enabled or if the files aren't migrated yet - // we return the default file size - if(!\OCP\App::isEnabled('files_encryption') || - $util->getMigrationStatus() !== Util::MIGRATION_COMPLETED) { - return $size; - } - - // if path is a folder do nothing - if ($view->is_dir($path)) { - $proxyState = \OC_FileProxy::$enabled; - \OC_FileProxy::$enabled = false; - $fileInfo = $view->getFileInfo($path); - \OC_FileProxy::$enabled = $proxyState; - if (isset($fileInfo['unencrypted_size']) && $fileInfo['unencrypted_size'] > 0) { - return $fileInfo['unencrypted_size']; - } - return $size; - } - - // get relative path - $relativePath = Helper::stripUserFilesPath($path); - - // if path is empty we cannot resolve anything - if (empty($relativePath)) { - return $size; - } - - // get file info from database/cache - if (empty($fileInfo)) { - $proxyState = \OC_FileProxy::$enabled; - \OC_FileProxy::$enabled = false; - $fileInfo = $view->getFileInfo($path); - \OC_FileProxy::$enabled = $proxyState; - } - - // if file is encrypted return real file size - if (isset($fileInfo['encrypted']) && $fileInfo['encrypted'] === true) { - // try to fix unencrypted file size if it doesn't look plausible - if ((int)$fileInfo['size'] > 0 && (int)$fileInfo['unencrypted_size'] === 0 ) { - $fixSize = $util->getFileSize($path); - $fileInfo['unencrypted_size'] = $fixSize; - // put file info if not .part file - if (!Helper::isPartialFilePath($relativePath)) { - $view->putFileInfo($path, array('unencrypted_size' => $fixSize)); - } - } - $size = $fileInfo['unencrypted_size']; - } else { - - $fileInfoUpdates = array(); - - $fixSize = $util->getFileSize($path); - if ($fixSize > 0) { - $size = $fixSize; - - $fileInfoUpdates['encrypted'] = true; - $fileInfoUpdates['unencrypted_size'] = $size; - - // put file info if not .part file - if (!Helper::isPartialFilePath($relativePath)) { - $view->putFileInfo($path, $fileInfoUpdates); - } - } - - } - return $size; - } - -} diff --git a/apps/files_encryption/lib/session.php b/apps/files_encryption/lib/session.php deleted file mode 100644 index 10e4c061b30..00000000000 --- a/apps/files_encryption/lib/session.php +++ /dev/null @@ -1,203 +0,0 @@ -<?php -/** - * @author Björn Schießle <schiessle@owncloud.com> - * @author Florin Peter <github@florin-peter.de> - * @author Joas Schilling <nickvergessen@owncloud.com> - * @author Jörn Friedrich Dreyer <jfd@butonic.de> - * @author Morris Jobke <hey@morrisjobke.de> - * @author Robin Appelman <icewind@owncloud.com> - * @author Robin McCorkell <rmccorkell@karoshi.org.uk> - * @author Sam Tuke <mail@samtuke.com> - * @author Thomas Müller <thomas.mueller@tmit.eu> - * - * @copyright Copyright (c) 2015, ownCloud, Inc. - * @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/> - * - */ - -namespace OCA\Files_Encryption; - -/** - * Class for handling encryption related session data - */ - -class Session { - - private $view; - private static $publicShareKey = false; - - const NOT_INITIALIZED = '0'; - const INIT_EXECUTED = '1'; - const INIT_SUCCESSFUL = '2'; - - - /** - * if session is started, check if ownCloud key pair is set up, if not create it - * @param \OC\Files\View $view - * - * @note The ownCloud key pair is used to allow public link sharing even if encryption is enabled - */ - public function __construct($view) { - - $this->view = $view; - - if (!$this->view->is_dir('files_encryption')) { - - $this->view->mkdir('files_encryption'); - - } - - $appConfig = \OC::$server->getAppConfig(); - - $publicShareKeyId = Helper::getPublicShareKeyId(); - - if ($publicShareKeyId === false) { - $publicShareKeyId = 'pubShare_' . substr(md5(time()), 0, 8); - $appConfig->setValue('files_encryption', 'publicShareKeyId', $publicShareKeyId); - } - - if (!Keymanager::publicShareKeyExists($view)) { - - $keypair = Crypt::createKeypair(); - - - // Save public key - Keymanager::setPublicKey($keypair['publicKey'], $publicShareKeyId); - - // Encrypt private key empty passphrase - $cipher = Helper::getCipher(); - $encryptedKey = Crypt::symmetricEncryptFileContent($keypair['privateKey'], '', $cipher); - if ($encryptedKey) { - Keymanager::setPrivateSystemKey($encryptedKey, $publicShareKeyId); - } else { - \OCP\Util::writeLog('files_encryption', 'Could not create public share keys', \OCP\Util::ERROR); - } - - } - - if (Helper::isPublicAccess() && !self::getPublicSharePrivateKey()) { - // Disable encryption proxy to prevent recursive calls - $proxyStatus = \OC_FileProxy::$enabled; - \OC_FileProxy::$enabled = false; - - $encryptedKey = Keymanager::getPrivateSystemKey($publicShareKeyId); - $privateKey = Crypt::decryptPrivateKey($encryptedKey, ''); - self::setPublicSharePrivateKey($privateKey); - - \OC_FileProxy::$enabled = $proxyStatus; - } - } - - /** - * Sets user private key to session - * @param string $privateKey - * @return bool - * - * @note this should only be set on login - */ - public function setPrivateKey($privateKey) { - - \OC::$server->getSession()->set('privateKey', $privateKey); - - return true; - - } - - /** - * remove keys from session - */ - public function removeKeys() { - \OC::$server->getSession()->remove('publicSharePrivateKey'); - \OC::$server->getSession()->remove('privateKey'); - } - - /** - * Sets status of encryption app - * @param string $init INIT_SUCCESSFUL, INIT_EXECUTED, NOT_INITIALIZED - * @return bool - * - * @note this doesn not indicate of the init was successful, we just remeber the try! - */ - public function setInitialized($init) { - - \OC::$server->getSession()->set('encryptionInitialized', $init); - - return true; - - } - - /** - * remove encryption keys and init status from session - */ - public function closeSession() { - \OC::$server->getSession()->remove('encryptionInitialized'); - \OC::$server->getSession()->remove('privateKey'); - } - - - /** - * Gets status if we already tried to initialize the encryption app - * @return string init status INIT_SUCCESSFUL, INIT_EXECUTED, NOT_INITIALIZED - * - * @note this doesn not indicate of the init was successful, we just remeber the try! - */ - public function getInitialized() { - if (!is_null(\OC::$server->getSession()->get('encryptionInitialized'))) { - return \OC::$server->getSession()->get('encryptionInitialized'); - } else if (Helper::isPublicAccess() && self::getPublicSharePrivateKey()) { - return self::INIT_SUCCESSFUL; - } else { - return self::NOT_INITIALIZED; - } - } - - /** - * Gets user or public share private key from session - * @return string $privateKey The user's plaintext private key - * - */ - public function getPrivateKey() { - // return the public share private key if this is a public access - if (Helper::isPublicAccess()) { - return self::getPublicSharePrivateKey(); - } else { - if (!is_null(\OC::$server->getSession()->get('privateKey'))) { - return \OC::$server->getSession()->get('privateKey'); - } else { - return false; - } - } - } - - /** - * Sets public user private key to session - * @param string $privateKey - * @return bool - */ - private static function setPublicSharePrivateKey($privateKey) { - self::$publicShareKey = $privateKey; - return true; - } - - /** - * Gets public share private key from session - * @return string $privateKey - * - */ - private static function getPublicSharePrivateKey() { - return self::$publicShareKey; - } - -} diff --git a/apps/files_encryption/lib/stream.php b/apps/files_encryption/lib/stream.php deleted file mode 100644 index 4cbf9e4a4b7..00000000000 --- a/apps/files_encryption/lib/stream.php +++ /dev/null @@ -1,700 +0,0 @@ -<?php -/** - * @author Björn Schießle <schiessle@owncloud.com> - * @author Florin Peter <github@florin-peter.de> - * @author jknockaert <jasper@knockaert.nl> - * @author Joas Schilling <nickvergessen@owncloud.com> - * @author Morris Jobke <hey@morrisjobke.de> - * @author Robin McCorkell <rmccorkell@karoshi.org.uk> - * @author Sam Tuke <mail@samtuke.com> - * @author Vincent Petry <pvince81@owncloud.com> - * - * @copyright Copyright (c) 2015, ownCloud, Inc. - * @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/> - * - */ - -/** - * transparently encrypted filestream - * - * you can use it as wrapper around an existing stream by setting CryptStream::$sourceStreams['foo']=array('path'=>$path,'stream'=>$stream) - * and then fopen('crypt://streams/foo'); - */ - -namespace OCA\Files_Encryption; - -use OCA\Files_Encryption\Exception\EncryptionException; - -/** - * Provides 'crypt://' stream wrapper protocol. - * @note We use a stream wrapper because it is the most secure way to handle - * decrypted content transfers. There is no safe way to decrypt the entire file - * somewhere on the server, so we have to encrypt and decrypt blocks on the fly. - * @note Paths used with this protocol MUST BE RELATIVE. Use URLs like: - * crypt://filename, or crypt://subdirectory/filename, NOT - * crypt:///home/user/owncloud/data. Otherwise keyfiles will be put in - * [owncloud]/data/user/files_encryption/keyfiles/home/user/owncloud/data and - * will not be accessible to other methods. - * @note Data read and written must always be 8192 bytes long, as this is the - * buffer size used internally by PHP. The encryption process makes the input - * data longer, and input is chunked into smaller pieces in order to result in - * a 8192 encrypted block size. - * @note When files are deleted via webdav, or when they are updated and the - * previous version deleted, this is handled by OC\Files\View, and thus the - * encryption proxies are used and keyfiles deleted. - */ -class Stream { - - const PADDING_CHAR = '-'; - - private $plainKey; - private $encKeyfiles; - private $rawPath; // The raw path relative to the data dir - private $relPath; // rel path to users file dir - private $userId; - private $keyId; - private $handle; // Resource returned by fopen - private $meta = array(); // Header / meta for source stream - private $cache; // Current block unencrypted - private $position; // Current pointer position in the unencrypted stream - private $writeFlag; // Flag to write current block when leaving it - private $size; - private $headerSize = 0; // Size of header - private $unencryptedSize; - private $publicKey; - private $encKeyfile; - private $newFile; // helper var, we only need to write the keyfile for new files - private $isLocalTmpFile = false; // do we operate on a local tmp file - private $localTmpFile; // path of local tmp file - private $containHeader = false; // the file contain a header - private $cipher; // cipher used for encryption/decryption - /** @var \OCA\Files_Encryption\Util */ - private $util; - - /** - * @var \OC\Files\View - */ - private $rootView; // a fsview object set to '/' - - /** - * @var \OCA\Files_Encryption\Session - */ - private $session; - private $privateKey; - - /** - * @param string $path raw path relative to data/ - * @param string $mode - * @param int $options - * @param string $opened_path - * @return bool - * @throw \OCA\Files_Encryption\Exception\EncryptionException - */ - public function stream_open($path, $mode, $options, &$opened_path) { - - // read default cipher from config - $this->cipher = Helper::getCipher(); - - // assume that the file already exist before we decide it finally in getKey() - $this->newFile = false; - - $this->rootView = new \OC\Files\View('/'); - - $this->session = new Session($this->rootView); - - $this->privateKey = $this->session->getPrivateKey(); - if ($this->privateKey === false) { - throw new EncryptionException('Session does not contain a private key, maybe your login password changed?', - EncryptionException::PRIVATE_KEY_MISSING); - } - - $normalizedPath = \OC\Files\Filesystem::normalizePath(str_replace('crypt://', '', $path)); - $originalFile = Helper::getPathFromTmpFile($normalizedPath); - if ($originalFile) { - $this->rawPath = $originalFile; - $this->isLocalTmpFile = true; - $this->localTmpFile = $normalizedPath; - } else { - $this->rawPath = $normalizedPath; - } - - $this->util = new Util($this->rootView, Helper::getUser($this->rawPath)); - - // get the key ID which we want to use, can be the users key or the - // public share key - $this->keyId = $this->util->getKeyId(); - - $fileType = Helper::detectFileType($this->rawPath); - - switch ($fileType) { - case Util::FILE_TYPE_FILE: - $this->relPath = Helper::stripUserFilesPath($this->rawPath); - $user = \OC::$server->getUserSession()->getUser(); - $this->userId = $user ? $user->getUID() : Helper::getUserFromPath($this->rawPath); - break; - case Util::FILE_TYPE_VERSION: - $this->relPath = Helper::getPathFromVersion($this->rawPath); - $this->userId = Helper::getUserFromPath($this->rawPath); - break; - case Util::FILE_TYPE_CACHE: - $this->relPath = Helper::getPathFromCachedFile($this->rawPath); - Helper::mkdirr($this->rawPath, new \OC\Files\View('/')); - $user = \OC::$server->getUserSession()->getUser(); - $this->userId = $user ? $user->getUID() : Helper::getUserFromPath($this->rawPath); - break; - default: - \OCP\Util::writeLog('Encryption library', 'failed to open file "' . $this->rawPath . '" expecting a path to "files", "files_versions" or "cache"', \OCP\Util::ERROR); - return false; - } - - // Disable fileproxies so we can get the file size and open the source file without recursive encryption - $proxyStatus = \OC_FileProxy::$enabled; - \OC_FileProxy::$enabled = false; - - $this->position = 0; - $this->cache = ''; - $this->writeFlag = 0; - - // Setting handle so it can be used for reading the header - if ($this->isLocalTmpFile) { - $this->handle = fopen($this->localTmpFile, $mode); - } else { - $this->handle = $this->rootView->fopen($this->rawPath, $mode); - } - - if ( - $mode === 'w' - or $mode === 'w+' - or $mode === 'wb' - or $mode === 'wb+' - ) { - // We're writing a new file so start write counter with 0 bytes - $this->size = 0; - $this->unencryptedSize = 0; - } else { - $this->size = $this->rootView->filesize($this->rawPath); - \OC_FileProxy::$enabled = true; - $this->unencryptedSize = $this->rootView->filesize($this->rawPath); - \OC_FileProxy::$enabled = false; - $this->readHeader(); - } - - \OC_FileProxy::$enabled = $proxyStatus; - - if (!is_resource($this->handle)) { - - \OCP\Util::writeLog('Encryption library', 'failed to open file "' . $this->rawPath . '"', \OCP\Util::ERROR); - - } else { - - $this->meta = stream_get_meta_data($this->handle); - // sometimes fopen changes the mode, e.g. for a url "r" convert to "r+" - // but we need to remember the original access type - $this->meta['mode'] = $mode; - - } - - - return is_resource($this->handle); - - } - - private function readHeader() { - - if (is_resource($this->handle)) { - $data = fread($this->handle, Crypt::BLOCKSIZE); - - $header = Crypt::parseHeader($data); - $this->cipher = Crypt::getCipher($header); - - // remeber that we found a header - if (!empty($header)) { - $this->containHeader = true; - $this->headerSize = Crypt::BLOCKSIZE; - // if there's no header then decrypt the block and store it in the cache - } else { - if (!$this->getKey()) { - throw new \Exception('Encryption key not found for "' . $this->rawPath . '" during attempted read via stream'); - } else { - $this->cache = Crypt::symmetricDecryptFileContent($data, $this->plainKey, $this->cipher); - } - } - - } - } - - /** - * Returns the current position of the file pointer - * @return int position of the file pointer - */ - public function stream_tell() { - return $this->position; - } - - /** - * @param int $offset - * @param int $whence - * @return bool true if fseek was successful, otherwise false - */ - - // seeking the stream tries to move the pointer on the encrypted stream to the beginning of the target block - // if that works, it flushes the current block and changes the position in the unencrypted stream - public function stream_seek($offset, $whence = SEEK_SET) { - // this wrapper needs to return "true" for success. - // the fseek call itself returns 0 on succeess - - $return=false; - - switch($whence) { - case SEEK_SET: - if($offset < $this->unencryptedSize && $offset >= 0) { - $newPosition=$offset; - } - break; - case SEEK_CUR: - if($offset>=0) { - $newPosition=$offset+$this->position; - } - break; - case SEEK_END: - if($this->unencryptedSize + $offset >= 0) { - $newPosition=$this->unencryptedSize+$offset; - } - break; - default: - return $return; - } - $newFilePosition=floor($newPosition/6126)*Crypt::BLOCKSIZE+$this->headerSize; - if (fseek($this->handle, $newFilePosition)===0) { - $this->flush(); - $this->position=$newPosition; - $return=true; - } - return $return; - - } - - /** - * @param int $count - * @return bool|string - * @throws \OCA\Files_Encryption\Exception\EncryptionException - */ - public function stream_read($count) { - - $result = ''; - - // limit to the end of the unencrypted file; otherwise getFileSize will fail and it is good practise anyway - $count=min($count,$this->unencryptedSize - $this->position); - - // loop over the 6126 sized unencrypted blocks - while ($count > 0) { - - $remainingLength = $count; - - // update the cache of the current block - $this->readCache(); - - // determine the relative position in the current block - $blockPosition=($this->position % 6126); - - // if entire read inside current block then only position needs to be updated - if ($remainingLength<(6126 - $blockPosition)) { - $result .= substr($this->cache,$blockPosition,$remainingLength); - $this->position += $remainingLength; - $count=0; - // otherwise remainder of current block is fetched, the block is flushed and the position updated - } else { - $result .= substr($this->cache,$blockPosition); - $this->flush(); - $this->position += (6126 - $blockPosition); - $count -= (6126 - $blockPosition); - } - - } - - return $result; - - } - - /** - * Encrypt and pad data ready for writing to disk - * @param string $plainData data to be encrypted - * @param string $key key to use for encryption - * @return string encrypted data on success, false on failure - */ - public function preWriteEncrypt($plainData, $key) { - - // Encrypt data to 'catfile', which includes IV - if ($encrypted = Crypt::symmetricEncryptFileContent($plainData, $key, $this->cipher)) { - - return $encrypted; - - } else { - - return false; - - } - - } - - /** - * Fetch the plain encryption key for the file and set it as plainKey property - * @internal param bool $generate if true, a new key will be generated if none can be found - * @return bool true on key found and set, false on key not found and new key generated and set - */ - public function getKey() { - - // Check if key is already set - if (isset($this->plainKey) && isset($this->encKeyfile)) { - - return true; - - } - - // Fetch and decrypt keyfile - // Fetch existing keyfile - $this->encKeyfile = Keymanager::getFileKey($this->rootView, $this->util, $this->relPath); - - // If a keyfile already exists - if ($this->encKeyfile) { - - $shareKey = Keymanager::getShareKey($this->rootView, $this->keyId, $this->util, $this->relPath); - - // if there is no valid private key return false - if ($this->privateKey === false) { - // if private key is not valid redirect user to a error page - Helper::redirectToErrorPage($this->session); - return false; - } - - if ($shareKey === false) { - // if no share key is available redirect user to a error page - Helper::redirectToErrorPage($this->session, Crypt::ENCRYPTION_NO_SHARE_KEY_FOUND); - return false; - } - - $this->plainKey = Crypt::multiKeyDecrypt($this->encKeyfile, $shareKey, $this->privateKey); - - return true; - - } else { - - $this->newFile = true; - - return false; - - } - - } - - /** - * write header at beginning of encrypted file - * - * @throws \OCA\Files_Encryption\Exception\EncryptionException - */ - private function writeHeader() { - - $header = Crypt::generateHeader(); - - if (strlen($header) > Crypt::BLOCKSIZE) { - throw new EncryptionException('max header size exceeded', EncryptionException::ENCRYPTION_HEADER_TO_LARGE); - } - - $paddedHeader = str_pad($header, Crypt::BLOCKSIZE, self::PADDING_CHAR, STR_PAD_RIGHT); - - fwrite($this->handle, $paddedHeader); - $this->headerWritten = true; - $this->containHeader = true; - $this->headerSize = Crypt::BLOCKSIZE; - $this->size += $this->headerSize; - } - - /** - * Handle plain data from the stream, and write it in 8192 byte blocks - * @param string $data data to be written to disk - * @note the data will be written to the path stored in the stream handle, set in stream_open() - * @note $data is only ever be a maximum of 8192 bytes long. This is set by PHP internally. stream_write() is called multiple times in a loop on data larger than 8192 bytes - * @note Because the encryption process used increases the length of $data, a cache is used to carry over data which would not fit in the required block size - * @note Padding is added to each encrypted block to ensure that the resulting block is exactly 8192 bytes. This is removed during stream_read - * @note PHP automatically updates the file pointer after writing data to reflect it's length. There is generally no need to update the poitner manually using fseek - */ - public function stream_write($data) { - - // if there is no valid private key return false - if ($this->privateKey === false) { - $this->size = 0; - return strlen($data); - } - - if ($this->size === 0) { - $this->writeHeader(); - } - - // Get / generate the keyfile for the file we're handling - // If we're writing a new file (not overwriting an existing - // one), save the newly generated keyfile - if (!$this->getKey()) { - - $this->plainKey = Crypt::generateKey(); - - } - - $length=0; - - // loop over $data to fit it in 6126 sized unencrypted blocks - while (strlen($data) > 0) { - - $remainingLength = strlen($data); - - // set the cache to the current 6126 block - $this->readCache(); - - // only allow writes on seekable streams, or at the end of the encrypted stream - // for seekable streams the pointer is moved back to the beginning of the encrypted block - // flush will start writing there when the position moves to another block - if((fseek($this->handle, floor($this->position/6126)*Crypt::BLOCKSIZE + $this->headerSize) === 0) || (floor($this->position/6126)*Crypt::BLOCKSIZE + $this->headerSize === $this->size)) { - - // switch the writeFlag so flush() will write the block - $this->writeFlag=1; - - // determine the relative position in the current block - $blockPosition=($this->position % 6126); - - // check if $data fits in current block - // if so, overwrite existing data (if any) - // update position and liberate $data - if ($remainingLength<(6126 - $blockPosition)) { - $this->cache=substr($this->cache,0,$blockPosition).$data.substr($this->cache,$blockPosition+$remainingLength); - $this->position += $remainingLength; - $length += $remainingLength; - $data = ''; - // if $data doens't fit the current block, the fill the current block and reiterate - // after the block is filled, it is flushed and $data is updated - } else { - $this->cache=substr($this->cache,0,$blockPosition).substr($data,0,6126-$blockPosition); - $this->flush(); - $this->position += (6126 - $blockPosition); - $length += (6126 - $blockPosition); - $data = substr($data, 6126 - $blockPosition); - } - - } else { - $data=''; - } - } - - $this->unencryptedSize = max($this->unencryptedSize,$this->position); - - return $length; - - } - - - /** - * @param int $option - * @param int $arg1 - * @param int|null $arg2 - */ - public function stream_set_option($option, $arg1, $arg2) { - $return = false; - switch ($option) { - case STREAM_OPTION_BLOCKING: - $return = stream_set_blocking($this->handle, $arg1); - break; - case STREAM_OPTION_READ_TIMEOUT: - $return = stream_set_timeout($this->handle, $arg1, $arg2); - break; - case STREAM_OPTION_WRITE_BUFFER: - $return = stream_set_write_buffer($this->handle, $arg1); - } - - return $return; - } - - /** - * @return array - */ - public function stream_stat() { - return fstat($this->handle); - } - - /** - * @param int $mode - */ - public function stream_lock($mode) { - return flock($this->handle, $mode); - } - - /** - * @return bool - */ - public function stream_flush() { - - $this->flush(); - return fflush($this->handle); - // Not a typo: http://php.net/manual/en/function.fflush.php - - } - - /** - * @return bool - */ - public function stream_eof() { - return ($this->position>=$this->unencryptedSize); - } - - private function flush() { - - // write to disk only when writeFlag was set to 1 - if ($this->writeFlag === 1) { - // 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 - $proxyStatus = \OC_FileProxy::$enabled; - \OC_FileProxy::$enabled = false; - // Set keyfile property for file in question - $this->getKey(); - $encrypted = $this->preWriteEncrypt($this->cache, $this->plainKey); - fwrite($this->handle, $encrypted); - $this->writeFlag = 0; - $this->size = max($this->size,ftell($this->handle)); - \OC_FileProxy::$enabled = $proxyStatus; - } - // always empty the cache (otherwise readCache() will not fill it with the new block) - $this->cache = ''; - } - - private function readCache() { - // cache should always be empty string when this function is called - // don't try to fill the cache when trying to write at the end of the unencrypted file when it coincides with new block - if ($this->cache === '' && !($this->position===$this->unencryptedSize && ($this->position % 6126)===0)) { - // Get the data from the file handle - $data = fread($this->handle, Crypt::BLOCKSIZE); - $result = ''; - if (strlen($data)) { - if (!$this->getKey()) { - // Error! We don't have a key to decrypt the file with - throw new \Exception('Encryption key not found for "'. $this->rawPath . '" during attempted read via stream'); - } else { - // Decrypt data - $result = Crypt::symmetricDecryptFileContent($data, $this->plainKey, $this->cipher); - } - } - $this->cache = $result; - } - } - - /** - * @return bool - */ - public function stream_close() { - - $this->flush(); - - // if there is no valid private key return false - if ($this->privateKey === false) { - - // cleanup - if ($this->meta['mode'] !== 'r' && $this->meta['mode'] !== 'rb' && !$this->isLocalTmpFile) { - - // Disable encryption proxy to prevent recursive calls - $proxyStatus = \OC_FileProxy::$enabled; - \OC_FileProxy::$enabled = false; - - if ($this->rootView->file_exists($this->rawPath) && $this->size === $this->headerSize) { - fclose($this->handle); - $this->rootView->unlink($this->rawPath); - } - - // Re-enable proxy - our work is done - \OC_FileProxy::$enabled = $proxyStatus; - } - - // if private key is not valid redirect user to a error page - Helper::redirectToErrorPage($this->session); - } - - if ( - $this->meta['mode'] !== 'r' && - $this->meta['mode'] !== 'rb' && - $this->isLocalTmpFile === false && - $this->size > $this->headerSize && - $this->unencryptedSize > 0 - ) { - - // only write keyfiles if it was a new file - if ($this->newFile === true) { - - // Disable encryption proxy to prevent recursive calls - $proxyStatus = \OC_FileProxy::$enabled; - \OC_FileProxy::$enabled = false; - - // Fetch user's public key - $this->publicKey = Keymanager::getPublicKey($this->rootView, $this->keyId); - - // Check if OC sharing api is enabled - $sharingEnabled = \OCP\Share::isEnabled(); - - // Get all users sharing the file includes current user - $uniqueUserIds = $this->util->getSharingUsersArray($sharingEnabled, $this->relPath); - $checkedUserIds = $this->util->filterShareReadyUsers($uniqueUserIds); - - // Fetch public keys for all sharing users - $publicKeys = Keymanager::getPublicKeys($this->rootView, $checkedUserIds['ready']); - - // Encrypt enc key for all sharing users - $this->encKeyfiles = Crypt::multiKeyEncrypt($this->plainKey, $publicKeys); - - // Save the new encrypted file key - Keymanager::setFileKey($this->rootView, $this->util, $this->relPath, $this->encKeyfiles['data']); - - // Save the sharekeys - Keymanager::setShareKeys($this->rootView, $this->util, $this->relPath, $this->encKeyfiles['keys']); - - // Re-enable proxy - our work is done - \OC_FileProxy::$enabled = $proxyStatus; - } - - // we need to update the file info for the real file, not for the - // part file. - $path = Helper::stripPartialFileExtension($this->rawPath); - - $fileInfo = array( - 'mimetype' => $this->rootView->getMimeType($this->rawPath), - 'encrypted' => true, - 'unencrypted_size' => $this->unencryptedSize, - ); - - // if we write a part file we also store the unencrypted size for - // the part file so that it can be re-used later - $this->rootView->putFileInfo($this->rawPath, $fileInfo); - if ($path !== $this->rawPath) { - $this->rootView->putFileInfo($path, $fileInfo); - } - - } - - $result = fclose($this->handle); - - if ($result === false) { - \OCP\Util::writeLog('Encryption library', 'Could not close stream, file could be corrupted', \OCP\Util::FATAL); - } - - return $result; - - } - -} diff --git a/apps/files_encryption/lib/util.php b/apps/files_encryption/lib/util.php deleted file mode 100644 index d8dd96d653f..00000000000 --- a/apps/files_encryption/lib/util.php +++ /dev/null @@ -1,1700 +0,0 @@ -<?php -/** - * @author Arthur Schiwon <blizzz@owncloud.com> - * @author Björn Schießle <schiessle@owncloud.com> - * @author Florin Peter <github@florin-peter.de> - * @author jknockaert <jasper@knockaert.nl> - * @author Joas Schilling <nickvergessen@owncloud.com> - * @author Jörn Friedrich Dreyer <jfd@butonic.de> - * @author Markus Goetz <markus@woboq.com> - * @author Morris Jobke <hey@morrisjobke.de> - * @author Robin Appelman <icewind@owncloud.com> - * @author Robin McCorkell <rmccorkell@karoshi.org.uk> - * @author Sam Tuke <mail@samtuke.com> - * @author Thomas Müller <thomas.mueller@tmit.eu> - * @author Vincent Petry <pvince81@owncloud.com> - * - * @copyright Copyright (c) 2015, ownCloud, Inc. - * @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/> - * - */ - -namespace OCA\Files_Encryption; - -/** - * Class for utilities relating to encrypted file storage system - * @param \OC\Files\View $view expected to have OC '/' as root path - * @param string $userId ID of the logged in user - * @param int $client indicating status of client side encryption. Currently - * unused, likely to become obsolete shortly - */ - -class Util { - - const MIGRATION_COMPLETED = 1; // migration to new encryption completed - const MIGRATION_IN_PROGRESS = -1; // migration is running - const MIGRATION_OPEN = 0; // user still needs to be migrated - - const FILE_TYPE_FILE = 0; - const FILE_TYPE_VERSION = 1; - const FILE_TYPE_CACHE = 2; - - /** - * @var \OC\Files\View - */ - private $view; // OC\Files\View object for filesystem operations - - /** - * @var string - */ - private $userId; // ID of the user we use to encrypt/decrypt files - - /** - * @var string - */ - private $keyId; // ID of the key we want to manipulate - - /** - * @var bool - */ - private $client; // Client side encryption mode flag - - /** - * @var string - */ - private $publicKeyDir; // Dir containing all public user keys - - /** - * @var string - */ - private $encryptionDir; // Dir containing user's files_encryption - - /** - * @var string - */ - private $keysPath; // Dir containing all file related encryption keys - - /** - * @var string - */ - private $publicKeyPath; // Path to user's public key - - /** - * @var string - */ - private $privateKeyPath; // Path to user's private key - - /** - * @var string - */ - private $userFilesDir; - - /** - * @var string - */ - private $publicShareKeyId; - - /** - * @var string - */ - private $recoveryKeyId; - - /** - * @var bool - */ - private $isPublic; - - /** - * @param \OC\Files\View $view - * @param string $userId - * @param bool $client - */ - public function __construct($view, $userId, $client = false) { - - $this->view = $view; - $this->client = $client; - $this->userId = $userId; - - $appConfig = \OC::$server->getAppConfig(); - - $this->publicShareKeyId = $appConfig->getValue('files_encryption', 'publicShareKeyId'); - $this->recoveryKeyId = $appConfig->getValue('files_encryption', 'recoveryKeyId'); - - $this->userDir = '/' . $this->userId; - $this->fileFolderName = 'files'; - $this->userFilesDir = - '/' . $userId . '/' . $this->fileFolderName; // TODO: Does this need to be user configurable? - $this->publicKeyDir = Keymanager::getPublicKeyPath(); - $this->encryptionDir = '/' . $this->userId . '/' . 'files_encryption'; - $this->keysPath = $this->encryptionDir . '/' . 'keys'; - $this->publicKeyPath = - $this->publicKeyDir . '/' . $this->userId . '.publicKey'; // e.g. data/public-keys/admin.publicKey - $this->privateKeyPath = - $this->encryptionDir . '/' . $this->userId . '.privateKey'; // e.g. data/admin/admin.privateKey - // make sure that the owners home is mounted - \OC\Files\Filesystem::initMountPoints($userId); - - if (Helper::isPublicAccess()) { - $this->keyId = $this->publicShareKeyId; - $this->isPublic = true; - } else { - $this->keyId = $this->userId; - $this->isPublic = false; - } - } - - /** - * @return bool - */ - public function ready() { - - if ( - !$this->view->file_exists($this->encryptionDir) - or !$this->view->file_exists($this->keysPath) - or !$this->view->file_exists($this->publicKeyPath) - or !$this->view->file_exists($this->privateKeyPath) - ) { - return false; - } else { - return true; - } - } - - /** - * check if the users private & public key exists - * @return boolean - */ - public function userKeysExists() { - if ( - $this->view->file_exists($this->privateKeyPath) && - $this->view->file_exists($this->publicKeyPath)) { - return true; - } else { - return false; - } - } - - /** - * create a new public/private key pair for the user - * - * @param string $password password for the private key - */ - public function replaceUserKeys($password) { - $this->backupAllKeys('password_reset'); - $this->view->unlink($this->publicKeyPath); - $this->view->unlink($this->privateKeyPath); - $this->setupServerSide($password); - } - - /** - * Sets up user folders and keys for serverside encryption - * - * @param string $passphrase to encrypt server-stored private key with - * @return bool - */ - public function setupServerSide($passphrase = null) { - - // Set directories to check / create - $setUpDirs = array( - $this->userDir, - $this->publicKeyDir, - $this->encryptionDir, - $this->keysPath - ); - - // Check / create all necessary dirs - foreach ($setUpDirs as $dirPath) { - - if (!$this->view->file_exists($dirPath)) { - - $this->view->mkdir($dirPath); - - } - - } - - // Create user keypair - // we should never override a keyfile - if ( - !$this->view->file_exists($this->publicKeyPath) - && !$this->view->file_exists($this->privateKeyPath) - ) { - - // Generate keypair - $keypair = Crypt::createKeypair(); - - if ($keypair) { - - \OC_FileProxy::$enabled = false; - - // Encrypt private key with user pwd as passphrase - $encryptedPrivateKey = Crypt::symmetricEncryptFileContent($keypair['privateKey'], $passphrase, Helper::getCipher()); - - // Save key-pair - if ($encryptedPrivateKey) { - $header = crypt::generateHeader(); - $this->view->file_put_contents($this->privateKeyPath, $header . $encryptedPrivateKey); - $this->view->file_put_contents($this->publicKeyPath, $keypair['publicKey']); - } - - \OC_FileProxy::$enabled = true; - } - - } else { - // check if public-key exists but private-key is missing - if ($this->view->file_exists($this->publicKeyPath) && !$this->view->file_exists($this->privateKeyPath)) { - \OCP\Util::writeLog('Encryption library', - 'public key exists but private key is missing for "' . $this->keyId . '"', \OCP\Util::FATAL); - return false; - } else { - if (!$this->view->file_exists($this->publicKeyPath) && $this->view->file_exists($this->privateKeyPath) - ) { - \OCP\Util::writeLog('Encryption library', - 'private key exists but public key is missing for "' . $this->keyId . '"', \OCP\Util::FATAL); - return false; - } - } - } - - return true; - - } - - /** - * @return string - */ - public function getPublicShareKeyId() { - return $this->publicShareKeyId; - } - - /** - * Check whether pwd recovery is enabled for a given user - * @return bool 1 = yes, 0 = no, false = no record - * - * @note If records are not being returned, check for a hidden space - * at the start of the uid in db - */ - public function recoveryEnabledForUser() { - - $recoveryMode = \OC::$server->getConfig()->getUserValue($this->userId, 'files_encryption', 'recovery_enabled', '0'); - - return ($recoveryMode === '1') ? true : false; - - } - - /** - * Enable / disable pwd recovery for a given user - * @param bool $enabled Whether to enable or disable recovery - * @return bool - */ - public function setRecoveryForUser($enabled) { - - $value = $enabled ? '1' : '0'; - try { - \OC::$server->getConfig()->setUserValue($this->userId, 'files_encryption', 'recovery_enabled', $value); - return true; - } catch(\OCP\PreConditionNotMetException $e) { - return false; - } - - } - - /** - * Find all files and their encryption status within a directory - * @param string $directory The path of the parent directory to search - * @param bool $found the founded files if called again - * @return array keys: plain, encrypted, broken - * @note $directory needs to be a path relative to OC data dir. e.g. - * /admin/files NOT /backup OR /home/www/oc/data/admin/files - */ - public function findEncFiles($directory, &$found = false) { - - // Disable proxy - we don't want files to be decrypted before - // we handle them - \OC_FileProxy::$enabled = false; - - if ($found === false) { - $found = array( - 'plain' => array(), - 'encrypted' => array(), - 'broken' => array(), - ); - } - - if ($this->view->is_dir($directory) && $handle = $this->view->opendir($directory)){ - if (is_resource($handle)) { - while (false !== ($file = readdir($handle))) { - - if ($file !== "." && $file !== "..") { - // skip stray part files - if (Helper::isPartialFilePath($file)) { - continue; - } - - $filePath = $directory . '/' . $this->view->getRelativePath('/' . $file); - $relPath = Helper::stripUserFilesPath($filePath); - - // If the path is a directory, search - // its contents - if ($this->view->is_dir($filePath)) { - - $this->findEncFiles($filePath, $found); - - // If the path is a file, determine - // its encryption status - } elseif ($this->view->is_file($filePath)) { - - // Disable proxies again, some- - // where they got re-enabled :/ - \OC_FileProxy::$enabled = false; - - $isEncryptedPath = $this->isEncryptedPath($filePath); - // If the file is encrypted - // NOTE: If the userId is - // empty or not set, file will - // detected as plain - // NOTE: This is inefficient; - // scanning every file like this - // will eat server resources :( - if ($isEncryptedPath) { - - $fileKey = Keymanager::getFileKey($this->view, $this, $relPath); - $shareKey = Keymanager::getShareKey($this->view, $this->userId, $this, $relPath); - // if file is encrypted but now file key is available, throw exception - if ($fileKey === false || $shareKey === false) { - \OCP\Util::writeLog('encryption library', 'No keys available to decrypt the file: ' . $filePath, \OCP\Util::ERROR); - $found['broken'][] = array( - 'name' => $file, - 'path' => $filePath, - ); - } else { - $found['encrypted'][] = array( - 'name' => $file, - 'path' => $filePath, - ); - } - - // If the file is not encrypted - } else { - - $found['plain'][] = array( - 'name' => $file, - 'path' => $relPath - ); - } - } - } - } - } - } - - \OC_FileProxy::$enabled = true; - - return $found; - } - - /** - * Check if a given path identifies an encrypted file - * @param string $path - * @return boolean - */ - public function isEncryptedPath($path) { - - // Disable encryption proxy so data retrieved is in its - // original form - $proxyStatus = \OC_FileProxy::$enabled; - \OC_FileProxy::$enabled = false; - - $data = ''; - - // we only need 24 byte from the last chunk - if ($this->view->file_exists($path)) { - $handle = $this->view->fopen($path, 'r'); - if (is_resource($handle)) { - // suppress fseek warining, we handle the case that fseek doesn't - // work in the else branch - if (@fseek($handle, -24, SEEK_END) === 0) { - $data = fgets($handle); - } else { - // if fseek failed on the storage we create a local copy from the file - // and read this one - fclose($handle); - $localFile = $this->view->getLocalFile($path); - $handle = fopen($localFile, 'r'); - if (is_resource($handle) && fseek($handle, -24, SEEK_END) === 0) { - $data = fgets($handle); - } - } - fclose($handle); - } - } - - // re-enable proxy - \OC_FileProxy::$enabled = $proxyStatus; - - return Crypt::isCatfileContent($data); - } - - /** - * get the file size of the unencrypted file - * @param string $path absolute path - * @return bool - */ - public function getFileSize($path) { - - $result = 0; - - // Disable encryption proxy to prevent recursive calls - $proxyStatus = \OC_FileProxy::$enabled; - \OC_FileProxy::$enabled = false; - - // split the path parts - $pathParts = explode('/', $path); - - if (isset($pathParts[2]) && $pathParts[2] === 'files' && $this->view->file_exists($path) - && $this->isEncryptedPath($path) - ) { - - $cipher = 'AES-128-CFB'; - $realSize = 0; - - // get the size from filesystem - $size = $this->view->filesize($path); - - // open stream - $stream = $this->view->fopen($path, "r"); - - if (is_resource($stream)) { - - // if the file contains a encryption header we - // we set the cipher - // and we update the size - if ($this->containHeader($path)) { - $data = fread($stream,Crypt::BLOCKSIZE); - $header = Crypt::parseHeader($data); - $cipher = Crypt::getCipher($header); - $size -= Crypt::BLOCKSIZE; - } - - // fast path, else the calculation for $lastChunkNr is bogus - if ($size === 0) { - \OC_FileProxy::$enabled = $proxyStatus; - return 0; - } - - // calculate last chunk nr - // next highest is end of chunks, one subtracted is last one - // we have to read the last chunk, we can't just calculate it (because of padding etc) - $lastChunkNr = ceil($size/Crypt::BLOCKSIZE)-1; - - // calculate last chunk position - $lastChunkPos = ($lastChunkNr * Crypt::BLOCKSIZE); - - // get the content of the last chunk - if (@fseek($stream, $lastChunkPos, SEEK_CUR) === 0) { - $realSize+=$lastChunkNr*6126; - } - $lastChunkContentEncrypted=''; - $count=Crypt::BLOCKSIZE; - while ($count>0) { - $data=fread($stream,Crypt::BLOCKSIZE); - $count=strlen($data); - $lastChunkContentEncrypted.=$data; - if(strlen($lastChunkContentEncrypted)>Crypt::BLOCKSIZE) { - $realSize+=6126; - $lastChunkContentEncrypted=substr($lastChunkContentEncrypted,Crypt::BLOCKSIZE); - } - } - fclose($stream); - $relPath = Helper::stripUserFilesPath($path); - $shareKey = Keymanager::getShareKey($this->view, $this->keyId, $this, $relPath); - if($shareKey===false) { - \OC_FileProxy::$enabled = $proxyStatus; - return $result; - } - $session = new Session($this->view); - $privateKey = $session->getPrivateKey(); - $plainKeyfile = $this->decryptKeyfile($relPath, $privateKey); - $plainKey = Crypt::multiKeyDecrypt($plainKeyfile, $shareKey, $privateKey); - $lastChunkContent=Crypt::symmetricDecryptFileContent($lastChunkContentEncrypted, $plainKey, $cipher); - - // calc the real file size with the size of the last chunk - $realSize += strlen($lastChunkContent); - - // store file size - $result = $realSize; - } - } - - \OC_FileProxy::$enabled = $proxyStatus; - - return $result; - } - - /** - * check if encrypted file contain a encryption header - * - * @param string $path - * @return boolean - */ - private function containHeader($path) { - // Disable encryption proxy to read the raw data - $proxyStatus = \OC_FileProxy::$enabled; - \OC_FileProxy::$enabled = false; - - $isHeader = false; - $handle = $this->view->fopen($path, 'r'); - - if (is_resource($handle)) { - $firstBlock = fread($handle, Crypt::BLOCKSIZE); - $isHeader = Crypt::isHeader($firstBlock); - } - - \OC_FileProxy::$enabled = $proxyStatus; - - return $isHeader; - } - - /** - * fix the file size of the encrypted file - * @param string $path absolute path - * @return boolean true / false if file is encrypted - */ - public function fixFileSize($path) { - - $result = false; - - // Disable encryption proxy to prevent recursive calls - $proxyStatus = \OC_FileProxy::$enabled; - \OC_FileProxy::$enabled = false; - - $realSize = $this->getFileSize($path); - - if ($realSize > 0) { - - $cached = $this->view->getFileInfo($path); - $cached['encrypted'] = true; - - // set the size - $cached['unencrypted_size'] = $realSize; - - // put file info - $this->view->putFileInfo($path, $cached); - - $result = true; - - } - - \OC_FileProxy::$enabled = $proxyStatus; - - return $result; - } - - /** - * encrypt versions from given file - * @param array $filelist list of encrypted files, relative to data/user/files - * @return boolean - */ - private function encryptVersions($filelist) { - - $successful = true; - - if (\OCP\App::isEnabled('files_versions')) { - - foreach ($filelist as $filename) { - - $versions = \OCA\Files_Versions\Storage::getVersions($this->userId, $filename); - foreach ($versions as $version) { - - $path = '/' . $this->userId . '/files_versions/' . $version['path'] . '.v' . $version['version']; - - $encHandle = fopen('crypt://' . $path . '.part', 'wb'); - - if ($encHandle === false) { - \OCP\Util::writeLog('Encryption library', 'couldn\'t open "' . $path . '", decryption failed!', \OCP\Util::FATAL); - $successful = false; - continue; - } - - $plainHandle = $this->view->fopen($path, 'rb'); - if ($plainHandle === false) { - \OCP\Util::writeLog('Encryption library', 'couldn\'t open "' . $path . '.part", decryption failed!', \OCP\Util::FATAL); - $successful = false; - continue; - } - - stream_copy_to_stream($plainHandle, $encHandle); - - fclose($encHandle); - fclose($plainHandle); - - $this->view->rename($path . '.part', $path); - } - } - } - - return $successful; - } - - /** - * decrypt versions from given file - * @param string $filelist list of decrypted files, relative to data/user/files - * @return boolean - */ - private function decryptVersions($filelist) { - - $successful = true; - - if (\OCP\App::isEnabled('files_versions')) { - - foreach ($filelist as $filename) { - - $versions = \OCA\Files_Versions\Storage::getVersions($this->userId, $filename); - foreach ($versions as $version) { - - $path = '/' . $this->userId . '/files_versions/' . $version['path'] . '.v' . $version['version']; - - $encHandle = fopen('crypt://' . $path, 'rb'); - - if ($encHandle === false) { - \OCP\Util::writeLog('Encryption library', 'couldn\'t open "' . $path . '", decryption failed!', \OCP\Util::FATAL); - $successful = false; - continue; - } - - $plainHandle = $this->view->fopen($path . '.part', 'wb'); - if ($plainHandle === false) { - \OCP\Util::writeLog('Encryption library', 'couldn\'t open "' . $path . '.part", decryption failed!', \OCP\Util::FATAL); - $successful = false; - continue; - } - - stream_copy_to_stream($encHandle, $plainHandle); - - fclose($encHandle); - fclose($plainHandle); - - $this->view->rename($path . '.part', $path); - } - } - } - - return $successful; - } - - /** - * Decrypt all files - * @return bool - */ - public function decryptAll() { - - $found = $this->findEncFiles($this->userId . '/files'); - - $successful = true; - - if ($found) { - - $versionStatus = \OCP\App::isEnabled('files_versions'); - \OC_App::disable('files_versions'); - - $decryptedFiles = array(); - - // Encrypt unencrypted files - foreach ($found['encrypted'] as $encryptedFile) { - - //relative to data/<user>/file - $relPath = Helper::stripUserFilesPath($encryptedFile['path']); - - //get file info - $fileInfo = \OC\Files\Filesystem::getFileInfo($relPath); - - //relative to /data - $rawPath = $encryptedFile['path']; - - //get timestamp - $timestamp = $fileInfo['mtime']; - - //enable proxy to use OC\Files\View to access the original file - \OC_FileProxy::$enabled = true; - - // Open enc file handle for binary reading - $encHandle = $this->view->fopen($rawPath, 'rb'); - - // Disable proxy to prevent file being encrypted again - \OC_FileProxy::$enabled = false; - - if ($encHandle === false) { - \OCP\Util::writeLog('Encryption library', 'couldn\'t open "' . $rawPath . '", decryption failed!', \OCP\Util::FATAL); - $successful = false; - continue; - } - - // Open plain file handle for binary writing, with same filename as original plain file - $plainHandle = $this->view->fopen($rawPath . '.part', 'wb'); - if ($plainHandle === false) { - \OCP\Util::writeLog('Encryption library', 'couldn\'t open "' . $rawPath . '.part", decryption failed!', \OCP\Util::FATAL); - $successful = false; - continue; - } - - // Move plain file to a temporary location - $size = stream_copy_to_stream($encHandle, $plainHandle); - if ($size === 0) { - \OCP\Util::writeLog('Encryption library', 'Zero bytes copied of "' . $rawPath . '", decryption failed!', \OCP\Util::FATAL); - $successful = false; - continue; - } - - fclose($encHandle); - fclose($plainHandle); - - $fakeRoot = $this->view->getRoot(); - $this->view->chroot('/' . $this->userId . '/files'); - - $this->view->rename($relPath . '.part', $relPath); - - //set timestamp - $this->view->touch($relPath, $timestamp); - - $this->view->chroot($fakeRoot); - - // Add the file to the cache - \OC\Files\Filesystem::putFileInfo($relPath, array( - 'encrypted' => false, - 'size' => $size, - 'unencrypted_size' => 0, - 'etag' => $fileInfo['etag'] - )); - - $decryptedFiles[] = $relPath; - - } - - if ($versionStatus) { - \OC_App::enable('files_versions'); - } - - if (!$this->decryptVersions($decryptedFiles)) { - $successful = false; - } - - // if there are broken encrypted files than the complete decryption - // was not successful - if (!empty($found['broken'])) { - $successful = false; - } - - if ($successful) { - $this->backupAllKeys('decryptAll', false, false); - $this->view->deleteAll($this->keysPath); - } - - \OC_FileProxy::$enabled = true; - } - - return $successful; - } - - /** - * Encrypt all files in a directory - * @param string $dirPath the directory whose files will be encrypted - * @return bool - * @note Encryption is recursive - */ - public function encryptAll($dirPath) { - - $result = true; - - $found = $this->findEncFiles($dirPath); - - // Disable proxy to prevent file being encrypted twice - \OC_FileProxy::$enabled = false; - - $versionStatus = \OCP\App::isEnabled('files_versions'); - \OC_App::disable('files_versions'); - - $encryptedFiles = array(); - - // Encrypt unencrypted files - foreach ($found['plain'] as $plainFile) { - - //get file info - $fileInfo = \OC\Files\Filesystem::getFileInfo($plainFile['path']); - - //relative to data/<user>/file - $relPath = $plainFile['path']; - - //relative to /data - $rawPath = '/' . $this->userId . '/files/' . $plainFile['path']; - - // keep timestamp - $timestamp = $fileInfo['mtime']; - - // Open plain file handle for binary reading - $plainHandle = $this->view->fopen($rawPath, 'rb'); - - // Open enc file handle for binary writing, with same filename as original plain file - $encHandle = fopen('crypt://' . $rawPath . '.part', 'wb'); - - if (is_resource($encHandle) && is_resource($plainHandle)) { - // Move plain file to a temporary location - $size = stream_copy_to_stream($plainHandle, $encHandle); - - fclose($encHandle); - fclose($plainHandle); - - $fakeRoot = $this->view->getRoot(); - $this->view->chroot('/' . $this->userId . '/files'); - - $this->view->rename($relPath . '.part', $relPath); - - // set timestamp - $this->view->touch($relPath, $timestamp); - - $encSize = $this->view->filesize($relPath); - - $this->view->chroot($fakeRoot); - - // Add the file to the cache - \OC\Files\Filesystem::putFileInfo($relPath, array( - 'encrypted' => true, - 'size' => $encSize, - 'unencrypted_size' => $size, - 'etag' => $fileInfo['etag'] - )); - - $encryptedFiles[] = $relPath; - } else { - \OCP\Util::writeLog('files_encryption', 'initial encryption: could not encrypt ' . $rawPath, \OCP\Util::FATAL); - $result = false; - } - } - - \OC_FileProxy::$enabled = true; - - if ($versionStatus) { - \OC_App::enable('files_versions'); - } - - $result = $result && $this->encryptVersions($encryptedFiles); - - return $result; - - } - - /** - * Return important encryption related paths - * @param string $pathName Name of the directory to return the path of - * @return string path - */ - public function getPath($pathName) { - - switch ($pathName) { - - case 'publicKeyDir': - - return $this->publicKeyDir; - - break; - - case 'encryptionDir': - - return $this->encryptionDir; - - break; - - case 'keysPath': - - return $this->keysPath; - - break; - - case 'publicKeyPath': - - return $this->publicKeyPath; - - break; - - case 'privateKeyPath': - - return $this->privateKeyPath; - - break; - } - - return false; - - } - - /** - * Returns whether the given user is ready for encryption. - * Also returns true if the given user is the public user - * or the recovery key user. - * - * @param string $user user to check - * - * @return boolean true if the user is ready, false otherwise - */ - private function isUserReady($user) { - if ($user === $this->publicShareKeyId - || $user === $this->recoveryKeyId - ) { - return true; - } - $util = new Util($this->view, $user); - return $util->ready(); - } - - /** - * Filter an array of UIDs to return only ones ready for sharing - * @param array $unfilteredUsers users to be checked for sharing readiness - * @return array as multi-dimensional array. keys: ready, unready - */ - public function filterShareReadyUsers($unfilteredUsers) { - - // This array will collect the filtered IDs - $readyIds = $unreadyIds = array(); - - // Loop through users and create array of UIDs that need new keyfiles - foreach ($unfilteredUsers as $user) { - // Check that the user is encryption capable, or is the - // public system user (for public shares) - if ($this->isUserReady($user)) { - - // Construct array of ready UIDs for Keymanager{} - $readyIds[] = $user; - - } else { - - // Construct array of unready UIDs for Keymanager{} - $unreadyIds[] = $user; - - // Log warning; we can't do necessary setup here - // because we don't have the user passphrase - \OCP\Util::writeLog('Encryption library', - '"' . $user . '" is not setup for encryption', \OCP\Util::WARN); - - } - - } - - return array( - 'ready' => $readyIds, - 'unready' => $unreadyIds - ); - - } - - /** - * Decrypt a keyfile - * @param string $filePath - * @param string $privateKey - * @return false|string - */ - private function decryptKeyfile($filePath, $privateKey) { - - // Get the encrypted keyfile - $encKeyfile = Keymanager::getFileKey($this->view, $this, $filePath); - - // The file has a shareKey and must use it for decryption - $shareKey = Keymanager::getShareKey($this->view, $this->keyId, $this, $filePath); - - $plainKeyfile = Crypt::multiKeyDecrypt($encKeyfile, $shareKey, $privateKey); - - return $plainKeyfile; - } - - /** - * Encrypt keyfile to multiple users - * @param Session $session - * @param array $users list of users which should be able to access the file - * @param string $filePath path of the file to be shared - * @return bool - */ - public function setSharedFileKeyfiles(Session $session, array $users, $filePath) { - - // Make sure users are capable of sharing - $filteredUids = $this->filterShareReadyUsers($users); - - // If we're attempting to share to unready users - if (!empty($filteredUids['unready'])) { - - \OCP\Util::writeLog('Encryption library', - 'Sharing to these user(s) failed as they are unready for encryption:"' - . print_r($filteredUids['unready'], 1), \OCP\Util::WARN); - - return false; - - } - - // Get public keys for each user, ready for generating sharekeys - $userPubKeys = Keymanager::getPublicKeys($this->view, $filteredUids['ready']); - - // Note proxy status then disable it - $proxyStatus = \OC_FileProxy::$enabled; - \OC_FileProxy::$enabled = false; - - // Get the current users's private key for decrypting existing keyfile - $privateKey = $session->getPrivateKey(); - - try { - // Decrypt keyfile - $plainKeyfile = $this->decryptKeyfile($filePath, $privateKey); - // Re-enc keyfile to (additional) sharekeys - $multiEncKey = Crypt::multiKeyEncrypt($plainKeyfile, $userPubKeys); - } catch (Exception\EncryptionException $e) { - $msg = 'set shareFileKeyFailed (code: ' . $e->getCode() . '): ' . $e->getMessage(); - \OCP\Util::writeLog('files_encryption', $msg, \OCP\Util::FATAL); - return false; - } catch (\Exception $e) { - $msg = 'set shareFileKeyFailed (unknown error): ' . $e->getMessage(); - \OCP\Util::writeLog('files_encryption', $msg, \OCP\Util::FATAL); - return false; - } - - // Save the recrypted key to it's owner's keyfiles directory - // Save new sharekeys to all necessary user directory - if ( - !Keymanager::setFileKey($this->view, $this, $filePath, $multiEncKey['data']) - || !Keymanager::setShareKeys($this->view, $this, $filePath, $multiEncKey['keys']) - ) { - - \OCP\Util::writeLog('Encryption library', - 'Keyfiles could not be saved for users sharing ' . $filePath, \OCP\Util::ERROR); - - return false; - - } - - // Return proxy to original status - \OC_FileProxy::$enabled = $proxyStatus; - - return true; - } - - /** - * Find, sanitise and format users sharing a file - * @note This wraps other methods into a portable bundle - * @param boolean $sharingEnabled - * @param string $filePath path relativ to current users files folder - */ - public function getSharingUsersArray($sharingEnabled, $filePath) { - - $appConfig = \OC::$server->getAppConfig(); - - // Check if key recovery is enabled - if ( - $appConfig->getValue('files_encryption', 'recoveryAdminEnabled') - && $this->recoveryEnabledForUser() - ) { - $recoveryEnabled = true; - } else { - $recoveryEnabled = false; - } - - // Make sure that a share key is generated for the owner too - list($owner, $ownerPath) = $this->getUidAndFilename($filePath); - - $ownerPath = Helper::stripPartialFileExtension($ownerPath); - - // always add owner to the list of users with access to the file - $userIds = array($owner); - - if ($sharingEnabled) { - - // Find out who, if anyone, is sharing the file - $result = \OCP\Share::getUsersSharingFile($ownerPath, $owner); - $userIds = \array_merge($userIds, $result['users']); - if ($result['public'] || $result['remote']) { - $userIds[] = $this->publicShareKeyId; - } - - } - - // If recovery is enabled, add the - // Admin UID to list of users to share to - if ($recoveryEnabled) { - // Find recoveryAdmin user ID - $recoveryKeyId = $appConfig->getValue('files_encryption', 'recoveryKeyId'); - // Add recoveryAdmin to list of users sharing - $userIds[] = $recoveryKeyId; - } - - // check if it is a group mount - if (\OCP\App::isEnabled("files_external")) { - $mounts = \OC_Mount_Config::getSystemMountPoints(); - foreach ($mounts as $mount) { - if ($mount['mountpoint'] == substr($ownerPath, 1, strlen($mount['mountpoint']))) { - $userIds = array_merge($userIds, $this->getUserWithAccessToMountPoint($mount['applicable']['users'], $mount['applicable']['groups'])); - } - } - } - - // Remove duplicate UIDs - $uniqueUserIds = array_unique($userIds); - - return $uniqueUserIds; - - } - - private function getUserWithAccessToMountPoint($users, $groups) { - $result = array(); - if (in_array('all', $users)) { - $result = \OCP\User::getUsers(); - } else { - $result = array_merge($result, $users); - foreach ($groups as $group) { - $result = array_merge($result, \OC_Group::usersInGroup($group)); - } - } - - return $result; - } - - /** - * set migration status - * @param int $status - * @param int $preCondition only update migration status if the previous value equals $preCondition - * @return boolean - */ - private function setMigrationStatus($status, $preCondition = null) { - - // convert to string if preCondition is set - $preCondition = ($preCondition === null) ? null : (string)$preCondition; - - try { - \OC::$server->getConfig()->setUserValue($this->userId, 'files_encryption', 'migration_status', (string)$status, $preCondition); - return true; - } catch(\OCP\PreConditionNotMetException $e) { - return false; - } - - } - - /** - * start migration mode to initially encrypt users data - * @return boolean - */ - public function beginMigration() { - - $result = $this->setMigrationStatus(self::MIGRATION_IN_PROGRESS, self::MIGRATION_OPEN); - - if ($result) { - \OCP\Util::writeLog('Encryption library', "Start migration to encryption mode for " . $this->userId, \OCP\Util::INFO); - } else { - \OCP\Util::writeLog('Encryption library', "Could not activate migration mode for " . $this->userId . ". Probably another process already started the initial encryption", \OCP\Util::WARN); - } - - return $result; - } - - public function resetMigrationStatus() { - return $this->setMigrationStatus(self::MIGRATION_OPEN); - - } - - /** - * close migration mode after users data has been encrypted successfully - * @return boolean - */ - public function finishMigration() { - $result = $this->setMigrationStatus(self::MIGRATION_COMPLETED); - - if ($result) { - \OCP\Util::writeLog('Encryption library', "Finish migration successfully for " . $this->userId, \OCP\Util::INFO); - } else { - \OCP\Util::writeLog('Encryption library', "Could not deactivate migration mode for " . $this->userId, \OCP\Util::WARN); - } - - return $result; - } - - /** - * check if files are already migrated to the encryption system - * @return int|false migration status, false = in case of no record - * @note If records are not being returned, check for a hidden space - * at the start of the uid in db - */ - public function getMigrationStatus() { - - $migrationStatus = false; - if (\OCP\User::userExists($this->userId)) { - $migrationStatus = \OC::$server->getConfig()->getUserValue($this->userId, 'files_encryption', 'migration_status', null); - if ($migrationStatus === null) { - \OC::$server->getConfig()->setUserValue($this->userId, 'files_encryption', 'migration_status', (string)self::MIGRATION_OPEN); - $migrationStatus = self::MIGRATION_OPEN; - } - } - - return (int)$migrationStatus; - - } - - /** - * get uid of the owners of the file and the path to the file - * @param string $path Path of the file to check - * @throws \Exception - * @note $shareFilePath must be relative to data/UID/files. Files - * relative to /Shared are also acceptable - * @return array - */ - public function getUidAndFilename($path) { - - $pathinfo = pathinfo($path); - $partfile = false; - $parentFolder = false; - if (array_key_exists('extension', $pathinfo) && $pathinfo['extension'] === 'part') { - // if the real file exists we check this file - $filePath = $this->userFilesDir . '/' .$pathinfo['dirname'] . '/' . $pathinfo['filename']; - if ($this->view->file_exists($filePath)) { - $pathToCheck = $pathinfo['dirname'] . '/' . $pathinfo['filename']; - } else { // otherwise we look for the parent - $pathToCheck = $pathinfo['dirname']; - $parentFolder = true; - } - $partfile = true; - } else { - $pathToCheck = $path; - } - - $view = new \OC\Files\View($this->userFilesDir); - $fileOwnerUid = $view->getOwner($pathToCheck); - - // handle public access - if ($this->isPublic) { - return array($this->userId, $path); - } else { - - // Check that UID is valid - if (!\OCP\User::userExists($fileOwnerUid)) { - throw new \Exception( - 'Could not find owner (UID = "' . var_export($fileOwnerUid, 1) . '") of file "' . $path . '"'); - } - - // NOTE: Bah, this dependency should be elsewhere - \OC\Files\Filesystem::initMountPoints($fileOwnerUid); - - // If the file owner is the currently logged in user - if ($fileOwnerUid === $this->userId) { - - // Assume the path supplied is correct - $filename = $path; - - } else { - $info = $view->getFileInfo($pathToCheck); - $ownerView = new \OC\Files\View('/' . $fileOwnerUid . '/files'); - - // Fetch real file path from DB - $filename = $ownerView->getPath($info['fileid']); - if ($parentFolder) { - $filename = $filename . '/'. $pathinfo['filename']; - } - - if ($partfile) { - $filename = $filename . '.' . $pathinfo['extension']; - } - - } - - return array( - $fileOwnerUid, - \OC\Files\Filesystem::normalizePath($filename) - ); - } - } - - /** - * go recursively through a dir and collect all files and sub files. - * @param string $dir relative to the users files folder - * @return array with list of files relative to the users files folder - */ - public function getAllFiles($dir, $mountPoint = '') { - $result = array(); - $dirList = array($dir); - - while ($dirList) { - $dir = array_pop($dirList); - $content = $this->view->getDirectoryContent(\OC\Files\Filesystem::normalizePath( - $this->userFilesDir . '/' . $dir)); - - foreach ($content as $c) { - // getDirectoryContent() returns the paths relative to the mount points, so we need - // to re-construct the complete path - $path = ($mountPoint !== '') ? $mountPoint . '/' . $c['path'] : $c['path']; - $path = \OC\Files\Filesystem::normalizePath($path); - if ($c['type'] === 'dir') { - $dirList[] = substr($path, strlen('/' . \OCP\User::getUser() . "/files")); - } else { - $result[] = substr($path, strlen('/' . \OCP\User::getUser() . "/files")); - } - } - - } - - return $result; - } - - /** - * get owner of the shared files. - * @param int $id ID of a share - * @return string owner - */ - public function getOwnerFromSharedFile($id) { - - $query = \OCP\DB::prepare('SELECT `parent`, `uid_owner` FROM `*PREFIX*share` WHERE `id` = ?', 1); - - $result = $query->execute(array($id)); - - $source = null; - if (\OCP\DB::isError($result)) { - \OCP\Util::writeLog('Encryption library', \OC_DB::getErrorMessage($result), \OCP\Util::ERROR); - } else { - $source = $result->fetchRow(); - } - - $fileOwner = false; - - if ($source && isset($source['parent'])) { - - $parent = $source['parent']; - - while (isset($parent)) { - - $query = \OCP\DB::prepare('SELECT `parent`, `uid_owner` FROM `*PREFIX*share` WHERE `id` = ?', 1); - - $result = $query->execute(array($parent)); - - $item = null; - if (\OCP\DB::isError($result)) { - \OCP\Util::writeLog('Encryption library', \OC_DB::getErrorMessage($result), \OCP\Util::ERROR); - } else { - $item = $result->fetchRow(); - } - - if ($item && isset($item['parent'])) { - - $parent = $item['parent']; - - } else { - - $fileOwner = $item['uid_owner']; - - break; - - } - } - - } else { - - $fileOwner = $source['uid_owner']; - - } - - return $fileOwner; - - } - - /** - * @return string - */ - public function getUserId() { - return $this->userId; - } - - /** - * @return string - */ - public function getKeyId() { - return $this->keyId; - } - - /** - * @return string - */ - public function getUserFilesDir() { - return $this->userFilesDir; - } - - /** - * @param string $password - * @return bool - */ - public function checkRecoveryPassword($password) { - - $result = false; - - $recoveryKey = Keymanager::getPrivateSystemKey($this->recoveryKeyId); - $decryptedRecoveryKey = Crypt::decryptPrivateKey($recoveryKey, $password); - - if ($decryptedRecoveryKey) { - $result = true; - } - - return $result; - } - - /** - * @return string - */ - public function getRecoveryKeyId() { - return $this->recoveryKeyId; - } - - /** - * add recovery key to all encrypted files - */ - public function addRecoveryKeys($path = '/') { - $dirContent = $this->view->getDirectoryContent($this->keysPath . '/' . $path); - foreach ($dirContent as $item) { - // get relative path from files_encryption/keyfiles/ - $filePath = substr($item['path'], strlen('files_encryption/keys')); - if ($this->view->is_dir($this->userFilesDir . '/' . $filePath)) { - $this->addRecoveryKeys($filePath . '/'); - } else { - $session = new Session(new \OC\Files\View('/')); - $sharingEnabled = \OCP\Share::isEnabled(); - $usersSharing = $this->getSharingUsersArray($sharingEnabled, $filePath); - $this->setSharedFileKeyfiles($session, $usersSharing, $filePath); - } - } - } - - /** - * remove recovery key to all encrypted files - */ - public function removeRecoveryKeys($path = '/') { - $dirContent = $this->view->getDirectoryContent($this->keysPath . '/' . $path); - foreach ($dirContent as $item) { - // get relative path from files_encryption/keyfiles - $filePath = substr($item['path'], strlen('files_encryption/keys')); - if ($this->view->is_dir($this->userFilesDir . '/' . $filePath)) { - $this->removeRecoveryKeys($filePath . '/'); - } else { - $this->view->unlink($this->keysPath . '/' . $filePath . '/' . $this->recoveryKeyId . '.shareKey'); - } - } - } - - /** - * decrypt given file with recovery key and encrypt it again to the owner and his new key - * @param string $file - * @param string $privateKey recovery key to decrypt the file - */ - private function recoverFile($file, $privateKey) { - - $sharingEnabled = \OCP\Share::isEnabled(); - - // Find out who, if anyone, is sharing the file - if ($sharingEnabled) { - $result = \OCP\Share::getUsersSharingFile($file, $this->userId, true); - $userIds = $result['users']; - $userIds[] = $this->recoveryKeyId; - if ($result['public']) { - $userIds[] = $this->publicShareKeyId; - } - } else { - $userIds = array( - $this->userId, - $this->recoveryKeyId - ); - } - $filteredUids = $this->filterShareReadyUsers($userIds); - - //decrypt file key - $encKeyfile = Keymanager::getFileKey($this->view, $this, $file); - $shareKey = Keymanager::getShareKey($this->view, $this->recoveryKeyId, $this, $file); - $plainKeyfile = Crypt::multiKeyDecrypt($encKeyfile, $shareKey, $privateKey); - // encrypt file key again to all users, this time with the new public key for the recovered use - $userPubKeys = Keymanager::getPublicKeys($this->view, $filteredUids['ready']); - $multiEncKey = Crypt::multiKeyEncrypt($plainKeyfile, $userPubKeys); - - Keymanager::setFileKey($this->view, $this, $file, $multiEncKey['data']); - Keymanager::setShareKeys($this->view, $this, $file, $multiEncKey['keys']); - - } - - /** - * collect all files and recover them one by one - * @param string $path to look for files keys - * @param string $privateKey private recovery key which is used to decrypt the files - */ - private function recoverAllFiles($path, $privateKey) { - $dirContent = $this->view->getDirectoryContent($this->keysPath . '/' . $path); - foreach ($dirContent as $item) { - // get relative path from files_encryption/keyfiles - $filePath = substr($item['path'], strlen('files_encryption/keys')); - if ($this->view->is_dir($this->userFilesDir . '/' . $filePath)) { - $this->recoverAllFiles($filePath . '/', $privateKey); - } else { - $this->recoverFile($filePath, $privateKey); - } - } - } - - /** - * recover users files in case of password lost - * @param string $recoveryPassword - */ - public function recoverUsersFiles($recoveryPassword) { - - $encryptedKey = Keymanager::getPrivateSystemKey( $this->recoveryKeyId); - $privateKey = Crypt::decryptPrivateKey($encryptedKey, $recoveryPassword); - - $this->recoverAllFiles('/', $privateKey); - } - - /** - * create a backup of all keys from the user - * - * @param string $purpose define the purpose of the backup, will be part of the backup folder name - * @param boolean $timestamp (optional) should a timestamp be added, default true - * @param boolean $includeUserKeys (optional) include users private-/public-key, default true - */ - public function backupAllKeys($purpose, $timestamp = true, $includeUserKeys = true) { - $this->userId; - $backupDir = $this->encryptionDir . '/backup.' . $purpose; - $backupDir .= ($timestamp) ? '.' . date("Y-m-d_H-i-s") . '/' : '/'; - $this->view->mkdir($backupDir); - $this->view->copy($this->keysPath, $backupDir . 'keys/'); - if ($includeUserKeys) { - $this->view->copy($this->privateKeyPath, $backupDir . $this->userId . '.privateKey'); - $this->view->copy($this->publicKeyPath, $backupDir . $this->userId . '.publicKey'); - } - } - - /** - * restore backup - * - * @param string $backup complete name of the backup - * @return boolean - */ - public function restoreBackup($backup) { - $backupDir = $this->encryptionDir . '/backup.' . $backup . '/'; - - $fileKeysRestored = $this->view->rename($backupDir . 'keys', $this->encryptionDir . '/keys'); - - $pubKeyRestored = $privKeyRestored = true; - if ( - $this->view->file_exists($backupDir . $this->userId . '.privateKey') && - $this->view->file_exists($backupDir . $this->userId . '.privateKey') - ) { - - $pubKeyRestored = $this->view->rename($backupDir . $this->userId . '.publicKey', $this->publicKeyPath); - $privKeyRestored = $this->view->rename($backupDir . $this->userId . '.privateKey', $this->privateKeyPath); - } - - if ($fileKeysRestored && $pubKeyRestored && $privKeyRestored) { - $this->view->deleteAll($backupDir); - - return true; - } - - return false; - } - - /** - * delete backup - * - * @param string $backup complete name of the backup - * @return boolean - */ - public function deleteBackup($backup) { - $backupDir = $this->encryptionDir . '/backup.' . $backup . '/'; - return $this->view->deleteAll($backupDir); - } - - /** - * check if the file is stored on a system wide mount point - * @param string $path relative to /data/user with leading '/' - * @return boolean - */ - public function isSystemWideMountPoint($path) { - $normalizedPath = ltrim($path, '/'); - if (\OCP\App::isEnabled("files_external")) { - $mounts = \OC_Mount_Config::getSystemMountPoints(); - foreach ($mounts as $mount) { - if ($mount['mountpoint'] == substr($normalizedPath, 0, strlen($mount['mountpoint']))) { - if ($this->isMountPointApplicableToUser($mount)) { - return true; - } - } - } - } - return false; - } - - /** - * check if mount point is applicable to user - * - * @param array $mount contains $mount['applicable']['users'], $mount['applicable']['groups'] - * @return boolean - */ - protected function isMountPointApplicableToUser($mount) { - $uid = \OCP\User::getUser(); - $acceptedUids = array('all', $uid); - // check if mount point is applicable for the user - $intersection = array_intersect($acceptedUids, $mount['applicable']['users']); - if (!empty($intersection)) { - return true; - } - // check if mount point is applicable for group where the user is a member - foreach ($mount['applicable']['groups'] as $gid) { - if (\OC_Group::inGroup($uid, $gid)) { - return true; - } - } - return false; - } - - /** - * decrypt private key and add it to the current session - * @param array $params with 'uid' and 'password' - * @return mixed session or false - */ - public function initEncryption($params) { - - $session = new Session($this->view); - - // we tried to initialize the encryption app for this session - $session->setInitialized(Session::INIT_EXECUTED); - - $encryptedKey = Keymanager::getPrivateKey($this->view, $params['uid']); - - $privateKey = false; - if ($encryptedKey) { - $privateKey = Crypt::decryptPrivateKey($encryptedKey, $params['password']); - } - - if ($privateKey === false) { - \OCP\Util::writeLog('Encryption library', 'Private key for user "' . $params['uid'] - . '" is not valid! Maybe the user password was changed from outside if so please change it back to gain access', \OCP\Util::ERROR); - return false; - } - - $session->setPrivateKey($privateKey); - $session->setInitialized(Session::INIT_SUCCESSFUL); - - return $session; - } - - /* - * remove encryption related keys from the session - */ - public function closeEncryptionSession() { - $session = new Session($this->view); - $session->closeSession(); - } - -} |