merge master into filesystem
commit
f858381775
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (c) 2012, Bjoern Schiessle <schiessle@owncloud.com>
|
||||
* This file is licensed under the Affero General Public License version 3 or later.
|
||||
* See the COPYING-README file.
|
||||
*/
|
||||
|
||||
use OCA\Encryption\Keymanager;
|
||||
|
||||
OCP\JSON::checkAppEnabled('files_encryption');
|
||||
OCP\JSON::checkLoggedIn();
|
||||
OCP\JSON::callCheck();
|
||||
|
||||
$mode = $_POST['mode'];
|
||||
$changePasswd = false;
|
||||
$passwdChanged = false;
|
||||
|
||||
if ( isset($_POST['newpasswd']) && isset($_POST['oldpasswd']) ) {
|
||||
$oldpasswd = $_POST['oldpasswd'];
|
||||
$newpasswd = $_POST['newpasswd'];
|
||||
$changePasswd = true;
|
||||
$passwdChanged = Keymanager::changePasswd($oldpasswd, $newpasswd);
|
||||
}
|
||||
|
||||
$query = \OC_DB::prepare( "SELECT mode FROM *PREFIX*encryption WHERE uid = ?" );
|
||||
$result = $query->execute(array(\OCP\User::getUser()));
|
||||
|
||||
if ($result->fetchRow()){
|
||||
$query = OC_DB::prepare( 'UPDATE *PREFIX*encryption SET mode = ? WHERE uid = ?' );
|
||||
} else {
|
||||
$query = OC_DB::prepare( 'INSERT INTO *PREFIX*encryption ( mode, uid ) VALUES( ?, ? )' );
|
||||
}
|
||||
|
||||
if ( (!$changePasswd || $passwdChanged) && $query->execute(array($mode, \OCP\User::getUser())) ) {
|
||||
OCP\JSON::success();
|
||||
} else {
|
||||
OCP\JSON::error();
|
||||
}
|
||||
@ -1,21 +1,37 @@
|
||||
<?php
|
||||
|
||||
OC::$CLASSPATH['OC_Crypt'] = 'apps/files_encryption/lib/crypt.php';
|
||||
OC::$CLASSPATH['OC_CryptStream'] = 'apps/files_encryption/lib/cryptstream.php';
|
||||
OC::$CLASSPATH['OC_FileProxy_Encryption'] = 'apps/files_encryption/lib/proxy.php';
|
||||
OC::$CLASSPATH['OCA\Encryption\Crypt'] = 'apps/files_encryption/lib/crypt.php';
|
||||
OC::$CLASSPATH['OCA\Encryption\Hooks'] = 'apps/files_encryption/hooks/hooks.php';
|
||||
OC::$CLASSPATH['OCA\Encryption\Util'] = 'apps/files_encryption/lib/util.php';
|
||||
OC::$CLASSPATH['OCA\Encryption\Keymanager'] = 'apps/files_encryption/lib/keymanager.php';
|
||||
OC::$CLASSPATH['OCA\Encryption\Stream'] = 'apps/files_encryption/lib/stream.php';
|
||||
OC::$CLASSPATH['OCA\Encryption\Proxy'] = 'apps/files_encryption/lib/proxy.php';
|
||||
OC::$CLASSPATH['OCA\Encryption\Session'] = 'apps/files_encryption/lib/session.php';
|
||||
|
||||
OC_FileProxy::register(new OC_FileProxy_Encryption());
|
||||
OC_FileProxy::register( new OCA\Encryption\Proxy() );
|
||||
|
||||
OCP\Util::connectHook('OC_User', 'post_login', 'OC_Crypt', 'loginListener');
|
||||
OCP\Util::connectHook( 'OC_User','post_login', 'OCA\Encryption\Hooks', 'login' );
|
||||
OCP\Util::connectHook( 'OC_Webdav_Properties', 'update', 'OCA\Encryption\Hooks', 'updateKeyfile' );
|
||||
OCP\Util::connectHook( 'OC_User','post_setPassword','OCA\Encryption\Hooks' ,'setPassphrase' );
|
||||
|
||||
stream_wrapper_register('crypt', 'OC_CryptStream');
|
||||
stream_wrapper_register( 'crypt', 'OCA\Encryption\Stream' );
|
||||
|
||||
// force the user to re-loggin if the encryption key isn't unlocked
|
||||
// (happens when a user is logged in before the encryption app is enabled)
|
||||
if ( ! isset($_SESSION['enckey']) and OCP\User::isLoggedIn()) {
|
||||
$session = new OCA\Encryption\Session();
|
||||
|
||||
if (
|
||||
! $session->getPrivateKey( \OCP\USER::getUser() )
|
||||
&& OCP\User::isLoggedIn()
|
||||
&& OCA\Encryption\Crypt::mode() == 'server'
|
||||
) {
|
||||
|
||||
// Force the user to re-log in if the encryption key isn't unlocked (happens when a user is logged in before the encryption app is enabled)
|
||||
OCP\User::logout();
|
||||
header("Location: ".OC::$WEBROOT.'/');
|
||||
|
||||
header( "Location: " . OC::$WEBROOT.'/' );
|
||||
|
||||
exit();
|
||||
|
||||
}
|
||||
|
||||
OCP\App::registerAdmin('files_encryption', 'settings');
|
||||
OCP\App::registerAdmin( 'files_encryption', 'settings');
|
||||
OCP\App::registerPersonal( 'files_encryption', 'settings-personal' );
|
||||
@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="ISO-8859-1" ?>
|
||||
<database>
|
||||
<name>*dbname*</name>
|
||||
<create>true</create>
|
||||
<overwrite>false</overwrite>
|
||||
<charset>utf8</charset>
|
||||
<table>
|
||||
<name>*dbprefix*encryption</name>
|
||||
<declaration>
|
||||
<field>
|
||||
<name>uid</name>
|
||||
<type>text</type>
|
||||
<notnull>true</notnull>
|
||||
<length>64</length>
|
||||
</field>
|
||||
<field>
|
||||
<name>mode</name>
|
||||
<type>text</type>
|
||||
<notnull>true</notnull>
|
||||
<length>64</length>
|
||||
</field>
|
||||
</declaration>
|
||||
</table>
|
||||
</database>
|
||||
@ -1 +1 @@
|
||||
0.2
|
||||
0.2.1
|
||||
@ -0,0 +1,143 @@
|
||||
<?php
|
||||
/**
|
||||
* ownCloud
|
||||
*
|
||||
* @author Sam Tuke
|
||||
* @copyright 2012 Sam Tuke samtuke@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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OCA\Encryption;
|
||||
|
||||
/**
|
||||
* Class for hook specific logic
|
||||
*/
|
||||
|
||||
class Hooks {
|
||||
|
||||
# TODO: use passphrase for encrypting private key that is separate to the login password
|
||||
|
||||
/**
|
||||
* @brief 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 ( Crypt::mode( $params['uid'] ) == 'server' ) {
|
||||
|
||||
# TODO: use lots of dependency injection here
|
||||
|
||||
$view = new \OC_FilesystemView( '/' );
|
||||
|
||||
$util = new Util( $view, $params['uid'] );
|
||||
|
||||
if ( ! $util->ready() ) {
|
||||
|
||||
\OC_Log::write( 'Encryption library', 'User account "' . $params['uid'] . '" is not ready for encryption; configuration started' , \OC_Log::DEBUG );
|
||||
|
||||
return $util->setupServerSide( $params['password'] );
|
||||
|
||||
}
|
||||
|
||||
\OC_FileProxy::$enabled = false;
|
||||
|
||||
$encryptedKey = Keymanager::getPrivateKey( $view, $params['uid'] );
|
||||
|
||||
\OC_FileProxy::$enabled = true;
|
||||
|
||||
# TODO: dont manually encrypt the private keyfile - use the config options of openssl_pkey_export instead for better mobile compatibility
|
||||
|
||||
$privateKey = Crypt::symmetricDecryptFileContent( $encryptedKey, $params['password'] );
|
||||
|
||||
$session = new Session();
|
||||
|
||||
$session->setPrivateKey( $privateKey, $params['uid'] );
|
||||
|
||||
$view1 = new \OC_FilesystemView( '/' . $params['uid'] );
|
||||
|
||||
// Set legacy encryption key if it exists, to support
|
||||
// depreciated encryption system
|
||||
if (
|
||||
$view1->file_exists( 'encryption.key' )
|
||||
&& $legacyKey = $view1->file_get_contents( 'encryption.key' )
|
||||
) {
|
||||
|
||||
$_SESSION['legacyenckey'] = Crypt::legacyDecrypt( $legacyKey, $params['password'] );
|
||||
|
||||
}
|
||||
// }
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Change a user's encryption passphrase
|
||||
* @param array $params keys: uid, password
|
||||
*/
|
||||
public static function setPassphrase( $params ) {
|
||||
|
||||
// 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' ) {
|
||||
|
||||
// Get existing decrypted private key
|
||||
$privateKey = $_SESSION['privateKey'];
|
||||
|
||||
// Encrypt private key with new user pwd as passphrase
|
||||
$encryptedPrivateKey = Crypt::symmetricEncryptFileContent( $privateKey, $params['password'] );
|
||||
|
||||
// Save private key
|
||||
Keymanager::setPrivateKey( $encryptedPrivateKey );
|
||||
|
||||
# NOTE: Session does not need to be updated as the
|
||||
# private key has not changed, only the passphrase
|
||||
# used to decrypt it has changed
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief update the encryption key of the file uploaded by the client
|
||||
*/
|
||||
public static function updateKeyfile( $params ) {
|
||||
|
||||
if ( Crypt::mode() == 'client' ) {
|
||||
|
||||
if ( isset( $params['properties']['key'] ) ) {
|
||||
|
||||
Keymanager::setFileKey( $params['path'], $params['properties']['key'] );
|
||||
|
||||
} else {
|
||||
|
||||
\OC_Log::write(
|
||||
'Encryption library', "Client side encryption is enabled but the client doesn't provide a encryption key for the file!"
|
||||
, \OC_Log::ERROR
|
||||
);
|
||||
|
||||
error_log( "Client side encryption is enabled but the client doesn't provide an encryption key for the file!" );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
?>
|
||||
@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Copyright (c) 2012, Bjoern Schiessle <schiessle@owncloud.com>
|
||||
* This file is licensed under the Affero General Public License version 3 or later.
|
||||
* See the COPYING-README file.
|
||||
*/
|
||||
|
||||
$(document).ready(function(){
|
||||
$('input[name=encryption_mode]').change(function(){
|
||||
var prevmode = document.getElementById('prev_encryption_mode').value
|
||||
var client=$('input[value="client"]:checked').val()
|
||||
,server=$('input[value="server"]:checked').val()
|
||||
,user=$('input[value="user"]:checked').val()
|
||||
,none=$('input[value="none"]:checked').val()
|
||||
if (client) {
|
||||
$.post(OC.filePath('files_encryption', 'ajax', 'mode.php'), { mode: 'client' });
|
||||
if (prevmode == 'server') {
|
||||
OC.dialogs.info(t('encryption', 'Please switch to your ownCloud client and change your encryption password to complete the conversion.'), t('encryption', 'switched to client side encryption'));
|
||||
}
|
||||
} else if (server) {
|
||||
if (prevmode == 'client') {
|
||||
OC.dialogs.form([{text:'Login password', name:'newpasswd', type:'password'},{text:'Encryption password used on the client', name:'oldpasswd', type:'password'}],t('encryption', 'Change encryption password to login password'), function(data) {
|
||||
$.post(OC.filePath('files_encryption', 'ajax', 'mode.php'), { mode: 'server', newpasswd: data[0].value, oldpasswd: data[1].value }, function(result) {
|
||||
if (result.status != 'success') {
|
||||
document.getElementById(prevmode+'_encryption').checked = true;
|
||||
OC.dialogs.alert(t('encryption', 'Please check your passwords and try again.'), t('encryption', 'Could not change your file encryption password to your login password'))
|
||||
} else {
|
||||
console.log("alles super");
|
||||
}
|
||||
}, true);
|
||||
});
|
||||
} else {
|
||||
$.post(OC.filePath('files_encryption', 'ajax', 'mode.php'), { mode: 'server' });
|
||||
}
|
||||
} else {
|
||||
$.post(OC.filePath('files_encryption', 'ajax', 'mode.php'), { mode: 'none' });
|
||||
}
|
||||
})
|
||||
})
|
||||
@ -1,6 +1,5 @@
|
||||
<?php $TRANSLATIONS = array(
|
||||
"Encryption" => "رمزگذاری",
|
||||
"Exclude the following file types from encryption" => "نادیده گرفتن فایل های زیر برای رمز گذاری",
|
||||
"None" => "هیچکدام",
|
||||
"Enable Encryption" => "فعال کردن رمزگذاری"
|
||||
);
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<?php $TRANSLATIONS = array(
|
||||
"Encryption" => "Cifrado",
|
||||
"Exclude the following file types from encryption" => "Excluír os seguintes tipos de ficheiro do cifrado",
|
||||
"Encryption" => "Encriptado",
|
||||
"Exclude the following file types from encryption" => "Excluír os seguintes tipos de ficheiro da encriptación",
|
||||
"None" => "Nada",
|
||||
"Enable Encryption" => "Activar o cifrado"
|
||||
"Enable Encryption" => "Habilitar encriptación"
|
||||
);
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<?php $TRANSLATIONS = array(
|
||||
"Encryption" => "Titkosítás",
|
||||
"Enable Encryption" => "A titkosítás engedélyezése",
|
||||
"Exclude the following file types from encryption" => "A következő fájl típusok kizárása a titkosításból",
|
||||
"None" => "Egyik sem",
|
||||
"Exclude the following file types from encryption" => "A következő fájltípusok kizárása a titkosításból"
|
||||
"Enable Encryption" => "Titkosítás engedélyezése"
|
||||
);
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<?php $TRANSLATIONS = array(
|
||||
"Encryption" => "Šifriranje",
|
||||
"Exclude the following file types from encryption" => "Navedene vrste datotek naj ne bodo šifrirane",
|
||||
"Exclude the following file types from encryption" => "Naslednje vrste datotek naj se ne šifrirajo",
|
||||
"None" => "Brez",
|
||||
"Enable Encryption" => "Omogoči šifriranje"
|
||||
);
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<?php $TRANSLATIONS = array(
|
||||
"Encryption" => "Mã hóa",
|
||||
"Exclude the following file types from encryption" => "Loại trừ các loại tập tin sau đây từ mã hóa",
|
||||
"None" => "Không có gì hết",
|
||||
"None" => "none",
|
||||
"Enable Encryption" => "BẬT mã hóa"
|
||||
);
|
||||
|
||||
@ -1,177 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* ownCloud
|
||||
*
|
||||
* @author Robin Appelman
|
||||
* @copyright 2011 Robin Appelman icewind1991@gmail.com
|
||||
*
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* transparently encrypted filestream
|
||||
*
|
||||
* you can use it as wrapper around an existing stream by setting
|
||||
* OC_CryptStream::$sourceStreams['foo']=array('path'=>$path, 'stream'=>$stream)
|
||||
* and then fopen('crypt://streams/foo');
|
||||
*/
|
||||
|
||||
class OC_CryptStream{
|
||||
public static $sourceStreams=array();
|
||||
private $source;
|
||||
private $path;
|
||||
private $meta=array();//header/meta for source stream
|
||||
private $writeCache;
|
||||
private $size;
|
||||
private static $rootView;
|
||||
|
||||
public function stream_open($path, $mode, $options, &$opened_path) {
|
||||
if (!self::$rootView) {
|
||||
self::$rootView=new \OC\Files\View('');
|
||||
}
|
||||
$path=str_replace('crypt://', '', $path);
|
||||
if (dirname($path)=='streams' and isset(self::$sourceStreams[basename($path)])) {
|
||||
$this->source=self::$sourceStreams[basename($path)]['stream'];
|
||||
$this->path=self::$sourceStreams[basename($path)]['path'];
|
||||
$this->size=self::$sourceStreams[basename($path)]['size'];
|
||||
} else {
|
||||
$this->path=$path;
|
||||
if ($mode=='w' or $mode=='w+' or $mode=='wb' or $mode=='wb+') {
|
||||
$this->size=0;
|
||||
} else {
|
||||
$this->size=self::$rootView->filesize($path, $mode);
|
||||
}
|
||||
OC_FileProxy::$enabled=false;//disable fileproxies so we can open the source file
|
||||
$this->source=self::$rootView->fopen($path, $mode);
|
||||
OC_FileProxy::$enabled=true;
|
||||
if ( ! is_resource($this->source)) {
|
||||
OCP\Util::writeLog('files_encryption', 'failed to open '.$path, OCP\Util::ERROR);
|
||||
}
|
||||
}
|
||||
if (is_resource($this->source)) {
|
||||
$this->meta=stream_get_meta_data($this->source);
|
||||
}
|
||||
return is_resource($this->source);
|
||||
}
|
||||
|
||||
public function stream_seek($offset, $whence=SEEK_SET) {
|
||||
$this->flush();
|
||||
fseek($this->source, $offset, $whence);
|
||||
}
|
||||
|
||||
public function stream_tell() {
|
||||
return ftell($this->source);
|
||||
}
|
||||
|
||||
public function stream_read($count) {
|
||||
//$count will always be 8192 https://bugs.php.net/bug.php?id=21641
|
||||
//This makes this function a lot simpler but will breake everything the moment it's fixed
|
||||
$this->writeCache='';
|
||||
if ($count!=8192) {
|
||||
OCP\Util::writeLog('files_encryption',
|
||||
'php bug 21641 no longer holds, decryption will not work',
|
||||
OCP\Util::FATAL);
|
||||
die();
|
||||
}
|
||||
$pos=ftell($this->source);
|
||||
$data=fread($this->source, 8192);
|
||||
if (strlen($data)) {
|
||||
$result=OC_Crypt::decrypt($data);
|
||||
} else {
|
||||
$result='';
|
||||
}
|
||||
$length=$this->size-$pos;
|
||||
if ($length<8192) {
|
||||
$result=substr($result, 0, $length);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function stream_write($data) {
|
||||
$length=strlen($data);
|
||||
$currentPos=ftell($this->source);
|
||||
if ($this->writeCache) {
|
||||
$data=$this->writeCache.$data;
|
||||
$this->writeCache='';
|
||||
}
|
||||
if ($currentPos%8192!=0) {
|
||||
//make sure we always start on a block start
|
||||
fseek($this->source, -($currentPos%8192), SEEK_CUR);
|
||||
$encryptedBlock=fread($this->source, 8192);
|
||||
fseek($this->source, -($currentPos%8192), SEEK_CUR);
|
||||
$block=OC_Crypt::decrypt($encryptedBlock);
|
||||
$data=substr($block, 0, $currentPos%8192).$data;
|
||||
fseek($this->source, -($currentPos%8192), SEEK_CUR);
|
||||
}
|
||||
$currentPos=ftell($this->source);
|
||||
while ($remainingLength=strlen($data)>0) {
|
||||
if ($remainingLength<8192) {
|
||||
$this->writeCache=$data;
|
||||
$data='';
|
||||
} else {
|
||||
$encrypted=OC_Crypt::encrypt(substr($data, 0, 8192));
|
||||
fwrite($this->source, $encrypted);
|
||||
$data=substr($data, 8192);
|
||||
}
|
||||
}
|
||||
$this->size=max($this->size, $currentPos+$length);
|
||||
return $length;
|
||||
}
|
||||
|
||||
public function stream_set_option($option, $arg1, $arg2) {
|
||||
switch($option) {
|
||||
case STREAM_OPTION_BLOCKING:
|
||||
stream_set_blocking($this->source, $arg1);
|
||||
break;
|
||||
case STREAM_OPTION_READ_TIMEOUT:
|
||||
stream_set_timeout($this->source, $arg1, $arg2);
|
||||
break;
|
||||
case STREAM_OPTION_WRITE_BUFFER:
|
||||
stream_set_write_buffer($this->source, $arg1, $arg2);
|
||||
}
|
||||
}
|
||||
|
||||
public function stream_stat() {
|
||||
return fstat($this->source);
|
||||
}
|
||||
|
||||
public function stream_lock($mode) {
|
||||
flock($this->source, $mode);
|
||||
}
|
||||
|
||||
public function stream_flush() {
|
||||
return fflush($this->source);
|
||||
}
|
||||
|
||||
public function stream_eof() {
|
||||
return feof($this->source);
|
||||
}
|
||||
|
||||
private function flush() {
|
||||
if ($this->writeCache) {
|
||||
$encrypted=OC_Crypt::encrypt($this->writeCache);
|
||||
fwrite($this->source, $encrypted);
|
||||
$this->writeCache='';
|
||||
}
|
||||
}
|
||||
|
||||
public function stream_close() {
|
||||
$this->flush();
|
||||
if($this->meta['mode']!='r' and $this->meta['mode']!='rb') {
|
||||
\OC\Files\Filesystem::putFileInfo($this->path, array('encrypted' => true, 'size' => $this->size), '');
|
||||
}
|
||||
return fclose($this->source);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,365 @@
|
||||
<?php
|
||||
/***
|
||||
* ownCloud
|
||||
*
|
||||
* @author Bjoern Schiessle
|
||||
* @copyright 2012 Bjoern Schiessle <schiessle@owncloud.com>
|
||||
*
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OCA\Encryption;
|
||||
|
||||
/**
|
||||
* @brief Class to manage storage and retrieval of encryption keys
|
||||
* @note Where a method requires a view object, it's root must be '/'
|
||||
*/
|
||||
class Keymanager {
|
||||
|
||||
# TODO: make all dependencies (including static classes) explicit, such as ocfsview objects, by adding them as method arguments (dependency injection)
|
||||
|
||||
/**
|
||||
* @brief retrieve the ENCRYPTED private key from a user
|
||||
*
|
||||
* @return string private key or false
|
||||
* @note the key returned by this method must be decrypted before use
|
||||
*/
|
||||
public static function getPrivateKey( \OC_FilesystemView $view, $user ) {
|
||||
|
||||
$path = '/' . $user . '/' . 'files_encryption' . '/' . $user.'.private.key';
|
||||
|
||||
$key = $view->file_get_contents( $path );
|
||||
|
||||
return $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief retrieve public key for a specified user
|
||||
* @return string public key or false
|
||||
*/
|
||||
public static function getPublicKey( \OC_FilesystemView $view, $userId ) {
|
||||
|
||||
return $view->file_get_contents( '/public-keys/' . '/' . $userId . '.public.key' );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief retrieve both keys from a user (private and public)
|
||||
* @return array keys: privateKey, publicKey
|
||||
*/
|
||||
public static function getUserKeys( \OC_FilesystemView $view, $userId ) {
|
||||
|
||||
return array(
|
||||
'publicKey' => self::getPublicKey( $view, $userId )
|
||||
, 'privateKey' => self::getPrivateKey( $view, $userId )
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Retrieve public keys of all users with access to a file
|
||||
* @param string $path Path to file
|
||||
* @return array of public keys for the given file
|
||||
* @note Checks that the sharing app is enabled should be performed
|
||||
* by client code, that isn't checked here
|
||||
*/
|
||||
public static function getPublicKeys( \OC_FilesystemView $view, $userId, $filePath ) {
|
||||
|
||||
$path = ltrim( $path, '/' );
|
||||
|
||||
$filepath = '/' . $userId . '/files/' . $filePath;
|
||||
|
||||
// Check if sharing is enabled
|
||||
if ( OC_App::isEnabled( 'files_sharing' ) ) {
|
||||
|
||||
// // Check if file was shared with other users
|
||||
// $query = \OC_DB::prepare( "
|
||||
// SELECT
|
||||
// uid_owner
|
||||
// , source
|
||||
// , target
|
||||
// , uid_shared_with
|
||||
// FROM
|
||||
// `*PREFIX*sharing`
|
||||
// WHERE
|
||||
// ( target = ? AND uid_shared_with = ? )
|
||||
// OR source = ?
|
||||
// " );
|
||||
//
|
||||
// $result = $query->execute( array ( $filepath, $userId, $filepath ) );
|
||||
//
|
||||
// $users = array();
|
||||
//
|
||||
// if ( $row = $result->fetchRow() )
|
||||
// {
|
||||
// $source = $row['source'];
|
||||
// $owner = $row['uid_owner'];
|
||||
// $users[] = $owner;
|
||||
// // get the uids of all user with access to the file
|
||||
// $query = \OC_DB::prepare( "SELECT source, uid_shared_with FROM `*PREFIX*sharing` WHERE source = ?" );
|
||||
// $result = $query->execute( array ($source));
|
||||
// while ( ($row = $result->fetchRow()) ) {
|
||||
// $users[] = $row['uid_shared_with'];
|
||||
//
|
||||
// }
|
||||
//
|
||||
// }
|
||||
|
||||
} else {
|
||||
|
||||
// check if it is a file owned by the user and not shared at all
|
||||
$userview = new \OC_FilesystemView( '/'.$userId.'/files/' );
|
||||
|
||||
if ( $userview->file_exists( $path ) ) {
|
||||
|
||||
$users[] = $userId;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$view = new \OC_FilesystemView( '/public-keys/' );
|
||||
|
||||
$keylist = array();
|
||||
|
||||
$count = 0;
|
||||
|
||||
foreach ( $users as $user ) {
|
||||
|
||||
$keylist['key'.++$count] = $view->file_get_contents( $user.'.public.key' );
|
||||
|
||||
}
|
||||
|
||||
return $keylist;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief retrieve keyfile for an encrypted file
|
||||
* @param string file name
|
||||
* @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( \OC_FilesystemView $view, $userId, $filePath ) {
|
||||
|
||||
$filePath_f = ltrim( $filePath, '/' );
|
||||
|
||||
// // update $keypath and $userId if path point to a file shared by someone else
|
||||
// $query = \OC_DB::prepare( "SELECT uid_owner, source, target FROM `*PREFIX*sharing` WHERE target = ? AND uid_shared_with = ?" );
|
||||
//
|
||||
// $result = $query->execute( array ('/'.$userId.'/files/'.$keypath, $userId));
|
||||
//
|
||||
// if ($row = $result->fetchRow()) {
|
||||
//
|
||||
// $keypath = $row['source'];
|
||||
// $keypath_parts = explode( '/', $keypath );
|
||||
// $userId = $keypath_parts[1];
|
||||
// $keypath = str_replace( '/' . $userId . '/files/', '', $keypath );
|
||||
//
|
||||
// }
|
||||
|
||||
return $view->file_get_contents( '/' . $userId . '/files_encryption/keyfiles/' . $filePath_f . '.key' );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief retrieve file encryption key
|
||||
*
|
||||
* @param string file name
|
||||
* @return string file key or false
|
||||
*/
|
||||
public static function deleteFileKey( $path, $staticUserClass = 'OCP\User' ) {
|
||||
|
||||
$keypath = ltrim( $path, '/' );
|
||||
$user = $staticUserClass::getUser();
|
||||
|
||||
// update $keypath and $user if path point to a file shared by someone else
|
||||
// $query = \OC_DB::prepare( "SELECT uid_owner, source, target FROM `*PREFIX*sharing` WHERE target = ? AND uid_shared_with = ?" );
|
||||
//
|
||||
// $result = $query->execute( array ('/'.$user.'/files/'.$keypath, $user));
|
||||
//
|
||||
// if ($row = $result->fetchRow()) {
|
||||
//
|
||||
// $keypath = $row['source'];
|
||||
// $keypath_parts = explode( '/', $keypath );
|
||||
// $user = $keypath_parts[1];
|
||||
// $keypath = str_replace( '/' . $user . '/files/', '', $keypath );
|
||||
//
|
||||
// }
|
||||
|
||||
$view = new \OC_FilesystemView('/'.$user.'/files_encryption/keyfiles/');
|
||||
|
||||
return $view->unlink( $keypath . '.key' );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 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 = \OCP\User::getUser();
|
||||
|
||||
$view = new \OC_FilesystemView( '/' . $user . '/files_encryption' );
|
||||
|
||||
\OC_FileProxy::$enabled = false;
|
||||
|
||||
if ( !$view->file_exists( '' ) ) $view->mkdir( '' );
|
||||
|
||||
return $view->file_put_contents( $user . '.private.key', $key );
|
||||
|
||||
\OC_FileProxy::$enabled = true;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief store private keys from the user
|
||||
*
|
||||
* @param string privatekey
|
||||
* @param string publickey
|
||||
* @return bool true/false
|
||||
*/
|
||||
public static function setUserKeys($privatekey, $publickey) {
|
||||
|
||||
return (self::setPrivateKey($privatekey) && self::setPublicKey($publickey));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief store public key of the user
|
||||
*
|
||||
* @param string key
|
||||
* @return bool true/false
|
||||
*/
|
||||
public static function setPublicKey( $key ) {
|
||||
|
||||
$view = new \OC_FilesystemView( '/public-keys' );
|
||||
|
||||
\OC_FileProxy::$enabled = false;
|
||||
|
||||
if ( !$view->file_exists( '' ) ) $view->mkdir( '' );
|
||||
|
||||
return $view->file_put_contents( \OCP\User::getUser() . '.public.key', $key );
|
||||
|
||||
\OC_FileProxy::$enabled = true;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief store file encryption key
|
||||
*
|
||||
* @param string $path relative path of the file, including filename
|
||||
* @param string $key
|
||||
* @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( $path, $key, $view = Null, $dbClassName = '\OC_DB') {
|
||||
|
||||
$targetPath = ltrim( $path, '/' );
|
||||
$user = \OCP\User::getUser();
|
||||
|
||||
// // update $keytarget and $user if key belongs to a file shared by someone else
|
||||
// $query = $dbClassName::prepare( "SELECT uid_owner, source, target FROM `*PREFIX*sharing` WHERE target = ? AND uid_shared_with = ?" );
|
||||
//
|
||||
// $result = $query->execute( array ( '/'.$user.'/files/'.$targetPath, $user ) );
|
||||
//
|
||||
// if ( $row = $result->fetchRow( ) ) {
|
||||
//
|
||||
// $targetPath = $row['source'];
|
||||
//
|
||||
// $targetPath_parts = explode( '/', $targetPath );
|
||||
//
|
||||
// $user = $targetPath_parts[1];
|
||||
//
|
||||
// $rootview = new \OC_FilesystemView( '/' );
|
||||
//
|
||||
// if ( ! $rootview->is_writable( $targetPath ) ) {
|
||||
//
|
||||
// \OC_Log::write( 'Encryption library', "File Key not updated because you don't have write access for the corresponding file", \OC_Log::ERROR );
|
||||
//
|
||||
// return false;
|
||||
//
|
||||
// }
|
||||
//
|
||||
// $targetPath = str_replace( '/'.$user.'/files/', '', $targetPath );
|
||||
//
|
||||
// //TODO: check for write permission on shared file once the new sharing API is in place
|
||||
//
|
||||
// }
|
||||
|
||||
$path_parts = pathinfo( $targetPath );
|
||||
|
||||
if ( !$view ) {
|
||||
|
||||
$view = new \OC_FilesystemView( '/' );
|
||||
|
||||
}
|
||||
|
||||
$view->chroot( '/' . $user . '/files_encryption/keyfiles' );
|
||||
|
||||
// If the file resides within a subdirectory, create it
|
||||
if (
|
||||
isset( $path_parts['dirname'] )
|
||||
&& ! $view->file_exists( $path_parts['dirname'] )
|
||||
) {
|
||||
|
||||
$view->mkdir( $path_parts['dirname'] );
|
||||
|
||||
}
|
||||
|
||||
// Save the keyfile in parallel directory
|
||||
return $view->file_put_contents( '/' . $targetPath . '.key', $key );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief change password of private encryption key
|
||||
*
|
||||
* @param string $oldpasswd old password
|
||||
* @param string $newpasswd new password
|
||||
* @return bool true/false
|
||||
*/
|
||||
public static function changePasswd($oldpasswd, $newpasswd) {
|
||||
|
||||
if ( \OCP\User::checkPassword(\OCP\User::getUser(), $newpasswd) ) {
|
||||
return Crypt::changekeypasscode($oldpasswd, $newpasswd);
|
||||
}
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Fetch the legacy encryption key from user files
|
||||
* @param string $login used to locate the legacy key
|
||||
* @param string $passphrase used to decrypt the legacy key
|
||||
* @return true / false
|
||||
*
|
||||
* if the key is left out, the default handeler will be used
|
||||
*/
|
||||
public function getLegacyKey() {
|
||||
|
||||
$user = \OCP\User::getUser();
|
||||
$view = new \OC_FilesystemView( '/' . $user );
|
||||
return $view->file_get_contents( 'encryption.key' );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,66 @@
|
||||
<?php
|
||||
/**
|
||||
* ownCloud
|
||||
*
|
||||
* @author Sam Tuke
|
||||
* @copyright 2012 Sam Tuke samtuke@owncloud.com
|
||||
*
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OCA\Encryption;
|
||||
|
||||
/**
|
||||
* Class for handling encryption related session data
|
||||
*/
|
||||
|
||||
class Session {
|
||||
|
||||
/**
|
||||
* @brief Sets user id for session and triggers emit
|
||||
* @return bool
|
||||
*
|
||||
*/
|
||||
public function setPrivateKey( $privateKey, $userId ) {
|
||||
|
||||
$_SESSION['privateKey'] = $privateKey;
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Gets user id for session and triggers emit
|
||||
* @returns string $privateKey The user's plaintext private key
|
||||
*
|
||||
*/
|
||||
public function getPrivateKey( $userId ) {
|
||||
|
||||
if (
|
||||
isset( $_SESSION['privateKey'] )
|
||||
&& !empty( $_SESSION['privateKey'] )
|
||||
) {
|
||||
|
||||
return $_SESSION['privateKey'];
|
||||
|
||||
} else {
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,464 @@
|
||||
<?php
|
||||
/**
|
||||
* ownCloud
|
||||
*
|
||||
* @author Robin Appelman
|
||||
* @copyright 2012 Sam Tuke <samtuke@owncloud.com>, 2011 Robin Appelman
|
||||
* <icewind1991@gmail.com>
|
||||
*
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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\Encryption;
|
||||
|
||||
/**
|
||||
* @brief 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.
|
||||
*/
|
||||
class Stream {
|
||||
|
||||
public static $sourceStreams = array();
|
||||
|
||||
# TODO: make all below properties private again once unit testing is configured correctly
|
||||
public $rawPath; // The raw path received by stream_open
|
||||
public $path_f; // The raw path formatted to include username and data directory
|
||||
private $userId;
|
||||
private $handle; // Resource returned by fopen
|
||||
private $path;
|
||||
private $readBuffer; // For streams that dont support seeking
|
||||
private $meta = array(); // Header / meta for source stream
|
||||
private $count;
|
||||
private $writeCache;
|
||||
public $size;
|
||||
private $publicKey;
|
||||
private $keyfile;
|
||||
private $encKeyfile;
|
||||
private static $view; // a fsview object set to user dir
|
||||
private $rootView; // a fsview object set to '/'
|
||||
|
||||
public function stream_open( $path, $mode, $options, &$opened_path ) {
|
||||
|
||||
// Get access to filesystem via filesystemview object
|
||||
if ( !self::$view ) {
|
||||
|
||||
self::$view = new \OC_FilesystemView( $this->userId . '/' );
|
||||
|
||||
}
|
||||
|
||||
// Set rootview object if necessary
|
||||
if ( ! $this->rootView ) {
|
||||
|
||||
$this->rootView = new \OC_FilesystemView( $this->userId . '/' );
|
||||
|
||||
}
|
||||
|
||||
$this->userId = \OCP\User::getUser();
|
||||
|
||||
// Get the bare file path
|
||||
$path = str_replace( 'crypt://', '', $path );
|
||||
|
||||
$this->rawPath = $path;
|
||||
|
||||
$this->path_f = $this->userId . '/files/' . $path;
|
||||
|
||||
if (
|
||||
dirname( $path ) == 'streams'
|
||||
and isset( self::$sourceStreams[basename( $path )] )
|
||||
) {
|
||||
|
||||
// Is this just for unit testing purposes?
|
||||
|
||||
$this->handle = self::$sourceStreams[basename( $path )]['stream'];
|
||||
|
||||
$this->path = self::$sourceStreams[basename( $path )]['path'];
|
||||
|
||||
$this->size = self::$sourceStreams[basename( $path )]['size'];
|
||||
|
||||
} else {
|
||||
|
||||
if (
|
||||
$mode == 'w'
|
||||
or $mode == 'w+'
|
||||
or $mode == 'wb'
|
||||
or $mode == 'wb+'
|
||||
) {
|
||||
|
||||
$this->size = 0;
|
||||
|
||||
} else {
|
||||
|
||||
|
||||
|
||||
$this->size = self::$view->filesize( $this->path_f, $mode );
|
||||
|
||||
//$this->size = filesize( $path );
|
||||
|
||||
}
|
||||
|
||||
// Disable fileproxies so we can open the source file without recursive encryption
|
||||
\OC_FileProxy::$enabled = false;
|
||||
|
||||
//$this->handle = fopen( $path, $mode );
|
||||
|
||||
$this->handle = self::$view->fopen( $this->path_f, $mode );
|
||||
|
||||
\OC_FileProxy::$enabled = true;
|
||||
|
||||
if ( !is_resource( $this->handle ) ) {
|
||||
|
||||
\OCP\Util::writeLog( 'files_encryption', 'failed to open '.$path, \OCP\Util::ERROR );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ( is_resource( $this->handle ) ) {
|
||||
|
||||
$this->meta = stream_get_meta_data( $this->handle );
|
||||
|
||||
}
|
||||
|
||||
return is_resource( $this->handle );
|
||||
|
||||
}
|
||||
|
||||
public function stream_seek( $offset, $whence = SEEK_SET ) {
|
||||
|
||||
$this->flush();
|
||||
|
||||
fseek( $this->handle, $offset, $whence );
|
||||
|
||||
}
|
||||
|
||||
public function stream_tell() {
|
||||
return ftell($this->handle);
|
||||
}
|
||||
|
||||
public function stream_read( $count ) {
|
||||
|
||||
$this->writeCache = '';
|
||||
|
||||
if ( $count != 8192 ) {
|
||||
|
||||
// $count will always be 8192 https://bugs.php.net/bug.php?id=21641
|
||||
// This makes this function a lot simpler, but will break this class if the above 'bug' gets 'fixed'
|
||||
\OCP\Util::writeLog( 'files_encryption', 'PHP "bug" 21641 no longer holds, decryption system requires refactoring', OCP\Util::FATAL );
|
||||
|
||||
die();
|
||||
|
||||
}
|
||||
|
||||
// $pos = ftell( $this->handle );
|
||||
//
|
||||
// Get the data from the file handle
|
||||
$data = fread( $this->handle, 8192 );
|
||||
|
||||
if ( strlen( $data ) ) {
|
||||
|
||||
$this->getKey();
|
||||
|
||||
$result = Crypt::symmetricDecryptFileContent( $data, $this->keyfile );
|
||||
|
||||
} else {
|
||||
|
||||
$result = '';
|
||||
|
||||
}
|
||||
|
||||
// $length = $this->size - $pos;
|
||||
//
|
||||
// if ( $length < 8192 ) {
|
||||
//
|
||||
// $result = substr( $result, 0, $length );
|
||||
//
|
||||
// }
|
||||
|
||||
return $result;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Encrypt and pad data ready for writting to disk
|
||||
* @param string $plainData data to be encrypted
|
||||
* @param string $key key to use for encryption
|
||||
* @return 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 ) ) {
|
||||
|
||||
return $encrypted;
|
||||
|
||||
} else {
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the keyfile for the current file, generate one if necessary
|
||||
* @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() {
|
||||
|
||||
// If a keyfile already exists for a file named identically to file to be written
|
||||
if ( self::$view->file_exists( $this->userId . '/'. 'files_encryption' . '/' . 'keyfiles' . '/' . $this->rawPath . '.key' ) ) {
|
||||
|
||||
# TODO: add error handling for when file exists but no keyfile
|
||||
|
||||
// Fetch existing keyfile
|
||||
$this->encKeyfile = Keymanager::getFileKey( $this->rootView, $this->userId, $this->rawPath );
|
||||
|
||||
$this->getUser();
|
||||
|
||||
$session = new Session();
|
||||
|
||||
$privateKey = $session->getPrivateKey( $this->userId );
|
||||
|
||||
$this->keyfile = Crypt::keyDecrypt( $this->encKeyfile, $privateKey );
|
||||
|
||||
return true;
|
||||
|
||||
} else {
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function getuser() {
|
||||
|
||||
// Only get the user again if it isn't already set
|
||||
if ( empty( $this->userId ) ) {
|
||||
|
||||
# TODO: Move this user call out of here - it belongs elsewhere
|
||||
$this->userId = \OCP\User::getUser();
|
||||
|
||||
}
|
||||
|
||||
# TODO: Add a method for getting the user in case OCP\User::
|
||||
# getUser() doesn't work (can that scenario ever occur?)
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 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 writeCache 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 ) {
|
||||
|
||||
// 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
|
||||
\OC_FileProxy::$enabled = false;
|
||||
|
||||
// Get the length of the unencrypted data that we are handling
|
||||
$length = strlen( $data );
|
||||
|
||||
// So far this round, no data has been written
|
||||
$written = 0;
|
||||
|
||||
// Find out where we are up to in the writing of data to the file
|
||||
$pointer = ftell( $this->handle );
|
||||
|
||||
// Make sure the userId is set
|
||||
$this->getuser();
|
||||
|
||||
// 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->keyfile = Crypt::generateKey();
|
||||
|
||||
$this->publicKey = Keymanager::getPublicKey( $this->rootView, $this->userId );
|
||||
|
||||
$this->encKeyfile = Crypt::keyEncrypt( $this->keyfile, $this->publicKey );
|
||||
|
||||
// Save the new encrypted file key
|
||||
Keymanager::setFileKey( $this->rawPath, $this->encKeyfile, new \OC_FilesystemView( '/' ) );
|
||||
|
||||
# TODO: move this new OCFSV out of here some how, use DI
|
||||
|
||||
}
|
||||
|
||||
// If extra data is left over from the last round, make sure it is integrated into the next 6126 / 8192 block
|
||||
if ( $this->writeCache ) {
|
||||
|
||||
// Concat writeCache to start of $data
|
||||
$data = $this->writeCache . $data;
|
||||
|
||||
// Clear the write cache, ready for resuse - it has been flushed and its old contents processed
|
||||
$this->writeCache = '';
|
||||
|
||||
}
|
||||
//
|
||||
// // Make sure we always start on a block start
|
||||
if ( 0 != ( $pointer % 8192 ) ) { // if the current positoin of file indicator is not aligned to a 8192 byte block, fix it so that it is
|
||||
|
||||
// fseek( $this->handle, - ( $pointer % 8192 ), SEEK_CUR );
|
||||
//
|
||||
// $pointer = ftell( $this->handle );
|
||||
//
|
||||
// $unencryptedNewBlock = fread( $this->handle, 8192 );
|
||||
//
|
||||
// fseek( $this->handle, - ( $currentPos % 8192 ), SEEK_CUR );
|
||||
//
|
||||
// $block = Crypt::symmetricDecryptFileContent( $unencryptedNewBlock, $this->keyfile );
|
||||
//
|
||||
// $x = substr( $block, 0, $currentPos % 8192 );
|
||||
//
|
||||
// $data = $x . $data;
|
||||
//
|
||||
// fseek( $this->handle, - ( $currentPos % 8192 ), SEEK_CUR );
|
||||
//
|
||||
}
|
||||
|
||||
// $currentPos = ftell( $this->handle );
|
||||
|
||||
// // While there still remains somed data to be processed & written
|
||||
while( strlen( $data ) > 0 ) {
|
||||
//
|
||||
// // Remaining length for this iteration, not of the entire file (may be greater than 8192 bytes)
|
||||
// $remainingLength = strlen( $data );
|
||||
//
|
||||
// // If data remaining to be written is less than the size of 1 6126 byte block
|
||||
if ( strlen( $data ) < 6126 ) {
|
||||
|
||||
// Set writeCache to contents of $data
|
||||
// The writeCache will be carried over to the next write round, and added to the start of $data to ensure that written blocks are always the correct length. If there is still data in writeCache after the writing round has finished, then the data will be written to disk by $this->flush().
|
||||
$this->writeCache = $data;
|
||||
|
||||
// Clear $data ready for next round
|
||||
$data = '';
|
||||
//
|
||||
} else {
|
||||
|
||||
// Read the chunk from the start of $data
|
||||
$chunk = substr( $data, 0, 6126 );
|
||||
|
||||
$encrypted = $this->preWriteEncrypt( $chunk, $this->keyfile );
|
||||
|
||||
// Write the data chunk to disk. This will be addended to the last data chunk if the file being handled totals more than 6126 bytes
|
||||
fwrite( $this->handle, $encrypted );
|
||||
|
||||
$writtenLen = strlen( $encrypted );
|
||||
//fseek( $this->handle, $writtenLen, SEEK_CUR );
|
||||
|
||||
// Remove the chunk we just processed from $data, leaving only unprocessed data in $data var, for handling on the next round
|
||||
$data = substr( $data, 6126 );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$this->size = max( $this->size, $pointer + $length );
|
||||
|
||||
return $length;
|
||||
|
||||
}
|
||||
|
||||
|
||||
public function stream_set_option($option,$arg1,$arg2) {
|
||||
switch($option) {
|
||||
case STREAM_OPTION_BLOCKING:
|
||||
stream_set_blocking($this->handle,$arg1);
|
||||
break;
|
||||
case STREAM_OPTION_READ_TIMEOUT:
|
||||
stream_set_timeout($this->handle,$arg1,$arg2);
|
||||
break;
|
||||
case STREAM_OPTION_WRITE_BUFFER:
|
||||
stream_set_write_buffer($this->handle,$arg1,$arg2);
|
||||
}
|
||||
}
|
||||
|
||||
public function stream_stat() {
|
||||
return fstat($this->handle);
|
||||
}
|
||||
|
||||
public function stream_lock($mode) {
|
||||
flock($this->handle,$mode);
|
||||
}
|
||||
|
||||
public function stream_flush() {
|
||||
|
||||
return fflush($this->handle); // Not a typo: http://php.net/manual/en/function.fflush.php
|
||||
|
||||
}
|
||||
|
||||
public function stream_eof() {
|
||||
return feof($this->handle);
|
||||
}
|
||||
|
||||
private function flush() {
|
||||
|
||||
if ( $this->writeCache ) {
|
||||
|
||||
// Set keyfile property for file in question
|
||||
$this->getKey();
|
||||
|
||||
$encrypted = $this->preWriteEncrypt( $this->writeCache, $this->keyfile );
|
||||
|
||||
fwrite( $this->handle, $encrypted );
|
||||
|
||||
$this->writeCache = '';
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function stream_close() {
|
||||
|
||||
$this->flush();
|
||||
|
||||
if (
|
||||
$this->meta['mode']!='r'
|
||||
and $this->meta['mode']!='rb'
|
||||
) {
|
||||
|
||||
\OC_FileCache::put( $this->path, array( 'encrypted' => true, 'size' => $this->size ), '' );
|
||||
|
||||
}
|
||||
|
||||
return fclose( $this->handle );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (c) 2012 Bjoern Schiessle <schiessle@owncloud.com>
|
||||
* This file is licensed under the Affero General Public License version 3 or
|
||||
* later.
|
||||
* See the COPYING-README file.
|
||||
*/
|
||||
|
||||
$sysEncMode = \OC_Appconfig::getValue('files_encryption', 'mode', 'none');
|
||||
|
||||
if ($sysEncMode == 'user') {
|
||||
|
||||
$tmpl = new OCP\Template( 'files_encryption', 'settings-personal');
|
||||
|
||||
$query = \OC_DB::prepare( "SELECT mode FROM *PREFIX*encryption WHERE uid = ?" );
|
||||
$result = $query->execute(array(\OCP\User::getUser()));
|
||||
|
||||
if ($row = $result->fetchRow()){
|
||||
$mode = $row['mode'];
|
||||
} else {
|
||||
$mode = 'none';
|
||||
}
|
||||
|
||||
OCP\Util::addscript('files_encryption','settings-personal');
|
||||
$tmpl->assign('encryption_mode', $mode);
|
||||
return $tmpl->fetchPage();
|
||||
}
|
||||
|
||||
return null;
|
||||
@ -0,0 +1,45 @@
|
||||
<form id="encryption">
|
||||
<fieldset class="personalblock">
|
||||
<strong><?php echo $l->t('Choose encryption mode:'); ?></strong>
|
||||
<p>
|
||||
<input
|
||||
type="hidden"
|
||||
name="prev_encryption_mode"
|
||||
id="prev_encryption_mode"
|
||||
value="<?php echo $_['encryption_mode']; ?>"
|
||||
>
|
||||
|
||||
<input
|
||||
type="radio"
|
||||
name="encryption_mode"
|
||||
value="client"
|
||||
id='client_encryption'
|
||||
style="width:20px;"
|
||||
<?php if ($_['encryption_mode'] == 'client') echo "checked='checked'"?>
|
||||
/>
|
||||
<?php echo $l->t('Client side encryption (most secure but makes it impossible to access your data from the web interface)'); ?>
|
||||
<br />
|
||||
|
||||
<input
|
||||
type="radio"
|
||||
name="encryption_mode"
|
||||
value="server"
|
||||
id='server_encryption'
|
||||
style="width:20px;" <?php if ($_['encryption_mode'] == 'server') echo "checked='checked'"?>
|
||||
/>
|
||||
<?php echo $l->t('Server side encryption (allows you to access your files from the web interface and the desktop client)'); ?>
|
||||
<br />
|
||||
|
||||
<input
|
||||
type="radio"
|
||||
name="encryption_mode"
|
||||
value="none"
|
||||
id='none_encryption'
|
||||
style="width:20px;"
|
||||
<?php if ($_['encryption_mode'] == 'none') echo "checked='checked'"?>
|
||||
/>
|
||||
<?php echo $l->t('None (no encryption at all)'); ?>
|
||||
<br/>
|
||||
</p>
|
||||
</fieldset>
|
||||
</form>
|
||||
@ -1,14 +1,79 @@
|
||||
<form id="calendar">
|
||||
<form id="encryption">
|
||||
<fieldset class="personalblock">
|
||||
<legend><strong><?php echo $l->t('Encryption');?></strong></legend>
|
||||
<input type='checkbox'<?php if ($_['encryption_enabled']): ?> checked="checked"<?php endif; ?>
|
||||
id='enable_encryption' />
|
||||
<label for='enable_encryption'><?php echo $l->t('Enable Encryption')?></label><br />
|
||||
<select id='encryption_blacklist' title="<?php echo $l->t('None')?>" multiple="multiple">
|
||||
<?php foreach ($_['blacklist'] as $type): ?>
|
||||
<option selected="selected" value="<?php echo $type;?>"><?php echo $type;?></option>
|
||||
|
||||
<strong>
|
||||
<?php echo $l->t('Choose encryption mode:'); ?>
|
||||
</strong>
|
||||
|
||||
<p>
|
||||
<i>
|
||||
<?php echo $l->t('Important: Once you selected an encryption mode there is no way to change it back'); ?>
|
||||
</i>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<input
|
||||
type="radio"
|
||||
name="encryption_mode"
|
||||
id="client_encryption"
|
||||
value="client"
|
||||
style="width:20px;"
|
||||
<?php if ($_['encryption_mode'] == 'client') echo "checked='checked'"; if ($_['encryption_mode'] != 'none') echo "DISABLED"?>
|
||||
/>
|
||||
|
||||
<?php echo $l->t("Client side encryption (most secure but makes it impossible to access your data from the web interface)"); ?>
|
||||
<br />
|
||||
|
||||
<input
|
||||
type="radio"
|
||||
name="encryption_mode"
|
||||
id="server_encryption"
|
||||
value="server"
|
||||
style="width:20px;"
|
||||
<?php if ($_['encryption_mode'] == 'server') echo "checked='checked'"; if ($_['encryption_mode'] != 'none') echo "DISABLED"?>
|
||||
/>
|
||||
|
||||
<?php echo $l->t('Server side encryption (allows you to access your files from the web interface and the desktop client)'); ?>
|
||||
<br />
|
||||
|
||||
<input
|
||||
type="radio"
|
||||
name="encryption_mode"
|
||||
id="user_encryption"
|
||||
value="user"
|
||||
style="width:20px;"
|
||||
<?php if ($_['encryption_mode'] == 'user') echo "checked='checked'"; if ($_['encryption_mode'] != 'none') echo "DISABLED"?>
|
||||
/>
|
||||
|
||||
<?php echo $l->t('User specific (let the user decide)'); ?>
|
||||
<br/>
|
||||
|
||||
<input
|
||||
type="radio"
|
||||
name="encryption_mode"
|
||||
id="none_encryption"
|
||||
value="none"
|
||||
style="width:20px;"
|
||||
<?php if ($_['encryption_mode'] == 'none') echo "checked='checked'"; if ($_['encryption_mode'] != 'none') echo "DISABLED"?>
|
||||
/>
|
||||
|
||||
<?php echo $l->t('None (no encryption at all)'); ?>
|
||||
<br/>
|
||||
|
||||
</p>
|
||||
<p>
|
||||
<strong><?php echo $l->t('Encryption'); ?></strong>
|
||||
|
||||
<?php echo $l->t("Exclude the following file types from encryption"); ?>
|
||||
|
||||
<select
|
||||
id='encryption_blacklist'
|
||||
title="<?php echo $l->t('None')?>"
|
||||
multiple="multiple">
|
||||
<?php foreach($_["blacklist"] as $type): ?>
|
||||
<option selected="selected" value="<?php echo $type;?>"> <?php echo $type;?> </option>
|
||||
<?php endforeach;?>
|
||||
</select><br />
|
||||
<?php echo $l->t('Exclude the following file types from encryption'); ?>
|
||||
</select>
|
||||
</p>
|
||||
</fieldset>
|
||||
</form>
|
||||
|
||||
@ -0,0 +1,667 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (c) 2012 Sam Tuke <samtuke@owncloud.com>, and
|
||||
* Robin Appelman <icewind@owncloud.com>
|
||||
* This file is licensed under the Affero General Public License version 3 or
|
||||
* later.
|
||||
* See the COPYING-README file.
|
||||
*/
|
||||
|
||||
//require_once "PHPUnit/Framework/TestCase.php";
|
||||
require_once realpath( dirname(__FILE__).'/../../../3rdparty/Crypt_Blowfish/Blowfish.php' );
|
||||
require_once realpath( dirname(__FILE__).'/../../../lib/base.php' );
|
||||
require_once realpath( dirname(__FILE__).'/../lib/crypt.php' );
|
||||
require_once realpath( dirname(__FILE__).'/../lib/keymanager.php' );
|
||||
require_once realpath( dirname(__FILE__).'/../lib/proxy.php' );
|
||||
require_once realpath( dirname(__FILE__).'/../lib/stream.php' );
|
||||
require_once realpath( dirname(__FILE__).'/../lib/util.php' );
|
||||
require_once realpath( dirname(__FILE__).'/../appinfo/app.php' );
|
||||
|
||||
use OCA\Encryption;
|
||||
|
||||
// This has to go here because otherwise session errors arise, and the private
|
||||
// encryption key needs to be saved in the session
|
||||
\OC_User::login( 'admin', 'admin' );
|
||||
|
||||
/**
|
||||
* @note It would be better to use Mockery here for mocking out the session
|
||||
* handling process, and isolate calls to session class and data from the unit
|
||||
* tests relating to them (stream etc.). However getting mockery to work and
|
||||
* overload classes whilst also using the OC autoloader is difficult due to
|
||||
* load order Pear errors.
|
||||
*/
|
||||
|
||||
class Test_Crypt extends \PHPUnit_Framework_TestCase {
|
||||
|
||||
function setUp() {
|
||||
|
||||
// set content for encrypting / decrypting in tests
|
||||
$this->dataLong = file_get_contents( realpath( dirname(__FILE__).'/../lib/crypt.php' ) );
|
||||
$this->dataShort = 'hats';
|
||||
$this->dataUrl = realpath( dirname(__FILE__).'/../lib/crypt.php' );
|
||||
$this->legacyData = realpath( dirname(__FILE__).'/legacy-text.txt' );
|
||||
$this->legacyEncryptedData = realpath( dirname(__FILE__).'/legacy-encrypted-text.txt' );
|
||||
$this->randomKey = Encryption\Crypt::generateKey();
|
||||
|
||||
$keypair = Encryption\Crypt::createKeypair();
|
||||
$this->genPublicKey = $keypair['publicKey'];
|
||||
$this->genPrivateKey = $keypair['privateKey'];
|
||||
|
||||
$this->view = new \OC_FilesystemView( '/' );
|
||||
|
||||
\OC_User::setUserId( 'admin' );
|
||||
$this->userId = 'admin';
|
||||
$this->pass = 'admin';
|
||||
|
||||
\OC_Filesystem::init( '/' );
|
||||
\OC_Filesystem::mount( 'OC_Filestorage_Local', array('datadir' => \OC_User::getHome($this->userId)), '/' );
|
||||
|
||||
}
|
||||
|
||||
function tearDown() {
|
||||
|
||||
}
|
||||
|
||||
function testGenerateKey() {
|
||||
|
||||
# TODO: use more accurate (larger) string length for test confirmation
|
||||
|
||||
$key = Encryption\Crypt::generateKey();
|
||||
|
||||
$this->assertTrue( strlen( $key ) > 16 );
|
||||
|
||||
}
|
||||
|
||||
function testGenerateIv() {
|
||||
|
||||
$iv = Encryption\Crypt::generateIv();
|
||||
|
||||
$this->assertEquals( 16, strlen( $iv ) );
|
||||
|
||||
return $iv;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testGenerateIv
|
||||
*/
|
||||
function testConcatIv( $iv ) {
|
||||
|
||||
$catFile = Encryption\Crypt::concatIv( $this->dataLong, $iv );
|
||||
|
||||
// Fetch encryption metadata from end of file
|
||||
$meta = substr( $catFile, -22 );
|
||||
|
||||
$identifier = substr( $meta, 0, 6);
|
||||
|
||||
// Fetch IV from end of file
|
||||
$foundIv = substr( $meta, 6 );
|
||||
|
||||
$this->assertEquals( '00iv00', $identifier );
|
||||
|
||||
$this->assertEquals( $iv, $foundIv );
|
||||
|
||||
// Remove IV and IV identifier text to expose encrypted content
|
||||
$data = substr( $catFile, 0, -22 );
|
||||
|
||||
$this->assertEquals( $this->dataLong, $data );
|
||||
|
||||
return array(
|
||||
'iv' => $iv
|
||||
, 'catfile' => $catFile
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testConcatIv
|
||||
*/
|
||||
function testSplitIv( $testConcatIv ) {
|
||||
|
||||
// Split catfile into components
|
||||
$splitCatfile = Encryption\Crypt::splitIv( $testConcatIv['catfile'] );
|
||||
|
||||
// Check that original IV and split IV match
|
||||
$this->assertEquals( $testConcatIv['iv'], $splitCatfile['iv'] );
|
||||
|
||||
// Check that original data and split data match
|
||||
$this->assertEquals( $this->dataLong, $splitCatfile['encrypted'] );
|
||||
|
||||
}
|
||||
|
||||
function testAddPadding() {
|
||||
|
||||
$padded = Encryption\Crypt::addPadding( $this->dataLong );
|
||||
|
||||
$padding = substr( $padded, -2 );
|
||||
|
||||
$this->assertEquals( 'xx' , $padding );
|
||||
|
||||
return $padded;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testAddPadding
|
||||
*/
|
||||
function testRemovePadding( $padded ) {
|
||||
|
||||
$noPadding = Encryption\Crypt::RemovePadding( $padded );
|
||||
|
||||
$this->assertEquals( $this->dataLong, $noPadding );
|
||||
|
||||
}
|
||||
|
||||
function testEncrypt() {
|
||||
|
||||
$random = openssl_random_pseudo_bytes( 13 );
|
||||
|
||||
$iv = substr( base64_encode( $random ), 0, -4 ); // i.e. E5IG033j+mRNKrht
|
||||
|
||||
$crypted = Encryption\Crypt::encrypt( $this->dataUrl, $iv, 'hat' );
|
||||
|
||||
$this->assertNotEquals( $this->dataUrl, $crypted );
|
||||
|
||||
}
|
||||
|
||||
function testDecrypt() {
|
||||
|
||||
$random = openssl_random_pseudo_bytes( 13 );
|
||||
|
||||
$iv = substr( base64_encode( $random ), 0, -4 ); // i.e. E5IG033j+mRNKrht
|
||||
|
||||
$crypted = Encryption\Crypt::encrypt( $this->dataUrl, $iv, 'hat' );
|
||||
|
||||
$decrypt = Encryption\Crypt::decrypt( $crypted, $iv, 'hat' );
|
||||
|
||||
$this->assertEquals( $this->dataUrl, $decrypt );
|
||||
|
||||
}
|
||||
|
||||
function testSymmetricEncryptFileContent() {
|
||||
|
||||
# TODO: search in keyfile for actual content as IV will ensure this test always passes
|
||||
|
||||
$crypted = Encryption\Crypt::symmetricEncryptFileContent( $this->dataShort, 'hat' );
|
||||
|
||||
$this->assertNotEquals( $this->dataShort, $crypted );
|
||||
|
||||
|
||||
$decrypt = Encryption\Crypt::symmetricDecryptFileContent( $crypted, 'hat' );
|
||||
|
||||
$this->assertEquals( $this->dataShort, $decrypt );
|
||||
|
||||
}
|
||||
|
||||
// These aren't used for now
|
||||
// function testSymmetricBlockEncryptShortFileContent() {
|
||||
//
|
||||
// $crypted = Encryption\Crypt::symmetricBlockEncryptFileContent( $this->dataShort, $this->randomKey );
|
||||
//
|
||||
// $this->assertNotEquals( $this->dataShort, $crypted );
|
||||
//
|
||||
//
|
||||
// $decrypt = Encryption\Crypt::symmetricBlockDecryptFileContent( $crypted, $this->randomKey );
|
||||
//
|
||||
// $this->assertEquals( $this->dataShort, $decrypt );
|
||||
//
|
||||
// }
|
||||
//
|
||||
// function testSymmetricBlockEncryptLongFileContent() {
|
||||
//
|
||||
// $crypted = Encryption\Crypt::symmetricBlockEncryptFileContent( $this->dataLong, $this->randomKey );
|
||||
//
|
||||
// $this->assertNotEquals( $this->dataLong, $crypted );
|
||||
//
|
||||
//
|
||||
// $decrypt = Encryption\Crypt::symmetricBlockDecryptFileContent( $crypted, $this->randomKey );
|
||||
//
|
||||
// $this->assertEquals( $this->dataLong, $decrypt );
|
||||
//
|
||||
// }
|
||||
|
||||
function testSymmetricStreamEncryptShortFileContent() {
|
||||
|
||||
$filename = 'tmp-'.time();
|
||||
|
||||
$cryptedFile = file_put_contents( 'crypt://' . $filename, $this->dataShort );
|
||||
|
||||
// Test that data was successfully written
|
||||
$this->assertTrue( is_int( $cryptedFile ) );
|
||||
|
||||
|
||||
// Get file contents without using any wrapper to get it's actual contents on disk
|
||||
$retreivedCryptedFile = $this->view->file_get_contents( $this->userId . '/files/' . $filename );
|
||||
|
||||
// Check that the file was encrypted before being written to disk
|
||||
$this->assertNotEquals( $this->dataShort, $retreivedCryptedFile );
|
||||
|
||||
// Get private key
|
||||
$encryptedPrivateKey = Encryption\Keymanager::getPrivateKey( $this->view, $this->userId );
|
||||
|
||||
$decryptedPrivateKey = Encryption\Crypt::symmetricDecryptFileContent( $encryptedPrivateKey, $this->pass );
|
||||
|
||||
|
||||
// Get keyfile
|
||||
$encryptedKeyfile = Encryption\Keymanager::getFileKey( $this->view, $this->userId, $filename );
|
||||
|
||||
$decryptedKeyfile = Encryption\Crypt::keyDecrypt( $encryptedKeyfile, $decryptedPrivateKey );
|
||||
|
||||
|
||||
// Manually decrypt
|
||||
$manualDecrypt = Encryption\Crypt::symmetricBlockDecryptFileContent( $retreivedCryptedFile, $decryptedKeyfile );
|
||||
|
||||
// Check that decrypted data matches
|
||||
$this->assertEquals( $this->dataShort, $manualDecrypt );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Test that data that is written by the crypto stream wrapper
|
||||
* @note Encrypted data is manually prepared and decrypted here to avoid dependency on success of stream_read
|
||||
* @note If this test fails with truncate content, check that enough array slices are being rejoined to form $e, as the crypt.php file may have gotten longer and broken the manual
|
||||
* reassembly of its data
|
||||
*/
|
||||
function testSymmetricStreamEncryptLongFileContent() {
|
||||
|
||||
// Generate a a random filename
|
||||
$filename = 'tmp-'.time();
|
||||
|
||||
// Save long data as encrypted file using stream wrapper
|
||||
$cryptedFile = file_put_contents( 'crypt://' . $filename, $this->dataLong.$this->dataLong );
|
||||
|
||||
// Test that data was successfully written
|
||||
$this->assertTrue( is_int( $cryptedFile ) );
|
||||
|
||||
// Get file contents without using any wrapper to get it's actual contents on disk
|
||||
$retreivedCryptedFile = $this->view->file_get_contents( $this->userId . '/files/' . $filename );
|
||||
|
||||
// echo "\n\n\$retreivedCryptedFile = $retreivedCryptedFile\n\n";
|
||||
|
||||
// Check that the file was encrypted before being written to disk
|
||||
$this->assertNotEquals( $this->dataLong.$this->dataLong, $retreivedCryptedFile );
|
||||
|
||||
// Manuallly split saved file into separate IVs and encrypted chunks
|
||||
$r = preg_split('/(00iv00.{16,18})/', $retreivedCryptedFile, NULL, PREG_SPLIT_DELIM_CAPTURE);
|
||||
|
||||
//print_r($r);
|
||||
|
||||
// Join IVs and their respective data chunks
|
||||
$e = array( $r[0].$r[1], $r[2].$r[3], $r[4].$r[5], $r[6].$r[7], $r[8].$r[9], $r[10].$r[11], $r[12].$r[13] );//.$r[11], $r[12].$r[13], $r[14] );
|
||||
|
||||
//print_r($e);
|
||||
|
||||
|
||||
// Get private key
|
||||
$encryptedPrivateKey = Encryption\Keymanager::getPrivateKey( $this->view, $this->userId );
|
||||
|
||||
$decryptedPrivateKey = Encryption\Crypt::symmetricDecryptFileContent( $encryptedPrivateKey, $this->pass );
|
||||
|
||||
|
||||
// Get keyfile
|
||||
$encryptedKeyfile = Encryption\Keymanager::getFileKey( $this->view, $this->userId, $filename );
|
||||
|
||||
$decryptedKeyfile = Encryption\Crypt::keyDecrypt( $encryptedKeyfile, $decryptedPrivateKey );
|
||||
|
||||
|
||||
// Set var for reassembling decrypted content
|
||||
$decrypt = '';
|
||||
|
||||
// Manually decrypt chunk
|
||||
foreach ($e as $e) {
|
||||
|
||||
// echo "\n\$e = $e";
|
||||
|
||||
$chunkDecrypt = Encryption\Crypt::symmetricDecryptFileContent( $e, $decryptedKeyfile );
|
||||
|
||||
// Assemble decrypted chunks
|
||||
$decrypt .= $chunkDecrypt;
|
||||
|
||||
// echo "\n\$chunkDecrypt = $chunkDecrypt";
|
||||
|
||||
}
|
||||
|
||||
// echo "\n\$decrypt = $decrypt";
|
||||
|
||||
$this->assertEquals( $this->dataLong.$this->dataLong, $decrypt );
|
||||
|
||||
// Teardown
|
||||
|
||||
$this->view->unlink( $filename );
|
||||
|
||||
Encryption\Keymanager::deleteFileKey( $filename );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Test that data that is read by the crypto stream wrapper
|
||||
*/
|
||||
function testSymmetricStreamDecryptShortFileContent() {
|
||||
|
||||
$filename = 'tmp-'.time();
|
||||
|
||||
// Save long data as encrypted file using stream wrapper
|
||||
$cryptedFile = file_put_contents( 'crypt://' . $filename, $this->dataShort );
|
||||
|
||||
// Test that data was successfully written
|
||||
$this->assertTrue( is_int( $cryptedFile ) );
|
||||
|
||||
|
||||
// Get file contents without using any wrapper to get it's actual contents on disk
|
||||
$retreivedCryptedFile = $this->view->file_get_contents( $this->userId . '/files/' . $filename );
|
||||
|
||||
$decrypt = file_get_contents( 'crypt://' . $filename );
|
||||
|
||||
$this->assertEquals( $this->dataShort, $decrypt );
|
||||
|
||||
}
|
||||
|
||||
function testSymmetricStreamDecryptLongFileContent() {
|
||||
|
||||
$filename = 'tmp-'.time();
|
||||
|
||||
// Save long data as encrypted file using stream wrapper
|
||||
$cryptedFile = file_put_contents( 'crypt://' . $filename, $this->dataLong );
|
||||
|
||||
// Test that data was successfully written
|
||||
$this->assertTrue( is_int( $cryptedFile ) );
|
||||
|
||||
|
||||
// Get file contents without using any wrapper to get it's actual contents on disk
|
||||
$retreivedCryptedFile = $this->view->file_get_contents( $this->userId . '/files/' . $filename );
|
||||
|
||||
$decrypt = file_get_contents( 'crypt://' . $filename );
|
||||
|
||||
$this->assertEquals( $this->dataLong, $decrypt );
|
||||
|
||||
}
|
||||
|
||||
// Is this test still necessary?
|
||||
// function testSymmetricBlockStreamDecryptFileContent() {
|
||||
//
|
||||
// \OC_User::setUserId( 'admin' );
|
||||
//
|
||||
// // Disable encryption proxy to prevent unwanted en/decryption
|
||||
// \OC_FileProxy::$enabled = false;
|
||||
//
|
||||
// $cryptedFile = file_put_contents( 'crypt://' . '/blockEncrypt', $this->dataUrl );
|
||||
//
|
||||
// // Disable encryption proxy to prevent unwanted en/decryption
|
||||
// \OC_FileProxy::$enabled = false;
|
||||
//
|
||||
// echo "\n\n\$cryptedFile = " . $this->view->file_get_contents( '/blockEncrypt' );
|
||||
//
|
||||
// $retreivedCryptedFile = file_get_contents( 'crypt://' . '/blockEncrypt' );
|
||||
//
|
||||
// $this->assertEquals( $this->dataUrl, $retreivedCryptedFile );
|
||||
//
|
||||
// \OC_FileProxy::$enabled = false;
|
||||
//
|
||||
// }
|
||||
|
||||
function testSymmetricEncryptFileContentKeyfile() {
|
||||
|
||||
# TODO: search in keyfile for actual content as IV will ensure this test always passes
|
||||
|
||||
$crypted = Encryption\Crypt::symmetricEncryptFileContentKeyfile( $this->dataUrl );
|
||||
|
||||
$this->assertNotEquals( $this->dataUrl, $crypted['encrypted'] );
|
||||
|
||||
|
||||
$decrypt = Encryption\Crypt::symmetricDecryptFileContent( $crypted['encrypted'], $crypted['key'] );
|
||||
|
||||
$this->assertEquals( $this->dataUrl, $decrypt );
|
||||
|
||||
}
|
||||
|
||||
function testIsEncryptedContent() {
|
||||
|
||||
$this->assertFalse( Encryption\Crypt::isEncryptedContent( $this->dataUrl ) );
|
||||
|
||||
$this->assertFalse( Encryption\Crypt::isEncryptedContent( $this->legacyEncryptedData ) );
|
||||
|
||||
$keyfileContent = Encryption\Crypt::symmetricEncryptFileContent( $this->dataUrl, 'hat' );
|
||||
|
||||
$this->assertTrue( Encryption\Crypt::isEncryptedContent( $keyfileContent ) );
|
||||
|
||||
}
|
||||
|
||||
function testMultiKeyEncrypt() {
|
||||
|
||||
# TODO: search in keyfile for actual content as IV will ensure this test always passes
|
||||
|
||||
$pair1 = Encryption\Crypt::createKeypair();
|
||||
|
||||
$this->assertEquals( 2, count( $pair1 ) );
|
||||
|
||||
$this->assertTrue( strlen( $pair1['publicKey'] ) > 1 );
|
||||
|
||||
$this->assertTrue( strlen( $pair1['privateKey'] ) > 1 );
|
||||
|
||||
|
||||
$crypted = Encryption\Crypt::multiKeyEncrypt( $this->dataUrl, array( $pair1['publicKey'] ) );
|
||||
|
||||
$this->assertNotEquals( $this->dataUrl, $crypted['encrypted'] );
|
||||
|
||||
|
||||
$decrypt = Encryption\Crypt::multiKeyDecrypt( $crypted['encrypted'], $crypted['keys'][0], $pair1['privateKey'] );
|
||||
|
||||
$this->assertEquals( $this->dataUrl, $decrypt );
|
||||
|
||||
}
|
||||
|
||||
function testKeyEncrypt() {
|
||||
|
||||
// Generate keypair
|
||||
$pair1 = Encryption\Crypt::createKeypair();
|
||||
|
||||
// Encrypt data
|
||||
$crypted = Encryption\Crypt::keyEncrypt( $this->dataUrl, $pair1['publicKey'] );
|
||||
|
||||
$this->assertNotEquals( $this->dataUrl, $crypted );
|
||||
|
||||
// Decrypt data
|
||||
$decrypt = Encryption\Crypt::keyDecrypt( $crypted, $pair1['privateKey'] );
|
||||
|
||||
$this->assertEquals( $this->dataUrl, $decrypt );
|
||||
|
||||
}
|
||||
|
||||
// What is the point of this test? It doesn't use keyEncryptKeyfile()
|
||||
function testKeyEncryptKeyfile() {
|
||||
|
||||
# TODO: Don't repeat encryption from previous tests, use PHPUnit test interdependency instead
|
||||
|
||||
// Generate keypair
|
||||
$pair1 = Encryption\Crypt::createKeypair();
|
||||
|
||||
// Encrypt plain data, generate keyfile & encrypted file
|
||||
$cryptedData = Encryption\Crypt::symmetricEncryptFileContentKeyfile( $this->dataUrl );
|
||||
|
||||
// Encrypt keyfile
|
||||
$cryptedKey = Encryption\Crypt::keyEncrypt( $cryptedData['key'], $pair1['publicKey'] );
|
||||
|
||||
// Decrypt keyfile
|
||||
$decryptKey = Encryption\Crypt::keyDecrypt( $cryptedKey, $pair1['privateKey'] );
|
||||
|
||||
// Decrypt encrypted file
|
||||
$decryptData = Encryption\Crypt::symmetricDecryptFileContent( $cryptedData['encrypted'], $decryptKey );
|
||||
|
||||
$this->assertEquals( $this->dataUrl, $decryptData );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief test functionality of keyEncryptKeyfile() and
|
||||
* keyDecryptKeyfile()
|
||||
*/
|
||||
function testKeyDecryptKeyfile() {
|
||||
|
||||
$encrypted = Encryption\Crypt::keyEncryptKeyfile( $this->dataShort, $this->genPublicKey );
|
||||
|
||||
$this->assertNotEquals( $encrypted['data'], $this->dataShort );
|
||||
|
||||
$decrypted = Encryption\Crypt::keyDecryptKeyfile( $encrypted['data'], $encrypted['key'], $this->genPrivateKey );
|
||||
|
||||
$this->assertEquals( $decrypted, $this->dataShort );
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief test encryption using legacy blowfish method
|
||||
*/
|
||||
function testLegacyEncryptShort() {
|
||||
|
||||
$crypted = Encryption\Crypt::legacyEncrypt( $this->dataShort, $this->pass );
|
||||
|
||||
$this->assertNotEquals( $this->dataShort, $crypted );
|
||||
|
||||
# TODO: search inencrypted text for actual content to ensure it
|
||||
# genuine transformation
|
||||
|
||||
return $crypted;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief test decryption using legacy blowfish method
|
||||
* @depends testLegacyEncryptShort
|
||||
*/
|
||||
function testLegacyDecryptShort( $crypted ) {
|
||||
|
||||
$decrypted = Encryption\Crypt::legacyDecrypt( $crypted, $this->pass );
|
||||
|
||||
$this->assertEquals( $this->dataShort, $decrypted );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief test encryption using legacy blowfish method
|
||||
*/
|
||||
function testLegacyEncryptLong() {
|
||||
|
||||
$crypted = Encryption\Crypt::legacyEncrypt( $this->dataLong, $this->pass );
|
||||
|
||||
$this->assertNotEquals( $this->dataLong, $crypted );
|
||||
|
||||
# TODO: search inencrypted text for actual content to ensure it
|
||||
# genuine transformation
|
||||
|
||||
return $crypted;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief test decryption using legacy blowfish method
|
||||
* @depends testLegacyEncryptLong
|
||||
*/
|
||||
function testLegacyDecryptLong( $crypted ) {
|
||||
|
||||
$decrypted = Encryption\Crypt::legacyDecrypt( $crypted, $this->pass );
|
||||
|
||||
$this->assertEquals( $this->dataLong, $decrypted );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief test generation of legacy encryption key
|
||||
* @depends testLegacyDecryptShort
|
||||
*/
|
||||
function testLegacyCreateKey() {
|
||||
|
||||
// Create encrypted key
|
||||
$encKey = Encryption\Crypt::legacyCreateKey( $this->pass );
|
||||
|
||||
// Decrypt key
|
||||
$key = Encryption\Crypt::legacyDecrypt( $encKey, $this->pass );
|
||||
|
||||
$this->assertTrue( is_numeric( $key ) );
|
||||
|
||||
// Check that key is correct length
|
||||
$this->assertEquals( 20, strlen( $key ) );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief test decryption using legacy blowfish method
|
||||
* @depends testLegacyEncryptLong
|
||||
*/
|
||||
function testLegacyKeyRecryptKeyfileEncrypt( $crypted ) {
|
||||
|
||||
$recrypted = Encryption\Crypt::LegacyKeyRecryptKeyfile( $crypted, $this->pass, $this->genPublicKey, $this->pass );
|
||||
|
||||
$this->assertNotEquals( $this->dataLong, $recrypted['data'] );
|
||||
|
||||
return $recrypted;
|
||||
|
||||
# TODO: search inencrypted text for actual content to ensure it
|
||||
# genuine transformation
|
||||
|
||||
}
|
||||
|
||||
// function testEncryption(){
|
||||
//
|
||||
// $key=uniqid();
|
||||
// $file=OC::$SERVERROOT.'/3rdparty/MDB2.php';
|
||||
// $source=file_get_contents($file); //nice large text file
|
||||
// $encrypted=OC_Encryption\Crypt::encrypt($source,$key);
|
||||
// $decrypted=OC_Encryption\Crypt::decrypt($encrypted,$key);
|
||||
// $decrypted=rtrim($decrypted, "\0");
|
||||
// $this->assertNotEquals($encrypted,$source);
|
||||
// $this->assertEqual($decrypted,$source);
|
||||
//
|
||||
// $chunk=substr($source,0,8192);
|
||||
// $encrypted=OC_Encryption\Crypt::encrypt($chunk,$key);
|
||||
// $this->assertEqual(strlen($chunk),strlen($encrypted));
|
||||
// $decrypted=OC_Encryption\Crypt::decrypt($encrypted,$key);
|
||||
// $decrypted=rtrim($decrypted, "\0");
|
||||
// $this->assertEqual($decrypted,$chunk);
|
||||
//
|
||||
// $encrypted=OC_Encryption\Crypt::blockEncrypt($source,$key);
|
||||
// $decrypted=OC_Encryption\Crypt::blockDecrypt($encrypted,$key);
|
||||
// $this->assertNotEquals($encrypted,$source);
|
||||
// $this->assertEqual($decrypted,$source);
|
||||
//
|
||||
// $tmpFileEncrypted=OCP\Files::tmpFile();
|
||||
// OC_Encryption\Crypt::encryptfile($file,$tmpFileEncrypted,$key);
|
||||
// $encrypted=file_get_contents($tmpFileEncrypted);
|
||||
// $decrypted=OC_Encryption\Crypt::blockDecrypt($encrypted,$key);
|
||||
// $this->assertNotEquals($encrypted,$source);
|
||||
// $this->assertEqual($decrypted,$source);
|
||||
//
|
||||
// $tmpFileDecrypted=OCP\Files::tmpFile();
|
||||
// OC_Encryption\Crypt::decryptfile($tmpFileEncrypted,$tmpFileDecrypted,$key);
|
||||
// $decrypted=file_get_contents($tmpFileDecrypted);
|
||||
// $this->assertEqual($decrypted,$source);
|
||||
//
|
||||
// $file=OC::$SERVERROOT.'/core/img/weather-clear.png';
|
||||
// $source=file_get_contents($file); //binary file
|
||||
// $encrypted=OC_Encryption\Crypt::encrypt($source,$key);
|
||||
// $decrypted=OC_Encryption\Crypt::decrypt($encrypted,$key);
|
||||
// $decrypted=rtrim($decrypted, "\0");
|
||||
// $this->assertEqual($decrypted,$source);
|
||||
//
|
||||
// $encrypted=OC_Encryption\Crypt::blockEncrypt($source,$key);
|
||||
// $decrypted=OC_Encryption\Crypt::blockDecrypt($encrypted,$key);
|
||||
// $this->assertEqual($decrypted,$source);
|
||||
//
|
||||
// }
|
||||
//
|
||||
// function testBinary(){
|
||||
// $key=uniqid();
|
||||
//
|
||||
// $file=__DIR__.'/binary';
|
||||
// $source=file_get_contents($file); //binary file
|
||||
// $encrypted=OC_Encryption\Crypt::encrypt($source,$key);
|
||||
// $decrypted=OC_Encryption\Crypt::decrypt($encrypted,$key);
|
||||
//
|
||||
// $decrypted=rtrim($decrypted, "\0");
|
||||
// $this->assertEqual($decrypted,$source);
|
||||
//
|
||||
// $encrypted=OC_Encryption\Crypt::blockEncrypt($source,$key);
|
||||
// $decrypted=OC_Encryption\Crypt::blockDecrypt($encrypted,$key,strlen($source));
|
||||
// $this->assertEqual($decrypted,$source);
|
||||
// }
|
||||
|
||||
}
|
||||
@ -0,0 +1,132 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (c) 2012 Sam Tuke <samtuke@owncloud.com>
|
||||
* This file is licensed under the Affero General Public License version 3 or
|
||||
* later.
|
||||
* See the COPYING-README file.
|
||||
*/
|
||||
|
||||
//require_once "PHPUnit/Framework/TestCase.php";
|
||||
require_once realpath( dirname(__FILE__).'/../../../lib/base.php' );
|
||||
require_once realpath( dirname(__FILE__).'/../lib/crypt.php' );
|
||||
require_once realpath( dirname(__FILE__).'/../lib/keymanager.php' );
|
||||
require_once realpath( dirname(__FILE__).'/../lib/proxy.php' );
|
||||
require_once realpath( dirname(__FILE__).'/../lib/stream.php' );
|
||||
require_once realpath( dirname(__FILE__).'/../lib/util.php' );
|
||||
require_once realpath( dirname(__FILE__).'/../appinfo/app.php' );
|
||||
|
||||
use OCA\Encryption;
|
||||
|
||||
// This has to go here because otherwise session errors arise, and the private
|
||||
// encryption key needs to be saved in the session
|
||||
\OC_User::login( 'admin', 'admin' );
|
||||
|
||||
class Test_Keymanager extends \PHPUnit_Framework_TestCase {
|
||||
|
||||
function setUp() {
|
||||
|
||||
\OC_FileProxy::$enabled = false;
|
||||
|
||||
// set content for encrypting / decrypting in tests
|
||||
$this->dataLong = file_get_contents( realpath( dirname(__FILE__).'/../lib/crypt.php' ) );
|
||||
$this->dataShort = 'hats';
|
||||
$this->dataUrl = realpath( dirname(__FILE__).'/../lib/crypt.php' );
|
||||
$this->legacyData = realpath( dirname(__FILE__).'/legacy-text.txt' );
|
||||
$this->legacyEncryptedData = realpath( dirname(__FILE__).'/legacy-encrypted-text.txt' );
|
||||
$this->randomKey = Encryption\Crypt::generateKey();
|
||||
|
||||
$keypair = Encryption\Crypt::createKeypair();
|
||||
$this->genPublicKey = $keypair['publicKey'];
|
||||
$this->genPrivateKey = $keypair['privateKey'];
|
||||
|
||||
$this->view = new \OC_FilesystemView( '/' );
|
||||
|
||||
\OC_User::setUserId( 'admin' );
|
||||
$this->userId = 'admin';
|
||||
$this->pass = 'admin';
|
||||
|
||||
\OC_Filesystem::init( '/' );
|
||||
\OC_Filesystem::mount( 'OC_Filestorage_Local', array('datadir' => \OC_User::getHome($this->userId)), '/' );
|
||||
|
||||
}
|
||||
|
||||
function tearDown(){
|
||||
|
||||
\OC_FileProxy::$enabled = true;
|
||||
|
||||
}
|
||||
|
||||
function testGetPrivateKey() {
|
||||
|
||||
$key = Encryption\Keymanager::getPrivateKey( $this->view, $this->userId );
|
||||
|
||||
// Will this length vary? Perhaps we should use a range instead
|
||||
$this->assertEquals( 2296, strlen( $key ) );
|
||||
|
||||
}
|
||||
|
||||
function testGetPublicKey() {
|
||||
|
||||
$key = Encryption\Keymanager::getPublicKey( $this->view, $this->userId );
|
||||
|
||||
$this->assertEquals( 451, strlen( $key ) );
|
||||
|
||||
$this->assertEquals( '-----BEGIN PUBLIC KEY-----', substr( $key, 0, 26 ) );
|
||||
}
|
||||
|
||||
function testSetFileKey() {
|
||||
|
||||
# NOTE: This cannot be tested until we are able to break out
|
||||
# of the FileSystemView data directory root
|
||||
|
||||
// $key = Crypt::symmetricEncryptFileContentKeyfile( $this->data, 'hat' );
|
||||
//
|
||||
// $tmpPath = sys_get_temp_dir(). '/' . 'testSetFileKey';
|
||||
//
|
||||
// $view = new \OC_FilesystemView( '/tmp/' );
|
||||
//
|
||||
// //$view = new \OC_FilesystemView( '/' . $this->userId . '/files_encryption/keyfiles' );
|
||||
//
|
||||
// Encryption\Keymanager::setFileKey( $tmpPath, $key['key'], $view );
|
||||
|
||||
}
|
||||
|
||||
// /**
|
||||
// * @depends testGetPrivateKey
|
||||
// */
|
||||
// function testGetPrivateKey_decrypt() {
|
||||
//
|
||||
// $key = Encryption\Keymanager::getPrivateKey( $this->view, $this->userId );
|
||||
//
|
||||
// # TODO: replace call to Crypt with a mock object?
|
||||
// $decrypted = Encryption\Crypt::symmetricDecryptFileContent( $key, $this->passphrase );
|
||||
//
|
||||
// $this->assertEquals( 1704, strlen( $decrypted ) );
|
||||
//
|
||||
// $this->assertEquals( '-----BEGIN PRIVATE KEY-----', substr( $decrypted, 0, 27 ) );
|
||||
//
|
||||
// }
|
||||
|
||||
function testGetUserKeys() {
|
||||
|
||||
$keys = Encryption\Keymanager::getUserKeys( $this->view, $this->userId );
|
||||
|
||||
$this->assertEquals( 451, strlen( $keys['publicKey'] ) );
|
||||
$this->assertEquals( '-----BEGIN PUBLIC KEY-----', substr( $keys['publicKey'], 0, 26 ) );
|
||||
$this->assertEquals( 2296, strlen( $keys['privateKey'] ) );
|
||||
|
||||
}
|
||||
|
||||
function testGetPublicKeys() {
|
||||
|
||||
# TODO: write me
|
||||
|
||||
}
|
||||
|
||||
function testGetFileKey() {
|
||||
|
||||
// Encryption\Keymanager::getFileKey( $this->view, $this->userId, $this->filePath );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Binary file not shown.
@ -0,0 +1,220 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (c) 2012 Sam Tuke <samtuke@owncloud.com>,
|
||||
* and Robin Appelman <icewind@owncloud.com>
|
||||
* This file is licensed under the Affero General Public License version 3 or
|
||||
* later.
|
||||
* See the COPYING-README file.
|
||||
*/
|
||||
|
||||
// require_once "PHPUnit/Framework/TestCase.php";
|
||||
// require_once realpath( dirname(__FILE__).'/../../../lib/base.php' );
|
||||
// require_once realpath( dirname(__FILE__).'/../../../3rdparty/mockery/Mockery.php' );
|
||||
// require_once realpath( dirname(__FILE__).'/../../../3rdparty/mockery/Mockery/Generator.php' );
|
||||
// require_once realpath( dirname(__FILE__).'/../../../3rdparty/mockery/Mockery/MockInterface.php' );
|
||||
// require_once realpath( dirname(__FILE__).'/../../../3rdparty/mockery/Mockery/Mock.php' );
|
||||
// require_once realpath( dirname(__FILE__).'/../../../3rdparty/mockery/Mockery/Container.php' );
|
||||
// require_once realpath( dirname(__FILE__).'/../../../3rdparty/mockery/Mockery/Configuration.php' );
|
||||
// require_once realpath( dirname(__FILE__).'/../../../3rdparty/mockery/Mockery/CompositeExpectation.php' );
|
||||
// require_once realpath( dirname(__FILE__).'/../../../3rdparty/mockery/Mockery/ExpectationDirector.php' );
|
||||
// require_once realpath( dirname(__FILE__).'/../../../3rdparty/mockery/Mockery/Expectation.php' );
|
||||
// require_once realpath( dirname(__FILE__).'/../../../3rdparty/mockery/Mockery/Exception.php' );
|
||||
// require_once realpath( dirname(__FILE__).'/../../../3rdparty/mockery/Mockery/CountValidator/CountValidatorAbstract.php' );
|
||||
// require_once realpath( dirname(__FILE__).'/../../../3rdparty/mockery/Mockery/CountValidator/Exception.php' );
|
||||
// require_once realpath( dirname(__FILE__).'/../../../3rdparty/mockery/Mockery/CountValidator/Exact.php' );
|
||||
//
|
||||
// use \Mockery as m;
|
||||
// use OCA\Encryption;
|
||||
|
||||
// class Test_Util extends \PHPUnit_Framework_TestCase {
|
||||
//
|
||||
// public function setUp() {
|
||||
//
|
||||
// $this->proxy = new Encryption\Proxy();
|
||||
//
|
||||
// $this->tmpFileName = "tmpFile-".time();
|
||||
//
|
||||
// $this->privateKey = file_get_contents( realpath( dirname(__FILE__).'/data/admin.public.key' ) );
|
||||
// $this->publicKey = file_get_contents( realpath( dirname(__FILE__).'/data/admin.private.key' ) );
|
||||
// $this->encDataShort = file_get_contents( realpath( dirname(__FILE__).'/data/yoga-manchester-enc' ) );
|
||||
// $this->encDataShortKey = file_get_contents( realpath( dirname(__FILE__).'/data/yoga-manchester.key' ) );
|
||||
//
|
||||
// $this->dataShort = file_get_contents( realpath( dirname(__FILE__).'/data/yoga-manchester' ) );
|
||||
// $this->dataLong = file_get_contents( realpath( dirname(__FILE__).'/../lib/crypt.php' ) );
|
||||
// $this->longDataPath = realpath( dirname(__FILE__).'/../lib/crypt.php' );
|
||||
//
|
||||
// $this->data1 = file_get_contents( realpath( dirname(__FILE__).'/../../../data/admin/files/enc-test.txt' ) );
|
||||
//
|
||||
// \OC_FileProxy::$enabled = false;
|
||||
// $this->Encdata1 = file_get_contents( realpath( dirname(__FILE__).'/../../../data/admin/files/enc-test.txt' ) );
|
||||
// \OC_FileProxy::$enabled = true;
|
||||
//
|
||||
// $this->userId = 'admin';
|
||||
// $this->pass = 'admin';
|
||||
//
|
||||
// $this->session = new Encryption\Session();
|
||||
//
|
||||
// $this->session->setPrivateKey(
|
||||
// '-----BEGIN PRIVATE KEY-----
|
||||
// MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDiH3EA4EpFA7Fx
|
||||
// s2dyyfL5jwXeYXrTqQJ6DqKgGn8VsbT3eu8R9KzM2XitVwZe8c8L52DvJ06o5vg0
|
||||
// GqPYxilFdOFJe/ggac5Tq8UmJiZS4EqYEMwxBIfIyWTxeGV06/0HOwnVAkqHMcBz
|
||||
// 64qldtgi5O8kZMEM2/gKBgU0kMLJzM+8oEWhL1+gsUWQhxd8cKLXypS6iWgqFJrz
|
||||
// f/X0hJsJR+gyYxNpahtnjzd/LxLAETrOMsl2tue+BAxmjbAM0aG0NEM0div+b59s
|
||||
// 2uz/iWbxImp5pOdYVKcVW89D4XBMyGegR40trV2VwiuX1blKCfdjMsJhiaL9pymp
|
||||
// ug1wzyQFAgMBAAECggEAK6c+PZkPPXuVCgpEcliiW6NM0r2m5K3AGKgypQ34csu3
|
||||
// z/8foCvIIFPrhCtEw5eTDQ1CHWlNOjY8vHJYJ0U6Onpx86nHIRrMBkMm8FJ1G5LJ
|
||||
// U8oKYXwqaozWu/cuPwA//OFc6I5krOzh5n8WaRMkbrgbor8AtebRX74By0AXGrXe
|
||||
// cswJI7zR96oFn4Dm7Pgvpg5Zhk1vFJ+w6QtH+4DDJ6PBvlZsRkGxYBLGVd/3qhAI
|
||||
// sBAyjFlSzuP4eCRhHOhHC/e4gmAH9evFVXB88jFyRZm3K+jQ5W5CwrVRBCV2lph6
|
||||
// 2B6P7CBJN+IjGKMhy+75y13UvvKPv9IwH8Fzl2x1gQKBgQD8qQOr7a6KhSj16wQE
|
||||
// jim2xqt9gQ2jH5No405NrKs/PFQQZnzD4YseQsiK//NUjOJiUhaT+L5jhIpzINHt
|
||||
// RJpt3bGkEZmLyjdjgTpB3GwZdXa28DNK9VdXZ19qIl/ZH0qAjKmJCRahUDASMnVi
|
||||
// M4Pkk9yx9ZIKkri4TcuMWqc0DQKBgQDlHKBTITZq/arYPD6Nl3NsoOdqVRqJrGay
|
||||
// 0TjXAVbBXe46+z5lnMsqwXb79nx14hdmSEsZULrw/3f+MnQbdjMTYLFP24visZg9
|
||||
// MN8vAiALiiiR1a+Crz+DTA1Q8sGOMVCMqMDmD7QBys3ZuWxuapm0txAiIYUtsjJZ
|
||||
// XN76T4nZ2QKBgQCHaT3igzwsWTmesxowJtEMeGWomeXpKx8h89EfqA8PkRGsyIDN
|
||||
// qq+YxEoe1RZgljEuaLhZDdNcGsjo8woPk9kAUPTH7fbRCMuutK+4ZJ469s1tNkcH
|
||||
// QX5SBcEJbOrZvv967ehe3VQXmJZq6kgnHVzuwKBjcC2ZJRGDFY6l5l/+cQKBgCqh
|
||||
// +Adf/8NK7paMJ0urqfPFwSodKfICXZ3apswDWMRkmSbqh4La+Uc8dsqN5Dz/VEFZ
|
||||
// JHhSeGbN8uMfOlG93eU2MehdPxtw1pZUWMNjjtj23XO9ooob2CKzbSrp8TBnZsi1
|
||||
// widNNr66oTFpeo7VUUK6acsgF6sYJJxSVr+XO1yJAoGAEhvitq8shNKcEY0xCipS
|
||||
// k1kbgyS7KKB7opVxI5+ChEqyUDijS3Y9FZixrRIWE6i2uGu86UG+v2lbKvSbM4Qm
|
||||
// xvbOcX9OVMnlRb7n8woOP10UMY+ZE2x+YEUXQTLtPYq7F66e1OfxltstMxLQA+3d
|
||||
// Y1d5piFV8PXK3Fg2F+Cj5qg=
|
||||
// -----END PRIVATE KEY-----
|
||||
// '
|
||||
// , $this->userId
|
||||
// );
|
||||
//
|
||||
// \OC_User::setUserId( $this->userId );
|
||||
//
|
||||
// }
|
||||
//
|
||||
// public function testpreFile_get_contents() {
|
||||
//
|
||||
// // This won't work for now because mocking of the static keymanager class isn't working :(
|
||||
//
|
||||
// // $mock = m::mock( 'alias:OCA\Encryption\Keymanager' );
|
||||
// //
|
||||
// // $mock->shouldReceive( 'getFileKey' )->times(2)->andReturn( $this->encDataShort );
|
||||
// //
|
||||
// // $encrypted = $this->proxy->postFile_get_contents( 'data/'.$this->tmpFileName, $this->encDataShortKey );
|
||||
// //
|
||||
// // $this->assertNotEquals( $this->dataShort, $encrypted );
|
||||
//
|
||||
// $decrypted = $this->proxy->postFile_get_contents( 'data/admin/files/enc-test.txt', $this->data1 );
|
||||
//
|
||||
// }
|
||||
//
|
||||
// }
|
||||
|
||||
// class Test_CryptProxy extends UnitTestCase {
|
||||
// private $oldConfig;
|
||||
// private $oldKey;
|
||||
//
|
||||
// public function setUp(){
|
||||
// $user=OC_User::getUser();
|
||||
//
|
||||
// $this->oldConfig=OCP\Config::getAppValue('files_encryption','enable_encryption','true');
|
||||
// OCP\Config::setAppValue('files_encryption','enable_encryption','true');
|
||||
// $this->oldKey=isset($_SESSION['privateKey'])?$_SESSION['privateKey']:null;
|
||||
//
|
||||
//
|
||||
// //set testing key
|
||||
// $_SESSION['privateKey']=md5(time());
|
||||
//
|
||||
// //clear all proxies and hooks so we can do clean testing
|
||||
// OC_FileProxy::clearProxies();
|
||||
// OC_Hook::clear('OC_Filesystem');
|
||||
//
|
||||
// //enable only the encryption hook
|
||||
// OC_FileProxy::register(new OC_FileProxy_Encryption());
|
||||
//
|
||||
// //set up temporary storage
|
||||
// OC_Filesystem::clearMounts();
|
||||
// OC_Filesystem::mount('OC_Filestorage_Temporary',array(),'/');
|
||||
//
|
||||
// OC_Filesystem::init('/'.$user.'/files');
|
||||
//
|
||||
// //set up the users home folder in the temp storage
|
||||
// $rootView=new OC_FilesystemView('');
|
||||
// $rootView->mkdir('/'.$user);
|
||||
// $rootView->mkdir('/'.$user.'/files');
|
||||
// }
|
||||
//
|
||||
// public function tearDown(){
|
||||
// OCP\Config::setAppValue('files_encryption','enable_encryption',$this->oldConfig);
|
||||
// if(!is_null($this->oldKey)){
|
||||
// $_SESSION['privateKey']=$this->oldKey;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public function testSimple(){
|
||||
// $file=OC::$SERVERROOT.'/3rdparty/MDB2.php';
|
||||
// $original=file_get_contents($file);
|
||||
//
|
||||
// OC_Filesystem::file_put_contents('/file',$original);
|
||||
//
|
||||
// OC_FileProxy::$enabled=false;
|
||||
// $stored=OC_Filesystem::file_get_contents('/file');
|
||||
// OC_FileProxy::$enabled=true;
|
||||
//
|
||||
// $fromFile=OC_Filesystem::file_get_contents('/file');
|
||||
// $this->assertNotEqual($original,$stored);
|
||||
// $this->assertEqual(strlen($original),strlen($fromFile));
|
||||
// $this->assertEqual($original,$fromFile);
|
||||
//
|
||||
// }
|
||||
//
|
||||
// public function testView(){
|
||||
// $file=OC::$SERVERROOT.'/3rdparty/MDB2.php';
|
||||
// $original=file_get_contents($file);
|
||||
//
|
||||
// $rootView=new OC_FilesystemView('');
|
||||
// $view=new OC_FilesystemView('/'.OC_User::getUser());
|
||||
// $userDir='/'.OC_User::getUser().'/files';
|
||||
//
|
||||
// $rootView->file_put_contents($userDir.'/file',$original);
|
||||
//
|
||||
// OC_FileProxy::$enabled=false;
|
||||
// $stored=$rootView->file_get_contents($userDir.'/file');
|
||||
// OC_FileProxy::$enabled=true;
|
||||
//
|
||||
// $this->assertNotEqual($original,$stored);
|
||||
// $fromFile=$rootView->file_get_contents($userDir.'/file');
|
||||
// $this->assertEqual($original,$fromFile);
|
||||
//
|
||||
// $fromFile=$view->file_get_contents('files/file');
|
||||
// $this->assertEqual($original,$fromFile);
|
||||
// }
|
||||
//
|
||||
// public function testBinary(){
|
||||
// $file=__DIR__.'/binary';
|
||||
// $original=file_get_contents($file);
|
||||
//
|
||||
// OC_Filesystem::file_put_contents('/file',$original);
|
||||
//
|
||||
// OC_FileProxy::$enabled=false;
|
||||
// $stored=OC_Filesystem::file_get_contents('/file');
|
||||
// OC_FileProxy::$enabled=true;
|
||||
//
|
||||
// $fromFile=OC_Filesystem::file_get_contents('/file');
|
||||
// $this->assertNotEqual($original,$stored);
|
||||
// $this->assertEqual(strlen($original),strlen($fromFile));
|
||||
// $this->assertEqual($original,$fromFile);
|
||||
//
|
||||
// $file=__DIR__.'/zeros';
|
||||
// $original=file_get_contents($file);
|
||||
//
|
||||
// OC_Filesystem::file_put_contents('/file',$original);
|
||||
//
|
||||
// OC_FileProxy::$enabled=false;
|
||||
// $stored=OC_Filesystem::file_get_contents('/file');
|
||||
// OC_FileProxy::$enabled=true;
|
||||
//
|
||||
// $fromFile=OC_Filesystem::file_get_contents('/file');
|
||||
// $this->assertNotEqual($original,$stored);
|
||||
// $this->assertEqual(strlen($original),strlen($fromFile));
|
||||
// }
|
||||
// }
|
||||
@ -0,0 +1,226 @@
|
||||
// <?php
|
||||
// /**
|
||||
// * Copyright (c) 2012 Robin Appelman <icewind@owncloud.com>
|
||||
// * This file is licensed under the Affero General Public License version 3 or
|
||||
// * later.
|
||||
// * See the COPYING-README file.
|
||||
// */
|
||||
//
|
||||
// namespace OCA\Encryption;
|
||||
//
|
||||
// class Test_Stream extends \PHPUnit_Framework_TestCase {
|
||||
//
|
||||
// function setUp() {
|
||||
//
|
||||
// \OC_Filesystem::mount( 'OC_Filestorage_Local', array(), '/' );
|
||||
//
|
||||
// $this->empty = '';
|
||||
//
|
||||
// $this->stream = new Stream();
|
||||
//
|
||||
// $this->dataLong = file_get_contents( realpath( dirname(__FILE__).'/../lib/crypt.php' ) );
|
||||
// $this->dataShort = 'hats';
|
||||
//
|
||||
// $this->emptyTmpFilePath = \OCP\Files::tmpFile();
|
||||
//
|
||||
// $this->dataTmpFilePath = \OCP\Files::tmpFile();
|
||||
//
|
||||
// file_put_contents( $this->dataTmpFilePath, "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a diam lectus. Sed sit amet ipsum mauris. Maecenas congue ligula ac quam viverra nec consectetur ante hendrerit. Donec et mollis dolor. Praesent et diam eget libero egestas mattis sit amet vitae augue. Nam tincidunt congue enim, ut porta lorem lacinia consectetur. Donec ut libero sed arcu vehicula ultricies a non tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean ut gravida lorem. Ut turpis felis, pulvinar a semper sed, adipiscing id dolor. Pellentesque auctor nisi id magna consequat sagittis. Curabitur dapibus enim sit amet elit pharetra tincidunt feugiat nisl imperdiet. Ut convallis libero in urna ultrices accumsan. Donec sed odio eros. Donec viverra mi quis quam pulvinar at malesuada arcu rhoncus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. In rutrum accumsan ultricies. Mauris vitae nisi at sem facilisis semper ac in est." );
|
||||
//
|
||||
// }
|
||||
//
|
||||
// function testStreamOpen() {
|
||||
//
|
||||
// $stream1 = new Stream();
|
||||
//
|
||||
// $handle1 = $stream1->stream_open( $this->emptyTmpFilePath, 'wb', array(), $this->empty );
|
||||
//
|
||||
// // Test that resource was returned successfully
|
||||
// $this->assertTrue( $handle1 );
|
||||
//
|
||||
// // Test that file has correct size
|
||||
// $this->assertEquals( 0, $stream1->size );
|
||||
//
|
||||
// // Test that path is correct
|
||||
// $this->assertEquals( $this->emptyTmpFilePath, $stream1->rawPath );
|
||||
//
|
||||
// $stream2 = new Stream();
|
||||
//
|
||||
// $handle2 = $stream2->stream_open( 'crypt://' . $this->emptyTmpFilePath, 'wb', array(), $this->empty );
|
||||
//
|
||||
// // Test that protocol identifier is removed from path
|
||||
// $this->assertEquals( $this->emptyTmpFilePath, $stream2->rawPath );
|
||||
//
|
||||
// // "Stat failed error" prevents this test from executing
|
||||
// // $stream3 = new Stream();
|
||||
// //
|
||||
// // $handle3 = $stream3->stream_open( $this->dataTmpFilePath, 'r', array(), $this->empty );
|
||||
// //
|
||||
// // $this->assertEquals( 0, $stream3->size );
|
||||
//
|
||||
// }
|
||||
//
|
||||
// function testStreamWrite() {
|
||||
//
|
||||
// $stream1 = new Stream();
|
||||
//
|
||||
// $handle1 = $stream1->stream_open( $this->emptyTmpFilePath, 'r+b', array(), $this->empty );
|
||||
//
|
||||
// # what about the keymanager? there is no key for the newly created temporary file!
|
||||
//
|
||||
// $stream1->stream_write( $this->dataShort );
|
||||
//
|
||||
// }
|
||||
//
|
||||
// // function getStream( $id, $mode, $size ) {
|
||||
// //
|
||||
// // if ( $id === '' ) {
|
||||
// //
|
||||
// // $id = uniqid();
|
||||
// // }
|
||||
// //
|
||||
// //
|
||||
// // if ( !isset( $this->tmpFiles[$id] ) ) {
|
||||
// //
|
||||
// // // If tempfile with given name does not already exist, create it
|
||||
// //
|
||||
// // $file = OCP\Files::tmpFile();
|
||||
// //
|
||||
// // $this->tmpFiles[$id] = $file;
|
||||
// //
|
||||
// // } else {
|
||||
// //
|
||||
// // $file = $this->tmpFiles[$id];
|
||||
// //
|
||||
// // }
|
||||
// //
|
||||
// // $stream = fopen( $file, $mode );
|
||||
// //
|
||||
// // Stream::$sourceStreams[$id] = array( 'path' => 'dummy' . $id, 'stream' => $stream, 'size' => $size );
|
||||
// //
|
||||
// // return fopen( 'crypt://streams/'.$id, $mode );
|
||||
// //
|
||||
// // }
|
||||
// //
|
||||
// // function testStream( ){
|
||||
// //
|
||||
// // $stream = $this->getStream( 'test1', 'w', strlen( 'foobar' ) );
|
||||
// //
|
||||
// // fwrite( $stream, 'foobar' );
|
||||
// //
|
||||
// // fclose( $stream );
|
||||
// //
|
||||
// //
|
||||
// // $stream = $this->getStream( 'test1', 'r', strlen( 'foobar' ) );
|
||||
// //
|
||||
// // $data = fread( $stream, 6 );
|
||||
// //
|
||||
// // fclose( $stream );
|
||||
// //
|
||||
// // $this->assertEqual( 'foobar', $data );
|
||||
// //
|
||||
// //
|
||||
// // $file = OC::$SERVERROOT.'/3rdparty/MDB2.php';
|
||||
// //
|
||||
// // $source = fopen( $file, 'r' );
|
||||
// //
|
||||
// // $target = $this->getStream( 'test2', 'w', 0 );
|
||||
// //
|
||||
// // OCP\Files::streamCopy( $source, $target );
|
||||
// //
|
||||
// // fclose( $target );
|
||||
// //
|
||||
// // fclose( $source );
|
||||
// //
|
||||
// //
|
||||
// // $stream = $this->getStream( 'test2', 'r', filesize( $file ) );
|
||||
// //
|
||||
// // $data = stream_get_contents( $stream );
|
||||
// //
|
||||
// // $original = file_get_contents( $file );
|
||||
// //
|
||||
// // $this->assertEqual( strlen( $original ), strlen( $data ) );
|
||||
// //
|
||||
// // $this->assertEqual( $original, $data );
|
||||
// //
|
||||
// // }
|
||||
//
|
||||
// }
|
||||
//
|
||||
// // class Test_CryptStream extends UnitTestCase {
|
||||
// // private $tmpFiles=array();
|
||||
// //
|
||||
// // function testStream(){
|
||||
// // $stream=$this->getStream('test1','w',strlen('foobar'));
|
||||
// // fwrite($stream,'foobar');
|
||||
// // fclose($stream);
|
||||
// //
|
||||
// // $stream=$this->getStream('test1','r',strlen('foobar'));
|
||||
// // $data=fread($stream,6);
|
||||
// // fclose($stream);
|
||||
// // $this->assertEqual('foobar',$data);
|
||||
// //
|
||||
// // $file=OC::$SERVERROOT.'/3rdparty/MDB2.php';
|
||||
// // $source=fopen($file,'r');
|
||||
// // $target=$this->getStream('test2','w',0);
|
||||
// // OCP\Files::streamCopy($source,$target);
|
||||
// // fclose($target);
|
||||
// // fclose($source);
|
||||
// //
|
||||
// // $stream=$this->getStream('test2','r',filesize($file));
|
||||
// // $data=stream_get_contents($stream);
|
||||
// // $original=file_get_contents($file);
|
||||
// // $this->assertEqual(strlen($original),strlen($data));
|
||||
// // $this->assertEqual($original,$data);
|
||||
// // }
|
||||
// //
|
||||
// // /**
|
||||
// // * get a cryptstream to a temporary file
|
||||
// // * @param string $id
|
||||
// // * @param string $mode
|
||||
// // * @param int size
|
||||
// // * @return resource
|
||||
// // */
|
||||
// // function getStream($id,$mode,$size){
|
||||
// // if($id===''){
|
||||
// // $id=uniqid();
|
||||
// // }
|
||||
// // if(!isset($this->tmpFiles[$id])){
|
||||
// // $file=OCP\Files::tmpFile();
|
||||
// // $this->tmpFiles[$id]=$file;
|
||||
// // }else{
|
||||
// // $file=$this->tmpFiles[$id];
|
||||
// // }
|
||||
// // $stream=fopen($file,$mode);
|
||||
// // OC_CryptStream::$sourceStreams[$id]=array('path'=>'dummy'.$id,'stream'=>$stream,'size'=>$size);
|
||||
// // return fopen('crypt://streams/'.$id,$mode);
|
||||
// // }
|
||||
// //
|
||||
// // function testBinary(){
|
||||
// // $file=__DIR__.'/binary';
|
||||
// // $source=file_get_contents($file);
|
||||
// //
|
||||
// // $stream=$this->getStream('test','w',strlen($source));
|
||||
// // fwrite($stream,$source);
|
||||
// // fclose($stream);
|
||||
// //
|
||||
// // $stream=$this->getStream('test','r',strlen($source));
|
||||
// // $data=stream_get_contents($stream);
|
||||
// // fclose($stream);
|
||||
// // $this->assertEqual(strlen($data),strlen($source));
|
||||
// // $this->assertEqual($source,$data);
|
||||
// //
|
||||
// // $file=__DIR__.'/zeros';
|
||||
// // $source=file_get_contents($file);
|
||||
// //
|
||||
// // $stream=$this->getStream('test2','w',strlen($source));
|
||||
// // fwrite($stream,$source);
|
||||
// // fclose($stream);
|
||||
// //
|
||||
// // $stream=$this->getStream('test2','r',strlen($source));
|
||||
// // $data=stream_get_contents($stream);
|
||||
// // fclose($stream);
|
||||
// // $this->assertEqual(strlen($data),strlen($source));
|
||||
// // $this->assertEqual($source,$data);
|
||||
// // }
|
||||
// // }
|
||||
@ -0,0 +1,210 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (c) 2012 Sam Tuke <samtuke@owncloud.com>
|
||||
* This file is licensed under the Affero General Public License version 3 or
|
||||
* later.
|
||||
* See the COPYING-README file.
|
||||
*/
|
||||
|
||||
//require_once "PHPUnit/Framework/TestCase.php";
|
||||
require_once realpath( dirname(__FILE__).'/../../../lib/base.php' );
|
||||
require_once realpath( dirname(__FILE__).'/../lib/crypt.php' );
|
||||
require_once realpath( dirname(__FILE__).'/../lib/keymanager.php' );
|
||||
require_once realpath( dirname(__FILE__).'/../lib/proxy.php' );
|
||||
require_once realpath( dirname(__FILE__).'/../lib/stream.php' );
|
||||
require_once realpath( dirname(__FILE__).'/../lib/util.php' );
|
||||
require_once realpath( dirname(__FILE__).'/../appinfo/app.php' );
|
||||
|
||||
// Load mockery files
|
||||
require_once 'Mockery/Loader.php';
|
||||
require_once 'Hamcrest/Hamcrest.php';
|
||||
$loader = new \Mockery\Loader;
|
||||
$loader->register();
|
||||
|
||||
use \Mockery as m;
|
||||
use OCA\Encryption;
|
||||
|
||||
class Test_Enc_Util extends \PHPUnit_Framework_TestCase {
|
||||
|
||||
function setUp() {
|
||||
|
||||
\OC_Filesystem::mount( 'OC_Filestorage_Local', array(), '/' );
|
||||
|
||||
// set content for encrypting / decrypting in tests
|
||||
$this->dataUrl = realpath( dirname(__FILE__).'/../lib/crypt.php' );
|
||||
$this->dataShort = 'hats';
|
||||
$this->dataLong = file_get_contents( realpath( dirname(__FILE__).'/../lib/crypt.php' ) );
|
||||
$this->legacyData = realpath( dirname(__FILE__).'/legacy-text.txt' );
|
||||
$this->legacyEncryptedData = realpath( dirname(__FILE__).'/legacy-encrypted-text.txt' );
|
||||
|
||||
$this->userId = 'admin';
|
||||
$this->pass = 'admin';
|
||||
|
||||
$keypair = Encryption\Crypt::createKeypair();
|
||||
|
||||
$this->genPublicKey = $keypair['publicKey'];
|
||||
$this->genPrivateKey = $keypair['privateKey'];
|
||||
|
||||
$this->publicKeyDir = '/' . 'public-keys';
|
||||
$this->encryptionDir = '/' . $this->userId . '/' . 'files_encryption';
|
||||
$this->keyfilesPath = $this->encryptionDir . '/' . 'keyfiles';
|
||||
$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
|
||||
|
||||
$this->view = new OC_FilesystemView( '/admin' );
|
||||
|
||||
$this->mockView = m::mock('OC_FilesystemView');
|
||||
$this->util = new Encryption\Util( $this->mockView, $this->userId );
|
||||
|
||||
}
|
||||
|
||||
function tearDown(){
|
||||
|
||||
m::close();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief test that paths set during User construction are correct
|
||||
*/
|
||||
function testKeyPaths() {
|
||||
|
||||
$mockView = m::mock('OC_FilesystemView');
|
||||
|
||||
$util = new Encryption\Util( $mockView, $this->userId );
|
||||
|
||||
$this->assertEquals( $this->publicKeyDir, $util->getPath( 'publicKeyDir' ) );
|
||||
$this->assertEquals( $this->encryptionDir, $util->getPath( 'encryptionDir' ) );
|
||||
$this->assertEquals( $this->keyfilesPath, $util->getPath( 'keyfilesPath' ) );
|
||||
$this->assertEquals( $this->publicKeyPath, $util->getPath( 'publicKeyPath' ) );
|
||||
$this->assertEquals( $this->privateKeyPath, $util->getPath( 'privateKeyPath' ) );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief test setup of encryption directories when they don't yet exist
|
||||
*/
|
||||
function testSetupServerSideNotSetup() {
|
||||
|
||||
$mockView = m::mock('OC_FilesystemView');
|
||||
|
||||
$mockView->shouldReceive( 'file_exists' )->times(4)->andReturn( false );
|
||||
$mockView->shouldReceive( 'mkdir' )->times(3)->andReturn( true );
|
||||
$mockView->shouldReceive( 'file_put_contents' )->withAnyArgs();
|
||||
|
||||
$util = new Encryption\Util( $mockView, $this->userId );
|
||||
|
||||
$this->assertEquals( true, $util->setupServerSide( $this->pass ) );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief test setup of encryption directories when they already exist
|
||||
*/
|
||||
function testSetupServerSideIsSetup() {
|
||||
|
||||
$mockView = m::mock('OC_FilesystemView');
|
||||
|
||||
$mockView->shouldReceive( 'file_exists' )->times(5)->andReturn( true );
|
||||
$mockView->shouldReceive( 'file_put_contents' )->withAnyArgs();
|
||||
|
||||
$util = new Encryption\Util( $mockView, $this->userId );
|
||||
|
||||
$this->assertEquals( true, $util->setupServerSide( $this->pass ) );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief test checking whether account is ready for encryption, when it isn't ready
|
||||
*/
|
||||
function testReadyNotReady() {
|
||||
|
||||
$mockView = m::mock('OC_FilesystemView');
|
||||
|
||||
$mockView->shouldReceive( 'file_exists' )->times(1)->andReturn( false );
|
||||
|
||||
$util = new Encryption\Util( $mockView, $this->userId );
|
||||
|
||||
$this->assertEquals( false, $util->ready() );
|
||||
|
||||
# TODO: Add more tests here to check that if any of the dirs are
|
||||
# then false will be returned. Use strict ordering?
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief test checking whether account is ready for encryption, when it is ready
|
||||
*/
|
||||
function testReadyIsReady() {
|
||||
|
||||
$mockView = m::mock('OC_FilesystemView');
|
||||
|
||||
$mockView->shouldReceive( 'file_exists' )->times(3)->andReturn( true );
|
||||
|
||||
$util = new Encryption\Util( $mockView, $this->userId );
|
||||
|
||||
$this->assertEquals( true, $util->ready() );
|
||||
|
||||
# TODO: Add more tests here to check that if any of the dirs are
|
||||
# then false will be returned. Use strict ordering?
|
||||
|
||||
}
|
||||
|
||||
// /**
|
||||
// * @brief test decryption using legacy blowfish method
|
||||
// * @depends testLegacyEncryptLong
|
||||
// */
|
||||
// function testLegacyKeyRecryptKeyfileDecrypt( $recrypted ) {
|
||||
//
|
||||
// $decrypted = Encryption\Crypt::keyDecryptKeyfile( $recrypted['data'], $recrypted['key'], $this->genPrivateKey );
|
||||
//
|
||||
// $this->assertEquals( $this->dataLong, $decrypted );
|
||||
//
|
||||
// }
|
||||
|
||||
// // Cannot use this test for now due to hidden dependencies in OC_FileCache
|
||||
// function testIsLegacyEncryptedContent() {
|
||||
//
|
||||
// $keyfileContent = OCA\Encryption\Crypt::symmetricEncryptFileContent( $this->legacyEncryptedData, 'hat' );
|
||||
//
|
||||
// $this->assertFalse( OCA\Encryption\Crypt::isLegacyEncryptedContent( $keyfileContent, '/files/admin/test.txt' ) );
|
||||
//
|
||||
// OC_FileCache::put( '/admin/files/legacy-encrypted-test.txt', $this->legacyEncryptedData );
|
||||
//
|
||||
// $this->assertTrue( OCA\Encryption\Crypt::isLegacyEncryptedContent( $this->legacyEncryptedData, '/files/admin/test.txt' ) );
|
||||
//
|
||||
// }
|
||||
|
||||
// // Cannot use this test for now due to need for different root in OC_Filesystem_view class
|
||||
// function testGetLegacyKey() {
|
||||
//
|
||||
// $c = new \OCA\Encryption\Util( $view, false );
|
||||
//
|
||||
// $bool = $c->getLegacyKey( 'admin' );
|
||||
//
|
||||
// $this->assertTrue( $bool );
|
||||
//
|
||||
// $this->assertTrue( $c->legacyKey );
|
||||
//
|
||||
// $this->assertTrue( is_int( $c->legacyKey ) );
|
||||
//
|
||||
// $this->assertTrue( strlen( $c->legacyKey ) == 20 );
|
||||
//
|
||||
// }
|
||||
|
||||
// // Cannot use this test for now due to need for different root in OC_Filesystem_view class
|
||||
// function testLegacyDecrypt() {
|
||||
//
|
||||
// $c = new OCA\Encryption\Util( $this->view, false );
|
||||
//
|
||||
// $bool = $c->getLegacyKey( 'admin' );
|
||||
//
|
||||
// $encrypted = $c->legacyEncrypt( $this->data, $c->legacyKey );
|
||||
//
|
||||
// $decrypted = $c->legacyDecrypt( $encrypted, $c->legacyKey );
|
||||
//
|
||||
// $this->assertEqual( $decrypted, $this->data );
|
||||
//
|
||||
// }
|
||||
|
||||
}
|
||||
@ -1,72 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (c) 2012 Robin Appelman <icewind@owncloud.com>
|
||||
* This file is licensed under the Affero General Public License version 3 or
|
||||
* later.
|
||||
* See the COPYING-README file.
|
||||
*/
|
||||
|
||||
class Test_Encryption extends UnitTestCase {
|
||||
function testEncryption() {
|
||||
$key=uniqid();
|
||||
$file=OC::$SERVERROOT.'/3rdparty/MDB2.php';
|
||||
$source=file_get_contents($file); //nice large text file
|
||||
$encrypted=OC_Crypt::encrypt($source, $key);
|
||||
$decrypted=OC_Crypt::decrypt($encrypted, $key);
|
||||
$decrypted=rtrim($decrypted, "\0");
|
||||
$this->assertNotEqual($encrypted, $source);
|
||||
$this->assertEqual($decrypted, $source);
|
||||
|
||||
$chunk=substr($source, 0, 8192);
|
||||
$encrypted=OC_Crypt::encrypt($chunk, $key);
|
||||
$this->assertEqual(strlen($chunk), strlen($encrypted));
|
||||
$decrypted=OC_Crypt::decrypt($encrypted, $key);
|
||||
$decrypted=rtrim($decrypted, "\0");
|
||||
$this->assertEqual($decrypted, $chunk);
|
||||
|
||||
$encrypted=OC_Crypt::blockEncrypt($source, $key);
|
||||
$decrypted=OC_Crypt::blockDecrypt($encrypted, $key);
|
||||
$this->assertNotEqual($encrypted, $source);
|
||||
$this->assertEqual($decrypted, $source);
|
||||
|
||||
$tmpFileEncrypted=OCP\Files::tmpFile();
|
||||
OC_Crypt::encryptfile($file, $tmpFileEncrypted, $key);
|
||||
$encrypted=file_get_contents($tmpFileEncrypted);
|
||||
$decrypted=OC_Crypt::blockDecrypt($encrypted, $key);
|
||||
$this->assertNotEqual($encrypted, $source);
|
||||
$this->assertEqual($decrypted, $source);
|
||||
|
||||
$tmpFileDecrypted=OCP\Files::tmpFile();
|
||||
OC_Crypt::decryptfile($tmpFileEncrypted, $tmpFileDecrypted, $key);
|
||||
$decrypted=file_get_contents($tmpFileDecrypted);
|
||||
$this->assertEqual($decrypted, $source);
|
||||
|
||||
$file=OC::$SERVERROOT.'/core/img/weather-clear.png';
|
||||
$source=file_get_contents($file); //binary file
|
||||
$encrypted=OC_Crypt::encrypt($source, $key);
|
||||
$decrypted=OC_Crypt::decrypt($encrypted, $key);
|
||||
$decrypted=rtrim($decrypted, "\0");
|
||||
$this->assertEqual($decrypted, $source);
|
||||
|
||||
$encrypted=OC_Crypt::blockEncrypt($source, $key);
|
||||
$decrypted=OC_Crypt::blockDecrypt($encrypted, $key);
|
||||
$this->assertEqual($decrypted, $source);
|
||||
|
||||
}
|
||||
|
||||
function testBinary() {
|
||||
$key=uniqid();
|
||||
|
||||
$file=__DIR__.'/binary';
|
||||
$source=file_get_contents($file); //binary file
|
||||
$encrypted=OC_Crypt::encrypt($source, $key);
|
||||
$decrypted=OC_Crypt::decrypt($encrypted, $key);
|
||||
|
||||
$decrypted=rtrim($decrypted, "\0");
|
||||
$this->assertEqual($decrypted, $source);
|
||||
|
||||
$encrypted=OC_Crypt::blockEncrypt($source, $key);
|
||||
$decrypted=OC_Crypt::blockDecrypt($encrypted, $key, strlen($source));
|
||||
$this->assertEqual($decrypted, $source);
|
||||
}
|
||||
}
|
||||
@ -1,117 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (c) 2012 Robin Appelman <icewind@owncloud.com>
|
||||
* This file is licensed under the Affero General Public License version 3 or
|
||||
* later.
|
||||
* See the COPYING-README file.
|
||||
*/
|
||||
|
||||
class Test_CryptProxy extends UnitTestCase {
|
||||
private $oldConfig;
|
||||
private $oldKey;
|
||||
|
||||
public function setUp() {
|
||||
$user=OC_User::getUser();
|
||||
|
||||
$this->oldConfig=OCP\Config::getAppValue('files_encryption','enable_encryption','true');
|
||||
OCP\Config::setAppValue('files_encryption','enable_encryption','true');
|
||||
$this->oldKey=isset($_SESSION['enckey'])?$_SESSION['enckey']:null;
|
||||
|
||||
|
||||
//set testing key
|
||||
$_SESSION['enckey']=md5(time());
|
||||
|
||||
//clear all proxies and hooks so we can do clean testing
|
||||
OC_FileProxy::clearProxies();
|
||||
OC_Hook::clear('OC_Filesystem');
|
||||
|
||||
//enable only the encryption hook
|
||||
OC_FileProxy::register(new OC_FileProxy_Encryption());
|
||||
|
||||
//set up temporary storage
|
||||
\OC\Files\Filesystem::clearMounts();
|
||||
\OC\Files\Filesystem::mount('\OC\Files\Storage\Temporary' ,array(), '/');
|
||||
|
||||
\OC\Files\Filesystem::init('/'.$user.'/files');
|
||||
|
||||
//set up the users home folder in the temp storage
|
||||
$rootView=new \OC\Files\View('');
|
||||
$rootView->mkdir('/'.$user);
|
||||
$rootView->mkdir('/'.$user.'/files');
|
||||
}
|
||||
|
||||
public function tearDown() {
|
||||
OCP\Config::setAppValue('files_encryption', 'enable_encryption', $this->oldConfig);
|
||||
if ( ! is_null($this->oldKey)) {
|
||||
$_SESSION['enckey']=$this->oldKey;
|
||||
}
|
||||
}
|
||||
|
||||
public function testSimple() {
|
||||
$file=OC::$SERVERROOT.'/3rdparty/MDB2.php';
|
||||
$original=file_get_contents($file);
|
||||
|
||||
\OC\Files\Filesystem::file_put_contents('/file',$original);
|
||||
|
||||
OC_FileProxy::$enabled=false;
|
||||
$stored=\OC\Files\Filesystem::file_get_contents('/file');
|
||||
OC_FileProxy::$enabled=true;
|
||||
|
||||
$fromFile=\OC\Files\Filesystem::file_get_contents('/file');
|
||||
$this->assertNotEqual($original,$stored);
|
||||
$this->assertEqual(strlen($original), strlen($fromFile));
|
||||
$this->assertEqual($original,$fromFile);
|
||||
|
||||
}
|
||||
|
||||
public function testView() {
|
||||
$file=OC::$SERVERROOT.'/3rdparty/MDB2.php';
|
||||
$original=file_get_contents($file);
|
||||
|
||||
$rootView=new \OC\Files\View('');
|
||||
$view=new \OC\Files\View('/'.OC_User::getUser());
|
||||
$userDir='/'.OC_User::getUser().'/files';
|
||||
|
||||
$rootView->file_put_contents($userDir.'/file',$original);
|
||||
|
||||
OC_FileProxy::$enabled=false;
|
||||
$stored=$rootView->file_get_contents($userDir.'/file');
|
||||
OC_FileProxy::$enabled=true;
|
||||
|
||||
$this->assertNotEqual($original,$stored);
|
||||
$fromFile=$rootView->file_get_contents($userDir.'/file');
|
||||
$this->assertEqual($original,$fromFile);
|
||||
|
||||
$fromFile=$view->file_get_contents('files/file');
|
||||
$this->assertEqual($original,$fromFile);
|
||||
}
|
||||
|
||||
public function testBinary() {
|
||||
$file=__DIR__.'/binary';
|
||||
$original=file_get_contents($file);
|
||||
|
||||
\OC\Files\Filesystem::file_put_contents('/file',$original);
|
||||
|
||||
OC_FileProxy::$enabled=false;
|
||||
$stored=\OC\Files\Filesystem::file_get_contents('/file');
|
||||
OC_FileProxy::$enabled=true;
|
||||
|
||||
$fromFile=\OC\Files\Filesystem::file_get_contents('/file');
|
||||
$this->assertNotEqual($original,$stored);
|
||||
$this->assertEqual(strlen($original), strlen($fromFile));
|
||||
$this->assertEqual($original,$fromFile);
|
||||
|
||||
$file=__DIR__.'/zeros';
|
||||
$original=file_get_contents($file);
|
||||
|
||||
\OC\Files\Filesystem::file_put_contents('/file',$original);
|
||||
|
||||
OC_FileProxy::$enabled=false;
|
||||
$stored=\OC\Files\Filesystem::file_get_contents('/file');
|
||||
OC_FileProxy::$enabled=true;
|
||||
|
||||
$fromFile=\OC\Files\Filesystem::file_get_contents('/file');
|
||||
$this->assertNotEqual($original,$stored);
|
||||
$this->assertEqual(strlen($original), strlen($fromFile));
|
||||
}
|
||||
}
|
||||
@ -1,85 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (c) 2012 Robin Appelman <icewind@owncloud.com>
|
||||
* This file is licensed under the Affero General Public License version 3 or
|
||||
* later.
|
||||
* See the COPYING-README file.
|
||||
*/
|
||||
|
||||
class Test_CryptStream extends UnitTestCase {
|
||||
private $tmpFiles=array();
|
||||
|
||||
function testStream() {
|
||||
$stream=$this->getStream('test1', 'w', strlen('foobar'));
|
||||
fwrite($stream, 'foobar');
|
||||
fclose($stream);
|
||||
|
||||
$stream=$this->getStream('test1', 'r', strlen('foobar'));
|
||||
$data=fread($stream, 6);
|
||||
fclose($stream);
|
||||
$this->assertEqual('foobar', $data);
|
||||
|
||||
$file=OC::$SERVERROOT.'/3rdparty/MDB2.php';
|
||||
$source=fopen($file, 'r');
|
||||
$target=$this->getStream('test2', 'w', 0);
|
||||
OCP\Files::streamCopy($source, $target);
|
||||
fclose($target);
|
||||
fclose($source);
|
||||
|
||||
$stream=$this->getStream('test2', 'r', filesize($file));
|
||||
$data=stream_get_contents($stream);
|
||||
$original=file_get_contents($file);
|
||||
$this->assertEqual(strlen($original), strlen($data));
|
||||
$this->assertEqual($original, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* get a cryptstream to a temporary file
|
||||
* @param string $id
|
||||
* @param string $mode
|
||||
* @param int size
|
||||
* @return resource
|
||||
*/
|
||||
function getStream($id, $mode, $size) {
|
||||
if ($id==='') {
|
||||
$id=uniqid();
|
||||
}
|
||||
if ( ! isset($this->tmpFiles[$id])) {
|
||||
$file=OCP\Files::tmpFile();
|
||||
$this->tmpFiles[$id]=$file;
|
||||
} else {
|
||||
$file=$this->tmpFiles[$id];
|
||||
}
|
||||
$stream=fopen($file, $mode);
|
||||
OC_CryptStream::$sourceStreams[$id]=array('path'=>'dummy'.$id, 'stream'=>$stream, 'size'=>$size);
|
||||
return fopen('crypt://streams/'.$id, $mode);
|
||||
}
|
||||
|
||||
function testBinary() {
|
||||
$file=__DIR__.'/binary';
|
||||
$source=file_get_contents($file);
|
||||
|
||||
$stream=$this->getStream('test', 'w', strlen($source));
|
||||
fwrite($stream, $source);
|
||||
fclose($stream);
|
||||
|
||||
$stream=$this->getStream('test', 'r', strlen($source));
|
||||
$data=stream_get_contents($stream);
|
||||
fclose($stream);
|
||||
$this->assertEqual(strlen($data), strlen($source));
|
||||
$this->assertEqual($source, $data);
|
||||
|
||||
$file=__DIR__.'/zeros';
|
||||
$source=file_get_contents($file);
|
||||
|
||||
$stream=$this->getStream('test2', 'w', strlen($source));
|
||||
fwrite($stream, $source);
|
||||
fclose($stream);
|
||||
|
||||
$stream=$this->getStream('test2', 'r', strlen($source));
|
||||
$data=stream_get_contents($stream);
|
||||
fclose($stream);
|
||||
$this->assertEqual(strlen($data), strlen($source));
|
||||
$this->assertEqual($source, $data);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue