Development snapshot:

Rewrote crtpt class as Util, Hooks, and Crypt
Switched blowfish for openssl with AES
Added setup() method for creating user keys and directory structure
Many other changes complete and in progress
This commit is contained in:
Sam Tuke 2012-07-11 17:51:27 +01:00
parent 91da4b05b7
commit 6af99f3a09
4 changed files with 433 additions and 181 deletions

View File

@ -0,0 +1,50 @@
<?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 {
public static function login( $params ){
$view = new \OC_FilesystemView( '/' . $params['uid'] );
$storage = new Storage( $view );
if ( !$storage->ready() ) {
return $storage->setup( $params['password'] );
} else {
return true;
}
}
}
?>

View File

@ -2,8 +2,10 @@
/**
* ownCloud
*
* @author Frank Karlitschek
* @copyright 2012 Frank Karlitschek frank@owncloud.org
* @author Sam Tuke, Frank Karlitschek, Robin Appelman
* @copyright 2012 Sam Tuke samtuke@owncloud.com,
* Robin Appelman icewind@owncloud.com, Frank Karlitschek
* frank@owncloud.org
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
@ -20,79 +22,207 @@
*
*/
// Todo:
// - Crypt/decrypt button in the userinterface
// - Setting if crypto should be on by default
// - Add a setting "Don´t encrypt files larger than xx because of performance reasons"
// - Transparent decrypt/encrypt in filesystem.php. Autodetect if a file is encrypted (.encrypted extension)
// - Don't use a password directly as encryption key. but a key which is stored on the server and encrypted with the user password. -> password change faster
// - IMPORTANT! Check if the block lenght of the encrypted data stays the same
require_once('Crypt_Blowfish/Blowfish.php');
namespace OCA_Encryption;
/**
* This class is for crypting and decrypting
* Class for common cryptography functionality
*/
class OC_Crypt {
static private $bf = null;
public static function loginListener($params){
self::init($params['uid'],$params['password']);
class Crypt {
/**
* @brief Create a new encryption keypair
* @return array publicKey, privatekey
*/
public static function createKeypair() {
$res = openssl_pkey_new();
// Get private key
openssl_pkey_export( $res, $privateKey );
// Get public key
$publicKey = openssl_pkey_get_details( $res );
$publicKey = $publicKey['key'];
return( array( 'publicKey' => $publicKey, 'privateKey' => $privateKey ) );
}
/**
* @brief Symmetrically encrypt a file
* @returns encrypted file
*/
public static function encrypt( $plainContent, $iv, $passphrase = '' ) {
# TODO: Move these methods into a separate public class for app developers
$iv64 = base64_encode( $iv );
$raw = false; // true returns raw bytes, false returns base64
if ( $encryptedContent = openssl_encrypt( $plainContent, 'AES-256-OFB', $passphrase, $raw, $iv ) ) {
public static function init($login,$password) {
$view=new OC_FilesystemView('/');
if(!$view->file_exists('/'.$login)){
$view->mkdir('/'.$login);
return $encryptedContent;
} else {
\OC_Log::write( 'Encrypted storage', 'Encryption (symmetric) of file failed' , \OC_Log::ERROR );
return false;
}
}
/**
* @brief Symmetrically decrypt a file
* @returns decrypted file
*/
public static function decrypt( $encryptedContent, $iv, $passphrase ) {
// $iv64 = base64_encode( $iv );
//
// $iv = base64_decode( $iv64 );
OC_FileProxy::$enabled=false;
if(!$view->file_exists('/'.$login.'/encryption.key')){// does key exist?
OC_Crypt::createkey($login,$password);
$raw = false; // true returns raw bytes, false returns base64
if ( $plainContent = openssl_decrypt( $encryptedContent, 'AES-256-OFB', $passphrase, $raw, $iv) ) {
return $plainContent;
} else {
\OC_Log::write( 'Encrypted storage', 'Decryption (symmetric) of file failed' , \OC_Log::ERROR );
return false;
}
}
/**
* @brief Asymetrically encrypt a file using a public key
* @returns encrypted file
*/
public static function keyEncrypt( $plainContent, $publicKey ) {
openssl_public_encrypt( $plainContent, $encryptedContent, $publicKey );
return $encryptedContent;
}
/**
* @brief Asymetrically decrypt a file using a private key
* @returns decrypted file
*/
public static function keyDecrypt( $encryptedContent, $privatekey ) {
openssl_private_decrypt( $encryptedContent, $plainContent, $privatekey );
return $plainContent;
}
public static function encryptFile( $source, $target, $key='') {
$handleread = fopen($source, "rb");
if($handleread!=FALSE) {
$handlewrite = fopen($target, "wb");
while (!feof($handleread)) {
$content = fread($handleread, 8192);
$enccontent=OC_CRYPT::encrypt( $content, $key);
fwrite($handlewrite, $enccontent);
}
fclose($handlewrite);
fclose($handleread);
}
$key=$view->file_get_contents('/'.$login.'/encryption.key');
OC_FileProxy::$enabled=true;
$_SESSION['enckey']=OC_Crypt::decrypt($key, $password);
}
/**
* get the blowfish encryption handeler for a key
* @param string $key (optional)
* @return Crypt_Blowfish
*
* if the key is left out, the default handeler will be used
*/
public static function getBlowfish($key=''){
if($key){
return new Crypt_Blowfish($key);
}else{
if(!isset($_SESSION['enckey'])){
return false;
* @brief decryption of a file
* @param string $source
* @param string $target
* @param string $key the decryption key
*
* This function decrypts a file
*/
public static function decryptFile( $source, $target, $key='') {
$handleread = fopen($source, "rb");
if($handleread!=FALSE) {
$handlewrite = fopen($target, "wb");
while (!feof($handleread)) {
$content = fread($handleread, 8192);
$enccontent=OC_CRYPT::decrypt( $content, $key);
if(feof($handleread)){
$enccontent=rtrim($enccontent, "\0");
}
fwrite($handlewrite, $enccontent);
}
if(!self::$bf){
self::$bf=new Crypt_Blowfish($_SESSION['enckey']);
}
return self::$bf;
fclose($handlewrite);
fclose($handleread);
}
}
public static function createkey($username,$passcode) {
// generate a random key
$key=mt_rand(10000,99999).mt_rand(10000,99999).mt_rand(10000,99999).mt_rand(10000,99999);
// encrypt the key with the passcode of the user
$enckey=OC_Crypt::encrypt($key,$passcode);
// Write the file
$proxyEnabled=OC_FileProxy::$enabled;
OC_FileProxy::$enabled=false;
$view=new OC_FilesystemView('/'.$username);
$view->file_put_contents('/encryption.key',$enckey);
OC_FileProxy::$enabled=$proxyEnabled;
/**
* @brief Encrypts data in 8192 byte sized blocks
* @returns encrypted data
*/
public static function blockEncrypt( $data, $key = '' ){
$result = '';
while( strlen( $data ) ) {
// Encrypt byte block
$result .= self::encrypt( substr( $data, 0, 8192 ), $key );
$data = substr( $data, 8192 );
}
return $result;
}
/**
* decrypt data in 8192b sized blocks
*/
public static function blockDecrypt( $data, $key='', $maxLength = 0 ) {
$result = '';
while( strlen( $data ) ) {
$result .= self::decrypt( substr( $data, 0, 8192 ), $key );
$data = substr( $data,8192 );
}
if ( $maxLength > 0 ) {
return substr( $result, 0, $maxLength );
} else {
return rtrim( $result, "\0" );
}
}
/**
* @brief Generate a random key for symmetric encryption
* @returns $key Generated key
*/
public static function generateKey() {
$key = mt_rand( 10000, 99999 ) . mt_rand( 10000, 99999 ) . mt_rand( 10000, 99999 ) . mt_rand( 10000, 99999 );
return $key;
}
public static function changekeypasscode($oldPassword, $newPassword) {
@ -114,106 +244,6 @@ class OC_Crypt {
}
}
/**
* @brief encrypts an content
* @param $content the cleartext message you want to encrypt
* @param $key the encryption key (optional)
* @returns encrypted content
*
* This function encrypts an content
*/
public static function encrypt( $content, $key='') {
$bf = self::getBlowfish($key);
return $bf->encrypt($content);
}
/**
* @brief decryption of an content
* @param $content the cleartext message you want to decrypt
* @param $key the encryption key (optional)
* @returns cleartext content
*
* This function decrypts an content
*/
public static function decrypt( $content, $key='') {
$bf = self::getBlowfish($key);
$data=$bf->decrypt($content);
return $data;
}
/**
* @brief encryption of a file
* @param string $source
* @param string $target
* @param string $key the decryption key
*
* This function encrypts a file
*/
public static function encryptFile( $source, $target, $key='') {
$handleread = fopen($source, "rb");
if($handleread!=FALSE) {
$handlewrite = fopen($target, "wb");
while (!feof($handleread)) {
$content = fread($handleread, 8192);
$enccontent=OC_CRYPT::encrypt( $content, $key);
fwrite($handlewrite, $enccontent);
}
fclose($handlewrite);
fclose($handleread);
}
}
/**
* @brief decryption of a file
* @param string $source
* @param string $target
* @param string $key the decryption key
*
* This function decrypts a file
*/
public static function decryptFile( $source, $target, $key='') {
$handleread = fopen($source, "rb");
if($handleread!=FALSE) {
$handlewrite = fopen($target, "wb");
while (!feof($handleread)) {
$content = fread($handleread, 8192);
$enccontent=OC_CRYPT::decrypt( $content, $key);
if(feof($handleread)){
$enccontent=rtrim($enccontent, "\0");
}
fwrite($handlewrite, $enccontent);
}
fclose($handlewrite);
fclose($handleread);
}
}
/**
* encrypt data in 8192b sized blocks
*/
public static function blockEncrypt($data, $key=''){
$result='';
while(strlen($data)){
$result.=self::encrypt(substr($data,0,8192),$key);
$data=substr($data,8192);
}
return $result;
}
/**
* decrypt data in 8192b sized blocks
*/
public static function blockDecrypt($data, $key='',$maxLength=0){
$result='';
while(strlen($data)){
$result.=self::decrypt(substr($data,0,8192),$key);
$data=substr($data,8192);
}
if($maxLength>0){
return substr($result,0,$maxLength);
}else{
return rtrim($result, "\0");
}
}
}
?>

View File

@ -21,6 +21,14 @@
*
*/
class OC_FileProxy_Encryption extends OC_FileProxy {
}
/**
* transparent encryption
*/
@ -30,45 +38,76 @@ class OC_FileProxy_Encryption extends OC_FileProxy{
private static $enableEncryption=null;
/**
* check if a file should be encrypted during write
* Check if a file requires encryption
* @param string $path
* @return bool
*
* Tests if encryption is enabled, and file is allowed by blacklists
*/
private static function shouldEncrypt($path){
if(is_null(self::$enableEncryption)){
self::$enableEncryption=(OCP\Config::getAppValue('files_encryption','enable_encryption','true')=='true');
private static function shouldEncrypt( $path ) {
if ( is_null( self::$enableEncryption ) ) {
self::$enableEncryption = ( OCP\Config::getAppValue( 'files_encryption', 'enable_encryption', 'true' ) == 'true' );
}
if(!self::$enableEncryption){
if( !self::$enableEncryption ) {
return false;
}
if(is_null(self::$blackList)){
self::$blackList=explode(',',OCP\Config::getAppValue('files_encryption','type_blacklist','jpg,png,jpeg,avi,mpg,mpeg,mkv,mp3,oga,ogv,ogg'));
if( is_null(self::$blackList ) ) {
self::$blackList = explode(',',OCP\Config::getAppValue( 'files_encryption','type_blacklist','jpg,png,jpeg,avi,mpg,mpeg,mkv,mp3,oga,ogv,ogg' ) );
}
if(self::isEncrypted($path)){
if( self::isEncrypted( $path ) ) {
return true;
}
$extension=substr($path,strrpos($path,'.')+1);
if(array_search($extension,self::$blackList)===false){
$extension = substr( $path, strrpos( $path,'.' ) +1 );
if ( array_search( $extension, self::$blackList ) === false ){
return true;
}
return false;
}
/**
* check if a file is encrypted
* Check if a file is encrypted according to database file cache
* @param string $path
* @return bool
*/
private static function isEncrypted($path){
$metadata=OC_FileCache_Cached::get($path,'');
return isset($metadata['encrypted']) and (bool)$metadata['encrypted'];
private static function isEncrypted( $path ){
// Fetch all file metadata from DB
$metadata = OC_FileCache_Cached::get( $path, '' );
// Return encryption status
return isset( $metadata['encrypted'] ) and ( bool )$metadata['encrypted'];
}
public function preFile_put_contents($path,&$data){
if(self::shouldEncrypt($path)){
if (!is_resource($data)) {//stream put contents should have been converter to fopen
$size=strlen($data);
$data=OC_Crypt::blockEncrypt($data);
OC_FileCache::put($path,array('encrypted'=>true,'size'=>$size),'');
public function preFile_put_contents( $path, &$data ) {
if ( self::shouldEncrypt( $path ) ) {
if ( !is_resource( $data ) ) {//stream put contents should have been converter to fopen
$size = strlen( $data );
$data = Crypt::blockEncrypt( $data );
OC_FileCache::put( $path, array( 'encrypted'=>true, 'size' => $size ), '' );
}
}
}

View File

@ -0,0 +1,133 @@
<?php
/**
* ownCloud
*
* @author Sam Tuke, Frank Karlitschek
* @copyright 2012 Sam Tuke samtuke@owncloud.com,
* Frank Karlitschek frank@owncloud.org
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
// Todo:
// - Crypt/decrypt button in the userinterface
// - Setting if crypto should be on by default
// - Add a setting "Don´t encrypt files larger than xx because of performance reasons"
// - Transparent decrypt/encrypt in filesystem.php. Autodetect if a file is encrypted (.encrypted extension)
// - Don't use a password directly as encryption key. but a key which is stored on the server and encrypted with the user password. -> password change faster
// - IMPORTANT! Check if the block lenght of the encrypted data stays the same
namespace OCA_Encryption;
/**
* Class for utilities relating to encrypted file storage system
*/
class Util {
private $view; // OC_FilesystemView object for filesystem operations
private $pwd; // User Password
private $client; // Client side encryption mode flag
/**
* @brief get a list of all available versions of a file in descending chronological order
* @param $filename file to find versions of, relative to the user files dir
* @param $count number of versions to return
* @returns array
*/
public function __construct( \OC_FilesystemView $view, $client = false ) {
$this->view = $view;
$this->client = $client;
}
public function ready() {
if(
!$this->view->file_exists( '/' . 'keyfiles' )
or !$this->view->file_exists( '/' . 'keypair' )
or !$this->view->file_exists( '/' . 'keypair' . '/'. 'encryption.public.key' )
or !$this->view->file_exists( '/' . 'keypair' . '/'. 'encryption.private.key' )
) {
return false;
} else {
return true;
}
}
public function setup( $passphrase = null ) {
$publicKeyFileName = 'encryption.public.key';
$privateKeyFileName = 'encryption.private.key';
// Log changes to user's filesystem
$this->appInfo = \OC_APP::getAppInfo( 'files_encryption' );
\OC_Log::write( $this->appInfo['name'], 'File encryption for user will be set up' , \OC_Log::INFO );
// Create mirrored keyfile directory
if( !$this->view->file_exists( '/' . 'keyfiles' ) ) {
$this->view->mkdir( '/'. 'keyfiles' );
}
// Create keypair directory
if( !$this->view->file_exists( '/'. 'keypair' ) ) {
$this->view->mkdir( '/'. 'keypair' );
}
// Create user keypair
if (
!$this->view->file_exists( '/'. 'keypair'. '/' . $publicKeyFileName )
or !$this->view->file_exists( '/'. 'keypair'. '/' . $privateKeyFileName )
) {
// Generate keypair
$keypair = Crypt::createKeypair();
// Save public key
$this->view->file_put_contents( '/'. 'keypair'. '/' . $publicKeyFileName, $keypair['publicKey'] );
if ( $this->client == false ) {
# TODO: Use proper IV in encryption
// Encrypt private key with user pwd as passphrase
$encryptedPrivateKey = Crypt::encrypt( $keypair['privateKey'], 1234567890123456, $passphrase );
// $iv = openssl_random_pseudo_bytes(16);
$this->view->file_put_contents( '/'. 'keypair'. '/' . $privateKeyFileName, $encryptedPrivateKey );
} else {
# TODO PHASE2: add public key to keyserver for client-side
# TODO PHASE2: encrypt private key using password / new client side specified key, instead of existing user pwd
}
}
}
}