parent
770dcbf663
commit
f89a3604aa
|
@ -70,4 +70,8 @@ Notes
|
|||
is handled in the login hook listener. Therefore each time the user logs in
|
||||
their files are scanned to detect unencrypted and legacy encrypted files, and
|
||||
they are (re)encrypted as necessary. This may present a performance issue; we
|
||||
need to monitor this.
|
||||
need to monitor this.
|
||||
- When files are saved to ownCloud via WebDAV, a .part file extension is used so
|
||||
that the file isn't cached before the upload has been completed. .part files
|
||||
are not compatible with files_encrytion's key management system however, so
|
||||
we have to always sanitise such paths manually before using them.
|
|
@ -132,6 +132,28 @@ class Keymanager {
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Remove .path extension from a file path
|
||||
* @param string $path Path that may identify a .part file
|
||||
* @return string File path without .part extension
|
||||
*/
|
||||
public static function fixPartialFilePath( $path ) {
|
||||
|
||||
if ( preg_match( '/\.part$/', $path ) ) {
|
||||
|
||||
$newLength = strlen( $path ) - 5;
|
||||
$fPath = substr( $path, 0, $newLength );
|
||||
|
||||
return $fPath;
|
||||
|
||||
} else {
|
||||
|
||||
return $path;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief retrieve keyfile for an encrypted file
|
||||
* @param \OC_FilesystemView $view
|
||||
|
|
|
@ -138,34 +138,9 @@ class Proxy extends \OC_FileProxy {
|
|||
// Encrypt data
|
||||
$encData = Crypt::symmetricEncryptFileContent( $data, $plainKey );
|
||||
|
||||
// Check if key recovery is enabled
|
||||
$recoveryEnabled = $util->recoveryEnabled();
|
||||
$sharingEnabled = \OCP\Share::isEnabled();
|
||||
|
||||
// Make sure that a share key is generated for the owner too
|
||||
$userIds = array( $userId );
|
||||
|
||||
if ( \OCP\Share::isEnabled() ) {
|
||||
|
||||
// Find out who, if anyone, is sharing the file
|
||||
$shareUids = \OCP\Share::getUsersSharingFile( $filePath, true, true, true );
|
||||
|
||||
$userIds = array_merge( $userIds, $shareUids );
|
||||
|
||||
}
|
||||
|
||||
// If recovery is enabled, add the
|
||||
// Admin UID to list of users to share to
|
||||
if ( $recoveryEnabled ) {
|
||||
|
||||
// FIXME: Create a separate admin user purely for recovery, and create method in util for fetching this id from DB?
|
||||
$adminUid = 'recoveryAdmin';
|
||||
|
||||
$userIds[] = $adminUid;
|
||||
|
||||
}
|
||||
|
||||
// Remove duplicate UIDs
|
||||
$uniqueUserIds = array_unique ( $userIds );
|
||||
$uniqueUserIds = $util->getSharingUsersArray( $sharingEnabled, $filePath );
|
||||
|
||||
// Fetch public keys for all users who will share the file
|
||||
$publicKeys = Keymanager::getPublicKeys( $rootView, $uniqueUserIds );
|
||||
|
@ -280,6 +255,8 @@ class Proxy extends \OC_FileProxy {
|
|||
*/
|
||||
public function preUnlink( $path ) {
|
||||
|
||||
$path = Keymanager::fixPartialFilePath( $path );
|
||||
|
||||
// Disable encryption proxy to prevent recursive calls
|
||||
\OC_FileProxy::$enabled = false;
|
||||
|
||||
|
@ -290,17 +267,20 @@ class Proxy extends \OC_FileProxy {
|
|||
$util = new Util( $view, $userId );
|
||||
|
||||
// Format path to be relative to user files dir
|
||||
$relPath = $util->stripUserFilesPath($path);
|
||||
$relPath = $util->stripUserFilesPath( $path );
|
||||
|
||||
list($owner, $ownerPath) = $util->getUidAndFilename($relPath);
|
||||
// list( $owner, $ownerPath ) = $util->getUidAndFilename( $relPath );
|
||||
|
||||
$filePath = $owner . '/' . 'files_encryption' . '/' . 'keyfiles' . '/'. $ownerPath;
|
||||
$fileOwner = \OC\Files\Filesystem::getOwner( $path );
|
||||
$ownerPath = $util->stripUserFilesPath( $path ); // TODO: Don't trust $path, fetch owner path
|
||||
|
||||
$filePath = $fileOwner . '/' . 'files_encryption' . '/' . 'keyfiles' . '/'. $ownerPath;
|
||||
|
||||
// Delete keyfile & shareKey so it isn't orphaned
|
||||
if (
|
||||
! (
|
||||
Keymanager::deleteFileKey( $view, $owner, $ownerPath )
|
||||
&& Keymanager::delShareKey( $view, $owner, $ownerPath )
|
||||
Keymanager::deleteFileKey( $view, $fileOwner, $ownerPath )
|
||||
&& Keymanager::delShareKey( $view, $fileOwner, $ownerPath )
|
||||
)
|
||||
) {
|
||||
|
||||
|
|
|
@ -44,6 +44,9 @@ namespace OCA\Encryption;
|
|||
* 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 {
|
||||
|
||||
|
@ -171,17 +174,21 @@ class Stream {
|
|||
//
|
||||
// Get the data from the file handle
|
||||
$data = fread( $this->handle, 8192 );
|
||||
|
||||
$result = '';
|
||||
|
||||
if ( strlen( $data ) ) {
|
||||
|
||||
$this->getKey();
|
||||
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' );
|
||||
|
||||
$result = Crypt::symmetricDecryptFileContent( $data, $this->keyfile );
|
||||
}
|
||||
|
||||
// Decrypt data
|
||||
$result = Crypt::symmetricDecryptFileContent( $data, $this->plainKey );
|
||||
|
||||
} else {
|
||||
|
||||
$result = '';
|
||||
|
||||
}
|
||||
|
||||
// $length = $this->size - $pos;
|
||||
|
@ -224,18 +231,20 @@ class Stream {
|
|||
*/
|
||||
public function getKey() {
|
||||
|
||||
// fix performance issues
|
||||
if(isset($this->keyfile) && isset($this->encKeyfile)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If a keyfile already exists for a file named identically to
|
||||
// file to be written
|
||||
if ( $this->rootView->file_exists( $this->userId . '/'. 'files_encryption' . '/' . 'keyfiles' . '/' . $this->relPath . '.key' ) ) {
|
||||
// Check if key is already set
|
||||
if ( isset( $this->plainKey ) && isset( $this->encKeyfile ) ) {
|
||||
|
||||
// TODO: add error handling for when file exists but no
|
||||
// keyfile
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
// Avoid problems with .part file extensions
|
||||
$this->relPath = Keymanager::fixPartialFilePath( $this->relPath );
|
||||
|
||||
// If a keyfile already exists
|
||||
if ( $this->rootView->file_exists( $this->userId . '/'. 'files_encryption' . '/' . 'keyfiles' . '/' . $this->relPath . '.key' ) ) {
|
||||
|
||||
// Fetch and decrypt keyfile
|
||||
// Fetch existing keyfile
|
||||
$this->encKeyfile = Keymanager::getFileKey( $this->rootView, $this->userId, $this->relPath );
|
||||
|
||||
|
@ -247,12 +256,17 @@ class Stream {
|
|||
|
||||
$shareKey = Keymanager::getShareKey( $this->rootView, $this->userId, $this->relPath );
|
||||
|
||||
$this->keyfile = Crypt::multiKeyDecrypt( $this->encKeyfile, $shareKey, $privateKey );
|
||||
$this->plainKey = Crypt::multiKeyDecrypt( $this->encKeyfile, $shareKey, $privateKey );
|
||||
|
||||
trigger_error( '$this->relPath = '.$this->relPath );
|
||||
trigger_error( '$this->userId = '.$this->userId);
|
||||
trigger_error( '$this->encKeyfile = '.$this->encKeyfile );
|
||||
trigger_error( '$this->plainKey1 = '.var_export($this->plainKey, 1));
|
||||
|
||||
return true;
|
||||
|
||||
} else {
|
||||
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
@ -303,7 +317,7 @@ class Stream {
|
|||
$pointer = ftell( $this->handle );
|
||||
|
||||
// Make sure the userId is set
|
||||
$this->getuser();
|
||||
$this->setUserProperty();
|
||||
|
||||
// TODO: Check if file is shared, if so, use multiKeyEncrypt and
|
||||
// save shareKeys in necessary user directories
|
||||
|
@ -313,21 +327,34 @@ class Stream {
|
|||
// one), save the newly generated keyfile
|
||||
if ( ! $this->getKey() ) {
|
||||
|
||||
// TODO: Reuse the keyfile, it it exists, instead of making a new one
|
||||
$this->keyfile = Crypt::generateKey();
|
||||
$util = new Util( $this->rootView, $this->userId );
|
||||
|
||||
$this->plainKey = Crypt::generateKey();
|
||||
|
||||
$this->publicKey = Keymanager::getPublicKey( $this->rootView, $this->userId );
|
||||
|
||||
$this->encKeyfile = Crypt::keyEncrypt( $this->keyfile, $this->publicKey );
|
||||
$sharingEnabled = \OCP\Share::isEnabled();
|
||||
|
||||
$uniqueUserIds = $util->getSharingUsersArray( $sharingEnabled, $this->relPath );
|
||||
|
||||
// Fetch public keys for all users who will share the file
|
||||
$publicKeys = Keymanager::getPublicKeys( $this->rootView, $uniqueUserIds );
|
||||
|
||||
$this->encKeyfiles = Crypt::multiKeyEncrypt( $this->plainKey, $publicKeys );
|
||||
|
||||
$view = new \OC_FilesystemView( '/' );
|
||||
$userId = \OCP\User::getUser();
|
||||
|
||||
// Save the new encrypted file key
|
||||
Keymanager::setFileKey( $view, $this->relPath, $userId, $this->encKeyfile );
|
||||
Keymanager::setShareKeys( $view, $this->relPath, $this->encKeyfiles['keys'] );
|
||||
|
||||
// trigger_error( '$this->relPath = '.$this->relPath );
|
||||
// trigger_error( '$this->userId = '.$this->userId);
|
||||
// trigger_error( '$this->encKeyfile = '.var_export($this->encKeyfiles, 1) );
|
||||
|
||||
}
|
||||
|
||||
|
||||
// trigger_error( '$this->plainKey2 = '.var_export($this->plainKey, 1));
|
||||
|
||||
// If extra data is left over from the last round, make sure it
|
||||
// is integrated into the next 6126 / 8192 block
|
||||
if ( $this->writeCache ) {
|
||||
|
@ -355,7 +382,7 @@ class Stream {
|
|||
//
|
||||
// fseek( $this->handle, - ( $currentPos % 8192 ), SEEK_CUR );
|
||||
//
|
||||
// $block = Crypt::symmetricDecryptFileContent( $unencryptedNewBlock, $this->keyfile );
|
||||
// $block = Crypt::symmetricDecryptFileContent( $unencryptedNewBlock, $this->plainKey );
|
||||
//
|
||||
// $x = substr( $block, 0, $currentPos % 8192 );
|
||||
//
|
||||
|
@ -396,7 +423,7 @@ class Stream {
|
|||
// Read the chunk from the start of $data
|
||||
$chunk = substr( $data, 0, 6126 );
|
||||
|
||||
$encrypted = $this->preWriteEncrypt( $chunk, $this->keyfile );
|
||||
$encrypted = $this->preWriteEncrypt( $chunk, $this->plainKey );
|
||||
|
||||
// Write the data chunk to disk. This will be
|
||||
// attended to the last data chunk if the file
|
||||
|
@ -461,7 +488,7 @@ class Stream {
|
|||
// Set keyfile property for file in question
|
||||
$this->getKey();
|
||||
|
||||
$encrypted = $this->preWriteEncrypt( $this->writeCache, $this->keyfile );
|
||||
$encrypted = $this->preWriteEncrypt( $this->writeCache, $this->plainKey );
|
||||
|
||||
fwrite( $this->handle, $encrypted );
|
||||
|
||||
|
|
|
@ -864,7 +864,46 @@ class Util {
|
|||
return array_unique( $users );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Find, sanitise and format users sharing a file
|
||||
* @note This wraps other methods into a portable bundle
|
||||
*/
|
||||
public function getSharingUsersArray( $sharingEnabled, $filePath ) {
|
||||
|
||||
// Check if key recovery is enabled
|
||||
$recoveryEnabled = $this->recoveryEnabled();
|
||||
|
||||
// Make sure that a share key is generated for the owner too
|
||||
$userIds = array( $this->userId );
|
||||
|
||||
if ( $sharingEnabled ) {
|
||||
|
||||
// Find out who, if anyone, is sharing the file
|
||||
$shareUids = \OCP\Share::getUsersSharingFile( $filePath, true, true, true );
|
||||
|
||||
$userIds = array_merge( $userIds, $shareUids );
|
||||
|
||||
}
|
||||
|
||||
// If recovery is enabled, add the
|
||||
// Admin UID to list of users to share to
|
||||
if ( $recoveryEnabled ) {
|
||||
|
||||
// FIXME: Create a separate admin user purely for recovery, and create method in util for fetching this id from DB?
|
||||
$adminUid = 'recoveryAdmin';
|
||||
|
||||
$userIds[] = $adminUid;
|
||||
|
||||
}
|
||||
|
||||
// Remove duplicate UIDs
|
||||
$uniqueUserIds = array_unique ( $userIds );
|
||||
|
||||
return $uniqueUserIds;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief get uid of the owners of the file and the path to the file
|
||||
* @param $shareFilePath Path of the file to check
|
||||
|
|
Loading…
Reference in New Issue