Working on stream{} writing

Development snapshot
This commit is contained in:
Sam Tuke 2013-04-16 18:29:22 +02:00
parent 770dcbf663
commit f89a3604aa
5 changed files with 133 additions and 61 deletions

View File

@ -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.

View File

@ -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

View File

@ -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 )
)
) {

View File

@ -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 );

View File

@ -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