parent
770dcbf663
commit
f89a3604aa
|
@ -70,4 +70,8 @@ Notes
|
||||||
is handled in the login hook listener. Therefore each time the user logs in
|
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
|
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
|
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
|
* @brief retrieve keyfile for an encrypted file
|
||||||
* @param \OC_FilesystemView $view
|
* @param \OC_FilesystemView $view
|
||||||
|
|
|
@ -138,34 +138,9 @@ class Proxy extends \OC_FileProxy {
|
||||||
// Encrypt data
|
// Encrypt data
|
||||||
$encData = Crypt::symmetricEncryptFileContent( $data, $plainKey );
|
$encData = Crypt::symmetricEncryptFileContent( $data, $plainKey );
|
||||||
|
|
||||||
// Check if key recovery is enabled
|
$sharingEnabled = \OCP\Share::isEnabled();
|
||||||
$recoveryEnabled = $util->recoveryEnabled();
|
|
||||||
|
|
||||||
// Make sure that a share key is generated for the owner too
|
$uniqueUserIds = $util->getSharingUsersArray( $sharingEnabled, $filePath );
|
||||||
$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 );
|
|
||||||
|
|
||||||
// Fetch public keys for all users who will share the file
|
// Fetch public keys for all users who will share the file
|
||||||
$publicKeys = Keymanager::getPublicKeys( $rootView, $uniqueUserIds );
|
$publicKeys = Keymanager::getPublicKeys( $rootView, $uniqueUserIds );
|
||||||
|
@ -280,6 +255,8 @@ class Proxy extends \OC_FileProxy {
|
||||||
*/
|
*/
|
||||||
public function preUnlink( $path ) {
|
public function preUnlink( $path ) {
|
||||||
|
|
||||||
|
$path = Keymanager::fixPartialFilePath( $path );
|
||||||
|
|
||||||
// Disable encryption proxy to prevent recursive calls
|
// Disable encryption proxy to prevent recursive calls
|
||||||
\OC_FileProxy::$enabled = false;
|
\OC_FileProxy::$enabled = false;
|
||||||
|
|
||||||
|
@ -290,17 +267,20 @@ class Proxy extends \OC_FileProxy {
|
||||||
$util = new Util( $view, $userId );
|
$util = new Util( $view, $userId );
|
||||||
|
|
||||||
// Format path to be relative to user files dir
|
// 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
|
// Delete keyfile & shareKey so it isn't orphaned
|
||||||
if (
|
if (
|
||||||
! (
|
! (
|
||||||
Keymanager::deleteFileKey( $view, $owner, $ownerPath )
|
Keymanager::deleteFileKey( $view, $fileOwner, $ownerPath )
|
||||||
&& Keymanager::delShareKey( $view, $owner, $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
|
* 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
|
* data longer, and input is chunked into smaller pieces in order to result in
|
||||||
* a 8192 encrypted block size.
|
* 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 {
|
class Stream {
|
||||||
|
|
||||||
|
@ -171,17 +174,21 @@ class Stream {
|
||||||
//
|
//
|
||||||
// Get the data from the file handle
|
// Get the data from the file handle
|
||||||
$data = fread( $this->handle, 8192 );
|
$data = fread( $this->handle, 8192 );
|
||||||
|
|
||||||
|
$result = '';
|
||||||
|
|
||||||
if ( strlen( $data ) ) {
|
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;
|
// $length = $this->size - $pos;
|
||||||
|
@ -224,18 +231,20 @@ class Stream {
|
||||||
*/
|
*/
|
||||||
public function getKey() {
|
public function getKey() {
|
||||||
|
|
||||||
// fix performance issues
|
// Check if key is already set
|
||||||
if(isset($this->keyfile) && isset($this->encKeyfile)) {
|
if ( isset( $this->plainKey ) && 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' ) ) {
|
|
||||||
|
|
||||||
// TODO: add error handling for when file exists but no
|
return true;
|
||||||
// keyfile
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
// Fetch existing keyfile
|
||||||
$this->encKeyfile = Keymanager::getFileKey( $this->rootView, $this->userId, $this->relPath );
|
$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 );
|
$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;
|
return true;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -303,7 +317,7 @@ class Stream {
|
||||||
$pointer = ftell( $this->handle );
|
$pointer = ftell( $this->handle );
|
||||||
|
|
||||||
// Make sure the userId is set
|
// Make sure the userId is set
|
||||||
$this->getuser();
|
$this->setUserProperty();
|
||||||
|
|
||||||
// TODO: Check if file is shared, if so, use multiKeyEncrypt and
|
// TODO: Check if file is shared, if so, use multiKeyEncrypt and
|
||||||
// save shareKeys in necessary user directories
|
// save shareKeys in necessary user directories
|
||||||
|
@ -313,21 +327,34 @@ class Stream {
|
||||||
// one), save the newly generated keyfile
|
// one), save the newly generated keyfile
|
||||||
if ( ! $this->getKey() ) {
|
if ( ! $this->getKey() ) {
|
||||||
|
|
||||||
// TODO: Reuse the keyfile, it it exists, instead of making a new one
|
$util = new Util( $this->rootView, $this->userId );
|
||||||
$this->keyfile = Crypt::generateKey();
|
|
||||||
|
$this->plainKey = Crypt::generateKey();
|
||||||
|
|
||||||
$this->publicKey = Keymanager::getPublicKey( $this->rootView, $this->userId );
|
$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( '/' );
|
$view = new \OC_FilesystemView( '/' );
|
||||||
$userId = \OCP\User::getUser();
|
|
||||||
|
|
||||||
// Save the new encrypted file key
|
// 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
|
// If extra data is left over from the last round, make sure it
|
||||||
// is integrated into the next 6126 / 8192 block
|
// is integrated into the next 6126 / 8192 block
|
||||||
if ( $this->writeCache ) {
|
if ( $this->writeCache ) {
|
||||||
|
@ -355,7 +382,7 @@ class Stream {
|
||||||
//
|
//
|
||||||
// fseek( $this->handle, - ( $currentPos % 8192 ), SEEK_CUR );
|
// 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 );
|
// $x = substr( $block, 0, $currentPos % 8192 );
|
||||||
//
|
//
|
||||||
|
@ -396,7 +423,7 @@ class Stream {
|
||||||
// Read the chunk from the start of $data
|
// Read the chunk from the start of $data
|
||||||
$chunk = substr( $data, 0, 6126 );
|
$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
|
// Write the data chunk to disk. This will be
|
||||||
// attended to the last data chunk if the file
|
// attended to the last data chunk if the file
|
||||||
|
@ -461,7 +488,7 @@ class Stream {
|
||||||
// Set keyfile property for file in question
|
// Set keyfile property for file in question
|
||||||
$this->getKey();
|
$this->getKey();
|
||||||
|
|
||||||
$encrypted = $this->preWriteEncrypt( $this->writeCache, $this->keyfile );
|
$encrypted = $this->preWriteEncrypt( $this->writeCache, $this->plainKey );
|
||||||
|
|
||||||
fwrite( $this->handle, $encrypted );
|
fwrite( $this->handle, $encrypted );
|
||||||
|
|
||||||
|
|
|
@ -864,7 +864,46 @@ class Util {
|
||||||
return array_unique( $users );
|
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
|
* @brief get uid of the owners of the file and the path to the file
|
||||||
* @param $shareFilePath Path of the file to check
|
* @param $shareFilePath Path of the file to check
|
||||||
|
|
Loading…
Reference in New Issue