nextcloud/apps/files_encryption/lib/util.php

1512 lines
37 KiB
PHP
Raw Normal View History

<?php
/**
* ownCloud
*
* @author Sam Tuke, Frank Karlitschek
2013-05-20 03:24:36 +04:00
* @copyright 2012 Sam Tuke <samtuke@owncloud.com>,
* Frank Karlitschek <frank@owncloud.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
# Bugs
# ----
# Sharing a file to a user without encryption set up will not provide them with access but won't notify the sharer
# Sharing all files to admin for recovery purposes still in progress
# Possibly public links are broken (not tested since last merge of master)
# Missing features
# ----------------
# Make sure user knows if large files weren't encrypted
# Test
# ----
# Test that writing files works when recovery is enabled, and sharing API is disabled
# Test trashbin support
// Old Todo:
// - Crypt/decrypt button in the userinterface
// - Setting if crypto should be on by default
// - Add a setting "Don´t encrypt files larger than xx because of performance
// reasons"
namespace OCA\Encryption;
/**
* @brief Class for utilities relating to encrypted file storage system
* @param OC_FilesystemView $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
*/
2013-05-20 03:24:36 +04:00
class Util
{
// Web UI:
2013-05-20 03:24:36 +04:00
//// DONE: files created via web ui are encrypted
//// DONE: file created & encrypted via web ui are readable in web ui
//// DONE: file created & encrypted via web ui are readable via webdav
2013-05-20 03:24:36 +04:00
// WebDAV:
2013-05-20 03:24:36 +04:00
//// DONE: new data filled files added via webdav get encrypted
//// DONE: new data filled files added via webdav are readable via webdav
//// DONE: reading unencrypted files when encryption is enabled works via
//// webdav
//// DONE: files created & encrypted via web ui are readable via webdav
2013-05-20 03:24:36 +04:00
// Legacy support:
2013-05-20 03:24:36 +04:00
//// DONE: add method to check if file is encrypted using new system
//// DONE: add method to check if file is encrypted using old system
//// DONE: add method to fetch legacy key
//// DONE: add method to decrypt legacy encrypted data
2013-05-20 03:24:36 +04:00
// Admin UI:
2013-05-20 03:24:36 +04:00
//// DONE: changing user password also changes encryption passphrase
2013-05-20 03:24:36 +04:00
//// TODO: add support for optional recovery in case of lost passphrase / keys
//// TODO: add admin optional required long passphrase for users
//// TODO: implement flag system to allow user to specify encryption by folder, subfolder, etc.
2013-05-20 03:24:36 +04:00
// Integration testing:
2013-05-20 03:24:36 +04:00
//// TODO: test new encryption with versioning
2013-05-11 02:23:30 +04:00
//// DONE: test new encryption with sharing
//// TODO: test new encryption with proxies
2013-05-20 03:24:36 +04:00
private $view; // OC_FilesystemView object for filesystem operations
private $userId; // ID of the currently logged-in user
private $pwd; // User Password
private $client; // Client side encryption mode flag
private $publicKeyDir; // Dir containing all public user keys
private $encryptionDir; // Dir containing user's files_encryption
private $keyfilesPath; // Dir containing user's keyfiles
private $shareKeysPath; // Dir containing env keys for shared files
private $publicKeyPath; // Path to user's public key
private $privateKeyPath; // Path to user's private key
private $publicShareKeyId;
private $recoveryKeyId;
2013-05-20 03:24:36 +04:00
private $isPublic;
2013-05-20 03:24:36 +04:00
/**
* @param \OC_FilesystemView $view
* @param $userId
* @param bool $client
*/
public function __construct(\OC_FilesystemView $view, $userId, $client = false)
{
$this->view = $view;
$this->userId = $userId;
$this->client = $client;
2013-05-20 03:24:36 +04:00
$this->isPublic = false;
$this->publicShareKeyId = \OC_Appconfig::getValue('files_encryption', 'publicShareKeyId');
$this->recoveryKeyId = \OC_Appconfig::getValue('files_encryption', 'recoveryKeyId');
// if we are anonymous/public
if ($this->userId === false) {
$this->userId = $this->publicShareKeyId;
// only handle for files_sharing app
if ($GLOBALS['app'] === 'files_sharing') {
$this->userDir = '/' . $GLOBALS['fileOwner'];
$this->fileFolderName = 'files';
$this->userFilesDir = '/' . $GLOBALS['fileOwner'] . '/' . $this->fileFolderName; // TODO: Does this need to be user configurable?
$this->publicKeyDir = '/' . 'public-keys';
$this->encryptionDir = '/' . $GLOBALS['fileOwner'] . '/' . 'files_encryption';
$this->keyfilesPath = $this->encryptionDir . '/' . 'keyfiles';
$this->shareKeysPath = $this->encryptionDir . '/' . 'share-keys';
$this->publicKeyPath = $this->publicKeyDir . '/' . $this->userId . '.public.key'; // e.g. data/public-keys/admin.public.key
$this->privateKeyPath = '/owncloud_private_key/' . $this->userId . '.private.key'; // e.g. data/admin/admin.private.key
$this->isPublic = true;
}
} else {
$this->userDir = '/' . $this->userId;
$this->fileFolderName = 'files';
$this->userFilesDir = '/' . $this->userId . '/' . $this->fileFolderName; // TODO: Does this need to be user configurable?
$this->publicKeyDir = '/' . 'public-keys';
$this->encryptionDir = '/' . $this->userId . '/' . 'files_encryption';
$this->keyfilesPath = $this->encryptionDir . '/' . 'keyfiles';
$this->shareKeysPath = $this->encryptionDir . '/' . 'share-keys';
$this->publicKeyPath = $this->publicKeyDir . '/' . $this->userId . '.public.key'; // e.g. data/public-keys/admin.public.key
$this->privateKeyPath = $this->encryptionDir . '/' . $this->userId . '.private.key'; // e.g. data/admin/admin.private.key
}
}
2013-05-20 03:24:36 +04:00
/**
* @return bool
*/
public function ready()
{
if (
!$this->view->file_exists($this->encryptionDir)
or !$this->view->file_exists($this->keyfilesPath)
or !$this->view->file_exists($this->shareKeysPath)
or !$this->view->file_exists($this->publicKeyPath)
or !$this->view->file_exists($this->privateKeyPath)
) {
2013-05-20 03:24:36 +04:00
return false;
2013-05-20 03:24:36 +04:00
} else {
2013-05-20 03:24:36 +04:00
return true;
2013-05-20 03:24:36 +04:00
}
2013-05-20 03:24:36 +04:00
}
2013-05-20 03:24:36 +04:00
/**
* @brief Sets up user folders and keys for serverside encryption
* @param string $passphrase passphrase to encrypt server-stored private key with
*/
public function setupServerSide($passphrase = null)
{
// Set directories to check / create
2013-05-20 03:24:36 +04:00
$setUpDirs = array(
$this->userDir
2013-05-20 03:24:36 +04:00
, $this->userFilesDir
, $this->publicKeyDir
, $this->encryptionDir
, $this->keyfilesPath
, $this->shareKeysPath
);
2013-05-20 03:24:36 +04:00
// Check / create all necessary dirs
2013-05-20 03:24:36 +04:00
foreach ($setUpDirs as $dirPath) {
if (!$this->view->file_exists($dirPath)) {
$this->view->mkdir($dirPath);
}
2013-05-20 03:24:36 +04:00
}
2013-05-20 03:24:36 +04:00
// Create user keypair
2013-05-20 03:24:36 +04:00
if (
!$this->view->file_exists($this->publicKeyPath)
or !$this->view->file_exists($this->privateKeyPath)
) {
2013-05-20 03:24:36 +04:00
// Generate keypair
$keypair = Crypt::createKeypair();
2013-05-20 03:24:36 +04:00
\OC_FileProxy::$enabled = false;
2013-05-20 03:24:36 +04:00
// Save public key
2013-05-20 03:24:36 +04:00
$this->view->file_put_contents($this->publicKeyPath, $keypair['publicKey']);
// Encrypt private key with user pwd as passphrase
2013-05-20 03:24:36 +04:00
$encryptedPrivateKey = Crypt::symmetricEncryptFileContent($keypair['privateKey'], $passphrase);
// Save private key
2013-05-20 03:24:36 +04:00
$this->view->file_put_contents($this->privateKeyPath, $encryptedPrivateKey);
\OC_FileProxy::$enabled = true;
2013-05-20 03:24:36 +04:00
}
2013-05-20 03:24:36 +04:00
// If there's no record for this user's encryption preferences
2013-05-20 03:24:36 +04:00
if (false === $this->recoveryEnabledForUser()) {
// create database configuration
$sql = 'INSERT INTO `*PREFIX*encryption` (`uid`,`mode`,`recovery_enabled`) VALUES (?,?,?)';
2013-05-20 03:24:36 +04:00
$args = array($this->userId, 'server-side', 0);
$query = \OCP\DB::prepare($sql);
$query->execute($args);
}
2013-05-20 03:24:36 +04:00
return true;
2013-05-20 03:24:36 +04:00
}
2013-05-20 03:24:36 +04:00
/**
* @return string
*/
public function getPublicShareKeyId()
{
return $this->publicShareKeyId;
}
2013-05-20 03:24:36 +04:00
/**
* @brief Check whether pwd recovery is enabled for a given user
2013-05-20 03:24:36 +04:00
* @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
*/
2013-05-20 03:24:36 +04:00
public function recoveryEnabledForUser()
{
$sql = 'SELECT
recovery_enabled
FROM
`*PREFIX*encryption`
WHERE
uid = ?';
2013-05-20 03:24:36 +04:00
$args = array($this->userId);
$query = \OCP\DB::prepare($sql);
$result = $query->execute($args);
$recoveryEnabled = array();
2013-05-20 03:24:36 +04:00
while ($row = $result->fetchRow()) {
$recoveryEnabled[] = $row['recovery_enabled'];
2013-05-20 03:24:36 +04:00
}
2013-05-20 03:24:36 +04:00
// If no record is found
2013-05-20 03:24:36 +04:00
if (empty($recoveryEnabled)) {
return false;
2013-05-20 03:24:36 +04:00
// If a record is found
} else {
2013-05-20 03:24:36 +04:00
return $recoveryEnabled[0];
2013-05-20 03:24:36 +04:00
}
2013-05-20 03:24:36 +04:00
}
2013-05-20 03:24:36 +04:00
/**
* @brief Enable / disable pwd recovery for a given user
* @param bool $enabled Whether to enable or disable recovery
* @return bool
*/
2013-05-20 03:24:36 +04:00
public function setRecoveryForUser($enabled)
{
$recoveryStatus = $this->recoveryEnabledForUser();
2013-05-20 03:24:36 +04:00
// If a record for this user already exists, update it
2013-05-20 03:24:36 +04:00
if (false === $recoveryStatus) {
$sql = 'INSERT INTO `*PREFIX*encryption`
(`uid`,`mode`,`recovery_enabled`)
VALUES (?,?,?)';
2013-05-20 03:24:36 +04:00
$args = array($this->userId, 'server-side', $enabled);
// Create a new record instead
} else {
2013-05-20 03:24:36 +04:00
$sql = 'UPDATE
*PREFIX*encryption
SET
recovery_enabled = ?
WHERE
uid = ?';
2013-05-20 03:24:36 +04:00
$args = array($enabled, $this->userId);
}
2013-05-20 03:24:36 +04:00
$query = \OCP\DB::prepare($sql);
if ($query->execute($args)) {
return true;
2013-05-20 03:24:36 +04:00
} else {
2013-05-20 03:24:36 +04:00
return false;
2013-05-20 03:24:36 +04:00
}
2013-05-20 03:24:36 +04:00
}
2013-05-20 03:24:36 +04:00
/**
* @brief Find all files and their encryption status within a directory
* @param string $directory The path of the parent directory to search
* @return mixed false if 0 found, array on success. Keys: name, path
* @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
*/
2013-05-20 03:24:36 +04:00
public function findEncFiles($directory)
{
// Disable proxy - we don't want files to be decrypted before
// we handle them
\OC_FileProxy::$enabled = false;
2013-05-20 03:24:36 +04:00
$found = array('plain' => array(), 'encrypted' => array(), 'legacy' => array());
if (
$this->view->is_dir($directory)
&& $handle = $this->view->opendir($directory)
) {
2013-05-20 03:24:36 +04:00
while (false !== ($file = readdir($handle))) {
if (
2013-05-20 03:24:36 +04:00
$file != "."
&& $file != ".."
) {
2013-05-20 03:24:36 +04:00
$filePath = $directory . '/' . $this->view->getRelativePath('/' . $file);
$relPath = $this->stripUserFilesPath($filePath);
// If the path is a directory, search
// its contents
2013-05-20 03:24:36 +04:00
if ($this->view->is_dir($filePath)) {
$this->findEncFiles($filePath);
// 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;
2013-05-20 03:24:36 +04:00
$data = $this->view->file_get_contents($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 :(
2013-05-20 03:24:36 +04:00
if (
Keymanager::getFileKey($this->view, $this->userId, $relPath)
&& Crypt::isCatfileContent($data)
) {
2013-05-20 03:24:36 +04:00
$found['encrypted'][] = array('name' => $file, 'path' => $filePath);
// If the file uses old
// encryption system
} elseif (Crypt::isLegacyEncryptedContent($this->tail($filePath, 3), $relPath)) {
$found['legacy'][] = array('name' => $file, 'path' => $filePath);
// If the file is not encrypted
} else {
2013-05-20 03:24:36 +04:00
$found['plain'][] = array('name' => $file, 'path' => $relPath);
}
2013-05-20 03:24:36 +04:00
}
2013-05-20 03:24:36 +04:00
}
2013-05-20 03:24:36 +04:00
}
2013-05-20 03:24:36 +04:00
\OC_FileProxy::$enabled = true;
2013-05-20 03:24:36 +04:00
if (empty($found)) {
return false;
2013-05-20 03:24:36 +04:00
} else {
2013-05-20 03:24:36 +04:00
return $found;
2013-05-20 03:24:36 +04:00
}
2013-05-20 03:24:36 +04:00
}
2013-05-20 03:24:36 +04:00
\OC_FileProxy::$enabled = true;
2013-05-20 03:24:36 +04:00
return false;
}
2013-05-20 03:24:36 +04:00
/**
* @brief Fetch the last lines of a file efficiently
* @note Safe to use on large files; does not read entire file to memory
* @note Derivative of http://tekkie.flashbit.net/php/tail-functionality-in-php
*/
public function tail($filename, $numLines)
{
\OC_FileProxy::$enabled = false;
2013-05-20 03:24:36 +04:00
$text = '';
$pos = -1;
2013-05-20 03:24:36 +04:00
$handle = $this->view->fopen($filename, 'r');
while ($numLines > 0) {
--$pos;
2013-05-20 03:24:36 +04:00
if (fseek($handle, $pos, SEEK_END) !== 0) {
rewind($handle);
$numLines = 0;
2013-05-20 03:24:36 +04:00
} elseif (fgetc($handle) === "\n") {
--$numLines;
2013-05-20 03:24:36 +04:00
}
2013-05-20 03:24:36 +04:00
$block_size = (-$pos) % 8192;
if ($block_size === 0 || $numLines === 0) {
$text = fread($handle, ($block_size === 0 ? 8192 : $block_size)) . $text;
}
}
2013-05-20 03:24:36 +04:00
fclose($handle);
\OC_FileProxy::$enabled = true;
2013-05-20 03:24:36 +04:00
return $text;
}
2013-05-20 03:24:36 +04:00
/**
2013-05-20 03:24:36 +04:00
* @brief Check if a given path identifies an encrypted file
* @param $path
* @return boolean
*/
public function isEncryptedPath($path)
{
// Disable encryption proxy so data retrieved is in its
// original form
2013-05-20 03:24:36 +04:00
$proxyStatus = \OC_FileProxy::$enabled;
\OC_FileProxy::$enabled = false;
2013-05-20 03:24:36 +04:00
// we only need 24 byte from the last chunk
$data = '';
$handle = $this->view->fopen($path, 'r');
if (!fseek($handle, -24, SEEK_END)) {
$data = fgets($handle);
}
2013-05-20 03:24:36 +04:00
// re-enable proxy
\OC_FileProxy::$enabled = $proxyStatus;
2013-05-20 03:24:36 +04:00
return Crypt::isCatfileContent($data);
}
/**
2013-05-20 03:24:36 +04:00
* @brief 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;
// Reformat path for use with OC_FSV
2013-05-20 03:24:36 +04:00
$pathSplit = explode('/', $path);
$pathRelative = implode('/', array_slice($pathSplit, 3));
if ($pathSplit[2] == 'files' && $this->view->file_exists($path) && $this->isEncryptedPath($path)) {
// get the size from filesystem
$fullPath = $this->view->getLocalFile($path);
$size = filesize($fullPath);
// calculate last chunk nr
$lastChunckNr = floor($size / 8192);
// open stream
$stream = fopen('crypt://' . $pathRelative, "r");
2013-05-20 03:24:36 +04:00
if (is_resource($stream)) {
// calculate last chunk position
$lastChunckPos = ($lastChunckNr * 8192);
// seek to end
fseek($stream, $lastChunckPos);
// get the content of the last chunk
$lastChunkContent = fread($stream, 8192);
// calc the real file size with the size of the last chunk
$realSize = (($lastChunckNr * 6126) + strlen($lastChunkContent));
// store file size
$result = $realSize;
}
}
2013-04-28 01:34:25 +04:00
\OC_FileProxy::$enabled = $proxyStatus;
2013-04-28 01:34:25 +04:00
return $result;
}
2013-05-20 03:24:36 +04:00
/**
* @brief fix the file size of the encrypted file
* @param $path absolute path
* @return true / false if file is encrypted
*/
2013-05-20 03:24:36 +04:00
public function fixFileSize($path)
{
2013-04-28 01:34:25 +04:00
$result = false;
2013-04-28 01:34:25 +04:00
// Disable encryption proxy to prevent recursive calls
$proxyStatus = \OC_FileProxy::$enabled;
\OC_FileProxy::$enabled = false;
2013-04-28 01:34:25 +04:00
2013-05-20 03:24:36 +04:00
$realSize = $this->getFileSize($path);
if ($realSize > 0) {
$cached = $this->view->getFileInfo($path);
2013-05-11 02:23:30 +04:00
$cached['encrypted'] = true;
2013-04-28 01:34:25 +04:00
// set the size
$cached['unencrypted_size'] = $realSize;
// put file info
2013-05-20 03:24:36 +04:00
$this->view->putFileInfo($path, $cached);
$result = true;
2013-05-20 03:24:36 +04:00
}
\OC_FileProxy::$enabled = $proxyStatus;
return $result;
}
/**
* @brief Format a path to be relative to the /user/files/ directory
* @note e.g. turns '/admin/files/test.txt' into 'test.txt'
*/
2013-05-20 03:24:36 +04:00
public function stripUserFilesPath($path)
{
$trimmed = ltrim($path, '/');
$split = explode('/', $trimmed);
$sliced = array_slice($split, 2);
$relPath = implode('/', $sliced);
return $relPath;
2013-05-20 03:24:36 +04:00
}
2013-05-20 03:24:36 +04:00
/**
* @brief Format a path to be relative to the /user directory
* @note e.g. turns '/admin/files/test.txt' into 'files/test.txt'
*/
2013-05-20 03:24:36 +04:00
public function stripFilesPath($path)
{
$trimmed = ltrim($path, '/');
$split = explode('/', $trimmed);
$sliced = array_slice($split, 1);
$relPath = implode('/', $sliced);
return $relPath;
2013-05-20 03:24:36 +04:00
}
2013-05-20 03:24:36 +04:00
/**
* @brief Format a shared path to be relative to the /user/files/ directory
* @note Expects a path like /uid/files/Shared/filepath
*/
2013-05-20 03:24:36 +04:00
public function stripSharedFilePath($path)
{
$trimmed = ltrim($path, '/');
$split = explode('/', $trimmed);
$sliced = array_slice($split, 3);
$relPath = implode('/', $sliced);
return $relPath;
2013-05-20 03:24:36 +04:00
}
2013-05-20 03:24:36 +04:00
/**
* @param $path
* @return bool
*/
public function isSharedPath($path)
{
$trimmed = ltrim($path, '/');
$split = explode('/', $trimmed);
if ($split[2] == "Shared") {
return true;
2013-05-20 03:24:36 +04:00
} else {
2013-05-20 03:24:36 +04:00
return false;
2013-05-20 03:24:36 +04:00
}
2013-05-20 03:24:36 +04:00
}
2013-05-20 03:24:36 +04:00
/**
* @brief Encrypt all files in a directory
* @param string $dirPath the directory whose files will be encrypted
2013-05-20 03:24:36 +04:00
* @param null $legacyPassphrase
* @param null $newPassphrase
* @return bool
* @note Encryption is recursive
*/
2013-05-20 03:24:36 +04:00
public function encryptAll($dirPath, $legacyPassphrase = null, $newPassphrase = null)
{
2013-05-17 16:13:05 +04:00
if ($found = $this->findEncFiles($dirPath)) {
// Disable proxy to prevent file being encrypted twice
\OC_FileProxy::$enabled = false;
2013-05-17 16:13:05 +04:00
// Encrypt unencrypted files
2013-05-17 16:13:05 +04:00
foreach ($found['plain'] as $plainFile) {
2013-04-12 17:18:19 +04:00
//relative to data/<user>/file
$relPath = $plainFile['path'];
2013-05-17 16:13:05 +04:00
2013-04-12 17:18:19 +04:00
//relative to /data
2013-05-17 16:13:05 +04:00
$rawPath = $this->userId . '/files/' . $plainFile['path'];
// Open plain file handle for binary reading
2013-05-17 16:13:05 +04:00
$plainHandle1 = $this->view->fopen($rawPath, 'rb');
// 2nd handle for moving plain file - view->rename() doesn't work, this is a workaround
2013-05-17 16:13:05 +04:00
$plainHandle2 = $this->view->fopen($rawPath . '.plaintmp', 'wb');
// Move plain file to a temporary location
2013-05-17 16:13:05 +04:00
stream_copy_to_stream($plainHandle1, $plainHandle2);
// Close access to original file
// $this->view->fclose( $plainHandle1 ); // not implemented in view{}
// Delete original plain file so we can rename enc file later
2013-05-17 16:13:05 +04:00
$this->view->unlink($rawPath);
// Open enc file handle for binary writing, with same filename as original plain file
2013-05-17 16:13:05 +04:00
$encHandle = fopen('crypt://' . $relPath, 'wb');
// Save data from plain stream to new encrypted file via enc stream
// NOTE: Stream{} will be invoked for handling
// the encryption, and should handle all keys
// and their generation etc. automatically
2013-05-17 16:13:05 +04:00
$size = stream_copy_to_stream($plainHandle2, $encHandle);
// Delete temporary plain copy of file
2013-05-17 16:13:05 +04:00
$this->view->unlink($rawPath . '.plaintmp');
// Add the file to the cache
2013-05-17 16:13:05 +04:00
\OC\Files\Filesystem::putFileInfo($plainFile['path'], array('encrypted' => true, 'size' => $size), '');
}
2013-05-17 16:13:05 +04:00
// Encrypt legacy encrypted files
2013-05-17 16:13:05 +04:00
if (
!empty($legacyPassphrase)
&& !empty($newPassphrase)
) {
2013-05-17 16:13:05 +04:00
foreach ($found['legacy'] as $legacyFile) {
// Fetch data from file
2013-05-17 16:13:05 +04:00
$legacyData = $this->view->file_get_contents($legacyFile['path']);
$sharingEnabled = \OCP\Share::isEnabled();
// if file exists try to get sharing users
2013-05-17 16:49:54 +04:00
if ($this->view->file_exists($legacyFile['path'])) {
$uniqueUserIds = $this->getSharingUsersArray($sharingEnabled, $legacyFile['path'], $this->userId);
2013-05-17 16:13:05 +04:00
} else {
$uniqueUserIds[] = $this->userId;
}
// Fetch public keys for all users who will share the file
$publicKeys = Keymanager::getPublicKeys($this->view, $uniqueUserIds);
// Recrypt data, generate catfile
2013-05-17 16:49:54 +04:00
$recrypted = Crypt::legacyKeyRecryptKeyfile($legacyData, $legacyPassphrase, $publicKeys, $newPassphrase, $legacyFile['path']);
2013-05-17 16:13:05 +04:00
$rawPath = $legacyFile['path'];
$relPath = $this->stripUserFilesPath($rawPath);
2013-05-17 16:13:05 +04:00
// Save keyfile
2013-05-17 16:13:05 +04:00
Keymanager::setFileKey($this->view, $relPath, $this->userId, $recrypted['filekey']);
// Save sharekeys to user folders
2013-05-17 16:13:05 +04:00
Keymanager::setShareKeys($this->view, $relPath, $recrypted['sharekeys']);
// Overwrite the existing file with the encrypted one
2013-05-17 16:13:05 +04:00
$this->view->file_put_contents($rawPath, $recrypted['data']);
$size = strlen($recrypted['data']);
// Add the file to the cache
2013-05-17 16:13:05 +04:00
\OC\Files\Filesystem::putFileInfo($rawPath, array('encrypted' => true, 'size' => $size), '');
}
}
2013-05-17 16:13:05 +04:00
\OC_FileProxy::$enabled = true;
2013-05-17 16:13:05 +04:00
// If files were found, return true
return true;
} else {
2013-05-17 16:13:05 +04:00
// If no files were found, return false
return false;
}
}
2013-05-17 16:13:05 +04:00
/**
* @brief Return important encryption related paths
* @param string $pathName Name of the directory to return the path of
* @return string path
*/
2013-05-20 03:24:36 +04:00
public function getPath($pathName)
{
switch ($pathName) {
case 'publicKeyDir':
2013-05-20 03:24:36 +04:00
return $this->publicKeyDir;
2013-05-20 03:24:36 +04:00
break;
2013-05-20 03:24:36 +04:00
case 'encryptionDir':
2013-05-20 03:24:36 +04:00
return $this->encryptionDir;
2013-05-20 03:24:36 +04:00
break;
2013-05-20 03:24:36 +04:00
case 'keyfilesPath':
2013-05-20 03:24:36 +04:00
return $this->keyfilesPath;
2013-05-20 03:24:36 +04:00
break;
2013-05-20 03:24:36 +04:00
case 'publicKeyPath':
2013-05-20 03:24:36 +04:00
return $this->publicKeyPath;
2013-05-20 03:24:36 +04:00
break;
2013-05-20 03:24:36 +04:00
case 'privateKeyPath':
2013-05-20 03:24:36 +04:00
return $this->privateKeyPath;
2013-05-20 03:24:36 +04:00
break;
2013-05-20 03:24:36 +04:00
}
2013-05-20 03:24:36 +04:00
}
2013-05-20 03:24:36 +04:00
/**
* @brief get path of a file.
2013-05-20 03:24:36 +04:00
* @param int $fileId id of the file
* @return string path of the file
*/
2013-05-20 03:24:36 +04:00
public static function fileIdToPath($fileId)
{
$query = \OC_DB::prepare('SELECT `path`'
. ' FROM `*PREFIX*filecache`'
. ' WHERE `fileid` = ?');
$result = $query->execute(array($fileId));
$row = $result->fetchRow();
2013-05-20 03:24:36 +04:00
return substr($row['path'], 5);
}
2013-05-20 03:24:36 +04:00
/**
* @brief Filter an array of UIDs to return only ones ready for sharing
* @param array $unfilteredUsers users to be checked for sharing readiness
* @return multi-dimensional array. keys: ready, unready
*/
2013-05-20 03:24:36 +04:00
public function filterShareReadyUsers($unfilteredUsers)
{
// This array will collect the filtered IDs
$readyIds = $unreadyIds = array();
2013-05-20 03:24:36 +04:00
// Loop through users and create array of UIDs that need new keyfiles
2013-05-20 03:24:36 +04:00
foreach ($unfilteredUsers as $user) {
$util = new Util($this->view, $user);
// Check that the user is encryption capable, or is the
// public system user 'ownCloud' (for public shares)
2013-05-20 03:24:36 +04:00
if (
$user == $this->publicShareKeyId
or $user == $this->recoveryKeyId
2013-05-20 03:24:36 +04:00
or $util->ready()
) {
2013-05-20 03:24:36 +04:00
// Construct array of ready UIDs for Keymanager{}
$readyIds[] = $user;
2013-05-20 03:24:36 +04:00
} else {
2013-05-20 03:24:36 +04:00
// Construct array of unready UIDs for Keymanager{}
$unreadyIds[] = $user;
2013-05-20 03:24:36 +04:00
// Log warning; we can't do necessary setup here
// because we don't have the user passphrase
2013-05-20 03:24:36 +04:00
\OC_Log::write('Encryption library', '"' . $user . '" is not setup for encryption', \OC_Log::WARN);
}
2013-05-20 03:24:36 +04:00
}
2013-05-20 03:24:36 +04:00
return array(
'ready' => $readyIds
2013-05-20 03:24:36 +04:00
, 'unready' => $unreadyIds
);
2013-05-20 03:24:36 +04:00
}
2013-05-20 03:24:36 +04:00
/**
* @brief Decrypt a keyfile without knowing how it was encrypted
* @param string $filePath
* @param string $fileOwner
* @param string $privateKey
2013-05-20 03:24:36 +04:00
* @note Checks whether file was encrypted with openssl_seal or
* openssl_encrypt, and decrypts accrdingly
2013-05-20 03:24:36 +04:00
* @note This was used when 2 types of encryption for keyfiles was used,
* but now we've switched to exclusively using openssl_seal()
*/
2013-05-20 03:24:36 +04:00
public function decryptUnknownKeyfile($filePath, $fileOwner, $privateKey)
{
// Get the encrypted keyfile
// NOTE: the keyfile format depends on how it was encrypted! At
// this stage we don't know how it was encrypted
2013-05-20 03:24:36 +04:00
$encKeyfile = Keymanager::getFileKey($this->view, $this->userId, $filePath);
// We need to decrypt the keyfile
// Has the file been shared yet?
2013-05-20 03:24:36 +04:00
if (
$this->userId == $fileOwner
2013-05-20 03:24:36 +04:00
&& !Keymanager::getShareKey($this->view, $this->userId, $filePath) // NOTE: we can't use isShared() here because it's a post share hook so it always returns true
) {
2013-05-20 03:24:36 +04:00
// The file has no shareKey, and its keyfile must be
// decrypted conventionally
2013-05-20 03:24:36 +04:00
$plainKeyfile = Crypt::keyDecrypt($encKeyfile, $privateKey);
} else {
2013-05-20 03:24:36 +04:00
// The file has a shareKey and must use it for decryption
2013-05-20 03:24:36 +04:00
$shareKey = Keymanager::getShareKey($this->view, $this->userId, $filePath);
$plainKeyfile = Crypt::multiKeyDecrypt($encKeyfile, $shareKey, $privateKey);
}
2013-05-20 03:24:36 +04:00
return $plainKeyfile;
}
2013-05-20 03:24:36 +04:00
/**
* @brief Encrypt keyfile to multiple users
2013-05-20 03:24:36 +04:00
* @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
2013-05-20 03:24:36 +04:00
* @return bool
*/
2013-05-20 03:24:36 +04:00
public function setSharedFileKeyfiles(Session $session, array $users, $filePath)
{
// Make sure users are capable of sharing
2013-05-20 03:24:36 +04:00
$filteredUids = $this->filterShareReadyUsers($users);
// If we're attempting to share to unready users
2013-05-20 03:24:36 +04:00
if (!empty($filteredUids['unready'])) {
\OC_Log::write('Encryption library', 'Sharing to these user(s) failed as they are unready for encryption:"' . print_r($filteredUids['unready'], 1), \OC_Log::WARN);
return false;
2013-05-20 03:24:36 +04:00
}
2013-05-20 03:24:36 +04:00
// Get public keys for each user, ready for generating sharekeys
2013-05-20 03:24:36 +04:00
$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();
2013-05-20 03:24:36 +04:00
$fileOwner = \OC\Files\Filesystem::getOwner($filePath);
// Decrypt keyfile
2013-05-20 03:24:36 +04:00
$plainKeyfile = $this->decryptUnknownKeyfile($filePath, $fileOwner, $privateKey);
// Re-enc keyfile to (additional) sharekeys
2013-05-20 03:24:36 +04:00
$multiEncKey = Crypt::multiKeyEncrypt($plainKeyfile, $userPubKeys);
// Save the recrypted key to it's owner's keyfiles directory
// Save new sharekeys to all necessary user directory
2013-05-20 03:24:36 +04:00
if (
!Keymanager::setFileKey($this->view, $filePath, $fileOwner, $multiEncKey['data'])
|| !Keymanager::setShareKeys($this->view, $filePath, $multiEncKey['keys'])
) {
2013-05-20 03:24:36 +04:00
\OC_Log::write('Encryption library', 'Keyfiles could not be saved for users sharing ' . $filePath, \OC_Log::ERROR);
return false;
}
2013-05-20 03:24:36 +04:00
// Return proxy to original status
\OC_FileProxy::$enabled = $proxyStatus;
return true;
}
2013-05-20 03:24:36 +04:00
/**
* @brief Find, sanitise and format users sharing a file
* @note This wraps other methods into a portable bundle
*/
2013-05-20 03:24:36 +04:00
public function getSharingUsersArray($sharingEnabled, $filePath, $currentUserId = false)
{
// Check if key recovery is enabled
if (
2013-05-20 03:24:36 +04:00
\OC_Appconfig::getValue('files_encryption', 'recoveryAdminEnabled')
&& $this->recoveryEnabledForUser()
) {
2013-05-20 03:24:36 +04:00
$recoveryEnabled = true;
2013-05-20 03:24:36 +04:00
} else {
2013-05-20 03:24:36 +04:00
$recoveryEnabled = false;
2013-05-20 03:24:36 +04:00
}
2013-05-20 03:24:36 +04:00
// Make sure that a share key is generated for the owner too
2013-05-20 03:24:36 +04:00
list($owner, $ownerPath) = $this->getUidAndFilename($filePath);
if ($sharingEnabled) {
// Find out who, if anyone, is sharing the file
2013-05-20 03:24:36 +04:00
$result = \OCP\Share::getUsersSharingFile($ownerPath, $owner, true, true, true);
$userIds = $result['users'];
2013-05-20 03:24:36 +04:00
if ($result['public']) {
$userIds[] = $this->publicShareKeyId;
}
2013-05-20 03:24:36 +04:00
}
2013-05-20 03:24:36 +04:00
// If recovery is enabled, add the
// Admin UID to list of users to share to
2013-05-20 03:24:36 +04:00
if ($recoveryEnabled) {
// Find recoveryAdmin user ID
2013-05-20 03:24:36 +04:00
$recoveryKeyId = \OC_Appconfig::getValue('files_encryption', 'recoveryKeyId');
// Add recoveryAdmin to list of users sharing
$userIds[] = $recoveryKeyId;
2013-05-20 03:24:36 +04:00
}
// add current user if given
2013-05-20 03:24:36 +04:00
if ($currentUserId != false) {
$userIds[] = $currentUserId;
2013-05-20 03:24:36 +04:00
}
// Remove duplicate UIDs
2013-05-20 03:24:36 +04:00
$uniqueUserIds = array_unique($userIds);
return $uniqueUserIds;
}
2013-05-20 03:24:36 +04:00
/**
* @brief Set file migration status for user
2013-05-20 03:24:36 +04:00
* @param $status
* @return bool
*/
2013-05-20 03:24:36 +04:00
public function setMigrationStatus($status)
{
$sql = 'UPDATE
*PREFIX*encryption
SET
migration_status = ?
WHERE
uid = ?';
2013-05-20 03:24:36 +04:00
$args = array($status, $this->userId);
$query = \OCP\DB::prepare($sql);
if ($query->execute($args)) {
return true;
2013-05-20 03:24:36 +04:00
} else {
2013-05-20 03:24:36 +04:00
return false;
2013-05-20 03:24:36 +04:00
}
2013-05-20 03:24:36 +04:00
}
2013-05-20 03:24:36 +04:00
/**
* @brief Check whether pwd recovery is enabled for a given user
2013-05-20 03:24:36 +04:00
* @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
*/
2013-05-20 03:24:36 +04:00
public function getMigrationStatus()
{
$sql = 'SELECT
migration_status
FROM
`*PREFIX*encryption`
WHERE
uid = ?';
2013-05-20 03:24:36 +04:00
$args = array($this->userId);
$query = \OCP\DB::prepare($sql);
$result = $query->execute($args);
$migrationStatus = array();
2013-05-20 03:24:36 +04:00
while ($row = $result->fetchRow()) {
$migrationStatus[] = $row['migration_status'];
2013-05-20 03:24:36 +04:00
}
2013-05-20 03:24:36 +04:00
// If no record is found
2013-05-20 03:24:36 +04:00
if (empty($migrationStatus)) {
return false;
2013-05-20 03:24:36 +04:00
// If a record is found
} else {
2013-05-20 03:24:36 +04:00
return $migrationStatus[0];
2013-05-20 03:24:36 +04:00
}
2013-05-20 03:24:36 +04:00
}
2013-05-20 03:24:36 +04:00
/**
2013-03-26 15:39:55 +04:00
* @brief get uid of the owners of the file and the path to the file
2013-05-20 03:24:36 +04:00
* @param string $path Path of the file to check
* @note $shareFilePath must be relative to data/UID/files. Files
* relative to /Shared are also acceptable
2013-03-26 15:39:55 +04:00
* @return array
*/
2013-05-20 03:24:36 +04:00
public function getUidAndFilename($path)
{
$view = new \OC\Files\View($this->userFilesDir);
$fileOwnerUid = $view->getOwner($path);
2013-05-20 03:24:36 +04:00
// handle public access
if ($fileOwnerUid === false && $this->isPublic) {
$filename = $path;
$fileOwnerUid = $GLOBALS['fileOwner'];
2013-05-20 03:24:36 +04:00
return array($fileOwnerUid, $filename);
} else {
2013-05-20 03:24:36 +04:00
// 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 . '"');
}
2013-05-20 03:24:36 +04:00
// NOTE: Bah, this dependency should be elsewhere
\OC\Files\Filesystem::initMountPoints($fileOwnerUid);
2013-05-20 03:24:36 +04:00
// If the file owner is the currently logged in user
if ($fileOwnerUid == $this->userId) {
2013-05-20 03:24:36 +04:00
// Assume the path supplied is correct
$filename = $path;
2013-05-20 03:24:36 +04:00
} else {
2013-05-20 03:24:36 +04:00
$info = $view->getFileInfo($path);
$ownerView = new \OC\Files\View('/' . $fileOwnerUid . '/files');
2013-05-20 03:24:36 +04:00
// Fetch real file path from DB
$filename = $ownerView->getPath($info['fileid']); // TODO: Check that this returns a path without including the user data dir
2013-05-20 03:24:36 +04:00
}
2013-05-20 03:24:36 +04:00
return array($fileOwnerUid, $filename);
}
2013-03-26 15:39:55 +04:00
}
/**
2013-04-22 16:14:28 +04:00
* @brief geo recursively through a dir and collect all files and sub files.
2013-05-20 03:24:36 +04:00
* @param string $dir relative to the users files folder
* @return array with list of files relative to the users files folder
*/
2013-05-20 03:24:36 +04:00
public function getAllFiles($dir)
{
$result = array();
2013-05-01 13:04:40 +04:00
2013-05-20 03:24:36 +04:00
$content = $this->view->getDirectoryContent($this->userFilesDir . $dir);
2013-05-01 13:04:40 +04:00
// handling for re shared folders
2013-05-20 03:24:36 +04:00
$path_split = explode('/', $dir);
$shared = '';
2013-05-20 03:24:36 +04:00
if ($path_split[1] === 'Shared') {
$shared = '/Shared';
2013-05-20 03:24:36 +04:00
}
2013-05-20 03:24:36 +04:00
foreach ($content as $c) {
$sharedPart = $path_split[sizeof($path_split) - 1];
$targetPathSplit = array_reverse(explode('/', $c['path']));
$path = '';
// rebuild path
2013-05-20 03:24:36 +04:00
foreach ($targetPathSplit as $pathPart) {
if ($pathPart !== $sharedPart) {
$path = '/' . $pathPart . $path;
2013-05-20 03:24:36 +04:00
} else {
2013-05-20 03:24:36 +04:00
break;
2013-05-20 03:24:36 +04:00
}
2013-05-20 03:24:36 +04:00
}
2013-05-20 03:24:36 +04:00
$path = $dir . $path;
if ($c['type'] === "dir") {
$result = array_merge($result, $this->getAllFiles($path));
} else {
2013-05-20 03:24:36 +04:00
$result[] = $path;
2013-05-20 03:24:36 +04:00
}
}
2013-05-20 03:24:36 +04:00
return $result;
2013-05-20 03:24:36 +04:00
}
/**
* @brief get shares parent.
* @param int $id of the current share
* @return array of the parent
*/
2013-05-20 03:24:36 +04:00
public static function getShareParent($id)
{
2013-05-04 05:37:22 +04:00
2013-05-20 03:24:36 +04:00
$query = \OC_DB::prepare('SELECT `file_target`, `item_type`'
. ' FROM `*PREFIX*share`'
. ' WHERE `id` = ?');
2013-05-04 05:37:22 +04:00
2013-05-20 03:24:36 +04:00
$result = $query->execute(array($id));
2013-05-04 05:37:22 +04:00
$row = $result->fetchRow();
2013-05-04 05:37:22 +04:00
return $row;
2013-05-04 05:37:22 +04:00
}
/**
* @brief get shares parent.
* @param int $id of the current share
* @return array of the parent
*/
2013-05-20 03:24:36 +04:00
public static function getParentFromShare($id)
{
2013-05-20 03:24:36 +04:00
$query = \OC_DB::prepare('SELECT `parent`'
. ' FROM `*PREFIX*share`'
. ' WHERE `id` = ?');
2013-05-20 03:24:36 +04:00
$result = $query->execute(array($id));
$row = $result->fetchRow();
return $row;
}
/**
* @brief get owner of the shared files.
2013-05-20 03:24:36 +04:00
* @param $id
* @internal param int $Id of a share
* @return string owner
*/
2013-05-20 03:24:36 +04:00
public function getOwnerFromSharedFile($id)
{
$query = \OC_DB::prepare('SELECT `parent`, `uid_owner` FROM `*PREFIX*share` WHERE `id` = ?', 1);
$source = $query->execute(array($id))->fetchRow();
if (isset($source['parent'])) {
$parent = $source['parent'];
2013-05-20 03:24:36 +04:00
while (isset($parent)) {
$query = \OC_DB::prepare('SELECT `parent`, `uid_owner` FROM `*PREFIX*share` WHERE `id` = ?', 1);
$item = $query->execute(array($parent))->fetchRow();
if (isset($item['parent'])) {
$parent = $item['parent'];
2013-05-20 03:24:36 +04:00
} else {
2013-05-20 03:24:36 +04:00
$fileOwner = $item['uid_owner'];
2013-05-20 03:24:36 +04:00
break;
2013-05-20 03:24:36 +04:00
}
}
2013-05-20 03:24:36 +04:00
} else {
2013-05-20 03:24:36 +04:00
$fileOwner = $source['uid_owner'];
2013-05-20 03:24:36 +04:00
}
return $fileOwner;
2013-05-20 03:24:36 +04:00
}
2013-05-20 03:24:36 +04:00
/**
* @return string
*/
public function getUserId()
{
return $this->userId;
}
2013-05-20 03:24:36 +04:00
/**
* @return string
*/
public function getUserFilesDir()
{
return $this->userFilesDir;
}
2013-05-20 03:24:36 +04:00
/**
* @param $password
* @return bool
*/
public function checkRecoveryPassword($password)
{
$pathKey = '/owncloud_private_key/' . $this->recoveryKeyId . ".private.key";
$pathControlData = '/control-file/controlfile.enc';
$proxyStatus = \OC_FileProxy::$enabled;
\OC_FileProxy::$enabled = false;
$recoveryKey = $this->view->file_get_contents($pathKey);
$decryptedRecoveryKey = Crypt::symmetricDecryptFileContent($recoveryKey, $password);
$controlData = $this->view->file_get_contents($pathControlData);
$decryptedControlData = Crypt::keyDecrypt($controlData, $decryptedRecoveryKey);
\OC_FileProxy::$enabled = $proxyStatus;
if ($decryptedControlData === 'ownCloud') {
return true;
2013-05-20 03:24:36 +04:00
}
return false;
}
2013-05-20 03:24:36 +04:00
/**
* @return string
*/
public function getRecoveryKeyId()
{
return $this->recoveryKeyId;
}
/**
* @brief add recovery key to all encrypted files
*/
2013-05-20 03:24:36 +04:00
public function addRecoveryKeys($path = '/')
{
$dirContent = $this->view->getDirectoryContent($this->keyfilesPath . $path);
foreach ($dirContent as $item) {
$filePath = substr($item['path'], 25);
if ($item['type'] == 'dir') {
2013-05-20 03:24:36 +04:00
$this->addRecoveryKeys($filePath . '/');
} else {
$session = new Session(new \OC_FilesystemView('/'));
$sharingEnabled = \OCP\Share::isEnabled();
$file = substr($filePath, 0, -4);
$usersSharing = $this->getSharingUsersArray($sharingEnabled, $file);
2013-05-20 03:24:36 +04:00
$this->setSharedFileKeyfiles($session, $usersSharing, $file);
}
}
}
/**
* @brief remove recovery key to all encrypted files
*/
2013-05-20 03:24:36 +04:00
public function removeRecoveryKeys($path = '/')
{
$dirContent = $this->view->getDirectoryContent($this->keyfilesPath . $path);
foreach ($dirContent as $item) {
$filePath = substr($item['path'], 25);
if ($item['type'] == 'dir') {
2013-05-20 03:24:36 +04:00
$this->removeRecoveryKeys($filePath . '/');
} else {
$file = substr($filePath, 0, -4);
2013-05-20 03:24:36 +04:00
$this->view->unlink($this->shareKeysPath . '/' . $file . '.' . $this->recoveryKeyId . '.shareKey');
}
}
}
/**
* @brief decrypt given file with recovery key and encrypt it again to the owner and his new key
2013-05-20 03:24:36 +04:00
* @param string $file
* @param string $privateKey recovery key to decrypt the file
*/
2013-05-20 03:24:36 +04:00
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, true, true);
$userIds = $result['users'];
$userIds[] = $this->recoveryKeyId;
if ($result['public']) {
$userIds[] = $this->publicShareKeyId;
}
} else {
$userIds = array($this->userId, $this->recoveryKeyId);
}
$filteredUids = $this->filterShareReadyUsers($userIds);
$proxyStatus = \OC_FileProxy::$enabled;
\OC_FileProxy::$enabled = false;
//decrypt file key
2013-05-20 03:24:36 +04:00
$encKeyfile = $this->view->file_get_contents($this->keyfilesPath . $file . ".key");
$shareKey = $this->view->file_get_contents($this->shareKeysPath . $file . "." . $this->recoveryKeyId . ".shareKey");
$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);
// write new keys to filesystem TDOO!
2013-05-20 03:24:36 +04:00
$this->view->file_put_contents($this->keyfilesPath . $file . '.key', $multiEncKey['data']);
foreach ($multiEncKey['keys'] as $userId => $shareKey) {
2013-05-20 03:24:36 +04:00
$shareKeyPath = $this->shareKeysPath . $file . '.' . $userId . '.shareKey';
$this->view->file_put_contents($shareKeyPath, $shareKey);
}
// Return proxy to original status
\OC_FileProxy::$enabled = $proxyStatus;
}
/**
* @brief collect all files and recover them one by one
2013-05-20 03:24:36 +04:00
* @param string $path to look for files keys
* @param string $privateKey private recovery key which is used to decrypt the files
*/
2013-05-20 03:24:36 +04:00
private function recoverAllFiles($path, $privateKey)
{
$dirContent = $this->view->getDirectoryContent($this->keyfilesPath . $path);
foreach ($dirContent as $item) {
$filePath = substr($item['path'], 25);
if ($item['type'] == 'dir') {
2013-05-17 00:57:55 +04:00
$this->recoverAllFiles($filePath . '/', $privateKey);
} else {
$file = substr($filePath, 0, -4);
$this->recoverFile($file, $privateKey);
}
}
}
/**
* @brief recover users files in case of password lost
2013-05-20 03:24:36 +04:00
* @param string $recoveryPassword
*/
2013-05-20 03:24:36 +04:00
public function recoverUsersFiles($recoveryPassword)
{
// Disable encryption proxy to prevent recursive calls
$proxyStatus = \OC_FileProxy::$enabled;
\OC_FileProxy::$enabled = false;
2013-05-20 03:24:36 +04:00
$encryptedKey = $this->view->file_get_contents('/owncloud_private_key/' . $this->recoveryKeyId . '.private.key');
$privateKey = Crypt::symmetricDecryptFileContent($encryptedKey, $recoveryPassword);
\OC_FileProxy::$enabled = $proxyStatus;
$this->recoverAllFiles('/', $privateKey);
}
}