From 6af99f3a099a7192c1f4864d5e9472cb69726060 Mon Sep 17 00:00:00 2001 From: Sam Tuke Date: Wed, 11 Jul 2012 17:51:27 +0100 Subject: 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 --- apps/files_encryption/lib/crypt.php | 314 ++++++++++++++++++++---------------- apps/files_encryption/lib/proxy.php | 79 ++++++--- apps/files_encryption/lib/util.php | 133 +++++++++++++++ 3 files changed, 364 insertions(+), 162 deletions(-) create mode 100644 apps/files_encryption/lib/util.php (limited to 'apps/files_encryption/lib') diff --git a/apps/files_encryption/lib/crypt.php b/apps/files_encryption/lib/crypt.php index 849e88ee0b2..7763f6bea56 100644 --- a/apps/files_encryption/lib/crypt.php +++ b/apps/files_encryption/lib/crypt.php @@ -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,135 +22,111 @@ * */ - - -// 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']); - } - public static function init($login,$password) { - $view=new OC_FilesystemView('/'); - if(!$view->file_exists('/'.$login)){ - $view->mkdir('/'.$login); - } +class Crypt { - OC_FileProxy::$enabled=false; - if(!$view->file_exists('/'.$login.'/encryption.key')){// does key exist? - OC_Crypt::createkey($login,$password); - } - $key=$view->file_get_contents('/'.$login.'/encryption.key'); - OC_FileProxy::$enabled=true; - $_SESSION['enckey']=OC_Crypt::decrypt($key, $password); - } + /** + * @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 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; - } - if(!self::$bf){ - self::$bf=new Crypt_Blowfish($_SESSION['enckey']); - } - return self::$bf; - } + // Get public key + $publicKey = openssl_pkey_get_details( $res ); + + $publicKey = $publicKey['key']; + + return( array( 'publicKey' => $publicKey, 'privateKey' => $privateKey ) ); + } - - 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 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 ) ) { + + return $encryptedContent; + + } else { + + \OC_Log::write( 'Encrypted storage', 'Encryption (symmetric) of file failed' , \OC_Log::ERROR ); + + return false; + + } + } - - public static function changekeypasscode($oldPassword, $newPassword) { - if(OCP\User::isLoggedIn()){ - $username=OCP\USER::getUser(); - $view=new OC_FilesystemView('/'.$username); - - // read old key - $key=$view->file_get_contents('/encryption.key'); - - // decrypt key with old passcode - $key=OC_Crypt::decrypt($key, $oldPassword); - - // encrypt again with new passcode - $key=OC_Crypt::encrypt($key, $newPassword); - - // store the new key - $view->file_put_contents('/encryption.key', $key ); + + /** + * @brief Symmetrically decrypt a file + * @returns decrypted file + */ + public static function decrypt( $encryptedContent, $iv, $passphrase ) { + +// $iv64 = base64_encode( $iv ); +// +// $iv = base64_decode( $iv64 ); + + $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 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 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 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 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; + } - - /** - * @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) { @@ -165,13 +143,13 @@ class OC_Crypt { /** - * @brief decryption of a file - * @param string $source - * @param string $target - * @param string $key the decryption key - * - * This function decrypts a file - */ + * @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) { @@ -189,31 +167,83 @@ class OC_Crypt { } } - /** - * 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); + /** + * @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); + 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"); + + 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) { + if(OCP\User::isLoggedIn()){ + $username=OCP\USER::getUser(); + $view=new OC_FilesystemView('/'.$username); + + // read old key + $key=$view->file_get_contents('/encryption.key'); + + // decrypt key with old passcode + $key=OC_Crypt::decrypt($key, $oldPassword); + + // encrypt again with new passcode + $key=OC_Crypt::encrypt($key, $newPassword); + + // store the new key + $view->file_put_contents('/encryption.key', $key ); + } + } + } + +?> \ No newline at end of file diff --git a/apps/files_encryption/lib/proxy.php b/apps/files_encryption/lib/proxy.php index f25e4a662f6..e06242e29d4 100644 --- a/apps/files_encryption/lib/proxy.php +++ b/apps/files_encryption/lib/proxy.php @@ -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 ), '' ); + } } } diff --git a/apps/files_encryption/lib/util.php b/apps/files_encryption/lib/util.php new file mode 100644 index 00000000000..d576b752944 --- /dev/null +++ b/apps/files_encryption/lib/util.php @@ -0,0 +1,133 @@ +. + * + */ + +// 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 + + } + + } + + } + +} -- cgit v1.2.3 From 283561823febbfb668ca33e234a01b5342e16e60 Mon Sep 17 00:00:00 2001 From: Sam Tuke Date: Tue, 17 Jul 2012 19:15:59 +0100 Subject: Added methods for handling encrypted file + iv content Improved IV generation --- apps/files_encryption/lib/crypt.php | 162 +++++++++++++++--------------------- apps/files_encryption/lib/util.php | 2 +- 2 files changed, 68 insertions(+), 96 deletions(-) (limited to 'apps/files_encryption/lib') diff --git a/apps/files_encryption/lib/crypt.php b/apps/files_encryption/lib/crypt.php index 7763f6bea56..e5bc3adcbc5 100644 --- a/apps/files_encryption/lib/crypt.php +++ b/apps/files_encryption/lib/crypt.php @@ -55,20 +55,14 @@ class Crypt { * @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 ) ) { + if ( $encryptedContent = openssl_encrypt( $plainContent, 'AES-128-CFB', $passphrase, false, $iv ) ) { return $encryptedContent; } else { - \OC_Log::write( 'Encrypted storage', 'Encryption (symmetric) of file failed' , \OC_Log::ERROR ); + \OC_Log::write( 'Encrypted storage', 'Encryption (symmetric) of content failed' , \OC_Log::ERROR ); return false; @@ -81,21 +75,15 @@ class Crypt { * @returns decrypted file */ public static function decrypt( $encryptedContent, $iv, $passphrase ) { - -// $iv64 = base64_encode( $iv ); -// -// $iv = base64_decode( $iv64 ); - - $raw = false; // true returns raw bytes, false returns base64 - if ( $plainContent = openssl_decrypt( $encryptedContent, 'AES-256-OFB', $passphrase, $raw, $iv) ) { + if ( $plainContent = openssl_decrypt( $encryptedContent, 'AES-128-CFB', $passphrase, false, $iv ) ) { return $plainContent; } else { - \OC_Log::write( 'Encrypted storage', 'Decryption (symmetric) of file failed' , \OC_Log::ERROR ); + \OC_Log::write( 'Encrypted storage', 'Decryption (symmetric) of content failed' , \OC_Log::ERROR ); return false; @@ -104,113 +92,97 @@ class Crypt { } /** - * @brief Asymetrically encrypt a file using a public key - * @returns encrypted file + * @brief Creates symmetric keyfile content + * @param $plainContent content to be encrypted in keyfile + * @returns encrypted content combined with IV + * @note IV need not be specified, as it will be stored in the returned keyfile + * and remain accessible therein. */ - public static function keyEncrypt( $plainContent, $publicKey ) { - - openssl_public_encrypt( $plainContent, $encryptedContent, $publicKey ); + public static function symmetricEncryptFileContent( $plainContent, $passphrase = '' ) { - return $encryptedContent; - - } - - /** - * @brief Asymetrically decrypt a file using a private key - * @returns decrypted file - */ - public static function keyDecrypt( $encryptedContent, $privatekey ) { + if ( !$plainContent ) { + + return false; + + } + + $random = openssl_random_pseudo_bytes( 13 ); - openssl_private_decrypt( $encryptedContent, $plainContent, $privatekey ); + $iv = substr( base64_encode( $random ), 0, -4 ); - 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); + if ( $encryptedContent = self::encrypt( $plainContent, $iv, $passphrase ) ) { + + $combinedKeyfile = $encryptedContent .= $iv; + + return $combinedKeyfile; + + } else { + + \OC_Log::write( 'Encrypted storage', 'Encryption (symmetric) of keyfile content failed' , \OC_Log::ERROR ); + + return false; + } + } /** - * @brief decryption of a file + * @brief Decrypts keyfile content * @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); - } - } + public static function symmetricDecryptFileContent( $keyfileContent, $passphrase = '' ) { - /** - * @brief Encrypts data in 8192 byte sized blocks - * @returns encrypted data - */ - public static function blockEncrypt( $data, $key = '' ){ - - $result = ''; - - while( strlen( $data ) ) { + if ( !$keyfileContent ) { - // Encrypt byte block - $result .= self::encrypt( substr( $data, 0, 8192 ), $key ); + return false; - $data = substr( $data, 8192 ); - } - return $result; - } - - /** - * decrypt data in 8192b sized blocks - */ - public static function blockDecrypt( $data, $key='', $maxLength = 0 ) { - - $result = ''; + $iv = substr( $keyfileContent, -16 ); - while( strlen( $data ) ) { - - $result .= self::decrypt( substr( $data, 0, 8192 ), $key ); - - $data = substr( $data,8192 ); - - } + $encryptedContent = substr( $keyfileContent, 0, -16 ); - if ( $maxLength > 0 ) { + if ( $plainContent = self::decrypt( $encryptedContent, $iv, $passphrase ) ) { - return substr( $result, 0, $maxLength ); + return $plainContent; } else { - return rtrim( $result, "\0" ); + \OC_Log::write( 'Encrypted storage', 'Decryption (symmetric) of keyfile content 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; + } /** diff --git a/apps/files_encryption/lib/util.php b/apps/files_encryption/lib/util.php index d576b752944..9c0f71fe395 100644 --- a/apps/files_encryption/lib/util.php +++ b/apps/files_encryption/lib/util.php @@ -114,7 +114,7 @@ class Util { # TODO: Use proper IV in encryption // Encrypt private key with user pwd as passphrase - $encryptedPrivateKey = Crypt::encrypt( $keypair['privateKey'], 1234567890123456, $passphrase ); + $encryptedPrivateKey = Crypt::createSymmetricKeyfile( $keypair['privateKey'], $passphrase ); // $iv = openssl_random_pseudo_bytes(16); $this->view->file_put_contents( '/'. 'keypair'. '/' . $privateKeyFileName, $encryptedPrivateKey ); -- cgit v1.2.3 From d294e7772156dc27b6d69df405f7dcf7d7f4326f Mon Sep 17 00:00:00 2001 From: Sam Tuke Date: Wed, 18 Jul 2012 18:52:00 +0100 Subject: Development snapshot: - Added methods for sealing data with multiple keys - Added method for encrypting data, generating iv and keyfile, and returning both - Added 6 unit test cases (containing 12 tests) for Crypt class - Commented out old unit tests for now --- apps/files_encryption/lib/crypt.php | 124 ++++++++++++++-- apps/files_encryption/lib/proxy.php | 7 - apps/files_encryption/lib/util.php | 2 +- apps/files_encryption/tests/encryption.php | 214 ++++++++++++++++++++-------- apps/files_encryption/tests/proxy.php | 218 ++++++++++++++--------------- apps/files_encryption/tests/stream.php | 154 ++++++++++---------- 6 files changed, 458 insertions(+), 261 deletions(-) (limited to 'apps/files_encryption/lib') diff --git a/apps/files_encryption/lib/crypt.php b/apps/files_encryption/lib/crypt.php index e5bc3adcbc5..098074c228d 100644 --- a/apps/files_encryption/lib/crypt.php +++ b/apps/files_encryption/lib/crypt.php @@ -51,7 +51,7 @@ class Crypt { } /** - * @brief Symmetrically encrypt a file + * @brief Symmetrically encrypt a string * @returns encrypted file */ public static function encrypt( $plainContent, $iv, $passphrase = '' ) { @@ -62,7 +62,7 @@ class Crypt { } else { - \OC_Log::write( 'Encrypted storage', 'Encryption (symmetric) of content failed' , \OC_Log::ERROR ); + \OC_Log::write( 'Encryption library', 'Encryption (symmetric) of content failed' , \OC_Log::ERROR ); return false; @@ -71,7 +71,7 @@ class Crypt { } /** - * @brief Symmetrically decrypt a file + * @brief Symmetrically decrypt a string * @returns decrypted file */ public static function decrypt( $encryptedContent, $iv, $passphrase ) { @@ -83,7 +83,7 @@ class Crypt { } else { - \OC_Log::write( 'Encrypted storage', 'Decryption (symmetric) of content failed' , \OC_Log::ERROR ); + \OC_Log::write( 'Encryption library', 'Decryption (symmetric) of content failed' , \OC_Log::ERROR ); return false; @@ -92,7 +92,7 @@ class Crypt { } /** - * @brief Creates symmetric keyfile content + * @brief Symmetrically encrypts a string and returns keyfile content * @param $plainContent content to be encrypted in keyfile * @returns encrypted content combined with IV * @note IV need not be specified, as it will be stored in the returned keyfile @@ -118,7 +118,7 @@ class Crypt { } else { - \OC_Log::write( 'Encrypted storage', 'Encryption (symmetric) of keyfile content failed' , \OC_Log::ERROR ); + \OC_Log::write( 'Encryption library', 'Encryption (symmetric) of keyfile content failed' , \OC_Log::ERROR ); return false; @@ -128,7 +128,7 @@ class Crypt { /** - * @brief Decrypts keyfile content + * @brief Symmetrically decrypts keyfile content * @param string $source * @param string $target * @param string $key the decryption key @@ -153,7 +153,91 @@ class Crypt { } else { - \OC_Log::write( 'Encrypted storage', 'Decryption (symmetric) of keyfile content failed' , \OC_Log::ERROR ); + \OC_Log::write( 'Encryption library', 'Decryption (symmetric) of keyfile content failed' , \OC_Log::ERROR ); + + return false; + + } + + } + + /** + * @brief Creates symmetric keyfile content using a generated key + * @param string $plainContent content to be encrypted + * @returns array keys: key, encrypted + * @note symmetricDecryptFileContent() can be used to decrypt files created using this method + * + * This function decrypts a file + */ + public static function symmetricEncryptFileContentKeyfile( $plainContent ) { + + $key = self::generateKey(); + + if( $encryptedContent = self::symmetricEncryptFileContent( $plainContent, $key ) ) { + + return array( + 'key' => $key + , 'encrypted' => $encryptedContent + ); + + } else { + + return false; + + } + + } + + /** + * @brief Create asymmetrically encrypted keyfile content using a generated key + * @param string $plainContent content to be encrypted + * @returns array keys: key, encrypted + * @note symmetricDecryptFileContent() can be used to decrypt files created using this method + * + * This function decrypts a file + */ + public static function multiKeyEncrypt( $plainContent, array $publicKeys ) { + + $envKeys = array(); + + if( openssl_seal( $plainContent, $sealed, $envKeys, $publicKeys ) ) { + + return array( + 'keys' => $envKeys + , 'encrypted' => $sealed + ); + + } else { + + return false; + + } + + } + + /** + * @brief Asymmetrically encrypt a file using multiple public keys + * @param string $plainContent content to be encrypted + * @returns array keys: key, encrypted + * @note symmetricDecryptFileContent() can be used to decrypt files created using this method + * + * This function decrypts a file + */ + public static function multiKeyDecrypt( $encryptedContent, $envKey, $privateKey ) { + + if ( !$encryptedContent ) { + + return false; + + } + + if ( openssl_open( $encryptedContent, $plainContent, $envKey, $privateKey ) ) { + + return $plainContent; + + } else { + + \OC_Log::write( 'Encryption library', 'Decryption (asymmetric) of sealed content failed' , \OC_Log::ERROR ); return false; @@ -162,7 +246,7 @@ class Crypt { } /** - * @brief Asymetrically encrypt a file using a public key + * @brief Asymetrically encrypt a string using a public key * @returns encrypted file */ public static function keyEncrypt( $plainContent, $publicKey ) { @@ -186,14 +270,30 @@ class Crypt { } /** - * @brief Generate a random key for symmetric encryption + * @brief Generate a pseudo random 1024kb ASCII key * @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 ); + // $key = mt_rand( 10000, 99999 ) . mt_rand( 10000, 99999 ) . mt_rand( 10000, 99999 ) . mt_rand( 10000, 99999 ); + + // Generate key + if ( $key = base64_encode( openssl_random_pseudo_bytes( 768000, $strong ) ) ) { + + if ( !$strong ) { + + // If OpenSSL indicates randomness is insecure, log error + \OC_Log::write( 'Encryption library', 'Insecure symmetric key was generated using openssl_random_pseudo_bytes()' , \OC_Log::WARN ); + + } + + return $key; + + } else { - return $key; + return false; + + } } diff --git a/apps/files_encryption/lib/proxy.php b/apps/files_encryption/lib/proxy.php index e06242e29d4..3f9b86b988b 100644 --- a/apps/files_encryption/lib/proxy.php +++ b/apps/files_encryption/lib/proxy.php @@ -22,13 +22,6 @@ */ -class OC_FileProxy_Encryption extends OC_FileProxy { - - - -} - - /** * transparent encryption */ diff --git a/apps/files_encryption/lib/util.php b/apps/files_encryption/lib/util.php index 9c0f71fe395..62b435583e3 100644 --- a/apps/files_encryption/lib/util.php +++ b/apps/files_encryption/lib/util.php @@ -114,7 +114,7 @@ class Util { # TODO: Use proper IV in encryption // Encrypt private key with user pwd as passphrase - $encryptedPrivateKey = Crypt::createSymmetricKeyfile( $keypair['privateKey'], $passphrase ); + $encryptedPrivateKey = Crypt::symmetricEncryptFileContent( $keypair['privateKey'], $passphrase ); // $iv = openssl_random_pseudo_bytes(16); $this->view->file_put_contents( '/'. 'keypair'. '/' . $privateKeyFileName, $encryptedPrivateKey ); diff --git a/apps/files_encryption/tests/encryption.php b/apps/files_encryption/tests/encryption.php index 286770a69f5..600e00fd3e4 100644 --- a/apps/files_encryption/tests/encryption.php +++ b/apps/files_encryption/tests/encryption.php @@ -6,67 +6,171 @@ * See the COPYING-README file. */ +require realpath( dirname(__FILE__).'/../lib/crypt.php' ); + 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 setUp() { + + // set content for encrypting / decrypting in tests + $this->data = realpath( dirname(__FILE__).'/../lib/crypt.php' ); + + } + + function tearDown(){} + + function testGenerateKey() { + + # TODO: use more accurate (larger) string length for test confirmation + + $key = OCA_Encryption\Crypt::generateKey(); + + $this->assertTrue( strlen( $key ) > 1000 ); + + } + + function testEncrypt() { + + $random = openssl_random_pseudo_bytes( 13 ); + + $iv = substr( base64_encode( $random ), 0, -4 ); // i.e. E5IG033j+mRNKrht + + $crypted = OCA_Encryption\Crypt::encrypt( $this->data, $iv, 'hat' ); + + $this->assertNotEqual( $this->data, $crypted ); + + } + + function testDecrypt() { + + $random = openssl_random_pseudo_bytes( 13 ); + + $iv = substr( base64_encode( $random ), 0, -4 ); // i.e. E5IG033j+mRNKrht + + $crypted = OCA_Encryption\Crypt::encrypt( $this->data, $iv, 'hat' ); + + $decrypt = OCA_Encryption\Crypt::decrypt( $crypted, $iv, 'hat' ); + + $this->assertEqual( $this->data, $decrypt ); + + } + + function testSymmetricEncryptFileContent() { + + # TODO: search in keyfile for actual content as IV will ensure this test always passes + + $keyfileContent = OCA_Encryption\Crypt::symmetricEncryptFileContent( $this->data, 'hat' ); + + $this->assertNotEqual( $this->data, $keyfileContent ); + + + $decrypt = OCA_Encryption\Crypt::symmetricDecryptFileContent( $keyfileContent, 'hat' ); + + $this->assertEqual( $this->data, $decrypt ); + } - function testBinary(){ - $key=uniqid(); + function testSymmetricEncryptFileContentKeyfile() { - $file=__DIR__.'/binary'; - $source=file_get_contents($file); //binary file - $encrypted=OC_Crypt::encrypt($source,$key); - $decrypted=OC_Crypt::decrypt($encrypted,$key); + # TODO: search in keyfile for actual content as IV will ensure this test always passes + + $crypted = OCA_Encryption\Crypt::symmetricEncryptFileContentKeyfile( $this->data ); + + $this->assertNotEqual( $this->data, $crypted['encrypted'] ); + + + $decrypt = OCA_Encryption\Crypt::symmetricDecryptFileContent( $crypted['encrypted'], $crypted['key'] ); + + $this->assertEqual( $this->data, $decrypt ); + + } + + function testMultiKeyEncrypt() { + + # TODO: search in keyfile for actual content as IV will ensure this test always passes + + $pair1 = OCA_Encryption\Crypt::createKeypair(); + + $this->assertEqual( 2, count( $pair1 ) ); + + $this->assertTrue( strlen( $pair1['publicKey'] ) > 1 ); + + $this->assertTrue( strlen( $pair1['privateKey'] ) > 1 ); + - $decrypted=rtrim($decrypted, "\0"); - $this->assertEqual($decrypted,$source); + $crypted = OCA_Encryption\Crypt::multiKeyEncrypt( $this->data, array( $pair1['publicKey'] ) ); + + $this->assertNotEqual( $this->data, $crypted['encrypted'] ); + - $encrypted=OC_Crypt::blockEncrypt($source,$key); - $decrypted=OC_Crypt::blockDecrypt($encrypted,$key,strlen($source)); - $this->assertEqual($decrypted,$source); + $decrypt = OCA_Encryption\Crypt::multiKeyDecrypt( $crypted['encrypted'], $crypted['keys'][0], $pair1['privateKey'] ); + + $this->assertEqual( $this->data, $decrypt ); + } + +// 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); +// } + } diff --git a/apps/files_encryption/tests/proxy.php b/apps/files_encryption/tests/proxy.php index 5463836a209..253a32164ec 100644 --- a/apps/files_encryption/tests/proxy.php +++ b/apps/files_encryption/tests/proxy.php @@ -6,112 +6,112 @@ * 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_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['enckey']=$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)); - } -} +// 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_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['enckey']=$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)); +// } +// } diff --git a/apps/files_encryption/tests/stream.php b/apps/files_encryption/tests/stream.php index d95ea792f72..4c78b2d7b0f 100644 --- a/apps/files_encryption/tests/stream.php +++ b/apps/files_encryption/tests/stream.php @@ -6,80 +6,80 @@ * 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); - } -} +// 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); +// } +// } -- cgit v1.2.3 From 92162898567c5b19e0119b63484e3beb228c5490 Mon Sep 17 00:00:00 2001 From: Sam Tuke Date: Tue, 24 Jul 2012 17:53:12 +0100 Subject: Wrote new methods for testing if a file is encrypted using AES or Blowfish Added more unit tests for crypt class Added new method for generating 16 character pseudo-random initialisation vectors Started writing new methods for handling legacy keys and en/de/re cryption Added comments to lib/filecache.php explaining expected $path type --- apps/files_encryption/lib/crypt.php | 98 ++++++++++++++++- apps/files_encryption/lib/util.php | 118 +++++++++++++++++++++ apps/files_encryption/tests/encryption.php | 44 +++++++- .../tests/legacy-encrypted-text.txt | Bin 0 -> 3360 bytes lib/filecache.php | 10 +- 5 files changed, 261 insertions(+), 9 deletions(-) create mode 100644 apps/files_encryption/tests/legacy-encrypted-text.txt (limited to 'apps/files_encryption/lib') diff --git a/apps/files_encryption/lib/crypt.php b/apps/files_encryption/lib/crypt.php index 098074c228d..668101014a2 100644 --- a/apps/files_encryption/lib/crypt.php +++ b/apps/files_encryption/lib/crypt.php @@ -50,6 +50,66 @@ class Crypt { } + /** + * @brief Check if a file's contents contains an IV and is symmetrically encrypted + * @return true / false + */ + public static function isEncryptedContent( $content ) { + + if ( !$content ) { + + return false; + + } + + // Fetch encryption metadata from end of file + $meta = substr( $content, -22 ); + + // Fetch IV from end of file + $iv = substr( $meta, -16 ); + + // Fetch identifier from start of metadata + $identifier = substr( $meta, 0, 6 ); + + if ( $identifier == '00iv00') { + + return true; + + } else { + + return false; + + } + + } + + /** + * @brief Check if a file is encrypted via legacy system + * @return true / false + */ + public static function isLegacyEncryptedContent( $content, $path ) { + + // Fetch all file metadata from DB + $metadata = \OC_FileCache_Cached::get( $content, '' ); + + // If a file is flagged with encryption in DB, but isn't a valid content + IV combination, it's probably using the legacy encryption system + if ( + $content + and isset( $metadata['encrypted'] ) + and $metadata['encrypted'] === true + and !self::isEncryptedContent( $content ) + ) { + + return true; + + } else { + + return false; + + } + + } + /** * @brief Symmetrically encrypt a string * @returns encrypted file @@ -106,13 +166,12 @@ class Crypt { } - $random = openssl_random_pseudo_bytes( 13 ); - - $iv = substr( base64_encode( $random ), 0, -4 ); + $iv = self::generateIv(); if ( $encryptedContent = self::encrypt( $plainContent, $iv, $passphrase ) ) { - $combinedKeyfile = $encryptedContent .= $iv; + // Combine content to encrypt with IV identifier and actual IV + $combinedKeyfile = $encryptedContent . '00iv00' . $iv; return $combinedKeyfile; @@ -143,9 +202,11 @@ class Crypt { } + // Fetch IV from end of file $iv = substr( $keyfileContent, -16 ); - $encryptedContent = substr( $keyfileContent, 0, -16 ); + // Remove IV and IV identifier text to expose encrypted content + $encryptedContent = substr( $keyfileContent, 0, -22 ); if ( $plainContent = self::decrypt( $encryptedContent, $iv, $passphrase ) ) { @@ -269,6 +330,33 @@ class Crypt { } + /** + * @brief Generate a pseudo random 1024kb ASCII key + * @returns $key Generated key + */ + public static function generateIv() { + + if ( $random = openssl_random_pseudo_bytes( 13, $strong ) ) { + + if ( !$strong ) { + + // If OpenSSL indicates randomness is insecure, log error + \OC_Log::write( 'Encryption library', 'Insecure symmetric key was generated using openssl_random_pseudo_bytes()' , \OC_Log::WARN ); + + } + + $iv = substr( base64_encode( $random ), 0, -4 ); + + return $iv; + + } else { + + return false; + + } + + } + /** * @brief Generate a pseudo random 1024kb ASCII key * @returns $key Generated key diff --git a/apps/files_encryption/lib/util.php b/apps/files_encryption/lib/util.php index 62b435583e3..5185ad351d6 100644 --- a/apps/files_encryption/lib/util.php +++ b/apps/files_encryption/lib/util.php @@ -37,6 +37,23 @@ namespace OCA_Encryption; class Util { + # DONE: add method to check if file is encrypted using new system + # DONE: add method to check if file is encrypted using old system + # TODO: add method to encrypt all user files using new system + # TODO: add method to decrypt all user files using new system + # TODO: add method to encrypt all user files using old system + # TODO: add method to decrypt all user files using old system + # TODO: fix / test the crypt stream proxy class + # TODO: add support for optional recovery user in case of lost passphrase / keys + # TODO: add admin optional required long passphrase for users + # TODO: implement flag system to allow user to specify encryption by folder, subfolder, etc. + # TODO: add UI buttons for encrypt / decrypt everything? + + # TODO: test new encryption with webdav + # TODO: test new encryption with versioning + # TODO: test new encryption with sharing + # TODO: test new encryption with proxies + private $view; // OC_FilesystemView object for filesystem operations private $pwd; // User Password private $client; // Client side encryption mode flag @@ -73,6 +90,10 @@ class Util { } + /** + * @brief Sets up encryption folders and keys for a user + * @param $passphrase passphrase to encrypt server-stored private key with + */ public function setup( $passphrase = null ) { $publicKeyFileName = 'encryption.public.key'; @@ -129,5 +150,102 @@ class Util { } } + + /** + * @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( $login, $passphrase ) { + + OC_FileProxy::$enabled = false; + + if ( + $login + and $passphrase + and $key = $this->view->file_get_contents( '/' . $login . '/encryption.key' ) + ) { + + OC_FileProxy::$enabled = true; + + return $this->legacyDecrypt( $key, $passphrase ); + + } else { + + OC_FileProxy::$enabled = true; + + return false; + + } + + } + + /** + * @brief Get the blowfish encryption handeler for a key + * @param $key string (optional) + * @return Crypt_Blowfish blowfish object + * + * if the key is left out, the default handeler will be used + */ + public function getBlowfish( $key = '' ) { + + if( $key ){ + + return new Crypt_Blowfish($key); + + } else { + + return false; + + } + + } + + /** + * @brief encrypts content using legacy blowfish system + * @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 legacyEncrypt( $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 legacyDecrypt( $content, $key = '' ) { + + $bf = $this->getBlowfish( $key ); + + $data = $bf->decrypt( $content ); + + return $data; + + } + + /** + * @brief Re-encryptes a legacy blowfish encrypted file using AES with integrated IV + * @param $legacyContent the legacy encrypted content to re-encrypt + * @returns cleartext content + * + * This function decrypts an content + */ + public function legacyRecrypt( $legacyContent ) { + + # TODO: write me + + } } diff --git a/apps/files_encryption/tests/encryption.php b/apps/files_encryption/tests/encryption.php index 600e00fd3e4..9246e715262 100644 --- a/apps/files_encryption/tests/encryption.php +++ b/apps/files_encryption/tests/encryption.php @@ -1,19 +1,22 @@ + * Copyright (c) 2012 Sam Tuke , and + * Robin Appelman * This file is licensed under the Affero General Public License version 3 or * later. * See the COPYING-README file. */ require realpath( dirname(__FILE__).'/../lib/crypt.php' ); +//require realpath( dirname(__FILE__).'/../../../lib/filecache.php' ); class Test_Encryption extends UnitTestCase { - + function setUp() { // set content for encrypting / decrypting in tests $this->data = realpath( dirname(__FILE__).'/../lib/crypt.php' ); + $this->legacyEncryptedData = realpath( dirname(__FILE__).'/legacy-encrypted-text.txt' ); } @@ -25,10 +28,22 @@ class Test_Encryption extends UnitTestCase { $key = OCA_Encryption\Crypt::generateKey(); + $this->assertTrue( $key ); + $this->assertTrue( strlen( $key ) > 1000 ); } + function testGenerateIv() { + + $iv = OCA_Encryption\Crypt::generateIv(); + + $this->assertTrue( $iv ); + + $this->assertTrue( strlen( $iv ) == 16 ); + + } + function testEncrypt() { $random = openssl_random_pseudo_bytes( 13 ); @@ -85,6 +100,31 @@ class Test_Encryption extends UnitTestCase { } + function testIsEncryptedContent() { + + $this->assertFalse( OCA_Encryption\Crypt::isEncryptedContent( $this->data ) ); + + $this->assertFalse( OCA_Encryption\Crypt::isEncryptedContent( $this->legacyEncryptedData ) ); + + $keyfileContent = OCA_Encryption\Crypt::symmetricEncryptFileContent( $this->data, 'hat' ); + + $this->assertTrue( OCA_Encryption\Crypt::isEncryptedContent( $keyfileContent ) ); + + } + +// // 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' ) ); +// +// } + function testMultiKeyEncrypt() { # TODO: search in keyfile for actual content as IV will ensure this test always passes diff --git a/apps/files_encryption/tests/legacy-encrypted-text.txt b/apps/files_encryption/tests/legacy-encrypted-text.txt new file mode 100644 index 00000000000..cb5bf50550d Binary files /dev/null and b/apps/files_encryption/tests/legacy-encrypted-text.txt differ diff --git a/lib/filecache.php b/lib/filecache.php index d956f34dc48..8894c8a3ebb 100644 --- a/lib/filecache.php +++ b/lib/filecache.php @@ -23,10 +23,16 @@ * provide caching for filesystem info in the database * * not used by OC_Filesystem for reading filesystem info, - * instread apps should use OC_FileCache::get where possible + * instead apps should use OC_FileCache::get where possible + * + * It will try to keep the data up to date but changes from outside + * ownCloud can invalidate the cache + * + * Methods that take $path and $root params expect $path to be relative, like + * /admin/files/file.txt, if $root is false * - * It will try to keep the data up to date but changes from outside ownCloud can invalidate the cache */ + class OC_FileCache{ /** * get the filesystem info from the cache -- cgit v1.2.3 From 9368ea73c862b2069d3de5cac6ad7827ab33591c Mon Sep 17 00:00:00 2001 From: Sam Tuke Date: Wed, 25 Jul 2012 12:38:40 +0100 Subject: added tests and methods relating to handling of legacy keys --- apps/files_encryption/lib/util.php | 72 +++++++++++++++++------------- apps/files_encryption/tests/encryption.php | 15 +------ apps/files_encryption/tests/util.php | 72 ++++++++++++++++++++++++++++++ 3 files changed, 115 insertions(+), 44 deletions(-) create mode 100644 apps/files_encryption/tests/util.php (limited to 'apps/files_encryption/lib') diff --git a/apps/files_encryption/lib/util.php b/apps/files_encryption/lib/util.php index 5185ad351d6..c7d9ec07d6e 100644 --- a/apps/files_encryption/lib/util.php +++ b/apps/files_encryption/lib/util.php @@ -39,6 +39,8 @@ class Util { # DONE: add method to check if file is encrypted using new system # DONE: add method to check if file is encrypted using old system + # DONE: add method to fetch legacy key + # DONE: add method to decrypt legacy encrypted data # TODO: add method to encrypt all user files using new system # TODO: add method to decrypt all user files using new system # TODO: add method to encrypt all user files using old system @@ -152,50 +154,55 @@ class Util { } /** - * @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 + * @brief Get the blowfish encryption handeler for a key + * @param $key string (optional) + * @return Crypt_Blowfish blowfish object * * if the key is left out, the default handeler will be used */ - public function getLegacyKey( $login, $passphrase ) { - - OC_FileProxy::$enabled = false; - - if ( - $login - and $passphrase - and $key = $this->view->file_get_contents( '/' . $login . '/encryption.key' ) - ) { + public function getBlowfish( $key = '' ) { + + if ( $key ) { - OC_FileProxy::$enabled = true; + return new \Crypt_Blowfish( $key ); - return $this->legacyDecrypt( $key, $passphrase ); - } else { - OC_FileProxy::$enabled = true; - return false; - + } } /** - * @brief Get the blowfish encryption handeler for a key - * @param $key string (optional) - * @return Crypt_Blowfish blowfish object + * @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 getBlowfish( $key = '' ) { - - if( $key ){ + public function getLegacyKey( $passphrase ) { + + //OC_FileProxy::$enabled = false; + + if ( + $passphrase + and $key = $this->view->file_get_contents( '/encryption.key' ) + ) { - return new Crypt_Blowfish($key); + //OC_FileProxy::$enabled = true; + if ( $this->legacyKey = $this->legacyDecrypt( $key, $passphrase ) ) { + + return true; + + } else { + + return false; + + } + } else { return false; @@ -212,9 +219,12 @@ class Util { * * This function encrypts an content */ - public static function legacyEncrypt( $content, $key='') { - $bf = self::getBlowfish($key); - return $bf->encrypt($content); + public function legacyEncrypt( $content, $passphrase = '' ) { + + $bf = $this->getBlowfish( $passphrase ); + + return $bf->encrypt( $content ); + } /** @@ -225,9 +235,9 @@ class Util { * * This function decrypts an content */ - public static function legacyDecrypt( $content, $key = '' ) { + public function legacyDecrypt( $content, $passphrase = '' ) { - $bf = $this->getBlowfish( $key ); + $bf = $this->getBlowfish( $passphrase ); $data = $bf->decrypt( $content ); diff --git a/apps/files_encryption/tests/encryption.php b/apps/files_encryption/tests/encryption.php index 9246e715262..ed3b65b1797 100644 --- a/apps/files_encryption/tests/encryption.php +++ b/apps/files_encryption/tests/encryption.php @@ -8,6 +8,7 @@ */ require realpath( dirname(__FILE__).'/../lib/crypt.php' ); +require realpath( dirname(__FILE__).'/../lib/util.php' ); //require realpath( dirname(__FILE__).'/../../../lib/filecache.php' ); class Test_Encryption extends UnitTestCase { @@ -16,6 +17,7 @@ class Test_Encryption extends UnitTestCase { // set content for encrypting / decrypting in tests $this->data = realpath( dirname(__FILE__).'/../lib/crypt.php' ); + $this->legacyData = realpath( dirname(__FILE__).'/legacy-text.txt' ); $this->legacyEncryptedData = realpath( dirname(__FILE__).'/legacy-encrypted-text.txt' ); } @@ -112,19 +114,6 @@ class Test_Encryption extends UnitTestCase { } -// // 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' ) ); -// -// } - function testMultiKeyEncrypt() { # TODO: search in keyfile for actual content as IV will ensure this test always passes diff --git a/apps/files_encryption/tests/util.php b/apps/files_encryption/tests/util.php new file mode 100644 index 00000000000..f24b1642052 --- /dev/null +++ b/apps/files_encryption/tests/util.php @@ -0,0 +1,72 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +require realpath( dirname(__FILE__).'/../lib/crypt.php' ); +require realpath( dirname(__FILE__).'/../lib/util.php' ); + +class Test_Encryption extends UnitTestCase { + + function setUp() { + + // set content for encrypting / decrypting in tests + $this->data = realpath( dirname(__FILE__).'/../lib/crypt.php' ); + $this->legacyData = realpath( dirname(__FILE__).'/legacy-text.txt' ); + $this->legacyEncryptedData = realpath( dirname(__FILE__).'/legacy-encrypted-text.txt' ); + + $this->view = new OC_FilesystemView( '/admin' ); + + } + + function tearDown(){} + +// // 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 ); +// +// } + +} \ No newline at end of file -- cgit v1.2.3 From d766ca8b19c8ac93ac42650cf98cca66ad947309 Mon Sep 17 00:00:00 2001 From: Sam Tuke Date: Wed, 25 Jul 2012 15:33:25 +0100 Subject: Changed util class methods to use / create public keys in single shared public-keys directory, and group encryption-related user files --- apps/files_encryption/lib/util.php | 83 +++++++++++++++++++------------------- 1 file changed, 42 insertions(+), 41 deletions(-) (limited to 'apps/files_encryption/lib') diff --git a/apps/files_encryption/lib/util.php b/apps/files_encryption/lib/util.php index c7d9ec07d6e..e876e886c42 100644 --- a/apps/files_encryption/lib/util.php +++ b/apps/files_encryption/lib/util.php @@ -32,7 +32,10 @@ namespace OCA_Encryption; /** - * Class for utilities relating to encrypted file storage system + * @brief Class for utilities relating to encrypted file storage system + * @param $view OC_FilesystemView object, expected to have OC '/' as root path + * @param $client flag indicating status of client side encryption. Currently + * unused, likely to become obsolete shortly */ class Util { @@ -41,10 +44,12 @@ class Util { # DONE: add method to check if file is encrypted using old system # DONE: add method to fetch legacy key # DONE: add method to decrypt legacy encrypted data + # TODO: add method to encrypt all user files using new system # TODO: add method to decrypt all user files using new system # TODO: add method to encrypt all user files using old system # TODO: add method to decrypt all user files using old system + # TODO: fix / test the crypt stream proxy class # TODO: add support for optional recovery user in case of lost passphrase / keys # TODO: add admin optional required long passphrase for users @@ -60,26 +65,25 @@ class Util { 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 ) { + public function __construct( \OC_FilesystemView $view, $userId, $client = false ) { $this->view = $view; + $this->userId = $userId; $this->client = $client; + $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 } 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' ) + !$this->view->file_exists( $this->keyfilesPath ) + or !$this->view->file_exists( $this->publicKeyPath ) + or !$this->view->file_exists( $this->privateKeyPath ) ) { return false; @@ -93,61 +97,58 @@ class Util { } /** - * @brief Sets up encryption folders and keys for a user + * @brief Sets up user folders and keys for serverside encryption * @param $passphrase passphrase to encrypt server-stored private key with */ - public function setup( $passphrase = null ) { - - $publicKeyFileName = 'encryption.public.key'; - $privateKeyFileName = 'encryption.private.key'; + public function setupServerSide( $passphrase = null ) { // 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 ); + \OC_Log::write( $this->appInfo['name'], 'File encryption for user "' . $this->userId . '" will be set up' , \OC_Log::INFO ); - // Create mirrored keyfile directory - if( !$this->view->file_exists( '/' . 'keyfiles' ) ) { + // Create shared public key directory + if( !$this->view->file_exists( $this->publicKeyDir ) ) { + + $this->view->mkdir( $this->publicKeyDir ); + + } - $this->view->mkdir( '/'. 'keyfiles' ); + // Create encryption app directory + if( !$this->view->file_exists( $this->encryptionDir ) ) { + + $this->view->mkdir( $this->encryptionDir ); } - // Create keypair directory - if( !$this->view->file_exists( '/'. 'keypair' ) ) { + // Create mirrored keyfile directory + if( !$this->view->file_exists( $this->keyfilesPath ) ) { - $this->view->mkdir( '/'. 'keypair' ); + $this->view->mkdir( $this->keyfilesPath ); } // Create user keypair if ( - !$this->view->file_exists( '/'. 'keypair'. '/' . $publicKeyFileName ) - or !$this->view->file_exists( '/'. 'keypair'. '/' . $privateKeyFileName ) + !$this->view->file_exists( $this->publicKeyPath ) + or !$this->view->file_exists( $this->privateKeyPath ) ) { // Generate keypair $keypair = Crypt::createKeypair(); + + \OC_FileProxy::$enabled = false; // Save public key - $this->view->file_put_contents( '/'. 'keypair'. '/' . $publicKeyFileName, $keypair['publicKey'] ); + $this->view->file_put_contents( $this->publicKeyPath, $keypair['publicKey'] ); - if ( $this->client == false ) { - - # TODO: Use proper IV in encryption - - // Encrypt private key with user pwd as passphrase - $encryptedPrivateKey = Crypt::symmetricEncryptFileContent( $keypair['privateKey'], $passphrase ); - - // $iv = openssl_random_pseudo_bytes(16); - $this->view->file_put_contents( '/'. 'keypair'. '/' . $privateKeyFileName, $encryptedPrivateKey ); - - } else { + // Encrypt private key with user pwd as passphrase + $encryptedPrivateKey = Crypt::symmetricEncryptFileContent( $keypair['privateKey'], $passphrase ); - # 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 + // Save private key + $this->view->file_put_contents( $this->privateKeyPath, $encryptedPrivateKey ); - } + \OC_FileProxy::$enabled = true; } -- cgit v1.2.3 From 6d1ed388c07329580251a91b3d03b8963b7cd884 Mon Sep 17 00:00:00 2001 From: Bjoern Schiessle Date: Wed, 25 Jul 2012 16:59:55 +0200 Subject: keymanager class for basic operations to store and retrieve keys --- apps/files_encryption/lib/keymanager.php | 110 +++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 apps/files_encryption/lib/keymanager.php (limited to 'apps/files_encryption/lib') diff --git a/apps/files_encryption/lib/keymanager.php b/apps/files_encryption/lib/keymanager.php new file mode 100644 index 00000000000..32ee77bb90c --- /dev/null +++ b/apps/files_encryption/lib/keymanager.php @@ -0,0 +1,110 @@ + + * + * 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 . + * + */ + +namespace OCA_Encryption; + +/* + * This class provides basic operations to read/write encryption keys from/to the filesystem + */ +class Keymanager { + + + /* + * @brief retrieve private key from a user + * + * @param string user name + * @return string private key or false + */ + public static function getPrivateKey($user) { + $privateKeyStorage = \OCP\Config::getSystemValue('datadirectory').'/'.$user.'/files_encryption/'; + $view = new \OC_FilesystemView($privateKeyStorage); + return $view->file_get_contents($user.'.private.key'); + } + + /* + * @brief retrieve public key from a user + * + * @param string user name + * @return string private key or false + */ + public static function getPublicKey($user) { + $publicKeyStorage = \OCP\Config::getSystemValue('datadirectory').'/public-keys/'; + $view = $view = new \OC_FilesystemView($publicKeyStorage); + return $view->file_get_contents($user.'.public.key'); + } + + /* + * @brief retrieve file encryption key + * + * @param string file name + * @param string user name of the file owner + * @return string file key or false + */ + public static function getFileKey($user, $file) { + $fileKeyStorage = \OCP\Config::getSystemValue('datadirectory').'/'.$user.'/files_encryption/keyfiles/'; + $view = new \OC_FilesystemView($fileKeyStorage); + return $view->file_get_contents($file.'.key'); + } + + /* + * @brief store private key from a user + * + * @param string user name + * @param string key + * @return bool true/false + */ + public static function setPrivateKey($user, $key) { + $privateKeyStorage = \OCP\Config::getSystemValue('datadirectory').'/'.$user.'/files_encryption/'; + $view = new \OC_FilesystemView($privateKeyStorage); + return $view->file_put_contents($user.'.private.key', $key); + } + + + /* + * @brief store public key from a user + * + * @param string user name + * @param string key + * @return bool true/false + */ + public static function setPublicKey($user, $key) { + $publicKeyStorage = \OCP\Config::getSystemValue('datadirectory').'/public-keys/'; + $view = new \OC_FilesystemView($publicKeyStorage); + return $view->file_put_contents($user.'.public.key', $key); + } + + /* + * @brief store file encryption key + * + * @param string user name of the file owner + * @param string file name + * @param string key + * @return bool true/false + */ + public static function setFileKey($user, $file, $key) { + $fileKeyStorage = \OCP\Config::getSystemValue('datadirectory').'/'.$user.'/files_encryption/keyfiles/'; + $view = new \OC_FilesystemView($fileKeyStorage); + return $view->file_put_contents($file.'.key', $key); + + } + +} \ No newline at end of file -- cgit v1.2.3 From 66b461629be6d1585ae0171b9128ad19d2c85bfb Mon Sep 17 00:00:00 2001 From: Sam Tuke Date: Wed, 25 Jul 2012 16:25:24 +0100 Subject: Started implementation of new encyryption classes into the encryption proxy --- apps/files_encryption/hooks/hooks.php | 14 ++++++++------ apps/files_encryption/lib/proxy.php | 23 +++++++++++++++-------- 2 files changed, 23 insertions(+), 14 deletions(-) (limited to 'apps/files_encryption/lib') diff --git a/apps/files_encryption/hooks/hooks.php b/apps/files_encryption/hooks/hooks.php index 89d526b7044..a8304261e47 100644 --- a/apps/files_encryption/hooks/hooks.php +++ b/apps/files_encryption/hooks/hooks.php @@ -30,19 +30,21 @@ class Hooks { public static function login( $params ){ - $view = new \OC_FilesystemView( '/' . $params['uid'] ); + $view = new \OC_FilesystemView( '/' ); - $storage = new Storage( $view ); + $storage = new Storage( $view, $params['uid'] ); if ( !$storage->ready() ) { return $storage->setup( $params['password'] ); - } else { - - return true; - } + + $_SESSION['enckey'] = OC_Crypt::decrypt($key, $password); + + return true; + + } } diff --git a/apps/files_encryption/lib/proxy.php b/apps/files_encryption/lib/proxy.php index 3f9b86b988b..080fd04cd7c 100644 --- a/apps/files_encryption/lib/proxy.php +++ b/apps/files_encryption/lib/proxy.php @@ -26,9 +26,11 @@ * transparent encryption */ -class OC_FileProxy_Encryption extends OC_FileProxy{ - private static $blackList=null; //mimetypes blacklisted from encryption - private static $enableEncryption=null; +class OC_FileProxy_Encryption extends OC_FileProxy { + + private static $blackList = null; //mimetypes blacklisted from encryption + + private static $enableEncryption = null; /** * Check if a file requires encryption @@ -97,7 +99,7 @@ class OC_FileProxy_Encryption extends OC_FileProxy{ $size = strlen( $data ); - $data = Crypt::blockEncrypt( $data ); + $data = OCA_Encryption\Crypt::symmetricEncryptFileContent( $data, '', $cached['size'] ); OC_FileCache::put( $path, array( 'encrypted'=>true, 'size' => $size ), '' ); @@ -105,11 +107,16 @@ class OC_FileProxy_Encryption extends OC_FileProxy{ } } - public function postFile_get_contents($path,$data){ - if(self::isEncrypted($path)){ - $cached=OC_FileCache_Cached::get($path,''); - $data=OC_Crypt::blockDecrypt($data,'',$cached['size']); + public function postFile_get_contents( $path, $data ) { + + if ( self::isEncrypted( $path ) ) { + + $cached = OC_FileCache_Cached::get( $path, '' ); + + $data = OCA_Encryption\Crypt::symmetricDecryptFileContent( $data, '' ); + } + return $data; } -- cgit v1.2.3 From e6de086fb66b029d70d1e24db5224f236e43198d Mon Sep 17 00:00:00 2001 From: Sam Tuke Date: Wed, 25 Jul 2012 16:51:48 +0100 Subject: Fixed various bugs in hooks class Fixed documentation syntax in keymanager --- apps/files_encryption/appinfo/app.php | 11 ++-- apps/files_encryption/hooks/hooks.php | 16 +++-- apps/files_encryption/lib/keymanager.php | 104 +++++++++++++++---------------- 3 files changed, 71 insertions(+), 60 deletions(-) (limited to 'apps/files_encryption/lib') diff --git a/apps/files_encryption/appinfo/app.php b/apps/files_encryption/appinfo/app.php index 679d0b95edc..969c824cfda 100644 --- a/apps/files_encryption/appinfo/app.php +++ b/apps/files_encryption/appinfo/app.php @@ -1,16 +1,19 @@ ready() ) { + if ( !$util->ready() ) { - return $storage->setup( $params['password'] ); + return $util->setup( $params['password'] ); } - $_SESSION['enckey'] = OC_Crypt::decrypt($key, $password); + $encryptedKey = Keymanager::getPrivateKey( $params['uid'] ); + + $_SESSION['enckey'] = Crypt::symmetricEncryptFileContent( $encryptedKey, $params['password'] ); return true; diff --git a/apps/files_encryption/lib/keymanager.php b/apps/files_encryption/lib/keymanager.php index 32ee77bb90c..a75242c7a2b 100644 --- a/apps/files_encryption/lib/keymanager.php +++ b/apps/files_encryption/lib/keymanager.php @@ -1,5 +1,5 @@ -. * - */ - -namespace OCA_Encryption; - -/* - * This class provides basic operations to read/write encryption keys from/to the filesystem - */ -class Keymanager { - - - /* - * @brief retrieve private key from a user - * - * @param string user name - * @return string private key or false - */ - public static function getPrivateKey($user) { + */ + +namespace OCA_Encryption; + +/** + * This class provides basic operations to read/write encryption keys from/to the filesystem + */ +class Keymanager { + + + /** + * @brief retrieve private key from a user + * + * @param string user name + * @return string private key or false + */ + public static function getPrivateKey($user) { $privateKeyStorage = \OCP\Config::getSystemValue('datadirectory').'/'.$user.'/files_encryption/'; - $view = new \OC_FilesystemView($privateKeyStorage); - return $view->file_get_contents($user.'.private.key'); - } - - /* + $view = new \OC_FilesystemView($privateKeyStorage); + return $view->file_get_contents($user.'.private.key'); + } + + /** * @brief retrieve public key from a user * * @param string user name * @return string private key or false */ public static function getPublicKey($user) { - $publicKeyStorage = \OCP\Config::getSystemValue('datadirectory').'/public-keys/'; - $view = $view = new \OC_FilesystemView($publicKeyStorage); + $publicKeyStorage = \OCP\Config::getSystemValue('datadirectory').'/public-keys/'; + $view = $view = new \OC_FilesystemView($publicKeyStorage); return $view->file_get_contents($user.'.public.key'); - } - - /* + } + + /** * @brief retrieve file encryption key * - * @param string file name - * @param string user name of the file owner + * @param string file name + * @param string user name of the file owner * @return string file key or false */ public static function getFileKey($user, $file) { - $fileKeyStorage = \OCP\Config::getSystemValue('datadirectory').'/'.$user.'/files_encryption/keyfiles/'; - $view = new \OC_FilesystemView($fileKeyStorage); + $fileKeyStorage = \OCP\Config::getSystemValue('datadirectory').'/'.$user.'/files_encryption/keyfiles/'; + $view = new \OC_FilesystemView($fileKeyStorage); return $view->file_get_contents($file.'.key'); - } - - /* + } + + /** * @brief store private key from a user * - * @param string user name + * @param string user name * @param string key * @return bool true/false - */ + */ public static function setPrivateKey($user, $key) { $privateKeyStorage = \OCP\Config::getSystemValue('datadirectory').'/'.$user.'/files_encryption/'; - $view = new \OC_FilesystemView($privateKeyStorage); + $view = new \OC_FilesystemView($privateKeyStorage); return $view->file_put_contents($user.'.private.key', $key); - } - - - /* + } + + + /** * @brief store public key from a user * * @param string user name * @param string key * @return bool true/false - */ - public static function setPublicKey($user, $key) { + */ + public static function setPublicKey($user, $key) { $publicKeyStorage = \OCP\Config::getSystemValue('datadirectory').'/public-keys/'; $view = new \OC_FilesystemView($publicKeyStorage); return $view->file_put_contents($user.'.public.key', $key); } - - /* + + /** * @brief store file encryption key - * - * @param string user name of the file owner + * + * @param string user name of the file owner * @param string file name * @param string key * @return bool true/false */ - public static function setFileKey($user, $file, $key) { + public static function setFileKey($user, $file, $key) { $fileKeyStorage = \OCP\Config::getSystemValue('datadirectory').'/'.$user.'/files_encryption/keyfiles/'; $view = new \OC_FilesystemView($fileKeyStorage); return $view->file_put_contents($file.'.key', $key); - } - + } + } \ No newline at end of file -- cgit v1.2.3 From adf5c953ddc0aeea15824e0e677d4e1fb753d554 Mon Sep 17 00:00:00 2001 From: Sam Tuke Date: Wed, 25 Jul 2012 16:56:52 +0100 Subject: Fixed use of OCFSV in getPrivateKey() --- apps/files_encryption/lib/keymanager.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'apps/files_encryption/lib') diff --git a/apps/files_encryption/lib/keymanager.php b/apps/files_encryption/lib/keymanager.php index a75242c7a2b..d78db132b16 100644 --- a/apps/files_encryption/lib/keymanager.php +++ b/apps/files_encryption/lib/keymanager.php @@ -34,10 +34,11 @@ class Keymanager { * @param string user name * @return string private key or false */ - public static function getPrivateKey($user) { - $privateKeyStorage = \OCP\Config::getSystemValue('datadirectory').'/'.$user.'/files_encryption/'; - $view = new \OC_FilesystemView($privateKeyStorage); - return $view->file_get_contents($user.'.private.key'); + public static function getPrivateKey( $user ) { + + $view = new \OC_FilesystemView( '/' . $user . '/' . 'files_encryption' . '/' ); + + return $view->file_get_contents( $user.'.private.key' ); } /** -- cgit v1.2.3 From 9f51841c57ca96eb7ce518dde9c6d35c905110c6 Mon Sep 17 00:00:00 2001 From: Sam Tuke Date: Wed, 25 Jul 2012 18:28:56 +0100 Subject: Mainly work on implementing new encryption system (+ keyfile handling) into proxy classs --- apps/files_encryption/appinfo/app.php | 16 ++++++-- apps/files_encryption/hooks/hooks.php | 1 - apps/files_encryption/lib/keymanager.php | 24 +++++++----- apps/files_encryption/lib/proxy.php | 65 ++++++++++++++++++++------------ apps/files_encryption/lib/util.php | 9 +++-- 5 files changed, 73 insertions(+), 42 deletions(-) (limited to 'apps/files_encryption/lib') diff --git a/apps/files_encryption/appinfo/app.php b/apps/files_encryption/appinfo/app.php index 969c824cfda..1a4021e9395 100644 --- a/apps/files_encryption/appinfo/app.php +++ b/apps/files_encryption/appinfo/app.php @@ -5,18 +5,26 @@ 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['OC_CryptStream'] = 'apps/files_encryption/lib/cryptstream.php'; -OC::$CLASSPATH['OC_FileProxy_Encryption'] = 'apps/files_encryption/lib/proxy.php'; +OC::$CLASSPATH['OCA_Encryption\Proxy'] = 'apps/files_encryption/lib/proxy.php'; -//OC_FileProxy::register(new OC_FileProxy_Encryption()); +OC_FileProxy::register(new OCA_Encryption\Proxy()); OCP\Util::connectHook('OC_User','post_login','OCA_Encryption\Hooks','login'); stream_wrapper_register('crypt','OC_CryptStream'); -if( !isset($_SESSION['enckey']) and OCP\User::isLoggedIn() ){//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() +) { + + // 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.'/'); + exit(); + } -OCP\App::registerAdmin('files_encryption', 'settings'); +OCP\App::registerAdmin('files_encryption', 'settings'); \ No newline at end of file diff --git a/apps/files_encryption/hooks/hooks.php b/apps/files_encryption/hooks/hooks.php index 70bbbcf4789..654686208fa 100644 --- a/apps/files_encryption/hooks/hooks.php +++ b/apps/files_encryption/hooks/hooks.php @@ -52,7 +52,6 @@ class Hooks { return true; - } } diff --git a/apps/files_encryption/lib/keymanager.php b/apps/files_encryption/lib/keymanager.php index d78db132b16..e4462796f0d 100644 --- a/apps/files_encryption/lib/keymanager.php +++ b/apps/files_encryption/lib/keymanager.php @@ -27,6 +27,7 @@ namespace OCA_Encryption; */ class Keymanager { + # TODO: Try and get rid of username dependencies as these methods need to be used in a proxy class that doesn't have username access /** * @brief retrieve private key from a user @@ -36,9 +37,9 @@ class Keymanager { */ public static function getPrivateKey( $user ) { - $view = new \OC_FilesystemView( '/' . $user . '/' . 'files_encryption' . '/' ); + $view = new \OC_FilesystemView( '/' . $user . '/' . 'files_encryption' ); - return $view->file_get_contents( $user.'.private.key' ); + return $view->file_get_contents( '/' . $user.'.private.key' ); } /** @@ -96,15 +97,20 @@ class Keymanager { /** * @brief store file encryption key * - * @param string user name of the file owner - * @param string file name - * @param string key + * @param string $userId name of the file owner + * @param string $path relative path of the file, including filename + * @param string $key * @return bool true/false */ - public static function setFileKey($user, $file, $key) { - $fileKeyStorage = \OCP\Config::getSystemValue('datadirectory').'/'.$user.'/files_encryption/keyfiles/'; - $view = new \OC_FilesystemView($fileKeyStorage); - return $view->file_put_contents($file.'.key', $key); + public static function setFileKey( $userId, $path, $key ) { + + \OC_FileProxy::$enabled = false; + + $view = new \OC_FilesystemView( '/' . $userId . '/' . 'files_encryption' ); + + return $view->file_put_contents( '/' . $path . '.key', $key ); + + \OC_FileProxy::$enabled = true; } diff --git a/apps/files_encryption/lib/proxy.php b/apps/files_encryption/lib/proxy.php index 080fd04cd7c..53ed05d2c3b 100644 --- a/apps/files_encryption/lib/proxy.php +++ b/apps/files_encryption/lib/proxy.php @@ -3,8 +3,9 @@ /** * ownCloud * -* @author Robin Appelman -* @copyright 2011 Robin Appelman icewind1991@gmail.com +* @author Sam Tuke, Robin Appelman +* @copyright 2012 Sam Tuke samtuke@owncloud.com, 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 @@ -26,7 +27,9 @@ * transparent encryption */ -class OC_FileProxy_Encryption extends OC_FileProxy { +namespace OCA_Encryption; + +class Proxy extends \OC_FileProxy { private static $blackList = null; //mimetypes blacklisted from encryption @@ -43,7 +46,7 @@ class OC_FileProxy_Encryption extends OC_FileProxy { if ( is_null( self::$enableEncryption ) ) { - self::$enableEncryption = ( OCP\Config::getAppValue( 'files_encryption', 'enable_encryption', 'true' ) == 'true' ); + self::$enableEncryption = ( \OCP\Config::getAppValue( 'files_encryption', 'enable_encryption', 'true' ) == 'true' ); } @@ -55,11 +58,11 @@ class OC_FileProxy_Encryption extends OC_FileProxy { 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' ) ); + 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( Crypt::isEncryptedContent( $path ) ) { return true; @@ -84,7 +87,7 @@ class OC_FileProxy_Encryption extends OC_FileProxy { private static function isEncrypted( $path ){ // Fetch all file metadata from DB - $metadata = OC_FileCache_Cached::get( $path, '' ); + $metadata = \OC_FileCache_Cached::get( $path, '' ); // Return encryption status return isset( $metadata['encrypted'] ) and ( bool )$metadata['encrypted']; @@ -95,13 +98,24 @@ class OC_FileProxy_Encryption extends OC_FileProxy { if ( self::shouldEncrypt( $path ) ) { - if ( !is_resource( $data ) ) {//stream put contents should have been converter to fopen + if ( !is_resource( $data ) ) { //stream put contents should have been converter to fopen + // Set the filesize for userland, before encrypting $size = strlen( $data ); - $data = OCA_Encryption\Crypt::symmetricEncryptFileContent( $data, '', $cached['size'] ); + // Encrypt plain data and fetch key + $encrypted = Crypt::symmetricEncryptFileContentKeyfile( $data, $_SESSION['enckey'] ); + + // Replace plain content with encrypted content by reference + $data = $encrypted['encrypted']; + + # TODO: check if file is in subdirectories, and if so, create those parent directories. Or else monitor creation of directories using hooks to ensure path will always exist (what about existing directories when encryption is enabled?) - OC_FileCache::put( $path, array( 'encrypted'=>true, 'size' => $size ), '' ); + // Save keyfile for newly encrypted file in parallel directory + Keymanager::setFileKey( \OCP\USER::getUser(), $path, $encrypted['key'] ); + + // Update the file cache with file info + \OC_FileCache::put( $path, array( 'encrypted'=>true, 'size' => $size ), '' ); } } @@ -109,11 +123,11 @@ class OC_FileProxy_Encryption extends OC_FileProxy { public function postFile_get_contents( $path, $data ) { - if ( self::isEncrypted( $path ) ) { - - $cached = OC_FileCache_Cached::get( $path, '' ); + if ( Crypt::isEncryptedContent( $data ) ) { + trigger_error('best'); + $cached = \OC_FileCache_Cached::get( $path, '' ); - $data = OCA_Encryption\Crypt::symmetricDecryptFileContent( $data, '' ); + $data = Crypt::symmetricDecryptFileContent( $data, $_SESSION['enckey'] ); } @@ -121,21 +135,22 @@ class OC_FileProxy_Encryption extends OC_FileProxy { } public function postFopen($path,&$result){ + if(!$result){ return $result; } $meta=stream_get_meta_data($result); - if(self::isEncrypted($path)){ + if(Crypt::isEncryptedContent($path)){ fclose($result); $result=fopen('crypt://'.$path,$meta['mode']); }elseif(self::shouldEncrypt($path) and $meta['mode']!='r' and $meta['mode']!='rb'){ - if(OC_Filesystem::file_exists($path) and OC_Filesystem::filesize($path)>0){ + if( \OC_Filesystem::file_exists( $path ) and \OC_Filesystem::filesize($path)>0){ //first encrypt the target file so we don't end up with a half encrypted file - OCP\Util::writeLog('files_encryption','Decrypting '.$path.' before writing',OCP\Util::DEBUG); + \OCP\Util::writeLog('files_encryption','Decrypting '.$path.' before writing', \OCP\Util::DEBUG); $tmp=fopen('php://temp'); - OCP\Files::streamCopy($result,$tmp); + \OCP\Files::streamCopy($result,$tmp); fclose($result); - OC_Filesystem::file_put_contents($path,$tmp); + \OC_Filesystem::file_put_contents($path,$tmp); fclose($tmp); } $result=fopen('crypt://'.$path,$meta['mode']); @@ -144,23 +159,23 @@ class OC_FileProxy_Encryption extends OC_FileProxy { } public function postGetMimeType($path,$mime){ - if(self::isEncrypted($path)){ - $mime=OCP\Files::getMimeType('crypt://'.$path,'w'); + if(Crypt::isEncryptedContent($path)){ + $mime = \OCP\Files::getMimeType('crypt://'.$path,'w'); } return $mime; } public function postStat($path,$data){ - if(self::isEncrypted($path)){ - $cached=OC_FileCache_Cached::get($path,''); + if(Crypt::isEncryptedContent($path)){ + $cached= \OC_FileCache_Cached::get($path,''); $data['size']=$cached['size']; } return $data; } public function postFileSize($path,$size){ - if(self::isEncrypted($path)){ - $cached=OC_FileCache_Cached::get($path,''); + if(Crypt::isEncryptedContent($path)){ + $cached = \OC_FileCache_Cached::get($path,''); return $cached['size']; }else{ return $size; diff --git a/apps/files_encryption/lib/util.php b/apps/files_encryption/lib/util.php index e876e886c42..ab58b4aa721 100644 --- a/apps/files_encryption/lib/util.php +++ b/apps/files_encryption/lib/util.php @@ -184,15 +184,16 @@ class Util { * if the key is left out, the default handeler will be used */ public function getLegacyKey( $passphrase ) { - - //OC_FileProxy::$enabled = false; + + // Disable proxies to prevent attempt to automatically decrypt key + OC_FileProxy::$enabled = false; if ( $passphrase and $key = $this->view->file_get_contents( '/encryption.key' ) ) { - //OC_FileProxy::$enabled = true; + OC_FileProxy::$enabled = true; if ( $this->legacyKey = $this->legacyDecrypt( $key, $passphrase ) ) { @@ -206,6 +207,8 @@ class Util { } else { + OC_FileProxy::$enabled = true; + return false; } -- cgit v1.2.3 From 3ab4ddd1da8a1d9c919a38c15770e14fbeda5ea2 Mon Sep 17 00:00:00 2001 From: Bjoern Schiessle Date: Thu, 26 Jul 2012 13:47:43 +0200 Subject: function to ask for the encryption mode (server side or client side). Needs to be implemented and integrated into the settings. --- apps/files_encryption/lib/crypt.php | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'apps/files_encryption/lib') diff --git a/apps/files_encryption/lib/crypt.php b/apps/files_encryption/lib/crypt.php index 668101014a2..090b1db0611 100644 --- a/apps/files_encryption/lib/crypt.php +++ b/apps/files_encryption/lib/crypt.php @@ -30,6 +30,17 @@ namespace OCA_Encryption; class Crypt { + /** + * @brief return encryption mode client or server side encryption + * @param string user name + * @return string 'client' or 'server' + */ + public static function mode($user) { + //TODO: allow user to set encryption mode and check the selection of the user + // for the moment I just return 'client' for test purposes + return 'client'; + } + /** * @brief Create a new encryption keypair * @return array publicKey, privatekey -- cgit v1.2.3 From bdb406916cf7306abf03a83e4fde81baf71d22bc Mon Sep 17 00:00:00 2001 From: Bjoern Schiessle Date: Thu, 26 Jul 2012 13:49:22 +0200 Subject: fixed path for filesystem view --- apps/files_encryption/lib/keymanager.php | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) (limited to 'apps/files_encryption/lib') diff --git a/apps/files_encryption/lib/keymanager.php b/apps/files_encryption/lib/keymanager.php index d78db132b16..de6db08ae18 100644 --- a/apps/files_encryption/lib/keymanager.php +++ b/apps/files_encryption/lib/keymanager.php @@ -48,8 +48,7 @@ class Keymanager { * @return string private key or false */ public static function getPublicKey($user) { - $publicKeyStorage = \OCP\Config::getSystemValue('datadirectory').'/public-keys/'; - $view = $view = new \OC_FilesystemView($publicKeyStorage); + $view = new \OC_FilesystemView( '/public-keys/' ); return $view->file_get_contents($user.'.public.key'); } @@ -61,8 +60,7 @@ class Keymanager { * @return string file key or false */ public static function getFileKey($user, $file) { - $fileKeyStorage = \OCP\Config::getSystemValue('datadirectory').'/'.$user.'/files_encryption/keyfiles/'; - $view = new \OC_FilesystemView($fileKeyStorage); + $view = new \OC_FilesystemView('/'.$user.'/files_encryption/keyfiles/'); return $view->file_get_contents($file.'.key'); } @@ -74,8 +72,7 @@ class Keymanager { * @return bool true/false */ public static function setPrivateKey($user, $key) { - $privateKeyStorage = \OCP\Config::getSystemValue('datadirectory').'/'.$user.'/files_encryption/'; - $view = new \OC_FilesystemView($privateKeyStorage); + $view = new \OC_FilesystemView('/'.$user.'/files_encryption/'); return $view->file_put_contents($user.'.private.key', $key); } @@ -88,8 +85,7 @@ class Keymanager { * @return bool true/false */ public static function setPublicKey($user, $key) { - $publicKeyStorage = \OCP\Config::getSystemValue('datadirectory').'/public-keys/'; - $view = new \OC_FilesystemView($publicKeyStorage); + $view = new \OC_FilesystemView('/public-keys/'); return $view->file_put_contents($user.'.public.key', $key); } @@ -102,8 +98,7 @@ class Keymanager { * @return bool true/false */ public static function setFileKey($user, $file, $key) { - $fileKeyStorage = \OCP\Config::getSystemValue('datadirectory').'/'.$user.'/files_encryption/keyfiles/'; - $view = new \OC_FilesystemView($fileKeyStorage); + $view = new \OC_FilesystemView('/'.$user.'/files_encryption/keyfiles/'); return $view->file_put_contents($file.'.key', $key); } -- cgit v1.2.3 From bb229f729114bcd20b861e8b118fd6c805b96b73 Mon Sep 17 00:00:00 2001 From: Bjoern Schiessle Date: Thu, 26 Jul 2012 17:19:55 +0200 Subject: write private/public key from the client to the server --- apps/files_encryption/lib/keymanager.php | 31 +++++++++++++---- lib/ocs.php | 58 ++++++++++++++++++++------------ 2 files changed, 62 insertions(+), 27 deletions(-) (limited to 'apps/files_encryption/lib') diff --git a/apps/files_encryption/lib/keymanager.php b/apps/files_encryption/lib/keymanager.php index f48047a692a..0bef3b74928 100644 --- a/apps/files_encryption/lib/keymanager.php +++ b/apps/files_encryption/lib/keymanager.php @@ -73,8 +73,16 @@ class Keymanager { * @return bool true/false */ public static function setPrivateKey($user, $key) { - $view = new \OC_FilesystemView('/'.$user.'/files_encryption/'); - return $view->file_put_contents($user.'.private.key', $key); + + \OC_FileProxy::$enabled = false; + + $view = new \OC_FilesystemView('/'.$user.'/files_encryption'); + if (!$view->file_exists('')) $view->mkdir(''); + $result = $view->file_put_contents($user.'.private.key', $key); + + \OC_FileProxy::$enabled = true; + + return $result; } @@ -86,8 +94,16 @@ class Keymanager { * @return bool true/false */ public static function setPublicKey($user, $key) { - $view = new \OC_FilesystemView('/public-keys/'); - return $view->file_put_contents($user.'.public.key', $key); + + \OC_FileProxy::$enabled = false; + + $view = new \OC_FilesystemView('/public-keys'); + if (!$view->file_exists('')) $view->mkdir(''); + $result = $view->file_put_contents($user.'.public.key', $key); + + \OC_FileProxy::$enabled = true; + + return $result; } /** @@ -103,10 +119,13 @@ class Keymanager { \OC_FileProxy::$enabled = false; $view = new \OC_FilesystemView( '/' . $userId . '/' . 'files_encryption' ); - - return $view->file_put_contents( '/' . $path . '.key', $key ); + $path_parts = pathinfo($path); + if (!$view->file_exists($path_parts['dirname'])) $view->mkdir($path_parts['dirname']); + $result = $view->file_put_contents( '/' . $path . '.key', $key ); \OC_FileProxy::$enabled = true; + + return $result; } } \ No newline at end of file diff --git a/lib/ocs.php b/lib/ocs.php index 9d30b062bce..5349053ad28 100644 --- a/lib/ocs.php +++ b/lib/ocs.php @@ -173,10 +173,20 @@ class OC_OCS { $user=$ex[$paracount-3]; OC_OCS::publicKeyGet($format,$user); + //keysetpublic + }elseif(($method=='post') and ($ex[$paracount-6] == 'v1.php') and ($ex[$paracount-5]=='cloud') and ($ex[$paracount-4] == 'user') and ($ex[$paracount-2] == 'publickey')){ + $user=$ex[$paracount-3]; + $key = self::readData('post', 'key', 'string'); + OC_OCS::publicKeySet($format,$user, $key); + // keygetprivate }elseif(($method=='get') and ($ex[$paracount-6] == 'v1.php') and ($ex[$paracount-5]=='cloud') and ($ex[$paracount-4] == 'user') and ($ex[$paracount-2] == 'privatekey')){ $user=$ex[$paracount-3]; OC_OCS::privateKeyGet($format,$user); + }elseif(($method=='post') and ($ex[$paracount-6] == 'v1.php') and ($ex[$paracount-5]=='cloud') and ($ex[$paracount-4] == 'user') and ($ex[$paracount-2] == 'privatekey')){ + $user=$ex[$paracount-3]; + $key = self::readData('post', 'key', 'string'); + OC_OCS::privateKeySet($format,$user, $key); // add more calls here @@ -678,20 +688,23 @@ class OC_OCS { * @param string $key * @return string xml/json */ - private static function publicKeySet($format, $user, $key) { + private static function publicKeySet($format, $user, $key) { $login=OC_OCS::checkpassword(); - if($login == $user) { - if(OC_User::userExists($user)){ - //TODO: SET public key - echo self::generateXml('', 'ok', 100, 'Public key uploaded'); - }else{ - echo self::generateXml('', 'fail', 300, 'User does not exist'); + if(($login==$user)) { + if(OC_App::isEnabled('files_encryption') && OCA_Encryption\Crypt::mode($user) === 'client') { + if (($key = OCA_Encryption\Keymanager::setPublicKey($user, $key))) { + echo self::generateXml('', 'ok', 100, ''); + } else { + echo self::generateXml('', 'fail', 404, 'could not add your public key to the key storage'); + } + } else { + echo self::generateXml('', 'fail', 300, 'Client side encryption not enabled for user ' . $user); } }else{ echo self::generateXml('', 'fail', 300, 'You don´t have permission to access this ressource.'); } } - + /** * get the private key of a user * @param string $format @@ -725,19 +738,22 @@ class OC_OCS { * @param string $key * @return string xml/json */ - private static function privateKeySet($format, $user, $key) { - $login=OC_OCS::checkpassword(); - if($login == $user) { - if(OC_User::userExists($user)){ - //TODO: SET private key - echo self::generateXml('', 'ok', 100, 'Private key uploaded'); - }else{ - echo self::generateXml('', 'fail', 300, 'User does not exist'); - } - }else{ - echo self::generateXml('', 'fail', 300, 'You don´t have permission to access this ressource.'); - } - } + private static function privateKeySet($format, $user, $key) { + $login=OC_OCS::checkpassword(); + if(($login==$user)) { + if(OC_App::isEnabled('files_encryption') && OCA_Encryption\Crypt::mode($user) === 'client') { + if (($key = OCA_Encryption\Keymanager::setPrivateKey($user, $key))) { + echo self::generateXml('', 'ok', 100, ''); + } else { + echo self::generateXml('', 'fail', 404, 'could not add your private key to the key storage'); + } + } else { + echo self::generateXml('', 'fail', 300, 'Client side encryption not enabled for user ' . $user); + } + }else{ + echo self::generateXml('', 'fail', 300, 'You don´t have permission to access this ressource.'); + } + } /** * get the encryption key of a file -- cgit v1.2.3 From f752a2760584614c160e4eaf5bf0fdeb938ee4ae Mon Sep 17 00:00:00 2001 From: Bjoern Schiessle Date: Fri, 27 Jul 2012 14:00:41 +0200 Subject: write keyfiles to server --- apps/files_encryption/lib/keymanager.php | 2 +- lib/filestorage/local.php | 2 +- lib/ocs.php | 42 +++++++++++++++++++++++--------- 3 files changed, 33 insertions(+), 13 deletions(-) (limited to 'apps/files_encryption/lib') diff --git a/apps/files_encryption/lib/keymanager.php b/apps/files_encryption/lib/keymanager.php index 0bef3b74928..ab50fba6977 100644 --- a/apps/files_encryption/lib/keymanager.php +++ b/apps/files_encryption/lib/keymanager.php @@ -118,7 +118,7 @@ class Keymanager { \OC_FileProxy::$enabled = false; - $view = new \OC_FilesystemView( '/' . $userId . '/' . 'files_encryption' ); + $view = new \OC_FilesystemView( '/' . $userId . '/' . 'files_encryption/keyfiles' ); $path_parts = pathinfo($path); if (!$view->file_exists($path_parts['dirname'])) $view->mkdir($path_parts['dirname']); $result = $view->file_put_contents( '/' . $path . '.key', $key ); diff --git a/lib/filestorage/local.php b/lib/filestorage/local.php index b2eba051515..f04cf7c1076 100644 --- a/lib/filestorage/local.php +++ b/lib/filestorage/local.php @@ -12,7 +12,7 @@ class OC_Filestorage_Local extends OC_Filestorage_Common{ } } public function mkdir($path){ - return @mkdir($this->datadir.$path); + return @mkdir($this->datadir.$path, 0755, true); } public function rmdir($path){ return @rmdir($this->datadir.$path); diff --git a/lib/ocs.php b/lib/ocs.php index 5349053ad28..e0c240d3306 100644 --- a/lib/ocs.php +++ b/lib/ocs.php @@ -183,11 +183,24 @@ class OC_OCS { }elseif(($method=='get') and ($ex[$paracount-6] == 'v1.php') and ($ex[$paracount-5]=='cloud') and ($ex[$paracount-4] == 'user') and ($ex[$paracount-2] == 'privatekey')){ $user=$ex[$paracount-3]; OC_OCS::privateKeyGet($format,$user); + + //keysetprivate }elseif(($method=='post') and ($ex[$paracount-6] == 'v1.php') and ($ex[$paracount-5]=='cloud') and ($ex[$paracount-4] == 'user') and ($ex[$paracount-2] == 'privatekey')){ $user=$ex[$paracount-3]; $key = self::readData('post', 'key', 'string'); OC_OCS::privateKeySet($format,$user, $key); - + + // keygetfiles + }elseif(($method=='get') and ($ex[$paracount-6] == 'v1.php') and ($ex[$paracount-5]=='cloud') and ($ex[$paracount-4] == 'user') and ($ex[$paracount-2] == 'filekey')){ + $user=$ex[$paracount-3]; + OC_OCS::fileKeyGet($format,$user); + + //keysetfiles + }elseif(($method=='post') and ($ex[$paracount-6] == 'v1.php') and ($ex[$paracount-5]=='cloud') and ($ex[$paracount-4] == 'user') and ($ex[$paracount-2] == 'filekey')){ + $user=$ex[$paracount-3]; + $key = self::readData('post', 'key', 'string'); + $file = self::readData('post', 'file', 'string'); + OC_OCS::fileKeySet($format,$user, $file, $key); // add more calls here // please document all the call in the draft spec @@ -766,7 +779,7 @@ class OC_OCS { $login=OC_OCS::checkpassword(); if(OC_Group::inGroup($login, 'admin') or ($login==$user)) { if(OC_User::userExists($user)){ - //TODO: GET file key + //TODO: GET file key, check needed if it is a shared file or not $xml=array(); $xml['key']="this is the key for $file"; $txt=OC_OCS::generatexml($format, 'ok', 100, '', $xml, 'cloud', '', 1, 0, 0); @@ -787,18 +800,25 @@ class OC_OCS { * @param string $key * @return string xml/json */ - private static function fileKeySet($format, $user, $file, $key) { + private static function fileKeySet($format, $user, $file, $key) { $login=OC_OCS::checkpassword(); - if($login == $user) { - if(OC_User::userExists($user)){ - //TODO: SET file key - echo self::generateXml('', 'ok', 100, 'File key uploaded'); - }else{ - echo self::generateXml('', 'fail', 300, 'User does not exist'); + if(($login==$user)) { + if(OC_App::isEnabled('files_encryption') && OCA_Encryption\Crypt::mode($user) === 'client') { + if (($key = OCA_Encryption\Keymanager::setFileKey($user, $file, $key))) { + // TODO: emit hook to move file from tmp location to the right place + echo self::generateXml('', 'ok', 100, ''); + return true; + } else { + echo self::generateXml('', 'fail', 404, 'could not write key file'); + } + } else { + echo self::generateXml('', 'fail', 300, 'Client side encryption not enabled for user ' . $user); } }else{ echo self::generateXml('', 'fail', 300, 'You don´t have permission to access this ressource.'); - } - } + } + //TODO: emit signal to remove file from tmp location + return false; + } } -- cgit v1.2.3 From 23391b3694120ee6a0aa443aab0a2eebd6400d11 Mon Sep 17 00:00:00 2001 From: Bjoern Schiessle Date: Mon, 30 Jul 2012 12:38:38 +0200 Subject: get/set key files, take shared files into account --- apps/files_encryption/lib/keymanager.php | 40 +++++++++++++++++++++++++++----- lib/ocs.php | 4 ++-- 2 files changed, 36 insertions(+), 8 deletions(-) (limited to 'apps/files_encryption/lib') diff --git a/apps/files_encryption/lib/keymanager.php b/apps/files_encryption/lib/keymanager.php index ab50fba6977..9dc95ea6764 100644 --- a/apps/files_encryption/lib/keymanager.php +++ b/apps/files_encryption/lib/keymanager.php @@ -60,9 +60,23 @@ class Keymanager { * @param string user name of the file owner * @return string file key or false */ - public static function getFileKey($user, $file) { + public static function getFileKey($userId, $path) { + + $keypath = ltrim($path, '/'); + $user = $userId; + + // 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 ('/'.$userId.'/files/'.$keypath, $userId)); + 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->file_get_contents($file.'.key'); + return $view->file_get_contents($keypath.'.key'); } /** @@ -115,13 +129,27 @@ class Keymanager { * @return bool true/false */ public static function setFileKey( $userId, $path, $key ) { - + \OC_FileProxy::$enabled = false; + + $targetpath = ltrim($path, '/'); + $user = $userId; + + // update $keytarget and $user if key belongs 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/'.$targetpath, $userId)); + if ($row = $result->fetchRow()){ + $targetpath = $row['source']; + $targetpath_parts=explode('/',$targetpath); + $user = $targetpath_parts[1]; + $targetpath = str_replace('/'.$user.'/files/', '', $targetpath); + } + + $view = new \OC_FilesystemView( '/' . $user . '/files_encryption/keyfiles' ); + $path_parts = pathinfo($targetpath); - $view = new \OC_FilesystemView( '/' . $userId . '/' . 'files_encryption/keyfiles' ); - $path_parts = pathinfo($path); if (!$view->file_exists($path_parts['dirname'])) $view->mkdir($path_parts['dirname']); - $result = $view->file_put_contents( '/' . $path . '.key', $key ); + $result = $view->file_put_contents( '/' . $targetpath . '.key', $key ); \OC_FileProxy::$enabled = true; diff --git a/lib/ocs.php b/lib/ocs.php index 526688b4309..16812196501 100644 --- a/lib/ocs.php +++ b/lib/ocs.php @@ -192,8 +192,8 @@ class OC_OCS { // keygetfiles }elseif(($method=='get') and ($ex[$paracount-7] == 'v1.php') and ($ex[$paracount-6]=='cloud') and ($ex[$paracount-5] == 'user') and ($ex[$paracount-3] == 'filekey')){ - $user=$ex[$paracount-4]; - $file = urldecode($ex[$paracount-2]); + $user=$ex[$paracount-4]; + $file = urldecode($ex[$paracount-2]); OC_OCS::fileKeyGet($format,$user, $file); //keysetfiles -- cgit v1.2.3 From ee15c40b1416507abbe6d0fb568bde77bb94e5f4 Mon Sep 17 00:00:00 2001 From: Bjoern Schiessle Date: Mon, 30 Jul 2012 12:43:17 +0200 Subject: comment added --- apps/files_encryption/lib/keymanager.php | 1 + 1 file changed, 1 insertion(+) (limited to 'apps/files_encryption/lib') diff --git a/apps/files_encryption/lib/keymanager.php b/apps/files_encryption/lib/keymanager.php index 9dc95ea6764..bafe8f1a5f0 100644 --- a/apps/files_encryption/lib/keymanager.php +++ b/apps/files_encryption/lib/keymanager.php @@ -143,6 +143,7 @@ class Keymanager { $targetpath_parts=explode('/',$targetpath); $user = $targetpath_parts[1]; $targetpath = str_replace('/'.$user.'/files/', '', $targetpath); + //TODO: check for write permission on shared file once the new sharing API is in place } $view = new \OC_FilesystemView( '/' . $user . '/files_encryption/keyfiles' ); -- cgit v1.2.3 From f6863f9e51ca1523e43c92a1ecbfe6f70c090494 Mon Sep 17 00:00:00 2001 From: Bjoern Schiessle Date: Tue, 31 Jul 2012 16:52:21 +0200 Subject: get encryption mode from the settings --- apps/files_encryption/lib/crypt.php | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) (limited to 'apps/files_encryption/lib') diff --git a/apps/files_encryption/lib/crypt.php b/apps/files_encryption/lib/crypt.php index 090b1db0611..fdace3e61dd 100644 --- a/apps/files_encryption/lib/crypt.php +++ b/apps/files_encryption/lib/crypt.php @@ -32,13 +32,19 @@ class Crypt { /** * @brief return encryption mode client or server side encryption - * @param string user name + * @param string user name (use system wide setting if name=null) * @return string 'client' or 'server' */ - public static function mode($user) { - //TODO: allow user to set encryption mode and check the selection of the user - // for the moment I just return 'client' for test purposes - return 'client'; + public static function mode($user=null) { + + $mode = \OC_Appconfig::getValue('files_encryption', 'mode', 'unknown'); + + if ($mode == 'unknown') { + error_log('no encryption mode configured'); + return false; + } + + return $mode; } /** -- cgit v1.2.3 From eebf76d34457df616d2b739582d9630f58df60b1 Mon Sep 17 00:00:00 2001 From: Sam Tuke Date: Tue, 31 Jul 2012 19:28:11 +0100 Subject: Implemented writing of keyfiles and directory hierarchy in proxy class Added crypt::findFiles() method for finding different types of files, ready for batch encrypting / decrypting Added comments to postFopen in proxy class --- apps/files_encryption/hooks/hooks.php | 4 +- apps/files_encryption/lib/crypt.php | 830 +++++++++++++++--------------- apps/files_encryption/lib/cryptstream.php | 83 ++- apps/files_encryption/lib/keymanager.php | 26 +- apps/files_encryption/lib/proxy.php | 86 +++- apps/files_encryption/lib/util.php | 96 +++- 6 files changed, 646 insertions(+), 479 deletions(-) (limited to 'apps/files_encryption/lib') diff --git a/apps/files_encryption/hooks/hooks.php b/apps/files_encryption/hooks/hooks.php index 80daf50a24d..57d379b9365 100644 --- a/apps/files_encryption/hooks/hooks.php +++ b/apps/files_encryption/hooks/hooks.php @@ -37,14 +37,14 @@ class Hooks { public static function login( $params ) { - if (Crypt::mode($params['uid'])=='server') { + if ( Crypt::mode( $params['uid'] ) == 'server' ) { $view = new \OC_FilesystemView( '/' ); $util = new Util( $view, $params['uid'] ); if ( !$util->ready()) { - + return $util->setupServerSide( $params['password'] ); } diff --git a/apps/files_encryption/lib/crypt.php b/apps/files_encryption/lib/crypt.php index 090b1db0611..cd658601845 100644 --- a/apps/files_encryption/lib/crypt.php +++ b/apps/files_encryption/lib/crypt.php @@ -1,420 +1,422 @@ -. - * - */ - -namespace OCA_Encryption; - -/** - * Class for common cryptography functionality - */ - -class Crypt { - +. + * + */ + +namespace OCA_Encryption; + +/** + * Class for common cryptography functionality + */ + +class Crypt { + /** - * @brief return encryption mode client or server side encryption + * @brief return encryption mode client or server side encryption * @param string user name * @return string 'client' or 'server' */ - public static function mode($user) { - //TODO: allow user to set encryption mode and check the selection of the user + public static function mode( $user ) { + + //TODO: allow user to set encryption mode and check the selection of the user // for the moment I just return 'client' for test purposes - return 'client'; - } - - /** - * @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 Check if a file's contents contains an IV and is symmetrically encrypted - * @return true / false - */ - public static function isEncryptedContent( $content ) { - - if ( !$content ) { - - return false; - - } - - // Fetch encryption metadata from end of file - $meta = substr( $content, -22 ); - - // Fetch IV from end of file - $iv = substr( $meta, -16 ); - - // Fetch identifier from start of metadata - $identifier = substr( $meta, 0, 6 ); - - if ( $identifier == '00iv00') { - - return true; - - } else { - - return false; - - } - - } - - /** - * @brief Check if a file is encrypted via legacy system - * @return true / false - */ - public static function isLegacyEncryptedContent( $content, $path ) { - - // Fetch all file metadata from DB - $metadata = \OC_FileCache_Cached::get( $content, '' ); - - // If a file is flagged with encryption in DB, but isn't a valid content + IV combination, it's probably using the legacy encryption system - if ( - $content - and isset( $metadata['encrypted'] ) - and $metadata['encrypted'] === true - and !self::isEncryptedContent( $content ) - ) { - - return true; - - } else { - - return false; - - } - - } - - /** - * @brief Symmetrically encrypt a string - * @returns encrypted file - */ - public static function encrypt( $plainContent, $iv, $passphrase = '' ) { - - if ( $encryptedContent = openssl_encrypt( $plainContent, 'AES-128-CFB', $passphrase, false, $iv ) ) { - - return $encryptedContent; - - } else { - - \OC_Log::write( 'Encryption library', 'Encryption (symmetric) of content failed' , \OC_Log::ERROR ); - - return false; - - } - - } - - /** - * @brief Symmetrically decrypt a string - * @returns decrypted file - */ - public static function decrypt( $encryptedContent, $iv, $passphrase ) { - - if ( $plainContent = openssl_decrypt( $encryptedContent, 'AES-128-CFB', $passphrase, false, $iv ) ) { - - return $plainContent; - - - } else { - - \OC_Log::write( 'Encryption library', 'Decryption (symmetric) of content failed' , \OC_Log::ERROR ); - - return false; - - } - - } - - /** - * @brief Symmetrically encrypts a string and returns keyfile content - * @param $plainContent content to be encrypted in keyfile - * @returns encrypted content combined with IV - * @note IV need not be specified, as it will be stored in the returned keyfile - * and remain accessible therein. - */ - public static function symmetricEncryptFileContent( $plainContent, $passphrase = '' ) { - - if ( !$plainContent ) { - - return false; - - } - - $iv = self::generateIv(); - - if ( $encryptedContent = self::encrypt( $plainContent, $iv, $passphrase ) ) { - - // Combine content to encrypt with IV identifier and actual IV - $combinedKeyfile = $encryptedContent . '00iv00' . $iv; - - return $combinedKeyfile; - - } else { - - \OC_Log::write( 'Encryption library', 'Encryption (symmetric) of keyfile content failed' , \OC_Log::ERROR ); - - return false; - - } - - } - - - /** - * @brief Symmetrically decrypts keyfile content - * @param string $source - * @param string $target - * @param string $key the decryption key - * - * This function decrypts a file - */ - public static function symmetricDecryptFileContent( $keyfileContent, $passphrase = '' ) { - - if ( !$keyfileContent ) { - - return false; - - } - - // Fetch IV from end of file - $iv = substr( $keyfileContent, -16 ); - - // Remove IV and IV identifier text to expose encrypted content - $encryptedContent = substr( $keyfileContent, 0, -22 ); - - if ( $plainContent = self::decrypt( $encryptedContent, $iv, $passphrase ) ) { - - return $plainContent; - - } else { - - \OC_Log::write( 'Encryption library', 'Decryption (symmetric) of keyfile content failed' , \OC_Log::ERROR ); - - return false; - - } - - } - - /** - * @brief Creates symmetric keyfile content using a generated key - * @param string $plainContent content to be encrypted - * @returns array keys: key, encrypted - * @note symmetricDecryptFileContent() can be used to decrypt files created using this method - * - * This function decrypts a file - */ - public static function symmetricEncryptFileContentKeyfile( $plainContent ) { - - $key = self::generateKey(); - - if( $encryptedContent = self::symmetricEncryptFileContent( $plainContent, $key ) ) { - - return array( - 'key' => $key - , 'encrypted' => $encryptedContent - ); - - } else { - - return false; - - } - - } - - /** - * @brief Create asymmetrically encrypted keyfile content using a generated key - * @param string $plainContent content to be encrypted - * @returns array keys: key, encrypted - * @note symmetricDecryptFileContent() can be used to decrypt files created using this method - * - * This function decrypts a file - */ - public static function multiKeyEncrypt( $plainContent, array $publicKeys ) { - - $envKeys = array(); - - if( openssl_seal( $plainContent, $sealed, $envKeys, $publicKeys ) ) { - - return array( - 'keys' => $envKeys - , 'encrypted' => $sealed - ); - - } else { - - return false; - - } - - } - - /** - * @brief Asymmetrically encrypt a file using multiple public keys - * @param string $plainContent content to be encrypted - * @returns array keys: key, encrypted - * @note symmetricDecryptFileContent() can be used to decrypt files created using this method - * - * This function decrypts a file - */ - public static function multiKeyDecrypt( $encryptedContent, $envKey, $privateKey ) { - - if ( !$encryptedContent ) { - - return false; - - } - - if ( openssl_open( $encryptedContent, $plainContent, $envKey, $privateKey ) ) { - - return $plainContent; - - } else { - - \OC_Log::write( 'Encryption library', 'Decryption (asymmetric) of sealed content failed' , \OC_Log::ERROR ); - - return false; - - } - - } - - /** - * @brief Asymetrically encrypt a string 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; - - } - - /** - * @brief Generate a pseudo random 1024kb ASCII key - * @returns $key Generated key - */ - public static function generateIv() { - - if ( $random = openssl_random_pseudo_bytes( 13, $strong ) ) { - - if ( !$strong ) { - - // If OpenSSL indicates randomness is insecure, log error - \OC_Log::write( 'Encryption library', 'Insecure symmetric key was generated using openssl_random_pseudo_bytes()' , \OC_Log::WARN ); - - } - - $iv = substr( base64_encode( $random ), 0, -4 ); - - return $iv; - - } else { - - return false; - - } - - } - - /** - * @brief Generate a pseudo random 1024kb ASCII key - * @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 ); - - // Generate key - if ( $key = base64_encode( openssl_random_pseudo_bytes( 768000, $strong ) ) ) { - - if ( !$strong ) { - - // If OpenSSL indicates randomness is insecure, log error - \OC_Log::write( 'Encryption library', 'Insecure symmetric key was generated using openssl_random_pseudo_bytes()' , \OC_Log::WARN ); - - } - - return $key; - - } else { - - return false; - - } - - } - - public static function changekeypasscode($oldPassword, $newPassword) { - if(OCP\User::isLoggedIn()){ - $username=OCP\USER::getUser(); - $view=new OC_FilesystemView('/'.$username); - - // read old key - $key=$view->file_get_contents('/encryption.key'); - - // decrypt key with old passcode - $key=OC_Crypt::decrypt($key, $oldPassword); - - // encrypt again with new passcode - $key=OC_Crypt::encrypt($key, $newPassword); - - // store the new key - $view->file_put_contents('/encryption.key', $key ); - } - } - -} - + return 'server'; + + } + + /** + * @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 Check if a file's contents contains an IV and is symmetrically encrypted + * @return true / false + */ + public static function isEncryptedContent( $content ) { + + if ( !$content ) { + + return false; + + } + + // Fetch encryption metadata from end of file + $meta = substr( $content, -22 ); + + // Fetch IV from end of file + $iv = substr( $meta, -16 ); + + // Fetch identifier from start of metadata + $identifier = substr( $meta, 0, 6 ); + + if ( $identifier == '00iv00') { + + return true; + + } else { + + return false; + + } + + } + + /** + * @brief Check if a file is encrypted via legacy system + * @return true / false + */ + public static function isLegacyEncryptedContent( $content, $path ) { + + // Fetch all file metadata from DB + $metadata = \OC_FileCache_Cached::get( $content, '' ); + + // If a file is flagged with encryption in DB, but isn't a valid content + IV combination, it's probably using the legacy encryption system + if ( + $content + and isset( $metadata['encrypted'] ) + and $metadata['encrypted'] === true + and !self::isEncryptedContent( $content ) + ) { + + return true; + + } else { + + return false; + + } + + } + + /** + * @brief Symmetrically encrypt a string + * @returns encrypted file + */ + public static function encrypt( $plainContent, $iv, $passphrase = '' ) { + + if ( $encryptedContent = openssl_encrypt( $plainContent, 'AES-128-CFB', $passphrase, false, $iv ) ) { + + return $encryptedContent; + + } else { + + \OC_Log::write( 'Encryption library', 'Encryption (symmetric) of content failed' , \OC_Log::ERROR ); + + return false; + + } + + } + + /** + * @brief Symmetrically decrypt a string + * @returns decrypted file + */ + public static function decrypt( $encryptedContent, $iv, $passphrase ) { + + if ( $plainContent = openssl_decrypt( $encryptedContent, 'AES-128-CFB', $passphrase, false, $iv ) ) { + + return $plainContent; + + + } else { + + \OC_Log::write( 'Encryption library', 'Decryption (symmetric) of content failed' , \OC_Log::ERROR ); + + return false; + + } + + } + + /** + * @brief Symmetrically encrypts a string and returns keyfile content + * @param $plainContent content to be encrypted in keyfile + * @returns encrypted content combined with IV + * @note IV need not be specified, as it will be stored in the returned keyfile + * and remain accessible therein. + */ + public static function symmetricEncryptFileContent( $plainContent, $passphrase = '' ) { + + if ( !$plainContent ) { + + return false; + + } + + $iv = self::generateIv(); + + if ( $encryptedContent = self::encrypt( $plainContent, $iv, $passphrase ) ) { + + // Combine content to encrypt with IV identifier and actual IV + $combinedKeyfile = $encryptedContent . '00iv00' . $iv; + + return $combinedKeyfile; + + } else { + + \OC_Log::write( 'Encryption library', 'Encryption (symmetric) of keyfile content failed' , \OC_Log::ERROR ); + + return false; + + } + + } + + + /** + * @brief Symmetrically decrypts keyfile content + * @param string $source + * @param string $target + * @param string $key the decryption key + * + * This function decrypts a file + */ + public static function symmetricDecryptFileContent( $keyfileContent, $passphrase = '' ) { + + if ( !$keyfileContent ) { + + return false; + + } + + // Fetch IV from end of file + $iv = substr( $keyfileContent, -16 ); + + // Remove IV and IV identifier text to expose encrypted content + $encryptedContent = substr( $keyfileContent, 0, -22 ); + + if ( $plainContent = self::decrypt( $encryptedContent, $iv, $passphrase ) ) { + + return $plainContent; + + } else { + + \OC_Log::write( 'Encryption library', 'Decryption (symmetric) of keyfile content failed' , \OC_Log::ERROR ); + + return false; + + } + + } + + /** + * @brief Creates symmetric keyfile content using a generated key + * @param string $plainContent content to be encrypted + * @returns array keys: key, encrypted + * @note symmetricDecryptFileContent() can be used to decrypt files created using this method + * + * This function decrypts a file + */ + public static function symmetricEncryptFileContentKeyfile( $plainContent ) { + + $key = self::generateKey(); + + if( $encryptedContent = self::symmetricEncryptFileContent( $plainContent, $key ) ) { + + return array( + 'key' => $key + , 'encrypted' => $encryptedContent + ); + + } else { + + return false; + + } + + } + + /** + * @brief Create asymmetrically encrypted keyfile content using a generated key + * @param string $plainContent content to be encrypted + * @returns array keys: key, encrypted + * @note symmetricDecryptFileContent() can be used to decrypt files created using this method + * + * This function decrypts a file + */ + public static function multiKeyEncrypt( $plainContent, array $publicKeys ) { + + $envKeys = array(); + + if( openssl_seal( $plainContent, $sealed, $envKeys, $publicKeys ) ) { + + return array( + 'keys' => $envKeys + , 'encrypted' => $sealed + ); + + } else { + + return false; + + } + + } + + /** + * @brief Asymmetrically encrypt a file using multiple public keys + * @param string $plainContent content to be encrypted + * @returns array keys: key, encrypted + * @note symmetricDecryptFileContent() can be used to decrypt files created using this method + * + * This function decrypts a file + */ + public static function multiKeyDecrypt( $encryptedContent, $envKey, $privateKey ) { + + if ( !$encryptedContent ) { + + return false; + + } + + if ( openssl_open( $encryptedContent, $plainContent, $envKey, $privateKey ) ) { + + return $plainContent; + + } else { + + \OC_Log::write( 'Encryption library', 'Decryption (asymmetric) of sealed content failed' , \OC_Log::ERROR ); + + return false; + + } + + } + + /** + * @brief Asymetrically encrypt a string 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; + + } + + /** + * @brief Generate a pseudo random 1024kb ASCII key + * @returns $key Generated key + */ + public static function generateIv() { + + if ( $random = openssl_random_pseudo_bytes( 13, $strong ) ) { + + if ( !$strong ) { + + // If OpenSSL indicates randomness is insecure, log error + \OC_Log::write( 'Encryption library', 'Insecure symmetric key was generated using openssl_random_pseudo_bytes()' , \OC_Log::WARN ); + + } + + $iv = substr( base64_encode( $random ), 0, -4 ); + + return $iv; + + } else { + + return false; + + } + + } + + /** + * @brief Generate a pseudo random 1024kb ASCII key + * @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 ); + + // Generate key + if ( $key = base64_encode( openssl_random_pseudo_bytes( 768000, $strong ) ) ) { + + if ( !$strong ) { + + // If OpenSSL indicates randomness is insecure, log error + \OC_Log::write( 'Encryption library', 'Insecure symmetric key was generated using openssl_random_pseudo_bytes()' , \OC_Log::WARN ); + + } + + return $key; + + } else { + + return false; + + } + + } + + public static function changekeypasscode($oldPassword, $newPassword) { + if(OCP\User::isLoggedIn()){ + $username=OCP\USER::getUser(); + $view=new OC_FilesystemView('/'.$username); + + // read old key + $key=$view->file_get_contents('/encryption.key'); + + // decrypt key with old passcode + $key=OC_Crypt::decrypt($key, $oldPassword); + + // encrypt again with new passcode + $key=OC_Crypt::encrypt($key, $newPassword); + + // store the new key + $view->file_put_contents('/encryption.key', $key ); + } + } + +} + ?> \ No newline at end of file diff --git a/apps/files_encryption/lib/cryptstream.php b/apps/files_encryption/lib/cryptstream.php index e0020537563..8c61c933cf8 100644 --- a/apps/files_encryption/lib/cryptstream.php +++ b/apps/files_encryption/lib/cryptstream.php @@ -28,11 +28,11 @@ */ class OC_CryptStream{ - public static $sourceStreams=array(); + public static $sourceStreams = array(); private $source; private $path; - private $readBuffer;//for streams that dont support seeking - private $meta=array();//header/meta for source stream + private $readBuffer; // For streams that dont support seeking + private $meta = array(); // Header / meta for source stream private $count; private $writeCache; private $size; @@ -98,38 +98,69 @@ class OC_CryptStream{ return $result; } - public function stream_write($data){ - $length=strlen($data); - $written=0; - $currentPos=ftell($this->source); - if($this->writeCache){ - $data=$this->writeCache.$data; - $this->writeCache=''; + public function stream_write( $data ){ + + $length = strlen( $data ); + + $written = 0; + + $currentPos = ftell( $this->source ); + + if( $this->writeCache ){ + + $data = $this->writeCache.$data; + + $this->writeCache = ''; + } - if($currentPos%8192!=0){ + + 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); + + 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=''; + + $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); + + $encrypted = OC_Crypt::encrypt( substr( $data,0,8192 ) ); + + fwrite( $this->source,$encrypted ); + + $data = substr( $data,8192 ); + } + } - $this->size=max($this->size,$currentPos+$length); + + $this->size = max( $this->size,$currentPos+$length ); + return $length; + } + public function stream_set_option($option,$arg1,$arg2){ switch($option){ case STREAM_OPTION_BLOCKING: diff --git a/apps/files_encryption/lib/keymanager.php b/apps/files_encryption/lib/keymanager.php index bafe8f1a5f0..0c76bf27a52 100644 --- a/apps/files_encryption/lib/keymanager.php +++ b/apps/files_encryption/lib/keymanager.php @@ -27,7 +27,7 @@ namespace OCA_Encryption; */ class Keymanager { - # TODO: Try and get rid of username dependencies as these methods need to be used in a proxy class that doesn't have username access + # TODO: make all dependencies explicit, such as ocfsview objects, by adding them as method arguments (dependency injection) /** * @brief retrieve private key from a user @@ -60,9 +60,9 @@ class Keymanager { * @param string user name of the file owner * @return string file key or false */ - public static function getFileKey($userId, $path) { + public static function getFileKey( $userId, $path ) { - $keypath = ltrim($path, '/'); + $keypath = ltrim( $path, '/' ); $user = $userId; // update $keypath and $user if path point to a file shared by someone else @@ -127,29 +127,33 @@ class Keymanager { * @param string $path relative path of the file, including filename * @param string $key * @return bool true/false - */ + */ public static function setFileKey( $userId, $path, $key ) { \OC_FileProxy::$enabled = false; - $targetpath = ltrim($path, '/'); + $targetpath = ltrim( $path, '/' ); $user = $userId; // update $keytarget and $user if key belongs 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/'.$targetpath, $userId)); - if ($row = $result->fetchRow()){ + + $result = $query->execute( array ( '/'.$userId.'/files/'.$targetpath, $userId ) ); + + if ( $row = $result->fetchRow( ) ) { $targetpath = $row['source']; - $targetpath_parts=explode('/',$targetpath); + $targetpath_parts=explode( '/',$targetpath ); $user = $targetpath_parts[1]; - $targetpath = str_replace('/'.$user.'/files/', '', $targetpath); + $targetpath = str_replace( '/'.$user.'/files/', '', $targetpath ); //TODO: check for write permission on shared file once the new sharing API is in place } $view = new \OC_FilesystemView( '/' . $user . '/files_encryption/keyfiles' ); - $path_parts = pathinfo($targetpath); - if (!$view->file_exists($path_parts['dirname'])) $view->mkdir($path_parts['dirname']); + $path_parts = pathinfo( $targetpath ); + + if ( !$view->file_exists( $path_parts['dirname'] ) ) $view->mkdir( $path_parts['dirname'] ); + $result = $view->file_put_contents( '/' . $targetpath . '.key', $key ); \OC_FileProxy::$enabled = true; diff --git a/apps/files_encryption/lib/proxy.php b/apps/files_encryption/lib/proxy.php index 53ed05d2c3b..c1956ad0216 100644 --- a/apps/files_encryption/lib/proxy.php +++ b/apps/files_encryption/lib/proxy.php @@ -109,10 +109,14 @@ class Proxy extends \OC_FileProxy { // Replace plain content with encrypted content by reference $data = $encrypted['encrypted']; - # TODO: check if file is in subdirectories, and if so, create those parent directories. Or else monitor creation of directories using hooks to ensure path will always exist (what about existing directories when encryption is enabled?) + $filePath = explode( '/', $path ); - // Save keyfile for newly encrypted file in parallel directory - Keymanager::setFileKey( \OCP\USER::getUser(), $path, $encrypted['key'] ); + $filePath = array_slice( $filePath, 3 ); + + $filePath = '/' . implode( '/', $filePath ); + + // Save keyfile for newly encrypted file in parallel directory tree + Keymanager::setFileKey( \OCP\USER::getUser(), $filePath, $encrypted['key'] ); // Update the file cache with file info \OC_FileCache::put( $path, array( 'encrypted'=>true, 'size' => $size ), '' ); @@ -124,38 +128,80 @@ class Proxy extends \OC_FileProxy { public function postFile_get_contents( $path, $data ) { if ( Crypt::isEncryptedContent( $data ) ) { - trigger_error('best'); + + $filePath = explode( '/', $path ); + + $filePath = array_slice( $filePath, 3 ); + + $filePath = '/' . implode( '/', $filePath ); + + trigger_error( "CAT " . $filePath); + $cached = \OC_FileCache_Cached::get( $path, '' ); - $data = Crypt::symmetricDecryptFileContent( $data, $_SESSION['enckey'] ); + // Get keyfile for encrypted file + $keyFile = Keymanager::getFileKey( \OCP\USER::getUser(), $filePath ); + + $data = Crypt::symmetricDecryptFileContent( $data, $keyFile ); } return $data; + } - public function postFopen($path,&$result){ + public function postFopen( $path, &$result ){ - if(!$result){ + if ( !$result ) { + return $result; + } - $meta=stream_get_meta_data($result); - if(Crypt::isEncryptedContent($path)){ - fclose($result); - $result=fopen('crypt://'.$path,$meta['mode']); - }elseif(self::shouldEncrypt($path) and $meta['mode']!='r' and $meta['mode']!='rb'){ - if( \OC_Filesystem::file_exists( $path ) and \OC_Filesystem::filesize($path)>0){ + + $meta = stream_get_meta_data( $result ); + + // If file is encrypted, decrypt using crypto protocol + if ( Crypt::isEncryptedContent( $path ) ) { + + fclose ( $result ); + + $result = fopen( 'crypt://'.$path, $meta['mode'] ); + + } elseif ( + self::shouldEncrypt( $path ) + and $meta ['mode'] != 'r' + and $meta['mode'] != 'rb' + ) { + + # TODO: figure out what this does + + if ( + \OC_Filesystem::file_exists( $path ) + and \OC_Filesystem::filesize( $path ) > 0 + ) { + //first encrypt the target file so we don't end up with a half encrypted file - \OCP\Util::writeLog('files_encryption','Decrypting '.$path.' before writing', \OCP\Util::DEBUG); - $tmp=fopen('php://temp'); - \OCP\Files::streamCopy($result,$tmp); - fclose($result); - \OC_Filesystem::file_put_contents($path,$tmp); - fclose($tmp); + \OCP\Util::writeLog( 'files_encryption', 'Decrypting '.$path.' before writing', \OCP\Util::DEBUG ); + + $tmp = fopen( 'php://temp' ); + + \OCP\Files::streamCopy( $result, $tmp ); + + // Close the original stream, we'll return another one + fclose( $result ); + + \OC_Filesystem::file_put_contents( $path, $tmp ); + + fclose( $tmp ); + } - $result=fopen('crypt://'.$path,$meta['mode']); + + $result = fopen( 'crypt://'.$path, $meta['mode'] ); + } + return $result; + } public function postGetMimeType($path,$mime){ diff --git a/apps/files_encryption/lib/util.php b/apps/files_encryption/lib/util.php index ab58b4aa721..609f7871241 100644 --- a/apps/files_encryption/lib/util.php +++ b/apps/files_encryption/lib/util.php @@ -44,18 +44,19 @@ class Util { # DONE: add method to check if file is encrypted using old system # DONE: add method to fetch legacy key # DONE: add method to decrypt legacy encrypted data + # DONE: fix / test the crypt stream proxy class - # TODO: add method to encrypt all user files using new system - # TODO: add method to decrypt all user files using new system - # TODO: add method to encrypt all user files using old system - # TODO: add method to decrypt all user files using old system - - # TODO: fix / test the crypt stream proxy class + # TODO: replace cryptstream wrapper with stream_socket_enable_crypto, or fix it to use new crypt class methods # TODO: add support for optional recovery user in case of lost passphrase / keys # TODO: add admin optional required long passphrase for users # TODO: implement flag system to allow user to specify encryption by folder, subfolder, etc. # TODO: add UI buttons for encrypt / decrypt everything? + # TODO: add method to encrypt all user files using new system + # TODO: add method to decrypt all user files using new system + # TODO: add method to encrypt all user files using old system + # TODO: add method to decrypt all user files using old system + # TODO: test new encryption with webdav # TODO: test new encryption with versioning # TODO: test new encryption with sharing @@ -154,6 +155,89 @@ class Util { } + public function findFiles( $directory, $type = 'plain' ) { + + # TODO: test finding non plain content + + if ( $handle = $this->view->opendir( $directory ) ) { + + while ( false !== ( $file = readdir( $handle ) ) ) { + + if ( + $file != "." + && $file != ".." + ) { + + $filePath = $directory . '/' . $this->view->getRelativePath( '/' . $file ); + + var_dump($filePath); + + if ( $this->view->is_dir( $filePath ) ) { + + $this->findFiles( $filePath ); + + } elseif ( $this->view->is_file( $filePath ) ) { + + if ( $type == 'plain' ) { + + $this->files[] = array( 'name' => $file, 'path' => $filePath ); + + } elseif ( $type == 'encrypted' ) { + + if ( Crypt::isEncryptedContent( $this->view->file_get_contents( $filePath ) ) ) { + + $this->files[] = array( 'name' => $file, 'path' => $filePath ); + + } + + } elseif ( $type == 'legacy' ) { + + if ( Crypt::isLegacyEncryptedContent( $this->view->file_get_contents( $filePath ) ) ) { + + $this->files[] = array( 'name' => $file, 'path' => $filePath ); + + } + + } + + } + + } + + } + + if ( !empty( $this->files ) ) { + + return $this->files; + + } else { + + return false; + + } + + } + + return false; + + } + + public function encryptAll( OC_FilesystemView $view ) { + + $plainFiles = $this->findPlainFiles( $view ); + + if ( $this->encryptFiles( $plainFiles ) ) { + + return true; + + } else { + + return false; + + } + + } + /** * @brief Get the blowfish encryption handeler for a key * @param $key string (optional) -- cgit v1.2.3 From 84fd62b13047cb756d9f39c192e17fd5f2179f83 Mon Sep 17 00:00:00 2001 From: Sam Tuke Date: Tue, 31 Jul 2012 19:35:36 +0100 Subject: Implemented writing of keyfiles and directory hierarchy in proxy class Added crypt::findFiles() method for finding different types of files, ready for batch encrypting / decrypting Added comments to postFopen in proxy class --- apps/files_encryption/hooks/hooks.php | 4 +- apps/files_encryption/lib/crypt.php | 843 +++++++++++++++--------------- apps/files_encryption/lib/cryptstream.php | 83 ++- apps/files_encryption/lib/keymanager.php | 26 +- apps/files_encryption/lib/proxy.php | 86 ++- apps/files_encryption/lib/util.php | 96 +++- 6 files changed, 653 insertions(+), 485 deletions(-) (limited to 'apps/files_encryption/lib') diff --git a/apps/files_encryption/hooks/hooks.php b/apps/files_encryption/hooks/hooks.php index 35e14e28106..d06e9a0d2d3 100644 --- a/apps/files_encryption/hooks/hooks.php +++ b/apps/files_encryption/hooks/hooks.php @@ -37,14 +37,14 @@ class Hooks { public static function login( $params ) { - if (Crypt::mode($params['uid'])=='server') { + if ( Crypt::mode( $params['uid'] ) == 'server' ) { $view = new \OC_FilesystemView( '/' ); $util = new Util( $view, $params['uid'] ); if ( !$util->ready()) { - + return $util->setupServerSide( $params['password'] ); } diff --git a/apps/files_encryption/lib/crypt.php b/apps/files_encryption/lib/crypt.php index fdace3e61dd..7e50c900fa1 100644 --- a/apps/files_encryption/lib/crypt.php +++ b/apps/files_encryption/lib/crypt.php @@ -1,426 +1,429 @@ -. - * - */ - -namespace OCA_Encryption; - -/** - * Class for common cryptography functionality - */ - -class Crypt { - +. + * + */ + +namespace OCA_Encryption; + +/** + * Class for common cryptography functionality + */ + +class Crypt { + /** - * @brief return encryption mode client or server side encryption + * @brief return encryption mode client or server side encryption * @param string user name (use system wide setting if name=null) * @return string 'client' or 'server' */ - public static function mode($user=null) { - - $mode = \OC_Appconfig::getValue('files_encryption', 'mode', 'unknown'); - - if ($mode == 'unknown') { - error_log('no encryption mode configured'); - return false; - } - + public static function mode( $user = null ) { + + $mode = \OC_Appconfig::getValue( 'files_encryption', 'mode', 'unknown' ); + + if ( $mode == 'unknown' ) { + + error_log('no encryption mode configured'); + + return false; + + } + return $mode; - } - - /** - * @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 Check if a file's contents contains an IV and is symmetrically encrypted - * @return true / false - */ - public static function isEncryptedContent( $content ) { - - if ( !$content ) { - - return false; - - } - - // Fetch encryption metadata from end of file - $meta = substr( $content, -22 ); - - // Fetch IV from end of file - $iv = substr( $meta, -16 ); - - // Fetch identifier from start of metadata - $identifier = substr( $meta, 0, 6 ); - - if ( $identifier == '00iv00') { - - return true; - - } else { - - return false; - - } - - } - - /** - * @brief Check if a file is encrypted via legacy system - * @return true / false - */ - public static function isLegacyEncryptedContent( $content, $path ) { - - // Fetch all file metadata from DB - $metadata = \OC_FileCache_Cached::get( $content, '' ); - - // If a file is flagged with encryption in DB, but isn't a valid content + IV combination, it's probably using the legacy encryption system - if ( - $content - and isset( $metadata['encrypted'] ) - and $metadata['encrypted'] === true - and !self::isEncryptedContent( $content ) - ) { - - return true; - - } else { - - return false; - - } - - } - - /** - * @brief Symmetrically encrypt a string - * @returns encrypted file - */ - public static function encrypt( $plainContent, $iv, $passphrase = '' ) { - - if ( $encryptedContent = openssl_encrypt( $plainContent, 'AES-128-CFB', $passphrase, false, $iv ) ) { - - return $encryptedContent; - - } else { - - \OC_Log::write( 'Encryption library', 'Encryption (symmetric) of content failed' , \OC_Log::ERROR ); - - return false; - - } - - } - - /** - * @brief Symmetrically decrypt a string - * @returns decrypted file - */ - public static function decrypt( $encryptedContent, $iv, $passphrase ) { - - if ( $plainContent = openssl_decrypt( $encryptedContent, 'AES-128-CFB', $passphrase, false, $iv ) ) { - - return $plainContent; - - - } else { - - \OC_Log::write( 'Encryption library', 'Decryption (symmetric) of content failed' , \OC_Log::ERROR ); - - return false; - - } - - } - - /** - * @brief Symmetrically encrypts a string and returns keyfile content - * @param $plainContent content to be encrypted in keyfile - * @returns encrypted content combined with IV - * @note IV need not be specified, as it will be stored in the returned keyfile - * and remain accessible therein. - */ - public static function symmetricEncryptFileContent( $plainContent, $passphrase = '' ) { - - if ( !$plainContent ) { - - return false; - - } - - $iv = self::generateIv(); - - if ( $encryptedContent = self::encrypt( $plainContent, $iv, $passphrase ) ) { - - // Combine content to encrypt with IV identifier and actual IV - $combinedKeyfile = $encryptedContent . '00iv00' . $iv; - - return $combinedKeyfile; - - } else { - - \OC_Log::write( 'Encryption library', 'Encryption (symmetric) of keyfile content failed' , \OC_Log::ERROR ); - - return false; - - } - - } - - - /** - * @brief Symmetrically decrypts keyfile content - * @param string $source - * @param string $target - * @param string $key the decryption key - * - * This function decrypts a file - */ - public static function symmetricDecryptFileContent( $keyfileContent, $passphrase = '' ) { - - if ( !$keyfileContent ) { - - return false; - - } - - // Fetch IV from end of file - $iv = substr( $keyfileContent, -16 ); - - // Remove IV and IV identifier text to expose encrypted content - $encryptedContent = substr( $keyfileContent, 0, -22 ); - - if ( $plainContent = self::decrypt( $encryptedContent, $iv, $passphrase ) ) { - - return $plainContent; - - } else { - - \OC_Log::write( 'Encryption library', 'Decryption (symmetric) of keyfile content failed' , \OC_Log::ERROR ); - - return false; - - } - - } - - /** - * @brief Creates symmetric keyfile content using a generated key - * @param string $plainContent content to be encrypted - * @returns array keys: key, encrypted - * @note symmetricDecryptFileContent() can be used to decrypt files created using this method - * - * This function decrypts a file - */ - public static function symmetricEncryptFileContentKeyfile( $plainContent ) { - - $key = self::generateKey(); - - if( $encryptedContent = self::symmetricEncryptFileContent( $plainContent, $key ) ) { - - return array( - 'key' => $key - , 'encrypted' => $encryptedContent - ); - - } else { - - return false; - - } - - } - - /** - * @brief Create asymmetrically encrypted keyfile content using a generated key - * @param string $plainContent content to be encrypted - * @returns array keys: key, encrypted - * @note symmetricDecryptFileContent() can be used to decrypt files created using this method - * - * This function decrypts a file - */ - public static function multiKeyEncrypt( $plainContent, array $publicKeys ) { - - $envKeys = array(); - - if( openssl_seal( $plainContent, $sealed, $envKeys, $publicKeys ) ) { - - return array( - 'keys' => $envKeys - , 'encrypted' => $sealed - ); - - } else { - - return false; - - } - - } - - /** - * @brief Asymmetrically encrypt a file using multiple public keys - * @param string $plainContent content to be encrypted - * @returns array keys: key, encrypted - * @note symmetricDecryptFileContent() can be used to decrypt files created using this method - * - * This function decrypts a file - */ - public static function multiKeyDecrypt( $encryptedContent, $envKey, $privateKey ) { - - if ( !$encryptedContent ) { - - return false; - - } - - if ( openssl_open( $encryptedContent, $plainContent, $envKey, $privateKey ) ) { - - return $plainContent; - - } else { - - \OC_Log::write( 'Encryption library', 'Decryption (asymmetric) of sealed content failed' , \OC_Log::ERROR ); - - return false; - - } - - } - - /** - * @brief Asymetrically encrypt a string 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; - - } - - /** - * @brief Generate a pseudo random 1024kb ASCII key - * @returns $key Generated key - */ - public static function generateIv() { - - if ( $random = openssl_random_pseudo_bytes( 13, $strong ) ) { - - if ( !$strong ) { - - // If OpenSSL indicates randomness is insecure, log error - \OC_Log::write( 'Encryption library', 'Insecure symmetric key was generated using openssl_random_pseudo_bytes()' , \OC_Log::WARN ); - - } - - $iv = substr( base64_encode( $random ), 0, -4 ); - - return $iv; - - } else { - - return false; - - } - - } - - /** - * @brief Generate a pseudo random 1024kb ASCII key - * @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 ); - - // Generate key - if ( $key = base64_encode( openssl_random_pseudo_bytes( 768000, $strong ) ) ) { - - if ( !$strong ) { - - // If OpenSSL indicates randomness is insecure, log error - \OC_Log::write( 'Encryption library', 'Insecure symmetric key was generated using openssl_random_pseudo_bytes()' , \OC_Log::WARN ); - - } - - return $key; - - } else { - - return false; - - } - - } - - public static function changekeypasscode($oldPassword, $newPassword) { - if(OCP\User::isLoggedIn()){ - $username=OCP\USER::getUser(); - $view=new OC_FilesystemView('/'.$username); - - // read old key - $key=$view->file_get_contents('/encryption.key'); - - // decrypt key with old passcode - $key=OC_Crypt::decrypt($key, $oldPassword); - - // encrypt again with new passcode - $key=OC_Crypt::encrypt($key, $newPassword); - - // store the new key - $view->file_put_contents('/encryption.key', $key ); - } - } - -} - + } + + /** + * @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 Check if a file's contents contains an IV and is symmetrically encrypted + * @return true / false + */ + public static function isEncryptedContent( $content ) { + + if ( !$content ) { + + return false; + + } + + // Fetch encryption metadata from end of file + $meta = substr( $content, -22 ); + + // Fetch IV from end of file + $iv = substr( $meta, -16 ); + + // Fetch identifier from start of metadata + $identifier = substr( $meta, 0, 6 ); + + if ( $identifier == '00iv00') { + + return true; + + } else { + + return false; + + } + + } + + /** + * @brief Check if a file is encrypted via legacy system + * @return true / false + */ + public static function isLegacyEncryptedContent( $content, $path ) { + + // Fetch all file metadata from DB + $metadata = \OC_FileCache_Cached::get( $content, '' ); + + // If a file is flagged with encryption in DB, but isn't a valid content + IV combination, it's probably using the legacy encryption system + if ( + $content + and isset( $metadata['encrypted'] ) + and $metadata['encrypted'] === true + and !self::isEncryptedContent( $content ) + ) { + + return true; + + } else { + + return false; + + } + + } + + /** + * @brief Symmetrically encrypt a string + * @returns encrypted file + */ + public static function encrypt( $plainContent, $iv, $passphrase = '' ) { + + if ( $encryptedContent = openssl_encrypt( $plainContent, 'AES-128-CFB', $passphrase, false, $iv ) ) { + + return $encryptedContent; + + } else { + + \OC_Log::write( 'Encryption library', 'Encryption (symmetric) of content failed' , \OC_Log::ERROR ); + + return false; + + } + + } + + /** + * @brief Symmetrically decrypt a string + * @returns decrypted file + */ + public static function decrypt( $encryptedContent, $iv, $passphrase ) { + + if ( $plainContent = openssl_decrypt( $encryptedContent, 'AES-128-CFB', $passphrase, false, $iv ) ) { + + return $plainContent; + + + } else { + + \OC_Log::write( 'Encryption library', 'Decryption (symmetric) of content failed' , \OC_Log::ERROR ); + + return false; + + } + + } + + /** + * @brief Symmetrically encrypts a string and returns keyfile content + * @param $plainContent content to be encrypted in keyfile + * @returns encrypted content combined with IV + * @note IV need not be specified, as it will be stored in the returned keyfile + * and remain accessible therein. + */ + public static function symmetricEncryptFileContent( $plainContent, $passphrase = '' ) { + + if ( !$plainContent ) { + + return false; + + } + + $iv = self::generateIv(); + + if ( $encryptedContent = self::encrypt( $plainContent, $iv, $passphrase ) ) { + + // Combine content to encrypt with IV identifier and actual IV + $combinedKeyfile = $encryptedContent . '00iv00' . $iv; + + return $combinedKeyfile; + + } else { + + \OC_Log::write( 'Encryption library', 'Encryption (symmetric) of keyfile content failed' , \OC_Log::ERROR ); + + return false; + + } + + } + + + /** + * @brief Symmetrically decrypts keyfile content + * @param string $source + * @param string $target + * @param string $key the decryption key + * + * This function decrypts a file + */ + public static function symmetricDecryptFileContent( $keyfileContent, $passphrase = '' ) { + + if ( !$keyfileContent ) { + + return false; + + } + + // Fetch IV from end of file + $iv = substr( $keyfileContent, -16 ); + + // Remove IV and IV identifier text to expose encrypted content + $encryptedContent = substr( $keyfileContent, 0, -22 ); + + if ( $plainContent = self::decrypt( $encryptedContent, $iv, $passphrase ) ) { + + return $plainContent; + + } else { + + \OC_Log::write( 'Encryption library', 'Decryption (symmetric) of keyfile content failed' , \OC_Log::ERROR ); + + return false; + + } + + } + + /** + * @brief Creates symmetric keyfile content using a generated key + * @param string $plainContent content to be encrypted + * @returns array keys: key, encrypted + * @note symmetricDecryptFileContent() can be used to decrypt files created using this method + * + * This function decrypts a file + */ + public static function symmetricEncryptFileContentKeyfile( $plainContent ) { + + $key = self::generateKey(); + + if( $encryptedContent = self::symmetricEncryptFileContent( $plainContent, $key ) ) { + + return array( + 'key' => $key + , 'encrypted' => $encryptedContent + ); + + } else { + + return false; + + } + + } + + /** + * @brief Create asymmetrically encrypted keyfile content using a generated key + * @param string $plainContent content to be encrypted + * @returns array keys: key, encrypted + * @note symmetricDecryptFileContent() can be used to decrypt files created using this method + * + * This function decrypts a file + */ + public static function multiKeyEncrypt( $plainContent, array $publicKeys ) { + + $envKeys = array(); + + if( openssl_seal( $plainContent, $sealed, $envKeys, $publicKeys ) ) { + + return array( + 'keys' => $envKeys + , 'encrypted' => $sealed + ); + + } else { + + return false; + + } + + } + + /** + * @brief Asymmetrically encrypt a file using multiple public keys + * @param string $plainContent content to be encrypted + * @returns array keys: key, encrypted + * @note symmetricDecryptFileContent() can be used to decrypt files created using this method + * + * This function decrypts a file + */ + public static function multiKeyDecrypt( $encryptedContent, $envKey, $privateKey ) { + + if ( !$encryptedContent ) { + + return false; + + } + + if ( openssl_open( $encryptedContent, $plainContent, $envKey, $privateKey ) ) { + + return $plainContent; + + } else { + + \OC_Log::write( 'Encryption library', 'Decryption (asymmetric) of sealed content failed' , \OC_Log::ERROR ); + + return false; + + } + + } + + /** + * @brief Asymetrically encrypt a string 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; + + } + + /** + * @brief Generate a pseudo random 1024kb ASCII key + * @returns $key Generated key + */ + public static function generateIv() { + + if ( $random = openssl_random_pseudo_bytes( 13, $strong ) ) { + + if ( !$strong ) { + + // If OpenSSL indicates randomness is insecure, log error + \OC_Log::write( 'Encryption library', 'Insecure symmetric key was generated using openssl_random_pseudo_bytes()' , \OC_Log::WARN ); + + } + + $iv = substr( base64_encode( $random ), 0, -4 ); + + return $iv; + + } else { + + return false; + + } + + } + + /** + * @brief Generate a pseudo random 1024kb ASCII key + * @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 ); + + // Generate key + if ( $key = base64_encode( openssl_random_pseudo_bytes( 768000, $strong ) ) ) { + + if ( !$strong ) { + + // If OpenSSL indicates randomness is insecure, log error + \OC_Log::write( 'Encryption library', 'Insecure symmetric key was generated using openssl_random_pseudo_bytes()' , \OC_Log::WARN ); + + } + + return $key; + + } else { + + return false; + + } + + } + + public static function changekeypasscode($oldPassword, $newPassword) { + if(OCP\User::isLoggedIn()){ + $username=OCP\USER::getUser(); + $view=new OC_FilesystemView('/'.$username); + + // read old key + $key=$view->file_get_contents('/encryption.key'); + + // decrypt key with old passcode + $key=OC_Crypt::decrypt($key, $oldPassword); + + // encrypt again with new passcode + $key=OC_Crypt::encrypt($key, $newPassword); + + // store the new key + $view->file_put_contents('/encryption.key', $key ); + } + } + +} + ?> \ No newline at end of file diff --git a/apps/files_encryption/lib/cryptstream.php b/apps/files_encryption/lib/cryptstream.php index e0020537563..8c61c933cf8 100644 --- a/apps/files_encryption/lib/cryptstream.php +++ b/apps/files_encryption/lib/cryptstream.php @@ -28,11 +28,11 @@ */ class OC_CryptStream{ - public static $sourceStreams=array(); + public static $sourceStreams = array(); private $source; private $path; - private $readBuffer;//for streams that dont support seeking - private $meta=array();//header/meta for source stream + private $readBuffer; // For streams that dont support seeking + private $meta = array(); // Header / meta for source stream private $count; private $writeCache; private $size; @@ -98,38 +98,69 @@ class OC_CryptStream{ return $result; } - public function stream_write($data){ - $length=strlen($data); - $written=0; - $currentPos=ftell($this->source); - if($this->writeCache){ - $data=$this->writeCache.$data; - $this->writeCache=''; + public function stream_write( $data ){ + + $length = strlen( $data ); + + $written = 0; + + $currentPos = ftell( $this->source ); + + if( $this->writeCache ){ + + $data = $this->writeCache.$data; + + $this->writeCache = ''; + } - if($currentPos%8192!=0){ + + 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); + + 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=''; + + $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); + + $encrypted = OC_Crypt::encrypt( substr( $data,0,8192 ) ); + + fwrite( $this->source,$encrypted ); + + $data = substr( $data,8192 ); + } + } - $this->size=max($this->size,$currentPos+$length); + + $this->size = max( $this->size,$currentPos+$length ); + return $length; + } + public function stream_set_option($option,$arg1,$arg2){ switch($option){ case STREAM_OPTION_BLOCKING: diff --git a/apps/files_encryption/lib/keymanager.php b/apps/files_encryption/lib/keymanager.php index bafe8f1a5f0..0c76bf27a52 100644 --- a/apps/files_encryption/lib/keymanager.php +++ b/apps/files_encryption/lib/keymanager.php @@ -27,7 +27,7 @@ namespace OCA_Encryption; */ class Keymanager { - # TODO: Try and get rid of username dependencies as these methods need to be used in a proxy class that doesn't have username access + # TODO: make all dependencies explicit, such as ocfsview objects, by adding them as method arguments (dependency injection) /** * @brief retrieve private key from a user @@ -60,9 +60,9 @@ class Keymanager { * @param string user name of the file owner * @return string file key or false */ - public static function getFileKey($userId, $path) { + public static function getFileKey( $userId, $path ) { - $keypath = ltrim($path, '/'); + $keypath = ltrim( $path, '/' ); $user = $userId; // update $keypath and $user if path point to a file shared by someone else @@ -127,29 +127,33 @@ class Keymanager { * @param string $path relative path of the file, including filename * @param string $key * @return bool true/false - */ + */ public static function setFileKey( $userId, $path, $key ) { \OC_FileProxy::$enabled = false; - $targetpath = ltrim($path, '/'); + $targetpath = ltrim( $path, '/' ); $user = $userId; // update $keytarget and $user if key belongs 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/'.$targetpath, $userId)); - if ($row = $result->fetchRow()){ + + $result = $query->execute( array ( '/'.$userId.'/files/'.$targetpath, $userId ) ); + + if ( $row = $result->fetchRow( ) ) { $targetpath = $row['source']; - $targetpath_parts=explode('/',$targetpath); + $targetpath_parts=explode( '/',$targetpath ); $user = $targetpath_parts[1]; - $targetpath = str_replace('/'.$user.'/files/', '', $targetpath); + $targetpath = str_replace( '/'.$user.'/files/', '', $targetpath ); //TODO: check for write permission on shared file once the new sharing API is in place } $view = new \OC_FilesystemView( '/' . $user . '/files_encryption/keyfiles' ); - $path_parts = pathinfo($targetpath); - if (!$view->file_exists($path_parts['dirname'])) $view->mkdir($path_parts['dirname']); + $path_parts = pathinfo( $targetpath ); + + if ( !$view->file_exists( $path_parts['dirname'] ) ) $view->mkdir( $path_parts['dirname'] ); + $result = $view->file_put_contents( '/' . $targetpath . '.key', $key ); \OC_FileProxy::$enabled = true; diff --git a/apps/files_encryption/lib/proxy.php b/apps/files_encryption/lib/proxy.php index 53ed05d2c3b..c1956ad0216 100644 --- a/apps/files_encryption/lib/proxy.php +++ b/apps/files_encryption/lib/proxy.php @@ -109,10 +109,14 @@ class Proxy extends \OC_FileProxy { // Replace plain content with encrypted content by reference $data = $encrypted['encrypted']; - # TODO: check if file is in subdirectories, and if so, create those parent directories. Or else monitor creation of directories using hooks to ensure path will always exist (what about existing directories when encryption is enabled?) + $filePath = explode( '/', $path ); - // Save keyfile for newly encrypted file in parallel directory - Keymanager::setFileKey( \OCP\USER::getUser(), $path, $encrypted['key'] ); + $filePath = array_slice( $filePath, 3 ); + + $filePath = '/' . implode( '/', $filePath ); + + // Save keyfile for newly encrypted file in parallel directory tree + Keymanager::setFileKey( \OCP\USER::getUser(), $filePath, $encrypted['key'] ); // Update the file cache with file info \OC_FileCache::put( $path, array( 'encrypted'=>true, 'size' => $size ), '' ); @@ -124,38 +128,80 @@ class Proxy extends \OC_FileProxy { public function postFile_get_contents( $path, $data ) { if ( Crypt::isEncryptedContent( $data ) ) { - trigger_error('best'); + + $filePath = explode( '/', $path ); + + $filePath = array_slice( $filePath, 3 ); + + $filePath = '/' . implode( '/', $filePath ); + + trigger_error( "CAT " . $filePath); + $cached = \OC_FileCache_Cached::get( $path, '' ); - $data = Crypt::symmetricDecryptFileContent( $data, $_SESSION['enckey'] ); + // Get keyfile for encrypted file + $keyFile = Keymanager::getFileKey( \OCP\USER::getUser(), $filePath ); + + $data = Crypt::symmetricDecryptFileContent( $data, $keyFile ); } return $data; + } - public function postFopen($path,&$result){ + public function postFopen( $path, &$result ){ - if(!$result){ + if ( !$result ) { + return $result; + } - $meta=stream_get_meta_data($result); - if(Crypt::isEncryptedContent($path)){ - fclose($result); - $result=fopen('crypt://'.$path,$meta['mode']); - }elseif(self::shouldEncrypt($path) and $meta['mode']!='r' and $meta['mode']!='rb'){ - if( \OC_Filesystem::file_exists( $path ) and \OC_Filesystem::filesize($path)>0){ + + $meta = stream_get_meta_data( $result ); + + // If file is encrypted, decrypt using crypto protocol + if ( Crypt::isEncryptedContent( $path ) ) { + + fclose ( $result ); + + $result = fopen( 'crypt://'.$path, $meta['mode'] ); + + } elseif ( + self::shouldEncrypt( $path ) + and $meta ['mode'] != 'r' + and $meta['mode'] != 'rb' + ) { + + # TODO: figure out what this does + + if ( + \OC_Filesystem::file_exists( $path ) + and \OC_Filesystem::filesize( $path ) > 0 + ) { + //first encrypt the target file so we don't end up with a half encrypted file - \OCP\Util::writeLog('files_encryption','Decrypting '.$path.' before writing', \OCP\Util::DEBUG); - $tmp=fopen('php://temp'); - \OCP\Files::streamCopy($result,$tmp); - fclose($result); - \OC_Filesystem::file_put_contents($path,$tmp); - fclose($tmp); + \OCP\Util::writeLog( 'files_encryption', 'Decrypting '.$path.' before writing', \OCP\Util::DEBUG ); + + $tmp = fopen( 'php://temp' ); + + \OCP\Files::streamCopy( $result, $tmp ); + + // Close the original stream, we'll return another one + fclose( $result ); + + \OC_Filesystem::file_put_contents( $path, $tmp ); + + fclose( $tmp ); + } - $result=fopen('crypt://'.$path,$meta['mode']); + + $result = fopen( 'crypt://'.$path, $meta['mode'] ); + } + return $result; + } public function postGetMimeType($path,$mime){ diff --git a/apps/files_encryption/lib/util.php b/apps/files_encryption/lib/util.php index ab58b4aa721..609f7871241 100644 --- a/apps/files_encryption/lib/util.php +++ b/apps/files_encryption/lib/util.php @@ -44,18 +44,19 @@ class Util { # DONE: add method to check if file is encrypted using old system # DONE: add method to fetch legacy key # DONE: add method to decrypt legacy encrypted data + # DONE: fix / test the crypt stream proxy class - # TODO: add method to encrypt all user files using new system - # TODO: add method to decrypt all user files using new system - # TODO: add method to encrypt all user files using old system - # TODO: add method to decrypt all user files using old system - - # TODO: fix / test the crypt stream proxy class + # TODO: replace cryptstream wrapper with stream_socket_enable_crypto, or fix it to use new crypt class methods # TODO: add support for optional recovery user in case of lost passphrase / keys # TODO: add admin optional required long passphrase for users # TODO: implement flag system to allow user to specify encryption by folder, subfolder, etc. # TODO: add UI buttons for encrypt / decrypt everything? + # TODO: add method to encrypt all user files using new system + # TODO: add method to decrypt all user files using new system + # TODO: add method to encrypt all user files using old system + # TODO: add method to decrypt all user files using old system + # TODO: test new encryption with webdav # TODO: test new encryption with versioning # TODO: test new encryption with sharing @@ -154,6 +155,89 @@ class Util { } + public function findFiles( $directory, $type = 'plain' ) { + + # TODO: test finding non plain content + + if ( $handle = $this->view->opendir( $directory ) ) { + + while ( false !== ( $file = readdir( $handle ) ) ) { + + if ( + $file != "." + && $file != ".." + ) { + + $filePath = $directory . '/' . $this->view->getRelativePath( '/' . $file ); + + var_dump($filePath); + + if ( $this->view->is_dir( $filePath ) ) { + + $this->findFiles( $filePath ); + + } elseif ( $this->view->is_file( $filePath ) ) { + + if ( $type == 'plain' ) { + + $this->files[] = array( 'name' => $file, 'path' => $filePath ); + + } elseif ( $type == 'encrypted' ) { + + if ( Crypt::isEncryptedContent( $this->view->file_get_contents( $filePath ) ) ) { + + $this->files[] = array( 'name' => $file, 'path' => $filePath ); + + } + + } elseif ( $type == 'legacy' ) { + + if ( Crypt::isLegacyEncryptedContent( $this->view->file_get_contents( $filePath ) ) ) { + + $this->files[] = array( 'name' => $file, 'path' => $filePath ); + + } + + } + + } + + } + + } + + if ( !empty( $this->files ) ) { + + return $this->files; + + } else { + + return false; + + } + + } + + return false; + + } + + public function encryptAll( OC_FilesystemView $view ) { + + $plainFiles = $this->findPlainFiles( $view ); + + if ( $this->encryptFiles( $plainFiles ) ) { + + return true; + + } else { + + return false; + + } + + } + /** * @brief Get the blowfish encryption handeler for a key * @param $key string (optional) -- cgit v1.2.3 From c4d1ad1b7d4507e387a5833622b4831044eb9e09 Mon Sep 17 00:00:00 2001 From: Sam Tuke Date: Wed, 1 Aug 2012 14:11:41 +0100 Subject: Made dependencies of Kaymanager::setFileKey() explicit using dependency injection --- apps/files_encryption/lib/crypt.php | 822 +++++++++++++++---------------- apps/files_encryption/lib/keymanager.php | 21 +- apps/files_encryption/lib/proxy.php | 5 +- 3 files changed, 427 insertions(+), 421 deletions(-) (limited to 'apps/files_encryption/lib') diff --git a/apps/files_encryption/lib/crypt.php b/apps/files_encryption/lib/crypt.php index 8cd8de73bce..7e50c900fa1 100644 --- a/apps/files_encryption/lib/crypt.php +++ b/apps/files_encryption/lib/crypt.php @@ -1,37 +1,37 @@ -. - * - */ - -namespace OCA_Encryption; - -/** - * Class for common cryptography functionality - */ - -class Crypt { - +. + * + */ + +namespace OCA_Encryption; + +/** + * Class for common cryptography functionality + */ + +class Crypt { + /** - * @brief return encryption mode client or server side encryption + * @brief return encryption mode client or server side encryption * @param string user name (use system wide setting if name=null) * @return string 'client' or 'server' */ @@ -48,382 +48,382 @@ class Crypt { } return $mode; - } - - /** - * @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 Check if a file's contents contains an IV and is symmetrically encrypted - * @return true / false - */ - public static function isEncryptedContent( $content ) { - - if ( !$content ) { - - return false; - - } - - // Fetch encryption metadata from end of file - $meta = substr( $content, -22 ); - - // Fetch IV from end of file - $iv = substr( $meta, -16 ); - - // Fetch identifier from start of metadata - $identifier = substr( $meta, 0, 6 ); - - if ( $identifier == '00iv00') { - - return true; - - } else { - - return false; - - } - - } - - /** - * @brief Check if a file is encrypted via legacy system - * @return true / false - */ - public static function isLegacyEncryptedContent( $content, $path ) { - - // Fetch all file metadata from DB - $metadata = \OC_FileCache_Cached::get( $content, '' ); - - // If a file is flagged with encryption in DB, but isn't a valid content + IV combination, it's probably using the legacy encryption system - if ( - $content - and isset( $metadata['encrypted'] ) - and $metadata['encrypted'] === true - and !self::isEncryptedContent( $content ) - ) { - - return true; - - } else { - - return false; - - } - - } - - /** - * @brief Symmetrically encrypt a string - * @returns encrypted file - */ - public static function encrypt( $plainContent, $iv, $passphrase = '' ) { - - if ( $encryptedContent = openssl_encrypt( $plainContent, 'AES-128-CFB', $passphrase, false, $iv ) ) { - - return $encryptedContent; - - } else { - - \OC_Log::write( 'Encryption library', 'Encryption (symmetric) of content failed' , \OC_Log::ERROR ); - - return false; - - } - - } - - /** - * @brief Symmetrically decrypt a string - * @returns decrypted file - */ - public static function decrypt( $encryptedContent, $iv, $passphrase ) { - - if ( $plainContent = openssl_decrypt( $encryptedContent, 'AES-128-CFB', $passphrase, false, $iv ) ) { - - return $plainContent; - - - } else { - - \OC_Log::write( 'Encryption library', 'Decryption (symmetric) of content failed' , \OC_Log::ERROR ); - - return false; - - } - - } - - /** - * @brief Symmetrically encrypts a string and returns keyfile content - * @param $plainContent content to be encrypted in keyfile - * @returns encrypted content combined with IV - * @note IV need not be specified, as it will be stored in the returned keyfile - * and remain accessible therein. - */ - public static function symmetricEncryptFileContent( $plainContent, $passphrase = '' ) { - - if ( !$plainContent ) { - - return false; - - } - - $iv = self::generateIv(); - - if ( $encryptedContent = self::encrypt( $plainContent, $iv, $passphrase ) ) { - - // Combine content to encrypt with IV identifier and actual IV - $combinedKeyfile = $encryptedContent . '00iv00' . $iv; - - return $combinedKeyfile; - - } else { - - \OC_Log::write( 'Encryption library', 'Encryption (symmetric) of keyfile content failed' , \OC_Log::ERROR ); - - return false; - - } - - } - - - /** - * @brief Symmetrically decrypts keyfile content - * @param string $source - * @param string $target - * @param string $key the decryption key - * - * This function decrypts a file - */ - public static function symmetricDecryptFileContent( $keyfileContent, $passphrase = '' ) { - - if ( !$keyfileContent ) { - - return false; - - } - - // Fetch IV from end of file - $iv = substr( $keyfileContent, -16 ); - - // Remove IV and IV identifier text to expose encrypted content - $encryptedContent = substr( $keyfileContent, 0, -22 ); - - if ( $plainContent = self::decrypt( $encryptedContent, $iv, $passphrase ) ) { - - return $plainContent; - - } else { - - \OC_Log::write( 'Encryption library', 'Decryption (symmetric) of keyfile content failed' , \OC_Log::ERROR ); - - return false; - - } - - } - - /** - * @brief Creates symmetric keyfile content using a generated key - * @param string $plainContent content to be encrypted - * @returns array keys: key, encrypted - * @note symmetricDecryptFileContent() can be used to decrypt files created using this method - * - * This function decrypts a file - */ - public static function symmetricEncryptFileContentKeyfile( $plainContent ) { - - $key = self::generateKey(); - - if( $encryptedContent = self::symmetricEncryptFileContent( $plainContent, $key ) ) { - - return array( - 'key' => $key - , 'encrypted' => $encryptedContent - ); - - } else { - - return false; - - } - - } - - /** - * @brief Create asymmetrically encrypted keyfile content using a generated key - * @param string $plainContent content to be encrypted - * @returns array keys: key, encrypted - * @note symmetricDecryptFileContent() can be used to decrypt files created using this method - * - * This function decrypts a file - */ - public static function multiKeyEncrypt( $plainContent, array $publicKeys ) { - - $envKeys = array(); - - if( openssl_seal( $plainContent, $sealed, $envKeys, $publicKeys ) ) { - - return array( - 'keys' => $envKeys - , 'encrypted' => $sealed - ); - - } else { - - return false; - - } - - } - - /** - * @brief Asymmetrically encrypt a file using multiple public keys - * @param string $plainContent content to be encrypted - * @returns array keys: key, encrypted - * @note symmetricDecryptFileContent() can be used to decrypt files created using this method - * - * This function decrypts a file - */ - public static function multiKeyDecrypt( $encryptedContent, $envKey, $privateKey ) { - - if ( !$encryptedContent ) { - - return false; - - } - - if ( openssl_open( $encryptedContent, $plainContent, $envKey, $privateKey ) ) { - - return $plainContent; - - } else { - - \OC_Log::write( 'Encryption library', 'Decryption (asymmetric) of sealed content failed' , \OC_Log::ERROR ); - - return false; - - } - - } - - /** - * @brief Asymetrically encrypt a string 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; - - } - - /** - * @brief Generate a pseudo random 1024kb ASCII key - * @returns $key Generated key - */ - public static function generateIv() { - - if ( $random = openssl_random_pseudo_bytes( 13, $strong ) ) { - - if ( !$strong ) { - - // If OpenSSL indicates randomness is insecure, log error - \OC_Log::write( 'Encryption library', 'Insecure symmetric key was generated using openssl_random_pseudo_bytes()' , \OC_Log::WARN ); - - } - - $iv = substr( base64_encode( $random ), 0, -4 ); - - return $iv; - - } else { - - return false; - - } - - } - - /** - * @brief Generate a pseudo random 1024kb ASCII key - * @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 ); - - // Generate key - if ( $key = base64_encode( openssl_random_pseudo_bytes( 768000, $strong ) ) ) { - - if ( !$strong ) { - - // If OpenSSL indicates randomness is insecure, log error - \OC_Log::write( 'Encryption library', 'Insecure symmetric key was generated using openssl_random_pseudo_bytes()' , \OC_Log::WARN ); - - } - - return $key; - - } else { - - return false; - - } - - } - - public static function changekeypasscode($oldPassword, $newPassword) { - if(OCP\User::isLoggedIn()){ - $username=OCP\USER::getUser(); - $view=new OC_FilesystemView('/'.$username); - - // read old key - $key=$view->file_get_contents('/encryption.key'); - - // decrypt key with old passcode - $key=OC_Crypt::decrypt($key, $oldPassword); - - // encrypt again with new passcode - $key=OC_Crypt::encrypt($key, $newPassword); - - // store the new key - $view->file_put_contents('/encryption.key', $key ); - } - } - -} - + } + + /** + * @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 Check if a file's contents contains an IV and is symmetrically encrypted + * @return true / false + */ + public static function isEncryptedContent( $content ) { + + if ( !$content ) { + + return false; + + } + + // Fetch encryption metadata from end of file + $meta = substr( $content, -22 ); + + // Fetch IV from end of file + $iv = substr( $meta, -16 ); + + // Fetch identifier from start of metadata + $identifier = substr( $meta, 0, 6 ); + + if ( $identifier == '00iv00') { + + return true; + + } else { + + return false; + + } + + } + + /** + * @brief Check if a file is encrypted via legacy system + * @return true / false + */ + public static function isLegacyEncryptedContent( $content, $path ) { + + // Fetch all file metadata from DB + $metadata = \OC_FileCache_Cached::get( $content, '' ); + + // If a file is flagged with encryption in DB, but isn't a valid content + IV combination, it's probably using the legacy encryption system + if ( + $content + and isset( $metadata['encrypted'] ) + and $metadata['encrypted'] === true + and !self::isEncryptedContent( $content ) + ) { + + return true; + + } else { + + return false; + + } + + } + + /** + * @brief Symmetrically encrypt a string + * @returns encrypted file + */ + public static function encrypt( $plainContent, $iv, $passphrase = '' ) { + + if ( $encryptedContent = openssl_encrypt( $plainContent, 'AES-128-CFB', $passphrase, false, $iv ) ) { + + return $encryptedContent; + + } else { + + \OC_Log::write( 'Encryption library', 'Encryption (symmetric) of content failed' , \OC_Log::ERROR ); + + return false; + + } + + } + + /** + * @brief Symmetrically decrypt a string + * @returns decrypted file + */ + public static function decrypt( $encryptedContent, $iv, $passphrase ) { + + if ( $plainContent = openssl_decrypt( $encryptedContent, 'AES-128-CFB', $passphrase, false, $iv ) ) { + + return $plainContent; + + + } else { + + \OC_Log::write( 'Encryption library', 'Decryption (symmetric) of content failed' , \OC_Log::ERROR ); + + return false; + + } + + } + + /** + * @brief Symmetrically encrypts a string and returns keyfile content + * @param $plainContent content to be encrypted in keyfile + * @returns encrypted content combined with IV + * @note IV need not be specified, as it will be stored in the returned keyfile + * and remain accessible therein. + */ + public static function symmetricEncryptFileContent( $plainContent, $passphrase = '' ) { + + if ( !$plainContent ) { + + return false; + + } + + $iv = self::generateIv(); + + if ( $encryptedContent = self::encrypt( $plainContent, $iv, $passphrase ) ) { + + // Combine content to encrypt with IV identifier and actual IV + $combinedKeyfile = $encryptedContent . '00iv00' . $iv; + + return $combinedKeyfile; + + } else { + + \OC_Log::write( 'Encryption library', 'Encryption (symmetric) of keyfile content failed' , \OC_Log::ERROR ); + + return false; + + } + + } + + + /** + * @brief Symmetrically decrypts keyfile content + * @param string $source + * @param string $target + * @param string $key the decryption key + * + * This function decrypts a file + */ + public static function symmetricDecryptFileContent( $keyfileContent, $passphrase = '' ) { + + if ( !$keyfileContent ) { + + return false; + + } + + // Fetch IV from end of file + $iv = substr( $keyfileContent, -16 ); + + // Remove IV and IV identifier text to expose encrypted content + $encryptedContent = substr( $keyfileContent, 0, -22 ); + + if ( $plainContent = self::decrypt( $encryptedContent, $iv, $passphrase ) ) { + + return $plainContent; + + } else { + + \OC_Log::write( 'Encryption library', 'Decryption (symmetric) of keyfile content failed' , \OC_Log::ERROR ); + + return false; + + } + + } + + /** + * @brief Creates symmetric keyfile content using a generated key + * @param string $plainContent content to be encrypted + * @returns array keys: key, encrypted + * @note symmetricDecryptFileContent() can be used to decrypt files created using this method + * + * This function decrypts a file + */ + public static function symmetricEncryptFileContentKeyfile( $plainContent ) { + + $key = self::generateKey(); + + if( $encryptedContent = self::symmetricEncryptFileContent( $plainContent, $key ) ) { + + return array( + 'key' => $key + , 'encrypted' => $encryptedContent + ); + + } else { + + return false; + + } + + } + + /** + * @brief Create asymmetrically encrypted keyfile content using a generated key + * @param string $plainContent content to be encrypted + * @returns array keys: key, encrypted + * @note symmetricDecryptFileContent() can be used to decrypt files created using this method + * + * This function decrypts a file + */ + public static function multiKeyEncrypt( $plainContent, array $publicKeys ) { + + $envKeys = array(); + + if( openssl_seal( $plainContent, $sealed, $envKeys, $publicKeys ) ) { + + return array( + 'keys' => $envKeys + , 'encrypted' => $sealed + ); + + } else { + + return false; + + } + + } + + /** + * @brief Asymmetrically encrypt a file using multiple public keys + * @param string $plainContent content to be encrypted + * @returns array keys: key, encrypted + * @note symmetricDecryptFileContent() can be used to decrypt files created using this method + * + * This function decrypts a file + */ + public static function multiKeyDecrypt( $encryptedContent, $envKey, $privateKey ) { + + if ( !$encryptedContent ) { + + return false; + + } + + if ( openssl_open( $encryptedContent, $plainContent, $envKey, $privateKey ) ) { + + return $plainContent; + + } else { + + \OC_Log::write( 'Encryption library', 'Decryption (asymmetric) of sealed content failed' , \OC_Log::ERROR ); + + return false; + + } + + } + + /** + * @brief Asymetrically encrypt a string 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; + + } + + /** + * @brief Generate a pseudo random 1024kb ASCII key + * @returns $key Generated key + */ + public static function generateIv() { + + if ( $random = openssl_random_pseudo_bytes( 13, $strong ) ) { + + if ( !$strong ) { + + // If OpenSSL indicates randomness is insecure, log error + \OC_Log::write( 'Encryption library', 'Insecure symmetric key was generated using openssl_random_pseudo_bytes()' , \OC_Log::WARN ); + + } + + $iv = substr( base64_encode( $random ), 0, -4 ); + + return $iv; + + } else { + + return false; + + } + + } + + /** + * @brief Generate a pseudo random 1024kb ASCII key + * @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 ); + + // Generate key + if ( $key = base64_encode( openssl_random_pseudo_bytes( 768000, $strong ) ) ) { + + if ( !$strong ) { + + // If OpenSSL indicates randomness is insecure, log error + \OC_Log::write( 'Encryption library', 'Insecure symmetric key was generated using openssl_random_pseudo_bytes()' , \OC_Log::WARN ); + + } + + return $key; + + } else { + + return false; + + } + + } + + public static function changekeypasscode($oldPassword, $newPassword) { + if(OCP\User::isLoggedIn()){ + $username=OCP\USER::getUser(); + $view=new OC_FilesystemView('/'.$username); + + // read old key + $key=$view->file_get_contents('/encryption.key'); + + // decrypt key with old passcode + $key=OC_Crypt::decrypt($key, $oldPassword); + + // encrypt again with new passcode + $key=OC_Crypt::encrypt($key, $newPassword); + + // store the new key + $view->file_put_contents('/encryption.key', $key ); + } + } + +} + ?> \ No newline at end of file diff --git a/apps/files_encryption/lib/keymanager.php b/apps/files_encryption/lib/keymanager.php index 0c76bf27a52..7f67fc7e5ea 100644 --- a/apps/files_encryption/lib/keymanager.php +++ b/apps/files_encryption/lib/keymanager.php @@ -27,7 +27,7 @@ namespace OCA_Encryption; */ class Keymanager { - # TODO: make all dependencies explicit, such as ocfsview objects, by adding them as method arguments (dependency injection) + # TODO: make all dependencies (including static classes) explicit, such as ocfsview objects, by adding them as method arguments (dependency injection) /** * @brief retrieve private key from a user @@ -128,35 +128,38 @@ class Keymanager { * @param string $key * @return bool true/false */ - public static function setFileKey( $userId, $path, $key ) { + public static function setFileKey( $user, $path, $key, $view, $dbClassName, $fileProxyClassName ) { - \OC_FileProxy::$enabled = false; + $fileProxyClassName::$enabled = false; $targetpath = ltrim( $path, '/' ); - $user = $userId; // update $keytarget and $user if key belongs 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 = ?" ); + $query = $dbClassName::prepare( "SELECT uid_owner, source, target FROM `*PREFIX*sharing` WHERE target = ? AND uid_shared_with = ?" ); - $result = $query->execute( array ( '/'.$userId.'/files/'.$targetpath, $userId ) ); + $result = $query->execute( array ( '/'.$user.'/files/'.$targetpath, $user ) ); if ( $row = $result->fetchRow( ) ) { + $targetpath = $row['source']; + $targetpath_parts=explode( '/',$targetpath ); + $user = $targetpath_parts[1]; + $targetpath = str_replace( '/'.$user.'/files/', '', $targetpath ); + //TODO: check for write permission on shared file once the new sharing API is in place + } - $view = new \OC_FilesystemView( '/' . $user . '/files_encryption/keyfiles' ); - $path_parts = pathinfo( $targetpath ); if ( !$view->file_exists( $path_parts['dirname'] ) ) $view->mkdir( $path_parts['dirname'] ); $result = $view->file_put_contents( '/' . $targetpath . '.key', $key ); - \OC_FileProxy::$enabled = true; + $fileProxyClassName::$enabled = true; return $result; } diff --git a/apps/files_encryption/lib/proxy.php b/apps/files_encryption/lib/proxy.php index c1956ad0216..94f427f2f22 100644 --- a/apps/files_encryption/lib/proxy.php +++ b/apps/files_encryption/lib/proxy.php @@ -115,8 +115,11 @@ class Proxy extends \OC_FileProxy { $filePath = '/' . implode( '/', $filePath ); + # TODO: make keyfile dir dynamic from app config + $view = new \OC_FilesystemView( '/' . \OCP\USER::getUser() . '/files_encryption/keyfiles' ); + // Save keyfile for newly encrypted file in parallel directory tree - Keymanager::setFileKey( \OCP\USER::getUser(), $filePath, $encrypted['key'] ); + Keymanager::setFileKey( \OCP\USER::getUser(), $filePath, $encrypted['key'], $view, '\OC_DB', '\OC_FileProxy' ); // Update the file cache with file info \OC_FileCache::put( $path, array( 'encrypted'=>true, 'size' => $size ), '' ); -- cgit v1.2.3 From 6b058cd3594d80a27097b04507355b219f4726e4 Mon Sep 17 00:00:00 2001 From: Bjoern Schiessle Date: Thu, 2 Aug 2012 10:39:30 +0200 Subject: allow user to choose encryption mode --- apps/files_encryption/ajax/changemode.php | 17 +++++++++++++++ apps/files_encryption/appinfo/app.php | 3 ++- apps/files_encryption/appinfo/database.xml | 24 +++++++++++++++++++++ apps/files_encryption/appinfo/version | 2 +- apps/files_encryption/js/settings-personal.js | 22 +++++++++++++++++++ apps/files_encryption/js/settings.js | 11 +++++----- apps/files_encryption/lib/crypt.php | 19 +++++++++------- apps/files_encryption/settings-personal.php | 25 ++++++++++++++++++++++ apps/files_encryption/settings.php | 1 - .../templates/settings-personal.php | 14 ++++++++++++ apps/files_encryption/templates/settings.php | 3 ++- apps/files_sharing/appinfo/database.xml | 2 +- 12 files changed, 124 insertions(+), 19 deletions(-) create mode 100644 apps/files_encryption/ajax/changemode.php create mode 100644 apps/files_encryption/appinfo/database.xml create mode 100644 apps/files_encryption/js/settings-personal.js create mode 100644 apps/files_encryption/settings-personal.php create mode 100644 apps/files_encryption/templates/settings-personal.php (limited to 'apps/files_encryption/lib') diff --git a/apps/files_encryption/ajax/changemode.php b/apps/files_encryption/ajax/changemode.php new file mode 100644 index 00000000000..0e6678e4bba --- /dev/null +++ b/apps/files_encryption/ajax/changemode.php @@ -0,0 +1,17 @@ +execute(array(\OCP\User::getUser())); + +if ($row = $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( ?, ? )' ); +} +$query->execute(array($mode, \OCP\User::getUser())); \ No newline at end of file diff --git a/apps/files_encryption/appinfo/app.php b/apps/files_encryption/appinfo/app.php index 2047bdbb1fb..1c2fafa8da6 100644 --- a/apps/files_encryption/appinfo/app.php +++ b/apps/files_encryption/appinfo/app.php @@ -28,4 +28,5 @@ and OCP\User::isLoggedIn() } -OCP\App::registerAdmin('files_encryption', 'settings'); \ No newline at end of file +OCP\App::registerAdmin('files_encryption', 'settings'); +OCP\App::registerPersonal('files_encryption','settings-personal'); \ No newline at end of file diff --git a/apps/files_encryption/appinfo/database.xml b/apps/files_encryption/appinfo/database.xml new file mode 100644 index 00000000000..d294c35d63d --- /dev/null +++ b/apps/files_encryption/appinfo/database.xml @@ -0,0 +1,24 @@ + + + *dbname* + true + false + utf8 + + *dbprefix*encryption + + + uid + text + true + 64 + + + mode + text + true + 64 + + +
+
\ No newline at end of file diff --git a/apps/files_encryption/appinfo/version b/apps/files_encryption/appinfo/version index 2f4536184bc..7dff5b89211 100644 --- a/apps/files_encryption/appinfo/version +++ b/apps/files_encryption/appinfo/version @@ -1 +1 @@ -0.2 \ No newline at end of file +0.2.1 \ No newline at end of file diff --git a/apps/files_encryption/js/settings-personal.js b/apps/files_encryption/js/settings-personal.js new file mode 100644 index 00000000000..76f9a37f69e --- /dev/null +++ b/apps/files_encryption/js/settings-personal.js @@ -0,0 +1,22 @@ +/** + * Copyright (c) 2012, Bjoern Schiessle + * This file is licensed under the Affero General Public License version 3 or later. + * See the COPYING-README file. + */ + +$(document).ready(function(){ + console.log("loaded!"); + $('input[name=encryption_mode]').change(function(){ + console.log("HERE!!!!!!!!!!!"); + 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', 'changemode.php'), { mode: 'client' }); + else if (server) + $.post(OC.filePath('files_encryption', 'ajax', 'changemode.php'), { mode: 'server' }); + else + $.post(OC.filePath('files_encryption', 'ajax', 'changemode.php'), { mode: 'none' }); + }) +}) \ No newline at end of file diff --git a/apps/files_encryption/js/settings.js b/apps/files_encryption/js/settings.js index 49dcf2bfca3..d4dc470ced8 100644 --- a/apps/files_encryption/js/settings.js +++ b/apps/files_encryption/js/settings.js @@ -17,19 +17,18 @@ $(document).ready(function(){ OC.AppConfig.setValue('files_encryption','type_blacklist',blackList); } - $('#enable_encryption').change(function(){ - var checked=$('#enable_encryption').is(':checked'); - OC.AppConfig.setValue('files_encryption','enable_encryption',(checked)?'true':'false'); - }) $('input[name=encryption_mode]').change(function(){ 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) OC.AppConfig.setValue('files_encryption','mode','client'); - if (server) + else if (server) OC.AppConfig.setValue('files_encryption','mode','server'); - if (none) + else if (user) + OC.AppConfig.setValue('files_encryption','mode','user'); + else OC.AppConfig.setValue('files_encryption','mode','none'); }) }) \ No newline at end of file diff --git a/apps/files_encryption/lib/crypt.php b/apps/files_encryption/lib/crypt.php index 7e50c900fa1..90b24dc8ddb 100644 --- a/apps/files_encryption/lib/crypt.php +++ b/apps/files_encryption/lib/crypt.php @@ -37,14 +37,17 @@ class Crypt { */ public static function mode( $user = null ) { - $mode = \OC_Appconfig::getValue( 'files_encryption', 'mode', 'unknown' ); - - if ( $mode == 'unknown' ) { - - error_log('no encryption mode configured'); - - return false; - + $mode = \OC_Appconfig::getValue( 'files_encryption', 'mode', 'none' ); + + if ( $mode == 'user') { + $mode = 'none'; + if ( $user ) { + $query = \OC_DB::prepare( "SELECT mode FROM *PREFIX*encryption WHERE uid = ?" ); + $result = $query->execute(array($user)); + if ($row = $result->fetchRow()){ + $mode = $row['mode']; + } + } } return $mode; diff --git a/apps/files_encryption/settings-personal.php b/apps/files_encryption/settings-personal.php new file mode 100644 index 00000000000..d4071e75ff4 --- /dev/null +++ b/apps/files_encryption/settings-personal.php @@ -0,0 +1,25 @@ +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; + +?> \ No newline at end of file diff --git a/apps/files_encryption/settings.php b/apps/files_encryption/settings.php index a4e91627dd9..963fdf826c1 100644 --- a/apps/files_encryption/settings.php +++ b/apps/files_encryption/settings.php @@ -8,7 +8,6 @@ $tmpl = new OCP\Template( 'files_encryption', 'settings'); $blackList=explode(',',OCP\Config::getAppValue('files_encryption','type_blacklist','jpg,png,jpeg,avi,mpg,mpeg,mkv,mp3,oga,ogv,ogg')); -$enabled=(OCP\Config::getAppValue('files_encryption','enable_encryption','true')=='true'); $tmpl->assign('blacklist',$blackList); $tmpl->assign('encryption_mode',\OC_Appconfig::getValue('files_encryption', 'mode', 'none')); diff --git a/apps/files_encryption/templates/settings-personal.php b/apps/files_encryption/templates/settings-personal.php new file mode 100644 index 00000000000..2b51ab8694d --- /dev/null +++ b/apps/files_encryption/templates/settings-personal.php @@ -0,0 +1,14 @@ + +
+
+ +Choose encryption mode: + +

+ /> Client side encryption (most secure but makes it impossible to access your data from the web interface)
+ /> Server side encryption (allows you to access your files from the web interface and the desktop client)
+ /> None (no encryption at all)
+

+
+
+ diff --git a/apps/files_encryption/templates/settings.php b/apps/files_encryption/templates/settings.php index 38c89ecde34..2a3c1fd5bdf 100644 --- a/apps/files_encryption/templates/settings.php +++ b/apps/files_encryption/templates/settings.php @@ -1,4 +1,4 @@ -
+
Choose encryption mode: @@ -6,6 +6,7 @@

/> Client side encryption (most secure but makes it impossible to access your data from the web interface)
/> Server side encryption (allows you to access your files from the web interface and the desktop client)
+ /> User specific (let the user decide)
/> None (no encryption at all)

diff --git a/apps/files_sharing/appinfo/database.xml b/apps/files_sharing/appinfo/database.xml index c5cb632d4fe..bb6275fbacf 100644 --- a/apps/files_sharing/appinfo/database.xml +++ b/apps/files_sharing/appinfo/database.xml @@ -39,4 +39,4 @@ - + \ No newline at end of file -- cgit v1.2.3 From d5808f07ca729c99482a44aa3e320ad5060cb86a Mon Sep 17 00:00:00 2001 From: Bjoern Schiessle Date: Fri, 3 Aug 2012 11:49:55 +0200 Subject: return a list of all public keys for a given file --- apps/files_encryption/lib/crypt.php | 9 ++++--- apps/files_encryption/lib/keymanager.php | 44 ++++++++++++++++++++++++++++---- lib/ocs.php | 37 ++++++++++++--------------- 3 files changed, 61 insertions(+), 29 deletions(-) (limited to 'apps/files_encryption/lib') diff --git a/apps/files_encryption/lib/crypt.php b/apps/files_encryption/lib/crypt.php index 90b24dc8ddb..64bbc17ec11 100644 --- a/apps/files_encryption/lib/crypt.php +++ b/apps/files_encryption/lib/crypt.php @@ -38,8 +38,11 @@ class Crypt { public static function mode( $user = null ) { $mode = \OC_Appconfig::getValue( 'files_encryption', 'mode', 'none' ); - - if ( $mode == 'user') { + + if ( $mode == 'user') { + if ( !$user ) { + $user = \OCP\User::getUser(); + } $mode = 'none'; if ( $user ) { $query = \OC_DB::prepare( "SELECT mode FROM *PREFIX*encryption WHERE uid = ?" ); @@ -49,7 +52,7 @@ class Crypt { } } } - + return $mode; } diff --git a/apps/files_encryption/lib/keymanager.php b/apps/files_encryption/lib/keymanager.php index 7f67fc7e5ea..6e3dcaf0ad9 100644 --- a/apps/files_encryption/lib/keymanager.php +++ b/apps/files_encryption/lib/keymanager.php @@ -43,14 +43,48 @@ class Keymanager { } /** - * @brief retrieve public key from a user + * @brief retrieve a list of the public key from all users with access to the file * - * @param string user name - * @return string private key or false + * @param string path to file + * @return array of public keys for the given file */ - public static function getPublicKey($user) { + public static function getPublicKeys($path) { + $userId = \OCP\User::getUser(); + $path = ltrim( $path, '/' ); + $filepath = '/'.$userId.'/files/'.$path; + + // 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/' ); - return $view->file_get_contents($user.'.public.key'); + + $keylist = array(); + $count = 0; + foreach ($users as $user) { + $keylist['key'.++$count] = $view->file_get_contents($user.'.public.key'); + } + + return $keylist; + } /** diff --git a/lib/ocs.php b/lib/ocs.php index 17ae649deb6..6617beb8066 100644 --- a/lib/ocs.php +++ b/lib/ocs.php @@ -169,9 +169,9 @@ class OC_OCS { OC_OCS::quotaset($format,$user,$quota); // keygetpublic - }elseif(($method=='get') and ($ex[$paracount-6] == 'v1.php') and ($ex[$paracount-5]=='cloud') and ($ex[$paracount-4] == 'user') and ($ex[$paracount-2] == 'publickey')){ - $user=$ex[$paracount-3]; - OC_OCS::publicKeyGet($format,$user); + }elseif(($method=='get') and ($ex[$paracount-6] == 'v1.php') and ($ex[$paracount-5]=='cloud') and ($ex[$paracount-4] == 'file') and ($ex[$paracount-2] == 'publickeys')){ + $file=urldecode($ex[$paracount-3]); + OC_OCS::publicKeyGet($format,$file); //keysetpublic }elseif(($method=='post') and ($ex[$paracount-6] == 'v1.php') and ($ex[$paracount-5]=='cloud') and ($ex[$paracount-4] == 'user') and ($ex[$paracount-2] == 'publickey')){ @@ -671,27 +671,22 @@ class OC_OCS { /** * get the public key of a user * @param string $format - * @param string $user - * @return string xml/json + * @param string $file + * @return string xml/json list of public keys */ - private static function publicKeyGet($format, $user) { + private static function publicKeyGet($format, $file) { $login=OC_OCS::checkpassword(); - if(OC_App::isEnabled('files_encryption') && OCA_Encryption\Crypt::mode($user) === 'client') { - if(OC_User::userExists($user)){ - if (($key = OCA_Encryption\Keymanager::getPublicKey($user))) { - $xml=array(); - $xml['key'] = $key; - $txt=OC_OCS::generatexml($format, 'ok', 100, '', $xml, 'cloud', '', 1, 0, 0); - echo($txt); - } - else { - echo self::generateXml('', 'fail', 404, 'public key does not exist'); - } - } else { - echo self::generateXml('', 'fail', 300, 'User does not exist'); + if(OC_App::isEnabled('files_encryption') && OCA_Encryption\Crypt::mode() === 'client') { + if (($keys = OCA_Encryption\Keymanager::getPublicKeys($file))) { + $xml=$keys; + $txt=OC_OCS::generatexml($format, 'ok', 100, '', $xml, 'cloud', '', 1, 0, 0); + echo($txt); + } + else { + echo self::generateXml('', 'fail', 404, 'public key does not exist'); } } else { - echo self::generateXml('', 'fail', 300, 'Client side encryption not enabled for user ' . $user); + echo self::generateXml('', 'fail', 300, 'Client side encryption not enabled'); } } @@ -782,7 +777,7 @@ class OC_OCS { if(OC_App::isEnabled('files_encryption') && OCA_Encryption\Crypt::mode($user) === 'client') { if (($key = OCA_Encryption\Keymanager::getFileKey($user, $file))) { $xml=array(); - $xml['key']=$key; + $xml['key']=$key; $txt=OC_OCS::generatexml($format, 'ok', 100, '', $xml, 'cloud', '', 1, 0, 0); echo($txt); } else { -- cgit v1.2.3 From 773d7b119d20d5962817cdd057bf68c8cb39d529 Mon Sep 17 00:00:00 2001 From: Bjoern Schiessle Date: Fri, 3 Aug 2012 13:52:41 +0200 Subject: OCS api calls cleanup --- apps/files_encryption/lib/keymanager.php | 28 +++-- lib/ocs.php | 172 +++++++++++++------------------ 2 files changed, 83 insertions(+), 117 deletions(-) (limited to 'apps/files_encryption/lib') diff --git a/apps/files_encryption/lib/keymanager.php b/apps/files_encryption/lib/keymanager.php index 6e3dcaf0ad9..825a3f78fcd 100644 --- a/apps/files_encryption/lib/keymanager.php +++ b/apps/files_encryption/lib/keymanager.php @@ -32,11 +32,11 @@ class Keymanager { /** * @brief retrieve private key from a user * - * @param string user name * @return string private key or false */ - public static function getPrivateKey( $user ) { + public static function getPrivateKey() { + $user = \OCP\User::getUser(); $view = new \OC_FilesystemView( '/' . $user . '/' . 'files_encryption' ); return $view->file_get_contents( '/' . $user.'.private.key' ); @@ -91,17 +91,16 @@ class Keymanager { * @brief retrieve file encryption key * * @param string file name - * @param string user name of the file owner * @return string file key or false */ - public static function getFileKey( $userId, $path ) { + public static function getFileKey( $path ) { $keypath = ltrim( $path, '/' ); - $user = $userId; + $user = \OCP\User::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 ('/'.$userId.'/files/'.$keypath, $userId)); + $result = $query->execute( array ('/'.$user.'/files/'.$keypath, $user)); if ($row = $result->fetchRow()){ $keypath = $row['source']; $keypath_parts=explode('/',$keypath); @@ -114,16 +113,16 @@ class Keymanager { } /** - * @brief store private key from a user + * @brief store private key from the user * - * @param string user name * @param string key * @return bool true/false */ - public static function setPrivateKey($user, $key) { + public static function setPrivateKey($key) { \OC_FileProxy::$enabled = false; + $user = \OCP\User::getUser(); $view = new \OC_FilesystemView('/'.$user.'/files_encryption'); if (!$view->file_exists('')) $view->mkdir(''); $result = $view->file_put_contents($user.'.private.key', $key); @@ -135,19 +134,18 @@ class Keymanager { /** - * @brief store public key from a user + * @brief store public key of the user * - * @param string user name * @param string key * @return bool true/false */ - public static function setPublicKey($user, $key) { + public static function setPublicKey($key) { \OC_FileProxy::$enabled = false; $view = new \OC_FilesystemView('/public-keys'); if (!$view->file_exists('')) $view->mkdir(''); - $result = $view->file_put_contents($user.'.public.key', $key); + $result = $view->file_put_contents(\OCP\User::getUser().'.public.key', $key); \OC_FileProxy::$enabled = true; @@ -157,16 +155,16 @@ class Keymanager { /** * @brief store file encryption key * - * @param string $userId name of the file owner * @param string $path relative path of the file, including filename * @param string $key * @return bool true/false */ - public static function setFileKey( $user, $path, $key, $view, $dbClassName, $fileProxyClassName ) { + public static function setFileKey( $path, $key, $view, $dbClassName, $fileProxyClassName ) { $fileProxyClassName::$enabled = false; $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 = ?" ); diff --git a/lib/ocs.php b/lib/ocs.php index 6617beb8066..97314d71ced 100644 --- a/lib/ocs.php +++ b/lib/ocs.php @@ -174,34 +174,29 @@ class OC_OCS { OC_OCS::publicKeyGet($format,$file); //keysetpublic - }elseif(($method=='post') and ($ex[$paracount-6] == 'v1.php') and ($ex[$paracount-5]=='cloud') and ($ex[$paracount-4] == 'user') and ($ex[$paracount-2] == 'publickey')){ - $user=$ex[$paracount-3]; + }elseif(($method=='post') and ($ex[$paracount-4] == 'v1.php') and ($ex[$paracount-3]=='cloud') and ($ex[$paracount-2] == 'publickey')){ $key = self::readData('post', 'key', 'string'); - OC_OCS::publicKeySet($format,$user, $key); + OC_OCS::publicKeySet($format, $key); // keygetprivate - }elseif(($method=='get') and ($ex[$paracount-6] == 'v1.php') and ($ex[$paracount-5]=='cloud') and ($ex[$paracount-4] == 'user') and ($ex[$paracount-2] == 'privatekey')){ - $user=$ex[$paracount-3]; - OC_OCS::privateKeyGet($format,$user); + }elseif(($method=='get') and ($ex[$paracount-4] == 'v1.php') and ($ex[$paracount-3]=='cloud') and ($ex[$paracount-2] == 'privatekey')){ + OC_OCS::privateKeyGet($format); //keysetprivate - }elseif(($method=='post') and ($ex[$paracount-6] == 'v1.php') and ($ex[$paracount-5]=='cloud') and ($ex[$paracount-4] == 'user') and ($ex[$paracount-2] == 'privatekey')){ - $user=$ex[$paracount-3]; + }elseif(($method=='post') and ($ex[$paracount-4] == 'v1.php') and ($ex[$paracount-3]=='cloud') and ($ex[$paracount-2] == 'privatekey')){ $key = self::readData('post', 'key', 'string'); - OC_OCS::privateKeySet($format,$user, $key); - + OC_OCS::privateKeySet($format, $key); + // keygetfiles - }elseif(($method=='get') and ($ex[$paracount-7] == 'v1.php') and ($ex[$paracount-6]=='cloud') and ($ex[$paracount-5] == 'user') and ($ex[$paracount-3] == 'filekey')){ - $user=$ex[$paracount-4]; - $file = urldecode($ex[$paracount-2]); - OC_OCS::fileKeyGet($format,$user, $file); + }elseif(($method=='get') and ($ex[$paracount-6] == 'v1.php') and ($ex[$paracount-5]=='cloud') and ($ex[$paracount-4] == 'file') and ($ex[$paracount-2] == 'filekey')){ + $file = urldecode($ex[$paracount-3]); + OC_OCS::fileKeyGet($format, $file); //keysetfiles - }elseif(($method=='post') and ($ex[$paracount-6] == 'v1.php') and ($ex[$paracount-5]=='cloud') and ($ex[$paracount-4] == 'user') and ($ex[$paracount-2] == 'filekey')){ - $user=$ex[$paracount-3]; + }elseif(($method=='post') and ($ex[$paracount-4] == 'v1.php') and ($ex[$paracount-3]=='cloud') and ($ex[$paracount-2] == 'filekey')){ $key = self::readData('post', 'key', 'string'); $file = self::readData('post', 'file', 'string'); - OC_OCS::fileKeySet($format,$user, $file, $key); + OC_OCS::fileKeySet($format, $file, $key); // add more calls here // please document all the call in the draft spec @@ -669,7 +664,7 @@ class OC_OCS { } /** - * get the public key of a user + * get the public key from all users associated with a given file * @param string $format * @param string $file * @return string xml/json list of public keys @@ -692,130 +687,103 @@ class OC_OCS { /** * set the public key of a user - * @param string $format - * @param string $user + * @param string $format * @param string $key * @return string xml/json */ - private static function publicKeySet($format, $user, $key) { + private static function publicKeySet($format, $key) { $login=OC_OCS::checkpassword(); - if(($login==$user)) { - if(OC_App::isEnabled('files_encryption') && OCA_Encryption\Crypt::mode($user) === 'client') { - if (($key = OCA_Encryption\Keymanager::setPublicKey($user, $key))) { - echo self::generateXml('', 'ok', 100, ''); - } else { - echo self::generateXml('', 'fail', 404, 'could not add your public key to the key storage'); - } + if(OC_App::isEnabled('files_encryption') && OCA_Encryption\Crypt::mode($user) === 'client') { + if (OCA_Encryption\Keymanager::setPublicKey($key)) { + echo self::generateXml('', 'ok', 100, ''); } else { - echo self::generateXml('', 'fail', 300, 'Client side encryption not enabled for user ' . $user); + echo self::generateXml('', 'fail', 404, 'could not add your public key to the key storage'); } - }else{ - echo self::generateXml('', 'fail', 300, 'You don´t have permission to access this ressource.'); + } else { + echo self::generateXml('', 'fail', 300, 'Client side encryption not enabled for user ' . $user); } } /** * get the private key of a user * @param string $format - * @param string $user * @return string xml/json */ - private static function privateKeyGet($format, $user) { - $login=OC_OCS::checkpassword(); - if(($login==$user)) { - if(OC_App::isEnabled('files_encryption') && OCA_Encryption\Crypt::mode($user) === 'client') { - if (($key = OCA_Encryption\Keymanager::getPrivateKey($user))) { - $xml=array(); - $xml['key']=$key; - $txt=OC_OCS::generatexml($format, 'ok', 100, '', $xml, 'cloud', '', 1, 0, 0); - echo($txt); - } else { - echo self::generateXml('', 'fail', 404, 'private key does not exist'); - } - } else { - echo self::generateXml('', 'fail', 300, 'Client side encryption not enabled for user ' . $user); + private static function privateKeyGet($format) { + $login=OC_OCS::checkpassword(); + if(OC_App::isEnabled('files_encryption') && OCA_Encryption\Crypt::mode($user) === 'client') { + if (($key = OCA_Encryption\Keymanager::getPrivateKey())) { + $xml=array(); + $xml['key']=$key; + $txt=OC_OCS::generatexml($format, 'ok', 100, '', $xml, 'cloud', '', 1, 0, 0); + echo($txt); + } else { + echo self::generateXml('', 'fail', 404, 'private key does not exist'); } - }else{ - echo self::generateXml('', 'fail', 300, 'You don´t have permission to access this ressource.'); + } else { + echo self::generateXml('', 'fail', 300, 'Client side encryption not enabled for user ' . $user); } } /** * set the private key of a user - * @param string $format - * @param string $user + * @param string $format * @param string $key * @return string xml/json */ - private static function privateKeySet($format, $user, $key) { + private static function privateKeySet($format, $key) { $login=OC_OCS::checkpassword(); - if(($login==$user)) { - if(OC_App::isEnabled('files_encryption') && OCA_Encryption\Crypt::mode($user) === 'client') { - if (($key = OCA_Encryption\Keymanager::setPrivateKey($user, $key))) { - echo self::generateXml('', 'ok', 100, ''); - } else { - echo self::generateXml('', 'fail', 404, 'could not add your private key to the key storage'); - } + if(OC_App::isEnabled('files_encryption') && OCA_Encryption\Crypt::mode($user) === 'client') { + if (($key = OCA_Encryption\Keymanager::setPrivateKey($key))) { + echo self::generateXml('', 'ok', 100, ''); } else { - echo self::generateXml('', 'fail', 300, 'Client side encryption not enabled for user ' . $user); + echo self::generateXml('', 'fail', 404, 'could not add your private key to the key storage'); } - }else{ - echo self::generateXml('', 'fail', 300, 'You don´t have permission to access this ressource.'); + } else { + echo self::generateXml('', 'fail', 300, 'Client side encryption not enabled for user ' . $user); } } /** * get the encryption key of a file - * @param string $format - * @param string $user + * @param string $format * @param string $file * @return string xml/json */ - private static function fileKeyGet($format, $user, $file) { - $login=OC_OCS::checkpassword(); - if(($login==$user)) { - if(OC_App::isEnabled('files_encryption') && OCA_Encryption\Crypt::mode($user) === 'client') { - if (($key = OCA_Encryption\Keymanager::getFileKey($user, $file))) { - $xml=array(); - $xml['key']=$key; - $txt=OC_OCS::generatexml($format, 'ok', 100, '', $xml, 'cloud', '', 1, 0, 0); - echo($txt); - } else { - echo self::generateXml('', 'fail', 404, 'file key does not exist'); - } - } else { - echo self::generateXml('', 'fail', 300, 'Client side encryption not enabled for user ' . $user); - } - }else{ - echo self::generateXml('', 'fail', 300, 'You don´t have permission to access this ressource.'); - } - } + private static function fileKeyGet($format, $file) { + $login=OC_OCS::checkpassword(); + if(OC_App::isEnabled('files_encryption') && OCA_Encryption\Crypt::mode($user) === 'client') { + if (($key = OCA_Encryption\Keymanager::getFileKey($file))) { + $xml=array(); + $xml['key']=$key; + $txt=OC_OCS::generatexml($format, 'ok', 100, '', $xml, 'cloud', '', 1, 0, 0); + echo($txt); + } else { + echo self::generateXml('', 'fail', 404, 'file key does not exist'); + } + } else { + echo self::generateXml('', 'fail', 300, 'Client side encryption not enabled for user ' . $user); + } + } /** * set the encryption keyn of a file - * @param string $format - * @param string $user + * @param string $format * @param string $file * @param string $key * @return string xml/json */ - private static function fileKeySet($format, $user, $file, $key) { - $login=OC_OCS::checkpassword(); - if(($login==$user)) { - if(OC_App::isEnabled('files_encryption') && OCA_Encryption\Crypt::mode($user) === 'client') { - if (($key = OCA_Encryption\Keymanager::setFileKey($user, $file, $key))) { - echo self::generateXml('', 'ok', 100, ''); - return true; - } else { - echo self::generateXml('', 'fail', 404, 'could not write key file'); - } - } else { - echo self::generateXml('', 'fail', 300, 'Client side encryption not enabled for user ' . $user); - } - }else{ - echo self::generateXml('', 'fail', 300, 'You don´t have permission to access this ressource.'); - } - return false; - } + private static function fileKeySet($format, $file, $key) { + $login=OC_OCS::checkpassword(); + if(OC_App::isEnabled('files_encryption') && OCA_Encryption\Crypt::mode($user) === 'client') { + if (($key = OCA_Encryption\Keymanager::setFileKey($file, $key))) { + echo self::generateXml('', 'ok', 100, ''); + } else { + echo self::generateXml('', 'fail', 404, 'could not write key file'); + } + } else { + echo self::generateXml('', 'fail', 300, 'Client side encryption not enabled for user ' . $user); + } + } } -- cgit v1.2.3 From 5bb3ea97407a251a26272383bfbb073ba2fc3965 Mon Sep 17 00:00:00 2001 From: Bjoern Schiessle Date: Wed, 8 Aug 2012 12:13:14 +0200 Subject: define default properties in keymanager.php --- apps/files_encryption/lib/keymanager.php | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'apps/files_encryption/lib') diff --git a/apps/files_encryption/lib/keymanager.php b/apps/files_encryption/lib/keymanager.php index 825a3f78fcd..ef1080e9d05 100644 --- a/apps/files_encryption/lib/keymanager.php +++ b/apps/files_encryption/lib/keymanager.php @@ -27,8 +27,16 @@ namespace OCA_Encryption; */ class Keymanager { + private static $defaultProperties = array('dbClassName' => \OC_DB, + 'fileProxyClassName' => \OC_FileProxy, + ); + # TODO: make all dependencies (including static classes) explicit, such as ocfsview objects, by adding them as method arguments (dependency injection) + private static function mergeProperties($properties) { + array_merge(self::$defaultProperties, $properties); + } + /** * @brief retrieve private key from a user * -- cgit v1.2.3 From d4974b6d4a9ce06dadbe419b193579eaa83393c4 Mon Sep 17 00:00:00 2001 From: Bjoern Schiessle Date: Wed, 8 Aug 2012 14:15:35 +0200 Subject: set default dependencies in keymanager.php fix calls in ocs.php --- apps/files_encryption/lib/keymanager.php | 11 +++++------ lib/ocs.php | 20 ++++++++++---------- 2 files changed, 15 insertions(+), 16 deletions(-) (limited to 'apps/files_encryption/lib') diff --git a/apps/files_encryption/lib/keymanager.php b/apps/files_encryption/lib/keymanager.php index ef1080e9d05..00817946fdf 100644 --- a/apps/files_encryption/lib/keymanager.php +++ b/apps/files_encryption/lib/keymanager.php @@ -27,10 +27,6 @@ namespace OCA_Encryption; */ class Keymanager { - private static $defaultProperties = array('dbClassName' => \OC_DB, - 'fileProxyClassName' => \OC_FileProxy, - ); - # TODO: make all dependencies (including static classes) explicit, such as ocfsview objects, by adding them as method arguments (dependency injection) private static function mergeProperties($properties) { @@ -167,8 +163,7 @@ class Keymanager { * @param string $key * @return bool true/false */ - public static function setFileKey( $path, $key, $view, $dbClassName, $fileProxyClassName ) { - + public static function setFileKey( $path, $key, $view = Null, $dbClassName = '\OC_DB', $fileProxyClassName = '\OC_FileProxy') { $fileProxyClassName::$enabled = false; $targetpath = ltrim( $path, '/' ); @@ -194,6 +189,10 @@ class Keymanager { } $path_parts = pathinfo( $targetpath ); + + if (!$view) { + $view = new \OC_FilesystemView( '/' . $user . '/files_encryption/keyfiles' ); + } if ( !$view->file_exists( $path_parts['dirname'] ) ) $view->mkdir( $path_parts['dirname'] ); diff --git a/lib/ocs.php b/lib/ocs.php index 97314d71ced..0fc208516ee 100644 --- a/lib/ocs.php +++ b/lib/ocs.php @@ -693,14 +693,14 @@ class OC_OCS { */ private static function publicKeySet($format, $key) { $login=OC_OCS::checkpassword(); - if(OC_App::isEnabled('files_encryption') && OCA_Encryption\Crypt::mode($user) === 'client') { + if(OC_App::isEnabled('files_encryption') && OCA_Encryption\Crypt::mode() === 'client') { if (OCA_Encryption\Keymanager::setPublicKey($key)) { echo self::generateXml('', 'ok', 100, ''); } else { echo self::generateXml('', 'fail', 404, 'could not add your public key to the key storage'); } } else { - echo self::generateXml('', 'fail', 300, 'Client side encryption not enabled for user ' . $user); + echo self::generateXml('', 'fail', 300, 'Client side encryption not enabled'); } } @@ -711,7 +711,7 @@ class OC_OCS { */ private static function privateKeyGet($format) { $login=OC_OCS::checkpassword(); - if(OC_App::isEnabled('files_encryption') && OCA_Encryption\Crypt::mode($user) === 'client') { + if(OC_App::isEnabled('files_encryption') && OCA_Encryption\Crypt::mode() === 'client') { if (($key = OCA_Encryption\Keymanager::getPrivateKey())) { $xml=array(); $xml['key']=$key; @@ -721,7 +721,7 @@ class OC_OCS { echo self::generateXml('', 'fail', 404, 'private key does not exist'); } } else { - echo self::generateXml('', 'fail', 300, 'Client side encryption not enabled for user ' . $user); + echo self::generateXml('', 'fail', 300, 'Client side encryption not enabled'); } } @@ -733,14 +733,14 @@ class OC_OCS { */ private static function privateKeySet($format, $key) { $login=OC_OCS::checkpassword(); - if(OC_App::isEnabled('files_encryption') && OCA_Encryption\Crypt::mode($user) === 'client') { + if(OC_App::isEnabled('files_encryption') && OCA_Encryption\Crypt::mode() === 'client') { if (($key = OCA_Encryption\Keymanager::setPrivateKey($key))) { echo self::generateXml('', 'ok', 100, ''); } else { echo self::generateXml('', 'fail', 404, 'could not add your private key to the key storage'); } } else { - echo self::generateXml('', 'fail', 300, 'Client side encryption not enabled for user ' . $user); + echo self::generateXml('', 'fail', 300, 'Client side encryption not enabled'); } } @@ -752,7 +752,7 @@ class OC_OCS { */ private static function fileKeyGet($format, $file) { $login=OC_OCS::checkpassword(); - if(OC_App::isEnabled('files_encryption') && OCA_Encryption\Crypt::mode($user) === 'client') { + if(OC_App::isEnabled('files_encryption') && OCA_Encryption\Crypt::mode() === 'client') { if (($key = OCA_Encryption\Keymanager::getFileKey($file))) { $xml=array(); $xml['key']=$key; @@ -762,7 +762,7 @@ class OC_OCS { echo self::generateXml('', 'fail', 404, 'file key does not exist'); } } else { - echo self::generateXml('', 'fail', 300, 'Client side encryption not enabled for user ' . $user); + echo self::generateXml('', 'fail', 300, 'Client side encryption not enabled'); } } @@ -775,14 +775,14 @@ class OC_OCS { */ private static function fileKeySet($format, $file, $key) { $login=OC_OCS::checkpassword(); - if(OC_App::isEnabled('files_encryption') && OCA_Encryption\Crypt::mode($user) === 'client') { + if(OC_App::isEnabled('files_encryption') && OCA_Encryption\Crypt::mode() === 'client') { if (($key = OCA_Encryption\Keymanager::setFileKey($file, $key))) { echo self::generateXml('', 'ok', 100, ''); } else { echo self::generateXml('', 'fail', 404, 'could not write key file'); } } else { - echo self::generateXml('', 'fail', 300, 'Client side encryption not enabled for user ' . $user); + echo self::generateXml('', 'fail', 300, 'Client side encryption not enabled'); } } -- cgit v1.2.3 From dc596a72c385a5bd1159ec1c19b8cb05f36a4a30 Mon Sep 17 00:00:00 2001 From: Bjoern Schiessle Date: Wed, 8 Aug 2012 14:20:29 +0200 Subject: remove function which is no longer needed --- apps/files_encryption/lib/keymanager.php | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) (limited to 'apps/files_encryption/lib') diff --git a/apps/files_encryption/lib/keymanager.php b/apps/files_encryption/lib/keymanager.php index 00817946fdf..0bf9be26ae0 100644 --- a/apps/files_encryption/lib/keymanager.php +++ b/apps/files_encryption/lib/keymanager.php @@ -28,11 +28,7 @@ namespace OCA_Encryption; class Keymanager { # TODO: make all dependencies (including static classes) explicit, such as ocfsview objects, by adding them as method arguments (dependency injection) - - private static function mergeProperties($properties) { - array_merge(self::$defaultProperties, $properties); - } - + /** * @brief retrieve private key from a user * -- cgit v1.2.3 From 5a261b5b8ffd01c34ce009a431a5587c548fa9a7 Mon Sep 17 00:00:00 2001 From: Bjoern Schiessle Date: Thu, 9 Aug 2012 12:19:51 +0200 Subject: ask user for passwords when switching from client to server side encryption --- apps/files_encryption/ajax/mode.php | 17 +++++++++++- apps/files_encryption/js/settings-personal.js | 31 +++++++++++++++++----- apps/files_encryption/lib/keymanager.php | 6 +++++ .../templates/settings-personal.php | 8 +++--- 4 files changed, 50 insertions(+), 12 deletions(-) (limited to 'apps/files_encryption/lib') diff --git a/apps/files_encryption/ajax/mode.php b/apps/files_encryption/ajax/mode.php index 0515cdccb0a..c81d4947956 100644 --- a/apps/files_encryption/ajax/mode.php +++ b/apps/files_encryption/ajax/mode.php @@ -7,11 +7,22 @@ //TODO: Handle switch between client and server side encryption +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())); @@ -21,4 +32,8 @@ if ($result->fetchRow()){ } else { $query = OC_DB::prepare( 'INSERT INTO *PREFIX*encryption ( mode, uid ) VALUES( ?, ? )' ); } -$query->execute(array($mode, \OCP\User::getUser())); \ No newline at end of file +if ( (!$changePasswd || $passwdChanged) && $query->execute(array($mode, \OCP\User::getUser())) ) { + OCP\JSON::success(); +} else { + OCP\JSON::error(); +} \ No newline at end of file diff --git a/apps/files_encryption/js/settings-personal.js b/apps/files_encryption/js/settings-personal.js index 6d3c9f9a486..fad077a8dd7 100644 --- a/apps/files_encryption/js/settings-personal.js +++ b/apps/files_encryption/js/settings-personal.js @@ -6,16 +6,33 @@ $(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) - var encmode= 'client'; - else if (server) - var encmode = 'server'; - else - var encmode = 'none'; - $.post(OC.filePath('files_encryption', 'ajax', 'mode.php'), { mode: encmode }); + if (client) { + $.post(OC.filePath('files_encryption', 'ajax', 'mode.php'), { mode: 'client' }); + if (prevmode == 'server') { + OC.dialogs.info(t('encryption', 'Please go 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', 'Please enter your passwords'), 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') { + console.log("change selection back to " + prevmode+'_encryption'); + document.getElementById(prevmode+'_encryption').checked = true; + } else { + } + + }); + }); + } else { + $.post(OC.filePath('files_encryption', 'ajax', 'mode.php'), { mode: 'server' }); + } + } else { + $.post(OC.filePath('files_encryption', 'ajax', 'mode.php'), { mode: 'none' }); + } }) }) \ No newline at end of file diff --git a/apps/files_encryption/lib/keymanager.php b/apps/files_encryption/lib/keymanager.php index 0bf9be26ae0..e546ba825e4 100644 --- a/apps/files_encryption/lib/keymanager.php +++ b/apps/files_encryption/lib/keymanager.php @@ -199,4 +199,10 @@ class Keymanager { return $result; } + public static function changePasswd($oldpasswd, $newpasswd) { + //TODO change password of private key + error_log("password changed from '$oldpasswd' to '$newpasswd'"); + return true; + } + } \ No newline at end of file diff --git a/apps/files_encryption/templates/settings-personal.php b/apps/files_encryption/templates/settings-personal.php index 4546aecacfa..de05fa5a4bc 100644 --- a/apps/files_encryption/templates/settings-personal.php +++ b/apps/files_encryption/templates/settings-personal.php @@ -4,10 +4,10 @@ Choose encryption mode:

- /> Client side encryption (most secure but makes it impossible to access your data from the web interface)
- /> Server side encryption (allows you to access your files from the web interface and the desktop client)
- /> None (no encryption at all)
+ + /> Client side encryption (most secure but makes it impossible to access your data from the web interface)
+ /> Server side encryption (allows you to access your files from the web interface and the desktop client)
+ /> None (no encryption at all)

- -- cgit v1.2.3 From 800942ece74ac336c4a9213228f14406d7e494f7 Mon Sep 17 00:00:00 2001 From: Bjoern Schiessle Date: Thu, 9 Aug 2012 13:47:27 +0200 Subject: change key password when user switches from client to server side encryption. make use of the keymanager class in changekeypasscode() --- apps/files_encryption/ajax/mode.php | 1 + apps/files_encryption/js/settings-personal.js | 6 ++---- apps/files_encryption/lib/crypt.php | 22 ++++++++++++---------- apps/files_encryption/lib/keymanager.php | 9 ++++++--- 4 files changed, 21 insertions(+), 17 deletions(-) (limited to 'apps/files_encryption/lib') diff --git a/apps/files_encryption/ajax/mode.php b/apps/files_encryption/ajax/mode.php index c81d4947956..f1a026ca431 100644 --- a/apps/files_encryption/ajax/mode.php +++ b/apps/files_encryption/ajax/mode.php @@ -32,6 +32,7 @@ if ($result->fetchRow()){ } 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 { diff --git a/apps/files_encryption/js/settings-personal.js b/apps/files_encryption/js/settings-personal.js index fad077a8dd7..f335cf7f880 100644 --- a/apps/files_encryption/js/settings-personal.js +++ b/apps/files_encryption/js/settings-personal.js @@ -18,14 +18,12 @@ $(document).ready(function(){ } } 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', 'Please enter your passwords'), function(data) { + 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') { - console.log("change selection back to " + prevmode+'_encryption'); document.getElementById(prevmode+'_encryption').checked = true; - } else { + OC.dialogs.alert(t('encryption', 'Please check your passwords and try again'), t('encryption', 'Could not change encryption password to login password')) } - }); }); } else { diff --git a/apps/files_encryption/lib/crypt.php b/apps/files_encryption/lib/crypt.php index 64bbc17ec11..1fa7013776a 100644 --- a/apps/files_encryption/lib/crypt.php +++ b/apps/files_encryption/lib/crypt.php @@ -412,21 +412,23 @@ class Crypt { } public static function changekeypasscode($oldPassword, $newPassword) { - if(OCP\User::isLoggedIn()){ - $username=OCP\USER::getUser(); - $view=new OC_FilesystemView('/'.$username); + if(\OCP\User::isLoggedIn()){ + $username = \OCP\USER::getUser(); + $view = new \OC_FilesystemView('/'.$username); // read old key - $key=$view->file_get_contents('/encryption.key'); + $key = Keymanager::getPrivateKey(); // decrypt key with old passcode - $key=OC_Crypt::decrypt($key, $oldPassword); + if ( ($key = self::decrypt($key, $oldPassword)) ) { + // encrypt again with new passcode + $key = self::encrypt($key, $newPassword); - // encrypt again with new passcode - $key=OC_Crypt::encrypt($key, $newPassword); - - // store the new key - $view->file_put_contents('/encryption.key', $key ); + // store the new key + return Keymanager::setPrivateKey($key); + } else { + return false; + } } } diff --git a/apps/files_encryption/lib/keymanager.php b/apps/files_encryption/lib/keymanager.php index e546ba825e4..4c30c163957 100644 --- a/apps/files_encryption/lib/keymanager.php +++ b/apps/files_encryption/lib/keymanager.php @@ -200,9 +200,12 @@ class Keymanager { } public static function changePasswd($oldpasswd, $newpasswd) { - //TODO change password of private key - error_log("password changed from '$oldpasswd' to '$newpasswd'"); - return true; + if ( \OCP\User::checkPassword(\OCP\User::getUser(), $newpasswd) ) { + return Crypt::changekeypasscode($oldpasswd, $newpasswd); + } else { + return false; + } + } } \ No newline at end of file -- cgit v1.2.3 From a969c23e59e26f2d82a1f8626444a59ae003c30e Mon Sep 17 00:00:00 2001 From: Bjoern Schiessle Date: Thu, 9 Aug 2012 14:25:09 +0200 Subject: disable admin choice of encryption mode once a decision was taken --- apps/files_encryption/js/settings-personal.js | 2 +- apps/files_encryption/js/settings.js | 19 +++++++++++++++---- apps/files_encryption/lib/keymanager.php | 7 +++++++ apps/files_encryption/templates/settings.php | 12 +++++++----- 4 files changed, 30 insertions(+), 10 deletions(-) (limited to 'apps/files_encryption/lib') diff --git a/apps/files_encryption/js/settings-personal.js b/apps/files_encryption/js/settings-personal.js index f335cf7f880..d70f9318e5d 100644 --- a/apps/files_encryption/js/settings-personal.js +++ b/apps/files_encryption/js/settings-personal.js @@ -22,7 +22,7 @@ $(document).ready(function(){ $.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 encryption password to login password')) + 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')) } }); }); diff --git a/apps/files_encryption/js/settings.js b/apps/files_encryption/js/settings.js index 19ff27a3b26..60563bde859 100644 --- a/apps/files_encryption/js/settings.js +++ b/apps/files_encryption/js/settings.js @@ -23,13 +23,24 @@ $(document).ready(function(){ ,server=$('input[value="server"]:checked').val() ,user=$('input[value="user"]:checked').val() ,none=$('input[value="none"]:checked').val() - if (client) + ,disable=false + if (client) { OC.AppConfig.setValue('files_encryption','mode','client'); - else if (server) + disable = true; + } else if (server) { OC.AppConfig.setValue('files_encryption','mode','server'); - else if (user) + disable = true; + } else if (user) { OC.AppConfig.setValue('files_encryption','mode','user'); - else + disable = true; + } else { OC.AppConfig.setValue('files_encryption','mode','none'); + } + if (disable) { + document.getElementById('server_encryption').disabled = true; + document.getElementById('client_encryption').disabled = true; + document.getElementById('user_encryption').disabled = true; + document.getElementById('none_encryption').disabled = true; + } }) }) \ No newline at end of file diff --git a/apps/files_encryption/lib/keymanager.php b/apps/files_encryption/lib/keymanager.php index 4c30c163957..62c5082a2f7 100644 --- a/apps/files_encryption/lib/keymanager.php +++ b/apps/files_encryption/lib/keymanager.php @@ -199,6 +199,13 @@ class Keymanager { return $result; } + /** + * @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); diff --git a/apps/files_encryption/templates/settings.php b/apps/files_encryption/templates/settings.php index 4133f4573d0..e2a9bcc3be1 100644 --- a/apps/files_encryption/templates/settings.php +++ b/apps/files_encryption/templates/settings.php @@ -2,12 +2,14 @@
Choose encryption mode: - + +

Important: Once you selected an encryption mode there is no way to change it back

+

- /> Client side encryption (most secure but makes it impossible to access your data from the web interface)
- /> Server side encryption (allows you to access your files from the web interface and the desktop client)
- /> User specific (let the user decide)
- /> None (no encryption at all)
+ /> Client side encryption (most secure but makes it impossible to access your data from the web interface)
+ /> Server side encryption (allows you to access your files from the web interface and the desktop client)
+ /> User specific (let the user decide)
+ /> None (no encryption at all)

t('Encryption'); ?> -- cgit v1.2.3 From bd7d5667330e1f9c8674602ea489805def2ba4f4 Mon Sep 17 00:00:00 2001 From: Bjoern Schiessle Date: Thu, 9 Aug 2012 15:45:34 +0200 Subject: change private key passphrase; disable file proxy for keymanager operations --- apps/files_encryption/lib/keymanager.php | 34 +++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 7 deletions(-) (limited to 'apps/files_encryption/lib') diff --git a/apps/files_encryption/lib/keymanager.php b/apps/files_encryption/lib/keymanager.php index 62c5082a2f7..8d56ac97dc5 100644 --- a/apps/files_encryption/lib/keymanager.php +++ b/apps/files_encryption/lib/keymanager.php @@ -36,10 +36,15 @@ class Keymanager { */ public static function getPrivateKey() { - $user = \OCP\User::getUser(); + \OC_FileProxy::$enabled = false; + + $user = \OCP\User::getUser(); $view = new \OC_FilesystemView( '/' . $user . '/' . 'files_encryption' ); + $result = $view->file_get_contents( '/' . $user.'.private.key' ); + + \OC_FileProxy::$enabled = true; - return $view->file_get_contents( '/' . $user.'.private.key' ); + return $result; } /** @@ -75,6 +80,8 @@ class Keymanager { } } + \OC_FileProxy::$enabled = false; + $view = new \OC_FilesystemView( '/public-keys/' ); $keylist = array(); @@ -83,6 +90,8 @@ class Keymanager { $keylist['key'.++$count] = $view->file_get_contents($user.'.public.key'); } + \OC_FileProxy::$enabled = true; + return $keylist; } @@ -108,8 +117,14 @@ class Keymanager { $keypath = str_replace('/'.$user.'/files/', '', $keypath); } + \OC_FileProxy::$enabled = false; + $view = new \OC_FilesystemView('/'.$user.'/files_encryption/keyfiles/'); - return $view->file_get_contents($keypath.'.key'); + $result = $view->file_get_contents($keypath.'.key'); + + \OC_FileProxy::$enabled = true; + + return $result; } /** @@ -208,11 +223,16 @@ class Keymanager { */ public static function changePasswd($oldpasswd, $newpasswd) { if ( \OCP\User::checkPassword(\OCP\User::getUser(), $newpasswd) ) { - return Crypt::changekeypasscode($oldpasswd, $newpasswd); - } else { - return false; + $key = Keymanager::getPrivateKey(); + if ( ($key = Crypt::symmetricDecryptFileContent($key,$oldpasswd)) ) { + if ( ($key = Crypt::symmetricEncryptFileContent($key, $newpasswd)) ) { + Keymanager::setPrivateKey($key); + return true; + + } + } } - + return false; } } \ No newline at end of file -- cgit v1.2.3 From 368ade6b2faef1d7ee0095b3921f285c62ba3a8f Mon Sep 17 00:00:00 2001 From: Bjoern Schiessle Date: Thu, 9 Aug 2012 17:25:57 +0200 Subject: code cleanup --- apps/files_encryption/js/settings-personal.js | 20 +++++++++++--------- apps/files_encryption/lib/keymanager.php | 1 - 2 files changed, 11 insertions(+), 10 deletions(-) (limited to 'apps/files_encryption/lib') diff --git a/apps/files_encryption/js/settings-personal.js b/apps/files_encryption/js/settings-personal.js index d70f9318e5d..8afea6d77df 100644 --- a/apps/files_encryption/js/settings-personal.js +++ b/apps/files_encryption/js/settings-personal.js @@ -14,18 +14,20 @@ $(document).ready(function(){ if (client) { $.post(OC.filePath('files_encryption', 'ajax', 'mode.php'), { mode: 'client' }); if (prevmode == 'server') { - OC.dialogs.info(t('encryption', 'Please go to your owncloud client and change your encryption password to complete the conversion'), t('encryption', 'switched to client side encryption')); + 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')) - } - }); - }); + 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' }); } diff --git a/apps/files_encryption/lib/keymanager.php b/apps/files_encryption/lib/keymanager.php index 8d56ac97dc5..43ac67f8cba 100644 --- a/apps/files_encryption/lib/keymanager.php +++ b/apps/files_encryption/lib/keymanager.php @@ -228,7 +228,6 @@ class Keymanager { if ( ($key = Crypt::symmetricEncryptFileContent($key, $newpasswd)) ) { Keymanager::setPrivateKey($key); return true; - } } } -- cgit v1.2.3 From 12628be38bd8a8225139c19c6725255fcebc3d93 Mon Sep 17 00:00:00 2001 From: Bjoern Schiessle Date: Fri, 10 Aug 2012 11:44:38 +0200 Subject: only call proxies for server side encryption --- apps/files_encryption/lib/keymanager.php | 31 ++++--------------------------- apps/files_encryption/lib/proxy.php | 2 +- 2 files changed, 5 insertions(+), 28 deletions(-) (limited to 'apps/files_encryption/lib') diff --git a/apps/files_encryption/lib/keymanager.php b/apps/files_encryption/lib/keymanager.php index 43ac67f8cba..0705205682a 100644 --- a/apps/files_encryption/lib/keymanager.php +++ b/apps/files_encryption/lib/keymanager.php @@ -35,15 +35,11 @@ class Keymanager { * @return string private key or false */ public static function getPrivateKey() { - - \OC_FileProxy::$enabled = false; - + $user = \OCP\User::getUser(); $view = new \OC_FilesystemView( '/' . $user . '/' . 'files_encryption' ); $result = $view->file_get_contents( '/' . $user.'.private.key' ); - - \OC_FileProxy::$enabled = true; - + return $result; } @@ -80,8 +76,6 @@ class Keymanager { } } - \OC_FileProxy::$enabled = false; - $view = new \OC_FilesystemView( '/public-keys/' ); $keylist = array(); @@ -89,8 +83,6 @@ class Keymanager { foreach ($users as $user) { $keylist['key'.++$count] = $view->file_get_contents($user.'.public.key'); } - - \OC_FileProxy::$enabled = true; return $keylist; @@ -117,13 +109,9 @@ class Keymanager { $keypath = str_replace('/'.$user.'/files/', '', $keypath); } - \OC_FileProxy::$enabled = false; - $view = new \OC_FilesystemView('/'.$user.'/files_encryption/keyfiles/'); $result = $view->file_get_contents($keypath.'.key'); - - \OC_FileProxy::$enabled = true; - + return $result; } @@ -134,16 +122,12 @@ class Keymanager { * @return bool true/false */ public static function setPrivateKey($key) { - - \OC_FileProxy::$enabled = false; $user = \OCP\User::getUser(); $view = new \OC_FilesystemView('/'.$user.'/files_encryption'); if (!$view->file_exists('')) $view->mkdir(''); $result = $view->file_put_contents($user.'.private.key', $key); - \OC_FileProxy::$enabled = true; - return $result; } @@ -156,14 +140,10 @@ class Keymanager { */ public static function setPublicKey($key) { - \OC_FileProxy::$enabled = false; - $view = new \OC_FilesystemView('/public-keys'); if (!$view->file_exists('')) $view->mkdir(''); $result = $view->file_put_contents(\OCP\User::getUser().'.public.key', $key); - \OC_FileProxy::$enabled = true; - return $result; } @@ -174,8 +154,7 @@ class Keymanager { * @param string $key * @return bool true/false */ - public static function setFileKey( $path, $key, $view = Null, $dbClassName = '\OC_DB', $fileProxyClassName = '\OC_FileProxy') { - $fileProxyClassName::$enabled = false; + public static function setFileKey( $path, $key, $view = Null, $dbClassName = '\OC_DB') { $targetpath = ltrim( $path, '/' ); $user = \OCP\User::getUser(); @@ -209,8 +188,6 @@ class Keymanager { $result = $view->file_put_contents( '/' . $targetpath . '.key', $key ); - $fileProxyClassName::$enabled = true; - return $result; } diff --git a/apps/files_encryption/lib/proxy.php b/apps/files_encryption/lib/proxy.php index 94f427f2f22..32b7a67e655 100644 --- a/apps/files_encryption/lib/proxy.php +++ b/apps/files_encryption/lib/proxy.php @@ -46,7 +46,7 @@ class Proxy extends \OC_FileProxy { if ( is_null( self::$enableEncryption ) ) { - self::$enableEncryption = ( \OCP\Config::getAppValue( 'files_encryption', 'enable_encryption', 'true' ) == 'true' ); + self::$enableEncryption = ( \OCP\Config::getAppValue( 'files_encryption', 'enable_encryption', 'true' ) == 'true' && Crypt::mode() == 'server' ); } -- cgit v1.2.3 From e4450d10354a23d8b20eea6657fb7bd1e57580fa Mon Sep 17 00:00:00 2001 From: Bjoern Schiessle Date: Fri, 10 Aug 2012 12:27:09 +0200 Subject: execute file hooks only if server side encryption is enabled --- apps/files_encryption/hooks/hooks.php | 2 +- apps/files_encryption/lib/keymanager.php | 21 +++++++++------------ apps/files_encryption/lib/proxy.php | 12 ++++++------ 3 files changed, 16 insertions(+), 19 deletions(-) (limited to 'apps/files_encryption/lib') diff --git a/apps/files_encryption/hooks/hooks.php b/apps/files_encryption/hooks/hooks.php index b7e8df9eac0..a9b3b2bcd8f 100644 --- a/apps/files_encryption/hooks/hooks.php +++ b/apps/files_encryption/hooks/hooks.php @@ -63,7 +63,7 @@ class Hooks { * @brief update the encryption key of the file uploaded by the client */ public static function updateKeyfile( $params ) { - if (Crypt::mode(\OCP\User::getUser()) == 'client') + if (Crypt::mode() == 'client') if (isset($params['properties']['key'])) { Keymanager::setFileKey($params['path'], $params['properties']['key']); } else { diff --git a/apps/files_encryption/lib/keymanager.php b/apps/files_encryption/lib/keymanager.php index 0705205682a..42aaf9b60bf 100644 --- a/apps/files_encryption/lib/keymanager.php +++ b/apps/files_encryption/lib/keymanager.php @@ -38,9 +38,8 @@ class Keymanager { $user = \OCP\User::getUser(); $view = new \OC_FilesystemView( '/' . $user . '/' . 'files_encryption' ); - $result = $view->file_get_contents( '/' . $user.'.private.key' ); - - return $result; + return $view->file_get_contents( '/' . $user.'.private.key' ); + } /** @@ -110,9 +109,8 @@ class Keymanager { } $view = new \OC_FilesystemView('/'.$user.'/files_encryption/keyfiles/'); - $result = $view->file_get_contents($keypath.'.key'); - - return $result; + return $view->file_get_contents($keypath.'.key'); + } /** @@ -126,9 +124,8 @@ class Keymanager { $user = \OCP\User::getUser(); $view = new \OC_FilesystemView('/'.$user.'/files_encryption'); if (!$view->file_exists('')) $view->mkdir(''); - $result = $view->file_put_contents($user.'.private.key', $key); + return $view->file_put_contents($user.'.private.key', $key); - return $result; } @@ -142,9 +139,8 @@ class Keymanager { $view = new \OC_FilesystemView('/public-keys'); if (!$view->file_exists('')) $view->mkdir(''); - $result = $view->file_put_contents(\OCP\User::getUser().'.public.key', $key); + return $view->file_put_contents(\OCP\User::getUser().'.public.key', $key); - return $result; } /** @@ -186,9 +182,8 @@ class Keymanager { if ( !$view->file_exists( $path_parts['dirname'] ) ) $view->mkdir( $path_parts['dirname'] ); - $result = $view->file_put_contents( '/' . $targetpath . '.key', $key ); + return $view->file_put_contents( '/' . $targetpath . '.key', $key ); - return $result; } /** @@ -199,6 +194,7 @@ class Keymanager { * @return bool true/false */ public static function changePasswd($oldpasswd, $newpasswd) { + if ( \OCP\User::checkPassword(\OCP\User::getUser(), $newpasswd) ) { $key = Keymanager::getPrivateKey(); if ( ($key = Crypt::symmetricDecryptFileContent($key,$oldpasswd)) ) { @@ -209,6 +205,7 @@ class Keymanager { } } return false; + } } \ No newline at end of file diff --git a/apps/files_encryption/lib/proxy.php b/apps/files_encryption/lib/proxy.php index 32b7a67e655..85b5c868f30 100644 --- a/apps/files_encryption/lib/proxy.php +++ b/apps/files_encryption/lib/proxy.php @@ -40,7 +40,7 @@ class Proxy extends \OC_FileProxy { * @param string $path * @return bool * - * Tests if encryption is enabled, and file is allowed by blacklists + * Tests if server side encryption is enabled, and file is allowed by blacklists */ private static function shouldEncrypt( $path ) { @@ -130,7 +130,7 @@ class Proxy extends \OC_FileProxy { public function postFile_get_contents( $path, $data ) { - if ( Crypt::isEncryptedContent( $data ) ) { + if ( Crypt::mode() == 'server' && Crypt::isEncryptedContent( $data ) ) { $filePath = explode( '/', $path ); @@ -164,7 +164,7 @@ class Proxy extends \OC_FileProxy { $meta = stream_get_meta_data( $result ); // If file is encrypted, decrypt using crypto protocol - if ( Crypt::isEncryptedContent( $path ) ) { + if ( Crypt::mode() == 'server' && Crypt::isEncryptedContent( $path ) ) { fclose ( $result ); @@ -208,14 +208,14 @@ class Proxy extends \OC_FileProxy { } public function postGetMimeType($path,$mime){ - if(Crypt::isEncryptedContent($path)){ + if( Crypt::isEncryptedContent($path)){ $mime = \OCP\Files::getMimeType('crypt://'.$path,'w'); } return $mime; } public function postStat($path,$data){ - if(Crypt::isEncryptedContent($path)){ + if( Crypt::isEncryptedContent($path)){ $cached= \OC_FileCache_Cached::get($path,''); $data['size']=$cached['size']; } @@ -223,7 +223,7 @@ class Proxy extends \OC_FileProxy { } public function postFileSize($path,$size){ - if(Crypt::isEncryptedContent($path)){ + if( Crypt::isEncryptedContent($path)){ $cached = \OC_FileCache_Cached::get($path,''); return $cached['size']; }else{ -- cgit v1.2.3 From 34f93ac765690c956fb7f7be1ab087c6b65c6f3b Mon Sep 17 00:00:00 2001 From: Bjoern Schiessle Date: Mon, 13 Aug 2012 11:31:15 +0200 Subject: check if user has write access to a given file before updating the filekey --- apps/files_encryption/lib/keymanager.php | 6 ++++++ lib/ocs.php | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) (limited to 'apps/files_encryption/lib') diff --git a/apps/files_encryption/lib/keymanager.php b/apps/files_encryption/lib/keymanager.php index 42aaf9b60bf..10b673f31aa 100644 --- a/apps/files_encryption/lib/keymanager.php +++ b/apps/files_encryption/lib/keymanager.php @@ -167,6 +167,12 @@ class Keymanager { $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 ); diff --git a/lib/ocs.php b/lib/ocs.php index 0fc208516ee..5d4e19c0c4a 100644 --- a/lib/ocs.php +++ b/lib/ocs.php @@ -767,7 +767,7 @@ class OC_OCS { } /** - * set the encryption keyn of a file + * set the encryption key of a file * @param string $format * @param string $file * @param string $key -- cgit v1.2.3 From 6ce315fe5826d058722353f3d4c0b2f025dabd43 Mon Sep 17 00:00:00 2001 From: Sam Tuke Date: Tue, 14 Aug 2012 19:06:56 +0100 Subject: added wrapper method in crypt class for encrypting asymmetric and symmetric simultaneously fixed bugs with keymanager integration added unit tests --- apps/files_encryption/hooks/hooks.php | 145 ++++++++--------- apps/files_encryption/lib/crypt.php | 51 +++++- apps/files_encryption/lib/keymanager.php | 27 +++- apps/files_encryption/lib/proxy.php | 25 ++- apps/files_encryption/tests/crypt.php | 245 +++++++++++++++++++++++++++++ apps/files_encryption/tests/encryption.php | 205 ------------------------ 6 files changed, 400 insertions(+), 298 deletions(-) create mode 100644 apps/files_encryption/tests/crypt.php delete mode 100644 apps/files_encryption/tests/encryption.php (limited to 'apps/files_encryption/lib') diff --git a/apps/files_encryption/hooks/hooks.php b/apps/files_encryption/hooks/hooks.php index 5215ac10624..e23e3a09d46 100644 --- a/apps/files_encryption/hooks/hooks.php +++ b/apps/files_encryption/hooks/hooks.php @@ -1,76 +1,77 @@ -. - * - */ - -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' ) { - - $view = new \OC_FilesystemView( '/' ); - - $util = new Util( $view, $params['uid'] ); - - if ( !$util->ready()) { - - return $util->setupServerSide( $params['password'] ); - - } - - $encryptedKey = Keymanager::getPrivateKey( $params['uid'] ); - - $_SESSION['enckey'] = Crypt::symmetricEncryptFileContent( $encryptedKey, $params['password'] ); - } - - return true; - - } - +. + * + */ + +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' ) { + + $view = new \OC_FilesystemView( '/' ); + + $util = new Util( $view, $params['uid'] ); + + if ( !$util->ready()) { + + return $util->setupServerSide( $params['password'] ); + + } + + $encryptedKey = Keymanager::getPrivateKey( $params['uid'] ); + + $_SESSION['enckey'] = Crypt::symmetricDecryptFileContent( $encryptedKey, $params['password'] ); + + } + + return true; + + } + /** * @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 a encryption key for the file!"); - } - } -} - + */ + 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 a encryption key for the file!"); + } + } +} + ?> \ No newline at end of file diff --git a/apps/files_encryption/lib/crypt.php b/apps/files_encryption/lib/crypt.php index 1fa7013776a..f868028be91 100644 --- a/apps/files_encryption/lib/crypt.php +++ b/apps/files_encryption/lib/crypt.php @@ -38,19 +38,19 @@ class Crypt { public static function mode( $user = null ) { $mode = \OC_Appconfig::getValue( 'files_encryption', 'mode', 'none' ); - + if ( $mode == 'user') { if ( !$user ) { $user = \OCP\User::getUser(); - } - $mode = 'none'; - if ( $user ) { + } + $mode = 'none'; + if ( $user ) { $query = \OC_DB::prepare( "SELECT mode FROM *PREFIX*encryption WHERE uid = ?" ); $result = $query->execute(array($user)); if ($row = $result->fetchRow()){ $mode = $row['mode']; - } - } + } + } } return $mode; @@ -217,6 +217,7 @@ class Crypt { * @param string $source * @param string $target * @param string $key the decryption key + * @returns decrypted content * * This function decrypts a file */ @@ -305,7 +306,7 @@ class Crypt { /** * @brief Asymmetrically encrypt a file using multiple public keys * @param string $plainContent content to be encrypted - * @returns array keys: key, encrypted + * @returns string $plainContent decrypted string * @note symmetricDecryptFileContent() can be used to decrypt files created using this method * * This function decrypts a file @@ -355,6 +356,40 @@ class Crypt { return $plainContent; } + + /** + * @brief Encrypts content symmetrically and generated keyfile asymmetrically + * @returns array keys: data, key + * @note this method is a wrapper for combining other crypt class methods + */ + public static function keyEncryptKeyfile( $plainContent, $publicKey ) { + + // Encrypt plain data, generate keyfile & encrypted file + $cryptedData = self::symmetricEncryptFileContentKeyfile( $plainContent ); + + // Encrypt keyfile + $cryptedKey = self::keyEncrypt( $cryptedData['key'], $publicKey ); + + return array( 'data' => $cryptedData['encrypted'], 'key' => $cryptedKey ); + + } + + /** + * @brief Encrypts content symmetrically and generated keyfile asymmetrically + * @returns decrypted content + * @note this method is a wrapper for combining other crypt class methods + */ + public static function keyDecryptKeyfile( $encryptedData, $encryptedKey, $privateKey ) { + + // Decrypt keyfile + $decryptedKey = self::keyDecrypt( $encryptedKey, $privateKey ); + + // Decrypt encrypted file + $decryptedData = self::symmetricDecryptFileContent( $encryptedData, $decryptedKey ); + + return $decryptedData; + + } /** * @brief Generate a pseudo random 1024kb ASCII key @@ -392,7 +427,7 @@ class Crypt { // $key = mt_rand( 10000, 99999 ) . mt_rand( 10000, 99999 ) . mt_rand( 10000, 99999 ) . mt_rand( 10000, 99999 ); // Generate key - if ( $key = base64_encode( openssl_random_pseudo_bytes( 768000, $strong ) ) ) { + if ( $key = base64_encode( openssl_random_pseudo_bytes( 183, $strong ) ) ) { if ( !$strong ) { diff --git a/apps/files_encryption/lib/keymanager.php b/apps/files_encryption/lib/keymanager.php index 10b673f31aa..8d81c97cfa3 100644 --- a/apps/files_encryption/lib/keymanager.php +++ b/apps/files_encryption/lib/keymanager.php @@ -41,6 +41,19 @@ class Keymanager { return $view->file_get_contents( '/' . $user.'.private.key' ); } + + /** + * @brief retrieve public key for a specified user + * + * @return string public key or false + */ + public static function getPublicKey() { + + $user = \OCP\User::getUser(); + $view = new \OC_FilesystemView( '/public-keys/' ); + return $view->file_get_contents( '/' . $user . '.public.key' ); + + } /** * @brief retrieve a list of the public key from all users with access to the file @@ -94,22 +107,26 @@ class Keymanager { * @return string file key or false */ public static function getFileKey( $path ) { - + trigger_error("div ".$path); $keypath = ltrim( $path, '/' ); $user = \OCP\User::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()){ + + if ($row = $result->fetchRow()) { + $keypath = $row['source']; - $keypath_parts=explode('/',$keypath); + $keypath_parts = explode( '/', $keypath ); $user = $keypath_parts[1]; - $keypath = str_replace('/'.$user.'/files/', '', $keypath); + $keypath = str_replace( '/' . $user . '/files/', '', $keypath ); + } $view = new \OC_FilesystemView('/'.$user.'/files_encryption/keyfiles/'); - return $view->file_get_contents($keypath.'.key'); + return $view->file_get_contents( $keypath . '.key' ); } diff --git a/apps/files_encryption/lib/proxy.php b/apps/files_encryption/lib/proxy.php index 85b5c868f30..51ed889d129 100644 --- a/apps/files_encryption/lib/proxy.php +++ b/apps/files_encryption/lib/proxy.php @@ -103,11 +103,14 @@ class Proxy extends \OC_FileProxy { // Set the filesize for userland, before encrypting $size = strlen( $data ); + // Disable encryption proxy to prevent recursive calls + \OC_FileProxy::$enabled = false; + // Encrypt plain data and fetch key - $encrypted = Crypt::symmetricEncryptFileContentKeyfile( $data, $_SESSION['enckey'] ); + $encrypted = Crypt::keyEncryptKeyfile( $data, Keymanager::getPublicKey() ); // Replace plain content with encrypted content by reference - $data = $encrypted['encrypted']; + $data = $encrypted['data']; $filePath = explode( '/', $path ); @@ -119,11 +122,13 @@ class Proxy extends \OC_FileProxy { $view = new \OC_FilesystemView( '/' . \OCP\USER::getUser() . '/files_encryption/keyfiles' ); // Save keyfile for newly encrypted file in parallel directory tree - Keymanager::setFileKey( \OCP\USER::getUser(), $filePath, $encrypted['key'], $view, '\OC_DB', '\OC_FileProxy' ); + Keymanager::setFileKey( $filePath, $encrypted['key'], $view, '\OC_DB' ); // Update the file cache with file info \OC_FileCache::put( $path, array( 'encrypted'=>true, 'size' => $size ), '' ); + \OC_FileProxy::$enabled = true; + } } } @@ -138,14 +143,18 @@ class Proxy extends \OC_FileProxy { $filePath = '/' . implode( '/', $filePath ); - trigger_error( "CAT " . $filePath); - $cached = \OC_FileCache_Cached::get( $path, '' ); - // Get keyfile for encrypted file - $keyFile = Keymanager::getFileKey( \OCP\USER::getUser(), $filePath ); + // Disable encryption proxy to prevent recursive calls + \OC_FileProxy::$enabled = false; + + $keyFile = Keymanager::getFileKey( $filePath ); + + $privateKey = Keymanager::getPrivateKey(); + + $data = Crypt::keyDecryptKeyfile( $data, $keyFile, $privateKey ); - $data = Crypt::symmetricDecryptFileContent( $data, $keyFile ); + \OC_FileProxy::$enabled = true; } diff --git a/apps/files_encryption/tests/crypt.php b/apps/files_encryption/tests/crypt.php new file mode 100644 index 00000000000..b6dc0f40aab --- /dev/null +++ b/apps/files_encryption/tests/crypt.php @@ -0,0 +1,245 @@ +, and + * Robin Appelman + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +require_once realpath( dirname(__FILE__).'/../lib/crypt.php' ); +require_once realpath( dirname(__FILE__).'/../lib/util.php' ); +//require realpath( dirname(__FILE__).'/../../../lib/filecache.php' ); + +class Test_Crypt extends UnitTestCase { + + function setUp() { + + // set content for encrypting / decrypting in tests + $this->data = realpath( dirname(__FILE__).'/../lib/crypt.php' ); + $this->legacyData = realpath( dirname(__FILE__).'/legacy-text.txt' ); + $this->legacyEncryptedData = realpath( dirname(__FILE__).'/legacy-encrypted-text.txt' ); + + } + + function tearDown(){} + + function testGenerateKey() { + + # TODO: use more accurate (larger) string length for test confirmation + + $key = OCA_Encryption\Crypt::generateKey(); + + $this->assertTrue( $key ); + + $this->assertTrue( strlen( $key ) > 16 ); + + } + + function testGenerateIv() { + + $iv = OCA_Encryption\Crypt::generateIv(); + + $this->assertTrue( $iv ); + + $this->assertTrue( strlen( $iv ) == 16 ); + + } + + function testEncrypt() { + + $random = openssl_random_pseudo_bytes( 13 ); + + $iv = substr( base64_encode( $random ), 0, -4 ); // i.e. E5IG033j+mRNKrht + + $crypted = OCA_Encryption\Crypt::encrypt( $this->data, $iv, 'hat' ); + + $this->assertNotEqual( $this->data, $crypted ); + + } + + function testDecrypt() { + + $random = openssl_random_pseudo_bytes( 13 ); + + $iv = substr( base64_encode( $random ), 0, -4 ); // i.e. E5IG033j+mRNKrht + + $crypted = OCA_Encryption\Crypt::encrypt( $this->data, $iv, 'hat' ); + + $decrypt = OCA_Encryption\Crypt::decrypt( $crypted, $iv, 'hat' ); + + $this->assertEqual( $this->data, $decrypt ); + + } + + function testSymmetricEncryptFileContent() { + + # TODO: search in keyfile for actual content as IV will ensure this test always passes + + $keyfileContent = OCA_Encryption\Crypt::symmetricEncryptFileContent( $this->data, 'hat' ); + + $this->assertNotEqual( $this->data, $keyfileContent ); + + + $decrypt = OCA_Encryption\Crypt::symmetricDecryptFileContent( $keyfileContent, 'hat' ); + + $this->assertEqual( $this->data, $decrypt ); + + } + + function testSymmetricEncryptFileContentKeyfile() { + + # TODO: search in keyfile for actual content as IV will ensure this test always passes + + $crypted = OCA_Encryption\Crypt::symmetricEncryptFileContentKeyfile( $this->data ); + + $this->assertNotEqual( $this->data, $crypted['encrypted'] ); + + + $decrypt = OCA_Encryption\Crypt::symmetricDecryptFileContent( $crypted['encrypted'], $crypted['key'] ); + + $this->assertEqual( $this->data, $decrypt ); + + } + + function testIsEncryptedContent() { + + $this->assertFalse( OCA_Encryption\Crypt::isEncryptedContent( $this->data ) ); + + $this->assertFalse( OCA_Encryption\Crypt::isEncryptedContent( $this->legacyEncryptedData ) ); + + $keyfileContent = OCA_Encryption\Crypt::symmetricEncryptFileContent( $this->data, 'hat' ); + + $this->assertTrue( OCA_Encryption\Crypt::isEncryptedContent( $keyfileContent ) ); + + } + + function testMultiKeyEncrypt() { + + # TODO: search in keyfile for actual content as IV will ensure this test always passes + + $pair1 = OCA_Encryption\Crypt::createKeypair(); + + $this->assertEqual( 2, count( $pair1 ) ); + + $this->assertTrue( strlen( $pair1['publicKey'] ) > 1 ); + + $this->assertTrue( strlen( $pair1['privateKey'] ) > 1 ); + + + $crypted = OCA_Encryption\Crypt::multiKeyEncrypt( $this->data, array( $pair1['publicKey'] ) ); + + $this->assertNotEqual( $this->data, $crypted['encrypted'] ); + + + $decrypt = OCA_Encryption\Crypt::multiKeyDecrypt( $crypted['encrypted'], $crypted['keys'][0], $pair1['privateKey'] ); + + $this->assertEqual( $this->data, $decrypt ); + + } + + function testKeyEncrypt() { + + // Generate keypair + $pair1 = OCA_Encryption\Crypt::createKeypair(); + + // Encrypt data + $crypted = OCA_Encryption\Crypt::keyEncrypt( $this->data, $pair1['publicKey'] ); + + $this->assertNotEqual( $this->data, $crypted ); + + // Decrypt data + $decrypt = OCA_Encryption\Crypt::keyDecrypt( $crypted, $pair1['privateKey'] ); + + $this->assertEqual( $this->data, $decrypt ); + + } + + function testKeyEncryptKeyfile() { + + # TODO: Don't repeat encryption from previous tests, use PHPUnit test interdependency instead + + // Generate keypair + $pair1 = OCA_Encryption\Crypt::createKeypair(); + + // Encrypt plain data, generate keyfile & encrypted file + $cryptedData = OCA_Encryption\Crypt::symmetricEncryptFileContentKeyfile( $this->data ); + + // Encrypt keyfile + $cryptedKey = OCA_Encryption\Crypt::keyEncrypt( $cryptedData['key'], $pair1['publicKey'] ); + + // Decrypt keyfile + $decryptKey = OCA_Encryption\Crypt::keyDecrypt( $cryptedKey, $pair1['privateKey'] ); + + // Decrypt encrypted file + $decryptData = OCA_Encryption\Crypt::symmetricDecryptFileContent( $cryptedData['encrypted'], $decryptKey ); + + $this->assertEqual( $this->data, $decryptData ); + + } + +// 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); +// } + +} diff --git a/apps/files_encryption/tests/encryption.php b/apps/files_encryption/tests/encryption.php deleted file mode 100644 index ed3b65b1797..00000000000 --- a/apps/files_encryption/tests/encryption.php +++ /dev/null @@ -1,205 +0,0 @@ -, and - * Robin Appelman - * This file is licensed under the Affero General Public License version 3 or - * later. - * See the COPYING-README file. - */ - -require realpath( dirname(__FILE__).'/../lib/crypt.php' ); -require realpath( dirname(__FILE__).'/../lib/util.php' ); -//require realpath( dirname(__FILE__).'/../../../lib/filecache.php' ); - -class Test_Encryption extends UnitTestCase { - - function setUp() { - - // set content for encrypting / decrypting in tests - $this->data = realpath( dirname(__FILE__).'/../lib/crypt.php' ); - $this->legacyData = realpath( dirname(__FILE__).'/legacy-text.txt' ); - $this->legacyEncryptedData = realpath( dirname(__FILE__).'/legacy-encrypted-text.txt' ); - - } - - function tearDown(){} - - function testGenerateKey() { - - # TODO: use more accurate (larger) string length for test confirmation - - $key = OCA_Encryption\Crypt::generateKey(); - - $this->assertTrue( $key ); - - $this->assertTrue( strlen( $key ) > 1000 ); - - } - - function testGenerateIv() { - - $iv = OCA_Encryption\Crypt::generateIv(); - - $this->assertTrue( $iv ); - - $this->assertTrue( strlen( $iv ) == 16 ); - - } - - function testEncrypt() { - - $random = openssl_random_pseudo_bytes( 13 ); - - $iv = substr( base64_encode( $random ), 0, -4 ); // i.e. E5IG033j+mRNKrht - - $crypted = OCA_Encryption\Crypt::encrypt( $this->data, $iv, 'hat' ); - - $this->assertNotEqual( $this->data, $crypted ); - - } - - function testDecrypt() { - - $random = openssl_random_pseudo_bytes( 13 ); - - $iv = substr( base64_encode( $random ), 0, -4 ); // i.e. E5IG033j+mRNKrht - - $crypted = OCA_Encryption\Crypt::encrypt( $this->data, $iv, 'hat' ); - - $decrypt = OCA_Encryption\Crypt::decrypt( $crypted, $iv, 'hat' ); - - $this->assertEqual( $this->data, $decrypt ); - - } - - function testSymmetricEncryptFileContent() { - - # TODO: search in keyfile for actual content as IV will ensure this test always passes - - $keyfileContent = OCA_Encryption\Crypt::symmetricEncryptFileContent( $this->data, 'hat' ); - - $this->assertNotEqual( $this->data, $keyfileContent ); - - - $decrypt = OCA_Encryption\Crypt::symmetricDecryptFileContent( $keyfileContent, 'hat' ); - - $this->assertEqual( $this->data, $decrypt ); - - } - - function testSymmetricEncryptFileContentKeyfile() { - - # TODO: search in keyfile for actual content as IV will ensure this test always passes - - $crypted = OCA_Encryption\Crypt::symmetricEncryptFileContentKeyfile( $this->data ); - - $this->assertNotEqual( $this->data, $crypted['encrypted'] ); - - - $decrypt = OCA_Encryption\Crypt::symmetricDecryptFileContent( $crypted['encrypted'], $crypted['key'] ); - - $this->assertEqual( $this->data, $decrypt ); - - } - - function testIsEncryptedContent() { - - $this->assertFalse( OCA_Encryption\Crypt::isEncryptedContent( $this->data ) ); - - $this->assertFalse( OCA_Encryption\Crypt::isEncryptedContent( $this->legacyEncryptedData ) ); - - $keyfileContent = OCA_Encryption\Crypt::symmetricEncryptFileContent( $this->data, 'hat' ); - - $this->assertTrue( OCA_Encryption\Crypt::isEncryptedContent( $keyfileContent ) ); - - } - - function testMultiKeyEncrypt() { - - # TODO: search in keyfile for actual content as IV will ensure this test always passes - - $pair1 = OCA_Encryption\Crypt::createKeypair(); - - $this->assertEqual( 2, count( $pair1 ) ); - - $this->assertTrue( strlen( $pair1['publicKey'] ) > 1 ); - - $this->assertTrue( strlen( $pair1['privateKey'] ) > 1 ); - - - $crypted = OCA_Encryption\Crypt::multiKeyEncrypt( $this->data, array( $pair1['publicKey'] ) ); - - $this->assertNotEqual( $this->data, $crypted['encrypted'] ); - - - $decrypt = OCA_Encryption\Crypt::multiKeyDecrypt( $crypted['encrypted'], $crypted['keys'][0], $pair1['privateKey'] ); - - $this->assertEqual( $this->data, $decrypt ); - - } - -// 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); -// } - -} -- cgit v1.2.3 From 92ec88c7bc1e252a3f5071e3bd59a24d02637f12 Mon Sep 17 00:00:00 2001 From: Bjoern Schiessle Date: Wed, 15 Aug 2012 09:54:21 +0200 Subject: move chane password code from keymanager.php to crypt.php --- apps/files_encryption/lib/crypt.php | 22 +++++++--------------- apps/files_encryption/lib/keymanager.php | 8 +------- 2 files changed, 8 insertions(+), 22 deletions(-) (limited to 'apps/files_encryption/lib') diff --git a/apps/files_encryption/lib/crypt.php b/apps/files_encryption/lib/crypt.php index f868028be91..07230fe8a24 100644 --- a/apps/files_encryption/lib/crypt.php +++ b/apps/files_encryption/lib/crypt.php @@ -447,26 +447,18 @@ class Crypt { } public static function changekeypasscode($oldPassword, $newPassword) { - if(\OCP\User::isLoggedIn()){ - $username = \OCP\USER::getUser(); - $view = new \OC_FilesystemView('/'.$username); - // read old key + if(\OCP\User::isLoggedIn()){ $key = Keymanager::getPrivateKey(); - - // decrypt key with old passcode - if ( ($key = self::decrypt($key, $oldPassword)) ) { - // encrypt again with new passcode - $key = self::encrypt($key, $newPassword); - - // store the new key - return Keymanager::setPrivateKey($key); - } else { - return false; + if ( ($key = Crypt::symmetricDecryptFileContent($key,$oldpasswd)) ) { + if ( ($key = Crypt::symmetricEncryptFileContent($key, $newpasswd)) ) { + Keymanager::setPrivateKey($key); + return true; + } } } + return false; } - } ?> \ No newline at end of file diff --git a/apps/files_encryption/lib/keymanager.php b/apps/files_encryption/lib/keymanager.php index 8d81c97cfa3..1ffeff99288 100644 --- a/apps/files_encryption/lib/keymanager.php +++ b/apps/files_encryption/lib/keymanager.php @@ -219,13 +219,7 @@ class Keymanager { public static function changePasswd($oldpasswd, $newpasswd) { if ( \OCP\User::checkPassword(\OCP\User::getUser(), $newpasswd) ) { - $key = Keymanager::getPrivateKey(); - if ( ($key = Crypt::symmetricDecryptFileContent($key,$oldpasswd)) ) { - if ( ($key = Crypt::symmetricEncryptFileContent($key, $newpasswd)) ) { - Keymanager::setPrivateKey($key); - return true; - } - } + return Crypt::changekeypasscode($oldpasswd, $newpasswd); } return false; -- cgit v1.2.3 From d039f11905658f2642d84f4054abde0c3b920ea8 Mon Sep 17 00:00:00 2001 From: Bjoern Schiessle Date: Wed, 15 Aug 2012 13:18:11 +0200 Subject: provide ocs calls and keymanager functions to get/set both keys (private, public) of a user together --- apps/files_encryption/lib/keymanager.php | 27 ++++++++++++++++ lib/ocs.php | 53 ++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+) (limited to 'apps/files_encryption/lib') diff --git a/apps/files_encryption/lib/keymanager.php b/apps/files_encryption/lib/keymanager.php index 1ffeff99288..ea6e4872d4b 100644 --- a/apps/files_encryption/lib/keymanager.php +++ b/apps/files_encryption/lib/keymanager.php @@ -55,6 +55,20 @@ class Keymanager { } + /** + * @brief retrieve both keys from a user (private and public) + * + * @return string private key or false + */ + public static function getUserKeys() { + + return array( + 'privatekey' => self::getPrivateKey(), + 'publickey' => self::getPublicKey(), + ); + + } + /** * @brief retrieve a list of the public key from all users with access to the file * @@ -145,6 +159,19 @@ class Keymanager { } + /** + * @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 diff --git a/lib/ocs.php b/lib/ocs.php index 5d4e19c0c4a..423e1752da6 100644 --- a/lib/ocs.php +++ b/lib/ocs.php @@ -187,6 +187,16 @@ class OC_OCS { $key = self::readData('post', 'key', 'string'); OC_OCS::privateKeySet($format, $key); + // keygetuser + }elseif(($method=='get') and ($ex[$paracount-4] == 'v1.php') and ($ex[$paracount-3]=='cloud') and ($ex[$paracount-2] == 'userkeys')){ + OC_OCS::userKeysGet($format); + + //keysetuser + }elseif(($method=='post') and ($ex[$paracount-4] == 'v1.php') and ($ex[$paracount-3]=='cloud') and ($ex[$paracount-2] == 'userkeys')){ + $privatekey = self::readData('post', 'privatekey', 'string'); + $publickey = self::readData('post', 'publickey', 'string'); + OC_OCS::userKeysSet($format, $privatekey, $publickey); + // keygetfiles }elseif(($method=='get') and ($ex[$paracount-6] == 'v1.php') and ($ex[$paracount-5]=='cloud') and ($ex[$paracount-4] == 'file') and ($ex[$paracount-2] == 'filekey')){ $file = urldecode($ex[$paracount-3]); @@ -744,6 +754,49 @@ class OC_OCS { } } + /** + * get both user keys (private and public) + * @param string $format + * @return string xml/json + */ + private static function userKeysGet($format) { + $login=OC_OCS::checkpassword(); + if(OC_App::isEnabled('files_encryption') && OCA_Encryption\Crypt::mode() === 'client') { + $keys = OCA_Encryption\Keymanager::getUserKeys(); + if ($keys['privatekey'] && $keys['publickey']) { + $xml=array(); + $xml['privatekey']=$keys['privatekey']; + $xml['publickey']=$keys['publickey']; + $txt=OC_OCS::generatexml($format, 'ok', 100, '', $xml, 'cloud', '', 1, 0, 0); + echo($txt); + } else { + echo self::generateXml('', 'fail', 404, 'Keys not found on the server'); + } + } else { + echo self::generateXml('', 'fail', 300, 'Client side encryption not enabled'); + } + } + + /** + * set both user keys (private and public) + * @param string $format + * @param string $privatekey + * @param string @publickey + * @return string xml/json + */ + private static function userKeysSet($format, $privatekey, $publickey) { + $login=OC_OCS::checkpassword(); + if(OC_App::isEnabled('files_encryption') && OCA_Encryption\Crypt::mode() === 'client') { + if (($key = OCA_Encryption\Keymanager::setUserKeys($privatekey, $publickey))) { + echo self::generateXml('', 'ok', 100, ''); + } else { + echo self::generateXml('', 'fail', 404, 'could not add your keys to the key storage'); + } + } else { + echo self::generateXml('', 'fail', 300, 'Client side encryption not enabled'); + } + } + /** * get the encryption key of a file * @param string $format -- cgit v1.2.3 From f11f524dfa17071dbabb2f950680966867f262a6 Mon Sep 17 00:00:00 2001 From: Sam Tuke Date: Wed, 15 Aug 2012 18:49:53 +0100 Subject: working on streaming decrypted content applied some dependency injection to keymanager.php --- apps/files_encryption/hooks/hooks.php | 8 +++++++- apps/files_encryption/lib/keymanager.php | 10 +++++----- apps/files_encryption/lib/proxy.php | 14 ++++++++++---- apps/files_encryption/lib/util.php | 4 ++-- apps/files_encryption/tests/keymanager.php | 1 + 5 files changed, 25 insertions(+), 12 deletions(-) (limited to 'apps/files_encryption/lib') diff --git a/apps/files_encryption/hooks/hooks.php b/apps/files_encryption/hooks/hooks.php index e23e3a09d46..b37c974b9c1 100644 --- a/apps/files_encryption/hooks/hooks.php +++ b/apps/files_encryption/hooks/hooks.php @@ -39,6 +39,8 @@ class Hooks { if ( Crypt::mode( $params['uid'] ) == 'server' ) { + # TODO: use lots of dependency injection here + $view = new \OC_FilesystemView( '/' ); $util = new Util( $view, $params['uid'] ); @@ -49,8 +51,12 @@ class Hooks { } - $encryptedKey = Keymanager::getPrivateKey( $params['uid'] ); + \OC_FileProxy::$enabled = false; + + $encryptedKey = Keymanager::getPrivateKey( $params['uid'], $view ); + \OC_FileProxy::$enabled = true; + $_SESSION['enckey'] = Crypt::symmetricDecryptFileContent( $encryptedKey, $params['password'] ); } diff --git a/apps/files_encryption/lib/keymanager.php b/apps/files_encryption/lib/keymanager.php index ea6e4872d4b..b06226397e8 100644 --- a/apps/files_encryption/lib/keymanager.php +++ b/apps/files_encryption/lib/keymanager.php @@ -30,14 +30,14 @@ class Keymanager { # TODO: make all dependencies (including static classes) explicit, such as ocfsview objects, by adding them as method arguments (dependency injection) /** - * @brief retrieve private key from a user + * @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() { + public static function getPrivateKey( $user, $view ) { - $user = \OCP\User::getUser(); - $view = new \OC_FilesystemView( '/' . $user . '/' . 'files_encryption' ); + $view->chroot( '/' . $user . '/' . 'files_encryption' ); return $view->file_get_contents( '/' . $user.'.private.key' ); } @@ -121,7 +121,7 @@ class Keymanager { * @return string file key or false */ public static function getFileKey( $path ) { - trigger_error("div ".$path); + $keypath = ltrim( $path, '/' ); $user = \OCP\User::getUser(); diff --git a/apps/files_encryption/lib/proxy.php b/apps/files_encryption/lib/proxy.php index 51ed889d129..5b0369bde9b 100644 --- a/apps/files_encryption/lib/proxy.php +++ b/apps/files_encryption/lib/proxy.php @@ -135,6 +135,8 @@ class Proxy extends \OC_FileProxy { public function postFile_get_contents( $path, $data ) { + # TODO: Use dependency injection to add required args for view and user etc. to this method + if ( Crypt::mode() == 'server' && Crypt::isEncryptedContent( $data ) ) { $filePath = explode( '/', $path ); @@ -150,9 +152,7 @@ class Proxy extends \OC_FileProxy { $keyFile = Keymanager::getFileKey( $filePath ); - $privateKey = Keymanager::getPrivateKey(); - - $data = Crypt::keyDecryptKeyfile( $data, $keyFile, $privateKey ); + $data = Crypt::keyDecryptKeyfile( $data, $keyFile, $_SESSION['enckey'] ); \OC_FileProxy::$enabled = true; @@ -175,9 +175,15 @@ class Proxy extends \OC_FileProxy { // If file is encrypted, decrypt using crypto protocol if ( Crypt::mode() == 'server' && Crypt::isEncryptedContent( $path ) ) { + $keyFile = Keymanager::getFileKey( $filePath ); + + $tmp = tmpfile(); + + file_put_contents( $tmp, Crypt::keyDecryptKeyfile( $result, $keyFile, $_SESSION['enckey'] ) ); + fclose ( $result ); - $result = fopen( 'crypt://'.$path, $meta['mode'] ); + $result = fopen( $tmp ); } elseif ( self::shouldEncrypt( $path ) diff --git a/apps/files_encryption/lib/util.php b/apps/files_encryption/lib/util.php index 609f7871241..b919c56a2eb 100644 --- a/apps/files_encryption/lib/util.php +++ b/apps/files_encryption/lib/util.php @@ -222,9 +222,9 @@ class Util { } - public function encryptAll( OC_FilesystemView $view ) { + public function encryptAll( $directory ) { - $plainFiles = $this->findPlainFiles( $view ); + $plainFiles = $this->findFiles( $this->view, 'plain' ); if ( $this->encryptFiles( $plainFiles ) ) { diff --git a/apps/files_encryption/tests/keymanager.php b/apps/files_encryption/tests/keymanager.php index 51b49c5da57..e0ce7a1d6ad 100644 --- a/apps/files_encryption/tests/keymanager.php +++ b/apps/files_encryption/tests/keymanager.php @@ -43,6 +43,7 @@ class Test_Keymanager extends \PHPUnit_Framework_TestCase { $key = Keymanager::getPrivateKey( $this->user, $this->view ); + # TODO: replace call to Crypt with a mock object? $decrypted = Crypt::symmetricDecryptFileContent( $key, $this->passphrase ); $this->assertEquals( 1708, strlen( $decrypted ) ); -- cgit v1.2.3 From 293a0f4d3229ab6737b3625d7ceb0718ef6dea00 Mon Sep 17 00:00:00 2001 From: Sam Tuke Date: Thu, 16 Aug 2012 19:18:18 +0100 Subject: Started rewrite of cryptstream class (renamed to stream) Added unit tests Fixed decryption of user private key at login Added functionality to keymanager --- apps/files_encryption/appinfo/app.php | 4 +- apps/files_encryption/lib/crypt.php | 41 +++++ apps/files_encryption/lib/cryptstream.php | 208 ----------------------- apps/files_encryption/lib/keymanager.php | 9 +- apps/files_encryption/lib/stream.php | 264 ++++++++++++++++++++++++++++++ apps/files_encryption/lib/util.php | 2 +- apps/files_encryption/tests/crypt.php | 50 +++++- 7 files changed, 361 insertions(+), 217 deletions(-) delete mode 100644 apps/files_encryption/lib/cryptstream.php create mode 100644 apps/files_encryption/lib/stream.php (limited to 'apps/files_encryption/lib') diff --git a/apps/files_encryption/appinfo/app.php b/apps/files_encryption/appinfo/app.php index 4fd9c37ed30..dd95a1f0944 100644 --- a/apps/files_encryption/appinfo/app.php +++ b/apps/files_encryption/appinfo/app.php @@ -4,7 +4,7 @@ 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['OC_CryptStream'] = 'apps/files_encryption/lib/cryptstream.php'; +OC::$CLASSPATH['OCA_Encryption\Stream'] = 'apps/files_encryption/lib/stream.php'; OC::$CLASSPATH['OCA_Encryption\Proxy'] = 'apps/files_encryption/lib/proxy.php'; OC_FileProxy::register(new OCA_Encryption\Proxy()); @@ -12,7 +12,7 @@ OC_FileProxy::register(new OCA_Encryption\Proxy()); OCP\Util::connectHook('OC_User','post_login','OCA_Encryption\Hooks','login'); OCP\Util::connectHook('OC_Webdav_Properties', 'update', 'OCA_Encryption\Hooks', 'updateKeyfile'); -stream_wrapper_register('crypt','OC_CryptStream'); +stream_wrapper_register( 'crypt', 'OCA_Encryption\Stream'); if( !isset( $_SESSION['enckey'] ) && OCP\User::isLoggedIn() && OCA_Encryption\Crypt::mode() == 'server' ) { diff --git a/apps/files_encryption/lib/crypt.php b/apps/files_encryption/lib/crypt.php index 07230fe8a24..fa7287a736b 100644 --- a/apps/files_encryption/lib/crypt.php +++ b/apps/files_encryption/lib/crypt.php @@ -391,6 +391,47 @@ class Crypt { } + /** + * @brief Symmetrically encrypt a file by combining encrypted component data blocks + */ + public static function symmetricBlockEncryptFileContent( $plainContent, $key ) { + + $crypted = ''; + + while( strlen( $plainContent ) ) { + + // Encrypt a chunk of unencrypted data and add it to the rest + $crypted .= self::symmetricEncryptFileContent( substr( $plainContent, 0, 8192 ), $key ); + + // Remove the data already encrypted from remaining unencrypted data + $plainContent = substr( $plainContent, 8192 ); + + } + + return $crypted; + + } + + + /** + * @brief Symmetrically decrypt a file by combining encrypted component data blocks + */ + public static function symmetricBlockDecryptFileContent( $crypted, $key ) { + + $decrypted = ''; + + while( strlen( $crypted ) ) { + + $decrypted .= self::symmetricDecryptFileContent( substr( $crypted, 0, 8192 ), $key ); + + $crypted = substr( $crypted, 8192 ); + + } + + return rtrim( $decrypted, "\0" ); + + } + /** * @brief Generate a pseudo random 1024kb ASCII key * @returns $key Generated key diff --git a/apps/files_encryption/lib/cryptstream.php b/apps/files_encryption/lib/cryptstream.php deleted file mode 100644 index 8c61c933cf8..00000000000 --- a/apps/files_encryption/lib/cryptstream.php +++ /dev/null @@ -1,208 +0,0 @@ -. - * - */ - -/** - * 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 $readBuffer; // For streams that dont support seeking - private $meta = array(); // Header / meta for source stream - private $count; - private $writeCache; - private $size; - private static $rootView; - - public function stream_open($path, $mode, $options, &$opened_path){ - if(!self::$rootView){ - self::$rootView=new OC_FilesystemView(''); - } - $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 ); - - $written = 0; - - $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_FileCache::put($this->path,array('encrypted'=>true,'size'=>$this->size),''); - } - return fclose($this->source); - } -} diff --git a/apps/files_encryption/lib/keymanager.php b/apps/files_encryption/lib/keymanager.php index b06226397e8..26101b8356c 100644 --- a/apps/files_encryption/lib/keymanager.php +++ b/apps/files_encryption/lib/keymanager.php @@ -120,10 +120,10 @@ class Keymanager { * @param string file name * @return string file key or false */ - public static function getFileKey( $path ) { + public static function getFileKey( $path, $staticUserClass = 'OCP\User' ) { $keypath = ltrim( $path, '/' ); - $user = \OCP\User::getUser(); + $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 = ?" ); @@ -140,6 +140,7 @@ class Keymanager { } $view = new \OC_FilesystemView('/'.$user.'/files_encryption/keyfiles/'); + return $view->file_get_contents( $keypath . '.key' ); } @@ -227,9 +228,11 @@ class Keymanager { $path_parts = pathinfo( $targetpath ); if (!$view) { - $view = new \OC_FilesystemView( '/' . $user . '/files_encryption/keyfiles' ); + $view = new \OC_FilesystemView( '/' ); } + $view->chroot( '/' . $user . '/files_encryption/keyfiles' ); + if ( !$view->file_exists( $path_parts['dirname'] ) ) $view->mkdir( $path_parts['dirname'] ); return $view->file_put_contents( '/' . $targetpath . '.key', $key ); diff --git a/apps/files_encryption/lib/stream.php b/apps/files_encryption/lib/stream.php new file mode 100644 index 00000000000..bdcfdfd73aa --- /dev/null +++ b/apps/files_encryption/lib/stream.php @@ -0,0 +1,264 @@ +. + * + */ + +/** + * 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; + +class Stream { + + public static $sourceStreams = array(); + private $source; + private $path; + private $readBuffer; // For streams that dont support seeking + private $meta = array(); // Header / meta for source stream + private $count; + private $writeCache; + private $size; + private static $view; + + public function stream_open( $path, $mode, $options, &$opened_path ) { + + // Get access to filesystem via filesystemview object + if ( !self::$view ) { + + self::$view = new \OC_FilesystemView( '' ); + + } + + // Get the bare file path + $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 { + + if ( + $mode == 'w' + or $mode == 'w+' + or $mode == 'wb' + or $mode == 'wb+' + ) { + + $this->size = 0; + + } else { + + $this->size = self::$view->filesize( $path, $mode ); + + } + + // Disable fileproxies so we can open the source file without recursive encryption + \OC_FileProxy::$enabled = false; + + $this->source = self::$view->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=Crypt::decrypt($data); + }else{ + $result=''; + } + $length=$this->size-$pos; + if ($length<8192) { + $result=substr($result,0,$length); + } + return $result; + } + + /** + * @brief + */ + public function stream_write( $data ) { + + $length = strlen( $data ); + + $written = 0; + + $currentPos = ftell( $this->source ); + + # TODO: Move this user call out of here - it belongs elsewhere + $user = \OCP\User::getUser(); + + if ( self::$view->file_exists( $this->path . $user ) ) { + + $key = Keymanager::getFileKey( $this->path . $user ); + + } else { + + $key = Crypt::generateKey(); + + Keymanager::setFileKey( $path, $key, new \OC_FilesystemView ); + + } + + if ( $this->writeCache ) { + + $data = $this->writeCache . $data; + + $this->writeCache = ''; + + } + + // Make sure we always start on a block start + if ( $currentPos % 8192 != 0 ) { + + fseek( $this->source, - ( $currentPos % 8192 ), SEEK_CUR ); + + $encryptedBlock = fread( $this->source, 8192 ); + + fseek( $this->source, - ( $currentPos % 8192 ), SEEK_CUR ); + + $block = Crypt::symmetricDecryptFileContent( $encryptedBlock, $key ); + + $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 = Crypt::symmetricBlockEncryptFileContent( $data, $key ); + + fwrite( $this->source . $user, $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=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_FileCache::put($this->path,array('encrypted'=>true,'size'=>$this->size),''); + } + return fclose($this->source); + } +} diff --git a/apps/files_encryption/lib/util.php b/apps/files_encryption/lib/util.php index b919c56a2eb..eab5b5edf5b 100644 --- a/apps/files_encryption/lib/util.php +++ b/apps/files_encryption/lib/util.php @@ -46,7 +46,7 @@ class Util { # DONE: add method to decrypt legacy encrypted data # DONE: fix / test the crypt stream proxy class - # TODO: replace cryptstream wrapper with stream_socket_enable_crypto, or fix it to use new crypt class methods + # TODO: replace cryptstream wrapper new AES based system # TODO: add support for optional recovery user in case of lost passphrase / keys # TODO: add admin optional required long passphrase for users # TODO: implement flag system to allow user to specify encryption by folder, subfolder, etc. diff --git a/apps/files_encryption/tests/crypt.php b/apps/files_encryption/tests/crypt.php index 2802f32a58d..ef453ab90d6 100644 --- a/apps/files_encryption/tests/crypt.php +++ b/apps/files_encryption/tests/crypt.php @@ -21,6 +21,8 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { $this->legacyData = realpath( dirname(__FILE__).'/legacy-text.txt' ); $this->legacyEncryptedData = realpath( dirname(__FILE__).'/legacy-encrypted-text.txt' ); + //stream_wrapper_register( 'crypt', 'OCA_Encryption\Stream' ); + } function tearDown(){} @@ -73,16 +75,58 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { # TODO: search in keyfile for actual content as IV will ensure this test always passes - $keyfileContent = Crypt::symmetricEncryptFileContent( $this->data, 'hat' ); + $crypted = Crypt::symmetricEncryptFileContent( $this->data, 'hat' ); - $this->assertNotEquals( $this->data, $keyfileContent ); + $this->assertNotEquals( $this->data, $crypted ); - $decrypt = Crypt::symmetricDecryptFileContent( $keyfileContent, 'hat' ); + $decrypt = Crypt::symmetricDecryptFileContent( $crypted, 'hat' ); $this->assertEquals( $this->data, $decrypt ); } + + function testSymmetricBlockEncryptFileContent() { + + $crypted = Crypt::symmetricBlockEncryptFileContent( $this->data, 'hat' ); + + $this->assertNotEquals( $this->data, $crypted ); + + + $decrypt = Crypt::symmetricBlockDecryptFileContent( $crypted, 'hat' ); + + $this->assertEquals( $this->data, $decrypt ); + + } + +// function testSymmetricBlockStreamEncryptFileContent() { +// +// $crypted = Crypt::symmetricBlockEncryptFileContent( $this->data, 'hat' ); +// +// $cryptedFile = file_put_contents( 'crypt://' . '/blockEncrypt', $crypted ); +// +// // Test that data was successfully written +// $this->assertTrue( $cryptedFile ); +// +// $retreivedCryptedFile = file_get_contents( '/blockEncrypt' ); +// +// $this->assertNotEquals( $this->data, $retreivedCryptedFile ); +// +// } + + function testSymmetricBlockStreamDecryptFileContent() { + + \OC_User::setUserId( 'admin' ); + + $crypted = Crypt::symmetricBlockEncryptFileContent( $this->data, 'hat' ); + + $cryptedFile = file_put_contents( 'crypt://' . '/blockEncrypt', $crypted ); + + $retreivedCryptedFile = file_get_contents( 'crypt://' . '/blockEncrypt' ); + + $this->assertEquals( $this->data, $retreivedCryptedFile ); + + } function testSymmetricEncryptFileContentKeyfile() { -- cgit v1.2.3 From 32ee3de9188a2373d5629ae2e00dcbe2cbe33e29 Mon Sep 17 00:00:00 2001 From: Sam Tuke Date: Thu, 23 Aug 2012 16:43:10 +0100 Subject: Extensive work on crypto stream wrapper implementation --- apps/files_encryption/lib/crypt.php | 35 +++++-- apps/files_encryption/lib/keymanager.php | 42 ++++++--- apps/files_encryption/lib/stream.php | 115 +++++++++++++++++------ apps/files_encryption/tests/crypt.php | 144 ++++++++++++++++++++--------- apps/files_encryption/tests/keymanager.php | 21 ++++- 5 files changed, 259 insertions(+), 98 deletions(-) (limited to 'apps/files_encryption/lib') diff --git a/apps/files_encryption/lib/crypt.php b/apps/files_encryption/lib/crypt.php index fa7287a736b..25ba906deb4 100644 --- a/apps/files_encryption/lib/crypt.php +++ b/apps/files_encryption/lib/crypt.php @@ -177,6 +177,14 @@ class Crypt { } + public static function concatIv ( $content, $iv ) { + + $combined = $content . '00iv00' . $iv; + + return $combined; + + } + /** * @brief Symmetrically encrypts a string and returns keyfile content * @param $plainContent content to be encrypted in keyfile @@ -197,7 +205,7 @@ class Crypt { if ( $encryptedContent = self::encrypt( $plainContent, $iv, $passphrase ) ) { // Combine content to encrypt with IV identifier and actual IV - $combinedKeyfile = $encryptedContent . '00iv00' . $iv; + $combinedKeyfile = self::concatIv( $encryptedContent, $iv ); return $combinedKeyfile; @@ -398,13 +406,17 @@ class Crypt { $crypted = ''; - while( strlen( $plainContent ) ) { + $remaining = $plainContent; + + while( strlen( $remaining ) ) { // Encrypt a chunk of unencrypted data and add it to the rest - $crypted .= self::symmetricEncryptFileContent( substr( $plainContent, 0, 8192 ), $key ); + $block = self::symmetricEncryptFileContent( substr( $remaining, 0, 8192 ), $key ); + + $crypted .= $block; // Remove the data already encrypted from remaining unencrypted data - $plainContent = substr( $plainContent, 8192 ); + $remaining = substr( $remaining, 8192 ); } @@ -418,17 +430,24 @@ class Crypt { */ public static function symmetricBlockDecryptFileContent( $crypted, $key ) { + //echo "\n\n\nfags \$crypted = $crypted\n\n\n"; + $decrypted = ''; - while( strlen( $crypted ) ) { + $remaining = $crypted; + + while( strlen( $remaining ) ) { - $decrypted .= self::symmetricDecryptFileContent( substr( $crypted, 0, 8192 ), $key ); + // Encrypt a chunk of unencrypted data and add it to the rest + // 10946 is the length of a 8192 string once it has been encrypted + $decrypted .= self::symmetricDecryptFileContent( substr( $remaining, 0, 10946 ), $key ); - $crypted = substr( $crypted, 8192 ); + // Remove the data already encrypted from remaining unencrypted data + $remaining = substr( $remaining, 10946 ); } - return rtrim( $decrypted, "\0" ); + return $decrypted; } diff --git a/apps/files_encryption/lib/keymanager.php b/apps/files_encryption/lib/keymanager.php index 26101b8356c..525943f13d6 100644 --- a/apps/files_encryption/lib/keymanager.php +++ b/apps/files_encryption/lib/keymanager.php @@ -197,45 +197,57 @@ class Keymanager { */ public static function setFileKey( $path, $key, $view = Null, $dbClassName = '\OC_DB') { - $targetpath = ltrim( $path, '/' ); + $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 ) ); + $result = $query->execute( array ( '/'.$user.'/files/'.$targetPath, $user ) ); if ( $row = $result->fetchRow( ) ) { - $targetpath = $row['source']; + $targetPath = $row['source']; - $targetpath_parts=explode( '/',$targetpath ); + $targetPath_parts = explode( '/', $targetPath ); - $user = $targetpath_parts[1]; + $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 ); + $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 ); + $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) { + $path_parts = pathinfo( $targetPath ); + + if ( !$view ) { + $view = new \OC_FilesystemView( '/' ); + } $view->chroot( '/' . $user . '/files_encryption/keyfiles' ); - if ( !$view->file_exists( $path_parts['dirname'] ) ) $view->mkdir( $path_parts['dirname'] ); + // If the file resides within a subdirectory, create it + if ( ! $view->file_exists( $path_parts['dirname'] ) ) { + + $view->mkdir( $path_parts['dirname'] ); + + } - return $view->file_put_contents( '/' . $targetpath . '.key', $key ); + // Save the keyfile in parallel directory + return $view->file_put_contents( '/' . $targetPath . '.key', $key ); } diff --git a/apps/files_encryption/lib/stream.php b/apps/files_encryption/lib/stream.php index bdcfdfd73aa..d1ab25a0192 100644 --- a/apps/files_encryption/lib/stream.php +++ b/apps/files_encryption/lib/stream.php @@ -34,11 +34,13 @@ class Stream { public static $sourceStreams = array(); private $source; private $path; + private $rawPath; // The raw path received by stream_open private $readBuffer; // For streams that dont support seeking private $meta = array(); // Header / meta for source stream private $count; private $writeCache; private $size; + private $keyfile; private static $view; public function stream_open( $path, $mode, $options, &$opened_path ) { @@ -52,7 +54,9 @@ class Stream { // Get the bare file path $path = str_replace( 'crypt://', '', $path ); - + + $this->rawPath = $path; + if ( dirname( $path ) == 'streams' and isset( self::$sourceStreams[basename( $path )] ) @@ -115,33 +119,77 @@ class Stream { 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); + 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->source); - $data=fread($this->source,8192); - if (strlen($data)) { - $result=Crypt::decrypt($data); - }else{ - $result=''; + + $pos = ftell( $this->source ); + + $data = fread( $this->source, 8192 ); + + if ( strlen( $data ) ) { + + $result = Crypt::symmetricDecryptFileContent( $data, $this->keyfile ); + + } else { + + $result = ''; + } - $length=$this->size-$pos; - if ($length<8192) { - $result=substr($result,0,$length); + + $length = $this->size - $pos; + + if ( $length < 8192 ) { + + $result = substr( $result, 0, $length ); + } + return $result; + + } + + /** + * @brief Get the keyfile for the current file, generate one if necessary + */ + public function getKey() { + + # TODO: Move this user call out of here - it belongs elsewhere + $user = \OCP\User::getUser(); + + if ( self::$view->file_exists( $this->rawPath . $user ) ) { + + // If the data is to be written to an existing file, fetch its keyfile + $this->keyfile = Keymanager::getFileKey( $this->rawPath . $user ); + + } else { + + // If the data is to be written to a new file, generate a new keyfile + $this->keyfile = Crypt::generateKey(); + + } + } /** * @brief */ public function stream_write( $data ) { - + + # TODO: Find a way to get path of file in order to know where to save its parallel keyfile + + \OC_FileProxy::$enabled = false; + $length = strlen( $data ); $written = 0; @@ -151,15 +199,13 @@ class Stream { # TODO: Move this user call out of here - it belongs elsewhere $user = \OCP\User::getUser(); - if ( self::$view->file_exists( $this->path . $user ) ) { + // Set keyfile property for file in question + $this->getKey(); - $key = Keymanager::getFileKey( $this->path . $user ); + if ( ! self::$view->file_exists( $this->rawPath . $user ) ) { - } else { - - $key = Crypt::generateKey(); - - Keymanager::setFileKey( $path, $key, new \OC_FilesystemView ); + // Save keyfile in parallel directory structure + Keymanager::setFileKey( $this->rawPath, $this->keyfile, new \OC_FilesystemView( '/' ) ); } @@ -180,7 +226,7 @@ class Stream { fseek( $this->source, - ( $currentPos % 8192 ), SEEK_CUR ); - $block = Crypt::symmetricDecryptFileContent( $encryptedBlock, $key ); + $block = Crypt::symmetricDecryptFileContent( $encryptedBlock, $this->keyfile ); $data = substr( $block, 0, $currentPos % 8192 ) . $data; @@ -190,9 +236,9 @@ class Stream { $currentPos = ftell( $this->source ); - while( $remainingLength = strlen( $data )>0 ) { + while( $remainingLength = strlen( $data ) > 0 ) { - if ( $remainingLength<8192 ) { + if ( $remainingLength < 8192 ) { $this->writeCache = $data; @@ -200,9 +246,11 @@ class Stream { } else { - $encrypted = Crypt::symmetricBlockEncryptFileContent( $data, $key ); + $encrypted = Crypt::symmetricBlockEncryptFileContent( $data, $this->keyfile ); + + //$encrypted = $data; - fwrite( $this->source . $user, $encrypted ); + fwrite( $this->source, $encrypted ); $data = substr( $data,8192 ); @@ -255,10 +303,17 @@ class Stream { } 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),''); + + \OC_FileCache::put($this->path,array('encrypted'=>true,'size'=>$this->size),''); + } + return fclose($this->source); + } + } diff --git a/apps/files_encryption/tests/crypt.php b/apps/files_encryption/tests/crypt.php index ef453ab90d6..78f5f74fbf4 100644 --- a/apps/files_encryption/tests/crypt.php +++ b/apps/files_encryption/tests/crypt.php @@ -17,9 +17,13 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { function setUp() { // set content for encrypting / decrypting in tests - $this->data = realpath( dirname(__FILE__).'/../lib/crypt.php' ); + $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->view = new \OC_FilesystemView( '/' ); //stream_wrapper_register( 'crypt', 'OCA_Encryption\Stream' ); @@ -51,9 +55,9 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { $iv = substr( base64_encode( $random ), 0, -4 ); // i.e. E5IG033j+mRNKrht - $crypted = Crypt::encrypt( $this->data, $iv, 'hat' ); + $crypted = Crypt::encrypt( $this->dataUrl, $iv, 'hat' ); - $this->assertNotEquals( $this->data, $crypted ); + $this->assertNotEquals( $this->dataUrl, $crypted ); } @@ -63,11 +67,11 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { $iv = substr( base64_encode( $random ), 0, -4 ); // i.e. E5IG033j+mRNKrht - $crypted = Crypt::encrypt( $this->data, $iv, 'hat' ); + $crypted = Crypt::encrypt( $this->dataUrl, $iv, 'hat' ); $decrypt = Crypt::decrypt( $crypted, $iv, 'hat' ); - $this->assertEquals( $this->data, $decrypt ); + $this->assertEquals( $this->dataUrl, $decrypt ); } @@ -75,81 +79,133 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { # TODO: search in keyfile for actual content as IV will ensure this test always passes - $crypted = Crypt::symmetricEncryptFileContent( $this->data, 'hat' ); + $crypted = Crypt::symmetricEncryptFileContent( $this->dataUrl, 'hat' ); - $this->assertNotEquals( $this->data, $crypted ); + $this->assertNotEquals( $this->dataUrl, $crypted ); $decrypt = Crypt::symmetricDecryptFileContent( $crypted, 'hat' ); - $this->assertEquals( $this->data, $decrypt ); + $this->assertEquals( $this->dataUrl, $decrypt ); } - function testSymmetricBlockEncryptFileContent() { + function testSymmetricBlockEncryptShortFileContent() { + + $key = file_get_contents( '/home/samtuke/owncloud/git/oc3/data/admin/files_encryption/keyfiles/sscceEncrypt-1345649062.key' ); + + $crypted = Crypt::symmetricBlockEncryptFileContent( $this->dataShort, $key ); - $crypted = Crypt::symmetricBlockEncryptFileContent( $this->data, 'hat' ); + $this->assertNotEquals( $this->dataShort, $crypted ); + + + $decrypt = Crypt::symmetricBlockDecryptFileContent( $crypted, $key ); - $this->assertNotEquals( $this->data, $crypted ); + $this->assertEquals( $this->dataShort, $decrypt ); + + } + + function testSymmetricBlockEncryptLongFileContent() { + + $key = file_get_contents( '/home/samtuke/owncloud/git/oc3/data/admin/files_encryption/keyfiles/sscceEncrypt-1345649062.key' ); + + $crypted = Crypt::symmetricBlockEncryptFileContent( substr( $this->dataLong, 0, 6500 ), $key ); + $this->assertNotEquals( $this->dataLong, $crypted ); + + //echo "\n\nCAT ".substr( $this->dataLong, 0, 7000 ); - $decrypt = Crypt::symmetricBlockDecryptFileContent( $crypted, 'hat' ); + $decrypt = Crypt::symmetricBlockDecryptFileContent( $crypted, $key ); - $this->assertEquals( $this->data, $decrypt ); + $this->assertEquals( substr( $this->dataLong, 0, 6500 + + ), $decrypt ); } // function testSymmetricBlockStreamEncryptFileContent() { // -// $crypted = Crypt::symmetricBlockEncryptFileContent( $this->data, 'hat' ); +// \OC_User::setUserId( 'admin' ); +// +// // Disable encryption proxy to prevent unwanted en/decryption +// \OC_FileProxy::$enabled = false; // -// $cryptedFile = file_put_contents( 'crypt://' . '/blockEncrypt', $crypted ); +// $cryptedFile = file_put_contents( 'crypt://' . '/blockEncrypt', $this->dataUrl ); // // // Test that data was successfully written -// $this->assertTrue( $cryptedFile ); +// $this->assertTrue( is_int( $cryptedFile ) ); +// +// // Disable encryption proxy to prevent unwanted en/decryption +// \OC_FileProxy::$enabled = false; +// +// +// +// // Get file contents without using any wrapper to get it's actual contents on disk +// $retreivedCryptedFile = $this->view->file_get_contents( '/blockEncrypt' ); +// +// echo "\n\n\$retreivedCryptedFile = !! $retreivedCryptedFile !!"; +// +// $key = file_get_contents( '/home/samtuke/owncloud/git/oc3/data/files_encryption/keyfiles/tmp/testSetFileKey.key' ); +// +// echo "\n\n\$key = !! $key !!"; // -// $retreivedCryptedFile = file_get_contents( '/blockEncrypt' ); +// $manualDecrypt = Crypt::symmetricDecryptFileContent( $retreivedCryptedFile, $key ); // -// $this->assertNotEquals( $this->data, $retreivedCryptedFile ); +// echo "\n\n\$manualDecrypt = !! $manualDecrypt !!"; +// +// // Check that the file was encrypted before being written to disk +// $this->assertNotEquals( $this->dataUrl, $retreivedCryptedFile ); +// +// $decrypt = Crypt::symmetricBlockDecryptFileContent( $retreivedCryptedFile, $key); +// +// $this->assertEquals( $this->dataUrl, $decrypt ); // // } - function testSymmetricBlockStreamDecryptFileContent() { - - \OC_User::setUserId( 'admin' ); - - $crypted = Crypt::symmetricBlockEncryptFileContent( $this->data, 'hat' ); - - $cryptedFile = file_put_contents( 'crypt://' . '/blockEncrypt', $crypted ); - - $retreivedCryptedFile = file_get_contents( 'crypt://' . '/blockEncrypt' ); - - $this->assertEquals( $this->data, $retreivedCryptedFile ); - - } +// 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 = Crypt::symmetricEncryptFileContentKeyfile( $this->data ); + $crypted = Crypt::symmetricEncryptFileContentKeyfile( $this->dataUrl ); - $this->assertNotEquals( $this->data, $crypted['encrypted'] ); + $this->assertNotEquals( $this->dataUrl, $crypted['encrypted'] ); $decrypt = Crypt::symmetricDecryptFileContent( $crypted['encrypted'], $crypted['key'] ); - $this->assertEquals( $this->data, $decrypt ); + $this->assertEquals( $this->dataUrl, $decrypt ); } function testIsEncryptedContent() { - $this->assertFalse( Crypt::isEncryptedContent( $this->data ) ); + $this->assertFalse( Crypt::isEncryptedContent( $this->dataUrl ) ); $this->assertFalse( Crypt::isEncryptedContent( $this->legacyEncryptedData ) ); - $keyfileContent = Crypt::symmetricEncryptFileContent( $this->data, 'hat' ); + $keyfileContent = Crypt::symmetricEncryptFileContent( $this->dataUrl, 'hat' ); $this->assertTrue( Crypt::isEncryptedContent( $keyfileContent ) ); @@ -168,14 +224,14 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { $this->assertTrue( strlen( $pair1['privateKey'] ) > 1 ); - $crypted = Crypt::multiKeyEncrypt( $this->data, array( $pair1['publicKey'] ) ); + $crypted = Crypt::multiKeyEncrypt( $this->dataUrl, array( $pair1['publicKey'] ) ); - $this->assertNotEquals( $this->data, $crypted['encrypted'] ); + $this->assertNotEquals( $this->dataUrl, $crypted['encrypted'] ); $decrypt = Crypt::multiKeyDecrypt( $crypted['encrypted'], $crypted['keys'][0], $pair1['privateKey'] ); - $this->assertEquals( $this->data, $decrypt ); + $this->assertEquals( $this->dataUrl, $decrypt ); } @@ -185,14 +241,14 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { $pair1 = Crypt::createKeypair(); // Encrypt data - $crypted = Crypt::keyEncrypt( $this->data, $pair1['publicKey'] ); + $crypted = Crypt::keyEncrypt( $this->dataUrl, $pair1['publicKey'] ); - $this->assertNotEquals( $this->data, $crypted ); + $this->assertNotEquals( $this->dataUrl, $crypted ); // Decrypt data $decrypt = Crypt::keyDecrypt( $crypted, $pair1['privateKey'] ); - $this->assertEquals( $this->data, $decrypt ); + $this->assertEquals( $this->dataUrl, $decrypt ); } @@ -204,7 +260,7 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { $pair1 = Crypt::createKeypair(); // Encrypt plain data, generate keyfile & encrypted file - $cryptedData = Crypt::symmetricEncryptFileContentKeyfile( $this->data ); + $cryptedData = Crypt::symmetricEncryptFileContentKeyfile( $this->dataUrl ); // Encrypt keyfile $cryptedKey = Crypt::keyEncrypt( $cryptedData['key'], $pair1['publicKey'] ); @@ -215,7 +271,7 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { // Decrypt encrypted file $decryptData = Crypt::symmetricDecryptFileContent( $cryptedData['encrypted'], $decryptKey ); - $this->assertEquals( $this->data, $decryptData ); + $this->assertEquals( $this->dataUrl, $decryptData ); } diff --git a/apps/files_encryption/tests/keymanager.php b/apps/files_encryption/tests/keymanager.php index e0ce7a1d6ad..a6b425bafa2 100644 --- a/apps/files_encryption/tests/keymanager.php +++ b/apps/files_encryption/tests/keymanager.php @@ -15,7 +15,8 @@ class Test_Keymanager extends \PHPUnit_Framework_TestCase { function setUp() { - // set content for encrypting / decrypting in tests + // Set data for use in tests + $this->data = realpath( dirname(__FILE__).'/../lib/crypt.php' ); $this->user = 'admin'; $this->passphrase = 'admin'; $this->view = new \OC_FilesystemView( '' ); @@ -39,6 +40,22 @@ class Test_Keymanager extends \PHPUnit_Framework_TestCase { } + 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->user . '/files_encryption/keyfiles' ); +// +// Keymanager::setFileKey( $tmpPath, $key['key'], $view ); + + } + function testGetDecryptedPrivateKey() { $key = Keymanager::getPrivateKey( $this->user, $this->view ); @@ -52,4 +69,6 @@ class Test_Keymanager extends \PHPUnit_Framework_TestCase { } + + } -- cgit v1.2.3 From e3ac15bad34697d1d2f797a4d6341e5ad6db3464 Mon Sep 17 00:00:00 2001 From: Sam Tuke Date: Thu, 23 Aug 2012 19:19:39 +0100 Subject: development snapshot --- apps/files_encryption/lib/crypt.php | 24 ++++++++- apps/files_encryption/lib/stream.php | 89 +++++++++++++++++---------------- apps/files_encryption/tests/crypt.php | 92 ++++++++++++++++++++--------------- 3 files changed, 121 insertions(+), 84 deletions(-) (limited to 'apps/files_encryption/lib') diff --git a/apps/files_encryption/lib/crypt.php b/apps/files_encryption/lib/crypt.php index 25ba906deb4..f1e747f111c 100644 --- a/apps/files_encryption/lib/crypt.php +++ b/apps/files_encryption/lib/crypt.php @@ -161,7 +161,7 @@ class Crypt { * @returns decrypted file */ public static function decrypt( $encryptedContent, $iv, $passphrase ) { - + echo "\n\nJET \$passphrase = $passphrase , \$iv = $iv\n\n"; if ( $plainContent = openssl_decrypt( $encryptedContent, 'AES-128-CFB', $passphrase, false, $iv ) ) { return $plainContent; @@ -408,18 +408,30 @@ class Crypt { $remaining = $plainContent; + $testarray = array(); + while( strlen( $remaining ) ) { + //echo "\n\n\$block = ".substr( $remaining, 0, 8192 ); + // Encrypt a chunk of unencrypted data and add it to the rest $block = self::symmetricEncryptFileContent( substr( $remaining, 0, 8192 ), $key ); $crypted .= $block; + $testarray[] = $block; + // Remove the data already encrypted from remaining unencrypted data $remaining = substr( $remaining, 8192 ); } + //echo "hags "; + + //echo "\n\n\n\$crypted = $crypted\n\n\n"; + + //print_r($testarray); + return $crypted; } @@ -430,13 +442,17 @@ class Crypt { */ public static function symmetricBlockDecryptFileContent( $crypted, $key ) { - //echo "\n\n\nfags \$crypted = $crypted\n\n\n"; + echo "\n\n\nfags \$crypted = $crypted\n\n\n"; $decrypted = ''; $remaining = $crypted; + $testarray = array(); + while( strlen( $remaining ) ) { + + $testarray[] = substr( $remaining, 0, 10946 ); // Encrypt a chunk of unencrypted data and add it to the rest // 10946 is the length of a 8192 string once it has been encrypted @@ -447,6 +463,10 @@ class Crypt { } + echo "nags "; + + print_r($testarray); + return $decrypted; } diff --git a/apps/files_encryption/lib/stream.php b/apps/files_encryption/lib/stream.php index d1ab25a0192..4fa266ce8ce 100644 --- a/apps/files_encryption/lib/stream.php +++ b/apps/files_encryption/lib/stream.php @@ -182,7 +182,7 @@ class Stream { } /** - * @brief + * @brief Write write plan data as encrypted data */ public function stream_write( $data ) { @@ -208,55 +208,60 @@ class Stream { Keymanager::setFileKey( $this->rawPath, $this->keyfile, new \OC_FilesystemView( '/' ) ); } - - if ( $this->writeCache ) { - - $data = $this->writeCache . $data; - - $this->writeCache = ''; - - } - // Make sure we always start on a block start - if ( $currentPos % 8192 != 0 ) { - - fseek( $this->source, - ( $currentPos % 8192 ), SEEK_CUR ); - - $encryptedBlock = fread( $this->source, 8192 ); - - fseek( $this->source, - ( $currentPos % 8192 ), SEEK_CUR ); - - $block = Crypt::symmetricDecryptFileContent( $encryptedBlock, $this->keyfile ); - - $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 { - +// // Set $data to contents of writeCache +// // Concat writeCache to start of $data +// if ( $this->writeCache ) { +// +// $data = $this->writeCache . $data; +// +// $this->writeCache = ''; +// +// } + +// // Make sure we always start on a block start +// if ( 0 != ( $currentPos % 8192 ) ) { // If we're not at the end of file yet (in the final chunk), if there will be no bytes left to read after the current chunk +// +// fseek( $this->source, - ( $currentPos % 8192 ), SEEK_CUR ); +// +// $encryptedBlock = fread( $this->source, 8192 ); +// +// fseek( $this->source, - ( $currentPos % 8192 ), SEEK_CUR ); +// +// $block = Crypt::symmetricDecryptFileContent( $encryptedBlock, $this->keyfile ); +// +// $x = substr( $block, 0, $currentPos % 8192 ); +// +// $data = $x . $data; +// +// fseek( $this->source, - ( $currentPos % 8192 ), SEEK_CUR ); +// +// } + +// $currentPos = ftell( $this->source ); +// +// while( $remainingLength = strlen( $data ) > 0 ) { +// +// // Set writeCache to contents of $data +// if ( $remainingLength < 8192 ) { +// +// $this->writeCache = $data; +// +// $data = ''; +// +// } else { + $encrypted = Crypt::symmetricBlockEncryptFileContent( $data, $this->keyfile ); //$encrypted = $data; fwrite( $this->source, $encrypted ); - $data = substr( $data,8192 ); + $data = substr( $data, 8192 ); - } - - } +// } +// +// } $this->size = max( $this->size, $currentPos + $length ); diff --git a/apps/files_encryption/tests/crypt.php b/apps/files_encryption/tests/crypt.php index 78f5f74fbf4..8be1565a435 100644 --- a/apps/files_encryption/tests/crypt.php +++ b/apps/files_encryption/tests/crypt.php @@ -25,8 +25,6 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { $this->view = new \OC_FilesystemView( '/' ); - //stream_wrapper_register( 'crypt', 'OCA_Encryption\Stream' ); - } function tearDown(){} @@ -109,58 +107,72 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { $key = file_get_contents( '/home/samtuke/owncloud/git/oc3/data/admin/files_encryption/keyfiles/sscceEncrypt-1345649062.key' ); - $crypted = Crypt::symmetricBlockEncryptFileContent( substr( $this->dataLong, 0, 6500 ), $key ); + $crypted = Crypt::symmetricBlockEncryptFileContent( $this->dataLong, $key ); $this->assertNotEquals( $this->dataLong, $crypted ); - //echo "\n\nCAT ".substr( $this->dataLong, 0, 7000 ); $decrypt = Crypt::symmetricBlockDecryptFileContent( $crypted, $key ); - $this->assertEquals( substr( $this->dataLong, 0, 6500 + $this->assertEquals( $this->dataLong, $decrypt ); + + } + + function testSymmetricStreamEncryptShortFileContent() { + + \OC_User::setUserId( 'admin' ); + + $filename = 'flockEncrypt'; + + $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( '/'. $filename ); + + // Check that the file was encrypted before being written to disk + $this->assertNotEquals( $this->dataShort, $retreivedCryptedFile ); - ), $decrypt ); + + $key = file_get_contents( '/home/samtuke/owncloud/git/oc3/data/admin/files_encryption/keyfiles/' . $filename . '.key' ); + + $manualDecrypt = Crypt::symmetricBlockDecryptFileContent( $retreivedCryptedFile, $key ); + + $this->assertEquals( $this->dataShort, $manualDecrypt ); } -// function testSymmetricBlockStreamEncryptFileContent() { -// -// \OC_User::setUserId( 'admin' ); -// -// // Disable encryption proxy to prevent unwanted en/decryption -// \OC_FileProxy::$enabled = false; -// -// $cryptedFile = file_put_contents( 'crypt://' . '/blockEncrypt', $this->dataUrl ); -// -// // Test that data was successfully written -// $this->assertTrue( is_int( $cryptedFile ) ); -// -// // Disable encryption proxy to prevent unwanted en/decryption -// \OC_FileProxy::$enabled = false; -// -// -// -// // Get file contents without using any wrapper to get it's actual contents on disk -// $retreivedCryptedFile = $this->view->file_get_contents( '/blockEncrypt' ); -// -// echo "\n\n\$retreivedCryptedFile = !! $retreivedCryptedFile !!"; -// -// $key = file_get_contents( '/home/samtuke/owncloud/git/oc3/data/files_encryption/keyfiles/tmp/testSetFileKey.key' ); -// -// echo "\n\n\$key = !! $key !!"; -// -// $manualDecrypt = Crypt::symmetricDecryptFileContent( $retreivedCryptedFile, $key ); -// -// echo "\n\n\$manualDecrypt = !! $manualDecrypt !!"; -// + function testSymmetricStreamEncryptLongFileContent() { + + \OC_User::setUserId( 'admin' ); + + $filename = 'clockEncrypt'; + + $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( '/'. $filename ); + + echo "\n\nsock $retreivedCryptedFile\n\n"; + // // Check that the file was encrypted before being written to disk -// $this->assertNotEquals( $this->dataUrl, $retreivedCryptedFile ); +// $this->assertNotEquals( $this->dataLong, $retreivedCryptedFile ); // -// $decrypt = Crypt::symmetricBlockDecryptFileContent( $retreivedCryptedFile, $key); // -// $this->assertEquals( $this->dataUrl, $decrypt ); +// $key = file_get_contents( '/home/samtuke/owncloud/git/oc3/data/admin/files_encryption/keyfiles/' . $filename . '.key' ); // -// } +// $manualDecrypt = Crypt::symmetricBlockDecryptFileContent( $retreivedCryptedFile, $key ); +// +// $this->assertEquals( $this->dataLong, $manualDecrypt ); + + } // function testSymmetricBlockStreamDecryptFileContent() { // -- cgit v1.2.3 From ed980674a6a225c30f747e8b8e8952ac182dcbae Mon Sep 17 00:00:00 2001 From: Sam Tuke Date: Tue, 11 Sep 2012 13:40:45 +0100 Subject: Development snapshot --- apps/files_encryption/hooks/hooks.php | 2 + apps/files_encryption/lib/crypt.php | 2 +- apps/files_encryption/lib/stream.php | 207 +++++++++++++++++++++------------ apps/files_encryption/tests/crypt.php | 31 +++-- apps/files_encryption/tests/stream.php | 142 ++++++++++++++++++++++ 5 files changed, 301 insertions(+), 83 deletions(-) (limited to 'apps/files_encryption/lib') diff --git a/apps/files_encryption/hooks/hooks.php b/apps/files_encryption/hooks/hooks.php index b37c974b9c1..71b1b060808 100644 --- a/apps/files_encryption/hooks/hooks.php +++ b/apps/files_encryption/hooks/hooks.php @@ -57,6 +57,8 @@ class Hooks { \OC_FileProxy::$enabled = true; + # TODO: dont manually encrypt the private keyfile - use the config options of openssl_pkey_export instead for better mobile compatibility + $_SESSION['enckey'] = Crypt::symmetricDecryptFileContent( $encryptedKey, $params['password'] ); } diff --git a/apps/files_encryption/lib/crypt.php b/apps/files_encryption/lib/crypt.php index f1e747f111c..bf5e1ab64f2 100644 --- a/apps/files_encryption/lib/crypt.php +++ b/apps/files_encryption/lib/crypt.php @@ -161,7 +161,7 @@ class Crypt { * @returns decrypted file */ public static function decrypt( $encryptedContent, $iv, $passphrase ) { - echo "\n\nJET \$passphrase = $passphrase , \$iv = $iv\n\n"; + if ( $plainContent = openssl_decrypt( $encryptedContent, 'AES-128-CFB', $passphrase, false, $iv ) ) { return $plainContent; diff --git a/apps/files_encryption/lib/stream.php b/apps/files_encryption/lib/stream.php index 4fa266ce8ce..81346b8be3b 100644 --- a/apps/files_encryption/lib/stream.php +++ b/apps/files_encryption/lib/stream.php @@ -32,14 +32,16 @@ namespace OCA_Encryption; class Stream { public static $sourceStreams = array(); - private $source; + + # TODO: make all below properties private again once unit testing is configured correctly + public $rawPath; // The raw path received by stream_open + private $handle; // Resource returned by fopen private $path; - private $rawPath; // The raw path received by stream_open private $readBuffer; // For streams that dont support seeking private $meta = array(); // Header / meta for source stream private $count; private $writeCache; - private $size; + public $size; private $keyfile; private static $view; @@ -61,8 +63,10 @@ class Stream { dirname( $path ) == 'streams' and isset( self::$sourceStreams[basename( $path )] ) ) { + + // Is this just for unit testing purposes? - $this->source = self::$sourceStreams[basename( $path )]['stream']; + $this->handle = self::$sourceStreams[basename( $path )]['stream']; $this->path = self::$sourceStreams[basename( $path )]['path']; @@ -81,18 +85,22 @@ class Stream { } else { - $this->size = self::$view->filesize( $path, $mode ); + //$this->size = self::$view->filesize( $path, $mode ); + $this->size = filesize( $path ); + } // Disable fileproxies so we can open the source file without recursive encryption \OC_FileProxy::$enabled = false; - $this->source = self::$view->fopen( $path, $mode ); + $this->handle = fopen( $path, $mode ); + + //$this->handle = self::$view->fopen( $path, $mode ); \OC_FileProxy::$enabled = true; - if ( !is_resource( $this->source ) ) { + if ( !is_resource( $this->handle ) ) { \OCP\Util::writeLog( 'files_encryption','failed to open '.$path,OCP\Util::ERROR ); @@ -100,27 +108,30 @@ class Stream { } - if ( is_resource( $this->source ) ) { + if ( is_resource( $this->handle ) ) { - $this->meta = stream_get_meta_data( $this->source ); + $this->meta = stream_get_meta_data( $this->handle ); } - return is_resource( $this->source ); + return is_resource( $this->handle ); } - public function stream_seek($offset, $whence=SEEK_SET) { + public function stream_seek( $offset, $whence = SEEK_SET ) { + $this->flush(); - fseek($this->source,$offset,$whence); + + fseek( $this->handle, $offset, $whence ); + } public function stream_tell() { - return ftell($this->source); + return ftell($this->handle); } public function stream_read( $count ) { - + $this->writeCache = ''; if ( $count != 8192 ) { @@ -133,27 +144,49 @@ class Stream { } - $pos = ftell( $this->source ); - - $data = fread( $this->source, 8192 ); - - if ( strlen( $data ) ) { +// $pos = ftell( $this->handle ); +// + $data = fread( $this->handle, 8192 ); + + //echo "\n\nPRE DECRYPTION = $data\n\n"; +// +// if ( strlen( $data ) ) { + + $this->getKey(); + + echo "\n\nGROWL {$this->keyfile}\n\n"; + + $key = file_get_contents( '/home/samtuke/owncloud/git/oc3/data/admin/files_encryption/keyfiles/tmp-1346255589.key' ); $result = Crypt::symmetricDecryptFileContent( $data, $this->keyfile ); + + echo "\n\n\n\n-----------------------------\n\nNEWS"; + + echo "\n\n\$data = $data"; + + echo "\n\n\$key = $key"; + + echo "\n\n\$result = $result"; + + echo "\n\n\n\n-----------------------------\n\n"; + + //trigger_error("CAT $result"); - } else { - - $result = ''; - - } - - $length = $this->size - $pos; - - if ( $length < 8192 ) { - - $result = substr( $result, 0, $length ); + + +// } else { +// +// $result = ''; +// +// } - } +// $length = $this->size - $pos; +// +// if ( $length < 8192 ) { +// +// $result = substr( $result, 0, $length ); +// +// } return $result; @@ -161,40 +194,45 @@ class Stream { /** * @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 */ - public function getKey() { + public function getKey( $generate = true ) { # TODO: Move this user call out of here - it belongs elsewhere $user = \OCP\User::getUser(); - if ( self::$view->file_exists( $this->rawPath . $user ) ) { - + if ( self::$view->file_exists( $this->rawPath ) ) { + + # TODO: add error handling for when file exists but no keyfile + // If the data is to be written to an existing file, fetch its keyfile - $this->keyfile = Keymanager::getFileKey( $this->rawPath . $user ); + $this->keyfile = Keymanager::getFileKey( $this->rawPath ); } else { - // If the data is to be written to a new file, generate a new keyfile - $this->keyfile = Crypt::generateKey(); + if ( $generate ) { + + // If the data is to be written to a new file, generate a new keyfile + $this->keyfile = Crypt::generateKey(); + + } } } /** - * @brief Write write plan data as encrypted data + * @brief Take plain data destined to be written, encrypt it, and write it block by block */ public function stream_write( $data ) { - # TODO: Find a way to get path of file in order to know where to save its parallel keyfile - \OC_FileProxy::$enabled = false; $length = strlen( $data ); $written = 0; - $currentPos = ftell( $this->source ); + $currentPos = ftell( $this->handle ); # TODO: Move this user call out of here - it belongs elsewhere $user = \OCP\User::getUser(); @@ -208,25 +246,27 @@ class Stream { Keymanager::setFileKey( $this->rawPath, $this->keyfile, new \OC_FilesystemView( '/' ) ); } - -// // Set $data to contents of writeCache -// // Concat writeCache to start of $data + +// // If data exists in the writeCache // if ( $this->writeCache ) { -// +// +// trigger_error("write cache is set"); +// +// // Concat writeCache to start of $data // $data = $this->writeCache . $data; // // $this->writeCache = ''; // // } - +// // // Make sure we always start on a block start // if ( 0 != ( $currentPos % 8192 ) ) { // If we're not at the end of file yet (in the final chunk), if there will be no bytes left to read after the current chunk // -// fseek( $this->source, - ( $currentPos % 8192 ), SEEK_CUR ); +// fseek( $this->handle, - ( $currentPos % 8192 ), SEEK_CUR ); // -// $encryptedBlock = fread( $this->source, 8192 ); +// $encryptedBlock = fread( $this->handle, 8192 ); // -// fseek( $this->source, - ( $currentPos % 8192 ), SEEK_CUR ); +// fseek( $this->handle, - ( $currentPos % 8192 ), SEEK_CUR ); // // $block = Crypt::symmetricDecryptFileContent( $encryptedBlock, $this->keyfile ); // @@ -234,28 +274,36 @@ class Stream { // // $data = $x . $data; // -// fseek( $this->source, - ( $currentPos % 8192 ), SEEK_CUR ); +// fseek( $this->handle, - ( $currentPos % 8192 ), SEEK_CUR ); // // } - -// $currentPos = ftell( $this->source ); -// -// while( $remainingLength = strlen( $data ) > 0 ) { -// -// // Set writeCache to contents of $data +/* + $currentPos = ftell( $this->handle );*/ + +// // While there still remains somed data to be written +// while( strlen( $data ) > 0 ) { +// +// $remainingLength = strlen( $data ); +// +// // If data remaining to be written is less than the size of 1 block // if ( $remainingLength < 8192 ) { -// +// +// //trigger_error("remaining length < 8192"); +// +// // Set writeCache to contents of $data // $this->writeCache = $data; // // $data = ''; // // } else { - $encrypted = Crypt::symmetricBlockEncryptFileContent( $data, $this->keyfile ); + $encrypted = Crypt::symmetricEncryptFileContent( $data, $this->keyfile ); + + file_put_contents('/home/samtuke/tmp.txt', $encrypted); - //$encrypted = $data; + //echo "\n\nFRESHLY ENCRYPTED = $encrypted\n\n"; - fwrite( $this->source, $encrypted ); + fwrite( $this->handle, $encrypted ); $data = substr( $data, 8192 ); @@ -273,38 +321,53 @@ class Stream { public function stream_set_option($option,$arg1,$arg2) { switch($option) { case STREAM_OPTION_BLOCKING: - stream_set_blocking($this->source,$arg1); + stream_set_blocking($this->handle,$arg1); break; case STREAM_OPTION_READ_TIMEOUT: - stream_set_timeout($this->source,$arg1,$arg2); + stream_set_timeout($this->handle,$arg1,$arg2); break; case STREAM_OPTION_WRITE_BUFFER: - stream_set_write_buffer($this->source,$arg1,$arg2); + stream_set_write_buffer($this->handle,$arg1,$arg2); } } public function stream_stat() { - return fstat($this->source); + return fstat($this->handle); } public function stream_lock($mode) { - flock($this->source,$mode); + flock($this->handle,$mode); } public function stream_flush() { - return fflush($this->source); + + return fflush($this->handle); // Not a typo: http://php.net/manual/en/function.fflush.php + } public function stream_eof() { - return feof($this->source); + return feof($this->handle); } private function flush() { - if ($this->writeCache) { - $encrypted=Crypt::encrypt($this->writeCache); - fwrite($this->source,$encrypted); - $this->writeCache=''; + + if ( $this->writeCache ) { + + // Set keyfile property for file in question + $this->getKey(); + + //echo "\n\nFLUSH = {$this->writeCache}\n\n"; + + $encrypted = Crypt::symmetricBlockEncryptFileContent( $this->writeCache, $this->keyfile ); + + //echo "\n\nENCFLUSH = $encrypted\n\n"; + + fwrite( $this->handle, $encrypted ); + + $this->writeCache = ''; + } + } public function stream_close() { @@ -317,7 +380,7 @@ class Stream { } - return fclose($this->source); + return fclose($this->handle); } diff --git a/apps/files_encryption/tests/crypt.php b/apps/files_encryption/tests/crypt.php index 8be1565a435..81d262460bd 100644 --- a/apps/files_encryption/tests/crypt.php +++ b/apps/files_encryption/tests/crypt.php @@ -24,6 +24,8 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { $this->legacyEncryptedData = realpath( dirname(__FILE__).'/legacy-encrypted-text.txt' ); $this->view = new \OC_FilesystemView( '/' ); + + \OC_User::setUserId( 'admin' ); } @@ -146,29 +148,38 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { } function testSymmetricStreamEncryptLongFileContent() { - - \OC_User::setUserId( 'admin' ); - $filename = 'clockEncrypt'; + $filename = 'tmp-'.time(); - $cryptedFile = file_put_contents( 'crypt://' . '/' . $filename, $this->dataLong ); + echo "\n\n\$filename = $filename\n\n"; + + $cryptedFile = file_put_contents( 'crypt://' . '/' . '/home/samtuke/owncloud/git/oc3/data/' . $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( '/'. $filename ); + $retreivedCryptedFile = $this->view->file_get_contents( '/' . $filename ); + + //echo "\n\nsock $retreivedCryptedFile\n\n"; + + // Check that the file was encrypted before being written to disk + $this->assertNotEquals( $this->dataLong.$this->dataLong, $retreivedCryptedFile ); + + $autoDecrypted = file_get_contents( 'crypt:////home/samtuke/owncloud/git/oc3/data/' . $filename ); + + //file_get_contents('crypt:///home/samtuke/tmp-1346255589'); + + $this->assertEquals( $this->dataLong.$this->dataLong, $autoDecrypted ); - echo "\n\nsock $retreivedCryptedFile\n\n"; -// // Check that the file was encrypted before being written to disk -// $this->assertNotEquals( $this->dataLong, $retreivedCryptedFile ); -// -// // $key = file_get_contents( '/home/samtuke/owncloud/git/oc3/data/admin/files_encryption/keyfiles/' . $filename . '.key' ); // // $manualDecrypt = Crypt::symmetricBlockDecryptFileContent( $retreivedCryptedFile, $key ); +// +// echo "\n\n\n\n\n\n\n\n\n\n\$manualDecrypt = $manualDecrypt\n\n"; + // // $this->assertEquals( $this->dataLong, $manualDecrypt ); diff --git a/apps/files_encryption/tests/stream.php b/apps/files_encryption/tests/stream.php index 4c78b2d7b0f..0f65e49f9ec 100644 --- a/apps/files_encryption/tests/stream.php +++ b/apps/files_encryption/tests/stream.php @@ -5,6 +5,148 @@ * later. * See the COPYING-README file. */ + +namespace OCA_Encryption; + +require_once "PHPUnit/Framework/TestCase.php"; +require_once realpath( dirname(__FILE__).'/../../../lib/base.php' ); + +class Test_Stream extends \PHPUnit_Framework_TestCase { + + function setUp() { + + $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(); -- cgit v1.2.3 From 4da67b0a08272ecb6a99a04d6b16ba784f4791da Mon Sep 17 00:00:00 2001 From: Sam Tuke Date: Wed, 10 Oct 2012 18:40:59 +0100 Subject: Development snapshot; encryption stream wrapper now successfully writes files, and passes unit tests --- apps/files_encryption/lib/crypt.php | 6 +- apps/files_encryption/lib/keymanager.php | 33 +++++++- apps/files_encryption/lib/stream.php | 124 +++++++++++++++++++++++-------- apps/files_encryption/tests/crypt.php | 112 ++++++++++++++++++++++++---- lib/filesystemview.php | 3 + 5 files changed, 228 insertions(+), 50 deletions(-) (limited to 'apps/files_encryption/lib') diff --git a/apps/files_encryption/lib/crypt.php b/apps/files_encryption/lib/crypt.php index bf5e1ab64f2..e805752137c 100644 --- a/apps/files_encryption/lib/crypt.php +++ b/apps/files_encryption/lib/crypt.php @@ -441,8 +441,6 @@ class Crypt { * @brief Symmetrically decrypt a file by combining encrypted component data blocks */ public static function symmetricBlockDecryptFileContent( $crypted, $key ) { - - echo "\n\n\nfags \$crypted = $crypted\n\n\n"; $decrypted = ''; @@ -463,9 +461,7 @@ class Crypt { } - echo "nags "; - - print_r($testarray); + //print_r($testarray); return $decrypted; diff --git a/apps/files_encryption/lib/keymanager.php b/apps/files_encryption/lib/keymanager.php index 525943f13d6..9d5e170e7fa 100644 --- a/apps/files_encryption/lib/keymanager.php +++ b/apps/files_encryption/lib/keymanager.php @@ -143,7 +143,38 @@ class Keymanager { return $view->file_get_contents( $keypath . '.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 diff --git a/apps/files_encryption/lib/stream.php b/apps/files_encryption/lib/stream.php index 81346b8be3b..96b754c1a86 100644 --- a/apps/files_encryption/lib/stream.php +++ b/apps/files_encryption/lib/stream.php @@ -3,7 +3,7 @@ * ownCloud * * @author Robin Appelman - * @copyright 2011 Robin Appelman icewind1991@gmail.com + * @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 @@ -29,6 +29,11 @@ namespace OCA_Encryption; +/** + * @brief Provides 'crypt://' stream wrapper protocol. + * @note Paths used with this protocol MUST BE RELATIVE, due to limitations of OC_FilesystemView. crypt:///home/user/owncloud/data <- will put keyfiles in [owncloud]/data/user/files_encryption/keyfiles/home/user/owncloud/data and will not be accessible by other functions. + * @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(); @@ -94,9 +99,9 @@ class Stream { // Disable fileproxies so we can open the source file without recursive encryption \OC_FileProxy::$enabled = false; - $this->handle = fopen( $path, $mode ); + //$this->handle = fopen( $path, $mode ); - //$this->handle = self::$view->fopen( $path, $mode ); + $this->handle = self::$view->fopen( \OCP\USER::getUser() . '/' . 'files' . '/' . $path, $mode ); \OC_FileProxy::$enabled = true; @@ -156,7 +161,7 @@ class Stream { echo "\n\nGROWL {$this->keyfile}\n\n"; - $key = file_get_contents( '/home/samtuke/owncloud/git/oc3/data/admin/files_encryption/keyfiles/tmp-1346255589.key' ); + //$key = file_get_contents( '/home/samtuke/owncloud/git/oc3/data/admin/files_encryption/keyfiles/tmp-1346255589.key' ); $result = Crypt::symmetricDecryptFileContent( $data, $this->keyfile ); @@ -201,17 +206,20 @@ class Stream { # TODO: Move this user call out of here - it belongs elsewhere $user = \OCP\User::getUser(); - if ( self::$view->file_exists( $this->rawPath ) ) { - + //echo "\n\$this->rawPath = {$this->rawPath}"; + + // If a keyfile already exists for a file named identically to file to be written + if ( self::$view->file_exists( $user . '/'. 'files_encryption' . '/' . 'keyfiles' . '/' . $this->rawPath . '.key' ) ) { + # TODO: add error handling for when file exists but no keyfile - // If the data is to be written to an existing file, fetch its keyfile + // Fetch existing keyfile $this->keyfile = Keymanager::getFileKey( $this->rawPath ); } else { if ( $generate ) { - + // If the data is to be written to a new file, generate a new keyfile $this->keyfile = Crypt::generateKey(); @@ -223,16 +231,23 @@ class Stream { /** * @brief Take plain data destined to be written, encrypt it, and write it block by block + * @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 x bytes long. stream_write() is called multiple times on data larger than x to process it x byte chunks. */ public function stream_write( $data ) { \OC_FileProxy::$enabled = false; $length = strlen( $data ); - + $written = 0; - $currentPos = ftell( $this->handle ); + $pointer = ftell( $this->handle ); + + echo "\n\n\$length = $length\n"; + + echo "\$pointer = $pointer\n"; # TODO: Move this user call out of here - it belongs elsewhere $user = \OCP\User::getUser(); @@ -247,10 +262,10 @@ class Stream { } -// // If data exists in the writeCache + // If data exists in the writeCache // if ( $this->writeCache ) { // -// trigger_error("write cache is set"); +// //trigger_error("write cache is set"); // // // Concat writeCache to start of $data // $data = $this->writeCache . $data; @@ -260,15 +275,30 @@ class Stream { // } // // // Make sure we always start on a block start -// if ( 0 != ( $currentPos % 8192 ) ) { // If we're not at the end of file yet (in the final chunk), if there will be no bytes left to read after the current chunk -// -// fseek( $this->handle, - ( $currentPos % 8192 ), SEEK_CUR ); + 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 +// +// echo "\n\nNOT ON BLOCK START "; +// echo $pointer % 8192; +// +// echo "\n\n1. $currentPos\n\n"; +// // +// echo "ftell() = ".ftell($this->handle)."\n"; + +// fseek( $this->handle, - ( $pointer % 8192 ), SEEK_CUR ); +// +// $pointer = ftell( $this->handle ); + +// echo "ftell() = ".ftell($this->handle)."\n"; // -// $encryptedBlock = fread( $this->handle, 8192 ); +// $unencryptedNewBlock = fread( $this->handle, 8192 ); // +// echo "\n\n2. $currentPos\n\n"; +// // fseek( $this->handle, - ( $currentPos % 8192 ), SEEK_CUR ); +// +// echo "\n\n3. $currentPos\n\n"; // -// $block = Crypt::symmetricDecryptFileContent( $encryptedBlock, $this->keyfile ); +// $block = Crypt::symmetricDecryptFileContent( $unencryptedNewBlock, $this->keyfile ); // // $x = substr( $block, 0, $currentPos % 8192 ); // @@ -276,13 +306,14 @@ class Stream { // // fseek( $this->handle, - ( $currentPos % 8192 ), SEEK_CUR ); // -// } -/* - $currentPos = ftell( $this->handle );*/ + } + +// $currentPos = ftell( $this->handle ); -// // While there still remains somed data to be written -// while( strlen( $data ) > 0 ) { +// // 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 block @@ -294,25 +325,56 @@ class Stream { // $this->writeCache = $data; // // $data = ''; -// +// // // } else { - $encrypted = Crypt::symmetricEncryptFileContent( $data, $this->keyfile ); + //echo "\n\nbefore before ".strlen($data)."\n"; + + // Read the chunk from the start of $data + $chunk = substr( $data, 0, 6126 ); + + //echo "before ".strlen($data)."\n"; + + //echo "\n\$this->keyfile 1 = {$this->keyfile}"; + + $encrypted = Crypt::symmetricEncryptFileContent( $chunk, $this->keyfile ); + + //echo "\n\n\$rawEnc = $encrypted\n\n"; + + //echo "\$encrypted = ".strlen($encrypted)."\n"; - file_put_contents('/home/samtuke/tmp.txt', $encrypted); + $padded = $encrypted . 'xx'; - //echo "\n\nFRESHLY ENCRYPTED = $encrypted\n\n"; + //echo "\$padded = ".strlen($padded)."\n"; - fwrite( $this->handle, $encrypted ); + //echo "after ".strlen($encrypted)."\n\n"; + + //file_put_contents('/home/samtuke/tmp.txt', $encrypted); + + fwrite( $this->handle, $padded ); + + $bef = ftell( $this->handle ); + //echo "ftell before = $bef\n"; + + $writtenLen = strlen( $padded ); + //fseek( $this->handle, $writtenLen, SEEK_CUR ); + +// $aft = ftell( $this->handle ); +// echo "ftell after = $aft\n"; +// echo "ftell sum = "; +// echo $aft - $bef."\n"; - $data = substr( $data, 8192 ); + // Remove the chunk we just processed from $data, leaving only unprocessed data in $data var + $data = substr( $data, 6126 ); // } // -// } - - $this->size = max( $this->size, $currentPos + $length ); + } + $this->size = max( $this->size, $pointer + $length ); + + echo "\$this->size = $this->size\n\n"; + return $length; } diff --git a/apps/files_encryption/tests/crypt.php b/apps/files_encryption/tests/crypt.php index 81d262460bd..f993ecf5bbc 100644 --- a/apps/files_encryption/tests/crypt.php +++ b/apps/files_encryption/tests/crypt.php @@ -124,22 +124,25 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { \OC_User::setUserId( 'admin' ); - $filename = 'flockEncrypt'; + $filename = 'tmp-'.time(); - $cryptedFile = file_put_contents( 'crypt://' . '/' . $filename, $this->dataShort ); + $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( '/'. $filename ); + $retreivedCryptedFile = $this->view->file_get_contents( '/admin/files/' . $filename ); + + // Manually remove padding from end of each chunk + $retreivedCryptedFile = substr( $retreivedCryptedFile, 0, -2 ); // Check that the file was encrypted before being written to disk $this->assertNotEquals( $this->dataShort, $retreivedCryptedFile ); - $key = file_get_contents( '/home/samtuke/owncloud/git/oc3/data/admin/files_encryption/keyfiles/' . $filename . '.key' ); + $key = Keymanager::getFileKey( $filename ); $manualDecrypt = Crypt::symmetricBlockDecryptFileContent( $retreivedCryptedFile, $key ); @@ -149,29 +152,84 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { function testSymmetricStreamEncryptLongFileContent() { + // Generate a a random filename $filename = 'tmp-'.time(); echo "\n\n\$filename = $filename\n\n"; - $cryptedFile = file_put_contents( 'crypt://' . '/' . '/home/samtuke/owncloud/git/oc3/data/' . $filename, $this->dataLong.$this->dataLong ); + // 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( '/' . $filename ); - - //echo "\n\nsock $retreivedCryptedFile\n\n"; + $retreivedCryptedFile = $this->view->file_get_contents( '/admin/files/' . $filename ); // Check that the file was encrypted before being written to disk $this->assertNotEquals( $this->dataLong.$this->dataLong, $retreivedCryptedFile ); - $autoDecrypted = file_get_contents( 'crypt:////home/samtuke/owncloud/git/oc3/data/' . $filename ); + // Get file contents without using any wrapper to get it's actual contents on disk + $undecrypted = file_get_contents( '/home/samtuke/owncloud/git/oc3/data/admin/files/' . $filename ); + + //echo "\n\n\$undecrypted = $undecrypted\n\n"; + + // Manuallly split saved file into separate IVs and encrypted chunks + $r = preg_split('/(00iv00.{16,18})/', $undecrypted, 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[14] ); + +// print_r($e); + + $f = array(); - //file_get_contents('crypt:///home/samtuke/tmp-1346255589'); + // Manually remove padding from end of each chunk + foreach ( $e as $e ) { + + $f[] = substr( $e, 0, -2 ); - $this->assertEquals( $this->dataLong.$this->dataLong, $autoDecrypted ); + } + +// print_r($f); + + // Manually fetch keyfile + $keyfile = Keymanager::getFileKey( $filename ); + + // Set var for reassembling decrypted content + $decrypt = ''; + + // Manually decrypt chunk + foreach ($f as $f) { + +// echo "\n\$encryptMe = $f"; + + $chunkDecrypt = Crypt::symmetricDecryptFileContent( $f, $keyfile ); + + // Assemble decrypted chunks + $decrypt .= $chunkDecrypt; + +// echo "\n\$chunkDecrypt = $chunkDecrypt"; + + } + + $this->assertEquals( $this->dataLong.$this->dataLong, $decrypt ); + + // Teadown + + $this->view->unlink( '/admin/files/' . $filename ); + + Keymanager::deleteFileKey( $filename ); + + // Fetch the saved encrypted file using stream wrapper to decrypt it +// $autoDecrypted = file_get_contents( 'crypt:////home/samtuke/owncloud/git/oc3/data/' . $filename ); +// +// //file_get_contents('crypt:///home/samtuke/tmp-1346255589'); +// +// // Check that the retreived decrypted contents match the original +// $this->assertEquals( $this->dataLong.$this->dataLong, $autoDecrypted ); // $key = file_get_contents( '/home/samtuke/owncloud/git/oc3/data/admin/files_encryption/keyfiles/' . $filename . '.key' ); @@ -183,8 +241,36 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { // // $this->assertEquals( $this->dataLong, $manualDecrypt ); - } + } + + function testSymmetricStreamDecryptShortFileContent() { + + \OC_User::setUserId( 'admin' ); + + $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( '/'. $filename ); + + // Check that the file was encrypted before being written to disk + $this->assertNotEquals( $this->dataShort, $retreivedCryptedFile ); + + + $key = file_get_contents( '/home/samtuke/owncloud/git/oc3/data/admin/files_encryption/keyfiles/' . $filename . '.key' ); + + $manualDecrypt = Crypt::symmetricBlockDecryptFileContent( $retreivedCryptedFile, $key ); + + $this->assertEquals( $this->dataShort, $manualDecrypt ); + + } + // Is this test still necessary? // function testSymmetricBlockStreamDecryptFileContent() { // // \OC_User::setUserId( 'admin' ); diff --git a/lib/filesystemview.php b/lib/filesystemview.php index 448663bb081..893305e3470 100644 --- a/lib/filesystemview.php +++ b/lib/filesystemview.php @@ -38,6 +38,9 @@ * OC_Filestorage object */ + /** + * @note default root (if $root is empty or '/') is /data/[user]/ + */ class OC_FilesystemView { private $fakeRoot=''; private $internal_path_cache=array(); -- cgit v1.2.3 From 06ea9c18f8bfbfa6fc7e44f27ab11a68c811bca2 Mon Sep 17 00:00:00 2001 From: Sam Tuke Date: Tue, 16 Oct 2012 15:02:51 +0100 Subject: Development snapshot Stream reading and writing of small and large files working, using write cache --- apps/files_encryption/lib/stream.php | 143 +++++++++++++++++++++------------- apps/files_encryption/tests/crypt.php | 86 ++++++++++---------- 2 files changed, 130 insertions(+), 99 deletions(-) (limited to 'apps/files_encryption/lib') diff --git a/apps/files_encryption/lib/stream.php b/apps/files_encryption/lib/stream.php index 96b754c1a86..0a8efa41d33 100644 --- a/apps/files_encryption/lib/stream.php +++ b/apps/files_encryption/lib/stream.php @@ -90,9 +90,9 @@ class Stream { } else { - //$this->size = self::$view->filesize( $path, $mode ); - - $this->size = filesize( $path ); + $this->size = self::$view->filesize( \OCP\USER::getUser() . '/' . 'files' . '/' . $path, $mode ); + + //$this->size = filesize( $path ); } @@ -140,7 +140,7 @@ class Stream { $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 ); @@ -151,7 +151,11 @@ class Stream { // $pos = ftell( $this->handle ); // - $data = fread( $this->handle, 8192 ); + // Get the data from the file handle, including IV and padding + $padded = fread( $this->handle, 8192 ); + + // Remove padding, leaving data and IV + $data = substr( $padded, 0, -2 ); //echo "\n\nPRE DECRYPTION = $data\n\n"; // @@ -159,8 +163,6 @@ class Stream { $this->getKey(); - echo "\n\nGROWL {$this->keyfile}\n\n"; - //$key = file_get_contents( '/home/samtuke/owncloud/git/oc3/data/admin/files_encryption/keyfiles/tmp-1346255589.key' ); $result = Crypt::symmetricDecryptFileContent( $data, $this->keyfile ); @@ -169,7 +171,7 @@ class Stream { echo "\n\n\$data = $data"; - echo "\n\n\$key = $key"; + echo "\n\n\$key = {$this->keyfile}"; echo "\n\n\$result = $result"; @@ -197,9 +199,34 @@ class Stream { } + /** + * @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::symmetricBlockEncryptFileContent( $plainData, $key ) ) { + + // Add padding. In order to end up with data exactly 8192 bytes long we must add two letters. Something about the encryption process always results in 8190 or 8194 byte length, hence the letters must be added manually after encryption takes place. They get removed in the stream read process + $padded = $encrypted . 'xx'; + + return $padded; + + } 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( $generate = true ) { @@ -216,6 +243,8 @@ class Stream { // Fetch existing keyfile $this->keyfile = Keymanager::getFileKey( $this->rawPath ); + return true; + } else { if ( $generate ) { @@ -223,6 +252,8 @@ class Stream { // If the data is to be written to a new file, generate a new keyfile $this->keyfile = Crypt::generateKey(); + return false; + } } @@ -230,54 +261,61 @@ class Stream { } /** - * @brief Take plain data destined to be written, encrypt it, and write it block by block + * @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 x bytes long. stream_write() is called multiple times on data larger than x to process it x byte chunks. + * @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 ); - echo "\n\n\$length = $length\n"; + //echo "\n\n\$rawLength = $length\n"; - echo "\$pointer = $pointer\n"; + //echo "\$pointer = $pointer\n"; # TODO: Move this user call out of here - it belongs elsewhere $user = \OCP\User::getUser(); - // Set keyfile property for file in question - $this->getKey(); - - if ( ! self::$view->file_exists( $this->rawPath . $user ) ) { + // 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() ) { // Save keyfile in parallel directory structure Keymanager::setFileKey( $this->rawPath, $this->keyfile, new \OC_FilesystemView( '/' ) ); } - // If data exists in the writeCache -// if ( $this->writeCache ) { -// -// //trigger_error("write cache is set"); -// -// // Concat writeCache to start of $data -// $data = $this->writeCache . $data; -// -// $this->writeCache = ''; -// -// } + // 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; + + //echo "\n\ncache + data length = ".strlen($data)."\n"; + + // 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 // -// echo "\n\nNOT ON BLOCK START "; + //echo "\n\nNOT ON BLOCK START "; // echo $pointer % 8192; // // echo "\n\n1. $currentPos\n\n"; @@ -316,17 +354,17 @@ class Stream { // // 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 block -// if ( $remainingLength < 8192 ) { -// -// //trigger_error("remaining length < 8192"); -// -// // Set writeCache to contents of $data -// $this->writeCache = $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 = ''; // -// $data = ''; -// // -// } else { + } else { //echo "\n\nbefore before ".strlen($data)."\n"; @@ -337,26 +375,25 @@ class Stream { //echo "\n\$this->keyfile 1 = {$this->keyfile}"; - $encrypted = Crypt::symmetricEncryptFileContent( $chunk, $this->keyfile ); + $encrypted = $this->preWriteEncrypt( $chunk, $this->keyfile ); //echo "\n\n\$rawEnc = $encrypted\n\n"; //echo "\$encrypted = ".strlen($encrypted)."\n"; - $padded = $encrypted . 'xx'; - - //echo "\$padded = ".strlen($padded)."\n"; + //echo "written = ".strlen($encrypted)."\n"; //echo "after ".strlen($encrypted)."\n\n"; //file_put_contents('/home/samtuke/tmp.txt', $encrypted); - fwrite( $this->handle, $padded ); + // 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 ); - $bef = ftell( $this->handle ); + //$bef = ftell( $this->handle ); //echo "ftell before = $bef\n"; - $writtenLen = strlen( $padded ); + $writtenLen = strlen( $encrypted ); //fseek( $this->handle, $writtenLen, SEEK_CUR ); // $aft = ftell( $this->handle ); @@ -364,16 +401,16 @@ class Stream { // echo "ftell sum = "; // echo $aft - $bef."\n"; - // Remove the chunk we just processed from $data, leaving only unprocessed data in $data var + // 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 ); - echo "\$this->size = $this->size\n\n"; + //echo "\$this->size = $this->size\n\n"; return $length; @@ -418,11 +455,7 @@ class Stream { // Set keyfile property for file in question $this->getKey(); - //echo "\n\nFLUSH = {$this->writeCache}\n\n"; - - $encrypted = Crypt::symmetricBlockEncryptFileContent( $this->writeCache, $this->keyfile ); - - //echo "\n\nENCFLUSH = $encrypted\n\n"; + $encrypted = $this->preWriteEncrypt( $this->writeCache, $this->keyfile ); fwrite( $this->handle, $encrypted ); diff --git a/apps/files_encryption/tests/crypt.php b/apps/files_encryption/tests/crypt.php index f993ecf5bbc..b1b13f2ac89 100644 --- a/apps/files_encryption/tests/crypt.php +++ b/apps/files_encryption/tests/crypt.php @@ -22,6 +22,7 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { $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 = Crypt::generateKey(); $this->view = new \OC_FilesystemView( '/' ); @@ -79,27 +80,25 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { # TODO: search in keyfile for actual content as IV will ensure this test always passes - $crypted = Crypt::symmetricEncryptFileContent( $this->dataUrl, 'hat' ); + $crypted = Crypt::symmetricEncryptFileContent( $this->dataShort, 'hat' ); - $this->assertNotEquals( $this->dataUrl, $crypted ); + $this->assertNotEquals( $this->dataShort, $crypted ); $decrypt = Crypt::symmetricDecryptFileContent( $crypted, 'hat' ); - $this->assertEquals( $this->dataUrl, $decrypt ); + $this->assertEquals( $this->dataShort, $decrypt ); } function testSymmetricBlockEncryptShortFileContent() { - $key = file_get_contents( '/home/samtuke/owncloud/git/oc3/data/admin/files_encryption/keyfiles/sscceEncrypt-1345649062.key' ); - - $crypted = Crypt::symmetricBlockEncryptFileContent( $this->dataShort, $key ); + $crypted = Crypt::symmetricBlockEncryptFileContent( $this->dataShort, $this->randomKey ); $this->assertNotEquals( $this->dataShort, $crypted ); - $decrypt = Crypt::symmetricBlockDecryptFileContent( $crypted, $key ); + $decrypt = Crypt::symmetricBlockDecryptFileContent( $crypted, $this->randomKey ); $this->assertEquals( $this->dataShort, $decrypt ); @@ -107,22 +106,18 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { function testSymmetricBlockEncryptLongFileContent() { - $key = file_get_contents( '/home/samtuke/owncloud/git/oc3/data/admin/files_encryption/keyfiles/sscceEncrypt-1345649062.key' ); - - $crypted = Crypt::symmetricBlockEncryptFileContent( $this->dataLong, $key ); + $crypted = Crypt::symmetricBlockEncryptFileContent( $this->dataLong, $this->randomKey ); $this->assertNotEquals( $this->dataLong, $crypted ); - $decrypt = Crypt::symmetricBlockDecryptFileContent( $crypted, $key ); + $decrypt = Crypt::symmetricBlockDecryptFileContent( $crypted, $this->randomKey ); $this->assertEquals( $this->dataLong, $decrypt ); } function testSymmetricStreamEncryptShortFileContent() { - - \OC_User::setUserId( 'admin' ); $filename = 'tmp-'.time(); @@ -150,6 +145,10 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { } + /** + * @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 + */ function testSymmetricStreamEncryptLongFileContent() { // Generate a a random filename @@ -177,12 +176,12 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { // Manuallly split saved file into separate IVs and encrypted chunks $r = preg_split('/(00iv00.{16,18})/', $undecrypted, NULL, PREG_SPLIT_DELIM_CAPTURE); - //print_r($r); + 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[14] ); + $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[14] ); -// print_r($e); + //print_r($e); $f = array(); @@ -211,7 +210,7 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { // Assemble decrypted chunks $decrypt .= $chunkDecrypt; -// echo "\n\$chunkDecrypt = $chunkDecrypt"; + //echo "\n\$chunkDecrypt = $chunkDecrypt"; } @@ -223,50 +222,49 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { Keymanager::deleteFileKey( $filename ); - // Fetch the saved encrypted file using stream wrapper to decrypt it -// $autoDecrypted = file_get_contents( 'crypt:////home/samtuke/owncloud/git/oc3/data/' . $filename ); -// -// //file_get_contents('crypt:///home/samtuke/tmp-1346255589'); -// -// // Check that the retreived decrypted contents match the original -// $this->assertEquals( $this->dataLong.$this->dataLong, $autoDecrypted ); - - -// $key = file_get_contents( '/home/samtuke/owncloud/git/oc3/data/admin/files_encryption/keyfiles/' . $filename . '.key' ); -// -// $manualDecrypt = Crypt::symmetricBlockDecryptFileContent( $retreivedCryptedFile, $key ); -// -// echo "\n\n\n\n\n\n\n\n\n\n\$manualDecrypt = $manualDecrypt\n\n"; - -// -// $this->assertEquals( $this->dataLong, $manualDecrypt ); - } + /** + * @brief Test that data that is read by the crypto stream wrapper + * @depends testSymmetricStreamEncryptLongFileContent + */ function testSymmetricStreamDecryptShortFileContent() { - - \OC_User::setUserId( 'admin' ); $filename = 'tmp-'.time(); - $cryptedFile = file_put_contents( 'crypt://' . '/' . $filename, $this->dataShort ); + // 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( '/'. $filename ); + $retreivedCryptedFile = $this->view->file_get_contents( '/admin/files/' . $filename ); - // Check that the file was encrypted before being written to disk - $this->assertNotEquals( $this->dataShort, $retreivedCryptedFile ); + $decrypt = file_get_contents( 'crypt://' . $filename ); + $this->assertEquals( $this->dataShort, $decrypt ); - $key = file_get_contents( '/home/samtuke/owncloud/git/oc3/data/admin/files_encryption/keyfiles/' . $filename . '.key' ); + } + + function testSymmetricStreamDecryptLongFileContent() { - $manualDecrypt = Crypt::symmetricBlockDecryptFileContent( $retreivedCryptedFile, $key ); + $filename = 'tmp-'.time(); - $this->assertEquals( $this->dataShort, $manualDecrypt ); + // 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( '/admin/files/' . $filename ); + + $decrypt = file_get_contents( 'crypt://' . $filename ); + + $this->assertEquals( $this->dataLong, $decrypt ); } -- cgit v1.2.3 From 265f3654af3a8abb96a74214e63cd65a0a40f150 Mon Sep 17 00:00:00 2001 From: Sam Tuke Date: Wed, 17 Oct 2012 16:35:19 +0100 Subject: all unit files_encryption crypt unit tests now passing after merge --- apps/files_encryption/ajax/mode.php | 2 +- apps/files_encryption/appinfo/app.php | 22 +++---- apps/files_encryption/appinfo/info.xml | 6 +- apps/files_encryption/hooks/hooks.php | 2 +- apps/files_encryption/lib/crypt.php | 89 ++++++++++++++++++++++----- apps/files_encryption/lib/keymanager.php | 2 +- apps/files_encryption/lib/proxy.php | 46 ++++++++++---- apps/files_encryption/lib/stream.php | 54 +++++++++-------- apps/files_encryption/lib/util.php | 16 ++++- apps/files_encryption/tests/crypt.php | 96 +++++++++++++----------------- apps/files_encryption/tests/keymanager.php | 2 +- apps/files_encryption/tests/out.txt | 79 +++--------------------- apps/files_encryption/tests/stream.php | 2 +- apps/files_encryption/tests/util.php | 10 ++-- lib/base.php | 6 ++ lib/ocs.php | 32 +++++----- 16 files changed, 248 insertions(+), 218 deletions(-) (limited to 'apps/files_encryption/lib') diff --git a/apps/files_encryption/ajax/mode.php b/apps/files_encryption/ajax/mode.php index 64203b42cf5..64c5be94401 100644 --- a/apps/files_encryption/ajax/mode.php +++ b/apps/files_encryption/ajax/mode.php @@ -5,7 +5,7 @@ * See the COPYING-README file. */ -use OCA_Encryption\Keymanager; +use OCA\Encryption\Keymanager; OCP\JSON::checkAppEnabled('files_encryption'); OCP\JSON::checkLoggedIn(); diff --git a/apps/files_encryption/appinfo/app.php b/apps/files_encryption/appinfo/app.php index dd95a1f0944..12920aa8291 100644 --- a/apps/files_encryption/appinfo/app.php +++ b/apps/files_encryption/appinfo/app.php @@ -1,20 +1,20 @@ files_encryption Encryption - Server side encryption of files. DEPRECATED. This app is no longer supported and will be replaced with an improved version in ownCloud 5. Only enable this features if you want to read old encrypted data. Warning: You will lose your data if you enable this App and forget your password. Encryption is not yet compatible with LDAP. + Server side encryption of files. Warning: You will lose your data if you enable this App and forget your password. Encryption is not yet compatible with LDAP. AGPL - Robin Appelman - 4.9 + Sam Tuke + 4 true diff --git a/apps/files_encryption/hooks/hooks.php b/apps/files_encryption/hooks/hooks.php index 71b1b060808..b9758ec0df2 100644 --- a/apps/files_encryption/hooks/hooks.php +++ b/apps/files_encryption/hooks/hooks.php @@ -20,7 +20,7 @@ * */ -namespace OCA_Encryption; +namespace OCA\Encryption; /** * Class for hook specific logic diff --git a/apps/files_encryption/lib/crypt.php b/apps/files_encryption/lib/crypt.php index e805752137c..e92c534a93c 100644 --- a/apps/files_encryption/lib/crypt.php +++ b/apps/files_encryption/lib/crypt.php @@ -22,7 +22,15 @@ * */ -namespace OCA_Encryption; +namespace OCA\Encryption; + +// 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 /** * Class for common cryptography functionality @@ -52,7 +60,7 @@ class Crypt { } } } - + return $mode; } @@ -61,7 +69,7 @@ class Crypt { * @return array publicKey, privatekey */ public static function createKeypair() { - + $res = openssl_pkey_new(); // Get private key @@ -76,9 +84,46 @@ class Crypt { } + /** + * @brief Add arbitrary padding to encrypted data + * @param string $data data to be padded + * @return padded data + * @note In order to end up with data exactly 8192 bytes long we must add two letters. Something about the encryption process always results in 8190 or 8194 byte length, hence the letters must be added manually after encryption takes place + */ + public static function addPadding( $data ) { + + $padded = $data . 'xx'; + + return $padded; + + } + + /** + * @brief Remove arbitrary padding to encrypted data + * @param string $padded padded data to remove padding from + * @return padded data on success, false on error + */ + public static function removePadding( $padded ) { + + if ( substr( $padded, -2 ) == 'xx' ) { + + $data = substr( $padded, 0, -2 ); + + return $data; + + } else { + + # TODO: log the fact that unpadded data was submitted for removal of padding + return false; + + } + + } + /** * @brief Check if a file's contents contains an IV and is symmetrically encrypted * @return true / false + * @note see also OCA\Encryption\Util->isEncryptedPath() */ public static function isEncryptedContent( $content ) { @@ -88,12 +133,18 @@ class Crypt { } + $noPadding = self::removePadding( $content ); + // Fetch encryption metadata from end of file - $meta = substr( $content, -22 ); + $meta = substr( $noPadding, -22 ); // Fetch IV from end of file $iv = substr( $meta, -16 ); +// $msg = "\$content = ".var_dump($content, 1).", \$noPadding = ".var_dump($noPadding, 1).", \$meta = ".var_dump($meta, 1).", \$iv = ".var_dump($iv, 1); +// +// file_put_contents('/home/samtuke/newtmp.txt', $msg ); + // Fetch identifier from start of metadata $identifier = substr( $meta, 0, 6 ); @@ -207,7 +258,9 @@ class Crypt { // Combine content to encrypt with IV identifier and actual IV $combinedKeyfile = self::concatIv( $encryptedContent, $iv ); - return $combinedKeyfile; + $padded = self::addPadding( $combinedKeyfile ); + + return $padded; } else { @@ -237,11 +290,14 @@ class Crypt { } + // Remove padding + $noPadding = self::removePadding( $keyfileContent ); + // Fetch IV from end of file - $iv = substr( $keyfileContent, -16 ); + $iv = substr( $noPadding, -16 ); // Remove IV and IV identifier text to expose encrypted content - $encryptedContent = substr( $keyfileContent, 0, -22 ); + $encryptedContent = substr( $noPadding, 0, -22 ); if ( $plainContent = self::decrypt( $encryptedContent, $iv, $passphrase ) ) { @@ -412,17 +468,19 @@ class Crypt { while( strlen( $remaining ) ) { - //echo "\n\n\$block = ".substr( $remaining, 0, 8192 ); + //echo "\n\n\$block = ".substr( $remaining, 0, 6126 ); // Encrypt a chunk of unencrypted data and add it to the rest - $block = self::symmetricEncryptFileContent( substr( $remaining, 0, 8192 ), $key ); + $block = self::symmetricEncryptFileContent( substr( $remaining, 0, 6126 ), $key ); + + $padded = self::addPadding( $block ); $crypted .= $block; $testarray[] = $block; // Remove the data already encrypted from remaining unencrypted data - $remaining = substr( $remaining, 8192 ); + $remaining = substr( $remaining, 6126 ); } @@ -450,18 +508,17 @@ class Crypt { while( strlen( $remaining ) ) { - $testarray[] = substr( $remaining, 0, 10946 ); + $testarray[] = substr( $remaining, 0, 8192 ); - // Encrypt a chunk of unencrypted data and add it to the rest - // 10946 is the length of a 8192 string once it has been encrypted - $decrypted .= self::symmetricDecryptFileContent( substr( $remaining, 0, 10946 ), $key ); + // Decrypt a chunk of unencrypted data and add it to the rest + $decrypted .= self::symmetricDecryptFileContent( $remaining, $key ); // Remove the data already encrypted from remaining unencrypted data - $remaining = substr( $remaining, 10946 ); + $remaining = substr( $remaining, 8192 ); } - //print_r($testarray); + //echo "\n\n\$testarray = "; print_r($testarray); return $decrypted; diff --git a/apps/files_encryption/lib/keymanager.php b/apps/files_encryption/lib/keymanager.php index 9d5e170e7fa..37669ef62c8 100644 --- a/apps/files_encryption/lib/keymanager.php +++ b/apps/files_encryption/lib/keymanager.php @@ -20,7 +20,7 @@ * */ -namespace OCA_Encryption; +namespace OCA\Encryption; /** * This class provides basic operations to read/write encryption keys from/to the filesystem diff --git a/apps/files_encryption/lib/proxy.php b/apps/files_encryption/lib/proxy.php index 5b0369bde9b..269521857b2 100644 --- a/apps/files_encryption/lib/proxy.php +++ b/apps/files_encryption/lib/proxy.php @@ -27,7 +27,7 @@ * transparent encryption */ -namespace OCA_Encryption; +namespace OCA\Encryption; class Proxy extends \OC_FileProxy { @@ -43,7 +43,7 @@ class Proxy extends \OC_FileProxy { * Tests if server side 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' && Crypt::mode() == 'server' ); @@ -127,6 +127,7 @@ class Proxy extends \OC_FileProxy { // Update the file cache with file info \OC_FileCache::put( $path, array( 'encrypted'=>true, 'size' => $size ), '' ); + // Re-enable proxy - our work is done \OC_FileProxy::$enabled = true; } @@ -170,22 +171,45 @@ class Proxy extends \OC_FileProxy { } + // Disable encryption proxy to prevent recursive calls + \OC_FileProxy::$enabled = false; + $meta = stream_get_meta_data( $result ); + $view = new \OC_FilesystemView(); + + $util = new Util( $view, \OCP\USER::getUser()); + // If file is encrypted, decrypt using crypto protocol - if ( Crypt::mode() == 'server' && Crypt::isEncryptedContent( $path ) ) { + if ( Crypt::mode() == 'server' && $util->isEncryptedPath( $path ) ) { - $keyFile = Keymanager::getFileKey( $filePath ); + file_put_contents('/home/samtuke/newtmp.txt', "bar" ); - $tmp = tmpfile(); + $tmp = fopen( 'php://temp' ); - file_put_contents( $tmp, Crypt::keyDecryptKeyfile( $result, $keyFile, $_SESSION['enckey'] ) ); - - fclose ( $result ); + \OCP\Files::streamCopy( $result, $tmp ); - $result = fopen( $tmp ); + fclose( $result ); - } elseif ( + \OC_Filesystem::file_put_contents( $path, $tmp ); + + fclose( $tmp ); + + $result = fopen( 'crypt://' . $path, $meta['mode'] ); + +// file_put_contents('/home/samtuke/newtmp.txt', "mode= server" ); + +// $keyFile = Keymanager::getFileKey( $filePath ); +// +// $tmp = tmpfile(); +// +// file_put_contents( $tmp, Crypt::keyDecryptKeyfile( $result, $keyFile, $_SESSION['enckey'] ) ); +// +// fclose ( $result ); +// +// $result = fopen( $tmp ); + + } /*elseif ( self::shouldEncrypt( $path ) and $meta ['mode'] != 'r' and $meta['mode'] != 'rb' @@ -216,7 +240,7 @@ class Proxy extends \OC_FileProxy { $result = fopen( 'crypt://'.$path, $meta['mode'] ); - } + }*/ return $result; diff --git a/apps/files_encryption/lib/stream.php b/apps/files_encryption/lib/stream.php index 0a8efa41d33..8264c507bda 100644 --- a/apps/files_encryption/lib/stream.php +++ b/apps/files_encryption/lib/stream.php @@ -27,7 +27,7 @@ * and then fopen('crypt://streams/foo'); */ -namespace OCA_Encryption; +namespace OCA\Encryption; /** * @brief Provides 'crypt://' stream wrapper protocol. @@ -89,8 +89,10 @@ class Stream { $this->size = 0; } else { - - $this->size = self::$view->filesize( \OCP\USER::getUser() . '/' . 'files' . '/' . $path, $mode ); + + + + $this->size = self::$view->filesize( $path, $mode ); //$this->size = filesize( $path ); @@ -101,13 +103,15 @@ class Stream { //$this->handle = fopen( $path, $mode ); - $this->handle = self::$view->fopen( \OCP\USER::getUser() . '/' . 'files' . '/' . $path, $mode ); - + $this->handle = self::$view->fopen( $path, $mode ); + + //file_put_contents('/home/samtuke/newtmp.txt', 'fucking hopeless = '.$path ); + \OC_FileProxy::$enabled = true; if ( !is_resource( $this->handle ) ) { - \OCP\Util::writeLog( 'files_encryption','failed to open '.$path,OCP\Util::ERROR ); + \OCP\Util::writeLog( 'files_encryption', 'failed to open '.$path, \OCP\Util::ERROR ); } @@ -137,6 +141,10 @@ class Stream { public function stream_read( $count ) { + trigger_error("\$count = $count"); + + file_put_contents('/home/samtuke/newtmp.txt', "\$count = $count" ); + $this->writeCache = ''; if ( $count != 8192 ) { @@ -151,11 +159,8 @@ class Stream { // $pos = ftell( $this->handle ); // - // Get the data from the file handle, including IV and padding - $padded = fread( $this->handle, 8192 ); - - // Remove padding, leaving data and IV - $data = substr( $padded, 0, -2 ); + // Get the data from the file handle + $data = fread( $this->handle, 8192 ); //echo "\n\nPRE DECRYPTION = $data\n\n"; // @@ -167,15 +172,17 @@ class Stream { $result = Crypt::symmetricDecryptFileContent( $data, $this->keyfile ); - echo "\n\n\n\n-----------------------------\n\nNEWS"; +// file_put_contents('/home/samtuke/newtmp.txt', '$result = '.$result ); - echo "\n\n\$data = $data"; - - echo "\n\n\$key = {$this->keyfile}"; - - echo "\n\n\$result = $result"; - - echo "\n\n\n\n-----------------------------\n\n"; +// echo "\n\n\n\n-----------------------------\n\nNEWS"; +// +// echo "\n\n\$data = $data"; +// +// echo "\n\n\$key = {$this->keyfile}"; +// +// echo "\n\n\$result = $result"; +// +// echo "\n\n\n\n-----------------------------\n\n"; //trigger_error("CAT $result"); @@ -208,12 +215,9 @@ class Stream { public function preWriteEncrypt( $plainData, $key ) { // Encrypt data to 'catfile', which includes IV - if ( $encrypted = Crypt::symmetricBlockEncryptFileContent( $plainData, $key ) ) { + if ( $encrypted = Crypt::symmetricEncryptFileContent( $plainData, $key ) ) { - // Add padding. In order to end up with data exactly 8192 bytes long we must add two letters. Something about the encryption process always results in 8190 or 8194 byte length, hence the letters must be added manually after encryption takes place. They get removed in the stream read process - $padded = $encrypted . 'xx'; - - return $padded; + return $encrypted; } else { @@ -271,6 +275,8 @@ class Stream { */ public function stream_write( $data ) { + //file_put_contents('/home/samtuke/newtmp.txt', '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; diff --git a/apps/files_encryption/lib/util.php b/apps/files_encryption/lib/util.php index eab5b5edf5b..0f1498885af 100644 --- a/apps/files_encryption/lib/util.php +++ b/apps/files_encryption/lib/util.php @@ -29,7 +29,7 @@ // - 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; +namespace OCA\Encryption; /** * @brief Class for utilities relating to encrypted file storage system @@ -45,8 +45,8 @@ class Util { # DONE: add method to fetch legacy key # DONE: add method to decrypt legacy encrypted data # DONE: fix / test the crypt stream proxy class + # DONE: replace cryptstream wrapper new AES based system - # TODO: replace cryptstream wrapper new AES based system # TODO: add support for optional recovery user in case of lost passphrase / keys # TODO: add admin optional required long passphrase for users # TODO: implement flag system to allow user to specify encryption by folder, subfolder, etc. @@ -222,6 +222,18 @@ class Util { } + /** + * @brief Check if a given path identifies an encrypted file + * @return true / false + */ + public function isEncryptedPath( $path ) { + + $data = $this->view->file_get_contents( $path ); + + return Crypt::isEncryptedContent( $data ); + + } + public function encryptAll( $directory ) { $plainFiles = $this->findFiles( $this->view, 'plain' ); diff --git a/apps/files_encryption/tests/crypt.php b/apps/files_encryption/tests/crypt.php index 30a0caf0034..7f423151490 100644 --- a/apps/files_encryption/tests/crypt.php +++ b/apps/files_encryption/tests/crypt.php @@ -7,11 +7,11 @@ * See the COPYING-README file. */ -namespace OCA_Encryption; +namespace OCA\Encryption; 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/crypt.php' ); class Test_Crypt extends \PHPUnit_Framework_TestCase { @@ -92,33 +92,34 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { } - function testSymmetricBlockEncryptShortFileContent() { - - $crypted = Crypt::symmetricBlockEncryptFileContent( $this->dataShort, $this->randomKey ); - - $this->assertNotEquals( $this->dataShort, $crypted ); - - - $decrypt = Crypt::symmetricBlockDecryptFileContent( $crypted, $this->randomKey ); - - $this->assertEquals( $this->dataShort, $decrypt ); - - } - - function testSymmetricBlockEncryptLongFileContent() { - - $crypted = Crypt::symmetricBlockEncryptFileContent( $this->dataLong, $this->randomKey ); - - $this->assertNotEquals( $this->dataLong, $crypted ); - - - $decrypt = Crypt::symmetricBlockDecryptFileContent( $crypted, $this->randomKey ); - - $this->assertEquals( $this->dataLong, $decrypt ); - - } + // These aren't used for now +// function testSymmetricBlockEncryptShortFileContent() { +// +// $crypted = Crypt::symmetricBlockEncryptFileContent( $this->dataShort, $this->randomKey ); +// +// $this->assertNotEquals( $this->dataShort, $crypted ); +// +// +// $decrypt = Crypt::symmetricBlockDecryptFileContent( $crypted, $this->randomKey ); +// +// $this->assertEquals( $this->dataShort, $decrypt ); +// +// } +// +// function testSymmetricBlockEncryptLongFileContent() { +// +// $crypted = Crypt::symmetricBlockEncryptFileContent( $this->dataLong, $this->randomKey ); +// +// $this->assertNotEquals( $this->dataLong, $crypted ); +// +// +// $decrypt = Crypt::symmetricBlockDecryptFileContent( $crypted, $this->randomKey ); +// +// $this->assertEquals( $this->dataLong, $decrypt ); +// +// } - function testSymmetricStreamEncryptShortFileContent() { + function testSymmetricStreamEncryptShortFileContent() { $filename = 'tmp-'.time(); @@ -129,10 +130,9 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { // Get file contents without using any wrapper to get it's actual contents on disk - $retreivedCryptedFile = $this->view->file_get_contents( '/admin/files/' . $filename ); + $retreivedCryptedFile = $this->view->file_get_contents( $filename ); - // Manually remove padding from end of each chunk - $retreivedCryptedFile = substr( $retreivedCryptedFile, 0, -2 ); + //echo "\n\n\$retreivedCryptedFile = $retreivedCryptedFile"; // Check that the file was encrypted before being written to disk $this->assertNotEquals( $this->dataShort, $retreivedCryptedFile ); @@ -164,37 +164,23 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { $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( '/admin/files/' . $filename ); + $retreivedCryptedFile = $this->view->file_get_contents( $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 ); - // Get file contents without using any wrapper to get it's actual contents on disk - $undecrypted = file_get_contents( '/home/samtuke/owncloud/git/oc3/data/admin/files/' . $filename ); - - //echo "\n\n\$undecrypted = $undecrypted\n\n"; - // Manuallly split saved file into separate IVs and encrypted chunks - $r = preg_split('/(00iv00.{16,18})/', $undecrypted, NULL, PREG_SPLIT_DELIM_CAPTURE); + $r = preg_split('/(00iv00.{16,18})/', $retreivedCryptedFile, NULL, PREG_SPLIT_DELIM_CAPTURE); - print_r($r); + //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[14] ); + $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[11], $r[12].$r[13], $r[14] ); //print_r($e); - $f = array(); - - // Manually remove padding from end of each chunk - foreach ( $e as $e ) { - - $f[] = substr( $e, 0, -2 ); - - } - -// print_r($f); - // Manually fetch keyfile $keyfile = Keymanager::getFileKey( $filename ); @@ -202,11 +188,11 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { $decrypt = ''; // Manually decrypt chunk - foreach ($f as $f) { + foreach ($e as $e) { // echo "\n\$encryptMe = $f"; - $chunkDecrypt = Crypt::symmetricDecryptFileContent( $f, $keyfile ); + $chunkDecrypt = Crypt::symmetricDecryptFileContent( $e, $keyfile ); // Assemble decrypted chunks $decrypt .= $chunkDecrypt; @@ -219,7 +205,7 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { // Teadown - $this->view->unlink( '/admin/files/' . $filename ); + $this->view->unlink( $filename ); Keymanager::deleteFileKey( $filename ); @@ -241,7 +227,7 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { // Get file contents without using any wrapper to get it's actual contents on disk - $retreivedCryptedFile = $this->view->file_get_contents( '/admin/files/' . $filename ); + $retreivedCryptedFile = $this->view->file_get_contents( $filename ); $decrypt = file_get_contents( 'crypt://' . $filename ); diff --git a/apps/files_encryption/tests/keymanager.php b/apps/files_encryption/tests/keymanager.php index a6b425bafa2..463b8a67c38 100644 --- a/apps/files_encryption/tests/keymanager.php +++ b/apps/files_encryption/tests/keymanager.php @@ -6,7 +6,7 @@ * See the COPYING-README file. */ -namespace OCA_Encryption; +namespace OCA\Encryption; require_once "PHPUnit/Framework/TestCase.php"; require_once realpath( dirname(__FILE__).'/../../../lib/base.php' ); diff --git a/apps/files_encryption/tests/out.txt b/apps/files_encryption/tests/out.txt index 00f61adacac..f3e7abfa0be 100644 --- a/apps/files_encryption/tests/out.txt +++ b/apps/files_encryption/tests/out.txt @@ -1,78 +1,17 @@ PHPUnit 3.6.12 by Sebastian Bergmann. -. - -$filename = tmp-1350393650 - - - -$rawLength = 8192 -$pointer = 0 - - -$rawEnc = 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 - -written = 8192 -$this->size = 8192 - - - -$rawLength = 8192 -$pointer = 8192 - - -cache + data length = 10258 - - -$rawEnc = znvUwmPGgw9YsH3B6+6BvP7UfXmHu2ut9QsVnJDwxaI1G7AGXHc9mlQRGm7h/sunL/zfPArAirZVIK9yLUHPc1dFGHutMxTqht8yBbh3xnEQ0AkWL8ZKEGVQuSfBh9hGP7UIa036ZHlHwGIlU4c2MCGdXtUMDrnMOFF41fkkEi9NBe4BENZbDGLqYFkbmg2DIRBZycH4AUJahkb19weNJMIPFRpMqDHNw261/PPdLRhUYKp9zLvzci5jXTTV6LQPcx/lu8kbM8JN74Vx34aJNEwt8ICrqK4XuRUB2RfcIZD+SW7aXNMVA2xOVPbonLEy0ciFGsb7/nx6br2o1ca99++oo0+MtyhKQG1759j4Bo1W/N4eo6+mDsg+/zgO0BGwgKk0ND8zqtl5Bp0tB+JAP3bRFn1QcWBVWXOK4JjQGCsbg7hncILIqn4aOSNJ2SOt+MQFu8ovD2dVVzOjqtYWbuuCgTPxyItwxxeaWADcmdAuPDZ9Fy+uRO/XUE3BCYypDUqSdmG72sd2uyrCPbvPGFKG6UjhwWagmux5HOP/+GCKFAeDPi4/Nc/LrgodZbRSdwL4EAiKPH4S6u9+lOWfciZhBAqsIAXfzeaEDwIZlnarmpcIFHnTiFHt+Lf8icEebldW2pCvnLoG+ralIS5durfutbLGz1l+wXOf8ZKTQf9Uh8qa7DBkGQYA0wLR+DbsJNuq4wd4kt7rhtkXC443gmvr7NdyJ8SajvUeia7jFYUdhANYMhv10blmIHRGHlnCtjesqJquyo/hQTsAL5gMsXcB7+81lZhzE6vfMcIuo8hfansNzVciIrgrG55wqk/CvS75mZS/A2znNhCaw9N5nTVMVfkix/JiQw7I0D38G0aVwvuGShYvur2kFHoIq6eBCIHjYCe1ESlWYinAbGzvUp7RS1wDYYsLgBN+6ofRLl+ItUTVzY8daXRgrIXfHwup1MzKsTJx91PKDhvqfoSWbcs54dXWQG2A7kIokscZ4gDJ49p2tmQn9cEAcCBRWZFHx2YbKWIYH5+ylBsEhG1e7ozVvyy97iTfVaEE+LqHVtrhzWjNVkS6ff9wDnSOfz1lvUyv7MNmcbx6N/VKeeTrpd093r27hKQAcOxQJ2suFsfR3XHLcDCBNuHI9HXU8wYBxcTtxtOMgRcfARVJ9KbMka+Xz2JIqOZcqAhCJBpUbRo3xkpxscwaRZFr7nJKZsQeezPlN8s8shX9+kowvMEI6ZFZRpwHhkwwm1OQwRQmxVI6zUCf80lvf/6ZbfqRawiWYXn/fxEprZRJw28TfVuvAcgN1rnJmjsGKAqmIQfGY8pbrcCS8yVFqBGeiruCUO7dkp+fmWRDXvFIxA+BqsFTbT8emiYaA6W4hyaRxJwD+xrSswM5fDWb5+m29Ietxxr7213T7UTiJ3STAz+F4Ryv4hvLk3KOqtGCxuKGCI8HA1X1f0hmnxg1eMnuh11eZwu55cKVhNU/+E/0K4SX39jCIrxgHbsGRBTVxvM7DeDT1T6uqNZbc0uoqnBdOo/30yzdh1oJcTOAh7SQrVBAvK1rK7so5G9vsHwlOT7f/1DK2PcE71nAqJCP8XcOGD5QyHbV5O8xW4qP8HIjTyJWLYbfcvGFF2Cgj6Rtu9TBXbWYHIUSJCV2KfVhYaI2Mh8vwkKZzbDUfDD/Oea4Uvk2PfSbRbp9zYY52Xm/NJAwRhCuGfVihu76rKWKqwxtBhHP+CVZPTel/RJ9wQ+hmqqRADhsqt4Zxl+GuEjVijnIb9csHPXfJcz0FX1CPkKhPYMDOOE9g1FKrEkLAJJrgGesEc53d1dWphjoM8gkq3PHHy95eWTayM+4MN0if9Ue71uoCxkEBT07jXHInxuOilRLrlX39J6Xf92WZCiZPu8QgDfjKICJ4RGo7hBtQWBiX+rEo1SDAYIZqeT5uEBmend7ct82eEBiHJBpOpOqOGD0zcbD4sqSiqdokL7is3YpPMTYqp2+eYrOBLR9wVMyGvnzr6mRl0YZxH3uFWiPrqNfHnro2DvZDA4Glq1GIrIpPtvFboPEUrJDTtiWliJ3YxlcdI/JU1pGuRTbcx6WVt0mtS8n0peY20kydYzzozdCk6dAClG6Bmf1NRJZKLHM8UrYs/IlRAy1+/w6opwN6+Qo3YbQye4s0N9WmP21fHcg4AUP0E0fAhkpXgWlnnTO9QZ4C89hzvtb80WN1fxPnQmbHujMD6lOH8K7A/ZNhSM2GoatOR2LYzcJYr1joitZhJwNlwy3KMCIa2DgT38cnjzQWr6DrWO5x3F3ujy4sATkLviDdFCrz2cdFBCqtavqIS+b5hq0dEevRrwfF1R1BCRv4Wxw+BaLw3heLij33djS4Qvz7GJOmwGR2kDAO90YPNOuvgcPdpEG9T2xNBGwIT38ZnNfoU+J4y2EHk6xobxgGG2ZPeobtstiSQFJiO9ZRJls5Hn5anVq2F42IHrI3uq5W8zpcYT00jGC0wNKn/DgrhNgk4xy8DPVIDj6csIEL9lOWXaWNuqUkYYmpWfnxJ7QKQeZEFOz74CzVGqnlK6rpjLHHQg75qneDJV23x6ojcYH2o1uLFWbeb0zXus3aCErR/H7ipHgS7Dd6C2KzRTK8tdjZYNqnqPpByh82q8FpOQUoSSJ0cHlUpujFDhXoc+d+am/kduph/DBo+MJiKINBi9Ethy+Xoydcy5tH1G38BuspZpIvs/zEeH5+08WB6JpwNyayvGTNGLE6oMeUXFAvNNA8vxxyrSYuVEFtM7fIZ2UErYw22yyOVyHZ32CBYOgNy3Pf2e1tzn/o5yaPIZMVmcQiZ5RHvzVMvPe5g+MDR6NY6LXocErbxxL/6FvOS9eY9LkwMU+cKwPy5OUOgAIo6dVztK4MFT6/qwecfhRkBK/THKqlPVx5smiLZq3KfoPEZyCJHe2TlrA1hVSG7VBzuRJM7+MOWgkjulT0jdYoXWQ553pHxWP5lkIpkiPaJGZCvq+Hfj570XLhRbKGCtn8k2JJQ2Fap21ZzRI6+o1fG+GyPB4xEfND7ZanmFtFPIIshepUR9anCJXdcO53w8W+YUg0Dmpgn/32KiVVyoMmBn9yQpTc7tybC4/CteQAKAXKz1ZoV58CLc7KOuOkEI0pR/GsM9WssMsR13tDe4dEWCWqgOQi1gP1LjA85jylRfZZz75d6fCYwZTiHVJcx7ZuBsBE142ody5XYprEt/HpYs7Sw8dJJE51drO6XxqClAUXw6rlAjuHuss6gfYwwxxa1bAUWWeLREm0s3QYBR19o4RQTbJrOD1vkhfS1atFmuBn1nQcUGYAw12z/MAMufDFNC77n/VDdRMFrrOmLlERwSNOFKgXCrfClW6XmoXkxOSrnywh5cYwz5v7vltNxychXf2ZAN33BhleZp7VrZ3Nul5ak//tZ4zX8Tp8Hdcop7AsxdUXRs64/6QSriZKvQSTjvYP1oO33DQE8X7q7mDktAi9sSbJwUaj2seiFmGH4t2nJffbakS2ZVk4YISaRLTf2OOaqHmPcKfoxfEiJ1C67m9rLvY1qjvryGkCF6/ug8WhBdkuwa9lcoHVqCZnpNnK4XHiJIQ6srCQJZJAr0WNykfX8AwX2qZJziUp8jnlbr38oRKCtm/VozJJSo1sTpBtxqnxQPajkOCHczcUOqgXVml3SdKeqsB7AtU53cLvnrbV7aLpt4g7zS+SkdcR7PhEPkR3X5VaXSlJFhsypeTTRGiyEKMuHANnbc9QsKTS4Kx6uQEbexFDlIYA8dR/xmjIKQluwSwq+baRESqCMW74l0acbH0CRjCYZEQl+FSZo+YyyvTihrjETVUiOPKLKsCAb2SAvLjTNuDtxb/vPTt2zyBT/09lh1JEsfLJiVD+u/sJgJyaMLKp+TZyZqsFZfTZxT+bJCSi+W4pnng8vLqeG94OqIanBPZf+phPWtQiUUjfhMJtNOdubPqFFZnFia+yow99qXaSZfSjcSXFGsxTTy3Tef+xroEMSrw2dHOOv7ulTRtCAjRIbMJvWvXqnHKl/F/vtVTJYfOd4WXzfjACNmtEjATpXNZb2dl3gZulrVziLWuVsPnR1ZQLdCBdQO3R4IHYvAon8zipUwFSVfydEtCBC3FnOBfRbXEH8qG2r2nmBdsRq2DQRwacGMxWH9lhzhJPzHb2xxejMK8cyW5GQXW6GIeyk7MaVrpBr7NsuqMsesxfZAbuP3/buJajC1Tf/1Vn3BcsmP2RHSxQow4ydbJmQql/KL2UUHXXy+kvAROPOXXcZ20VJj/X9IgrZJNdvxZ2sEBjdl9zJTzHNvDlaRl+EPwYx7QVkfbuaqWkAiygLur0HDPTICgpESGpCj5x39PP3uN0ebaV6lAO/vMJtB3tnQ7ZUOxR1D/2gkHgO15MudB5M+Tccl4TgFAgI3BEVJnAL0QavQlJhptE+Oyf4+HgxZIg+Mbe+Z5V21NEXQ65etIwy5EvIpDcUX2AVDam1P4hc7D7TRNE+owQwfBXwXpDo0eTJHmkz1NWsTdt4C9MgPt5cEUBn/eSSBtMAZViXlwPnfAc0YF4c2SohBbNL553lWyrAGVJYhE+9aKErTVtRURdAqBCCE7XwOQbcH9/Pjee75t+F7PPQFeXGJ5Ezl5p3ZtHrKaDnzX/n6/fK94wn3jKCf0cmXINqxJGh78gBdIZTym1RcrEI5YT1aAIHtEhgp1EbCduXuGb2pwoLgyrcJuCQa1LSZyYSEuavfz0ub252aUu9fCz5btW+6vk5jUty0W1NgE5xhNA6xbp0TRswEZqO6IkWdEbPVuK4AQDmKu2em0UbHwV9O8kUacTJ5bXwNEyR6kFf0oA+7enmRdCusVkidp5S1Ya4saFR839oSy/I+9tFsX3BVYWsXbNmZ9WJ3owcxGH+r8eHTE+KFST/v05ySDtpyR3UNX6BBp3ROzq3OuMHAc0Nc7TLmbyUDTdAzUh6WjgURWNqLq+Kfzlo6Td53TBtrwHjWp6sYSEiRG6W8PbXSXsZCIDdpm8NqCNjukej/1yUUrtjbnwZ4HkjPVqsl+LVFvT3tu7N3b42R7rB533TGWZ1/s4MHaWmiyPQT94ReyRQGb/kISabsxGGTSqkdOjZ7URaOMlB8lDxw05VuYjdj+MBT8RRufVfRrX+N1FptcMnkmqDv4CIWMR3ekNzv6dACovzM1SruTU38lDpl8cgxGbTGMsSA1b/Ww6rHqiGpIyvSN3FAYPcBB5F4jixceEsqPrQRpvy5yg/ofAq8AWpH1uaqlgTbd1mwmIb8aNnEG3MMbRoFWRJTZeUwVkNC2CLne6vzBKiPa/va0crtiBccK/3vVES7XvaK/onteu83Uz/zXZVkUVN3hcRG7pdQgKZR5TEC/3q10KCKHF/eAAwhWlbsKPJ8UFR9aXU7yCzjRyu8U610RMQ0q5PDwmz7MoK+8Mp8CaEjCOjiTxkIPRKzJudWYO0O+uKJfMLPraREGoxARGJOCwPT+ZD7eUm4KyA7SzljA5ywJjWExvGlr2KfeXwVnyhY6SPkq4MBVOmq63Os0ZauDFfSNQSlVt5ae8H1M5iK+R0hD29cb2CuYiRYhab7dmB4/d3HT0ybJN7O9zozo0vmqplnWlQ1zW+W01gtlUfq+oKpda3G4YpoXUQ2td+blGFBKv0QcL+YAo7AjrFQ0YWA0w/6ToV6B3/Ddal0h7pQxZXskpkov5QT2xKGPLsJtADKMUOFqYWT1vl1B6n2Yd7Uw5BGeHymqNK1NoaJJGnJHoVArKfo2RzJLjtKKMUBTHGJ0zhT/0yKmnzysmA83uunU1Q6Zc0jWYKUnGYhHS8E21WfS48z2cIkHXxyFPwx7WbQAmzvQZzgg2m4yrcm4zwsMwh5lrHpQ1rypYzu2GW2tiByq2ZAbbGyJBa5ZMJZRSTiU+i2hLpsjWGmso6nmWXZ5Wy+4LQFRqm0jGDcoyniNz5R43eSJKxLkuE+05to9C69yiVbkBDd57YMwGPjkX0X4Zba3gpXMP02z2aX2JQLoZp9AszWMRiY3/BSXFg6UaqEkZbqh48pS+NqvDoe0Teib7rO58CwFVy0FEoAKq5bw+MwDoYCtwXjFavq6ygnWMshu6NBXuBWyj3it3GvqXaxf1TKxUEyXhr0+IpQBBSlosrlkC3gDXyhDVygnYNMsaLWOYykP+iUiWdlC/1t2RGn/5FhSeSBO+4bocB2g7qvntuI3c1fKaZAWTauw8XCfdRqFNyTlpC5LqsIrhayqy8SRr6y8FXwb+qFKHuJRzaSB0U1KvAhkBCliZJFuI4zfZQ0JQ3YnR+sbRfPJcdkuW9A3sLrDwrp8LVWhKnEORtRfsG56eM8A9I5t1NGY2xsWBgfAXkdpx7cpJIaoSejEO467Kc5YqmpcjTOV+iOb9IS/eR7MblaG8K0+Dri8rBLTR7B1C4js2pZcjbwS2DKJiAUyowE1ziDMxdVYry5EAr06ddaU4x2OumjIL8GFIRkWPaE7dPvoNqs9DXqCWds/ZnQ+Z9yD4Q7cAzkYoJXx3alVjxZhnPHuTNTA49JlNrlMeCxBtolzDNe8ERWgB5OQYOrwa750zEfkt+NM/5Y7WIFuSyxEZmoYwEz7bma5sU8NrkepvWg0Nt+OIoBLXnyUsTk/ekS3PONCSBvS5fTyLrQ+33NKXeaV3Ik+Fld5O4hNS/ku6dsxFwRxnamiIjgfYlfJ9xkcJxMMVs9y/KbPfKSELEtHfKmBVMJDPWT58WZbRn+OQmVscKph38/jjqi6Ftl/W/Sn+rN61jF2RLBoPZxZ/2LzWB4lXis0XDZdoSOcOq5HfSSLTMe/OC7Aqr56LXSCLo/fT5Z6X/HUVcZvKyko+oT4ux/apmemMXU8tYt9oCaRTsHL0KXH4CMCm8xSJRFHxRiCV43Oo+kLWmLdZxf8UZQCcb594g7jnGo/wXMTQPqvZ33ZL9aWlLk05aDouit+TMJWFRE7XPjiLFGP5En8j5dqbHYauF05qAIPtNZ/Hpz6TpO044PpuN1SSQI2r234+xEA3L6I8T/YMms96nugnyj1iuuwc1uOcDPzIoaeati3Km5a9BAl+0D8fYU2YsSn4cRU3nV7Kz1doIw6+F0FbR7QpAwjxGbtpn2TtoCD+6ITycgoRoo5faquyDlE8u1AUOm1VurulWNwJhGF1dtMhHzPrstvjIBljpTgT/Tb41Ai1ywhwIMtLrxAQLCubj1pesMLlBpF80zwV/mskqY69sWRVcA5mCCZVgTiCsi7xOKeqVOhQRmgs8J3medtF4rsYgFH2AWbTmewXA9uQeyW4xxs/u3XRrmQ3Q7kuvkdV4OecEdYYFu1vd/NYVmbT0Z6b7UECjw8O/ouOQbcAb8a9cKj/SZovzqdT2tm/77IxAraCGgvHnIFcLdWr9Wg8zrUME+ok3AX35NW74VsCB74SM+XCiaHQt656lNYwQj+Z1TwOncBnS/EH2WbT5E+LXJ6bunvO5mPe7zxeKw1jSnta9KJvHYBFkQg95A4oergGMRqGqXdGbvqe5wkGEWOUVn4tkLtWt64BUttot0YB8dU5HXjSdg9iybVGvkhTXPmHcSliYsvHvnSopboqkDpKoUXlRoslsmOF0w5nuoEzx5QoOV5X0qAg6GuflLrfSnHD6P3+wzGULNHbh2YRoH/Hqx+3+rdOc7YbBvvigXEUISDp3SzRyGhqNEFwkQaUVw2Cj9OB5YYTzZMH8OCIouoTZqaCknRoD0ax6iUo6T0k6pNBo+sEoVM8nhZodzmIt+jmOadU2M4ocXO/qlkVfe0ftk7lKV1oDtpRbmD4NvtZqoqCkDA81utH2MZ4mrbD6G0mFfBNH5r8GvIf4F+WHA/nE/hFTw8ZL0PbWlHtA5mnxIsRKuhgu3NtHYe8qGXDnr2nmT7gwZZGzPBr3sHedW0anKP21+ejcYFI/xj11bURDOBDivQ78bNkH9WgfWI01qsLrBMpGHOjCvoTe4Jed4H836JVn9LHb8l1++cKN5S2NYlVbby9acdLXw2oRUQtNPD0NYLEOIKi/SMXoWIWjOqmDCZQfhqFvv4qY3lRtnnTsRniZbuLBQc472avDyI28CmYwwpkC5uismVBmWFkz7ZrpVvu9HX1GiusJ8KsV+20SPXWwu+u/ORLODYPoF+iRkdcHc6tW4f8QCQKLTYEU4jOY0NEuJlB6de00iv009zGKkK9rKWzgOhM4xx - -written = 8192 -$this->size = 16384 - +...... - -$rawLength = 8192 -$pointer = 16384 - - -cache + data length = 12324 - - -$rawEnc = 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 - -written = 8192 - - -$rawEnc = 8qBRm/MxQrRBbkF27qHgF5TfX+22Q9LmJqs25zI1ywAZk+UCPa3dNkcLpU6YQAkayLtpWi0Z3SETQv+RawwEhT0I7oj0zUXpRuRn1kZ8YZSUwmgEwZHR90twbhFMbkpMpwF6Hx/GoOMrG6TU8HbGjXi6hAEmU8vD1RRf8ksBSxeSQqhgCpQmzoH7gG/zeLQIhsLoB+lo0OZVJDWcadamt5j8/y3pI8Yyxv14GbVZsDQ9DL8fDCAb9VSnpH7Sv7S/75flG6JONlW/c/GZn4yNTIZ8yUfwvQFwqQCSAO497GvQkicKfqIj4+pO642Eru6XAUXTSiUquOKsVGg8oaCpTvXSda+ot72UIVbBJUTlX3ARbfNbJdeiSOmKwwrwbh34bMuZXaJa7nHPiI9YyMwCH7KC4bZhEcUnXNZEd7LBUuI7tDZxVYkZxsVdAZjGJLGYO8umCxUTMqsvEjaWtaEY38Noj/rgNAe9htELuNG/0q8dTMcImYuyQ5kYiEzRzaEsK1K3wLajSAY2nEt1JtGdiIrD+xzy/CP9N0JInwoLRlbCEQ/Ik3bbxA5lwnNjln+kIMDe+uXeWqFFUhOnu/ZNnam3uwPMEClmiV+knzB/MSAtw8zeyH1ousRZNy+cgwvs9vyFJammSCsFfLGAHjpdPKmeK5bLwR06QMLPQB4bixVda9odYj6G6Bp7WFIUyaiwf68RN8hTKnXhRaZWGP+cv1jn0j0a3VHIzIRqHqxB4xJNta8E4fXTesSwU2zTfa5T4IJDJhsF6mPZ8WqospxIRC/gK2LQon3frfgXTqz36lcuoYNZ4LssOT2cdYIEi2vcCJokb4Ae+teI5sEZxlKYxbfgktI0usCTeru5nFRjIGTZldS61h1+/jhBwV0yWGIYPT3K2mw6tnDZAU9bQDX2OtsValBRXNXxGjuvuNPO69vy8mI5Ed6/Oln+NwHhmW2RSNfZEOqL5QRwiiqXqw+p1yZ3CgJr5Uwx8OSExedhPtINgRagygM/xxXi/amhnPxJsk/gbtx6zEAf/adKlF6PjEchOsYeBgo/vhY1Ycuo7l+e2/HNHo+wO+RlRt1AaSzZfIMOQv72gMsXhQIser05SGCS6OAEwJjsyfUFYxqWldOUP+v/q3raKX/elDHjuPQHsQRX1n88fPqmx89N3wsirwHgefPX4Ah+85FvwS+tqr2kugVsmkkxGwvBXCW6tK0GV8T3BGBUYRG0wEgg8sOUhmxoDFHRnCw7IlCjRW/4m03hg0HY6fGo4JcV79Yq87VEFgAQmMkubop2zrwrdK7FE5ugvZgzqkUKZqgTqs62q/BQJjZwcUf80OF2GEDu9FeE9P7aD3ugb/1U0diC5QO6y8U7ywIfc6kwfqLIZ4Uwqlvawp2XV4qV2bx0cLiGjk96HyG7YLyIi0xGuCZbzRqDzefze4rDDsRH0fCx0Et/2KMdFA8V03V/sT6nDtaA1pf3bioxJ2w3xMNUEQI3vX06JlxJO2J3ZF4bLe2284FE/DZUl1NP5i9JHbExkWGrHEYUOe19JGd9ayHk9WmNudNoizZtqdjcKtyAEGDFXdaTy3GaW1NDQTnbdVA2Ih/WkRR4LLvyRp9IHSsm/9s6j+vnrPXaaqEoTwba6rRmgQaaBzjzkGbWJ1w0DbNU1c5tS+rlS/xmRDQFwQaFnWpr0CqrGV5pYLKa0Q4uHB2FuDJ/cKlSacv2P0WmWlWEwml7FKQVLi+nxysh5Banf4EJlOlerqZRao1vmr7QMJL0hA2WNFDOHISiJTMSbOsskVrd4144OJ6kEOh6xMUV0mZKVG+Zh1VS5YvIH9gDrB3tIuE9MxgpQvxzt2tq00TnsIPqICSB3lptwFow6Vg+iYscqpYHcZRzCZ99cm9ejYqgDxmodb3zy/H8TsQSPQBDsyI8XvN8kyo06ePX/iM4TQ6SjdskTh2J+lqK1l4gvgL3RQ05Cz3Z4+LMTG4v5T6VdBIYKGKIErVXF8OWl7SY40XSSiy6SI2mA+j/iGdNhdR8UyT28e6RNVqzvcAdRZqcsTK9xTlzkXtSNVm71bbibeRcTzJT6J+G721Nnq8J9VO8jxvTNIIxBn+R9zbPTfB0iCn+heu8qgOk2NVpU6Gmi9gdwhI4DDLfe9BLc5H6laPd1q8YE3j8+pfR/gCQUfst347/Bh22eGTbxP8cI2HdXCw2xKzXnyTrKj76ldFQxKX6dPMm7madCkWKxgVypzT0l4fZLaMxUOK/BczXOt2iLLdIZ52sX896Sd/Ngq4vPQu+EeRNlWLy23A5ldwbx8I8DZpm8/u3qdHCXHPyKMwNXK1dO1W0ycKC/poizoNADnKm9Bt6InJ7tex1mc5H0WKgqtRkl9bagQQp0kBsLdwOYYq6gU+dw/JQDw3B8nYQWz39k+HWZKAJc0GlYihKhFOkiBO77EG2gyS5kX73Uzq6l/xMpCTnbS8vhVjTiL6NbyVLy6t4WLyPk69BaR0H9xLXtVxx02YYog2SJPtdw2hdn0qIvhCJWRu10jJiPZVe+9qDZma7fZteCXomqtG1XcJv0shCBttuRdJzBwk8bzwOqENoDZnBctaTalv+28wHAglFPWvsQs/fpVchy3qIFFonbUo91GmPH6Oj74PVoJc0D1IsVDc0a68zFEKcqFZrlKHScm52iwTAm+cs7Eevb/d1cEJqDlq1WYtVl1ViNUGScuHGJBYv5swyEIper3VqpccNiZKeR296hTtOA2K5PP/COqdij7LNgzG17dML6Kf/9mJrwiG/fVScCw+iIu1tKGZ3msqyXTFwq78MBk1gc38V6EkUE1xD3Gv8Nki2w0/ITNzpwPThykJCB5Kn9LyH1WXlzuvEK7+/arpnNDh45ZB93KddERuY97N5oEt09hJ/klMy3EKm0zXpXrewE7I+fQBmm+4MKzuDm+n5js1O5DhZ44We4Qy8+ZkdTfMkl9ylC4/QsNYMjH0lrWQl04tCc7UpENOq3hkdQGCXLM/BFBdPQWaGG/nOoVvQi1oS45+KS9h7bkg3ten3k/TjLVtXHWUX/rttMsir9zWuUsq5r29MTMirqvSDLI5aXfGgZQW4oDdaZV4dS/+/+aVD/62FVKda05yZoeNYxm5cj9ZOAy6x5WEMYMzc0VP95izlutbI/dp73c/k9ifStt/dWLOh5nBE9HEnlbKXMDwPyhVLUz3QXtGphJHIuzYeYk8vtrTbHEIUhwQOe+iikQ/Z8q9IWS+O5cN2qbPonSm30dZhqQXicuQFa062LTjfz9KiJ7qKdggZSEcXa6caXX1uoUm0ow3JzAQbdcy0s1L0wGHuvubBOpiDMm0SLas1A0a/2TeBki49GD3/Ft56sMU5AaTlbI4BdpRE6Z+i34MAeWCy3cAgtrdiHVGODvjHGyxhAwWimcaujyTipRESBxquVmPHwrCL8hICB0YOhFsLIgPRpxkO0YtKUT1/gqQuzVNVxkyhXwUO46KWz44vr1u50abMlMDzBdz18CvOv+pL5v+cEgyqsEyqJEYhZH11c1RotmoCR53U4+0YL1PmoSC/4GrBb2HMl3DPT5axF3zIfqTEdRv/iFKzd9R7R0MgHJqmyM8n8Ut0szPpCFa9UW75LBsiLen4OruzMSDa3mhCs0nDqiIEtv++LlKlK8TlHdNc1OJ8BtQo2n/gyVfoAFsamcgXOK6PoRV+KMQdrlGsl7nUrXwu/1GuVEfqhVJWImEriC+PLFQM3ine7Yq+MtwMHY0EGF3MZ9uPvm4+PPlTHFjQscHtIvnrewFYJkc0DyYJh97EhewS2ULlJ40rgRLs+eN7Ym9/X5SMn/96kSlUDiqIEtf+L1HJlzwYmYCI97SP6aRZUW97wzOwMSc4I1fgtzZ3sa0d8utQQsACoDoue0cc5vfzMcJL10R4LtN1KcRAbGZ1uTtwdJ6zbvrwcafnmYeUR1smEQ1Eq3BhhBuj2MdLKRKwysqM2j+XCBf4Iaf2zFxCKUnbhQNMaEzHRocu2B0hUFRHJgnldWpb0AWQTSsdHNAEc7ZQg9zOTfeaHq8qD4TMTU4yNUBkXI8ICr9oiNYKsQrEQ8zZN4mKXyQwCJOW7EKYqj8jDDmbO1q6DLVEfVuzovbjw5wR8UhSfCIO/vm1llq/B8XiKwqbbKjIJ0Bm9lMpA6Lk8Td8ws97d/IKDK+qBLo6WpHoPQ0Wb9BJQWDd4eD+YKhChlH/o3LRTMnYvvDUs9U/DKjE8mQ0pafK9XW2207A8qO5FgexoFExCipWhtm7VLTr96cJz0Fp3rFVw2ZALKAOEWSpn87k78ag5e9pT9DQ6KeWSJRNFNEVQLYF0/WXQsCe5AGSUjtknCLiZnoaYR15xWsv2jMo+9hZdcmuC8Z3zUe3eU0pgWuciHaT42S/Etc+BxFEOMw8LOEZ8Juz27HYuemrABq4QluInLkNC41Jt/eOBvjR5Rh6oyBWEi2WfTLd6JoT8eWtriON7hTmLBzG9JanWdUXpghonEBSmoHmhhm2D10Rrsb21pq5AGMiKsSDM1kd0e1q7E6gn0o4IYy693y0NDvmQF2qPyGSzAa3Jqb8qYSC65faZzZfld6nlXUSF9oqW6CWSRwKyMWMxLDTobqIPRch/iVThXT/ge2qbSj1w8UjUSJCyu9jaQo1y3UzVm9rTH4KRdfgqO7HpRk7xQeEZ5jZ/aU/oDyioV+ZsYFEub62Sq6/SADwX+rvnC5CgG4R283TBCN4yFjVbIsJLwkRdzJxfbf3K0+7ADfBoI73K9vlLYk169cWtzWAW14GZ+6e17plMAIumH4nYES6DzPFNHT1LLVq1Nm2cD/oiXra8IPyrbQKzB43uu3r1arwF/apRmVgl1Dlu57UGVpJWXEvxDJr6oltqlYFBMggXMzX9TZ5YVo2ieuqrAs3dXHdns4etEGRfxLAC2Af9ZekY73E/j9qNTSY9biZJdFKhWye6fwCQUsxEggL3pBLXm3/FDRfSECPUNi1fkmVo7tZeIrXgE2qRMAupQlktmQ2pFsp7mygjvFRFUh6Lk/BT9QZ7blBbIr/aquPNg/EVE7xJ86EMLzvPSCm4k84GvLwaFitIZYi7veWN885KU2c7cRvtjDKXu0Uwd+aoUdjsL5O4bGOfyYIoezKI4UeYBnAGSPPgx9hUikNlGxlY+LBjX9reczhpDhVn5eH4vmPGnK3DTpyx1nT366qoVpspTik7RWPTu41fA9JSPP+CHLSobK14JYOVf7h8Boj389CGzL+oMyPgnoWOVVrdPKxRIbQ+sBNiKfkpl+rzo7uJQGgMyeLkvT8MeQu4fZDvnb42x0XfIGAr99eo/fxRIARcySpJM+cteiyT1H6/nUJxCRM9AD7w5zgJHtxbgwbXKWyA9/gaTk3yTcCmPM9eoszPnVYAmiSlxNWknDhluCQRS16nc6NOjvyC83QOqbQ9CxH+9Q1NZWYrZIIfbXjDvxfwHUQpnw20+g1FSwCj8Ws5YFE50iq8niRqHT3Zyn2I8SCf1M8Wj0qB3zkwkApzC6qH4z5DOMAzA7zNv5eoeYFusNlZ9sgAUsq7T57YvYes294koUX0VwzA0O8/0CRSVNyjErMvs3NEXff7OcgcxiH+F1nzshEynmhTTwbg40vTh+PFcNA+ofLCMze/W99mHqRzzfmgxdzh7LZjT41MUwPCUvU5vzdKbLRtA6D5o0O1kgQRNP0i0ixjJRC8XooCdpCtdSfbg5JfjBDCzdL41m61jvcVlFWIATx9BvTaAk7HoFDL3eD3Mfr4KR8GeOPp6HgbuD2WAroQb6NvqudjhUOQr8FaYySBYtfvjEBFPjXf2OLfyGhwvGCEZdIkqd1Q5YrJA9pM3ZyW8LomIzXNw5JFS+BxrS2GKcBeK2RLC1BqM6D8BLQCgiY05xc/pyzhbOGNagwlUPyWEHi22/7EW2P+XS3oyISxIj8UMX5DGcTmbYOq1K173z/TMpsOPSdZ7FDOyD3oTZGQzZig6L41/cGPIMHmeDeKuvyaNJ/AcwBejBbF5/GfKFJMUWD/N6MyphnCCPcTuBuIVay/aUgc9WgSmnH2jLWNZM4w5+p6GUdTN6PtXh6PArFOVHfN3uNRA5mnIFkuomenw/cbDRiYeMxBwN+JQ3MjhIO78jkaj8kIjG/F03Pp58oaLzzDGmNpUhNGl3x6dYMSDibLIHPoOp6DJKzuqmeP1lMXcpSkz/62mNXtysKkD3Wv+1t/IHreswW3IPsh8iGWsz6TvH5YrFJHeO6ZdgYH00i/l2Z3EkHJjgl7P1SygVxmQFF+82YgNWtG5RPu7IFvhpj0Tk0E7HwuPByqYrOR0FOzjg2zWQMkPUnurCtx4Wl9OknkwIwnGkqAUBjNo5KeCmBpOOdzstdIvAObuMCPXXl4mtKKk8ZcMiRNQp0VXfQIyoFc70bMgM5lD/7i40NPt9qc9voja0IVIkPqktwsSac/kx72gXFKLDnzxgHP2ZpIif8qSz1qZJAyKEhpiUcvlS3SR7YEeHQ1Ia+rmQTN3FIerN9NlGRli+9m2lS130aq5gw/Iw7FdYdsXZSHNe+YFrXXamOS5flx2x1ZwimfYp96BxuBdKdSUdp0Sj3fFJV82Up3gGmxfDn1QHbaNBe/rZZjUqZpGcAkZE4Jhkk73JELGNJbXysysrhEeQBzkOVEJH9PXD42Ww03aK31ipiEq26K4mQyPwTBDjtpj+69amPwkUwWjK5DLvxoQ9v+r8oxq7/K6O78dVxcAXdG3HfmSjZ3Sjz+18CDeLrdQg+na4hp22mshQGqv1XXVqPzkj5ceEj/EQMojr4dgdVtXoTnODuiUTUXJRyDa2Lh/h69aZcd0nKUI8eXyl0zz5TAyseTEm1BZ+9z6CUnhCUFzVPOAav+DPY+xeciUgFybBzsHvci+lK2af2Mn/CBepTGJLnM8Y3bQgseL3gmoNJXFaoKGnaKqpwgRxWfgQ3YDauk8UiwyEXNfnGYAlHPvC6oaiYdjbRU3M5G3euzXX8f1mF4UhHdlwVt4kEsLjXoqdbmTYQcuXT5JI0NCwJDeRqLXwePJKcRPDqr0Zu1SDbGKxBrbZjW/qYDN6y3WjpZyC8X2hJyv/gGQzitd0NXcG5kKTjMTkNoOLcBbnoWN9NzsLeC8RTHO0KJwFxgYP5ofbgcClPohRKBmtkSDixjS3mr8IuFWJqLCKz/WS8aaMyKx0/ov1Mp7B/DnoI730uSnshp31XRuPHXCq57I84lvXi2Unoh894lJc3V63g9S/guN99bOyQ0qzmuDQr+gGLEnKpuQpyzrmYjPBkRjUy28qi4Pgti9qegkDghNN+E61zoLDVQIUFfqiczf25bzkucPIxJQAAIVBfUrsJc5cbP+v+waFFsNshSwg8hihQHsfy7yBcPYP+7w2KybF/RxajcxL8STCo+VR4PcS9CaN07NqrLOaiSUDiLN7YjKIc2N4saOF6WGmCm7wc3JTTbDuJe6/72bLzih+ruZFpR3Yp46bayWKEbVw9PVHvhIHycFJoBI7mPU7IL0DESuOp1iFevdNI96ExlvIUkWtbT+vsM6bX8pRGGAoV2TsdvArUcZSjluNLg4ZT9wA3p5VaHHhu1JJmS+CCjlO14AnerRZpxdjOGg4SA6/Ncl+fmB/QqxQ7jdENn1FKGAIOj5vMK4Az+3azOrsKtS2EvTpsq9sNP1R84+7kdnu6JlEWqRbp2Vo/26+dJ0t7h8lgHsYDy+zp7nwdWDs5zZABq0jrJfylgDxr7gZ5KgRtjWhznYkcMhkLn7VL+xtyayZSfg7v0dnU5XRQsvAnj0pP+opBNiCk7WY+FBODjDT6DJgoElZY1BKOdXsu0sWaVfo/grHS5IDGQZt53/jDQajA0e653ZNjEx3Hc9+j1OO8ed6jQ9TMBDg/zm1G591u5uJQNwb75u/DjqCoxTRBmYSqp+8Lf30IN4Ph698sURfmKey0LBRqf4S6Gq40mUi90ApJTEFgxAeOj/mXbPci4siutXMtSUcpd4CNeoUGd4cLo63PFvUou7SkAJ0QQvPF2hdVb5jtviMxxA+QPtlmHz1/LH4f8VpWkfnBn7I46CxtXa4lMx5EMoQUrp9rufKDyjLVXSgZdLEy7C2XWDOGZrQYaGAs0nylRDfosfBH9KvtCJ/m7sVzzjGjXH877VtxSu9iUtbL14TJUnpF00iv00nIRclAfQYB7zKU8mxx - -written = 8192 -$this->size = 24576 - - - -$rawLength = 2954 -$pointer = 32768 - - -cache + data length = 3026 -$this->size = 35722 - -Array +$testarray = Array ( - [0] => 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 - [1] => 00iv00M/4PgOZwLTXSOHH7xx - [2] => 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 - [3] => 00iv009zGKkK9rKWzgOhM4xx - [4] => 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 - [5] => 00iv00sbQ4oOpn7O1qZ/Wzxx - [6] => 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 - [7] => 00iv00nIRclAfQYB7zKU8mxx - [8] => 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 - [9] => 00iv00Mkl5TvkupI7ENPlrxx - [10] => + [0] => Syz/Pg==00iv002QCKh0n9d8/x9Fk7xx ) +. + +$filename = tmp-1350488042 +....... -Time: 0 seconds, Memory: 5.50Mb +Time: 1 second, Memory: 6.00Mb -OK (1 test, 3 assertions) +OK (14 tests, 29 assertions) diff --git a/apps/files_encryption/tests/stream.php b/apps/files_encryption/tests/stream.php index 0f65e49f9ec..52e85fe4850 100644 --- a/apps/files_encryption/tests/stream.php +++ b/apps/files_encryption/tests/stream.php @@ -6,7 +6,7 @@ * See the COPYING-README file. */ -namespace OCA_Encryption; +namespace OCA\Encryption; require_once "PHPUnit/Framework/TestCase.php"; require_once realpath( dirname(__FILE__).'/../../../lib/base.php' ); diff --git a/apps/files_encryption/tests/util.php b/apps/files_encryption/tests/util.php index f24b1642052..ea8fade142a 100644 --- a/apps/files_encryption/tests/util.php +++ b/apps/files_encryption/tests/util.php @@ -27,20 +27,20 @@ class Test_Encryption extends UnitTestCase { // // Cannot use this test for now due to hidden dependencies in OC_FileCache // function testIsLegacyEncryptedContent() { // -// $keyfileContent = OCA_Encryption\Crypt::symmetricEncryptFileContent( $this->legacyEncryptedData, 'hat' ); +// $keyfileContent = OCA\Encryption\Crypt::symmetricEncryptFileContent( $this->legacyEncryptedData, 'hat' ); // -// $this->assertFalse( OCA_Encryption\Crypt::isLegacyEncryptedContent( $keyfileContent, '/files/admin/test.txt' ) ); +// $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' ) ); +// $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 ); +// $c = new \OCA\Encryption\Util( $view, false ); // // $bool = $c->getLegacyKey( 'admin' ); // @@ -57,7 +57,7 @@ class Test_Encryption extends UnitTestCase { // // 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 ); +// $c = new OCA\Encryption\Util( $this->view, false ); // // $bool = $c->getLegacyKey( 'admin' ); // diff --git a/lib/base.php b/lib/base.php index 803d3e8bde5..3cec1449474 100644 --- a/lib/base.php +++ b/lib/base.php @@ -71,6 +71,11 @@ class OC{ * SPL autoload */ public static function autoload($className) { + + //trigger_error('seth', E_ERROR); + + //debug_print_backtrace(); + if(array_key_exists($className, OC::$CLASSPATH)) { $path = OC::$CLASSPATH[$className]; /** @TODO: Remove this when necessary @@ -106,6 +111,7 @@ class OC{ } public static function initPaths() { + // calculate the root directories OC::$SERVERROOT=str_replace("\\", '/', substr(__DIR__, 0, -4)); OC::$SUBURI= str_replace("\\", "/", substr(realpath($_SERVER["SCRIPT_FILENAME"]), strlen(OC::$SERVERROOT))); diff --git a/lib/ocs.php b/lib/ocs.php index 19fbe26d903..d04959c715d 100644 --- a/lib/ocs.php +++ b/lib/ocs.php @@ -681,8 +681,8 @@ class OC_OCS { */ private static function publicKeyGet($format, $file) { $login=OC_OCS::checkpassword(); - if(OC_App::isEnabled('files_encryption') && OCA_Encryption\Crypt::mode() === 'client') { - if (($keys = OCA_Encryption\Keymanager::getPublicKeys($file))) { + if(OC_App::isEnabled('files_encryption') && OCA\Encryption\Crypt::mode() === 'client') { + if (($keys = OCA\Encryption\Keymanager::getPublicKeys($file))) { $xml=$keys; $txt=OC_OCS::generatexml($format, 'ok', 100, '', $xml, 'cloud', '', 1, 0, 0); echo($txt); @@ -703,8 +703,8 @@ class OC_OCS { */ private static function publicKeySet($format, $key) { $login=OC_OCS::checkpassword(); - if(OC_App::isEnabled('files_encryption') && OCA_Encryption\Crypt::mode() === 'client') { - if (OCA_Encryption\Keymanager::setPublicKey($key)) { + if(OC_App::isEnabled('files_encryption') && OCA\Encryption\Crypt::mode() === 'client') { + if (OCA\Encryption\Keymanager::setPublicKey($key)) { echo self::generateXml('', 'ok', 100, ''); } else { echo self::generateXml('', 'fail', 404, 'could not add your public key to the key storage'); @@ -721,8 +721,8 @@ class OC_OCS { */ private static function privateKeyGet($format) { $login=OC_OCS::checkpassword(); - if(OC_App::isEnabled('files_encryption') && OCA_Encryption\Crypt::mode() === 'client') { - if (($key = OCA_Encryption\Keymanager::getPrivateKey())) { + if(OC_App::isEnabled('files_encryption') && OCA\Encryption\Crypt::mode() === 'client') { + if (($key = OCA\Encryption\Keymanager::getPrivateKey())) { $xml=array(); $xml['key']=$key; $txt=OC_OCS::generatexml($format, 'ok', 100, '', $xml, 'cloud', '', 1, 0, 0); @@ -743,8 +743,8 @@ class OC_OCS { */ private static function privateKeySet($format, $key) { $login=OC_OCS::checkpassword(); - if(OC_App::isEnabled('files_encryption') && OCA_Encryption\Crypt::mode() === 'client') { - if (($key = OCA_Encryption\Keymanager::setPrivateKey($key))) { + if(OC_App::isEnabled('files_encryption') && OCA\Encryption\Crypt::mode() === 'client') { + if (($key = OCA\Encryption\Keymanager::setPrivateKey($key))) { echo self::generateXml('', 'ok', 100, ''); } else { echo self::generateXml('', 'fail', 404, 'could not add your private key to the key storage'); @@ -761,8 +761,8 @@ class OC_OCS { */ private static function userKeysGet($format) { $login=OC_OCS::checkpassword(); - if(OC_App::isEnabled('files_encryption') && OCA_Encryption\Crypt::mode() === 'client') { - $keys = OCA_Encryption\Keymanager::getUserKeys(); + if(OC_App::isEnabled('files_encryption') && OCA\Encryption\Crypt::mode() === 'client') { + $keys = OCA\Encryption\Keymanager::getUserKeys(); if ($keys['privatekey'] && $keys['publickey']) { $xml=array(); $xml['privatekey']=$keys['privatekey']; @@ -786,8 +786,8 @@ class OC_OCS { */ private static function userKeysSet($format, $privatekey, $publickey) { $login=OC_OCS::checkpassword(); - if(OC_App::isEnabled('files_encryption') && OCA_Encryption\Crypt::mode() === 'client') { - if (($key = OCA_Encryption\Keymanager::setUserKeys($privatekey, $publickey))) { + if(OC_App::isEnabled('files_encryption') && OCA\Encryption\Crypt::mode() === 'client') { + if (($key = OCA\Encryption\Keymanager::setUserKeys($privatekey, $publickey))) { echo self::generateXml('', 'ok', 100, ''); } else { echo self::generateXml('', 'fail', 404, 'could not add your keys to the key storage'); @@ -805,8 +805,8 @@ class OC_OCS { */ private static function fileKeyGet($format, $file) { $login=OC_OCS::checkpassword(); - if(OC_App::isEnabled('files_encryption') && OCA_Encryption\Crypt::mode() === 'client') { - if (($key = OCA_Encryption\Keymanager::getFileKey($file))) { + if(OC_App::isEnabled('files_encryption') && OCA\Encryption\Crypt::mode() === 'client') { + if (($key = OCA\Encryption\Keymanager::getFileKey($file))) { $xml=array(); $xml['key']=$key; $txt=OC_OCS::generatexml($format, 'ok', 100, '', $xml, 'cloud', '', 1, 0, 0); @@ -828,8 +828,8 @@ class OC_OCS { */ private static function fileKeySet($format, $file, $key) { $login=OC_OCS::checkpassword(); - if(OC_App::isEnabled('files_encryption') && OCA_Encryption\Crypt::mode() === 'client') { - if (($key = OCA_Encryption\Keymanager::setFileKey($file, $key))) { + if(OC_App::isEnabled('files_encryption') && OCA\Encryption\Crypt::mode() === 'client') { + if (($key = OCA\Encryption\Keymanager::setFileKey($file, $key))) { echo self::generateXml('', 'ok', 100, ''); } else { echo self::generateXml('', 'fail', 404, 'could not write key file'); -- cgit v1.2.3 From dd987a8bd1e405c2335abcd0c3db66fcab865214 Mon Sep 17 00:00:00 2001 From: Sam Tuke Date: Wed, 14 Nov 2012 13:58:57 +0000 Subject: Added minor documentation --- apps/files_encryption/lib/proxy.php | 12 ++++++++++-- apps/files_encryption/lib/stream.php | 9 +++++---- apps/files_encryption/tests/out.txt | 17 ----------------- 3 files changed, 15 insertions(+), 23 deletions(-) delete mode 100644 apps/files_encryption/tests/out.txt (limited to 'apps/files_encryption/lib') diff --git a/apps/files_encryption/lib/proxy.php b/apps/files_encryption/lib/proxy.php index 269521857b2..e3e2161a141 100644 --- a/apps/files_encryption/lib/proxy.php +++ b/apps/files_encryption/lib/proxy.php @@ -183,18 +183,23 @@ class Proxy extends \OC_FileProxy { // If file is encrypted, decrypt using crypto protocol if ( Crypt::mode() == 'server' && $util->isEncryptedPath( $path ) ) { - file_put_contents('/home/samtuke/newtmp.txt', "bar" ); - $tmp = fopen( 'php://temp' ); \OCP\Files::streamCopy( $result, $tmp ); fclose( $result ); + $encrypted = $view->file_get_contents( $path ); + + //file_put_contents('/home/samtuke/newtmp.txt', "\$path = $path, \$data = $data" ); + + // Replace the contents of \OC_Filesystem::file_put_contents( $path, $tmp ); fclose( $tmp ); + //file_put_contents('/home/samtuke/newtmp.txt', file_get_contents( 'crypt://' . $path ) ); + $result = fopen( 'crypt://' . $path, $meta['mode'] ); // file_put_contents('/home/samtuke/newtmp.txt', "mode= server" ); @@ -242,6 +247,9 @@ class Proxy extends \OC_FileProxy { }*/ + // Re-enable the proxy + \OC_FileProxy::$enabled = true; + return $result; } diff --git a/apps/files_encryption/lib/stream.php b/apps/files_encryption/lib/stream.php index 8264c507bda..2e3cdaabe44 100644 --- a/apps/files_encryption/lib/stream.php +++ b/apps/files_encryption/lib/stream.php @@ -31,6 +31,7 @@ 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, due to limitations of OC_FilesystemView. crypt:///home/user/owncloud/data <- will put keyfiles in [owncloud]/data/user/files_encryption/keyfiles/home/user/owncloud/data and will not be accessible by other functions. * @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. */ @@ -52,6 +53,8 @@ class Stream { public function stream_open( $path, $mode, $options, &$opened_path ) { + file_put_contents('/home/samtuke/newtmp.txt', 'stream_open('.$path.')' ); + // Get access to filesystem via filesystemview object if ( !self::$view ) { @@ -141,9 +144,7 @@ class Stream { public function stream_read( $count ) { - trigger_error("\$count = $count"); - - file_put_contents('/home/samtuke/newtmp.txt', "\$count = $count" ); +// file_put_contents('/home/samtuke/newtmp.txt', "\$count = $count" ); $this->writeCache = ''; @@ -275,7 +276,7 @@ class Stream { */ public function stream_write( $data ) { - //file_put_contents('/home/samtuke/newtmp.txt', 'stream_write('.$data.')' ); +// file_put_contents('/home/samtuke/newtmp.txt', '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; diff --git a/apps/files_encryption/tests/out.txt b/apps/files_encryption/tests/out.txt deleted file mode 100644 index f3e7abfa0be..00000000000 --- a/apps/files_encryption/tests/out.txt +++ /dev/null @@ -1,17 +0,0 @@ -PHPUnit 3.6.12 by Sebastian Bergmann. - -...... - -$testarray = Array -( - [0] => Syz/Pg==00iv002QCKh0n9d8/x9Fk7xx -) -. - -$filename = tmp-1350488042 - -....... - -Time: 1 second, Memory: 6.00Mb - -OK (14 tests, 29 assertions) -- cgit v1.2.3 From 459a7622dcebea9a0c31c58ecad4b7fb23a004e6 Mon Sep 17 00:00:00 2001 From: Sam Tuke Date: Wed, 14 Nov 2012 14:58:27 +0000 Subject: Added check if sharing app is enabled, commented out sharing code due to issue --- apps/files_encryption/lib/keymanager.php | 119 ++++++++++++++++--------------- 1 file changed, 61 insertions(+), 58 deletions(-) mode change 100644 => 100755 apps/files_encryption/lib/keymanager.php (limited to 'apps/files_encryption/lib') diff --git a/apps/files_encryption/lib/keymanager.php b/apps/files_encryption/lib/keymanager.php old mode 100644 new mode 100755 index 37669ef62c8..dd13b62d556 --- a/apps/files_encryption/lib/keymanager.php +++ b/apps/files_encryption/lib/keymanager.php @@ -44,7 +44,6 @@ class Keymanager { /** * @brief retrieve public key for a specified user - * * @return string public key or false */ public static function getPublicKey() { @@ -65,7 +64,7 @@ class Keymanager { return array( 'privatekey' => self::getPrivateKey(), 'publickey' => self::getPublicKey(), - ); + ); } @@ -80,20 +79,25 @@ class Keymanager { $path = ltrim( $path, '/' ); $filepath = '/'.$userId.'/files/'.$path; - // 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']; - } + // 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/' ); @@ -125,19 +129,19 @@ class Keymanager { $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 ); - - } +// // 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/'); @@ -204,7 +208,6 @@ class Keymanager { } - /** * @brief store public key of the user * @@ -231,34 +234,34 @@ class Keymanager { $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 - - } +// // 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 ); -- cgit v1.2.3 From 886fb188cdf12c8b0f048c095a18b3c6b79c4ee1 Mon Sep 17 00:00:00 2001 From: Sam Tuke Date: Wed, 14 Nov 2012 15:00:40 +0000 Subject: Improved documentation Implemented exceptions Added method for splitting catfiles --- apps/files_encryption/lib/crypt.php | 49 +++++++++++++++++++++++++++++++------ 1 file changed, 42 insertions(+), 7 deletions(-) mode change 100644 => 100755 apps/files_encryption/lib/crypt.php (limited to 'apps/files_encryption/lib') diff --git a/apps/files_encryption/lib/crypt.php b/apps/files_encryption/lib/crypt.php old mode 100644 new mode 100755 index e92c534a93c..48e114846b7 --- a/apps/files_encryption/lib/crypt.php +++ b/apps/files_encryption/lib/crypt.php @@ -88,7 +88,7 @@ class Crypt { * @brief Add arbitrary padding to encrypted data * @param string $data data to be padded * @return padded data - * @note In order to end up with data exactly 8192 bytes long we must add two letters. Something about the encryption process always results in 8190 or 8194 byte length, hence the letters must be added manually after encryption takes place + * @note In order to end up with data exactly 8192 bytes long we must add two letters. It is impossible to achieve exactly 8192 length blocks with encryption alone, hence padding is added to achieve the required length. */ public static function addPadding( $data ) { @@ -228,6 +228,12 @@ class Crypt { } + /** + * @brief Concatenate encrypted data with its IV and padding + * @param string $content content to be concatenated + * @param string $iv IV to be concatenated + * @returns string concatenated content + */ public static function concatIv ( $content, $iv ) { $combined = $content . '00iv00' . $iv; @@ -236,6 +242,33 @@ class Crypt { } + /** + * @brief Split concatenated data and IV into respective parts + * @param string $catFile concatenated data to be split + * @returns array keys: encrypted, iv + */ + public static function splitIv ( $catFile ) { + + // Fetch encryption metadata from end of file + $meta = substr( $catFile, -22 ); + + // Fetch IV from end of file + $iv = substr( $meta, -16 ); + + // Remove IV and IV identifier text to expose encrypted content + $encrypted = substr( $catFile, 0, -22 ); + + $split = array( + 'encrypted' => $encrypted + , 'iv' => $iv + ); + + $combined = $content . '00iv00' . $iv; + + return $combined; + + } + /** * @brief Symmetrically encrypts a string and returns keyfile content * @param $plainContent content to be encrypted in keyfile @@ -525,12 +558,12 @@ class Crypt { } /** - * @brief Generate a pseudo random 1024kb ASCII key - * @returns $key Generated key + * @brief Generates a pseudo random initialisation vector + * @return String $iv generated IV */ public static function generateIv() { - if ( $random = openssl_random_pseudo_bytes( 13, $strong ) ) { + if ( $random = openssl_random_pseudo_bytes( 12, $strong ) ) { if ( !$strong ) { @@ -539,13 +572,15 @@ class Crypt { } - $iv = substr( base64_encode( $random ), 0, -4 ); + // We encode the iv purely for string manipulation + // purposes - it gets decoded before use + $iv = base64_encode( $random ); return $iv; } else { - return false; + throw new Exception( 'Generating IV failed' ); } @@ -565,7 +600,7 @@ class Crypt { if ( !$strong ) { // If OpenSSL indicates randomness is insecure, log error - \OC_Log::write( 'Encryption library', 'Insecure symmetric key was generated using openssl_random_pseudo_bytes()' , \OC_Log::WARN ); + throw new Exception ( 'Encryption library, Insecure symmetric key was generated using openssl_random_pseudo_bytes()' ); } -- cgit v1.2.3 From b5c0e4042eab30110b59ddbfa9d877af32ce546a Mon Sep 17 00:00:00 2001 From: Sam Tuke Date: Thu, 15 Nov 2012 11:50:05 +0000 Subject: Fixing use of splitIv Fixed unit tests for splitIv --- apps/files_encryption/lib/crypt.php | 4 +--- apps/files_encryption/tests/crypt.php | 45 ++++++++++++++++------------------- 2 files changed, 21 insertions(+), 28 deletions(-) (limited to 'apps/files_encryption/lib') diff --git a/apps/files_encryption/lib/crypt.php b/apps/files_encryption/lib/crypt.php index 48e114846b7..8026ac4361d 100755 --- a/apps/files_encryption/lib/crypt.php +++ b/apps/files_encryption/lib/crypt.php @@ -263,9 +263,7 @@ class Crypt { , 'iv' => $iv ); - $combined = $content . '00iv00' . $iv; - - return $combined; + return $split; } diff --git a/apps/files_encryption/tests/crypt.php b/apps/files_encryption/tests/crypt.php index f29a72c24b9..4315e347cb9 100755 --- a/apps/files_encryption/tests/crypt.php +++ b/apps/files_encryption/tests/crypt.php @@ -67,17 +67,17 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { */ function testConcatIv( $iv ) { - Encryption\Crypt::concatIv( $this->dataLong, $iv ); + $catFile = Encryption\Crypt::concatIv( $this->dataLong, $iv ); // Fetch encryption metadata from end of file $meta = substr( $catFile, -22 ); - $identifier = substr( $meta, 6 ); - - $this->assertEquals( '00iv00', $identifier ); + $identifier = substr( $meta, 0, 6); // Fetch IV from end of file - $foundIv = substr( $meta, -16 ); + $foundIv = substr( $meta, 6 ); + + $this->assertEquals( '00iv00', $identifier ); $this->assertEquals( $iv, $foundIv ); @@ -85,32 +85,27 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { $data = substr( $catFile, 0, -22 ); $this->assertEquals( $this->dataLong, $data ); + + return array( + 'iv' => $iv + , 'catfile' => $catFile + ); } /** - * @depends testGenerateIv + * @depends testConcatIv */ - function testSplitIv( $iv ) { + function testSplitIv( $testConcatIv ) { - Encryption\Crypt::concatIv( $this->dataLong, $iv ); + // Split catfile into components + $splitCatfile = Encryption\Crypt::splitIv( $testConcatIv['catfile'] ); - // Fetch encryption metadata from end of file - $meta = substr( $catFile, -22 ); + // Check that original IV and split IV match + $this->assertEquals( $testConcatIv['iv'], $splitCatfile['iv'] ); - $identifier = substr( $meta, 6 ); - - $this->assertEquals( '00iv00', $identifier ); - - // Fetch IV from end of file - $foundIv = substr( $meta, -16 ); - - $this->assertEquals( $iv, $foundIv ); - - // Remove IV and IV identifier text to expose encrypted content - $data = substr( $catFile, 0, -22 ); - - $this->assertEquals( $this->dataLong, $data ); + // Check that original data and split data match + $this->assertEquals( $this->dataLong, $splitCatfile['encrypted'] ); } @@ -201,7 +196,7 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { $this->assertNotEquals( $this->dataShort, $retreivedCryptedFile ); - $key = Keymanager::getFileKey( $filename ); + $key = Encryption\Keymanager::getFileKey( $filename ); $manualDecrypt = Encryption\Crypt::symmetricBlockDecryptFileContent( $retreivedCryptedFile, $key ); @@ -245,7 +240,7 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { //print_r($e); // Manually fetch keyfile - $keyfile = Keymanager::getFileKey( $filename ); + $keyfile = Encryption\Keymanager::getFileKey( $filename ); // Set var for reassembling decrypted content $decrypt = ''; -- cgit v1.2.3 From 637891b77120a1acf25a907971a11c36bf5e35b7 Mon Sep 17 00:00:00 2001 From: Sam Tuke Date: Fri, 16 Nov 2012 18:31:37 +0000 Subject: Development snapshot, lots of fixes Web UI based encryption working Crypt and Util unit tests passing --- apps/files_encryption/hooks/hooks.php | 24 +++++++++----- apps/files_encryption/lib/crypt.php | 47 ++++++++++++++-------------- apps/files_encryption/lib/keymanager.php | 7 +++-- apps/files_encryption/lib/proxy.php | 20 +++++++++--- apps/files_encryption/lib/stream.php | 50 ++++++++++++++++++------------ apps/files_encryption/lib/util.php | 50 +++++++++++++++++++++++++++--- apps/files_encryption/tests/crypt.php | 40 +++++++++++++++++++----- apps/files_encryption/tests/keymanager.php | 14 +++++---- 8 files changed, 176 insertions(+), 76 deletions(-) (limited to 'apps/files_encryption/lib') diff --git a/apps/files_encryption/hooks/hooks.php b/apps/files_encryption/hooks/hooks.php index b9758ec0df2..d2b546e8d1f 100644 --- a/apps/files_encryption/hooks/hooks.php +++ b/apps/files_encryption/hooks/hooks.php @@ -36,32 +36,40 @@ class Hooks { */ public static function login( $params ) { - - if ( Crypt::mode( $params['uid'] ) == 'server' ) { - + +// 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()) { + + 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( $params['uid'], $view ); - + \OC_FileProxy::$enabled = true; # TODO: dont manually encrypt the private keyfile - use the config options of openssl_pkey_export instead for better mobile compatibility + //trigger_error( "\$encryptedKey = ".var_export($encryptedKey)." \n\n\$params['password'] = ".var_export($params['password'] ) ); + +// trigger_error( "\$params['password'] = {$params['password']}" ); + $_SESSION['enckey'] = Crypt::symmetricDecryptFileContent( $encryptedKey, $params['password'] ); - } +// trigger_error( "\$_SESSION['enckey'] = {$_SESSION['enckey']}" ); + +// } return true; diff --git a/apps/files_encryption/lib/crypt.php b/apps/files_encryption/lib/crypt.php index 8026ac4361d..a5278ad3308 100755 --- a/apps/files_encryption/lib/crypt.php +++ b/apps/files_encryption/lib/crypt.php @@ -45,23 +45,26 @@ class Crypt { */ public static function mode( $user = null ) { - $mode = \OC_Appconfig::getValue( 'files_encryption', 'mode', 'none' ); +// $mode = \OC_Appconfig::getValue( 'files_encryption', 'mode', 'none' ); +// +// if ( $mode == 'user') { +// if ( !$user ) { +// $user = \OCP\User::getUser(); +// } +// $mode = 'none'; +// if ( $user ) { +// $query = \OC_DB::prepare( "SELECT mode FROM *PREFIX*encryption WHERE uid = ?" ); +// $result = $query->execute(array($user)); +// if ($row = $result->fetchRow()){ +// $mode = $row['mode']; +// } +// } +// } +// +// return $mode; - if ( $mode == 'user') { - if ( !$user ) { - $user = \OCP\User::getUser(); - } - $mode = 'none'; - if ( $user ) { - $query = \OC_DB::prepare( "SELECT mode FROM *PREFIX*encryption WHERE uid = ?" ); - $result = $query->execute(array($user)); - if ($row = $result->fetchRow()){ - $mode = $row['mode']; - } - } - } + return 'server'; - return $mode; } /** @@ -101,7 +104,7 @@ class Crypt { /** * @brief Remove arbitrary padding to encrypted data * @param string $padded padded data to remove padding from - * @return padded data on success, false on error + * @return unpadded data on success, false on error */ public static function removePadding( $padded ) { @@ -220,7 +223,7 @@ class Crypt { } else { - \OC_Log::write( 'Encryption library', 'Decryption (symmetric) of content failed' , \OC_Log::ERROR ); + throw new \Exception( 'Encryption library: Decryption (symmetric) of content failed' ); return false; @@ -317,7 +320,7 @@ class Crypt { if ( !$keyfileContent ) { - return false; + throw new \Exception( 'Encryption library: no data provided for decryption' ); } @@ -330,16 +333,12 @@ class Crypt { // Remove IV and IV identifier text to expose encrypted content $encryptedContent = substr( $noPadding, 0, -22 ); + //trigger_error( "\n\n\$noPadding = ".var_export($noPadding)."\n\n\$iv = ".var_export($iv )."\n\n\$encryptedContent = ".var_export($encryptedContent) ); + if ( $plainContent = self::decrypt( $encryptedContent, $iv, $passphrase ) ) { return $plainContent; - } else { - - \OC_Log::write( 'Encryption library', 'Decryption (symmetric) of keyfile content failed' , \OC_Log::ERROR ); - - return false; - } } diff --git a/apps/files_encryption/lib/keymanager.php b/apps/files_encryption/lib/keymanager.php index dd13b62d556..0eaca463c74 100755 --- a/apps/files_encryption/lib/keymanager.php +++ b/apps/files_encryption/lib/keymanager.php @@ -230,7 +230,7 @@ class Keymanager { * @return bool true/false */ public static function setFileKey( $path, $key, $view = Null, $dbClassName = '\OC_DB') { - +var_dump($path); $targetPath = ltrim( $path, '/' ); $user = \OCP\User::getUser(); @@ -274,7 +274,10 @@ class Keymanager { $view->chroot( '/' . $user . '/files_encryption/keyfiles' ); // If the file resides within a subdirectory, create it - if ( ! $view->file_exists( $path_parts['dirname'] ) ) { + if ( + isset( $path_parts['dirname'] ) + && ! $view->file_exists( $path_parts['dirname'] ) + ) { $view->mkdir( $path_parts['dirname'] ); diff --git a/apps/files_encryption/lib/proxy.php b/apps/files_encryption/lib/proxy.php index e3e2161a141..7c179e62b74 100644 --- a/apps/files_encryption/lib/proxy.php +++ b/apps/files_encryption/lib/proxy.php @@ -46,23 +46,34 @@ class Proxy extends \OC_FileProxy { if ( is_null( self::$enableEncryption ) ) { - self::$enableEncryption = ( \OCP\Config::getAppValue( 'files_encryption', 'enable_encryption', 'true' ) == 'true' && Crypt::mode() == 'server' ); + if ( + \OCP\Config::getAppValue( 'files_encryption', 'enable_encryption', 'true' ) == 'true' + && Crypt::mode() == 'server' + ) { + + self::$enableEncryption = true; + + } else { + + self::$enableEncryption = false; + + } } - if( !self::$enableEncryption ) { + if ( !self::$enableEncryption ) { return false; } - if( is_null(self::$blackList ) ) { + 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( Crypt::isEncryptedContent( $path ) ) { + if ( Crypt::isEncryptedContent( $path ) ) { return true; @@ -132,6 +143,7 @@ class Proxy extends \OC_FileProxy { } } + } public function postFile_get_contents( $path, $data ) { diff --git a/apps/files_encryption/lib/stream.php b/apps/files_encryption/lib/stream.php index 2e3cdaabe44..74dff1531a9 100644 --- a/apps/files_encryption/lib/stream.php +++ b/apps/files_encryption/lib/stream.php @@ -31,9 +31,18 @@ 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, due to limitations of OC_FilesystemView. crypt:///home/user/owncloud/data <- will put keyfiles in [owncloud]/data/user/files_encryption/keyfiles/home/user/owncloud/data and will not be accessible by other functions. - * @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. + * @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 keyfiles in + * [owncloud]/data/user/files_encryption/keyfiles/home/user/owncloud/data and + * will not be accessible to other functions. + * @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 { @@ -41,6 +50,8 @@ class Stream { # 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 @@ -53,20 +64,24 @@ class Stream { public function stream_open( $path, $mode, $options, &$opened_path ) { - file_put_contents('/home/samtuke/newtmp.txt', 'stream_open('.$path.')' ); + //file_put_contents('/home/samtuke/newtmp.txt', 'stream_open('.$path.')' ); // Get access to filesystem via filesystemview object if ( !self::$view ) { - self::$view = new \OC_FilesystemView( '' ); + self::$view = 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 )] ) @@ -95,7 +110,7 @@ class Stream { - $this->size = self::$view->filesize( $path, $mode ); + $this->size = self::$view->filesize( $this->path_f, $mode ); //$this->size = filesize( $path ); @@ -106,7 +121,7 @@ class Stream { //$this->handle = fopen( $path, $mode ); - $this->handle = self::$view->fopen( $path, $mode ); + $this->handle = self::$view->fopen( $this->path_f, $mode ); //file_put_contents('/home/samtuke/newtmp.txt', 'fucking hopeless = '.$path ); @@ -165,7 +180,7 @@ class Stream { //echo "\n\nPRE DECRYPTION = $data\n\n"; // -// if ( strlen( $data ) ) { + if ( strlen( $data ) ) { $this->getKey(); @@ -186,14 +201,12 @@ class Stream { // echo "\n\n\n\n-----------------------------\n\n"; //trigger_error("CAT $result"); - - -// } else { -// -// $result = ''; -// -// } + } else { + + $result = ''; + + } // $length = $this->size - $pos; // @@ -234,14 +247,11 @@ class Stream { * @return bool true on key found and set, false on key not found and new key generated and set */ public function getKey( $generate = true ) { - - # TODO: Move this user call out of here - it belongs elsewhere - $user = \OCP\User::getUser(); //echo "\n\$this->rawPath = {$this->rawPath}"; // If a keyfile already exists for a file named identically to file to be written - if ( self::$view->file_exists( $user . '/'. 'files_encryption' . '/' . 'keyfiles' . '/' . $this->rawPath . '.key' ) ) { + if ( self::$view->file_exists( $this->userId . '/'. 'files_encryption' . '/' . 'keyfiles' . '/' . $this->rawPath . '.key' ) ) { # TODO: add error handling for when file exists but no keyfile @@ -276,6 +286,8 @@ class Stream { */ public function stream_write( $data ) { + + // file_put_contents('/home/samtuke/newtmp.txt', '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 diff --git a/apps/files_encryption/lib/util.php b/apps/files_encryption/lib/util.php index 0f1498885af..ea2791650f9 100644 --- a/apps/files_encryption/lib/util.php +++ b/apps/files_encryption/lib/util.php @@ -65,6 +65,11 @@ class Util { private $view; // OC_FilesystemView object for filesystem operations private $pwd; // User Password private $client; // Client side encryption mode flag + private $publicKeyDir; // Directory containing all public user keys + private $encryptionDir; // Directory containing user's files_encryption + private $keyfilesPath; // Directory containing user's keyfiles + private $publicKeyPath; // Path to user's public key + private $privateKeyPath; // Path to user's private key public function __construct( \OC_FilesystemView $view, $userId, $client = false ) { @@ -102,11 +107,6 @@ class Util { * @param $passphrase passphrase to encrypt server-stored private key with */ public function setupServerSide( $passphrase = null ) { - - // Log changes to user's filesystem - $this->appInfo = \OC_APP::getAppInfo( 'files_encryption' ); - - \OC_Log::write( $this->appInfo['name'], 'File encryption for user "' . $this->userId . '" will be set up' , \OC_Log::INFO ); // Create shared public key directory if( !$this->view->file_exists( $this->publicKeyDir ) ) { @@ -152,6 +152,8 @@ class Util { \OC_FileProxy::$enabled = true; } + + return true; } @@ -357,5 +359,43 @@ class Util { # TODO: write me } + + public function getPath( $pathName ) { + + switch ( $pathName ) { + + case 'publicKeyDir': + + return $this->publicKeyDir; + + break; + + case 'encryptionDir': + + return $this->encryptionDir; + + break; + + case 'keyfilesPath': + + return $this->keyfilesPath; + + break; + + case 'publicKeyPath': + + return $this->publicKeyPath; + + break; + + case 'privateKeyPath': + + return $this->privateKeyPath; + + break; + + } + + } } diff --git a/apps/files_encryption/tests/crypt.php b/apps/files_encryption/tests/crypt.php index 4315e347cb9..1ff894bc7a6 100755 --- a/apps/files_encryption/tests/crypt.php +++ b/apps/files_encryption/tests/crypt.php @@ -34,7 +34,9 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { $this->view = new \OC_FilesystemView( '/' ); - \OC_User::setUserId( 'admin' ); + $this->userId = 'admin'; + + \OC_User::setUserId( $this->userId ); } @@ -109,6 +111,29 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { } + 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 ); @@ -188,7 +213,7 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { // Get file contents without using any wrapper to get it's actual contents on disk - $retreivedCryptedFile = $this->view->file_get_contents( $filename ); + $retreivedCryptedFile = $this->view->file_get_contents( $this->userId . '/files/' . $filename ); //echo "\n\n\$retreivedCryptedFile = $retreivedCryptedFile"; @@ -222,7 +247,7 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { $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( $filename ); + $retreivedCryptedFile = $this->view->file_get_contents( $this->userId . '/files/' . $filename ); // echo "\n\n\$retreivedCryptedFile = $retreivedCryptedFile\n\n"; @@ -261,17 +286,16 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { $this->assertEquals( $this->dataLong.$this->dataLong, $decrypt ); - // Teadown + // Teardown $this->view->unlink( $filename ); - Keymanager::deleteFileKey( $filename ); + Encryption\Keymanager::deleteFileKey( $filename ); } /** * @brief Test that data that is read by the crypto stream wrapper - * @depends testSymmetricStreamEncryptLongFileContent */ function testSymmetricStreamDecryptShortFileContent() { @@ -285,7 +309,7 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { // Get file contents without using any wrapper to get it's actual contents on disk - $retreivedCryptedFile = $this->view->file_get_contents( $filename ); + $retreivedCryptedFile = $this->view->file_get_contents( $this->userId . '/files/' . $filename ); $decrypt = file_get_contents( 'crypt://' . $filename ); @@ -305,7 +329,7 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { // Get file contents without using any wrapper to get it's actual contents on disk - $retreivedCryptedFile = $this->view->file_get_contents( '/admin/files/' . $filename ); + $retreivedCryptedFile = $this->view->file_get_contents( $this->userId . '/files/' . $filename ); $decrypt = file_get_contents( 'crypt://' . $filename ); diff --git a/apps/files_encryption/tests/keymanager.php b/apps/files_encryption/tests/keymanager.php index 463b8a67c38..3c313708de3 100644 --- a/apps/files_encryption/tests/keymanager.php +++ b/apps/files_encryption/tests/keymanager.php @@ -5,12 +5,12 @@ * later. * See the COPYING-README file. */ - -namespace OCA\Encryption; require_once "PHPUnit/Framework/TestCase.php"; require_once realpath( dirname(__FILE__).'/../../../lib/base.php' ); +use OCA\Encryption; + class Test_Keymanager extends \PHPUnit_Framework_TestCase { function setUp() { @@ -34,7 +34,7 @@ class Test_Keymanager extends \PHPUnit_Framework_TestCase { function testGetEncryptedPrivateKey() { - $key = Keymanager::getPrivateKey( $this->user, $this->view ); + $key = Encryption\Keymanager::getPrivateKey( $this->user, $this->view ); $this->assertEquals( 2302, strlen( $key ) ); @@ -52,16 +52,18 @@ class Test_Keymanager extends \PHPUnit_Framework_TestCase { // // //$view = new \OC_FilesystemView( '/' . $this->user . '/files_encryption/keyfiles' ); // -// Keymanager::setFileKey( $tmpPath, $key['key'], $view ); +// Encryption\Keymanager::setFileKey( $tmpPath, $key['key'], $view ); } function testGetDecryptedPrivateKey() { - $key = Keymanager::getPrivateKey( $this->user, $this->view ); + $key = Encryption\Keymanager::getPrivateKey( $this->user, $this->view ); # TODO: replace call to Crypt with a mock object? - $decrypted = Crypt::symmetricDecryptFileContent( $key, $this->passphrase ); + $decrypted = Encryption\Crypt::symmetricDecryptFileContent( $key, $this->passphrase ); + + var_dump($decrypted); $this->assertEquals( 1708, strlen( $decrypted ) ); -- cgit v1.2.3 From 5328aae8a83d3e29131de3963f9222c4e6adcfe4 Mon Sep 17 00:00:00 2001 From: Sam Tuke Date: Tue, 20 Nov 2012 19:10:10 +0000 Subject: Added unit tests for legacy encryption methods Improvements to documentation --- apps/files_encryption/lib/crypt.php | 7 +-- apps/files_encryption/lib/util.php | 18 +++++-- apps/files_encryption/tests/util.php | 99 +++++++++++++++++++++++++++++++++++- 3 files changed, 117 insertions(+), 7 deletions(-) (limited to 'apps/files_encryption/lib') diff --git a/apps/files_encryption/lib/crypt.php b/apps/files_encryption/lib/crypt.php index a5278ad3308..5e6ebd7a86e 100755 --- a/apps/files_encryption/lib/crypt.php +++ b/apps/files_encryption/lib/crypt.php @@ -452,8 +452,8 @@ class Crypt { } /** - * @brief Encrypts content symmetrically and generated keyfile asymmetrically - * @returns array keys: data, key + * @brief Encrypts content symmetrically and generates keyfile asymmetrically + * @returns array keys: encrypted, key * @note this method is a wrapper for combining other crypt class methods */ public static function keyEncryptKeyfile( $plainContent, $publicKey ) { @@ -469,7 +469,8 @@ class Crypt { } /** - * @brief Encrypts content symmetrically and generated keyfile asymmetrically + * @brief Takes encrypted data, encrypted catfile, and private key, and + * performs decryption * @returns decrypted content * @note this method is a wrapper for combining other crypt class methods */ diff --git a/apps/files_encryption/lib/util.php b/apps/files_encryption/lib/util.php index ea2791650f9..051ac46091a 100644 --- a/apps/files_encryption/lib/util.php +++ b/apps/files_encryption/lib/util.php @@ -341,10 +341,22 @@ class Util { $bf = $this->getBlowfish( $passphrase ); - $data = $bf->decrypt( $content ); + $decrypted = $bf->decrypt( $content ); - return $data; + $trimmed = rtrim( $decrypted, "\0" ); + return $trimmed; + + } + + public function legacyKeyRecryptKeyfile( $legacyEncryptedContent, $legacyPassphrase, $publicKey, $newPassphrase ) { + + $decrypted = $this->legacyDecrypt( $legacyEncryptedContent, $legacyPassphrase ); + + $recrypted = Crypt::keyEncryptKeyfile( $decrypted, $publicKey ); + + return $recrypted; + } /** @@ -354,7 +366,7 @@ class Util { * * This function decrypts an content */ - public function legacyRecrypt( $legacyContent ) { + public function legacyRecrypt( $legacyContent, $legacyPassphrase, $newPassphrase ) { # TODO: write me diff --git a/apps/files_encryption/tests/util.php b/apps/files_encryption/tests/util.php index 0044844eb84..44e779d1717 100755 --- a/apps/files_encryption/tests/util.php +++ b/apps/files_encryption/tests/util.php @@ -8,6 +8,7 @@ require_once "PHPUnit/Framework/TestCase.php"; require_once realpath( dirname(__FILE__).'/../../../lib/base.php' ); +require_once realpath( dirname(__FILE__).'/../../../3rdparty/Crypt_Blowfish/Blowfish.php' ); require_once realpath( dirname(__FILE__).'/../../../3rdparty/mockery/Mockery.php' ); require_once realpath( dirname(__FILE__).'/../../../3rdparty/mockery/Mockery/Container.php' ); require_once realpath( dirname(__FILE__).'/../../../3rdparty/mockery/Mockery/Generator.php' ); @@ -29,12 +30,20 @@ class Test_Util extends \PHPUnit_Framework_TestCase { function setUp() { // set content for encrypting / decrypting in tests - $this->data = realpath( dirname(__FILE__).'/../lib/crypt.php' ); + $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'; @@ -42,6 +51,9 @@ class Test_Util extends \PHPUnit_Framework_TestCase { $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 ); } @@ -137,6 +149,91 @@ class Test_Util extends \PHPUnit_Framework_TestCase { } + /** + * @brief test encryption using legacy blowfish method + */ + function testLegacyEncryptShort() { + + $crypted = $this->util->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 = $this->util->legacyDecrypt( $crypted, $this->pass ); + + $this->assertEquals( $this->dataShort, $decrypted ); + + } + + /** + * @brief test encryption using legacy blowfish method + */ + function testLegacyEncryptLong() { + + $crypted = $this->util->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 = $this->util->legacyDecrypt( $crypted, $this->pass ); + + $this->assertEquals( $this->dataLong, $decrypted ); + + } + + /** + * @brief test decryption using legacy blowfish method + * @depends testLegacyEncryptLong + */ + function testLegacyKeyRecryptKeyfileEncrypt( $crypted ) { + + $recrypted = $this->util->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 + + } + +// /** +// * @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() { // -- cgit v1.2.3 From 13d93fb416709f9dca5660752eefb78a7c3dc1f7 Mon Sep 17 00:00:00 2001 From: Sam Tuke Date: Thu, 22 Nov 2012 14:08:19 +0000 Subject: Development snapshot --- apps/files_encryption/lib/crypt.php | 22 +++++++++++++++++---- apps/files_encryption/lib/proxy.php | 38 ++++++++----------------------------- apps/files_encryption/lib/util.php | 7 +++++++ 3 files changed, 33 insertions(+), 34 deletions(-) (limited to 'apps/files_encryption/lib') diff --git a/apps/files_encryption/lib/crypt.php b/apps/files_encryption/lib/crypt.php index 5e6ebd7a86e..efbcdb4b35a 100755 --- a/apps/files_encryption/lib/crypt.php +++ b/apps/files_encryption/lib/crypt.php @@ -144,10 +144,6 @@ class Crypt { // Fetch IV from end of file $iv = substr( $meta, -16 ); -// $msg = "\$content = ".var_dump($content, 1).", \$noPadding = ".var_dump($noPadding, 1).", \$meta = ".var_dump($meta, 1).", \$iv = ".var_dump($iv, 1); -// -// file_put_contents('/home/samtuke/newtmp.txt', $msg ); - // Fetch identifier from start of metadata $identifier = substr( $meta, 0, 6 ); @@ -163,6 +159,23 @@ class Crypt { } + /** + * Check if a file is encrypted according to database file cache + * @param string $path + * @return bool + */ + private static function isEncryptedMeta( $path ) { + + # TODO: Use DI to get OC_FileCache_Cached out of here + + // Fetch all file metadata from DB + $metadata = \OC_FileCache_Cached::get( $path, '' ); + + // Return encryption status + return isset( $metadata['encrypted'] ) and ( bool )$metadata['encrypted']; + + } + /** * @brief Check if a file is encrypted via legacy system * @return true / false @@ -625,6 +638,7 @@ class Crypt { } return false; } + } ?> \ No newline at end of file diff --git a/apps/files_encryption/lib/proxy.php b/apps/files_encryption/lib/proxy.php index 7c179e62b74..f021eb4c92e 100644 --- a/apps/files_encryption/lib/proxy.php +++ b/apps/files_encryption/lib/proxy.php @@ -89,27 +89,12 @@ class Proxy extends \OC_FileProxy { return false; } - - /** - * Check if a file is encrypted according to database file cache - * @param string $path - * @return bool - */ - 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 + if ( !is_resource( $data ) ) { //stream put contents should have been converted to fopen // Set the filesize for userland, before encrypting $size = strlen( $data ); @@ -176,7 +161,7 @@ class Proxy extends \OC_FileProxy { } public function postFopen( $path, &$result ){ - + trigger_error(var_export($path)); if ( !$result ) { return $result; @@ -188,7 +173,7 @@ class Proxy extends \OC_FileProxy { $meta = stream_get_meta_data( $result ); - $view = new \OC_FilesystemView(); + $view = new \OC_FilesystemView( '' ); $util = new Util( $view, \OCP\USER::getUser()); @@ -203,30 +188,22 @@ class Proxy extends \OC_FileProxy { $encrypted = $view->file_get_contents( $path ); - //file_put_contents('/home/samtuke/newtmp.txt', "\$path = $path, \$data = $data" ); - // Replace the contents of \OC_Filesystem::file_put_contents( $path, $tmp ); fclose( $tmp ); - //file_put_contents('/home/samtuke/newtmp.txt', file_get_contents( 'crypt://' . $path ) ); - $result = fopen( 'crypt://' . $path, $meta['mode'] ); -// file_put_contents('/home/samtuke/newtmp.txt', "mode= server" ); - // $keyFile = Keymanager::getFileKey( $filePath ); // // $tmp = tmpfile(); -// -// file_put_contents( $tmp, Crypt::keyDecryptKeyfile( $result, $keyFile, $_SESSION['enckey'] ) ); // // fclose ( $result ); // // $result = fopen( $tmp ); - } /*elseif ( + } elseif ( self::shouldEncrypt( $path ) and $meta ['mode'] != 'r' and $meta['mode'] != 'rb' @@ -235,8 +212,8 @@ class Proxy extends \OC_FileProxy { # TODO: figure out what this does if ( - \OC_Filesystem::file_exists( $path ) - and \OC_Filesystem::filesize( $path ) > 0 + $view->file_exists( $path ) + and $view->filesize( $path ) > 0 ) { //first encrypt the target file so we don't end up with a half encrypted file @@ -244,6 +221,7 @@ class Proxy extends \OC_FileProxy { $tmp = fopen( 'php://temp' ); + // Make a temporary copy of the original file \OCP\Files::streamCopy( $result, $tmp ); // Close the original stream, we'll return another one @@ -257,7 +235,7 @@ class Proxy extends \OC_FileProxy { $result = fopen( 'crypt://'.$path, $meta['mode'] ); - }*/ + } // Re-enable the proxy \OC_FileProxy::$enabled = true; diff --git a/apps/files_encryption/lib/util.php b/apps/files_encryption/lib/util.php index 051ac46091a..a1a2dddf43b 100644 --- a/apps/files_encryption/lib/util.php +++ b/apps/files_encryption/lib/util.php @@ -46,6 +46,11 @@ class Util { # DONE: add method to decrypt legacy encrypted data # DONE: fix / test the crypt stream proxy class # DONE: replace cryptstream wrapper new AES based system + # DONE: Encryption works for writing new text files in web ui + # DONE: reading unencrypted files when encryption is enabled works via webdav + + # TODO: file uploaded via web ui get encrypted + # TODO: new files created and uploaded via webdav get encrypted # TODO: add support for optional recovery user in case of lost passphrase / keys # TODO: add admin optional required long passphrase for users @@ -61,6 +66,8 @@ class Util { # TODO: test new encryption with versioning # TODO: test new encryption with sharing # TODO: test new encryption with proxies + + # NOTE: Curretly code on line 206 onwards in lib/proxy.php needs work. This code is executed when webdav writes take place, and appears to need to convert streams into fopen resources. Currently code within the if statement on 215 is not executing. Investigate the paths (handled there (which appear to be blank), and whether oc_fsv is borking them during processing. private $view; // OC_FilesystemView object for filesystem operations private $pwd; // User Password -- cgit v1.2.3 From 5f78f9d64276806f392f24784c1594b285554c7e Mon Sep 17 00:00:00 2001 From: Sam Tuke Date: Thu, 22 Nov 2012 19:36:48 +0000 Subject: Development snapshot --- apps/files_encryption/lib/proxy.php | 68 ++++++++++++++----------------------- apps/files_encryption/lib/util.php | 4 +++ 2 files changed, 30 insertions(+), 42 deletions(-) (limited to 'apps/files_encryption/lib') diff --git a/apps/files_encryption/lib/proxy.php b/apps/files_encryption/lib/proxy.php index f021eb4c92e..45890287aeb 100644 --- a/apps/files_encryption/lib/proxy.php +++ b/apps/files_encryption/lib/proxy.php @@ -137,11 +137,8 @@ class Proxy extends \OC_FileProxy { if ( Crypt::mode() == 'server' && Crypt::isEncryptedContent( $data ) ) { - $filePath = explode( '/', $path ); - - $filePath = array_slice( $filePath, 3 ); - - $filePath = '/' . implode( '/', $filePath ); + $path_split = explode( '/', $path ); + $path_f = implode( array_slice( $path_split, 3 ) ); $cached = \OC_FileCache_Cached::get( $path, '' ); @@ -161,7 +158,7 @@ class Proxy extends \OC_FileProxy { } public function postFopen( $path, &$result ){ - trigger_error(var_export($path)); + if ( !$result ) { return $result; @@ -173,67 +170,54 @@ class Proxy extends \OC_FileProxy { $meta = stream_get_meta_data( $result ); + // Reformat path for use with OC_FSV + $path_split = explode( '/', $path ); + $path_f = implode( array_slice( $path_split, 3 ) ); + +// trigger_error("\$meta(result) = ".var_export($meta, 1)); + $view = new \OC_FilesystemView( '' ); $util = new Util( $view, \OCP\USER::getUser()); - // If file is encrypted, decrypt using crypto protocol + // If file is already encrypted, decrypt using crypto protocol if ( Crypt::mode() == 'server' && $util->isEncryptedPath( $path ) ) { - - $tmp = fopen( 'php://temp' ); - - \OCP\Files::streamCopy( $result, $tmp ); + // Close the original encrypted file fclose( $result ); - $encrypted = $view->file_get_contents( $path ); - - // Replace the contents of - \OC_Filesystem::file_put_contents( $path, $tmp ); - - fclose( $tmp ); - + // Open the file using the crypto protocol and let + // it do the decryption work instead $result = fopen( 'crypt://' . $path, $meta['mode'] ); - -// $keyFile = Keymanager::getFileKey( $filePath ); -// -// $tmp = tmpfile(); -// -// fclose ( $result ); -// -// $result = fopen( $tmp ); + } elseif ( self::shouldEncrypt( $path ) and $meta ['mode'] != 'r' and $meta['mode'] != 'rb' ) { + // If the file should be encrypted and has been opened for + // reading only - # TODO: figure out what this does - - if ( - $view->file_exists( $path ) - and $view->filesize( $path ) > 0 + if ( + \OC_Filesystem::file_exists( $path_f ) + and \OC_Filesystem::filesize( $path_f ) > 0 ) { - //first encrypt the target file so we don't end up with a half encrypted file - \OCP\Util::writeLog( 'files_encryption', 'Decrypting '.$path.' before writing', \OCP\Util::DEBUG ); + trigger_error("BAT"); + $tmp = tmpfile(); - $tmp = fopen( 'php://temp' ); + \OCP\Files::streamCopy($result, $tmp); - // Make a temporary copy of the original file - \OCP\Files::streamCopy( $result, $tmp ); + fclose($result); - // Close the original stream, we'll return another one - fclose( $result ); + \OC_Filesystem::file_put_contents($path_f, $tmp); - \OC_Filesystem::file_put_contents( $path, $tmp ); + fclose($tmp); - fclose( $tmp ); - } - $result = fopen( 'crypt://'.$path, $meta['mode'] ); + $result = fopen( 'crypt://' . $path_f, $meta['mode'] ); } diff --git a/apps/files_encryption/lib/util.php b/apps/files_encryption/lib/util.php index a1a2dddf43b..af13dbe3f84 100644 --- a/apps/files_encryption/lib/util.php +++ b/apps/files_encryption/lib/util.php @@ -68,6 +68,10 @@ class Util { # TODO: test new encryption with proxies # NOTE: Curretly code on line 206 onwards in lib/proxy.php needs work. This code is executed when webdav writes take place, and appears to need to convert streams into fopen resources. Currently code within the if statement on 215 is not executing. Investigate the paths (handled there (which appear to be blank), and whether oc_fsv is borking them during processing. + + # NOTE: When files are written via webdav, they are encrypted and saved on the server, though they are not readable via web ui or webdav. proof of this is the changing length of content. When read in web ui, text reads "false", persumably because decryption failed. Why no error in + + # NOTE: for some reason file_get_contents is not working in proxy class postfopen. The same line works in sscce, but always returns an empty string in proxy.php. this is the same regardless of whether oc_fs, oc_fsv, or direct use of phps file_get_contents is used private $view; // OC_FilesystemView object for filesystem operations private $pwd; // User Password -- cgit v1.2.3 From a465b3cb639e00b4f1bdcaa8ee44383a67e01112 Mon Sep 17 00:00:00 2001 From: Sam Tuke Date: Thu, 22 Nov 2012 20:19:03 +0000 Subject: Development snapshot --- apps/files_encryption/lib/proxy.php | 47 +++++++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 18 deletions(-) (limited to 'apps/files_encryption/lib') diff --git a/apps/files_encryption/lib/proxy.php b/apps/files_encryption/lib/proxy.php index 45890287aeb..6f0fd01e29d 100644 --- a/apps/files_encryption/lib/proxy.php +++ b/apps/files_encryption/lib/proxy.php @@ -137,8 +137,11 @@ class Proxy extends \OC_FileProxy { if ( Crypt::mode() == 'server' && Crypt::isEncryptedContent( $data ) ) { - $path_split = explode( '/', $path ); - $path_f = implode( array_slice( $path_split, 3 ) ); + $filePath = explode( '/', $path ); + + $filePath = array_slice( $filePath, 3 ); + + $filePath = '/' . implode( '/', $filePath ); $cached = \OC_FileCache_Cached::get( $path, '' ); @@ -170,10 +173,6 @@ class Proxy extends \OC_FileProxy { $meta = stream_get_meta_data( $result ); - // Reformat path for use with OC_FSV - $path_split = explode( '/', $path ); - $path_f = implode( array_slice( $path_split, 3 ) ); - // trigger_error("\$meta(result) = ".var_export($meta, 1)); $view = new \OC_FilesystemView( '' ); @@ -199,25 +198,37 @@ class Proxy extends \OC_FileProxy { // If the file should be encrypted and has been opened for // reading only - if ( - \OC_Filesystem::file_exists( $path_f ) - and \OC_Filesystem::filesize( $path_f ) > 0 - ) { + // Reformat path for use with OC_FSV + $path_split = explode( '/', $path ); + $path_f = implode( array_slice( $path_split, 3 ) ); - trigger_error("BAT"); - $tmp = tmpfile(); - - \OCP\Files::streamCopy($result, $tmp); +// trigger_error("$path_f = ".var_export($path_f, 1)); + + if ( + $view->file_exists( $path ) + and $view->filesize( $path ) > 0 + ) { + $x = $view->file_get_contents( $path ); - fclose($result); + trigger_error( "size = ".var_export( $x, 1 ) ); - \OC_Filesystem::file_put_contents($path_f, $tmp); + $tmp = tmpfile(); - fclose($tmp); +// trigger_error("Result meta = ".var_export($meta, 1)); +// // Make a temporary copy of the original file +// \OCP\Files::streamCopy( $result, $tmp ); +// +// // Close the original stream, we'll return another one +// fclose( $result ); +// +// $view->file_put_contents( $path_f, $tmp ); +// +// fclose( $tmp ); + } - $result = fopen( 'crypt://' . $path_f, $meta['mode'] ); + $result = fopen( 'crypt://'.$path_f, $meta['mode'] ); } -- cgit v1.2.3 From bfd47cd2dfad9c613d51fa9b4e5391f25ab57a87 Mon Sep 17 00:00:00 2001 From: Sam Tuke Date: Wed, 28 Nov 2012 18:39:19 +0000 Subject: Development snapshot Moved legacy crypto methods from Util into Crypt Added preliminary support for reading legacy encrypted files Added some unit tests --- apps/files_encryption/hooks/hooks.php | 13 ++ apps/files_encryption/lib/crypt.php | 106 ++++++++++++++- apps/files_encryption/lib/keymanager.php | 18 ++- apps/files_encryption/lib/proxy.php | 26 +++- apps/files_encryption/lib/util.php | 120 ---------------- apps/files_encryption/tests/crypt.php | 227 ++++++++++++++++++++++--------- apps/files_encryption/tests/util.php | 74 ---------- 7 files changed, 316 insertions(+), 268 deletions(-) (limited to 'apps/files_encryption/lib') diff --git a/apps/files_encryption/hooks/hooks.php b/apps/files_encryption/hooks/hooks.php index d2b546e8d1f..2c8921ef351 100644 --- a/apps/files_encryption/hooks/hooks.php +++ b/apps/files_encryption/hooks/hooks.php @@ -69,6 +69,19 @@ class Hooks { // trigger_error( "\$_SESSION['enckey'] = {$_SESSION['enckey']}" ); + $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'] ); + trigger_error('leg enc key = '.$_SESSION['legacyenckey']); + + } // } return true; diff --git a/apps/files_encryption/lib/crypt.php b/apps/files_encryption/lib/crypt.php index efbcdb4b35a..8df3cd43270 100755 --- a/apps/files_encryption/lib/crypt.php +++ b/apps/files_encryption/lib/crypt.php @@ -24,6 +24,8 @@ namespace OCA\Encryption; +require_once 'Crypt_Blowfish/Blowfish.php'; + // Todo: // - Crypt/decrypt button in the userinterface // - Setting if crypto should be on by default @@ -164,7 +166,7 @@ class Crypt { * @param string $path * @return bool */ - private static function isEncryptedMeta( $path ) { + public static function isEncryptedMeta( $path ) { # TODO: Use DI to get OC_FileCache_Cached out of here @@ -180,7 +182,7 @@ class Crypt { * @brief Check if a file is encrypted via legacy system * @return true / false */ - public static function isLegacyEncryptedContent( $content, $path ) { + public static function isLegacyEncryptedContent( $content ) { // Fetch all file metadata from DB $metadata = \OC_FileCache_Cached::get( $content, '' ); @@ -639,6 +641,106 @@ class Crypt { return false; } + /** + * @brief Get the blowfish encryption handeler for a key + * @param $key string (optional) + * @return Crypt_Blowfish blowfish object + * + * 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 { + + return false; + + } + + } + + public static function legacyCreateKey( $passphrase ) { + + // Generate a random integer + $key = mt_rand( 10000, 99999 ) . mt_rand( 10000, 99999 ) . mt_rand( 10000, 99999 ) . mt_rand( 10000, 99999 ); + + // Encrypt the key with the passphrase + $legacyEncKey = self::legacyEncrypt( $key, $passphrase ); + + return $legacyEncKey; + + } + + /** + * @brief encrypts content using legacy blowfish system + * @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 legacyEncrypt( $content, $passphrase = '' ) { + + trigger_error("OC2 enc \$content = $content \$passphrase = ".var_export($passphrase, 1) ); + + $bf = self::getBlowfish( $passphrase ); + + return $bf->encrypt( $content ); + + } + + /** + * @brief decrypts content using legacy blowfish system + * @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 legacyDecrypt( $content, $passphrase = '' ) { + + $passphrase = ''; + + //trigger_error("OC2 dec \$content = $content \$key = ".strlen($passphrase) ); + + $bf = self::getBlowfish( "67362885833455692562" ); + + trigger_error(var_export($bf, 1) ); + + $decrypted = $bf->decrypt( $content ); + + $trimmed = rtrim( $decrypted, "\0" ); + + return $trimmed; + + } + + public static function legacyKeyRecryptKeyfile( $legacyEncryptedContent, $legacyPassphrase, $publicKey, $newPassphrase ) { + + $decrypted = self::legacyDecrypt( $legacyEncryptedContent, $legacyPassphrase ); + + $recrypted = self::keyEncryptKeyfile( $decrypted, $publicKey ); + + return $recrypted; + + } + + /** + * @brief Re-encryptes a legacy blowfish encrypted file using AES with integrated IV + * @param $legacyContent the legacy encrypted content to re-encrypt + * @returns cleartext content + * + * This function decrypts an content + */ + public static function legacyRecrypt( $legacyContent, $legacyPassphrase, $newPassphrase ) { + + # TODO: write me + + } + } ?> \ No newline at end of file diff --git a/apps/files_encryption/lib/keymanager.php b/apps/files_encryption/lib/keymanager.php index 0eaca463c74..02fb6acbaa1 100755 --- a/apps/files_encryption/lib/keymanager.php +++ b/apps/files_encryption/lib/keymanager.php @@ -230,7 +230,7 @@ class Keymanager { * @return bool true/false */ public static function setFileKey( $path, $key, $view = Null, $dbClassName = '\OC_DB') { -var_dump($path); + $targetPath = ltrim( $path, '/' ); $user = \OCP\User::getUser(); @@ -304,4 +304,20 @@ var_dump($path); } + /** + * @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' ); + + } + } \ No newline at end of file diff --git a/apps/files_encryption/lib/proxy.php b/apps/files_encryption/lib/proxy.php index 6f0fd01e29d..6dcb5e803e7 100644 --- a/apps/files_encryption/lib/proxy.php +++ b/apps/files_encryption/lib/proxy.php @@ -134,8 +134,14 @@ class Proxy extends \OC_FileProxy { public function postFile_get_contents( $path, $data ) { # TODO: Use dependency injection to add required args for view and user etc. to this method - - if ( Crypt::mode() == 'server' && Crypt::isEncryptedContent( $data ) ) { + + // Disable encryption proxy to prevent recursive calls + \OC_FileProxy::$enabled = false; + + if ( + Crypt::mode() == 'server' + && Crypt::isEncryptedContent( $data ) + ) { $filePath = explode( '/', $path ); @@ -145,17 +151,23 @@ class Proxy extends \OC_FileProxy { $cached = \OC_FileCache_Cached::get( $path, '' ); - // Disable encryption proxy to prevent recursive calls - \OC_FileProxy::$enabled = false; - $keyFile = Keymanager::getFileKey( $filePath ); $data = Crypt::keyDecryptKeyfile( $data, $keyFile, $_SESSION['enckey'] ); - - \OC_FileProxy::$enabled = true; + } elseif ( + Crypt::mode() == 'server' + && isset( $_SESSION['legacyenckey'] ) + //&& Crypt::isEncryptedMeta( $path ) + ) { + + $data = Crypt::legacyDecrypt( $data, $_SESSION['legacyenckey'] ); + //trigger_error($data); + } + \OC_FileProxy::$enabled = true; + return $data; } diff --git a/apps/files_encryption/lib/util.php b/apps/files_encryption/lib/util.php index af13dbe3f84..acc03250772 100644 --- a/apps/files_encryption/lib/util.php +++ b/apps/files_encryption/lib/util.php @@ -263,126 +263,6 @@ class Util { } - /** - * @brief Get the blowfish encryption handeler for a key - * @param $key string (optional) - * @return Crypt_Blowfish blowfish object - * - * if the key is left out, the default handeler will be used - */ - public function getBlowfish( $key = '' ) { - - if ( $key ) { - - return new \Crypt_Blowfish( $key ); - - } else { - - 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( $passphrase ) { - - // Disable proxies to prevent attempt to automatically decrypt key - OC_FileProxy::$enabled = false; - - if ( - $passphrase - and $key = $this->view->file_get_contents( '/encryption.key' ) - ) { - - OC_FileProxy::$enabled = true; - - if ( $this->legacyKey = $this->legacyDecrypt( $key, $passphrase ) ) { - - return true; - - } else { - - return false; - - } - - } else { - - OC_FileProxy::$enabled = true; - - return false; - - } - - } - - /** - * @brief encrypts content using legacy blowfish system - * @param $content the cleartext message you want to encrypt - * @param $key the encryption key (optional) - * @returns encrypted content - * - * This function encrypts an content - */ - public function legacyEncrypt( $content, $passphrase = '' ) { - - $bf = $this->getBlowfish( $passphrase ); - - 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 function legacyDecrypt( $content, $passphrase = '' ) { - - $bf = $this->getBlowfish( $passphrase ); - - $decrypted = $bf->decrypt( $content ); - - $trimmed = rtrim( $decrypted, "\0" ); - - return $trimmed; - - } - - public function legacyKeyRecryptKeyfile( $legacyEncryptedContent, $legacyPassphrase, $publicKey, $newPassphrase ) { - - $decrypted = $this->legacyDecrypt( $legacyEncryptedContent, $legacyPassphrase ); - - $recrypted = Crypt::keyEncryptKeyfile( $decrypted, $publicKey ); - - return $recrypted; - - } - - /** - * @brief Re-encryptes a legacy blowfish encrypted file using AES with integrated IV - * @param $legacyContent the legacy encrypted content to re-encrypt - * @returns cleartext content - * - * This function decrypts an content - */ - public function legacyRecrypt( $legacyContent, $legacyPassphrase, $newPassphrase ) { - - # TODO: write me - - } - public function getPath( $pathName ) { switch ( $pathName ) { diff --git a/apps/files_encryption/tests/crypt.php b/apps/files_encryption/tests/crypt.php index 1ff894bc7a6..09347dd578a 100755 --- a/apps/files_encryption/tests/crypt.php +++ b/apps/files_encryption/tests/crypt.php @@ -10,6 +10,7 @@ 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' ); @@ -32,9 +33,14 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { $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( '/' ); $this->userId = 'admin'; + $this->pass = 'admin'; \OC_User::setUserId( $this->userId ); @@ -229,70 +235,70 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { } - /** - * @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 - */ - function testSymmetricStreamEncryptLongFileContent() { - - // Generate a a random filename - $filename = 'tmp-'.time(); - - echo "\n\n\$filename = $filename\n\n"; - - // 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[11], $r[12].$r[13], $r[14] ); - - //print_r($e); - - // Manually fetch keyfile - $keyfile = Encryption\Keymanager::getFileKey( $filename ); - - // Set var for reassembling decrypted content - $decrypt = ''; - - // Manually decrypt chunk - foreach ($e as $e) { - -// echo "\n\$encryptMe = $f"; - - $chunkDecrypt = Encryption\Crypt::symmetricDecryptFileContent( $e, $keyfile ); - - // Assemble decrypted chunks - $decrypt .= $chunkDecrypt; - - //echo "\n\$chunkDecrypt = $chunkDecrypt"; - - } - - $this->assertEquals( $this->dataLong.$this->dataLong, $decrypt ); - - // Teardown - - $this->view->unlink( $filename ); - - Encryption\Keymanager::deleteFileKey( $filename ); - - } +// /** +// * @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 +// */ +// function testSymmetricStreamEncryptLongFileContent() { +// +// // Generate a a random filename +// $filename = 'tmp-'.time(); +// +// echo "\n\n\$filename = $filename\n\n"; +// +// // 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[11], $r[12].$r[13], $r[14] ); +// +// //print_r($e); +// +// // Manually fetch keyfile +// $keyfile = Encryption\Keymanager::getFileKey( $filename ); +// +// // Set var for reassembling decrypted content +// $decrypt = ''; +// +// // Manually decrypt chunk +// foreach ($e as $e) { +// +// // echo "\n\$encryptMe = $f"; +// +// $chunkDecrypt = Encryption\Crypt::symmetricDecryptFileContent( $e, $keyfile ); +// +// // Assemble decrypted chunks +// $decrypt .= $chunkDecrypt; +// +// //echo "\n\$chunkDecrypt = $chunkDecrypt"; +// +// } +// +// $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 @@ -451,6 +457,99 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { } + + /** + * @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(); diff --git a/apps/files_encryption/tests/util.php b/apps/files_encryption/tests/util.php index 44e779d1717..593eabd0d55 100755 --- a/apps/files_encryption/tests/util.php +++ b/apps/files_encryption/tests/util.php @@ -8,7 +8,6 @@ require_once "PHPUnit/Framework/TestCase.php"; require_once realpath( dirname(__FILE__).'/../../../lib/base.php' ); -require_once realpath( dirname(__FILE__).'/../../../3rdparty/Crypt_Blowfish/Blowfish.php' ); require_once realpath( dirname(__FILE__).'/../../../3rdparty/mockery/Mockery.php' ); require_once realpath( dirname(__FILE__).'/../../../3rdparty/mockery/Mockery/Container.php' ); require_once realpath( dirname(__FILE__).'/../../../3rdparty/mockery/Mockery/Generator.php' ); @@ -148,79 +147,6 @@ class Test_Util extends \PHPUnit_Framework_TestCase { # then false will be returned. Use strict ordering? } - - /** - * @brief test encryption using legacy blowfish method - */ - function testLegacyEncryptShort() { - - $crypted = $this->util->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 = $this->util->legacyDecrypt( $crypted, $this->pass ); - - $this->assertEquals( $this->dataShort, $decrypted ); - - } - - /** - * @brief test encryption using legacy blowfish method - */ - function testLegacyEncryptLong() { - - $crypted = $this->util->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 = $this->util->legacyDecrypt( $crypted, $this->pass ); - - $this->assertEquals( $this->dataLong, $decrypted ); - - } - - /** - * @brief test decryption using legacy blowfish method - * @depends testLegacyEncryptLong - */ - function testLegacyKeyRecryptKeyfileEncrypt( $crypted ) { - - $recrypted = $this->util->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 - - } // /** // * @brief test decryption using legacy blowfish method -- cgit v1.2.3 From bc3550b37bd3a069edc374df58218fb216056c0e Mon Sep 17 00:00:00 2001 From: Sam Tuke Date: Tue, 4 Dec 2012 19:53:13 +0000 Subject: Development Snapshot Opening short files via webdav, that were saved via webdav, now works --- apps/files_encryption/hooks/hooks.php | 4 +- apps/files_encryption/lib/proxy.php | 35 +++++++----- apps/files_encryption/lib/util.php | 79 ++++++++++++++++++--------- apps/files_encryption/tests/proxy.php | 100 +++++++++++++++++++++++++++++++++- apps/files_encryption/tests/util.php | 3 +- 5 files changed, 176 insertions(+), 45 deletions(-) (limited to 'apps/files_encryption/lib') diff --git a/apps/files_encryption/hooks/hooks.php b/apps/files_encryption/hooks/hooks.php index 2c8921ef351..20ce45244ac 100644 --- a/apps/files_encryption/hooks/hooks.php +++ b/apps/files_encryption/hooks/hooks.php @@ -67,7 +67,9 @@ class Hooks { $_SESSION['enckey'] = Crypt::symmetricDecryptFileContent( $encryptedKey, $params['password'] ); -// trigger_error( "\$_SESSION['enckey'] = {$_SESSION['enckey']}" ); + \OC_FileProxy::$enabled = false; + file_put_contents( '/home/samtuke/enckey', $_SESSION['enckey'] ); + \OC_FileProxy::$enabled = true; $view1 = new \OC_FilesystemView( '/' . $params['uid'] ); diff --git a/apps/files_encryption/lib/proxy.php b/apps/files_encryption/lib/proxy.php index 6dcb5e803e7..914632d3387 100644 --- a/apps/files_encryption/lib/proxy.php +++ b/apps/files_encryption/lib/proxy.php @@ -142,14 +142,15 @@ class Proxy extends \OC_FileProxy { Crypt::mode() == 'server' && Crypt::isEncryptedContent( $data ) ) { - + //trigger_error("bong"); + $filePath = explode( '/', $path ); $filePath = array_slice( $filePath, 3 ); $filePath = '/' . implode( '/', $filePath ); - $cached = \OC_FileCache_Cached::get( $path, '' ); + //$cached = \OC_FileCache_Cached::get( $path, '' ); $keyFile = Keymanager::getFileKey( $filePath ); @@ -158,8 +159,9 @@ class Proxy extends \OC_FileProxy { } elseif ( Crypt::mode() == 'server' && isset( $_SESSION['legacyenckey'] ) - //&& Crypt::isEncryptedMeta( $path ) + && Crypt::isEncryptedMeta( $path ) ) { + trigger_error("mong"); $data = Crypt::legacyDecrypt( $data, $_SESSION['legacyenckey'] ); //trigger_error($data); @@ -180,6 +182,10 @@ class Proxy extends \OC_FileProxy { } + // Reformat path for use with OC_FSV + $path_split = explode( '/', $path ); + $path_f = implode( array_slice( $path_split, 3 ) ); + // Disable encryption proxy to prevent recursive calls \OC_FileProxy::$enabled = false; @@ -192,14 +198,17 @@ class Proxy extends \OC_FileProxy { $util = new Util( $view, \OCP\USER::getUser()); // If file is already encrypted, decrypt using crypto protocol - if ( Crypt::mode() == 'server' && $util->isEncryptedPath( $path ) ) { + if ( + Crypt::mode() == 'server' + && $util->isEncryptedPath( $path ) + ) { // Close the original encrypted file fclose( $result ); - // Open the file using the crypto protocol and let - // it do the decryption work instead - $result = fopen( 'crypt://' . $path, $meta['mode'] ); + // Open the file using the crypto stream wrapper + // protocol and let it do the decryption work instead + $result = fopen( 'crypt://' . $path_f, $meta['mode'] ); } elseif ( @@ -207,14 +216,10 @@ class Proxy extends \OC_FileProxy { and $meta ['mode'] != 'r' and $meta['mode'] != 'rb' ) { - // If the file should be encrypted and has been opened for - // reading only + // If the file is not yet encrypted, but should be + // encrypted when it's saved (it's not read only) - // Reformat path for use with OC_FSV - $path_split = explode( '/', $path ); - $path_f = implode( array_slice( $path_split, 3 ) ); - -// trigger_error("$path_f = ".var_export($path_f, 1)); + // NOTE: this is the case for new files saved via WebDAV if ( $view->file_exists( $path ) @@ -222,7 +227,7 @@ class Proxy extends \OC_FileProxy { ) { $x = $view->file_get_contents( $path ); - trigger_error( "size = ".var_export( $x, 1 ) ); + //trigger_error( "size = ".var_export( $x, 1 ) ); $tmp = tmpfile(); diff --git a/apps/files_encryption/lib/util.php b/apps/files_encryption/lib/util.php index acc03250772..907a04e5c00 100644 --- a/apps/files_encryption/lib/util.php +++ b/apps/files_encryption/lib/util.php @@ -39,33 +39,51 @@ namespace OCA\Encryption; */ class Util { - - # DONE: add method to check if file is encrypted using new system - # DONE: add method to check if file is encrypted using old system - # DONE: add method to fetch legacy key - # DONE: add method to decrypt legacy encrypted data - # DONE: fix / test the crypt stream proxy class - # DONE: replace cryptstream wrapper new AES based system - # DONE: Encryption works for writing new text files in web ui - # DONE: reading unencrypted files when encryption is enabled works via webdav - - # TODO: file uploaded via web ui get encrypted - # TODO: new files created and uploaded via webdav get encrypted - - # TODO: add support for optional recovery user in case of lost passphrase / keys - # TODO: add admin optional required long passphrase for users - # TODO: implement flag system to allow user to specify encryption by folder, subfolder, etc. - # TODO: add UI buttons for encrypt / decrypt everything? - - # TODO: add method to encrypt all user files using new system - # TODO: add method to decrypt all user files using new system - # TODO: add method to encrypt all user files using old system - # TODO: add method to decrypt all user files using old system - - # TODO: test new encryption with webdav - # TODO: test new encryption with versioning - # TODO: test new encryption with sharing - # TODO: test new encryption with proxies + + + # Web UI: + + ## DONE: files created via web ui are encrypted + ## DONE: file created & encrypted via web ui are readable in web ui + + + # WebDAV: + + ## DONE: new data filled files added via webdav get encrypted + ## DONE: new data filled files added via webdav are readable via webdav + ## DONE: reading unencrypted files when encryption is enabled works via webdav + + # TODO: files created & encrypted via web ui are readable via webdav + + + # Legacy support: + + ## DONE: add method to check if file is encrypted using new system + ## DONE: add method to check if file is encrypted using old system + ## DONE: add method to fetch legacy key + ## DONE: add method to decrypt legacy encrypted data + + ## TODO: add method to encrypt all user files using new system + ## TODO: add method to decrypt all user files using new system + ## TODO: add method to encrypt all user files using old system + ## TODO: add method to decrypt all user files using old system + + + # Admin UI: + + ## TODO: add support for optional recovery in case of lost passphrase / keys + ## TODO: add admin optional required long passphrase for users + ## TODO: add UI buttons for encrypt / decrypt everything + ## TODO: implement flag system to allow user to specify encryption by folder, subfolder, etc. + + + # Integration testing: + + ## TODO: test new encryption with webdav + ## TODO: test new encryption with versioning + ## TODO: test new encryption with sharing + ## TODO: test new encryption with proxies + # NOTE: Curretly code on line 206 onwards in lib/proxy.php needs work. This code is executed when webdav writes take place, and appears to need to convert streams into fopen resources. Currently code within the if statement on 215 is not executing. Investigate the paths (handled there (which appear to be blank), and whether oc_fsv is borking them during processing. @@ -73,6 +91,7 @@ class Util { # NOTE: for some reason file_get_contents is not working in proxy class postfopen. The same line works in sscce, but always returns an empty string in proxy.php. this is the same regardless of whether oc_fs, oc_fsv, or direct use of phps file_get_contents is used + private $view; // OC_FilesystemView object for filesystem operations private $pwd; // User Password private $client; // Client side encryption mode flag @@ -241,8 +260,14 @@ class Util { */ public function isEncryptedPath( $path ) { + // Disable encryption proxy so data retreived is in its + // original form + \OC_FileProxy::$enabled = false; + $data = $this->view->file_get_contents( $path ); + \OC_FileProxy::$enabled = true; + return Crypt::isEncryptedContent( $data ); } diff --git a/apps/files_encryption/tests/proxy.php b/apps/files_encryption/tests/proxy.php index 253a32164ec..8b2c92c2f53 100644 --- a/apps/files_encryption/tests/proxy.php +++ b/apps/files_encryption/tests/proxy.php @@ -1,11 +1,109 @@ + * Copyright (c) 2012 Sam Tuke , + * and Robin Appelman * 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' ) ); + + $this->userId = 'admin'; + $this->pass = 'admin'; + +$_SESSION['enckey'] = '-----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----- +'; + + \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 ); +// +// var_dump($encrypted); + + $decrypted = $this->proxy->postFile_get_contents( 'data/admin/files/enc-test.txt', $this->data1 ); + + var_dump($decrypted); + + } + +} + // class Test_CryptProxy extends UnitTestCase { // private $oldConfig; // private $oldKey; diff --git a/apps/files_encryption/tests/util.php b/apps/files_encryption/tests/util.php index 593eabd0d55..556ba11fe56 100755 --- a/apps/files_encryption/tests/util.php +++ b/apps/files_encryption/tests/util.php @@ -9,9 +9,10 @@ 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/Container.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' ); -- cgit v1.2.3 From c56fb905d1a300b2fe6c011848ea520031ea0df1 Mon Sep 17 00:00:00 2001 From: Sam Tuke Date: Wed, 5 Dec 2012 18:57:44 +0000 Subject: Development snapshot Read/write interoperability working through web UI and WebDAV New class Session for handling session data A few new unit tests Some additional unit tests are now failing, esp. legacy enc related ones --- apps/files_encryption/appinfo/app.php | 9 ++++- apps/files_encryption/hooks/hooks.php | 10 ++--- apps/files_encryption/lib/crypt.php | 25 ++++++------ apps/files_encryption/lib/keymanager.php | 20 ++++++++-- apps/files_encryption/lib/proxy.php | 27 +++++++++---- apps/files_encryption/lib/session.php | 66 +++++++++++++++++++++++++++++++ apps/files_encryption/lib/stream.php | 54 ++++++++++++++++++------- apps/files_encryption/lib/util.php | 4 +- apps/files_encryption/tests/crypt.php | 67 ++++++++++++++++++++------------ apps/files_encryption/tests/proxy.php | 19 ++++++--- 10 files changed, 227 insertions(+), 74 deletions(-) create mode 100644 apps/files_encryption/lib/session.php (limited to 'apps/files_encryption/lib') diff --git a/apps/files_encryption/appinfo/app.php b/apps/files_encryption/appinfo/app.php index 12920aa8291..7a8eee41bb5 100644 --- a/apps/files_encryption/appinfo/app.php +++ b/apps/files_encryption/appinfo/app.php @@ -6,6 +6,7 @@ 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 OCA\Encryption\Proxy()); @@ -14,7 +15,13 @@ OCP\Util::connectHook('OC_Webdav_Properties', 'update', 'OCA\Encryption\Hooks', stream_wrapper_register( 'crypt', 'OCA\Encryption\Stream'); -if( !isset( $_SESSION['enckey'] ) && OCP\User::isLoggedIn() && OCA\Encryption\Crypt::mode() == 'server' ) { +$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(); diff --git a/apps/files_encryption/hooks/hooks.php b/apps/files_encryption/hooks/hooks.php index 20ce45244ac..9752dbf0a15 100644 --- a/apps/files_encryption/hooks/hooks.php +++ b/apps/files_encryption/hooks/hooks.php @@ -65,11 +65,11 @@ class Hooks { // trigger_error( "\$params['password'] = {$params['password']}" ); - $_SESSION['enckey'] = Crypt::symmetricDecryptFileContent( $encryptedKey, $params['password'] ); + $privateKey = Crypt::symmetricDecryptFileContent( $encryptedKey, $params['password'] ); - \OC_FileProxy::$enabled = false; - file_put_contents( '/home/samtuke/enckey', $_SESSION['enckey'] ); - \OC_FileProxy::$enabled = true; + $session = new Session(); + + $session->setPrivateKey( $privateKey, $params['uid'] ); $view1 = new \OC_FilesystemView( '/' . $params['uid'] ); @@ -81,7 +81,7 @@ class Hooks { ) { $_SESSION['legacyenckey'] = Crypt::legacyDecrypt( $legacyKey, $params['password'] ); - trigger_error('leg enc key = '.$_SESSION['legacyenckey']); +// trigger_error('leg enc key = '.$_SESSION['legacyenckey']); } // } diff --git a/apps/files_encryption/lib/crypt.php b/apps/files_encryption/lib/crypt.php index 8df3cd43270..5e1078c9e1b 100755 --- a/apps/files_encryption/lib/crypt.php +++ b/apps/files_encryption/lib/crypt.php @@ -305,9 +305,9 @@ class Crypt { if ( $encryptedContent = self::encrypt( $plainContent, $iv, $passphrase ) ) { // Combine content to encrypt with IV identifier and actual IV - $combinedKeyfile = self::concatIv( $encryptedContent, $iv ); + $catfile = self::concatIv( $encryptedContent, $iv ); - $padded = self::addPadding( $combinedKeyfile ); + $padded = self::addPadding( $catfile ); return $padded; @@ -468,7 +468,8 @@ class Crypt { /** * @brief Encrypts content symmetrically and generates keyfile asymmetrically - * @returns array keys: encrypted, key + * @returns array containing catfile and new keyfile. + * keys: data, key * @note this method is a wrapper for combining other crypt class methods */ public static function keyEncryptKeyfile( $plainContent, $publicKey ) { @@ -484,18 +485,20 @@ class Crypt { } /** - * @brief Takes encrypted data, encrypted catfile, and private key, and + * @brief Takes catfile, keyfile, and private key, and * performs decryption * @returns decrypted content * @note this method is a wrapper for combining other crypt class methods */ - public static function keyDecryptKeyfile( $encryptedData, $encryptedKey, $privateKey ) { + public static function keyDecryptKeyfile( $catfile, $keyfile, $privateKey ) { - // Decrypt keyfile - $decryptedKey = self::keyDecrypt( $encryptedKey, $privateKey ); + // Decrypt the keyfile with the user's private key + $decryptedKey = self::keyDecrypt( $keyfile, $privateKey ); - // Decrypt encrypted file - $decryptedData = self::symmetricDecryptFileContent( $encryptedData, $decryptedKey ); +// trigger_error( "\$keyfile = ".var_export($keyfile, 1)); + + // Decrypt the catfile symmetrically using the decrypted keyfile + $decryptedData = self::symmetricDecryptFileContent( $catfile, $decryptedKey ); return $decryptedData; @@ -684,7 +687,7 @@ class Crypt { */ public static function legacyEncrypt( $content, $passphrase = '' ) { - trigger_error("OC2 enc \$content = $content \$passphrase = ".var_export($passphrase, 1) ); + //trigger_error("OC2 enc \$content = $content \$passphrase = ".var_export($passphrase, 1) ); $bf = self::getBlowfish( $passphrase ); @@ -708,7 +711,7 @@ class Crypt { $bf = self::getBlowfish( "67362885833455692562" ); - trigger_error(var_export($bf, 1) ); +// trigger_error(var_export($bf, 1) ); $decrypted = $bf->decrypt( $content ); diff --git a/apps/files_encryption/lib/keymanager.php b/apps/files_encryption/lib/keymanager.php index 02fb6acbaa1..9eb9bad3db4 100755 --- a/apps/files_encryption/lib/keymanager.php +++ b/apps/files_encryption/lib/keymanager.php @@ -46,11 +46,19 @@ class Keymanager { * @brief retrieve public key for a specified user * @return string public key or false */ - public static function getPublicKey() { + public static function getPublicKey( $userId = NULL ) { - $user = \OCP\User::getUser(); + // If the username wasn't specified, fetch it + if ( ! $userId ) { + + $userId = \OCP\User::getUser(); + + } + + // Create new view with the right $view = new \OC_FilesystemView( '/public-keys/' ); - return $view->file_get_contents( '/' . $user . '.public.key' ); + + return $view->file_get_contents( '/' . $userId . '.public.key' ); } @@ -119,10 +127,12 @@ class Keymanager { } /** - * @brief retrieve file encryption key + * @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( $path, $staticUserClass = 'OCP\User' ) { @@ -228,6 +238,8 @@ class Keymanager { * @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') { diff --git a/apps/files_encryption/lib/proxy.php b/apps/files_encryption/lib/proxy.php index 914632d3387..85664734d7a 100644 --- a/apps/files_encryption/lib/proxy.php +++ b/apps/files_encryption/lib/proxy.php @@ -131,6 +131,10 @@ class Proxy extends \OC_FileProxy { } + /** + * @param string $path Path of file from which has been read + * @param string $data Data that has been read from file + */ public function postFile_get_contents( $path, $data ) { # TODO: Use dependency injection to add required args for view and user etc. to this method @@ -138,24 +142,27 @@ class Proxy extends \OC_FileProxy { // Disable encryption proxy to prevent recursive calls \OC_FileProxy::$enabled = false; + // If data is a catfile if ( Crypt::mode() == 'server' && Crypt::isEncryptedContent( $data ) ) { - //trigger_error("bong"); +// trigger_error("bong"); - $filePath = explode( '/', $path ); + $split = explode( '/', $path ); - $filePath = array_slice( $filePath, 3 ); + $filePath = array_slice( $split, 3 ); $filePath = '/' . implode( '/', $filePath ); //$cached = \OC_FileCache_Cached::get( $path, '' ); $keyFile = Keymanager::getFileKey( $filePath ); + + $session = new Session(); + + $decrypted = Crypt::keyDecryptKeyfile( $data, $keyFile, $session->getPrivateKey( $split[1] ) ); - $data = Crypt::keyDecryptKeyfile( $data, $keyFile, $_SESSION['enckey'] ); - } elseif ( Crypt::mode() == 'server' && isset( $_SESSION['legacyenckey'] ) @@ -163,14 +170,20 @@ class Proxy extends \OC_FileProxy { ) { trigger_error("mong"); - $data = Crypt::legacyDecrypt( $data, $_SESSION['legacyenckey'] ); + $decrypted = Crypt::legacyDecrypt( $data, $_SESSION['legacyenckey'] ); //trigger_error($data); } \OC_FileProxy::$enabled = true; - return $data; + if ( ! isset( $decrypted ) ) { + + $decrypted = $data; + + } + + return $decrypted; } diff --git a/apps/files_encryption/lib/session.php b/apps/files_encryption/lib/session.php new file mode 100644 index 00000000000..946e5a6eddd --- /dev/null +++ b/apps/files_encryption/lib/session.php @@ -0,0 +1,66 @@ +. + * + */ + +namespace OCA\Encryption; + +/** + * Class for handling encryption related session data + */ + +class Session { + + /** + * @brief Sets user id for session and triggers emit + * @return bool + * + */ + public static 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 static function getPrivateKey( $userId ) { + + if ( + isset( $_SESSION['privateKey'] ) + && !empty( $_SESSION['privateKey'] ) + ) { + + return $_SESSION['privateKey']; + + } else { + + return false; + + } + + } + +} \ No newline at end of file diff --git a/apps/files_encryption/lib/stream.php b/apps/files_encryption/lib/stream.php index 74dff1531a9..ac5fadd4e03 100644 --- a/apps/files_encryption/lib/stream.php +++ b/apps/files_encryption/lib/stream.php @@ -59,7 +59,9 @@ class Stream { private $count; private $writeCache; public $size; + private $publicKey; private $keyfile; + private $encKeyfile; private static $view; public function stream_open( $path, $mode, $options, &$opened_path ) { @@ -246,7 +248,7 @@ class Stream { * @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( $generate = true ) { + public function getKey() { //echo "\n\$this->rawPath = {$this->rawPath}"; @@ -256,23 +258,37 @@ class Stream { # TODO: add error handling for when file exists but no keyfile // Fetch existing keyfile - $this->keyfile = Keymanager::getFileKey( $this->rawPath ); + $this->encKeyfile = Keymanager::getFileKey( $this->rawPath ); + + $this->getUser(); + + $session = new Session(); + + $this->keyfile = Crypt::keyDecrypt( $this->encKeyfile, $session->getPrivateKey( $this->userId ) ); return true; } else { - if ( $generate ) { - - // If the data is to be written to a new file, generate a new keyfile - $this->keyfile = Crypt::generateKey(); - - return false; - - } - + 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?) + } /** @@ -306,15 +322,23 @@ class Stream { //echo "\$pointer = $pointer\n"; - # TODO: Move this user call out of here - it belongs elsewhere - $user = \OCP\User::getUser(); + // 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->userId ); + + $this->encKeyfile = Crypt::keyEncrypt( $this->keyfile, $this->publicKey ); + + // Save the new encrypted file key + Keymanager::setFileKey( $this->rawPath, $this->encKeyfile, new \OC_FilesystemView( '/' ) ); - // Save keyfile in parallel directory structure - Keymanager::setFileKey( $this->rawPath, $this->keyfile, new \OC_FilesystemView( '/' ) ); + # TODO: move this new OCFSV out of here some how, use DI } diff --git a/apps/files_encryption/lib/util.php b/apps/files_encryption/lib/util.php index 907a04e5c00..77f8dffe00f 100644 --- a/apps/files_encryption/lib/util.php +++ b/apps/files_encryption/lib/util.php @@ -45,6 +45,7 @@ class Util { ## DONE: files created via web ui are encrypted ## DONE: file created & encrypted via web ui are readable in web ui + ## DONE: file created & encrypted via web ui are readable via webdav # WebDAV: @@ -52,8 +53,7 @@ class Util { ## DONE: new data filled files added via webdav get encrypted ## DONE: new data filled files added via webdav are readable via webdav ## DONE: reading unencrypted files when encryption is enabled works via webdav - - # TODO: files created & encrypted via web ui are readable via webdav + ## DONE: files created & encrypted via web ui are readable via webdav # Legacy support: diff --git a/apps/files_encryption/tests/crypt.php b/apps/files_encryption/tests/crypt.php index 09347dd578a..f72f15ca236 100755 --- a/apps/files_encryption/tests/crypt.php +++ b/apps/files_encryption/tests/crypt.php @@ -21,6 +21,10 @@ 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_Crypt extends \PHPUnit_Framework_TestCase { function setUp() { @@ -41,8 +45,6 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { $this->userId = 'admin'; $this->pass = 'admin'; - - \OC_User::setUserId( $this->userId ); } @@ -434,6 +436,7 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { } + // 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 @@ -456,6 +459,22 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { $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 ); + + } /** @@ -474,17 +493,17 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { } - /** - * @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 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 @@ -502,17 +521,17 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { } - /** - * @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 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 diff --git a/apps/files_encryption/tests/proxy.php b/apps/files_encryption/tests/proxy.php index 8b2c92c2f53..87151234e0e 100644 --- a/apps/files_encryption/tests/proxy.php +++ b/apps/files_encryption/tests/proxy.php @@ -45,10 +45,17 @@ class Test_Util extends \PHPUnit_Framework_TestCase { $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'; -$_SESSION['enckey'] = '-----BEGIN PRIVATE KEY----- + $this->session = new Encryption\Session(); + +$this->session->setPrivateKey( +'-----BEGIN PRIVATE KEY----- MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDiH3EA4EpFA7Fx s2dyyfL5jwXeYXrTqQJ6DqKgGn8VsbT3eu8R9KzM2XitVwZe8c8L52DvJ06o5vg0 GqPYxilFdOFJe/ggac5Tq8UmJiZS4EqYEMwxBIfIyWTxeGV06/0HOwnVAkqHMcBz @@ -76,7 +83,9 @@ k1kbgyS7KKB7opVxI5+ChEqyUDijS3Y9FZixrRIWE6i2uGu86UG+v2lbKvSbM4Qm xvbOcX9OVMnlRb7n8woOP10UMY+ZE2x+YEUXQTLtPYq7F66e1OfxltstMxLQA+3d Y1d5piFV8PXK3Fg2F+Cj5qg= -----END PRIVATE KEY----- -'; +' +, $this->userId +); \OC_User::setUserId( $this->userId ); @@ -113,11 +122,11 @@ Y1d5piFV8PXK3Fg2F+Cj5qg= // // $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; +// $this->oldKey=isset($_SESSION['privateKey'])?$_SESSION['privateKey']:null; // // // //set testing key -// $_SESSION['enckey']=md5(time()); +// $_SESSION['privateKey']=md5(time()); // // //clear all proxies and hooks so we can do clean testing // OC_FileProxy::clearProxies(); @@ -141,7 +150,7 @@ Y1d5piFV8PXK3Fg2F+Cj5qg= // public function tearDown(){ // OCP\Config::setAppValue('files_encryption','enable_encryption',$this->oldConfig); // if(!is_null($this->oldKey)){ -// $_SESSION['enckey']=$this->oldKey; +// $_SESSION['privateKey']=$this->oldKey; // } // } // -- cgit v1.2.3 From aabef796a0a0b22ff4299935111e442cb43a93ce Mon Sep 17 00:00:00 2001 From: Sam Tuke Date: Tue, 11 Dec 2012 13:22:46 +0000 Subject: Added library phpseclib First implementation of passphrase changing on user pwd change --- 3rdparty/phpseclib/Crypt/AES.php | 611 +++++ 3rdparty/phpseclib/Crypt/DES.php | 1295 ++++++++++ 3rdparty/phpseclib/Crypt/Hash.php | 825 +++++++ 3rdparty/phpseclib/Crypt/RC4.php | 531 +++++ 3rdparty/phpseclib/Crypt/RSA.php | 2640 +++++++++++++++++++++ 3rdparty/phpseclib/Crypt/Random.php | 142 ++ 3rdparty/phpseclib/Crypt/Rijndael.php | 1478 ++++++++++++ 3rdparty/phpseclib/Crypt/TripleDES.php | 1061 +++++++++ 3rdparty/phpseclib/File/ANSI.php | 540 +++++ 3rdparty/phpseclib/File/ASN1.php | 1273 ++++++++++ 3rdparty/phpseclib/File/X509.php | 3766 ++++++++++++++++++++++++++++++ 3rdparty/phpseclib/Math/BigInteger.php | 3630 ++++++++++++++++++++++++++++ 3rdparty/phpseclib/Net/SFTP.php | 2015 ++++++++++++++++ 3rdparty/phpseclib/Net/SSH1.php | 1421 +++++++++++ 3rdparty/phpseclib/Net/SSH2.php | 2945 +++++++++++++++++++++++ 3rdparty/phpseclib/openssl.cnf | 6 + 3rdparty/phpseclib/version.txt | 1 + apps/files_encryption/appinfo/app.php | 1 + apps/files_encryption/hooks/hooks.php | 73 +- apps/files_encryption/lib/keymanager.php | 29 +- lib/base.php | 6 +- lib/filestorage/local.php | 4 +- lib/filesystemview.php | 3 + lib/user.php | 14 +- 24 files changed, 24282 insertions(+), 28 deletions(-) create mode 100644 3rdparty/phpseclib/Crypt/AES.php create mode 100644 3rdparty/phpseclib/Crypt/DES.php create mode 100644 3rdparty/phpseclib/Crypt/Hash.php create mode 100644 3rdparty/phpseclib/Crypt/RC4.php create mode 100644 3rdparty/phpseclib/Crypt/RSA.php create mode 100644 3rdparty/phpseclib/Crypt/Random.php create mode 100644 3rdparty/phpseclib/Crypt/Rijndael.php create mode 100644 3rdparty/phpseclib/Crypt/TripleDES.php create mode 100644 3rdparty/phpseclib/File/ANSI.php create mode 100644 3rdparty/phpseclib/File/ASN1.php create mode 100644 3rdparty/phpseclib/File/X509.php create mode 100644 3rdparty/phpseclib/Math/BigInteger.php create mode 100644 3rdparty/phpseclib/Net/SFTP.php create mode 100644 3rdparty/phpseclib/Net/SSH1.php create mode 100644 3rdparty/phpseclib/Net/SSH2.php create mode 100644 3rdparty/phpseclib/openssl.cnf create mode 100644 3rdparty/phpseclib/version.txt (limited to 'apps/files_encryption/lib') diff --git a/3rdparty/phpseclib/Crypt/AES.php b/3rdparty/phpseclib/Crypt/AES.php new file mode 100644 index 00000000000..5da1cc5b99b --- /dev/null +++ b/3rdparty/phpseclib/Crypt/AES.php @@ -0,0 +1,611 @@ + + * setKey('abcdefghijklmnop'); + * + * $size = 10 * 1024; + * $plaintext = ''; + * for ($i = 0; $i < $size; $i++) { + * $plaintext.= 'a'; + * } + * + * echo $aes->decrypt($aes->encrypt($plaintext)); + * ?> + * + * + * LICENSE: Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @category Crypt + * @package Crypt_AES + * @author Jim Wigginton + * @copyright MMVIII Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @version $Id: AES.php,v 1.7 2010/02/09 06:10:25 terrafrost Exp $ + * @link http://phpseclib.sourceforge.net + */ + +/** + * Include Crypt_Rijndael + */ +if (!class_exists('Crypt_Rijndael')) { + require_once 'Rijndael.php'; +} + +/**#@+ + * @access public + * @see Crypt_AES::encrypt() + * @see Crypt_AES::decrypt() + */ +/** + * Encrypt / decrypt using the Counter mode. + * + * Set to -1 since that's what Crypt/Random.php uses to index the CTR mode. + * + * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Counter_.28CTR.29 + */ +define('CRYPT_AES_MODE_CTR', -1); +/** + * Encrypt / decrypt using the Electronic Code Book mode. + * + * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Electronic_codebook_.28ECB.29 + */ +define('CRYPT_AES_MODE_ECB', 1); +/** + * Encrypt / decrypt using the Code Book Chaining mode. + * + * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Cipher-block_chaining_.28CBC.29 + */ +define('CRYPT_AES_MODE_CBC', 2); +/** + * Encrypt / decrypt using the Cipher Feedback mode. + * + * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Cipher_feedback_.28CFB.29 + */ +define('CRYPT_AES_MODE_CFB', 3); +/** + * Encrypt / decrypt using the Cipher Feedback mode. + * + * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Output_feedback_.28OFB.29 + */ +define('CRYPT_AES_MODE_OFB', 4); +/**#@-*/ + +/**#@+ + * @access private + * @see Crypt_AES::Crypt_AES() + */ +/** + * Toggles the internal implementation + */ +define('CRYPT_AES_MODE_INTERNAL', 1); +/** + * Toggles the mcrypt implementation + */ +define('CRYPT_AES_MODE_MCRYPT', 2); +/**#@-*/ + +/** + * Pure-PHP implementation of AES. + * + * @author Jim Wigginton + * @version 0.1.0 + * @access public + * @package Crypt_AES + */ +class Crypt_AES extends Crypt_Rijndael { + /** + * mcrypt resource for encryption + * + * The mcrypt resource can be recreated every time something needs to be created or it can be created just once. + * Since mcrypt operates in continuous mode, by default, it'll need to be recreated when in non-continuous mode. + * + * @see Crypt_AES::encrypt() + * @var String + * @access private + */ + var $enmcrypt; + + /** + * mcrypt resource for decryption + * + * The mcrypt resource can be recreated every time something needs to be created or it can be created just once. + * Since mcrypt operates in continuous mode, by default, it'll need to be recreated when in non-continuous mode. + * + * @see Crypt_AES::decrypt() + * @var String + * @access private + */ + var $demcrypt; + + /** + * mcrypt resource for CFB mode + * + * @see Crypt_AES::encrypt() + * @see Crypt_AES::decrypt() + * @var String + * @access private + */ + var $ecb; + + /** + * Default Constructor. + * + * Determines whether or not the mcrypt extension should be used. $mode should only, at present, be + * CRYPT_AES_MODE_ECB or CRYPT_AES_MODE_CBC. If not explictly set, CRYPT_AES_MODE_CBC will be used. + * + * @param optional Integer $mode + * @return Crypt_AES + * @access public + */ + function Crypt_AES($mode = CRYPT_AES_MODE_CBC) + { + if ( !defined('CRYPT_AES_MODE') ) { + switch (true) { + case extension_loaded('mcrypt') && in_array('rijndael-128', mcrypt_list_algorithms()): + define('CRYPT_AES_MODE', CRYPT_AES_MODE_MCRYPT); + break; + default: + define('CRYPT_AES_MODE', CRYPT_AES_MODE_INTERNAL); + } + } + + switch ( CRYPT_AES_MODE ) { + case CRYPT_AES_MODE_MCRYPT: + switch ($mode) { + case CRYPT_AES_MODE_ECB: + $this->paddable = true; + $this->mode = MCRYPT_MODE_ECB; + break; + case CRYPT_AES_MODE_CTR: + // ctr doesn't have a constant associated with it even though it appears to be fairly widely + // supported. in lieu of knowing just how widely supported it is, i've, for now, opted not to + // include a compatibility layer. the layer has been implemented but, for now, is commented out. + $this->mode = 'ctr'; + //$this->mode = in_array('ctr', mcrypt_list_modes()) ? 'ctr' : CRYPT_AES_MODE_CTR; + break; + case CRYPT_AES_MODE_CFB: + $this->mode = 'ncfb'; + break; + case CRYPT_AES_MODE_OFB: + $this->mode = MCRYPT_MODE_NOFB; + break; + case CRYPT_AES_MODE_CBC: + default: + $this->paddable = true; + $this->mode = MCRYPT_MODE_CBC; + } + + $this->debuffer = $this->enbuffer = ''; + + break; + default: + switch ($mode) { + case CRYPT_AES_MODE_ECB: + $this->paddable = true; + $this->mode = CRYPT_RIJNDAEL_MODE_ECB; + break; + case CRYPT_AES_MODE_CTR: + $this->mode = CRYPT_RIJNDAEL_MODE_CTR; + break; + case CRYPT_AES_MODE_CFB: + $this->mode = CRYPT_RIJNDAEL_MODE_CFB; + break; + case CRYPT_AES_MODE_OFB: + $this->mode = CRYPT_RIJNDAEL_MODE_OFB; + break; + case CRYPT_AES_MODE_CBC: + default: + $this->paddable = true; + $this->mode = CRYPT_RIJNDAEL_MODE_CBC; + } + } + + if (CRYPT_AES_MODE == CRYPT_AES_MODE_INTERNAL) { + parent::Crypt_Rijndael($this->mode); + } + } + + /** + * Dummy function + * + * Since Crypt_AES extends Crypt_Rijndael, this function is, technically, available, but it doesn't do anything. + * + * @access public + * @param Integer $length + */ + function setBlockLength($length) + { + return; + } + + + /** + * Sets the initialization vector. (optional) + * + * SetIV is not required when CRYPT_RIJNDAEL_MODE_ECB is being used. If not explictly set, it'll be assumed + * to be all zero's. + * + * @access public + * @param String $iv + */ + function setIV($iv) + { + parent::setIV($iv); + if ( CRYPT_AES_MODE == CRYPT_AES_MODE_MCRYPT ) { + $this->changed = true; + } + } + + /** + * Encrypts a message. + * + * $plaintext will be padded with up to 16 additional bytes. Other AES implementations may or may not pad in the + * same manner. Other common approaches to padding and the reasons why it's necessary are discussed in the following + * URL: + * + * {@link http://www.di-mgt.com.au/cryptopad.html http://www.di-mgt.com.au/cryptopad.html} + * + * An alternative to padding is to, separately, send the length of the file. This is what SSH, in fact, does. + * strlen($plaintext) will still need to be a multiple of 16, however, arbitrary values can be added to make it that + * length. + * + * @see Crypt_AES::decrypt() + * @access public + * @param String $plaintext + */ + function encrypt($plaintext) + { + if ( CRYPT_AES_MODE == CRYPT_AES_MODE_MCRYPT ) { + $changed = $this->changed; + $this->_mcryptSetup(); + /* + if ($this->mode == CRYPT_AES_MODE_CTR) { + $iv = $this->encryptIV; + $xor = mcrypt_generic($this->enmcrypt, $this->_generate_xor(strlen($plaintext), $iv)); + $ciphertext = $plaintext ^ $xor; + if ($this->continuousBuffer) { + $this->encryptIV = $iv; + } + return $ciphertext; + } + */ + // re: http://phpseclib.sourceforge.net/cfb-demo.phps + // using mcrypt's default handing of CFB the above would output two different things. using phpseclib's + // rewritten CFB implementation the above outputs the same thing twice. + if ($this->mode == 'ncfb') { + if ($changed) { + $this->ecb = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_ECB, ''); + mcrypt_generic_init($this->ecb, $this->key, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"); + } + + if (strlen($this->enbuffer)) { + $ciphertext = $plaintext ^ substr($this->encryptIV, strlen($this->enbuffer)); + $this->enbuffer.= $ciphertext; + if (strlen($this->enbuffer) == 16) { + $this->encryptIV = $this->enbuffer; + $this->enbuffer = ''; + mcrypt_generic_init($this->enmcrypt, $this->key, $this->encryptIV); + } + $plaintext = substr($plaintext, strlen($ciphertext)); + } else { + $ciphertext = ''; + } + + $last_pos = strlen($plaintext) & 0xFFFFFFF0; + $ciphertext.= $last_pos ? mcrypt_generic($this->enmcrypt, substr($plaintext, 0, $last_pos)) : ''; + + if (strlen($plaintext) & 0xF) { + if (strlen($ciphertext)) { + $this->encryptIV = substr($ciphertext, -16); + } + $this->encryptIV = mcrypt_generic($this->ecb, $this->encryptIV); + $this->enbuffer = substr($plaintext, $last_pos) ^ $this->encryptIV; + $ciphertext.= $this->enbuffer; + } + + return $ciphertext; + } + + if ($this->paddable) { + $plaintext = $this->_pad($plaintext); + } + + $ciphertext = mcrypt_generic($this->enmcrypt, $plaintext); + + if (!$this->continuousBuffer) { + mcrypt_generic_init($this->enmcrypt, $this->key, $this->iv); + } + + return $ciphertext; + } + + return parent::encrypt($plaintext); + } + + /** + * Decrypts a message. + * + * If strlen($ciphertext) is not a multiple of 16, null bytes will be added to the end of the string until it is. + * + * @see Crypt_AES::encrypt() + * @access public + * @param String $ciphertext + */ + function decrypt($ciphertext) + { + if ( CRYPT_AES_MODE == CRYPT_AES_MODE_MCRYPT ) { + $changed = $this->changed; + $this->_mcryptSetup(); + /* + if ($this->mode == CRYPT_AES_MODE_CTR) { + $iv = $this->decryptIV; + $xor = mcrypt_generic($this->enmcrypt, $this->_generate_xor(strlen($ciphertext), $iv)); + $plaintext = $ciphertext ^ $xor; + if ($this->continuousBuffer) { + $this->decryptIV = $iv; + } + return $plaintext; + } + */ + if ($this->mode == 'ncfb') { + if ($changed) { + $this->ecb = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_ECB, ''); + mcrypt_generic_init($this->ecb, $this->key, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"); + } + + if (strlen($this->debuffer)) { + $plaintext = $ciphertext ^ substr($this->decryptIV, strlen($this->debuffer)); + + $this->debuffer.= substr($ciphertext, 0, strlen($plaintext)); + if (strlen($this->debuffer) == 16) { + $this->decryptIV = $this->debuffer; + $this->debuffer = ''; + mcrypt_generic_init($this->demcrypt, $this->key, $this->decryptIV); + } + $ciphertext = substr($ciphertext, strlen($plaintext)); + } else { + $plaintext = ''; + } + + $last_pos = strlen($ciphertext) & 0xFFFFFFF0; + $plaintext.= $last_pos ? mdecrypt_generic($this->demcrypt, substr($ciphertext, 0, $last_pos)) : ''; + + if (strlen($ciphertext) & 0xF) { + if (strlen($plaintext)) { + $this->decryptIV = substr($ciphertext, $last_pos - 16, 16); + } + $this->decryptIV = mcrypt_generic($this->ecb, $this->decryptIV); + $this->debuffer = substr($ciphertext, $last_pos); + $plaintext.= $this->debuffer ^ $this->decryptIV; + } + + return $plaintext; + } + + if ($this->paddable) { + // we pad with chr(0) since that's what mcrypt_generic does. to quote from http://php.net/function.mcrypt-generic : + // "The data is padded with "\0" to make sure the length of the data is n * blocksize." + $ciphertext = str_pad($ciphertext, (strlen($ciphertext) + 15) & 0xFFFFFFF0, chr(0)); + } + + $plaintext = mdecrypt_generic($this->demcrypt, $ciphertext); + + if (!$this->continuousBuffer) { + mcrypt_generic_init($this->demcrypt, $this->key, $this->iv); + } + + return $this->paddable ? $this->_unpad($plaintext) : $plaintext; + } + + return parent::decrypt($ciphertext); + } + + /** + * Setup mcrypt + * + * Validates all the variables. + * + * @access private + */ + function _mcryptSetup() + { + if (!$this->changed) { + return; + } + + if (!$this->explicit_key_length) { + // this just copied from Crypt_Rijndael::_setup() + $length = strlen($this->key) >> 2; + if ($length > 8) { + $length = 8; + } else if ($length < 4) { + $length = 4; + } + $this->Nk = $length; + $this->key_size = $length << 2; + } + + switch ($this->Nk) { + case 4: // 128 + $this->key_size = 16; + break; + case 5: // 160 + case 6: // 192 + $this->key_size = 24; + break; + case 7: // 224 + case 8: // 256 + $this->key_size = 32; + } + + $this->key = str_pad(substr($this->key, 0, $this->key_size), $this->key_size, chr(0)); + $this->encryptIV = $this->decryptIV = $this->iv = str_pad(substr($this->iv, 0, 16), 16, chr(0)); + + if (!isset($this->enmcrypt)) { + $mode = $this->mode; + //$mode = $this->mode == CRYPT_AES_MODE_CTR ? MCRYPT_MODE_ECB : $this->mode; + + $this->demcrypt = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', $mode, ''); + $this->enmcrypt = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', $mode, ''); + } // else should mcrypt_generic_deinit be called? + + mcrypt_generic_init($this->demcrypt, $this->key, $this->iv); + mcrypt_generic_init($this->enmcrypt, $this->key, $this->iv); + + $this->changed = false; + } + + /** + * Encrypts a block + * + * Optimized over Crypt_Rijndael's implementation by means of loop unrolling. + * + * @see Crypt_Rijndael::_encryptBlock() + * @access private + * @param String $in + * @return String + */ + function _encryptBlock($in) + { + $state = unpack('N*word', $in); + + $Nr = $this->Nr; + $w = $this->w; + $t0 = $this->t0; + $t1 = $this->t1; + $t2 = $this->t2; + $t3 = $this->t3; + + // addRoundKey and reindex $state + $state = array( + $state['word1'] ^ $w[0][0], + $state['word2'] ^ $w[0][1], + $state['word3'] ^ $w[0][2], + $state['word4'] ^ $w[0][3] + ); + + // shiftRows + subWord + mixColumns + addRoundKey + // we could loop unroll this and use if statements to do more rounds as necessary, but, in my tests, that yields + // only a marginal improvement. since that also, imho, hinders the readability of the code, i've opted not to do it. + for ($round = 1; $round < $this->Nr; $round++) { + $state = array( + $t0[$state[0] & 0xFF000000] ^ $t1[$state[1] & 0x00FF0000] ^ $t2[$state[2] & 0x0000FF00] ^ $t3[$state[3] & 0x000000FF] ^ $w[$round][0], + $t0[$state[1] & 0xFF000000] ^ $t1[$state[2] & 0x00FF0000] ^ $t2[$state[3] & 0x0000FF00] ^ $t3[$state[0] & 0x000000FF] ^ $w[$round][1], + $t0[$state[2] & 0xFF000000] ^ $t1[$state[3] & 0x00FF0000] ^ $t2[$state[0] & 0x0000FF00] ^ $t3[$state[1] & 0x000000FF] ^ $w[$round][2], + $t0[$state[3] & 0xFF000000] ^ $t1[$state[0] & 0x00FF0000] ^ $t2[$state[1] & 0x0000FF00] ^ $t3[$state[2] & 0x000000FF] ^ $w[$round][3] + ); + + } + + // subWord + $state = array( + $this->_subWord($state[0]), + $this->_subWord($state[1]), + $this->_subWord($state[2]), + $this->_subWord($state[3]) + ); + + // shiftRows + addRoundKey + $state = array( + ($state[0] & 0xFF000000) ^ ($state[1] & 0x00FF0000) ^ ($state[2] & 0x0000FF00) ^ ($state[3] & 0x000000FF) ^ $this->w[$this->Nr][0], + ($state[1] & 0xFF000000) ^ ($state[2] & 0x00FF0000) ^ ($state[3] & 0x0000FF00) ^ ($state[0] & 0x000000FF) ^ $this->w[$this->Nr][1], + ($state[2] & 0xFF000000) ^ ($state[3] & 0x00FF0000) ^ ($state[0] & 0x0000FF00) ^ ($state[1] & 0x000000FF) ^ $this->w[$this->Nr][2], + ($state[3] & 0xFF000000) ^ ($state[0] & 0x00FF0000) ^ ($state[1] & 0x0000FF00) ^ ($state[2] & 0x000000FF) ^ $this->w[$this->Nr][3] + ); + + return pack('N*', $state[0], $state[1], $state[2], $state[3]); + } + + /** + * Decrypts a block + * + * Optimized over Crypt_Rijndael's implementation by means of loop unrolling. + * + * @see Crypt_Rijndael::_decryptBlock() + * @access private + * @param String $in + * @return String + */ + function _decryptBlock($in) + { + $state = unpack('N*word', $in); + + $Nr = $this->Nr; + $dw = $this->dw; + $dt0 = $this->dt0; + $dt1 = $this->dt1; + $dt2 = $this->dt2; + $dt3 = $this->dt3; + + // addRoundKey and reindex $state + $state = array( + $state['word1'] ^ $dw[$this->Nr][0], + $state['word2'] ^ $dw[$this->Nr][1], + $state['word3'] ^ $dw[$this->Nr][2], + $state['word4'] ^ $dw[$this->Nr][3] + ); + + + // invShiftRows + invSubBytes + invMixColumns + addRoundKey + for ($round = $this->Nr - 1; $round > 0; $round--) { + $state = array( + $dt0[$state[0] & 0xFF000000] ^ $dt1[$state[3] & 0x00FF0000] ^ $dt2[$state[2] & 0x0000FF00] ^ $dt3[$state[1] & 0x000000FF] ^ $dw[$round][0], + $dt0[$state[1] & 0xFF000000] ^ $dt1[$state[0] & 0x00FF0000] ^ $dt2[$state[3] & 0x0000FF00] ^ $dt3[$state[2] & 0x000000FF] ^ $dw[$round][1], + $dt0[$state[2] & 0xFF000000] ^ $dt1[$state[1] & 0x00FF0000] ^ $dt2[$state[0] & 0x0000FF00] ^ $dt3[$state[3] & 0x000000FF] ^ $dw[$round][2], + $dt0[$state[3] & 0xFF000000] ^ $dt1[$state[2] & 0x00FF0000] ^ $dt2[$state[1] & 0x0000FF00] ^ $dt3[$state[0] & 0x000000FF] ^ $dw[$round][3] + ); + } + + // invShiftRows + invSubWord + addRoundKey + $state = array( + $this->_invSubWord(($state[0] & 0xFF000000) ^ ($state[3] & 0x00FF0000) ^ ($state[2] & 0x0000FF00) ^ ($state[1] & 0x000000FF)) ^ $dw[0][0], + $this->_invSubWord(($state[1] & 0xFF000000) ^ ($state[0] & 0x00FF0000) ^ ($state[3] & 0x0000FF00) ^ ($state[2] & 0x000000FF)) ^ $dw[0][1], + $this->_invSubWord(($state[2] & 0xFF000000) ^ ($state[1] & 0x00FF0000) ^ ($state[0] & 0x0000FF00) ^ ($state[3] & 0x000000FF)) ^ $dw[0][2], + $this->_invSubWord(($state[3] & 0xFF000000) ^ ($state[2] & 0x00FF0000) ^ ($state[1] & 0x0000FF00) ^ ($state[0] & 0x000000FF)) ^ $dw[0][3] + ); + + return pack('N*', $state[0], $state[1], $state[2], $state[3]); + } +} + +// vim: ts=4:sw=4:et: +// vim6: fdl=1: diff --git a/3rdparty/phpseclib/Crypt/DES.php b/3rdparty/phpseclib/Crypt/DES.php new file mode 100644 index 00000000000..5ed193d6533 --- /dev/null +++ b/3rdparty/phpseclib/Crypt/DES.php @@ -0,0 +1,1295 @@ + + * setKey('abcdefgh'); + * + * $size = 10 * 1024; + * $plaintext = ''; + * for ($i = 0; $i < $size; $i++) { + * $plaintext.= 'a'; + * } + * + * echo $des->decrypt($des->encrypt($plaintext)); + * ?> + * + * + * LICENSE: Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @category Crypt + * @package Crypt_DES + * @author Jim Wigginton + * @copyright MMVII Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @version $Id: DES.php,v 1.12 2010/02/09 06:10:26 terrafrost Exp $ + * @link http://phpseclib.sourceforge.net + */ + +/**#@+ + * @access private + * @see Crypt_DES::_prepareKey() + * @see Crypt_DES::_processBlock() + */ +/** + * Contains array_reverse($keys[CRYPT_DES_DECRYPT]) + */ +define('CRYPT_DES_ENCRYPT', 0); +/** + * Contains array_reverse($keys[CRYPT_DES_ENCRYPT]) + */ +define('CRYPT_DES_DECRYPT', 1); +/**#@-*/ + +/**#@+ + * @access public + * @see Crypt_DES::encrypt() + * @see Crypt_DES::decrypt() + */ +/** + * Encrypt / decrypt using the Counter mode. + * + * Set to -1 since that's what Crypt/Random.php uses to index the CTR mode. + * + * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Counter_.28CTR.29 + */ +define('CRYPT_DES_MODE_CTR', -1); +/** + * Encrypt / decrypt using the Electronic Code Book mode. + * + * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Electronic_codebook_.28ECB.29 + */ +define('CRYPT_DES_MODE_ECB', 1); +/** + * Encrypt / decrypt using the Code Book Chaining mode. + * + * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Cipher-block_chaining_.28CBC.29 + */ +define('CRYPT_DES_MODE_CBC', 2); +/** + * Encrypt / decrypt using the Cipher Feedback mode. + * + * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Cipher_feedback_.28CFB.29 + */ +define('CRYPT_DES_MODE_CFB', 3); +/** + * Encrypt / decrypt using the Cipher Feedback mode. + * + * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Output_feedback_.28OFB.29 + */ +define('CRYPT_DES_MODE_OFB', 4); +/**#@-*/ + +/**#@+ + * @access private + * @see Crypt_DES::Crypt_DES() + */ +/** + * Toggles the internal implementation + */ +define('CRYPT_DES_MODE_INTERNAL', 1); +/** + * Toggles the mcrypt implementation + */ +define('CRYPT_DES_MODE_MCRYPT', 2); +/**#@-*/ + +/** + * Pure-PHP implementation of DES. + * + * @author Jim Wigginton + * @version 0.1.0 + * @access public + * @package Crypt_DES + */ +class Crypt_DES { + /** + * The Key Schedule + * + * @see Crypt_DES::setKey() + * @var Array + * @access private + */ + var $keys = "\0\0\0\0\0\0\0\0"; + + /** + * The Encryption Mode + * + * @see Crypt_DES::Crypt_DES() + * @var Integer + * @access private + */ + var $mode; + + /** + * Continuous Buffer status + * + * @see Crypt_DES::enableContinuousBuffer() + * @var Boolean + * @access private + */ + var $continuousBuffer = false; + + /** + * Padding status + * + * @see Crypt_DES::enablePadding() + * @var Boolean + * @access private + */ + var $padding = true; + + /** + * The Initialization Vector + * + * @see Crypt_DES::setIV() + * @var String + * @access private + */ + var $iv = "\0\0\0\0\0\0\0\0"; + + /** + * A "sliding" Initialization Vector + * + * @see Crypt_DES::enableContinuousBuffer() + * @var String + * @access private + */ + var $encryptIV = "\0\0\0\0\0\0\0\0"; + + /** + * A "sliding" Initialization Vector + * + * @see Crypt_DES::enableContinuousBuffer() + * @var String + * @access private + */ + var $decryptIV = "\0\0\0\0\0\0\0\0"; + + /** + * mcrypt resource for encryption + * + * The mcrypt resource can be recreated every time something needs to be created or it can be created just once. + * Since mcrypt operates in continuous mode, by default, it'll need to be recreated when in non-continuous mode. + * + * @see Crypt_DES::encrypt() + * @var String + * @access private + */ + var $enmcrypt; + + /** + * mcrypt resource for decryption + * + * The mcrypt resource can be recreated every time something needs to be created or it can be created just once. + * Since mcrypt operates in continuous mode, by default, it'll need to be recreated when in non-continuous mode. + * + * @see Crypt_DES::decrypt() + * @var String + * @access private + */ + var $demcrypt; + + /** + * Does the enmcrypt resource need to be (re)initialized? + * + * @see Crypt_DES::setKey() + * @see Crypt_DES::setIV() + * @var Boolean + * @access private + */ + var $enchanged = true; + + /** + * Does the demcrypt resource need to be (re)initialized? + * + * @see Crypt_DES::setKey() + * @see Crypt_DES::setIV() + * @var Boolean + * @access private + */ + var $dechanged = true; + + /** + * Is the mode one that is paddable? + * + * @see Crypt_DES::Crypt_DES() + * @var Boolean + * @access private + */ + var $paddable = false; + + /** + * Encryption buffer for CTR, OFB and CFB modes + * + * @see Crypt_DES::encrypt() + * @var String + * @access private + */ + var $enbuffer = ''; + + /** + * Decryption buffer for CTR, OFB and CFB modes + * + * @see Crypt_DES::decrypt() + * @var String + * @access private + */ + var $debuffer = ''; + + /** + * mcrypt resource for CFB mode + * + * @see Crypt_DES::encrypt() + * @see Crypt_DES::decrypt() + * @var String + * @access private + */ + var $ecb; + + /** + * Default Constructor. + * + * Determines whether or not the mcrypt extension should be used. $mode should only, at present, be + * CRYPT_DES_MODE_ECB or CRYPT_DES_MODE_CBC. If not explictly set, CRYPT_DES_MODE_CBC will be used. + * + * @param optional Integer $mode + * @return Crypt_DES + * @access public + */ + function Crypt_DES($mode = CRYPT_DES_MODE_CBC) + { + if ( !defined('CRYPT_DES_MODE') ) { + switch (true) { + case extension_loaded('mcrypt') && in_array('des', mcrypt_list_algorithms()): + define('CRYPT_DES_MODE', CRYPT_DES_MODE_MCRYPT); + break; + default: + define('CRYPT_DES_MODE', CRYPT_DES_MODE_INTERNAL); + } + } + + switch ( CRYPT_DES_MODE ) { + case CRYPT_DES_MODE_MCRYPT: + switch ($mode) { + case CRYPT_DES_MODE_ECB: + $this->paddable = true; + $this->mode = MCRYPT_MODE_ECB; + break; + case CRYPT_DES_MODE_CTR: + $this->mode = 'ctr'; + //$this->mode = in_array('ctr', mcrypt_list_modes()) ? 'ctr' : CRYPT_DES_MODE_CTR; + break; + case CRYPT_DES_MODE_CFB: + $this->mode = 'ncfb'; + break; + case CRYPT_DES_MODE_OFB: + $this->mode = MCRYPT_MODE_NOFB; + break; + case CRYPT_DES_MODE_CBC: + default: + $this->paddable = true; + $this->mode = MCRYPT_MODE_CBC; + } + + break; + default: + switch ($mode) { + case CRYPT_DES_MODE_ECB: + case CRYPT_DES_MODE_CBC: + $this->paddable = true; + $this->mode = $mode; + break; + case CRYPT_DES_MODE_CTR: + case CRYPT_DES_MODE_CFB: + case CRYPT_DES_MODE_OFB: + $this->mode = $mode; + break; + default: + $this->paddable = true; + $this->mode = CRYPT_DES_MODE_CBC; + } + } + } + + /** + * Sets the key. + * + * Keys can be of any length. DES, itself, uses 64-bit keys (eg. strlen($key) == 8), however, we + * only use the first eight, if $key has more then eight characters in it, and pad $key with the + * null byte if it is less then eight characters long. + * + * DES also requires that every eighth bit be a parity bit, however, we'll ignore that. + * + * If the key is not explicitly set, it'll be assumed to be all zero's. + * + * @access public + * @param String $key + */ + function setKey($key) + { + $this->keys = ( CRYPT_DES_MODE == CRYPT_DES_MODE_MCRYPT ) ? str_pad(substr($key, 0, 8), 8, chr(0)) : $this->_prepareKey($key); + $this->changed = true; + } + + /** + * Sets the password. + * + * Depending on what $method is set to, setPassword()'s (optional) parameters are as follows: + * {@link http://en.wikipedia.org/wiki/PBKDF2 pbkdf2}: + * $hash, $salt, $count + * + * @param String $password + * @param optional String $method + * @access public + */ + function setPassword($password, $method = 'pbkdf2') + { + $key = ''; + + switch ($method) { + default: // 'pbkdf2' + list(, , $hash, $salt, $count) = func_get_args(); + if (!isset($hash)) { + $hash = 'sha1'; + } + // WPA and WPA use the SSID as the salt + if (!isset($salt)) { + $salt = 'phpseclib/salt'; + } + // RFC2898#section-4.2 uses 1,000 iterations by default + // WPA and WPA2 use 4,096. + if (!isset($count)) { + $count = 1000; + } + + if (!class_exists('Crypt_Hash')) { + require_once('Crypt/Hash.php'); + } + + $i = 1; + while (strlen($key) < 8) { // $dkLen == 8 + //$dk.= $this->_pbkdf($password, $salt, $count, $i++); + $hmac = new Crypt_Hash(); + $hmac->setHash($hash); + $hmac->setKey($password); + $f = $u = $hmac->hash($salt . pack('N', $i++)); + for ($j = 2; $j <= $count; $j++) { + $u = $hmac->hash($u); + $f^= $u; + } + $key.= $f; + } + } + + $this->setKey($key); + } + + /** + * Sets the initialization vector. (optional) + * + * SetIV is not required when CRYPT_DES_MODE_ECB is being used. If not explictly set, it'll be assumed + * to be all zero's. + * + * @access public + * @param String $iv + */ + function setIV($iv) + { + $this->encryptIV = $this->decryptIV = $this->iv = str_pad(substr($iv, 0, 8), 8, chr(0)); + $this->changed = true; + } + + /** + * Generate CTR XOR encryption key + * + * Encrypt the output of this and XOR it against the ciphertext / plaintext to get the + * plaintext / ciphertext in CTR mode. + * + * @see Crypt_DES::decrypt() + * @see Crypt_DES::encrypt() + * @access public + * @param Integer $length + * @param String $iv + */ + function _generate_xor($length, &$iv) + { + $xor = ''; + $num_blocks = ($length + 7) >> 3; + for ($i = 0; $i < $num_blocks; $i++) { + $xor.= $iv; + for ($j = 4; $j <= 8; $j+=4) { + $temp = substr($iv, -$j, 4); + switch ($temp) { + case "\xFF\xFF\xFF\xFF": + $iv = substr_replace($iv, "\x00\x00\x00\x00", -$j, 4); + break; + case "\x7F\xFF\xFF\xFF": + $iv = substr_replace($iv, "\x80\x00\x00\x00", -$j, 4); + break 2; + default: + extract(unpack('Ncount', $temp)); + $iv = substr_replace($iv, pack('N', $count + 1), -$j, 4); + break 2; + } + } + } + + return $xor; + } + + /** + * Encrypts a message. + * + * $plaintext will be padded with up to 8 additional bytes. Other DES implementations may or may not pad in the + * same manner. Other common approaches to padding and the reasons why it's necessary are discussed in the following + * URL: + * + * {@link http://www.di-mgt.com.au/cryptopad.html http://www.di-mgt.com.au/cryptopad.html} + * + * An alternative to padding is to, separately, send the length of the file. This is what SSH, in fact, does. + * strlen($plaintext) will still need to be a multiple of 8, however, arbitrary values can be added to make it that + * length. + * + * @see Crypt_DES::decrypt() + * @access public + * @param String $plaintext + */ + function encrypt($plaintext) + { + if ($this->paddable) { + $plaintext = $this->_pad($plaintext); + } + + if ( CRYPT_DES_MODE == CRYPT_DES_MODE_MCRYPT ) { + if ($this->enchanged) { + if (!isset($this->enmcrypt)) { + $this->enmcrypt = mcrypt_module_open(MCRYPT_DES, '', $this->mode, ''); + } + mcrypt_generic_init($this->enmcrypt, $this->keys, $this->encryptIV); + if ($this->mode != 'ncfb') { + $this->enchanged = false; + } + } + + if ($this->mode != 'ncfb') { + $ciphertext = mcrypt_generic($this->enmcrypt, $plaintext); + } else { + if ($this->enchanged) { + $this->ecb = mcrypt_module_open(MCRYPT_DES, '', MCRYPT_MODE_ECB, ''); + mcrypt_generic_init($this->ecb, $this->keys, "\0\0\0\0\0\0\0\0"); + $this->enchanged = false; + } + + if (strlen($this->enbuffer)) { + $ciphertext = $plaintext ^ substr($this->encryptIV, strlen($this->enbuffer)); + $this->enbuffer.= $ciphertext; + if (strlen($this->enbuffer) == 8) { + $this->encryptIV = $this->enbuffer; + $this->enbuffer = ''; + mcrypt_generic_init($this->enmcrypt, $this->keys, $this->encryptIV); + } + $plaintext = substr($plaintext, strlen($ciphertext)); + } else { + $ciphertext = ''; + } + + $last_pos = strlen($plaintext) & 0xFFFFFFF8; + $ciphertext.= $last_pos ? mcrypt_generic($this->enmcrypt, substr($plaintext, 0, $last_pos)) : ''; + + if (strlen($plaintext) & 0x7) { + if (strlen($ciphertext)) { + $this->encryptIV = substr($ciphertext, -8); + } + $this->encryptIV = mcrypt_generic($this->ecb, $this->encryptIV); + $this->enbuffer = substr($plaintext, $last_pos) ^ $this->encryptIV; + $ciphertext.= $this->enbuffer; + } + } + + if (!$this->continuousBuffer) { + mcrypt_generic_init($this->enmcrypt, $this->keys, $this->encryptIV); + } + + return $ciphertext; + } + + if (!is_array($this->keys)) { + $this->keys = $this->_prepareKey("\0\0\0\0\0\0\0\0"); + } + + $buffer = &$this->enbuffer; + $continuousBuffer = $this->continuousBuffer; + $ciphertext = ''; + switch ($this->mode) { + case CRYPT_DES_MODE_ECB: + for ($i = 0; $i < strlen($plaintext); $i+=8) { + $ciphertext.= $this->_processBlock(substr($plaintext, $i, 8), CRYPT_DES_ENCRYPT); + } + break; + case CRYPT_DES_MODE_CBC: + $xor = $this->encryptIV; + for ($i = 0; $i < strlen($plaintext); $i+=8) { + $block = substr($plaintext, $i, 8); + $block = $this->_processBlock($block ^ $xor, CRYPT_DES_ENCRYPT); + $xor = $block; + $ciphertext.= $block; + } + if ($this->continuousBuffer) { + $this->encryptIV = $xor; + } + break; + case CRYPT_DES_MODE_CTR: + $xor = $this->encryptIV; + if (strlen($buffer['encrypted'])) { + for ($i = 0; $i < strlen($plaintext); $i+=8) { + $block = substr($plaintext, $i, 8); + $buffer['encrypted'].= $this->_processBlock($this->_generate_xor(8, $xor), CRYPT_DES_ENCRYPT); + $key = $this->_string_shift($buffer['encrypted'], 8); + $ciphertext.= $block ^ $key; + } + } else { + for ($i = 0; $i < strlen($plaintext); $i+=8) { + $block = substr($plaintext, $i, 8); + $key = $this->_processBlock($this->_generate_xor(8, $xor), CRYPT_DES_ENCRYPT); + $ciphertext.= $block ^ $key; + } + } + if ($this->continuousBuffer) { + $this->encryptIV = $xor; + if ($start = strlen($plaintext) & 7) { + $buffer['encrypted'] = substr($key, $start) . $buffer['encrypted']; + } + } + break; + case CRYPT_DES_MODE_CFB: + if (!empty($buffer['xor'])) { + $ciphertext = $plaintext ^ $buffer['xor']; + $iv = $buffer['encrypted'] . $ciphertext; + $start = strlen($ciphertext); + $buffer['encrypted'].= $ciphertext; + $buffer['xor'] = substr($buffer['xor'], strlen($ciphertext)); + } else { + $ciphertext = ''; + $iv = $this->encryptIV; + $start = 0; + } + + for ($i = $start; $i < strlen($plaintext); $i+=8) { + $block = substr($plaintext, $i, 8); + $xor = $this->_processBlock($iv, CRYPT_DES_ENCRYPT); + $iv = $block ^ $xor; + if ($continuousBuffer && strlen($iv) != 8) { + $buffer = array( + 'encrypted' => $iv, + 'xor' => substr($xor, strlen($iv)) + ); + } + $ciphertext.= $iv; + } + + if ($this->continuousBuffer) { + $this->encryptIV = $iv; + } + break; + case CRYPT_DES_MODE_OFB: + $xor = $this->encryptIV; + if (strlen($buffer)) { + for ($i = 0; $i < strlen($plaintext); $i+=8) { + $xor = $this->_processBlock($xor, CRYPT_DES_ENCRYPT); + $buffer.= $xor; + $key = $this->_string_shift($buffer, 8); + $ciphertext.= substr($plaintext, $i, 8) ^ $key; + } + } else { + for ($i = 0; $i < strlen($plaintext); $i+=8) { + $xor = $this->_processBlock($xor, CRYPT_DES_ENCRYPT); + $ciphertext.= substr($plaintext, $i, 8) ^ $xor; + } + $key = $xor; + } + if ($this->continuousBuffer) { + $this->encryptIV = $xor; + if ($start = strlen($plaintext) & 7) { + $buffer = substr($key, $start) . $buffer; + } + } + } + + return $ciphertext; + } + + /** + * Decrypts a message. + * + * If strlen($ciphertext) is not a multiple of 8, null bytes will be added to the end of the string until it is. + * + * @see Crypt_DES::encrypt() + * @access public + * @param String $ciphertext + */ + function decrypt($ciphertext) + { + if ($this->paddable) { + // we pad with chr(0) since that's what mcrypt_generic does. to quote from http://php.net/function.mcrypt-generic : + // "The data is padded with "\0" to make sure the length of the data is n * blocksize." + $ciphertext = str_pad($ciphertext, (strlen($ciphertext) + 7) & 0xFFFFFFF8, chr(0)); + } + + if ( CRYPT_DES_MODE == CRYPT_DES_MODE_MCRYPT ) { + if ($this->dechanged) { + if (!isset($this->demcrypt)) { + $this->demcrypt = mcrypt_module_open(MCRYPT_DES, '', $this->mode, ''); + } + mcrypt_generic_init($this->demcrypt, $this->keys, $this->decryptIV); + if ($this->mode != 'ncfb') { + $this->dechanged = false; + } + } + + if ($this->mode != 'ncfb') { + $plaintext = mdecrypt_generic($this->demcrypt, $ciphertext); + } else { + if ($this->dechanged) { + $this->ecb = mcrypt_module_open(MCRYPT_DES, '', MCRYPT_MODE_ECB, ''); + mcrypt_generic_init($this->ecb, $this->keys, "\0\0\0\0\0\0\0\0"); + $this->dechanged = false; + } + + if (strlen($this->debuffer)) { + $plaintext = $ciphertext ^ substr($this->decryptIV, strlen($this->debuffer)); + + $this->debuffer.= substr($ciphertext, 0, strlen($plaintext)); + if (strlen($this->debuffer) == 8) { + $this->decryptIV = $this->debuffer; + $this->debuffer = ''; + mcrypt_generic_init($this->demcrypt, $this->keys, $this->decryptIV); + } + $ciphertext = substr($ciphertext, strlen($plaintext)); + } else { + $plaintext = ''; + } + + $last_pos = strlen($ciphertext) & 0xFFFFFFF8; + $plaintext.= $last_pos ? mdecrypt_generic($this->demcrypt, substr($ciphertext, 0, $last_pos)) : ''; + + if (strlen($ciphertext) & 0x7) { + if (strlen($plaintext)) { + $this->decryptIV = substr($ciphertext, $last_pos - 8, 8); + } + $this->decryptIV = mcrypt_generic($this->ecb, $this->decryptIV); + $this->debuffer = substr($ciphertext, $last_pos); + $plaintext.= $this->debuffer ^ $this->decryptIV; + } + + return $plaintext; + } + + if (!$this->continuousBuffer) { + mcrypt_generic_init($this->demcrypt, $this->keys, $this->decryptIV); + } + + return $this->paddable ? $this->_unpad($plaintext) : $plaintext; + } + + if (!is_array($this->keys)) { + $this->keys = $this->_prepareKey("\0\0\0\0\0\0\0\0"); + } + + $buffer = &$this->debuffer; + $continuousBuffer = $this->continuousBuffer; + $plaintext = ''; + switch ($this->mode) { + case CRYPT_DES_MODE_ECB: + for ($i = 0; $i < strlen($ciphertext); $i+=8) { + $plaintext.= $this->_processBlock(substr($ciphertext, $i, 8), CRYPT_DES_DECRYPT); + } + break; + case CRYPT_DES_MODE_CBC: + $xor = $this->decryptIV; + for ($i = 0; $i < strlen($ciphertext); $i+=8) { + $block = substr($ciphertext, $i, 8); + $plaintext.= $this->_processBlock($block, CRYPT_DES_DECRYPT) ^ $xor; + $xor = $block; + } + if ($this->continuousBuffer) { + $this->decryptIV = $xor; + } + break; + case CRYPT_DES_MODE_CTR: + $xor = $this->decryptIV; + if (strlen($buffer['ciphertext'])) { + for ($i = 0; $i < strlen($ciphertext); $i+=8) { + $block = substr($ciphertext, $i, 8); + $buffer['ciphertext'].= $this->_processBlock($this->_generate_xor(8, $xor), CRYPT_DES_ENCRYPT); + $key = $this->_string_shift($buffer['ciphertext'], 8); + $plaintext.= $block ^ $key; + } + } else { + for ($i = 0; $i < strlen($ciphertext); $i+=8) { + $block = substr($ciphertext, $i, 8); + $key = $this->_processBlock($this->_generate_xor(8, $xor), CRYPT_DES_ENCRYPT); + $plaintext.= $block ^ $key; + } + } + if ($this->continuousBuffer) { + $this->decryptIV = $xor; + if ($start = strlen($ciphertext) % 8) { + $buffer['ciphertext'] = substr($key, $start) . $buffer['ciphertext']; + } + } + break; + case CRYPT_DES_MODE_CFB: + if (!empty($buffer['ciphertext'])) { + $plaintext = $ciphertext ^ substr($this->decryptIV, strlen($buffer['ciphertext'])); + $buffer['ciphertext'].= substr($ciphertext, 0, strlen($plaintext)); + if (strlen($buffer['ciphertext']) == 8) { + $xor = $this->_processBlock($buffer['ciphertext'], CRYPT_DES_ENCRYPT); + $buffer['ciphertext'] = ''; + } + $start = strlen($plaintext); + $block = $this->decryptIV; + } else { + $plaintext = ''; + $xor = $this->_processBlock($this->decryptIV, CRYPT_DES_ENCRYPT); + $start = 0; + } + + for ($i = $start; $i < strlen($ciphertext); $i+=8) { + $block = substr($ciphertext, $i, 8); + $plaintext.= $block ^ $xor; + if ($continuousBuffer && strlen($block) != 8) { + $buffer['ciphertext'].= $block; + $block = $xor; + } else if (strlen($block) == 8) { + $xor = $this->_processBlock($block, CRYPT_DES_ENCRYPT); + } + } + if ($this->continuousBuffer) { + $this->decryptIV = $block; + } + break; + case CRYPT_DES_MODE_OFB: + $xor = $this->decryptIV; + if (strlen($buffer)) { + for ($i = 0; $i < strlen($ciphertext); $i+=8) { + $xor = $this->_processBlock($xor, CRYPT_DES_ENCRYPT); + $buffer.= $xor; + $key = $this->_string_shift($buffer, 8); + $plaintext.= substr($ciphertext, $i, 8) ^ $key; + } + } else { + for ($i = 0; $i < strlen($ciphertext); $i+=8) { + $xor = $this->_processBlock($xor, CRYPT_DES_ENCRYPT); + $plaintext.= substr($ciphertext, $i, 8) ^ $xor; + } + $key = $xor; + } + if ($this->continuousBuffer) { + $this->decryptIV = $xor; + if ($start = strlen($ciphertext) % 8) { + $buffer = substr($key, $start) . $buffer; + } + } + } + + return $this->paddable ? $this->_unpad($plaintext) : $plaintext; + } + + /** + * Treat consecutive "packets" as if they are a continuous buffer. + * + * Say you have a 16-byte plaintext $plaintext. Using the default behavior, the two following code snippets + * will yield different outputs: + * + * + * echo $des->encrypt(substr($plaintext, 0, 8)); + * echo $des->encrypt(substr($plaintext, 8, 8)); + * + * + * echo $des->encrypt($plaintext); + * + * + * The solution is to enable the continuous buffer. Although this will resolve the above discrepancy, it creates + * another, as demonstrated with the following: + * + * + * $des->encrypt(substr($plaintext, 0, 8)); + * echo $des->decrypt($des->encrypt(substr($plaintext, 8, 8))); + * + * + * echo $des->decrypt($des->encrypt(substr($plaintext, 8, 8))); + * + * + * With the continuous buffer disabled, these would yield the same output. With it enabled, they yield different + * outputs. The reason is due to the fact that the initialization vector's change after every encryption / + * decryption round when the continuous buffer is enabled. When it's disabled, they remain constant. + * + * Put another way, when the continuous buffer is enabled, the state of the Crypt_DES() object changes after each + * encryption / decryption round, whereas otherwise, it'd remain constant. For this reason, it's recommended that + * continuous buffers not be used. They do offer better security and are, in fact, sometimes required (SSH uses them), + * however, they are also less intuitive and more likely to cause you problems. + * + * @see Crypt_DES::disableContinuousBuffer() + * @access public + */ + function enableContinuousBuffer() + { + $this->continuousBuffer = true; + } + + /** + * Treat consecutive packets as if they are a discontinuous buffer. + * + * The default behavior. + * + * @see Crypt_DES::enableContinuousBuffer() + * @access public + */ + function disableContinuousBuffer() + { + $this->continuousBuffer = false; + $this->encryptIV = $this->iv; + $this->decryptIV = $this->iv; + } + + /** + * Pad "packets". + * + * DES works by encrypting eight bytes at a time. If you ever need to encrypt or decrypt something that's not + * a multiple of eight, it becomes necessary to pad the input so that it's length is a multiple of eight. + * + * Padding is enabled by default. Sometimes, however, it is undesirable to pad strings. Such is the case in SSH1, + * where "packets" are padded with random bytes before being encrypted. Unpad these packets and you risk stripping + * away characters that shouldn't be stripped away. (SSH knows how many bytes are added because the length is + * transmitted separately) + * + * @see Crypt_DES::disablePadding() + * @access public + */ + function enablePadding() + { + $this->padding = true; + } + + /** + * Do not pad packets. + * + * @see Crypt_DES::enablePadding() + * @access public + */ + function disablePadding() + { + $this->padding = false; + } + + /** + * Pads a string + * + * Pads a string using the RSA PKCS padding standards so that its length is a multiple of the blocksize (8). + * 8 - (strlen($text) & 7) bytes are added, each of which is equal to chr(8 - (strlen($text) & 7) + * + * If padding is disabled and $text is not a multiple of the blocksize, the string will be padded regardless + * and padding will, hence forth, be enabled. + * + * @see Crypt_DES::_unpad() + * @access private + */ + function _pad($text) + { + $length = strlen($text); + + if (!$this->padding) { + if (($length & 7) == 0) { + return $text; + } else { + user_error("The plaintext's length ($length) is not a multiple of the block size (8)", E_USER_NOTICE); + $this->padding = true; + } + } + + $pad = 8 - ($length & 7); + return str_pad($text, $length + $pad, chr($pad)); + } + + /** + * Unpads a string + * + * If padding is enabled and the reported padding length is invalid the encryption key will be assumed to be wrong + * and false will be returned. + * + * @see Crypt_DES::_pad() + * @access private + */ + function _unpad($text) + { + if (!$this->padding) { + return $text; + } + + $length = ord($text[strlen($text) - 1]); + + if (!$length || $length > 8) { + return false; + } + + return substr($text, 0, -$length); + } + + /** + * Encrypts or decrypts a 64-bit block + * + * $mode should be either CRYPT_DES_ENCRYPT or CRYPT_DES_DECRYPT. See + * {@link http://en.wikipedia.org/wiki/Image:Feistel.png Feistel.png} to get a general + * idea of what this function does. + * + * @access private + * @param String $block + * @param Integer $mode + * @return String + */ + function _processBlock($block, $mode) + { + // s-boxes. in the official DES docs, they're described as being matrices that + // one accesses by using the first and last bits to determine the row and the + // middle four bits to determine the column. in this implementation, they've + // been converted to vectors + static $sbox = array( + array( + 14, 0, 4, 15, 13, 7, 1, 4, 2, 14, 15, 2, 11, 13, 8, 1, + 3, 10 ,10, 6, 6, 12, 12, 11, 5, 9, 9, 5, 0, 3, 7, 8, + 4, 15, 1, 12, 14, 8, 8, 2, 13, 4, 6, 9, 2, 1, 11, 7, + 15, 5, 12, 11, 9, 3, 7, 14, 3, 10, 10, 0, 5, 6, 0, 13 + ), + array( + 15, 3, 1, 13, 8, 4, 14, 7, 6, 15, 11, 2, 3, 8, 4, 14, + 9, 12, 7, 0, 2, 1, 13, 10, 12, 6, 0, 9, 5, 11, 10, 5, + 0, 13, 14, 8, 7, 10, 11, 1, 10, 3, 4, 15, 13, 4, 1, 2, + 5, 11, 8, 6, 12, 7, 6, 12, 9, 0, 3, 5, 2, 14, 15, 9 + ), + array( + 10, 13, 0, 7, 9, 0, 14, 9, 6, 3, 3, 4, 15, 6, 5, 10, + 1, 2, 13, 8, 12, 5, 7, 14, 11, 12, 4, 11, 2, 15, 8, 1, + 13, 1, 6, 10, 4, 13, 9, 0, 8, 6, 15, 9, 3, 8, 0, 7, + 11, 4, 1, 15, 2, 14, 12, 3, 5, 11, 10, 5, 14, 2, 7, 12 + ), + array( + 7, 13, 13, 8, 14, 11, 3, 5, 0, 6, 6, 15, 9, 0, 10, 3, + 1, 4, 2, 7, 8, 2, 5, 12, 11, 1, 12, 10, 4, 14, 15, 9, + 10, 3, 6, 15, 9, 0, 0, 6, 12, 10, 11, 1, 7, 13, 13, 8, + 15, 9, 1, 4, 3, 5, 14, 11, 5, 12, 2, 7, 8, 2, 4, 14 + ), + array( + 2, 14, 12, 11, 4, 2, 1, 12, 7, 4, 10, 7, 11, 13, 6, 1, + 8, 5, 5, 0, 3, 15, 15, 10, 13, 3, 0, 9, 14, 8, 9, 6, + 4, 11, 2, 8, 1, 12, 11, 7, 10, 1, 13, 14, 7, 2, 8, 13, + 15, 6, 9, 15, 12, 0, 5, 9, 6, 10, 3, 4, 0, 5, 14, 3 + ), + array( + 12, 10, 1, 15, 10, 4, 15, 2, 9, 7, 2, 12, 6, 9, 8, 5, + 0, 6, 13, 1, 3, 13, 4, 14, 14, 0, 7, 11, 5, 3, 11, 8, + 9, 4, 14, 3, 15, 2, 5, 12, 2, 9, 8, 5, 12, 15, 3, 10, + 7, 11, 0, 14, 4, 1, 10, 7, 1, 6, 13, 0, 11, 8, 6, 13 + ), + array( + 4, 13, 11, 0, 2, 11, 14, 7, 15, 4, 0, 9, 8, 1, 13, 10, + 3, 14, 12, 3, 9, 5, 7, 12, 5, 2, 10, 15, 6, 8, 1, 6, + 1, 6, 4, 11, 11, 13, 13, 8, 12, 1, 3, 4, 7, 10, 14, 7, + 10, 9, 15, 5, 6, 0, 8, 15, 0, 14, 5, 2, 9, 3, 2, 12 + ), + array( + 13, 1, 2, 15, 8, 13, 4, 8, 6, 10, 15, 3, 11, 7, 1, 4, + 10, 12, 9, 5, 3, 6, 14, 11, 5, 0, 0, 14, 12, 9, 7, 2, + 7, 2, 11, 1, 4, 14, 1, 7, 9, 4, 12, 10, 14, 8, 2, 13, + 0, 15, 6, 12, 10, 9, 13, 0, 15, 3, 3, 5, 5, 6, 8, 11 + ) + ); + + $keys = $this->keys; + + $temp = unpack('Na/Nb', $block); + $block = array($temp['a'], $temp['b']); + + // because php does arithmetic right shifts, if the most significant bits are set, right + // shifting those into the correct position will add 1's - not 0's. this will intefere + // with the | operation unless a second & is done. so we isolate these bits and left shift + // them into place. we then & each block with 0x7FFFFFFF to prevennt 1's from being added + // for any other shifts. + $msb = array( + ($block[0] >> 31) & 1, + ($block[1] >> 31) & 1 + ); + $block[0] &= 0x7FFFFFFF; + $block[1] &= 0x7FFFFFFF; + + // we isolate the appropriate bit in the appropriate integer and shift as appropriate. in + // some cases, there are going to be multiple bits in the same integer that need to be shifted + // in the same way. we combine those into one shift operation. + $block = array( + (($block[1] & 0x00000040) << 25) | (($block[1] & 0x00004000) << 16) | + (($block[1] & 0x00400001) << 7) | (($block[1] & 0x40000100) >> 2) | + (($block[0] & 0x00000040) << 21) | (($block[0] & 0x00004000) << 12) | + (($block[0] & 0x00400001) << 3) | (($block[0] & 0x40000100) >> 6) | + (($block[1] & 0x00000010) << 19) | (($block[1] & 0x00001000) << 10) | + (($block[1] & 0x00100000) << 1) | (($block[1] & 0x10000000) >> 8) | + (($block[0] & 0x00000010) << 15) | (($block[0] & 0x00001000) << 6) | + (($block[0] & 0x00100000) >> 3) | (($block[0] & 0x10000000) >> 12) | + (($block[1] & 0x00000004) << 13) | (($block[1] & 0x00000400) << 4) | + (($block[1] & 0x00040000) >> 5) | (($block[1] & 0x04000000) >> 14) | + (($block[0] & 0x00000004) << 9) | ( $block[0] & 0x00000400 ) | + (($block[0] & 0x00040000) >> 9) | (($block[0] & 0x04000000) >> 18) | + (($block[1] & 0x00010000) >> 11) | (($block[1] & 0x01000000) >> 20) | + (($block[0] & 0x00010000) >> 15) | (($block[0] & 0x01000000) >> 24) + , + (($block[1] & 0x00000080) << 24) | (($block[1] & 0x00008000) << 15) | + (($block[1] & 0x00800002) << 6) | (($block[0] & 0x00000080) << 20) | + (($block[0] & 0x00008000) << 11) | (($block[0] & 0x00800002) << 2) | + (($block[1] & 0x00000020) << 18) | (($block[1] & 0x00002000) << 9) | + ( $block[1] & 0x00200000 ) | (($block[1] & 0x20000000) >> 9) | + (($block[0] & 0x00000020) << 14) | (($block[0] & 0x00002000) << 5) | + (($block[0] & 0x00200000) >> 4) | (($block[0] & 0x20000000) >> 13) | + (($block[1] & 0x00000008) << 12) | (($block[1] & 0x00000800) << 3) | + (($block[1] & 0x00080000) >> 6) | (($block[1] & 0x08000000) >> 15) | + (($block[0] & 0x00000008) << 8) | (($block[0] & 0x00000800) >> 1) | + (($block[0] & 0x00080000) >> 10) | (($block[0] & 0x08000000) >> 19) | + (($block[1] & 0x00000200) >> 3) | (($block[0] & 0x00000200) >> 7) | + (($block[1] & 0x00020000) >> 12) | (($block[1] & 0x02000000) >> 21) | + (($block[0] & 0x00020000) >> 16) | (($block[0] & 0x02000000) >> 25) | + ($msb[1] << 28) | ($msb[0] << 24) + ); + + for ($i = 0; $i < 16; $i++) { + // start of "the Feistel (F) function" - see the following URL: + // http://en.wikipedia.org/wiki/Image:Data_Encryption_Standard_InfoBox_Diagram.png + $temp = (($sbox[0][((($block[1] >> 27) & 0x1F) | (($block[1] & 1) << 5)) ^ $keys[$mode][$i][0]]) << 28) + | (($sbox[1][(($block[1] & 0x1F800000) >> 23) ^ $keys[$mode][$i][1]]) << 24) + | (($sbox[2][(($block[1] & 0x01F80000) >> 19) ^ $keys[$mode][$i][2]]) << 20) + | (($sbox[3][(($block[1] & 0x001F8000) >> 15) ^ $keys[$mode][$i][3]]) << 16) + | (($sbox[4][(($block[1] & 0x0001F800) >> 11) ^ $keys[$mode][$i][4]]) << 12) + | (($sbox[5][(($block[1] & 0x00001F80) >> 7) ^ $keys[$mode][$i][5]]) << 8) + | (($sbox[6][(($block[1] & 0x000001F8) >> 3) ^ $keys[$mode][$i][6]]) << 4) + | ( $sbox[7][((($block[1] & 0x1F) << 1) | (($block[1] >> 31) & 1)) ^ $keys[$mode][$i][7]]); + + $msb = ($temp >> 31) & 1; + $temp &= 0x7FFFFFFF; + $newBlock = (($temp & 0x00010000) << 15) | (($temp & 0x02020120) << 5) + | (($temp & 0x00001800) << 17) | (($temp & 0x01000000) >> 10) + | (($temp & 0x00000008) << 24) | (($temp & 0x00100000) << 6) + | (($temp & 0x00000010) << 21) | (($temp & 0x00008000) << 9) + | (($temp & 0x00000200) << 12) | (($temp & 0x10000000) >> 27) + | (($temp & 0x00000040) << 14) | (($temp & 0x08000000) >> 8) + | (($temp & 0x00004000) << 4) | (($temp & 0x00000002) << 16) + | (($temp & 0x00442000) >> 6) | (($temp & 0x40800000) >> 15) + | (($temp & 0x00000001) << 11) | (($temp & 0x20000000) >> 20) + | (($temp & 0x00080000) >> 13) | (($temp & 0x00000004) << 3) + | (($temp & 0x04000000) >> 22) | (($temp & 0x00000480) >> 7) + | (($temp & 0x00200000) >> 19) | ($msb << 23); + // end of "the Feistel (F) function" - $newBlock is F's output + + $temp = $block[1]; + $block[1] = $block[0] ^ $newBlock; + $block[0] = $temp; + } + + $msb = array( + ($block[0] >> 31) & 1, + ($block[1] >> 31) & 1 + ); + $block[0] &= 0x7FFFFFFF; + $block[1] &= 0x7FFFFFFF; + + $block = array( + (($block[0] & 0x01000004) << 7) | (($block[1] & 0x01000004) << 6) | + (($block[0] & 0x00010000) << 13) | (($block[1] & 0x00010000) << 12) | + (($block[0] & 0x00000100) << 19) | (($block[1] & 0x00000100) << 18) | + (($block[0] & 0x00000001) << 25) | (($block[1] & 0x00000001) << 24) | + (($block[0] & 0x02000008) >> 2) | (($block[1] & 0x02000008) >> 3) | + (($block[0] & 0x00020000) << 4) | (($block[1] & 0x00020000) << 3) | + (($block[0] & 0x00000200) << 10) | (($block[1] & 0x00000200) << 9) | + (($block[0] & 0x00000002) << 16) | (($block[1] & 0x00000002) << 15) | + (($block[0] & 0x04000000) >> 11) | (($block[1] & 0x04000000) >> 12) | + (($block[0] & 0x00040000) >> 5) | (($block[1] & 0x00040000) >> 6) | + (($block[0] & 0x00000400) << 1) | ( $block[1] & 0x00000400 ) | + (($block[0] & 0x08000000) >> 20) | (($block[1] & 0x08000000) >> 21) | + (($block[0] & 0x00080000) >> 14) | (($block[1] & 0x00080000) >> 15) | + (($block[0] & 0x00000800) >> 8) | (($block[1] & 0x00000800) >> 9) + , + (($block[0] & 0x10000040) << 3) | (($block[1] & 0x10000040) << 2) | + (($block[0] & 0x00100000) << 9) | (($block[1] & 0x00100000) << 8) | + (($block[0] & 0x00001000) << 15) | (($block[1] & 0x00001000) << 14) | + (($block[0] & 0x00000010) << 21) | (($block[1] & 0x00000010) << 20) | + (($block[0] & 0x20000080) >> 6) | (($block[1] & 0x20000080) >> 7) | + ( $block[0] & 0x00200000 ) | (($block[1] & 0x00200000) >> 1) | + (($block[0] & 0x00002000) << 6) | (($block[1] & 0x00002000) << 5) | + (($block[0] & 0x00000020) << 12) | (($block[1] & 0x00000020) << 11) | + (($block[0] & 0x40000000) >> 15) | (($block[1] & 0x40000000) >> 16) | + (($block[0] & 0x00400000) >> 9) | (($block[1] & 0x00400000) >> 10) | + (($block[0] & 0x00004000) >> 3) | (($block[1] & 0x00004000) >> 4) | + (($block[0] & 0x00800000) >> 18) | (($block[1] & 0x00800000) >> 19) | + (($block[0] & 0x00008000) >> 12) | (($block[1] & 0x00008000) >> 13) | + ($msb[0] << 7) | ($msb[1] << 6) + ); + + return pack('NN', $block[0], $block[1]); + } + + /** + * Creates the key schedule. + * + * @access private + * @param String $key + * @return Array + */ + function _prepareKey($key) + { + static $shifts = array( // number of key bits shifted per round + 1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1 + ); + + // pad the key and remove extra characters as appropriate. + $key = str_pad(substr($key, 0, 8), 8, chr(0)); + + $temp = unpack('Na/Nb', $key); + $key = array($temp['a'], $temp['b']); + $msb = array( + ($key[0] >> 31) & 1, + ($key[1] >> 31) & 1 + ); + $key[0] &= 0x7FFFFFFF; + $key[1] &= 0x7FFFFFFF; + + $key = array( + (($key[1] & 0x00000002) << 26) | (($key[1] & 0x00000204) << 17) | + (($key[1] & 0x00020408) << 8) | (($key[1] & 0x02040800) >> 1) | + (($key[0] & 0x00000002) << 22) | (($key[0] & 0x00000204) << 13) | + (($key[0] & 0x00020408) << 4) | (($key[0] & 0x02040800) >> 5) | + (($key[1] & 0x04080000) >> 10) | (($key[0] & 0x04080000) >> 14) | + (($key[1] & 0x08000000) >> 19) | (($key[0] & 0x08000000) >> 23) | + (($key[0] & 0x00000010) >> 1) | (($key[0] & 0x00001000) >> 10) | + (($key[0] & 0x00100000) >> 19) | (($key[0] & 0x10000000) >> 28) + , + (($key[1] & 0x00000080) << 20) | (($key[1] & 0x00008000) << 11) | + (($key[1] & 0x00800000) << 2) | (($key[0] & 0x00000080) << 16) | + (($key[0] & 0x00008000) << 7) | (($key[0] & 0x00800000) >> 2) | + (($key[1] & 0x00000040) << 13) | (($key[1] & 0x00004000) << 4) | + (($key[1] & 0x00400000) >> 5) | (($key[1] & 0x40000000) >> 14) | + (($key[0] & 0x00000040) << 9) | ( $key[0] & 0x00004000 ) | + (($key[0] & 0x00400000) >> 9) | (($key[0] & 0x40000000) >> 18) | + (($key[1] & 0x00000020) << 6) | (($key[1] & 0x00002000) >> 3) | + (($key[1] & 0x00200000) >> 12) | (($key[1] & 0x20000000) >> 21) | + (($key[0] & 0x00000020) << 2) | (($key[0] & 0x00002000) >> 7) | + (($key[0] & 0x00200000) >> 16) | (($key[0] & 0x20000000) >> 25) | + (($key[1] & 0x00000010) >> 1) | (($key[1] & 0x00001000) >> 10) | + (($key[1] & 0x00100000) >> 19) | (($key[1] & 0x10000000) >> 28) | + ($msb[1] << 24) | ($msb[0] << 20) + ); + + $keys = array(); + for ($i = 0; $i < 16; $i++) { + $key[0] <<= $shifts[$i]; + $temp = ($key[0] & 0xF0000000) >> 28; + $key[0] = ($key[0] | $temp) & 0x0FFFFFFF; + + $key[1] <<= $shifts[$i]; + $temp = ($key[1] & 0xF0000000) >> 28; + $key[1] = ($key[1] | $temp) & 0x0FFFFFFF; + + $temp = array( + (($key[1] & 0x00004000) >> 9) | (($key[1] & 0x00000800) >> 7) | + (($key[1] & 0x00020000) >> 14) | (($key[1] & 0x00000010) >> 2) | + (($key[1] & 0x08000000) >> 26) | (($key[1] & 0x00800000) >> 23) + , + (($key[1] & 0x02400000) >> 20) | (($key[1] & 0x00000001) << 4) | + (($key[1] & 0x00002000) >> 10) | (($key[1] & 0x00040000) >> 18) | + (($key[1] & 0x00000080) >> 6) + , + ( $key[1] & 0x00000020 ) | (($key[1] & 0x00000200) >> 5) | + (($key[1] & 0x00010000) >> 13) | (($key[1] & 0x01000000) >> 22) | + (($key[1] & 0x00000004) >> 1) | (($key[1] & 0x00100000) >> 20) + , + (($key[1] & 0x00001000) >> 7) | (($key[1] & 0x00200000) >> 17) | + (($key[1] & 0x00000002) << 2) | (($key[1] & 0x00000100) >> 6) | + (($key[1] & 0x00008000) >> 14) | (($key[1] & 0x04000000) >> 26) + , + (($key[0] & 0x00008000) >> 10) | ( $key[0] & 0x00000010 ) | + (($key[0] & 0x02000000) >> 22) | (($key[0] & 0x00080000) >> 17) | + (($key[0] & 0x00000200) >> 8) | (($key[0] & 0x00000002) >> 1) + , + (($key[0] & 0x04000000) >> 21) | (($key[0] & 0x00010000) >> 12) | + (($key[0] & 0x00000020) >> 2) | (($key[0] & 0x00000800) >> 9) | + (($key[0] & 0x00800000) >> 22) | (($key[0] & 0x00000100) >> 8) + , + (($key[0] & 0x00001000) >> 7) | (($key[0] & 0x00000088) >> 3) | + (($key[0] & 0x00020000) >> 14) | (($key[0] & 0x00000001) << 2) | + (($key[0] & 0x00400000) >> 21) + , + (($key[0] & 0x00000400) >> 5) | (($key[0] & 0x00004000) >> 10) | + (($key[0] & 0x00000040) >> 3) | (($key[0] & 0x00100000) >> 18) | + (($key[0] & 0x08000000) >> 26) | (($key[0] & 0x01000000) >> 24) + ); + + $keys[] = $temp; + } + + $temp = array( + CRYPT_DES_ENCRYPT => $keys, + CRYPT_DES_DECRYPT => array_reverse($keys) + ); + + return $temp; + } + + /** + * String Shift + * + * Inspired by array_shift + * + * @param String $string + * @param optional Integer $index + * @return String + * @access private + */ + function _string_shift(&$string, $index = 1) + { + $substr = substr($string, 0, $index); + $string = substr($string, $index); + return $substr; + } +} + +// vim: ts=4:sw=4:et: +// vim6: fdl=1: \ No newline at end of file diff --git a/3rdparty/phpseclib/Crypt/Hash.php b/3rdparty/phpseclib/Crypt/Hash.php new file mode 100644 index 00000000000..d621b14e9c1 --- /dev/null +++ b/3rdparty/phpseclib/Crypt/Hash.php @@ -0,0 +1,825 @@ + + * setKey('abcdefg'); + * + * echo base64_encode($hash->hash('abcdefg')); + * ?> + * + * + * LICENSE: Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @category Crypt + * @package Crypt_Hash + * @author Jim Wigginton + * @copyright MMVII Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @version $Id: Hash.php,v 1.6 2009/11/23 23:37:07 terrafrost Exp $ + * @link http://phpseclib.sourceforge.net + */ + +/**#@+ + * @access private + * @see Crypt_Hash::Crypt_Hash() + */ +/** + * Toggles the internal implementation + */ +define('CRYPT_HASH_MODE_INTERNAL', 1); +/** + * Toggles the mhash() implementation, which has been deprecated on PHP 5.3.0+. + */ +define('CRYPT_HASH_MODE_MHASH', 2); +/** + * Toggles the hash() implementation, which works on PHP 5.1.2+. + */ +define('CRYPT_HASH_MODE_HASH', 3); +/**#@-*/ + +/** + * Pure-PHP implementations of keyed-hash message authentication codes (HMACs) and various cryptographic hashing functions. + * + * @author Jim Wigginton + * @version 0.1.0 + * @access public + * @package Crypt_Hash + */ +class Crypt_Hash { + /** + * Byte-length of compression blocks / key (Internal HMAC) + * + * @see Crypt_Hash::setAlgorithm() + * @var Integer + * @access private + */ + var $b; + + /** + * Byte-length of hash output (Internal HMAC) + * + * @see Crypt_Hash::setHash() + * @var Integer + * @access private + */ + var $l = false; + + /** + * Hash Algorithm + * + * @see Crypt_Hash::setHash() + * @var String + * @access private + */ + var $hash; + + /** + * Key + * + * @see Crypt_Hash::setKey() + * @var String + * @access private + */ + var $key = false; + + /** + * Outer XOR (Internal HMAC) + * + * @see Crypt_Hash::setKey() + * @var String + * @access private + */ + var $opad; + + /** + * Inner XOR (Internal HMAC) + * + * @see Crypt_Hash::setKey() + * @var String + * @access private + */ + var $ipad; + + /** + * Default Constructor. + * + * @param optional String $hash + * @return Crypt_Hash + * @access public + */ + function Crypt_Hash($hash = 'sha1') + { + if ( !defined('CRYPT_HASH_MODE') ) { + switch (true) { + case extension_loaded('hash'): + define('CRYPT_HASH_MODE', CRYPT_HASH_MODE_HASH); + break; + case extension_loaded('mhash'): + define('CRYPT_HASH_MODE', CRYPT_HASH_MODE_MHASH); + break; + default: + define('CRYPT_HASH_MODE', CRYPT_HASH_MODE_INTERNAL); + } + } + + $this->setHash($hash); + } + + /** + * Sets the key for HMACs + * + * Keys can be of any length. + * + * @access public + * @param String $key + */ + function setKey($key = false) + { + $this->key = $key; + } + + /** + * Sets the hash function. + * + * @access public + * @param String $hash + */ + function setHash($hash) + { + $hash = strtolower($hash); + switch ($hash) { + case 'md5-96': + case 'sha1-96': + $this->l = 12; // 96 / 8 = 12 + break; + case 'md2': + case 'md5': + $this->l = 16; + break; + case 'sha1': + $this->l = 20; + break; + case 'sha256': + $this->l = 32; + break; + case 'sha384': + $this->l = 48; + break; + case 'sha512': + $this->l = 64; + } + + switch ($hash) { + case 'md2': + $mode = CRYPT_HASH_MODE == CRYPT_HASH_MODE_HASH && in_array('md2', hash_algos()) ? + CRYPT_HASH_MODE_HASH : CRYPT_HASH_MODE_INTERNAL; + break; + case 'sha384': + case 'sha512': + $mode = CRYPT_HASH_MODE == CRYPT_HASH_MODE_MHASH ? CRYPT_HASH_MODE_INTERNAL : CRYPT_HASH_MODE; + break; + default: + $mode = CRYPT_HASH_MODE; + } + + switch ( $mode ) { + case CRYPT_HASH_MODE_MHASH: + switch ($hash) { + case 'md5': + case 'md5-96': + $this->hash = MHASH_MD5; + break; + case 'sha256': + $this->hash = MHASH_SHA256; + break; + case 'sha1': + case 'sha1-96': + default: + $this->hash = MHASH_SHA1; + } + return; + case CRYPT_HASH_MODE_HASH: + switch ($hash) { + case 'md5': + case 'md5-96': + $this->hash = 'md5'; + return; + case 'md2': + case 'sha256': + case 'sha384': + case 'sha512': + $this->hash = $hash; + return; + case 'sha1': + case 'sha1-96': + default: + $this->hash = 'sha1'; + } + return; + } + + switch ($hash) { + case 'md2': + $this->b = 16; + $this->hash = array($this, '_md2'); + break; + case 'md5': + case 'md5-96': + $this->b = 64; + $this->hash = array($this, '_md5'); + break; + case 'sha256': + $this->b = 64; + $this->hash = array($this, '_sha256'); + break; + case 'sha384': + case 'sha512': + $this->b = 128; + $this->hash = array($this, '_sha512'); + break; + case 'sha1': + case 'sha1-96': + default: + $this->b = 64; + $this->hash = array($this, '_sha1'); + } + + $this->ipad = str_repeat(chr(0x36), $this->b); + $this->opad = str_repeat(chr(0x5C), $this->b); + } + + /** + * Compute the HMAC. + * + * @access public + * @param String $text + * @return String + */ + function hash($text) + { + $mode = is_array($this->hash) ? CRYPT_HASH_MODE_INTERNAL : CRYPT_HASH_MODE; + + if (!empty($this->key) || is_string($this->key)) { + switch ( $mode ) { + case CRYPT_HASH_MODE_MHASH: + $output = mhash($this->hash, $text, $this->key); + break; + case CRYPT_HASH_MODE_HASH: + $output = hash_hmac($this->hash, $text, $this->key, true); + break; + case CRYPT_HASH_MODE_INTERNAL: + /* "Applications that use keys longer than B bytes will first hash the key using H and then use the + resultant L byte string as the actual key to HMAC." + + -- http://tools.ietf.org/html/rfc2104#section-2 */ + $key = strlen($this->key) > $this->b ? call_user_func($this->hash, $this->key) : $this->key; + + $key = str_pad($key, $this->b, chr(0)); // step 1 + $temp = $this->ipad ^ $key; // step 2 + $temp .= $text; // step 3 + $temp = call_user_func($this->hash, $temp); // step 4 + $output = $this->opad ^ $key; // step 5 + $output.= $temp; // step 6 + $output = call_user_func($this->hash, $output); // step 7 + } + } else { + switch ( $mode ) { + case CRYPT_HASH_MODE_MHASH: + $output = mhash($this->hash, $text); + break; + case CRYPT_HASH_MODE_HASH: + $output = hash($this->hash, $text, true); + break; + case CRYPT_HASH_MODE_INTERNAL: + $output = call_user_func($this->hash, $text); + } + } + + return substr($output, 0, $this->l); + } + + /** + * Returns the hash length (in bytes) + * + * @access public + * @return Integer + */ + function getLength() + { + return $this->l; + } + + /** + * Wrapper for MD5 + * + * @access private + * @param String $text + */ + function _md5($m) + { + return pack('H*', md5($m)); + } + + /** + * Wrapper for SHA1 + * + * @access private + * @param String $text + */ + function _sha1($m) + { + return pack('H*', sha1($m)); + } + + /** + * Pure-PHP implementation of MD2 + * + * See {@link http://tools.ietf.org/html/rfc1319 RFC1319}. + * + * @access private + * @param String $text + */ + function _md2($m) + { + static $s = array( + 41, 46, 67, 201, 162, 216, 124, 1, 61, 54, 84, 161, 236, 240, 6, + 19, 98, 167, 5, 243, 192, 199, 115, 140, 152, 147, 43, 217, 188, + 76, 130, 202, 30, 155, 87, 60, 253, 212, 224, 22, 103, 66, 111, 24, + 138, 23, 229, 18, 190, 78, 196, 214, 218, 158, 222, 73, 160, 251, + 245, 142, 187, 47, 238, 122, 169, 104, 121, 145, 21, 178, 7, 63, + 148, 194, 16, 137, 11, 34, 95, 33, 128, 127, 93, 154, 90, 144, 50, + 39, 53, 62, 204, 231, 191, 247, 151, 3, 255, 25, 48, 179, 72, 165, + 181, 209, 215, 94, 146, 42, 172, 86, 170, 198, 79, 184, 56, 210, + 150, 164, 125, 182, 118, 252, 107, 226, 156, 116, 4, 241, 69, 157, + 112, 89, 100, 113, 135, 32, 134, 91, 207, 101, 230, 45, 168, 2, 27, + 96, 37, 173, 174, 176, 185, 246, 28, 70, 97, 105, 52, 64, 126, 15, + 85, 71, 163, 35, 221, 81, 175, 58, 195, 92, 249, 206, 186, 197, + 234, 38, 44, 83, 13, 110, 133, 40, 132, 9, 211, 223, 205, 244, 65, + 129, 77, 82, 106, 220, 55, 200, 108, 193, 171, 250, 36, 225, 123, + 8, 12, 189, 177, 74, 120, 136, 149, 139, 227, 99, 232, 109, 233, + 203, 213, 254, 59, 0, 29, 57, 242, 239, 183, 14, 102, 88, 208, 228, + 166, 119, 114, 248, 235, 117, 75, 10, 49, 68, 80, 180, 143, 237, + 31, 26, 219, 153, 141, 51, 159, 17, 131, 20 + ); + + // Step 1. Append Padding Bytes + $pad = 16 - (strlen($m) & 0xF); + $m.= str_repeat(chr($pad), $pad); + + $length = strlen($m); + + // Step 2. Append Checksum + $c = str_repeat(chr(0), 16); + $l = chr(0); + for ($i = 0; $i < $length; $i+= 16) { + for ($j = 0; $j < 16; $j++) { + // RFC1319 incorrectly states that C[j] should be set to S[c xor L] + //$c[$j] = chr($s[ord($m[$i + $j] ^ $l)]); + // per , however, C[j] should be set to S[c xor L] xor C[j] + $c[$j] = chr($s[ord($m[$i + $j] ^ $l)] ^ ord($c[$j])); + $l = $c[$j]; + } + } + $m.= $c; + + $length+= 16; + + // Step 3. Initialize MD Buffer + $x = str_repeat(chr(0), 48); + + // Step 4. Process Message in 16-Byte Blocks + for ($i = 0; $i < $length; $i+= 16) { + for ($j = 0; $j < 16; $j++) { + $x[$j + 16] = $m[$i + $j]; + $x[$j + 32] = $x[$j + 16] ^ $x[$j]; + } + $t = chr(0); + for ($j = 0; $j < 18; $j++) { + for ($k = 0; $k < 48; $k++) { + $x[$k] = $t = $x[$k] ^ chr($s[ord($t)]); + //$t = $x[$k] = $x[$k] ^ chr($s[ord($t)]); + } + $t = chr(ord($t) + $j); + } + } + + // Step 5. Output + return substr($x, 0, 16); + } + + /** + * Pure-PHP implementation of SHA256 + * + * See {@link http://en.wikipedia.org/wiki/SHA_hash_functions#SHA-256_.28a_SHA-2_variant.29_pseudocode SHA-256 (a SHA-2 variant) pseudocode - Wikipedia}. + * + * @access private + * @param String $text + */ + function _sha256($m) + { + if (extension_loaded('suhosin')) { + return pack('H*', sha256($m)); + } + + // Initialize variables + $hash = array( + 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 + ); + // Initialize table of round constants + // (first 32 bits of the fractional parts of the cube roots of the first 64 primes 2..311) + static $k = array( + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 + ); + + // Pre-processing + $length = strlen($m); + // to round to nearest 56 mod 64, we'll add 64 - (length + (64 - 56)) % 64 + $m.= str_repeat(chr(0), 64 - (($length + 8) & 0x3F)); + $m[$length] = chr(0x80); + // we don't support hashing strings 512MB long + $m.= pack('N2', 0, $length << 3); + + // Process the message in successive 512-bit chunks + $chunks = str_split($m, 64); + foreach ($chunks as $chunk) { + $w = array(); + for ($i = 0; $i < 16; $i++) { + extract(unpack('Ntemp', $this->_string_shift($chunk, 4))); + $w[] = $temp; + } + + // Extend the sixteen 32-bit words into sixty-four 32-bit words + for ($i = 16; $i < 64; $i++) { + $s0 = $this->_rightRotate($w[$i - 15], 7) ^ + $this->_rightRotate($w[$i - 15], 18) ^ + $this->_rightShift( $w[$i - 15], 3); + $s1 = $this->_rightRotate($w[$i - 2], 17) ^ + $this->_rightRotate($w[$i - 2], 19) ^ + $this->_rightShift( $w[$i - 2], 10); + $w[$i] = $this->_add($w[$i - 16], $s0, $w[$i - 7], $s1); + + } + + // Initialize hash value for this chunk + list($a, $b, $c, $d, $e, $f, $g, $h) = $hash; + + // Main loop + for ($i = 0; $i < 64; $i++) { + $s0 = $this->_rightRotate($a, 2) ^ + $this->_rightRotate($a, 13) ^ + $this->_rightRotate($a, 22); + $maj = ($a & $b) ^ + ($a & $c) ^ + ($b & $c); + $t2 = $this->_add($s0, $maj); + + $s1 = $this->_rightRotate($e, 6) ^ + $this->_rightRotate($e, 11) ^ + $this->_rightRotate($e, 25); + $ch = ($e & $f) ^ + ($this->_not($e) & $g); + $t1 = $this->_add($h, $s1, $ch, $k[$i], $w[$i]); + + $h = $g; + $g = $f; + $f = $e; + $e = $this->_add($d, $t1); + $d = $c; + $c = $b; + $b = $a; + $a = $this->_add($t1, $t2); + } + + // Add this chunk's hash to result so far + $hash = array( + $this->_add($hash[0], $a), + $this->_add($hash[1], $b), + $this->_add($hash[2], $c), + $this->_add($hash[3], $d), + $this->_add($hash[4], $e), + $this->_add($hash[5], $f), + $this->_add($hash[6], $g), + $this->_add($hash[7], $h) + ); + } + + // Produce the final hash value (big-endian) + return pack('N8', $hash[0], $hash[1], $hash[2], $hash[3], $hash[4], $hash[5], $hash[6], $hash[7]); + } + + /** + * Pure-PHP implementation of SHA384 and SHA512 + * + * @access private + * @param String $text + */ + function _sha512($m) + { + if (!class_exists('Math_BigInteger')) { + require_once('Math/BigInteger.php'); + } + + static $init384, $init512, $k; + + if (!isset($k)) { + // Initialize variables + $init384 = array( // initial values for SHA384 + 'cbbb9d5dc1059ed8', '629a292a367cd507', '9159015a3070dd17', '152fecd8f70e5939', + '67332667ffc00b31', '8eb44a8768581511', 'db0c2e0d64f98fa7', '47b5481dbefa4fa4' + ); + $init512 = array( // initial values for SHA512 + '6a09e667f3bcc908', 'bb67ae8584caa73b', '3c6ef372fe94f82b', 'a54ff53a5f1d36f1', + '510e527fade682d1', '9b05688c2b3e6c1f', '1f83d9abfb41bd6b', '5be0cd19137e2179' + ); + + for ($i = 0; $i < 8; $i++) { + $init384[$i] = new Math_BigInteger($init384[$i], 16); + $init384[$i]->setPrecision(64); + $init512[$i] = new Math_BigInteger($init512[$i], 16); + $init512[$i]->setPrecision(64); + } + + // Initialize table of round constants + // (first 64 bits of the fractional parts of the cube roots of the first 80 primes 2..409) + $k = array( + '428a2f98d728ae22', '7137449123ef65cd', 'b5c0fbcfec4d3b2f', 'e9b5dba58189dbbc', + '3956c25bf348b538', '59f111f1b605d019', '923f82a4af194f9b', 'ab1c5ed5da6d8118', + 'd807aa98a3030242', '12835b0145706fbe', '243185be4ee4b28c', '550c7dc3d5ffb4e2', + '72be5d74f27b896f', '80deb1fe3b1696b1', '9bdc06a725c71235', 'c19bf174cf692694', + 'e49b69c19ef14ad2', 'efbe4786384f25e3', '0fc19dc68b8cd5b5', '240ca1cc77ac9c65', + '2de92c6f592b0275', '4a7484aa6ea6e483', '5cb0a9dcbd41fbd4', '76f988da831153b5', + '983e5152ee66dfab', 'a831c66d2db43210', 'b00327c898fb213f', 'bf597fc7beef0ee4', + 'c6e00bf33da88fc2', 'd5a79147930aa725', '06ca6351e003826f', '142929670a0e6e70', + '27b70a8546d22ffc', '2e1b21385c26c926', '4d2c6dfc5ac42aed', '53380d139d95b3df', + '650a73548baf63de', '766a0abb3c77b2a8', '81c2c92e47edaee6', '92722c851482353b', + 'a2bfe8a14cf10364', 'a81a664bbc423001', 'c24b8b70d0f89791', 'c76c51a30654be30', + 'd192e819d6ef5218', 'd69906245565a910', 'f40e35855771202a', '106aa07032bbd1b8', + '19a4c116b8d2d0c8', '1e376c085141ab53', '2748774cdf8eeb99', '34b0bcb5e19b48a8', + '391c0cb3c5c95a63', '4ed8aa4ae3418acb', '5b9cca4f7763e373', '682e6ff3d6b2b8a3', + '748f82ee5defb2fc', '78a5636f43172f60', '84c87814a1f0ab72', '8cc702081a6439ec', + '90befffa23631e28', 'a4506cebde82bde9', 'bef9a3f7b2c67915', 'c67178f2e372532b', + 'ca273eceea26619c', 'd186b8c721c0c207', 'eada7dd6cde0eb1e', 'f57d4f7fee6ed178', + '06f067aa72176fba', '0a637dc5a2c898a6', '113f9804bef90dae', '1b710b35131c471b', + '28db77f523047d84', '32caab7b40c72493', '3c9ebe0a15c9bebc', '431d67c49c100d4c', + '4cc5d4becb3e42b6', '597f299cfc657e2a', '5fcb6fab3ad6faec', '6c44198c4a475817' + ); + + for ($i = 0; $i < 80; $i++) { + $k[$i] = new Math_BigInteger($k[$i], 16); + } + } + + $hash = $this->l == 48 ? $init384 : $init512; + + // Pre-processing + $length = strlen($m); + // to round to nearest 112 mod 128, we'll add 128 - (length + (128 - 112)) % 128 + $m.= str_repeat(chr(0), 128 - (($length + 16) & 0x7F)); + $m[$length] = chr(0x80); + // we don't support hashing strings 512MB long + $m.= pack('N4', 0, 0, 0, $length << 3); + + // Process the message in successive 1024-bit chunks + $chunks = str_split($m, 128); + foreach ($chunks as $chunk) { + $w = array(); + for ($i = 0; $i < 16; $i++) { + $temp = new Math_BigInteger($this->_string_shift($chunk, 8), 256); + $temp->setPrecision(64); + $w[] = $temp; + } + + // Extend the sixteen 32-bit words into eighty 32-bit words + for ($i = 16; $i < 80; $i++) { + $temp = array( + $w[$i - 15]->bitwise_rightRotate(1), + $w[$i - 15]->bitwise_rightRotate(8), + $w[$i - 15]->bitwise_rightShift(7) + ); + $s0 = $temp[0]->bitwise_xor($temp[1]); + $s0 = $s0->bitwise_xor($temp[2]); + $temp = array( + $w[$i - 2]->bitwise_rightRotate(19), + $w[$i - 2]->bitwise_rightRotate(61), + $w[$i - 2]->bitwise_rightShift(6) + ); + $s1 = $temp[0]->bitwise_xor($temp[1]); + $s1 = $s1->bitwise_xor($temp[2]); + $w[$i] = $w[$i - 16]->copy(); + $w[$i] = $w[$i]->add($s0); + $w[$i] = $w[$i]->add($w[$i - 7]); + $w[$i] = $w[$i]->add($s1); + } + + // Initialize hash value for this chunk + $a = $hash[0]->copy(); + $b = $hash[1]->copy(); + $c = $hash[2]->copy(); + $d = $hash[3]->copy(); + $e = $hash[4]->copy(); + $f = $hash[5]->copy(); + $g = $hash[6]->copy(); + $h = $hash[7]->copy(); + + // Main loop + for ($i = 0; $i < 80; $i++) { + $temp = array( + $a->bitwise_rightRotate(28), + $a->bitwise_rightRotate(34), + $a->bitwise_rightRotate(39) + ); + $s0 = $temp[0]->bitwise_xor($temp[1]); + $s0 = $s0->bitwise_xor($temp[2]); + $temp = array( + $a->bitwise_and($b), + $a->bitwise_and($c), + $b->bitwise_and($c) + ); + $maj = $temp[0]->bitwise_xor($temp[1]); + $maj = $maj->bitwise_xor($temp[2]); + $t2 = $s0->add($maj); + + $temp = array( + $e->bitwise_rightRotate(14), + $e->bitwise_rightRotate(18), + $e->bitwise_rightRotate(41) + ); + $s1 = $temp[0]->bitwise_xor($temp[1]); + $s1 = $s1->bitwise_xor($temp[2]); + $temp = array( + $e->bitwise_and($f), + $g->bitwise_and($e->bitwise_not()) + ); + $ch = $temp[0]->bitwise_xor($temp[1]); + $t1 = $h->add($s1); + $t1 = $t1->add($ch); + $t1 = $t1->add($k[$i]); + $t1 = $t1->add($w[$i]); + + $h = $g->copy(); + $g = $f->copy(); + $f = $e->copy(); + $e = $d->add($t1); + $d = $c->copy(); + $c = $b->copy(); + $b = $a->copy(); + $a = $t1->add($t2); + } + + // Add this chunk's hash to result so far + $hash = array( + $hash[0]->add($a), + $hash[1]->add($b), + $hash[2]->add($c), + $hash[3]->add($d), + $hash[4]->add($e), + $hash[5]->add($f), + $hash[6]->add($g), + $hash[7]->add($h) + ); + } + + // Produce the final hash value (big-endian) + // (Crypt_Hash::hash() trims the output for hashes but not for HMACs. as such, we trim the output here) + $temp = $hash[0]->toBytes() . $hash[1]->toBytes() . $hash[2]->toBytes() . $hash[3]->toBytes() . + $hash[4]->toBytes() . $hash[5]->toBytes(); + if ($this->l != 48) { + $temp.= $hash[6]->toBytes() . $hash[7]->toBytes(); + } + + return $temp; + } + + /** + * Right Rotate + * + * @access private + * @param Integer $int + * @param Integer $amt + * @see _sha256() + * @return Integer + */ + function _rightRotate($int, $amt) + { + $invamt = 32 - $amt; + $mask = (1 << $invamt) - 1; + return (($int << $invamt) & 0xFFFFFFFF) | (($int >> $amt) & $mask); + } + + /** + * Right Shift + * + * @access private + * @param Integer $int + * @param Integer $amt + * @see _sha256() + * @return Integer + */ + function _rightShift($int, $amt) + { + $mask = (1 << (32 - $amt)) - 1; + return ($int >> $amt) & $mask; + } + + /** + * Not + * + * @access private + * @param Integer $int + * @see _sha256() + * @return Integer + */ + function _not($int) + { + return ~$int & 0xFFFFFFFF; + } + + /** + * Add + * + * _sha256() adds multiple unsigned 32-bit integers. Since PHP doesn't support unsigned integers and since the + * possibility of overflow exists, care has to be taken. Math_BigInteger() could be used but this should be faster. + * + * @param String $string + * @param optional Integer $index + * @return String + * @see _sha256() + * @access private + */ + function _add() + { + static $mod; + if (!isset($mod)) { + $mod = pow(2, 32); + } + + $result = 0; + $arguments = func_get_args(); + foreach ($arguments as $argument) { + $result+= $argument < 0 ? ($argument & 0x7FFFFFFF) + 0x80000000 : $argument; + } + + return fmod($result, $mod); + } + + /** + * String Shift + * + * Inspired by array_shift + * + * @param String $string + * @param optional Integer $index + * @return String + * @access private + */ + function _string_shift(&$string, $index = 1) + { + $substr = substr($string, 0, $index); + $string = substr($string, $index); + return $substr; + } +} diff --git a/3rdparty/phpseclib/Crypt/RC4.php b/3rdparty/phpseclib/Crypt/RC4.php new file mode 100644 index 00000000000..7405c11fa54 --- /dev/null +++ b/3rdparty/phpseclib/Crypt/RC4.php @@ -0,0 +1,531 @@ + + * setKey('abcdefgh'); + * + * $size = 10 * 1024; + * $plaintext = ''; + * for ($i = 0; $i < $size; $i++) { + * $plaintext.= 'a'; + * } + * + * echo $rc4->decrypt($rc4->encrypt($plaintext)); + * ?> + * + * + * LICENSE: Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @category Crypt + * @package Crypt_RC4 + * @author Jim Wigginton + * @copyright MMVII Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @version $Id: RC4.php,v 1.8 2009/06/09 04:00:38 terrafrost Exp $ + * @link http://phpseclib.sourceforge.net + */ + +/**#@+ + * @access private + * @see Crypt_RC4::Crypt_RC4() + */ +/** + * Toggles the internal implementation + */ +define('CRYPT_RC4_MODE_INTERNAL', 1); +/** + * Toggles the mcrypt implementation + */ +define('CRYPT_RC4_MODE_MCRYPT', 2); +/**#@-*/ + +/**#@+ + * @access private + * @see Crypt_RC4::_crypt() + */ +define('CRYPT_RC4_ENCRYPT', 0); +define('CRYPT_RC4_DECRYPT', 1); +/**#@-*/ + +/** + * Pure-PHP implementation of RC4. + * + * @author Jim Wigginton + * @version 0.1.0 + * @access public + * @package Crypt_RC4 + */ +class Crypt_RC4 { + /** + * The Key + * + * @see Crypt_RC4::setKey() + * @var String + * @access private + */ + var $key = "\0"; + + /** + * The Key Stream for encryption + * + * If CRYPT_RC4_MODE == CRYPT_RC4_MODE_MCRYPT, this will be equal to the mcrypt object + * + * @see Crypt_RC4::setKey() + * @var Array + * @access private + */ + var $encryptStream = false; + + /** + * The Key Stream for decryption + * + * If CRYPT_RC4_MODE == CRYPT_RC4_MODE_MCRYPT, this will be equal to the mcrypt object + * + * @see Crypt_RC4::setKey() + * @var Array + * @access private + */ + var $decryptStream = false; + + /** + * The $i and $j indexes for encryption + * + * @see Crypt_RC4::_crypt() + * @var Integer + * @access private + */ + var $encryptIndex = 0; + + /** + * The $i and $j indexes for decryption + * + * @see Crypt_RC4::_crypt() + * @var Integer + * @access private + */ + var $decryptIndex = 0; + + /** + * The Encryption Algorithm + * + * Only used if CRYPT_RC4_MODE == CRYPT_RC4_MODE_MCRYPT. Only possible values are MCRYPT_RC4 or MCRYPT_ARCFOUR. + * + * @see Crypt_RC4::Crypt_RC4() + * @var Integer + * @access private + */ + var $mode; + + /** + * Continuous Buffer status + * + * @see Crypt_RC4::enableContinuousBuffer() + * @var Boolean + * @access private + */ + var $continuousBuffer = false; + + /** + * Default Constructor. + * + * Determines whether or not the mcrypt extension should be used. + * + * @param optional Integer $mode + * @return Crypt_RC4 + * @access public + */ + function Crypt_RC4() + { + if ( !defined('CRYPT_RC4_MODE') ) { + switch (true) { + case extension_loaded('mcrypt') && (defined('MCRYPT_ARCFOUR') || defined('MCRYPT_RC4')) && in_array('arcfour', mcrypt_list_algorithms()): + define('CRYPT_RC4_MODE', CRYPT_RC4_MODE_MCRYPT); + break; + default: + define('CRYPT_RC4_MODE', CRYPT_RC4_MODE_INTERNAL); + } + } + + switch ( CRYPT_RC4_MODE ) { + case CRYPT_RC4_MODE_MCRYPT: + switch (true) { + case defined('MCRYPT_ARCFOUR'): + $this->mode = MCRYPT_ARCFOUR; + break; + case defined('MCRYPT_RC4'); + $this->mode = MCRYPT_RC4; + } + } + } + + /** + * Sets the key. + * + * Keys can be between 1 and 256 bytes long. If they are longer then 256 bytes, the first 256 bytes will + * be used. If no key is explicitly set, it'll be assumed to be a single null byte. + * + * @access public + * @param String $key + */ + function setKey($key) + { + $this->key = $key; + + if ( CRYPT_RC4_MODE == CRYPT_RC4_MODE_MCRYPT ) { + return; + } + + $keyLength = strlen($key); + $keyStream = array(); + for ($i = 0; $i < 256; $i++) { + $keyStream[$i] = $i; + } + $j = 0; + for ($i = 0; $i < 256; $i++) { + $j = ($j + $keyStream[$i] + ord($key[$i % $keyLength])) & 255; + $temp = $keyStream[$i]; + $keyStream[$i] = $keyStream[$j]; + $keyStream[$j] = $temp; + } + + $this->encryptIndex = $this->decryptIndex = array(0, 0); + $this->encryptStream = $this->decryptStream = $keyStream; + } + + /** + * Sets the password. + * + * Depending on what $method is set to, setPassword()'s (optional) parameters are as follows: + * {@link http://en.wikipedia.org/wiki/PBKDF2 pbkdf2}: + * $hash, $salt, $count, $dkLen + * + * @param String $password + * @param optional String $method + * @access public + */ + function setPassword($password, $method = 'pbkdf2') + { + $key = ''; + + switch ($method) { + default: // 'pbkdf2' + list(, , $hash, $salt, $count) = func_get_args(); + if (!isset($hash)) { + $hash = 'sha1'; + } + // WPA and WPA use the SSID as the salt + if (!isset($salt)) { + $salt = 'phpseclib/salt'; + } + // RFC2898#section-4.2 uses 1,000 iterations by default + // WPA and WPA2 use 4,096. + if (!isset($count)) { + $count = 1000; + } + if (!isset($dkLen)) { + $dkLen = 128; + } + + if (!class_exists('Crypt_Hash')) { + require_once('Crypt/Hash.php'); + } + + $i = 1; + while (strlen($key) < $dkLen) { + //$dk.= $this->_pbkdf($password, $salt, $count, $i++); + $hmac = new Crypt_Hash(); + $hmac->setHash($hash); + $hmac->setKey($password); + $f = $u = $hmac->hash($salt . pack('N', $i++)); + for ($j = 2; $j <= $count; $j++) { + $u = $hmac->hash($u); + $f^= $u; + } + $key.= $f; + } + } + + $this->setKey(substr($key, 0, $dkLen)); + } + + /** + * Dummy function. + * + * Some protocols, such as WEP, prepend an "initialization vector" to the key, effectively creating a new key [1]. + * If you need to use an initialization vector in this manner, feel free to prepend it to the key, yourself, before + * calling setKey(). + * + * [1] WEP's initialization vectors (IV's) are used in a somewhat insecure way. Since, in that protocol, + * the IV's are relatively easy to predict, an attack described by + * {@link http://www.drizzle.com/~aboba/IEEE/rc4_ksaproc.pdf Scott Fluhrer, Itsik Mantin, and Adi Shamir} + * can be used to quickly guess at the rest of the key. The following links elaborate: + * + * {@link http://www.rsa.com/rsalabs/node.asp?id=2009 http://www.rsa.com/rsalabs/node.asp?id=2009} + * {@link http://en.wikipedia.org/wiki/Related_key_attack http://en.wikipedia.org/wiki/Related_key_attack} + * + * @param String $iv + * @see Crypt_RC4::setKey() + * @access public + */ + function setIV($iv) + { + } + + /** + * Encrypts a message. + * + * @see Crypt_RC4::_crypt() + * @access public + * @param String $plaintext + */ + function encrypt($plaintext) + { + return $this->_crypt($plaintext, CRYPT_RC4_ENCRYPT); + } + + /** + * Decrypts a message. + * + * $this->decrypt($this->encrypt($plaintext)) == $this->encrypt($this->encrypt($plaintext)). + * Atleast if the continuous buffer is disabled. + * + * @see Crypt_RC4::_crypt() + * @access public + * @param String $ciphertext + */ + function decrypt($ciphertext) + { + return $this->_crypt($ciphertext, CRYPT_RC4_DECRYPT); + } + + /** + * Encrypts or decrypts a message. + * + * @see Crypt_RC4::encrypt() + * @see Crypt_RC4::decrypt() + * @access private + * @param String $text + * @param Integer $mode + */ + function _crypt($text, $mode) + { + if ( CRYPT_RC4_MODE == CRYPT_RC4_MODE_MCRYPT ) { + $keyStream = $mode == CRYPT_RC4_ENCRYPT ? 'encryptStream' : 'decryptStream'; + + if ($this->$keyStream === false) { + $this->$keyStream = mcrypt_module_open($this->mode, '', MCRYPT_MODE_STREAM, ''); + mcrypt_generic_init($this->$keyStream, $this->key, ''); + } else if (!$this->continuousBuffer) { + mcrypt_generic_init($this->$keyStream, $this->key, ''); + } + $newText = mcrypt_generic($this->$keyStream, $text); + if (!$this->continuousBuffer) { + mcrypt_generic_deinit($this->$keyStream); + } + + return $newText; + } + + if ($this->encryptStream === false) { + $this->setKey($this->key); + } + + switch ($mode) { + case CRYPT_RC4_ENCRYPT: + $keyStream = $this->encryptStream; + list($i, $j) = $this->encryptIndex; + break; + case CRYPT_RC4_DECRYPT: + $keyStream = $this->decryptStream; + list($i, $j) = $this->decryptIndex; + } + + $newText = ''; + for ($k = 0; $k < strlen($text); $k++) { + $i = ($i + 1) & 255; + $j = ($j + $keyStream[$i]) & 255; + $temp = $keyStream[$i]; + $keyStream[$i] = $keyStream[$j]; + $keyStream[$j] = $temp; + $temp = $keyStream[($keyStream[$i] + $keyStream[$j]) & 255]; + $newText.= chr(ord($text[$k]) ^ $temp); + } + + if ($this->continuousBuffer) { + switch ($mode) { + case CRYPT_RC4_ENCRYPT: + $this->encryptStream = $keyStream; + $this->encryptIndex = array($i, $j); + break; + case CRYPT_RC4_DECRYPT: + $this->decryptStream = $keyStream; + $this->decryptIndex = array($i, $j); + } + } + + return $newText; + } + + /** + * Treat consecutive "packets" as if they are a continuous buffer. + * + * Say you have a 16-byte plaintext $plaintext. Using the default behavior, the two following code snippets + * will yield different outputs: + * + * + * echo $rc4->encrypt(substr($plaintext, 0, 8)); + * echo $rc4->encrypt(substr($plaintext, 8, 8)); + * + * + * echo $rc4->encrypt($plaintext); + * + * + * The solution is to enable the continuous buffer. Although this will resolve the above discrepancy, it creates + * another, as demonstrated with the following: + * + * + * $rc4->encrypt(substr($plaintext, 0, 8)); + * echo $rc4->decrypt($des->encrypt(substr($plaintext, 8, 8))); + * + * + * echo $rc4->decrypt($des->encrypt(substr($plaintext, 8, 8))); + * + * + * With the continuous buffer disabled, these would yield the same output. With it enabled, they yield different + * outputs. The reason is due to the fact that the initialization vector's change after every encryption / + * decryption round when the continuous buffer is enabled. When it's disabled, they remain constant. + * + * Put another way, when the continuous buffer is enabled, the state of the Crypt_DES() object changes after each + * encryption / decryption round, whereas otherwise, it'd remain constant. For this reason, it's recommended that + * continuous buffers not be used. They do offer better security and are, in fact, sometimes required (SSH uses them), + * however, they are also less intuitive and more likely to cause you problems. + * + * @see Crypt_RC4::disableContinuousBuffer() + * @access public + */ + function enableContinuousBuffer() + { + $this->continuousBuffer = true; + } + + /** + * Treat consecutive packets as if they are a discontinuous buffer. + * + * The default behavior. + * + * @see Crypt_RC4::enableContinuousBuffer() + * @access public + */ + function disableContinuousBuffer() + { + if ( CRYPT_RC4_MODE == CRYPT_RC4_MODE_INTERNAL ) { + $this->encryptIndex = $this->decryptIndex = array(0, 0); + $this->setKey($this->key); + } + + $this->continuousBuffer = false; + } + + /** + * Dummy function. + * + * Since RC4 is a stream cipher and not a block cipher, no padding is necessary. The only reason this function is + * included is so that you can switch between a block cipher and a stream cipher transparently. + * + * @see Crypt_RC4::disablePadding() + * @access public + */ + function enablePadding() + { + } + + /** + * Dummy function. + * + * @see Crypt_RC4::enablePadding() + * @access public + */ + function disablePadding() + { + } + + /** + * Class destructor. + * + * Will be called, automatically, if you're using PHP5. If you're using PHP4, call it yourself. Only really + * needs to be called if mcrypt is being used. + * + * @access public + */ + function __destruct() + { + if ( CRYPT_RC4_MODE == CRYPT_RC4_MODE_MCRYPT ) { + $this->_closeMCrypt(); + } + } + + /** + * Properly close the MCrypt objects. + * + * @access prviate + */ + function _closeMCrypt() + { + if ( $this->encryptStream !== false ) { + if ( $this->continuousBuffer ) { + mcrypt_generic_deinit($this->encryptStream); + } + + mcrypt_module_close($this->encryptStream); + + $this->encryptStream = false; + } + + if ( $this->decryptStream !== false ) { + if ( $this->continuousBuffer ) { + mcrypt_generic_deinit($this->decryptStream); + } + + mcrypt_module_close($this->decryptStream); + + $this->decryptStream = false; + } + } +} diff --git a/3rdparty/phpseclib/Crypt/RSA.php b/3rdparty/phpseclib/Crypt/RSA.php new file mode 100644 index 00000000000..11a5bfad211 --- /dev/null +++ b/3rdparty/phpseclib/Crypt/RSA.php @@ -0,0 +1,2640 @@ + + * createKey()); + * + * $plaintext = 'terrafrost'; + * + * $rsa->loadKey($privatekey); + * $ciphertext = $rsa->encrypt($plaintext); + * + * $rsa->loadKey($publickey); + * echo $rsa->decrypt($ciphertext); + * ?> + * + * + * Here's an example of how to create signatures and verify signatures with this library: + * + * createKey()); + * + * $plaintext = 'terrafrost'; + * + * $rsa->loadKey($privatekey); + * $signature = $rsa->sign($plaintext); + * + * $rsa->loadKey($publickey); + * echo $rsa->verify($plaintext, $signature) ? 'verified' : 'unverified'; + * ?> + * + * + * LICENSE: Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @category Crypt + * @package Crypt_RSA + * @author Jim Wigginton + * @copyright MMIX Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @version $Id: RSA.php,v 1.19 2010/09/12 21:58:54 terrafrost Exp $ + * @link http://phpseclib.sourceforge.net + */ + +/** + * Include Math_BigInteger + */ +if (!class_exists('Math_BigInteger')) { + require_once('Math/BigInteger.php'); +} + +/** + * Include Crypt_Random + */ +// the class_exists() will only be called if the crypt_random function hasn't been defined and +// will trigger a call to __autoload() if you're wanting to auto-load classes +// call function_exists() a second time to stop the require_once from being called outside +// of the auto loader +if (!function_exists('crypt_random') && !class_exists('Crypt_Random') && !function_exists('crypt_random')) { + require_once('Crypt/Random.php'); +} + +/** + * Include Crypt_Hash + */ +if (!class_exists('Crypt_Hash')) { + require_once('Crypt/Hash.php'); +} + +/**#@+ + * @access public + * @see Crypt_RSA::encrypt() + * @see Crypt_RSA::decrypt() + */ +/** + * Use {@link http://en.wikipedia.org/wiki/Optimal_Asymmetric_Encryption_Padding Optimal Asymmetric Encryption Padding} + * (OAEP) for encryption / decryption. + * + * Uses sha1 by default. + * + * @see Crypt_RSA::setHash() + * @see Crypt_RSA::setMGFHash() + */ +define('CRYPT_RSA_ENCRYPTION_OAEP', 1); +/** + * Use PKCS#1 padding. + * + * Although CRYPT_RSA_ENCRYPTION_OAEP offers more security, including PKCS#1 padding is necessary for purposes of backwards + * compatability with protocols (like SSH-1) written before OAEP's introduction. + */ +define('CRYPT_RSA_ENCRYPTION_PKCS1', 2); +/**#@-*/ + +/**#@+ + * @access public + * @see Crypt_RSA::sign() + * @see Crypt_RSA::verify() + * @see Crypt_RSA::setHash() + */ +/** + * Use the Probabilistic Signature Scheme for signing + * + * Uses sha1 by default. + * + * @see Crypt_RSA::setSaltLength() + * @see Crypt_RSA::setMGFHash() + */ +define('CRYPT_RSA_SIGNATURE_PSS', 1); +/** + * Use the PKCS#1 scheme by default. + * + * Although CRYPT_RSA_SIGNATURE_PSS offers more security, including PKCS#1 signing is necessary for purposes of backwards + * compatability with protocols (like SSH-2) written before PSS's introduction. + */ +define('CRYPT_RSA_SIGNATURE_PKCS1', 2); +/**#@-*/ + +/**#@+ + * @access private + * @see Crypt_RSA::createKey() + */ +/** + * ASN1 Integer + */ +define('CRYPT_RSA_ASN1_INTEGER', 2); +/** + * ASN1 Bit String + */ +define('CRYPT_RSA_ASN1_BITSTRING', 3); +/** + * ASN1 Sequence (with the constucted bit set) + */ +define('CRYPT_RSA_ASN1_SEQUENCE', 48); +/**#@-*/ + +/**#@+ + * @access private + * @see Crypt_RSA::Crypt_RSA() + */ +/** + * To use the pure-PHP implementation + */ +define('CRYPT_RSA_MODE_INTERNAL', 1); +/** + * To use the OpenSSL library + * + * (if enabled; otherwise, the internal implementation will be used) + */ +define('CRYPT_RSA_MODE_OPENSSL', 2); +/**#@-*/ + +/**#@+ + * @access public + * @see Crypt_RSA::createKey() + * @see Crypt_RSA::setPrivateKeyFormat() + */ +/** + * PKCS#1 formatted private key + * + * Used by OpenSSH + */ +define('CRYPT_RSA_PRIVATE_FORMAT_PKCS1', 0); +/** + * PuTTY formatted private key + */ +define('CRYPT_RSA_PRIVATE_FORMAT_PUTTY', 1); +/** + * XML formatted private key + */ +define('CRYPT_RSA_PRIVATE_FORMAT_XML', 2); +/**#@-*/ + +/**#@+ + * @access public + * @see Crypt_RSA::createKey() + * @see Crypt_RSA::setPublicKeyFormat() + */ +/** + * Raw public key + * + * An array containing two Math_BigInteger objects. + * + * The exponent can be indexed with any of the following: + * + * 0, e, exponent, publicExponent + * + * The modulus can be indexed with any of the following: + * + * 1, n, modulo, modulus + */ +define('CRYPT_RSA_PUBLIC_FORMAT_RAW', 3); +/** + * PKCS#1 formatted public key (raw) + * + * Used by File/X509.php + */ +define('CRYPT_RSA_PUBLIC_FORMAT_PKCS1_RAW', 4); +/** + * XML formatted public key + */ +define('CRYPT_RSA_PUBLIC_FORMAT_XML', 5); +/** + * OpenSSH formatted public key + * + * Place in $HOME/.ssh/authorized_keys + */ +define('CRYPT_RSA_PUBLIC_FORMAT_OPENSSH', 6); +/** + * PKCS#1 formatted public key (encapsulated) + * + * Used by PHP's openssl_public_encrypt() and openssl's rsautl (when -pubin is set) + */ +define('CRYPT_RSA_PUBLIC_FORMAT_PKCS1', 7); +/**#@-*/ + +/** + * Pure-PHP PKCS#1 compliant implementation of RSA. + * + * @author Jim Wigginton + * @version 0.1.0 + * @access public + * @package Crypt_RSA + */ +class Crypt_RSA { + /** + * Precomputed Zero + * + * @var Array + * @access private + */ + var $zero; + + /** + * Precomputed One + * + * @var Array + * @access private + */ + var $one; + + /** + * Private Key Format + * + * @var Integer + * @access private + */ + var $privateKeyFormat = CRYPT_RSA_PRIVATE_FORMAT_PKCS1; + + /** + * Public Key Format + * + * @var Integer + * @access public + */ + var $publicKeyFormat = CRYPT_RSA_PUBLIC_FORMAT_PKCS1; + + /** + * Modulus (ie. n) + * + * @var Math_BigInteger + * @access private + */ + var $modulus; + + /** + * Modulus length + * + * @var Math_BigInteger + * @access private + */ + var $k; + + /** + * Exponent (ie. e or d) + * + * @var Math_BigInteger + * @access private + */ + var $exponent; + + /** + * Primes for Chinese Remainder Theorem (ie. p and q) + * + * @var Array + * @access private + */ + var $primes; + + /** + * Exponents for Chinese Remainder Theorem (ie. dP and dQ) + * + * @var Array + * @access private + */ + var $exponents; + + /** + * Coefficients for Chinese Remainder Theorem (ie. qInv) + * + * @var Array + * @access private + */ + var $coefficients; + + /** + * Hash name + * + * @var String + * @access private + */ + var $hashName; + + /** + * Hash function + * + * @var Crypt_Hash + * @access private + */ + var $hash; + + /** + * Length of hash function output + * + * @var Integer + * @access private + */ + var $hLen; + + /** + * Length of salt + * + * @var Integer + * @access private + */ + var $sLen; + + /** + * Hash function for the Mask Generation Function + * + * @var Crypt_Hash + * @access private + */ + var $mgfHash; + + /** + * Length of MGF hash function output + * + * @var Integer + * @access private + */ + var $mgfHLen; + + /** + * Encryption mode + * + * @var Integer + * @access private + */ + var $encryptionMode = CRYPT_RSA_ENCRYPTION_OAEP; + + /** + * Signature mode + * + * @var Integer + * @access private + */ + var $signatureMode = CRYPT_RSA_SIGNATURE_PSS; + + /** + * Public Exponent + * + * @var Mixed + * @access private + */ + var $publicExponent = false; + + /** + * Password + * + * @var String + * @access private + */ + var $password = false; + + /** + * Components + * + * For use with parsing XML formatted keys. PHP's XML Parser functions use utilized - instead of PHP's DOM functions - + * because PHP's XML Parser functions work on PHP4 whereas PHP's DOM functions - although surperior - don't. + * + * @see Crypt_RSA::_start_element_handler() + * @var Array + * @access private + */ + var $components = array(); + + /** + * Current String + * + * For use with parsing XML formatted keys. + * + * @see Crypt_RSA::_character_handler() + * @see Crypt_RSA::_stop_element_handler() + * @var Mixed + * @access private + */ + var $current; + + /** + * The constructor + * + * If you want to make use of the openssl extension, you'll need to set the mode manually, yourself. The reason + * Crypt_RSA doesn't do it is because OpenSSL doesn't fail gracefully. openssl_pkey_new(), in particular, requires + * openssl.cnf be present somewhere and, unfortunately, the only real way to find out is too late. + * + * @return Crypt_RSA + * @access public + */ + function Crypt_RSA() + { + if ( !defined('CRYPT_RSA_MODE') ) { + switch (true) { + case extension_loaded('openssl') && version_compare(PHP_VERSION, '4.2.0', '>='): + define('CRYPT_RSA_MODE', CRYPT_RSA_MODE_OPENSSL); + break; + default: + define('CRYPT_RSA_MODE', CRYPT_RSA_MODE_INTERNAL); + } + } + + if (!defined('CRYPT_RSA_COMMENT')) { + define('CRYPT_RSA_COMMENT', 'phpseclib-generated-key'); + } + + $this->zero = new Math_BigInteger(); + $this->one = new Math_BigInteger(1); + + $this->hash = new Crypt_Hash('sha1'); + $this->hLen = $this->hash->getLength(); + $this->hashName = 'sha1'; + $this->mgfHash = new Crypt_Hash('sha1'); + $this->mgfHLen = $this->mgfHash->getLength(); + } + + /** + * Create public / private key pair + * + * Returns an array with the following three elements: + * - 'privatekey': The private key. + * - 'publickey': The public key. + * - 'partialkey': A partially computed key (if the execution time exceeded $timeout). + * Will need to be passed back to Crypt_RSA::createKey() as the third parameter for further processing. + * + * @access public + * @param optional Integer $bits + * @param optional Integer $timeout + * @param optional Math_BigInteger $p + */ + function createKey($bits = 1024, $timeout = false, $partial = array()) + { + if (!defined('CRYPT_RSA_EXPONENT')) { + // http://en.wikipedia.org/wiki/65537_%28number%29 + define('CRYPT_RSA_EXPONENT', '65537'); + } + // per , this number ought not result in primes smaller + // than 256 bits. as a consequence if the key you're trying to create is 1024 bits and you've set CRYPT_RSA_SMALLEST_PRIME + // to 384 bits then you're going to get a 384 bit prime and a 640 bit prime (384 + 1024 % 384). at least if + // CRYPT_RSA_MODE is set to CRYPT_RSA_MODE_INTERNAL. if CRYPT_RSA_MODE is set to CRYPT_RSA_MODE_OPENSSL then + // CRYPT_RSA_SMALLEST_PRIME is ignored (ie. multi-prime RSA support is more intended as a way to speed up RSA key + // generation when there's a chance neither gmp nor OpenSSL are installed) + if (!defined('CRYPT_RSA_SMALLEST_PRIME')) { + define('CRYPT_RSA_SMALLEST_PRIME', 4096); + } + + // OpenSSL uses 65537 as the exponent and requires RSA keys be 384 bits minimum + if ( CRYPT_RSA_MODE == CRYPT_RSA_MODE_OPENSSL && $bits >= 384 && CRYPT_RSA_EXPONENT == 65537) { + $rsa = openssl_pkey_new(array( + 'private_key_bits' => $bits, + 'config' => dirname(__FILE__) . '/../openssl.cnf' + )); + + openssl_pkey_export($rsa, $privatekey, NULL, array('config' => dirname(__FILE__) . '/../openssl.cnf')); + $publickey = openssl_pkey_get_details($rsa); + $publickey = $publickey['key']; + + $privatekey = call_user_func_array(array($this, '_convertPrivateKey'), array_values($this->_parseKey($privatekey, CRYPT_RSA_PRIVATE_FORMAT_PKCS1))); + $publickey = call_user_func_array(array($this, '_convertPublicKey'), array_values($this->_parseKey($publickey, CRYPT_RSA_PUBLIC_FORMAT_PKCS1))); + + // clear the buffer of error strings stemming from a minimalistic openssl.cnf + while (openssl_error_string() !== false); + + return array( + 'privatekey' => $privatekey, + 'publickey' => $publickey, + 'partialkey' => false + ); + } + + static $e; + if (!isset($e)) { + $e = new Math_BigInteger(CRYPT_RSA_EXPONENT); + } + + extract($this->_generateMinMax($bits)); + $absoluteMin = $min; + $temp = $bits >> 1; // divide by two to see how many bits P and Q would be + if ($temp > CRYPT_RSA_SMALLEST_PRIME) { + $num_primes = floor($bits / CRYPT_RSA_SMALLEST_PRIME); + $temp = CRYPT_RSA_SMALLEST_PRIME; + } else { + $num_primes = 2; + } + extract($this->_generateMinMax($temp + $bits % $temp)); + $finalMax = $max; + extract($this->_generateMinMax($temp)); + + $generator = new Math_BigInteger(); + $generator->setRandomGenerator('crypt_random'); + + $n = $this->one->copy(); + if (!empty($partial)) { + extract(unserialize($partial)); + } else { + $exponents = $coefficients = $primes = array(); + $lcm = array( + 'top' => $this->one->copy(), + 'bottom' => false + ); + } + + $start = time(); + $i0 = count($primes) + 1; + + do { + for ($i = $i0; $i <= $num_primes; $i++) { + if ($timeout !== false) { + $timeout-= time() - $start; + $start = time(); + if ($timeout <= 0) { + return array( + 'privatekey' => '', + 'publickey' => '', + 'partialkey' => serialize(array( + 'primes' => $primes, + 'coefficients' => $coefficients, + 'lcm' => $lcm, + 'exponents' => $exponents + )) + ); + } + } + + if ($i == $num_primes) { + list($min, $temp) = $absoluteMin->divide($n); + if (!$temp->equals($this->zero)) { + $min = $min->add($this->one); // ie. ceil() + } + $primes[$i] = $generator->randomPrime($min, $finalMax, $timeout); + } else { + $primes[$i] = $generator->randomPrime($min, $max, $timeout); + } + + if ($primes[$i] === false) { // if we've reached the timeout + if (count($primes) > 1) { + $partialkey = ''; + } else { + array_pop($primes); + $partialkey = serialize(array( + 'primes' => $primes, + 'coefficients' => $coefficients, + 'lcm' => $lcm, + 'exponents' => $exponents + )); + } + + return array( + 'privatekey' => '', + 'publickey' => '', + 'partialkey' => $partialkey + ); + } + + // the first coefficient is calculated differently from the rest + // ie. instead of being $primes[1]->modInverse($primes[2]), it's $primes[2]->modInverse($primes[1]) + if ($i > 2) { + $coefficients[$i] = $n->modInverse($primes[$i]); + } + + $n = $n->multiply($primes[$i]); + + $temp = $primes[$i]->subtract($this->one); + + // textbook RSA implementations use Euler's totient function instead of the least common multiple. + // see http://en.wikipedia.org/wiki/Euler%27s_totient_function + $lcm['top'] = $lcm['top']->multiply($temp); + $lcm['bottom'] = $lcm['bottom'] === false ? $temp : $lcm['bottom']->gcd($temp); + + $exponents[$i] = $e->modInverse($temp); + } + + list($lcm) = $lcm['top']->divide($lcm['bottom']); + $gcd = $lcm->gcd($e); + $i0 = 1; + } while (!$gcd->equals($this->one)); + + $d = $e->modInverse($lcm); + + $coefficients[2] = $primes[2]->modInverse($primes[1]); + + // from : + // RSAPrivateKey ::= SEQUENCE { + // version Version, + // modulus INTEGER, -- n + // publicExponent INTEGER, -- e + // privateExponent INTEGER, -- d + // prime1 INTEGER, -- p + // prime2 INTEGER, -- q + // exponent1 INTEGER, -- d mod (p-1) + // exponent2 INTEGER, -- d mod (q-1) + // coefficient INTEGER, -- (inverse of q) mod p + // otherPrimeInfos OtherPrimeInfos OPTIONAL + // } + + return array( + 'privatekey' => $this->_convertPrivateKey($n, $e, $d, $primes, $exponents, $coefficients), + 'publickey' => $this->_convertPublicKey($n, $e), + 'partialkey' => false + ); + } + + /** + * Convert a private key to the appropriate format. + * + * @access private + * @see setPrivateKeyFormat() + * @param String $RSAPrivateKey + * @return String + */ + function _convertPrivateKey($n, $e, $d, $primes, $exponents, $coefficients) + { + $num_primes = count($primes); + $raw = array( + 'version' => $num_primes == 2 ? chr(0) : chr(1), // two-prime vs. multi + 'modulus' => $n->toBytes(true), + 'publicExponent' => $e->toBytes(true), + 'privateExponent' => $d->toBytes(true), + 'prime1' => $primes[1]->toBytes(true), + 'prime2' => $primes[2]->toBytes(true), + 'exponent1' => $exponents[1]->toBytes(true), + 'exponent2' => $exponents[2]->toBytes(true), + 'coefficient' => $coefficients[2]->toBytes(true) + ); + + // if the format in question does not support multi-prime rsa and multi-prime rsa was used, + // call _convertPublicKey() instead. + switch ($this->privateKeyFormat) { + case CRYPT_RSA_PRIVATE_FORMAT_XML: + if ($num_primes != 2) { + return false; + } + return "\r\n" . + ' ' . base64_encode($raw['modulus']) . "\r\n" . + ' ' . base64_encode($raw['publicExponent']) . "\r\n" . + '

' . base64_encode($raw['prime1']) . "

\r\n" . + ' ' . base64_encode($raw['prime2']) . "\r\n" . + ' ' . base64_encode($raw['exponent1']) . "\r\n" . + ' ' . base64_encode($raw['exponent2']) . "\r\n" . + ' ' . base64_encode($raw['coefficient']) . "\r\n" . + ' ' . base64_encode($raw['privateExponent']) . "\r\n" . + ''; + break; + case CRYPT_RSA_PRIVATE_FORMAT_PUTTY: + if ($num_primes != 2) { + return false; + } + $key = "PuTTY-User-Key-File-2: ssh-rsa\r\nEncryption: "; + $encryption = (!empty($this->password) || is_string($this->password)) ? 'aes256-cbc' : 'none'; + $key.= $encryption; + $key.= "\r\nComment: " . CRYPT_RSA_COMMENT . "\r\n"; + $public = pack('Na*Na*Na*', + strlen('ssh-rsa'), 'ssh-rsa', strlen($raw['publicExponent']), $raw['publicExponent'], strlen($raw['modulus']), $raw['modulus'] + ); + $source = pack('Na*Na*Na*Na*', + strlen('ssh-rsa'), 'ssh-rsa', strlen($encryption), $encryption, + strlen(CRYPT_RSA_COMMENT), CRYPT_RSA_COMMENT, strlen($public), $public + ); + $public = base64_encode($public); + $key.= "Public-Lines: " . ((strlen($public) + 32) >> 6) . "\r\n"; + $key.= chunk_split($public, 64); + $private = pack('Na*Na*Na*Na*', + strlen($raw['privateExponent']), $raw['privateExponent'], strlen($raw['prime1']), $raw['prime1'], + strlen($raw['prime2']), $raw['prime2'], strlen($raw['coefficient']), $raw['coefficient'] + ); + if (empty($this->password) && !is_string($this->password)) { + $source.= pack('Na*', strlen($private), $private); + $hashkey = 'putty-private-key-file-mac-key'; + } else { + $private.= $this->_random(16 - (strlen($private) & 15)); + $source.= pack('Na*', strlen($private), $private); + if (!class_exists('Crypt_AES')) { + require_once('Crypt/AES.php'); + } + $sequence = 0; + $symkey = ''; + while (strlen($symkey) < 32) { + $temp = pack('Na*', $sequence++, $this->password); + $symkey.= pack('H*', sha1($temp)); + } + $symkey = substr($symkey, 0, 32); + $crypto = new Crypt_AES(); + + $crypto->setKey($symkey); + $crypto->disablePadding(); + $private = $crypto->encrypt($private); + $hashkey = 'putty-private-key-file-mac-key' . $this->password; + } + + $private = base64_encode($private); + $key.= 'Private-Lines: ' . ((strlen($private) + 32) >> 6) . "\r\n"; + $key.= chunk_split($private, 64); + if (!class_exists('Crypt_Hash')) { + require_once('Crypt/Hash.php'); + } + $hash = new Crypt_Hash('sha1'); + $hash->setKey(pack('H*', sha1($hashkey))); + $key.= 'Private-MAC: ' . bin2hex($hash->hash($source)) . "\r\n"; + + return $key; + default: // eg. CRYPT_RSA_PRIVATE_FORMAT_PKCS1 + $components = array(); + foreach ($raw as $name => $value) { + $components[$name] = pack('Ca*a*', CRYPT_RSA_ASN1_INTEGER, $this->_encodeLength(strlen($value)), $value); + } + + $RSAPrivateKey = implode('', $components); + + if ($num_primes > 2) { + $OtherPrimeInfos = ''; + for ($i = 3; $i <= $num_primes; $i++) { + // OtherPrimeInfos ::= SEQUENCE SIZE(1..MAX) OF OtherPrimeInfo + // + // OtherPrimeInfo ::= SEQUENCE { + // prime INTEGER, -- ri + // exponent INTEGER, -- di + // coefficient INTEGER -- ti + // } + $OtherPrimeInfo = pack('Ca*a*', CRYPT_RSA_ASN1_INTEGER, $this->_encodeLength(strlen($primes[$i]->toBytes(true))), $primes[$i]->toBytes(true)); + $OtherPrimeInfo.= pack('Ca*a*', CRYPT_RSA_ASN1_INTEGER, $this->_encodeLength(strlen($exponents[$i]->toBytes(true))), $exponents[$i]->toBytes(true)); + $OtherPrimeInfo.= pack('Ca*a*', CRYPT_RSA_ASN1_INTEGER, $this->_encodeLength(strlen($coefficients[$i]->toBytes(true))), $coefficients[$i]->toBytes(true)); + $OtherPrimeInfos.= pack('Ca*a*', CRYPT_RSA_ASN1_SEQUENCE, $this->_encodeLength(strlen($OtherPrimeInfo)), $OtherPrimeInfo); + } + $RSAPrivateKey.= pack('Ca*a*', CRYPT_RSA_ASN1_SEQUENCE, $this->_encodeLength(strlen($OtherPrimeInfos)), $OtherPrimeInfos); + } + + $RSAPrivateKey = pack('Ca*a*', CRYPT_RSA_ASN1_SEQUENCE, $this->_encodeLength(strlen($RSAPrivateKey)), $RSAPrivateKey); + + if (!empty($this->password) || is_string($this->password)) { + $iv = $this->_random(8); + $symkey = pack('H*', md5($this->password . $iv)); // symkey is short for symmetric key + $symkey.= substr(pack('H*', md5($symkey . $this->password . $iv)), 0, 8); + if (!class_exists('Crypt_TripleDES')) { + require_once('Crypt/TripleDES.php'); + } + $des = new Crypt_TripleDES(); + $des->setKey($symkey); + $des->setIV($iv); + $iv = strtoupper(bin2hex($iv)); + $RSAPrivateKey = "-----BEGIN RSA PRIVATE KEY-----\r\n" . + "Proc-Type: 4,ENCRYPTED\r\n" . + "DEK-Info: DES-EDE3-CBC,$iv\r\n" . + "\r\n" . + chunk_split(base64_encode($des->encrypt($RSAPrivateKey))) . + '-----END RSA PRIVATE KEY-----'; + } else { + $RSAPrivateKey = "-----BEGIN RSA PRIVATE KEY-----\r\n" . + chunk_split(base64_encode($RSAPrivateKey)) . + '-----END RSA PRIVATE KEY-----'; + } + + return $RSAPrivateKey; + } + } + + /** + * Convert a public key to the appropriate format + * + * @access private + * @see setPublicKeyFormat() + * @param String $RSAPrivateKey + * @return String + */ + function _convertPublicKey($n, $e) + { + $modulus = $n->toBytes(true); + $publicExponent = $e->toBytes(true); + + switch ($this->publicKeyFormat) { + case CRYPT_RSA_PUBLIC_FORMAT_RAW: + return array('e' => $e->copy(), 'n' => $n->copy()); + case CRYPT_RSA_PUBLIC_FORMAT_XML: + return "\r\n" . + ' ' . base64_encode($modulus) . "\r\n" . + ' ' . base64_encode($publicExponent) . "\r\n" . + ''; + break; + case CRYPT_RSA_PUBLIC_FORMAT_OPENSSH: + // from : + // string "ssh-rsa" + // mpint e + // mpint n + $RSAPublicKey = pack('Na*Na*Na*', strlen('ssh-rsa'), 'ssh-rsa', strlen($publicExponent), $publicExponent, strlen($modulus), $modulus); + $RSAPublicKey = 'ssh-rsa ' . base64_encode($RSAPublicKey) . ' ' . CRYPT_RSA_COMMENT; + + return $RSAPublicKey; + default: // eg. CRYPT_RSA_PUBLIC_FORMAT_PKCS1_RAW or CRYPT_RSA_PUBLIC_FORMAT_PKCS1 + // from : + // RSAPublicKey ::= SEQUENCE { + // modulus INTEGER, -- n + // publicExponent INTEGER -- e + // } + $components = array( + 'modulus' => pack('Ca*a*', CRYPT_RSA_ASN1_INTEGER, $this->_encodeLength(strlen($modulus)), $modulus), + 'publicExponent' => pack('Ca*a*', CRYPT_RSA_ASN1_INTEGER, $this->_encodeLength(strlen($publicExponent)), $publicExponent) + ); + + $RSAPublicKey = pack('Ca*a*a*', + CRYPT_RSA_ASN1_SEQUENCE, $this->_encodeLength(strlen($components['modulus']) + strlen($components['publicExponent'])), + $components['modulus'], $components['publicExponent'] + ); + + if ($this->publicKeyFormat == CRYPT_RSA_PUBLIC_FORMAT_PKCS1) { + // sequence(oid(1.2.840.113549.1.1.1), null)) = rsaEncryption. + $rsaOID = pack('H*', '300d06092a864886f70d0101010500'); // hex version of MA0GCSqGSIb3DQEBAQUA + $RSAPublicKey = chr(0) . $RSAPublicKey; + $RSAPublicKey = chr(3) . $this->_encodeLength(strlen($RSAPublicKey)) . $RSAPublicKey; + + $RSAPublicKey = pack('Ca*a*', + CRYPT_RSA_ASN1_SEQUENCE, $this->_encodeLength(strlen($rsaOID . $RSAPublicKey)), $rsaOID . $RSAPublicKey + ); + } + + $RSAPublicKey = "-----BEGIN PUBLIC KEY-----\r\n" . + chunk_split(base64_encode($RSAPublicKey)) . + '-----END PUBLIC KEY-----'; + + return $RSAPublicKey; + } + } + + /** + * Break a public or private key down into its constituant components + * + * @access private + * @see _convertPublicKey() + * @see _convertPrivateKey() + * @param String $key + * @param Integer $type + * @return Array + */ + function _parseKey($key, $type) + { + if ($type != CRYPT_RSA_PUBLIC_FORMAT_RAW && !is_string($key)) { + return false; + } + + switch ($type) { + case CRYPT_RSA_PUBLIC_FORMAT_RAW: + if (!is_array($key)) { + return false; + } + $components = array(); + switch (true) { + case isset($key['e']): + $components['publicExponent'] = $key['e']->copy(); + break; + case isset($key['exponent']): + $components['publicExponent'] = $key['exponent']->copy(); + break; + case isset($key['publicExponent']): + $components['publicExponent'] = $key['publicExponent']->copy(); + break; + case isset($key[0]): + $components['publicExponent'] = $key[0]->copy(); + } + switch (true) { + case isset($key['n']): + $components['modulus'] = $key['n']->copy(); + break; + case isset($key['modulo']): + $components['modulus'] = $key['modulo']->copy(); + break; + case isset($key['modulus']): + $components['modulus'] = $key['modulus']->copy(); + break; + case isset($key[1]): + $components['modulus'] = $key[1]->copy(); + } + return isset($components['modulus']) && isset($components['publicExponent']) ? $components : false; + case CRYPT_RSA_PRIVATE_FORMAT_PKCS1: + case CRYPT_RSA_PUBLIC_FORMAT_PKCS1: + /* Although PKCS#1 proposes a format that public and private keys can use, encrypting them is + "outside the scope" of PKCS#1. PKCS#1 then refers you to PKCS#12 and PKCS#15 if you're wanting to + protect private keys, however, that's not what OpenSSL* does. OpenSSL protects private keys by adding + two new "fields" to the key - DEK-Info and Proc-Type. These fields are discussed here: + + http://tools.ietf.org/html/rfc1421#section-4.6.1.1 + http://tools.ietf.org/html/rfc1421#section-4.6.1.3 + + DES-EDE3-CBC as an algorithm, however, is not discussed anywhere, near as I can tell. + DES-CBC and DES-EDE are discussed in RFC1423, however, DES-EDE3-CBC isn't, nor is its key derivation + function. As is, the definitive authority on this encoding scheme isn't the IETF but rather OpenSSL's + own implementation. ie. the implementation *is* the standard and any bugs that may exist in that + implementation are part of the standard, as well. + + * OpenSSL is the de facto standard. It's utilized by OpenSSH and other projects */ + if (preg_match('#DEK-Info: (.+),(.+)#', $key, $matches)) { + $iv = pack('H*', trim($matches[2])); + $symkey = pack('H*', md5($this->password . substr($iv, 0, 8))); // symkey is short for symmetric key + $symkey.= substr(pack('H*', md5($symkey . $this->password . $iv)), 0, 8); + $ciphertext = preg_replace('#.+(\r|\n|\r\n)\1|[\r\n]|-.+-| #s', '', $key); + $ciphertext = preg_match('#^[a-zA-Z\d/+]*={0,2}$#', $ciphertext) ? base64_decode($ciphertext) : false; + if ($ciphertext === false) { + $ciphertext = $key; + } + switch ($matches[1]) { + case 'AES-128-CBC': + if (!class_exists('Crypt_AES')) { + require_once('Crypt/AES.php'); + } + $symkey = substr($symkey, 0, 16); + $crypto = new Crypt_AES(); + break; + case 'DES-EDE3-CFB': + if (!class_exists('Crypt_TripleDES')) { + require_once('Crypt/TripleDES.php'); + } + $crypto = new Crypt_TripleDES(CRYPT_DES_MODE_CFB); + break; + case 'DES-EDE3-CBC': + if (!class_exists('Crypt_TripleDES')) { + require_once('Crypt/TripleDES.php'); + } + $crypto = new Crypt_TripleDES(); + break; + case 'DES-CBC': + if (!class_exists('Crypt_DES')) { + require_once('Crypt/DES.php'); + } + $crypto = new Crypt_DES(); + break; + default: + return false; + } + $crypto->setKey($symkey); + $crypto->setIV($iv); + $decoded = $crypto->decrypt($ciphertext); + } else { + $decoded = preg_replace('#-.+-|[\r\n]| #', '', $key); + $decoded = preg_match('#^[a-zA-Z\d/+]*={0,2}$#', $decoded) ? base64_decode($decoded) : false; + } + + if ($decoded !== false) { + $key = $decoded; + } + + $components = array(); + + if (ord($this->_string_shift($key)) != CRYPT_RSA_ASN1_SEQUENCE) { + return false; + } + if ($this->_decodeLength($key) != strlen($key)) { + return false; + } + + $tag = ord($this->_string_shift($key)); + /* intended for keys for which OpenSSL's asn1parse returns the following: + + 0:d=0 hl=4 l= 631 cons: SEQUENCE + 4:d=1 hl=2 l= 1 prim: INTEGER :00 + 7:d=1 hl=2 l= 13 cons: SEQUENCE + 9:d=2 hl=2 l= 9 prim: OBJECT :rsaEncryption + 20:d=2 hl=2 l= 0 prim: NULL + 22:d=1 hl=4 l= 609 prim: OCTET STRING */ + + if ($tag == CRYPT_RSA_ASN1_INTEGER && substr($key, 0, 3) == "\x01\x00\x30") { + $this->_string_shift($key, 3); + $tag = CRYPT_RSA_ASN1_SEQUENCE; + } + + if ($tag == CRYPT_RSA_ASN1_SEQUENCE) { + /* intended for keys for which OpenSSL's asn1parse returns the following: + + 0:d=0 hl=4 l= 290 cons: SEQUENCE + 4:d=1 hl=2 l= 13 cons: SEQUENCE + 6:d=2 hl=2 l= 9 prim: OBJECT :rsaEncryption + 17:d=2 hl=2 l= 0 prim: NULL + 19:d=1 hl=4 l= 271 prim: BIT STRING */ + $this->_string_shift($key, $this->_decodeLength($key)); + $tag = ord($this->_string_shift($key)); // skip over the BIT STRING / OCTET STRING tag + $this->_decodeLength($key); // skip over the BIT STRING / OCTET STRING length + // "The initial octet shall encode, as an unsigned binary integer wtih bit 1 as the least significant bit, the number of + // unused bits in the final subsequent octet. The number shall be in the range zero to seven." + // -- http://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf (section 8.6.2.2) + if ($tag == CRYPT_RSA_ASN1_BITSTRING) { + $this->_string_shift($key); + } + if (ord($this->_string_shift($key)) != CRYPT_RSA_ASN1_SEQUENCE) { + return false; + } + if ($this->_decodeLength($key) != strlen($key)) { + return false; + } + $tag = ord($this->_string_shift($key)); + } + if ($tag != CRYPT_RSA_ASN1_INTEGER) { + return false; + } + + $length = $this->_decodeLength($key); + $temp = $this->_string_shift($key, $length); + if (strlen($temp) != 1 || ord($temp) > 2) { + $components['modulus'] = new Math_BigInteger($temp, 256); + $this->_string_shift($key); // skip over CRYPT_RSA_ASN1_INTEGER + $length = $this->_decodeLength($key); + $components[$type == CRYPT_RSA_PUBLIC_FORMAT_PKCS1 ? 'publicExponent' : 'privateExponent'] = new Math_BigInteger($this->_string_shift($key, $length), 256); + + return $components; + } + if (ord($this->_string_shift($key)) != CRYPT_RSA_ASN1_INTEGER) { + return false; + } + $length = $this->_decodeLength($key); + $components['modulus'] = new Math_BigInteger($this->_string_shift($key, $length), 256); + $this->_string_shift($key); + $length = $this->_decodeLength($key); + $components['publicExponent'] = new Math_BigInteger($this->_string_shift($key, $length), 256); + $this->_string_shift($key); + $length = $this->_decodeLength($key); + $components['privateExponent'] = new Math_BigInteger($this->_string_shift($key, $length), 256); + $this->_string_shift($key); + $length = $this->_decodeLength($key); + $components['primes'] = array(1 => new Math_BigInteger($this->_string_shift($key, $length), 256)); + $this->_string_shift($key); + $length = $this->_decodeLength($key); + $components['primes'][] = new Math_BigInteger($this->_string_shift($key, $length), 256); + $this->_string_shift($key); + $length = $this->_decodeLength($key); + $components['exponents'] = array(1 => new Math_BigInteger($this->_string_shift($key, $length), 256)); + $this->_string_shift($key); + $length = $this->_decodeLength($key); + $components['exponents'][] = new Math_BigInteger($this->_string_shift($key, $length), 256); + $this->_string_shift($key); + $length = $this->_decodeLength($key); + $components['coefficients'] = array(2 => new Math_BigInteger($this->_string_shift($key, $length), 256)); + + if (!empty($key)) { + if (ord($this->_string_shift($key)) != CRYPT_RSA_ASN1_SEQUENCE) { + return false; + } + $this->_decodeLength($key); + while (!empty($key)) { + if (ord($this->_string_shift($key)) != CRYPT_RSA_ASN1_SEQUENCE) { + return false; + } + $this->_decodeLength($key); + $key = substr($key, 1); + $length = $this->_decodeLength($key); + $components['primes'][] = new Math_BigInteger($this->_string_shift($key, $length), 256); + $this->_string_shift($key); + $length = $this->_decodeLength($key); + $components['exponents'][] = new Math_BigInteger($this->_string_shift($key, $length), 256); + $this->_string_shift($key); + $length = $this->_decodeLength($key); + $components['coefficients'][] = new Math_BigInteger($this->_string_shift($key, $length), 256); + } + } + + return $components; + case CRYPT_RSA_PUBLIC_FORMAT_OPENSSH: + $key = base64_decode(preg_replace('#^ssh-rsa | .+$#', '', $key)); + if ($key === false) { + return false; + } + + $cleanup = substr($key, 0, 11) == "\0\0\0\7ssh-rsa"; + + if (strlen($key) <= 4) { + return false; + } + extract(unpack('Nlength', $this->_string_shift($key, 4))); + $publicExponent = new Math_BigInteger($this->_string_shift($key, $length), -256); + if (strlen($key) <= 4) { + return false; + } + extract(unpack('Nlength', $this->_string_shift($key, 4))); + $modulus = new Math_BigInteger($this->_string_shift($key, $length), -256); + + if ($cleanup && strlen($key)) { + if (strlen($key) <= 4) { + return false; + } + extract(unpack('Nlength', $this->_string_shift($key, 4))); + $realModulus = new Math_BigInteger($this->_string_shift($key, $length), -256); + return strlen($key) ? false : array( + 'modulus' => $realModulus, + 'publicExponent' => $modulus + ); + } else { + return strlen($key) ? false : array( + 'modulus' => $modulus, + 'publicExponent' => $publicExponent + ); + } + // http://www.w3.org/TR/xmldsig-core/#sec-RSAKeyValue + // http://en.wikipedia.org/wiki/XML_Signature + case CRYPT_RSA_PRIVATE_FORMAT_XML: + case CRYPT_RSA_PUBLIC_FORMAT_XML: + $this->components = array(); + + $xml = xml_parser_create('UTF-8'); + xml_set_object($xml, $this); + xml_set_element_handler($xml, '_start_element_handler', '_stop_element_handler'); + xml_set_character_data_handler($xml, '_data_handler'); + if (!xml_parse($xml, $key)) { + return false; + } + + return isset($this->components['modulus']) && isset($this->components['publicExponent']) ? $this->components : false; + // from PuTTY's SSHPUBK.C + case CRYPT_RSA_PRIVATE_FORMAT_PUTTY: + $components = array(); + $key = preg_split('#\r\n|\r|\n#', $key); + $type = trim(preg_replace('#PuTTY-User-Key-File-2: (.+)#', '$1', $key[0])); + if ($type != 'ssh-rsa') { + return false; + } + $encryption = trim(preg_replace('#Encryption: (.+)#', '$1', $key[1])); + + $publicLength = trim(preg_replace('#Public-Lines: (\d+)#', '$1', $key[3])); + $public = base64_decode(implode('', array_map('trim', array_slice($key, 4, $publicLength)))); + $public = substr($public, 11); + extract(unpack('Nlength', $this->_string_shift($public, 4))); + $components['publicExponent'] = new Math_BigInteger($this->_string_shift($public, $length), -256); + extract(unpack('Nlength', $this->_string_shift($public, 4))); + $components['modulus'] = new Math_BigInteger($this->_string_shift($public, $length), -256); + + $privateLength = trim(preg_replace('#Private-Lines: (\d+)#', '$1', $key[$publicLength + 4])); + $private = base64_decode(implode('', array_map('trim', array_slice($key, $publicLength + 5, $privateLength)))); + + switch ($encryption) { + case 'aes256-cbc': + if (!class_exists('Crypt_AES')) { + require_once('Crypt/AES.php'); + } + $symkey = ''; + $sequence = 0; + while (strlen($symkey) < 32) { + $temp = pack('Na*', $sequence++, $this->password); + $symkey.= pack('H*', sha1($temp)); + } + $symkey = substr($symkey, 0, 32); + $crypto = new Crypt_AES(); + } + + if ($encryption != 'none') { + $crypto->setKey($symkey); + $crypto->disablePadding(); + $private = $crypto->decrypt($private); + if ($private === false) { + return false; + } + } + + extract(unpack('Nlength', $this->_string_shift($private, 4))); + if (strlen($private) < $length) { + return false; + } + $components['privateExponent'] = new Math_BigInteger($this->_string_shift($private, $length), -256); + extract(unpack('Nlength', $this->_string_shift($private, 4))); + if (strlen($private) < $length) { + return false; + } + $components['primes'] = array(1 => new Math_BigInteger($this->_string_shift($private, $length), -256)); + extract(unpack('Nlength', $this->_string_shift($private, 4))); + if (strlen($private) < $length) { + return false; + } + $components['primes'][] = new Math_BigInteger($this->_string_shift($private, $length), -256); + + $temp = $components['primes'][1]->subtract($this->one); + $components['exponents'] = array(1 => $components['publicExponent']->modInverse($temp)); + $temp = $components['primes'][2]->subtract($this->one); + $components['exponents'][] = $components['publicExponent']->modInverse($temp); + + extract(unpack('Nlength', $this->_string_shift($private, 4))); + if (strlen($private) < $length) { + return false; + } + $components['coefficients'] = array(2 => new Math_BigInteger($this->_string_shift($private, $length), -256)); + + return $components; + } + } + + /** + * Returns the key size + * + * More specifically, this returns the size of the modulo in bits. + * + * @access public + * @return Integer + */ + function getSize() + { + return !isset($this->modulus) ? 0 : strlen($this->modulus->toBits()); + } + + /** + * Start Element Handler + * + * Called by xml_set_element_handler() + * + * @access private + * @param Resource $parser + * @param String $name + * @param Array $attribs + */ + function _start_element_handler($parser, $name, $attribs) + { + //$name = strtoupper($name); + switch ($name) { + case 'MODULUS': + $this->current = &$this->components['modulus']; + break; + case 'EXPONENT': + $this->current = &$this->components['publicExponent']; + break; + case 'P': + $this->current = &$this->components['primes'][1]; + break; + case 'Q': + $this->current = &$this->components['primes'][2]; + break; + case 'DP': + $this->current = &$this->components['exponents'][1]; + break; + case 'DQ': + $this->current = &$this->components['exponents'][2]; + break; + case 'INVERSEQ': + $this->current = &$this->components['coefficients'][2]; + break; + case 'D': + $this->current = &$this->components['privateExponent']; + break; + default: + unset($this->current); + } + $this->current = ''; + } + + /** + * Stop Element Handler + * + * Called by xml_set_element_handler() + * + * @access private + * @param Resource $parser + * @param String $name + */ + function _stop_element_handler($parser, $name) + { + //$name = strtoupper($name); + if ($name == 'RSAKEYVALUE') { + return; + } + $this->current = new Math_BigInteger(base64_decode($this->current), 256); + } + + /** + * Data Handler + * + * Called by xml_set_character_data_handler() + * + * @access private + * @param Resource $parser + * @param String $data + */ + function _data_handler($parser, $data) + { + if (!isset($this->current) || is_object($this->current)) { + return; + } + $this->current.= trim($data); + } + + /** + * Loads a public or private key + * + * Returns true on success and false on failure (ie. an incorrect password was provided or the key was malformed) + * + * @access public + * @param String $key + * @param Integer $type optional + */ + function loadKey($key, $type = false) + { + if ($type === false) { + $types = array( + CRYPT_RSA_PUBLIC_FORMAT_RAW, + CRYPT_RSA_PRIVATE_FORMAT_PKCS1, + CRYPT_RSA_PRIVATE_FORMAT_XML, + CRYPT_RSA_PRIVATE_FORMAT_PUTTY, + CRYPT_RSA_PUBLIC_FORMAT_OPENSSH + ); + foreach ($types as $type) { + $components = $this->_parseKey($key, $type); + if ($components !== false) { + break; + } + } + + } else { + $components = $this->_parseKey($key, $type); + } + + if ($components === false) { + return false; + } + + $this->modulus = $components['modulus']; + $this->k = strlen($this->modulus->toBytes()); + $this->exponent = isset($components['privateExponent']) ? $components['privateExponent'] : $components['publicExponent']; + if (isset($components['primes'])) { + $this->primes = $components['primes']; + $this->exponents = $components['exponents']; + $this->coefficients = $components['coefficients']; + $this->publicExponent = $components['publicExponent']; + } else { + $this->primes = array(); + $this->exponents = array(); + $this->coefficients = array(); + $this->publicExponent = false; + } + + return true; + } + + /** + * Sets the password + * + * Private keys can be encrypted with a password. To unset the password, pass in the empty string or false. + * Or rather, pass in $password such that empty($password) && !is_string($password) is true. + * + * @see createKey() + * @see loadKey() + * @access public + * @param String $password + */ + function setPassword($password = false) + { + $this->password = $password; + } + + /** + * Defines the public key + * + * Some private key formats define the public exponent and some don't. Those that don't define it are problematic when + * used in certain contexts. For example, in SSH-2, RSA authentication works by sending the public key along with a + * message signed by the private key to the server. The SSH-2 server looks the public key up in an index of public keys + * and if it's present then proceeds to verify the signature. Problem is, if your private key doesn't include the public + * exponent this won't work unless you manually add the public exponent. + * + * Do note that when a new key is loaded the index will be cleared. + * + * Returns true on success, false on failure + * + * @see getPublicKey() + * @access public + * @param String $key optional + * @param Integer $type optional + * @return Boolean + */ + function setPublicKey($key = false, $type = false) + { + if ($key === false && !empty($this->modulus)) { + $this->publicExponent = $this->exponent; + return true; + } + + if ($type === false) { + $types = array( + CRYPT_RSA_PUBLIC_FORMAT_RAW, + CRYPT_RSA_PUBLIC_FORMAT_PKCS1, + CRYPT_RSA_PUBLIC_FORMAT_XML, + CRYPT_RSA_PUBLIC_FORMAT_OPENSSH + ); + foreach ($types as $type) { + $components = $this->_parseKey($key, $type); + if ($components !== false) { + break; + } + } + } else { + $components = $this->_parseKey($key, $type); + } + + if ($components === false) { + return false; + } + + if (empty($this->modulus) || !$this->modulus->equals($components['modulus'])) { + $this->modulus = $components['modulus']; + $this->exponent = $this->publicExponent = $components['publicExponent']; + return true; + } + + $this->publicExponent = $components['publicExponent']; + + return true; + } + + /** + * Returns the public key + * + * The public key is only returned under two circumstances - if the private key had the public key embedded within it + * or if the public key was set via setPublicKey(). If the currently loaded key is supposed to be the public key this + * function won't return it since this library, for the most part, doesn't distinguish between public and private keys. + * + * @see getPublicKey() + * @access public + * @param String $key + * @param Integer $type optional + */ + function getPublicKey($type = CRYPT_RSA_PUBLIC_FORMAT_PKCS1) + { + if (empty($this->modulus) || empty($this->publicExponent)) { + return false; + } + + $oldFormat = $this->publicKeyFormat; + $this->publicKeyFormat = $type; + $temp = $this->_convertPublicKey($this->modulus, $this->publicExponent); + $this->publicKeyFormat = $oldFormat; + return $temp; + } + + /** + * Returns the private key + * + * The private key is only returned if the currently loaded key contains the constituent prime numbers. + * + * @see getPublicKey() + * @access public + * @param String $key + * @param Integer $type optional + */ + function getPrivateKey($type = CRYPT_RSA_PUBLIC_FORMAT_PKCS1) + { + if (empty($this->primes)) { + return false; + } + + $oldFormat = $this->privateKeyFormat; + $this->privateKeyFormat = $type; + $temp = $this->_convertPrivateKey($this->modulus, $this->publicExponent, $this->exponent, $this->primes, $this->exponents, $this->coefficients); + $this->privateKeyFormat = $oldFormat; + return $temp; + } + + /** + * Returns a minimalistic private key + * + * Returns the private key without the prime number constituants. Structurally identical to a public key that + * hasn't been set as the public key + * + * @see getPrivateKey() + * @access private + * @param String $key + * @param Integer $type optional + */ + function _getPrivatePublicKey($mode = CRYPT_RSA_PUBLIC_FORMAT_PKCS1) + { + if (empty($this->modulus) || empty($this->exponent)) { + return false; + } + + $oldFormat = $this->publicKeyFormat; + $this->publicKeyFormat = $mode; + $temp = $this->_convertPublicKey($this->modulus, $this->exponent); + $this->publicKeyFormat = $oldFormat; + return $temp; + } + + /** + * __toString() magic method + * + * @access public + */ + function __toString() + { + $key = $this->getPrivateKey($this->privateKeyFormat); + if ($key !== false) { + return $key; + } + $key = $this->_getPrivatePublicKey($this->publicKeyFormat); + return $key !== false ? $key : ''; + } + + /** + * Generates the smallest and largest numbers requiring $bits bits + * + * @access private + * @param Integer $bits + * @return Array + */ + function _generateMinMax($bits) + { + $bytes = $bits >> 3; + $min = str_repeat(chr(0), $bytes); + $max = str_repeat(chr(0xFF), $bytes); + $msb = $bits & 7; + if ($msb) { + $min = chr(1 << ($msb - 1)) . $min; + $max = chr((1 << $msb) - 1) . $max; + } else { + $min[0] = chr(0x80); + } + + return array( + 'min' => new Math_BigInteger($min, 256), + 'max' => new Math_BigInteger($max, 256) + ); + } + + /** + * DER-decode the length + * + * DER supports lengths up to (2**8)**127, however, we'll only support lengths up to (2**8)**4. See + * {@link http://itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#p=13 X.690 § 8.1.3} for more information. + * + * @access private + * @param String $string + * @return Integer + */ + function _decodeLength(&$string) + { + $length = ord($this->_string_shift($string)); + if ( $length & 0x80 ) { // definite length, long form + $length&= 0x7F; + $temp = $this->_string_shift($string, $length); + list(, $length) = unpack('N', substr(str_pad($temp, 4, chr(0), STR_PAD_LEFT), -4)); + } + return $length; + } + + /** + * DER-encode the length + * + * DER supports lengths up to (2**8)**127, however, we'll only support lengths up to (2**8)**4. See + * {@link http://itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#p=13 X.690 § 8.1.3} for more information. + * + * @access private + * @param Integer $length + * @return String + */ + function _encodeLength($length) + { + if ($length <= 0x7F) { + return chr($length); + } + + $temp = ltrim(pack('N', $length), chr(0)); + return pack('Ca*', 0x80 | strlen($temp), $temp); + } + + /** + * String Shift + * + * Inspired by array_shift + * + * @param String $string + * @param optional Integer $index + * @return String + * @access private + */ + function _string_shift(&$string, $index = 1) + { + $substr = substr($string, 0, $index); + $string = substr($string, $index); + return $substr; + } + + /** + * Determines the private key format + * + * @see createKey() + * @access public + * @param Integer $format + */ + function setPrivateKeyFormat($format) + { + $this->privateKeyFormat = $format; + } + + /** + * Determines the public key format + * + * @see createKey() + * @access public + * @param Integer $format + */ + function setPublicKeyFormat($format) + { + $this->publicKeyFormat = $format; + } + + /** + * Determines which hashing function should be used + * + * Used with signature production / verification and (if the encryption mode is CRYPT_RSA_ENCRYPTION_OAEP) encryption and + * decryption. If $hash isn't supported, sha1 is used. + * + * @access public + * @param String $hash + */ + function setHash($hash) + { + // Crypt_Hash supports algorithms that PKCS#1 doesn't support. md5-96 and sha1-96, for example. + switch ($hash) { + case 'md2': + case 'md5': + case 'sha1': + case 'sha256': + case 'sha384': + case 'sha512': + $this->hash = new Crypt_Hash($hash); + $this->hashName = $hash; + break; + default: + $this->hash = new Crypt_Hash('sha1'); + $this->hashName = 'sha1'; + } + $this->hLen = $this->hash->getLength(); + } + + /** + * Determines which hashing function should be used for the mask generation function + * + * The mask generation function is used by CRYPT_RSA_ENCRYPTION_OAEP and CRYPT_RSA_SIGNATURE_PSS and although it's + * best if Hash and MGFHash are set to the same thing this is not a requirement. + * + * @access public + * @param String $hash + */ + function setMGFHash($hash) + { + // Crypt_Hash supports algorithms that PKCS#1 doesn't support. md5-96 and sha1-96, for example. + switch ($hash) { + case 'md2': + case 'md5': + case 'sha1': + case 'sha256': + case 'sha384': + case 'sha512': + $this->mgfHash = new Crypt_Hash($hash); + break; + default: + $this->mgfHash = new Crypt_Hash('sha1'); + } + $this->mgfHLen = $this->mgfHash->getLength(); + } + + /** + * Determines the salt length + * + * To quote from {@link http://tools.ietf.org/html/rfc3447#page-38 RFC3447#page-38}: + * + * Typical salt lengths in octets are hLen (the length of the output + * of the hash function Hash) and 0. + * + * @access public + * @param Integer $format + */ + function setSaltLength($sLen) + { + $this->sLen = $sLen; + } + + /** + * Generates a random string x bytes long + * + * @access public + * @param Integer $bytes + * @param optional Integer $nonzero + * @return String + */ + function _random($bytes, $nonzero = false) + { + $temp = ''; + for ($i = 0; $i < $bytes; $i++) { + $temp.= chr(crypt_random($nonzero, 255)); + } + return $temp; + } + + /** + * Integer-to-Octet-String primitive + * + * See {@link http://tools.ietf.org/html/rfc3447#section-4.1 RFC3447#section-4.1}. + * + * @access private + * @param Math_BigInteger $x + * @param Integer $xLen + * @return String + */ + function _i2osp($x, $xLen) + { + $x = $x->toBytes(); + if (strlen($x) > $xLen) { + user_error('Integer too large', E_USER_NOTICE); + return false; + } + return str_pad($x, $xLen, chr(0), STR_PAD_LEFT); + } + + /** + * Octet-String-to-Integer primitive + * + * See {@link http://tools.ietf.org/html/rfc3447#section-4.2 RFC3447#section-4.2}. + * + * @access private + * @param String $x + * @return Math_BigInteger + */ + function _os2ip($x) + { + return new Math_BigInteger($x, 256); + } + + /** + * Exponentiate with or without Chinese Remainder Theorem + * + * See {@link http://tools.ietf.org/html/rfc3447#section-5.1.1 RFC3447#section-5.1.2}. + * + * @access private + * @param Math_BigInteger $x + * @return Math_BigInteger + */ + function _exponentiate($x) + { + if (empty($this->primes) || empty($this->coefficients) || empty($this->exponents)) { + return $x->modPow($this->exponent, $this->modulus); + } + + $num_primes = count($this->primes); + + if (defined('CRYPT_RSA_DISABLE_BLINDING')) { + $m_i = array( + 1 => $x->modPow($this->exponents[1], $this->primes[1]), + 2 => $x->modPow($this->exponents[2], $this->primes[2]) + ); + $h = $m_i[1]->subtract($m_i[2]); + $h = $h->multiply($this->coefficients[2]); + list(, $h) = $h->divide($this->primes[1]); + $m = $m_i[2]->add($h->multiply($this->primes[2])); + + $r = $this->primes[1]; + for ($i = 3; $i <= $num_primes; $i++) { + $m_i = $x->modPow($this->exponents[$i], $this->primes[$i]); + + $r = $r->multiply($this->primes[$i - 1]); + + $h = $m_i->subtract($m); + $h = $h->multiply($this->coefficients[$i]); + list(, $h) = $h->divide($this->primes[$i]); + + $m = $m->add($r->multiply($h)); + } + } else { + $smallest = $this->primes[1]; + for ($i = 2; $i <= $num_primes; $i++) { + if ($smallest->compare($this->primes[$i]) > 0) { + $smallest = $this->primes[$i]; + } + } + + $one = new Math_BigInteger(1); + $one->setRandomGenerator('crypt_random'); + + $r = $one->random($one, $smallest->subtract($one)); + + $m_i = array( + 1 => $this->_blind($x, $r, 1), + 2 => $this->_blind($x, $r, 2) + ); + $h = $m_i[1]->subtract($m_i[2]); + $h = $h->multiply($this->coefficients[2]); + list(, $h) = $h->divide($this->primes[1]); + $m = $m_i[2]->add($h->multiply($this->primes[2])); + + $r = $this->primes[1]; + for ($i = 3; $i <= $num_primes; $i++) { + $m_i = $this->_blind($x, $r, $i); + + $r = $r->multiply($this->primes[$i - 1]); + + $h = $m_i->subtract($m); + $h = $h->multiply($this->coefficients[$i]); + list(, $h) = $h->divide($this->primes[$i]); + + $m = $m->add($r->multiply($h)); + } + } + + return $m; + } + + /** + * Performs RSA Blinding + * + * Protects against timing attacks by employing RSA Blinding. + * Returns $x->modPow($this->exponents[$i], $this->primes[$i]) + * + * @access private + * @param Math_BigInteger $x + * @param Math_BigInteger $r + * @param Integer $i + * @return Math_BigInteger + */ + function _blind($x, $r, $i) + { + $x = $x->multiply($r->modPow($this->publicExponent, $this->primes[$i])); + $x = $x->modPow($this->exponents[$i], $this->primes[$i]); + + $r = $r->modInverse($this->primes[$i]); + $x = $x->multiply($r); + list(, $x) = $x->divide($this->primes[$i]); + + return $x; + } + + /** + * Performs blinded RSA equality testing + * + * Protects against a particular type of timing attack described. + * + * See {@link http://codahale.com/a-lesson-in-timing-attacks/ A Lesson In Timing Attacks (or, Don’t use MessageDigest.isEquals)} + * + * Thanks for the heads up singpolyma! + * + * @access private + * @param String $x + * @param String $y + * @return Boolean + */ + function _equals($x, $y) + { + if (strlen($x) != strlen($y)) { + return false; + } + + $result = 0; + for ($i = 0; $i < strlen($x); $i++) { + $result |= ord($x[$i]) ^ ord($y[$i]); + } + + return $result == 0; + } + + /** + * RSAEP + * + * See {@link http://tools.ietf.org/html/rfc3447#section-5.1.1 RFC3447#section-5.1.1}. + * + * @access private + * @param Math_BigInteger $m + * @return Math_BigInteger + */ + function _rsaep($m) + { + if ($m->compare($this->zero) < 0 || $m->compare($this->modulus) > 0) { + user_error('Message representative out of range', E_USER_NOTICE); + return false; + } + return $this->_exponentiate($m); + } + + /** + * RSADP + * + * See {@link http://tools.ietf.org/html/rfc3447#section-5.1.2 RFC3447#section-5.1.2}. + * + * @access private + * @param Math_BigInteger $c + * @return Math_BigInteger + */ + function _rsadp($c) + { + if ($c->compare($this->zero) < 0 || $c->compare($this->modulus) > 0) { + user_error('Ciphertext representative out of range', E_USER_NOTICE); + return false; + } + return $this->_exponentiate($c); + } + + /** + * RSASP1 + * + * See {@link http://tools.ietf.org/html/rfc3447#section-5.2.1 RFC3447#section-5.2.1}. + * + * @access private + * @param Math_BigInteger $m + * @return Math_BigInteger + */ + function _rsasp1($m) + { + if ($m->compare($this->zero) < 0 || $m->compare($this->modulus) > 0) { + user_error('Message representative out of range', E_USER_NOTICE); + return false; + } + return $this->_exponentiate($m); + } + + /** + * RSAVP1 + * + * See {@link http://tools.ietf.org/html/rfc3447#section-5.2.2 RFC3447#section-5.2.2}. + * + * @access private + * @param Math_BigInteger $s + * @return Math_BigInteger + */ + function _rsavp1($s) + { + if ($s->compare($this->zero) < 0 || $s->compare($this->modulus) > 0) { + user_error('Signature representative out of range', E_USER_NOTICE); + return false; + } + return $this->_exponentiate($s); + } + + /** + * MGF1 + * + * See {@link http://tools.ietf.org/html/rfc3447#appendix-B.2.1 RFC3447#appendix-B.2.1}. + * + * @access private + * @param String $mgfSeed + * @param Integer $mgfLen + * @return String + */ + function _mgf1($mgfSeed, $maskLen) + { + // if $maskLen would yield strings larger than 4GB, PKCS#1 suggests a "Mask too long" error be output. + + $t = ''; + $count = ceil($maskLen / $this->mgfHLen); + for ($i = 0; $i < $count; $i++) { + $c = pack('N', $i); + $t.= $this->mgfHash->hash($mgfSeed . $c); + } + + return substr($t, 0, $maskLen); + } + + /** + * RSAES-OAEP-ENCRYPT + * + * See {@link http://tools.ietf.org/html/rfc3447#section-7.1.1 RFC3447#section-7.1.1} and + * {http://en.wikipedia.org/wiki/Optimal_Asymmetric_Encryption_Padding OAES}. + * + * @access private + * @param String $m + * @param String $l + * @return String + */ + function _rsaes_oaep_encrypt($m, $l = '') + { + $mLen = strlen($m); + + // Length checking + + // if $l is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error + // be output. + + if ($mLen > $this->k - 2 * $this->hLen - 2) { + user_error('Message too long', E_USER_NOTICE); + return false; + } + + // EME-OAEP encoding + + $lHash = $this->hash->hash($l); + $ps = str_repeat(chr(0), $this->k - $mLen - 2 * $this->hLen - 2); + $db = $lHash . $ps . chr(1) . $m; + $seed = $this->_random($this->hLen); + $dbMask = $this->_mgf1($seed, $this->k - $this->hLen - 1); + $maskedDB = $db ^ $dbMask; + $seedMask = $this->_mgf1($maskedDB, $this->hLen); + $maskedSeed = $seed ^ $seedMask; + $em = chr(0) . $maskedSeed . $maskedDB; + + // RSA encryption + + $m = $this->_os2ip($em); + $c = $this->_rsaep($m); + $c = $this->_i2osp($c, $this->k); + + // Output the ciphertext C + + return $c; + } + + /** + * RSAES-OAEP-DECRYPT + * + * See {@link http://tools.ietf.org/html/rfc3447#section-7.1.2 RFC3447#section-7.1.2}. The fact that the error + * messages aren't distinguishable from one another hinders debugging, but, to quote from RFC3447#section-7.1.2: + * + * Note. Care must be taken to ensure that an opponent cannot + * distinguish the different error conditions in Step 3.g, whether by + * error message or timing, or, more generally, learn partial + * information about the encoded message EM. Otherwise an opponent may + * be able to obtain useful information about the decryption of the + * ciphertext C, leading to a chosen-ciphertext attack such as the one + * observed by Manger [36]. + * + * As for $l... to quote from {@link http://tools.ietf.org/html/rfc3447#page-17 RFC3447#page-17}: + * + * Both the encryption and the decryption operations of RSAES-OAEP take + * the value of a label L as input. In this version of PKCS #1, L is + * the empty string; other uses of the label are outside the scope of + * this document. + * + * @access private + * @param String $c + * @param String $l + * @return String + */ + function _rsaes_oaep_decrypt($c, $l = '') + { + // Length checking + + // if $l is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error + // be output. + + if (strlen($c) != $this->k || $this->k < 2 * $this->hLen + 2) { + user_error('Decryption error', E_USER_NOTICE); + return false; + } + + // RSA decryption + + $c = $this->_os2ip($c); + $m = $this->_rsadp($c); + if ($m === false) { + user_error('Decryption error', E_USER_NOTICE); + return false; + } + $em = $this->_i2osp($m, $this->k); + + // EME-OAEP decoding + + $lHash = $this->hash->hash($l); + $y = ord($em[0]); + $maskedSeed = substr($em, 1, $this->hLen); + $maskedDB = substr($em, $this->hLen + 1); + $seedMask = $this->_mgf1($maskedDB, $this->hLen); + $seed = $maskedSeed ^ $seedMask; + $dbMask = $this->_mgf1($seed, $this->k - $this->hLen - 1); + $db = $maskedDB ^ $dbMask; + $lHash2 = substr($db, 0, $this->hLen); + $m = substr($db, $this->hLen); + if ($lHash != $lHash2) { + user_error('Decryption error', E_USER_NOTICE); + return false; + } + $m = ltrim($m, chr(0)); + if (ord($m[0]) != 1) { + user_error('Decryption error', E_USER_NOTICE); + return false; + } + + // Output the message M + + return substr($m, 1); + } + + /** + * RSAES-PKCS1-V1_5-ENCRYPT + * + * See {@link http://tools.ietf.org/html/rfc3447#section-7.2.1 RFC3447#section-7.2.1}. + * + * @access private + * @param String $m + * @return String + */ + function _rsaes_pkcs1_v1_5_encrypt($m) + { + $mLen = strlen($m); + + // Length checking + + if ($mLen > $this->k - 11) { + user_error('Message too long', E_USER_NOTICE); + return false; + } + + // EME-PKCS1-v1_5 encoding + + $ps = $this->_random($this->k - $mLen - 3, true); + $em = chr(0) . chr(2) . $ps . chr(0) . $m; + + // RSA encryption + $m = $this->_os2ip($em); + $c = $this->_rsaep($m); + $c = $this->_i2osp($c, $this->k); + + // Output the ciphertext C + + return $c; + } + + /** + * RSAES-PKCS1-V1_5-DECRYPT + * + * See {@link http://tools.ietf.org/html/rfc3447#section-7.2.2 RFC3447#section-7.2.2}. + * + * For compatability purposes, this function departs slightly from the description given in RFC3447. + * The reason being that RFC2313#section-8.1 (PKCS#1 v1.5) states that ciphertext's encrypted by the + * private key should have the second byte set to either 0 or 1 and that ciphertext's encrypted by the + * public key should have the second byte set to 2. In RFC3447 (PKCS#1 v2.1), the second byte is supposed + * to be 2 regardless of which key is used. For compatability purposes, we'll just check to make sure the + * second byte is 2 or less. If it is, we'll accept the decrypted string as valid. + * + * As a consequence of this, a private key encrypted ciphertext produced with Crypt_RSA may not decrypt + * with a strictly PKCS#1 v1.5 compliant RSA implementation. Public key encrypted ciphertext's should but + * not private key encrypted ciphertext's. + * + * @access private + * @param String $c + * @return String + */ + function _rsaes_pkcs1_v1_5_decrypt($c) + { + // Length checking + + if (strlen($c) != $this->k) { // or if k < 11 + user_error('Decryption error', E_USER_NOTICE); + return false; + } + + // RSA decryption + + $c = $this->_os2ip($c); + $m = $this->_rsadp($c); + + if ($m === false) { + user_error('Decryption error', E_USER_NOTICE); + return false; + } + $em = $this->_i2osp($m, $this->k); + + // EME-PKCS1-v1_5 decoding + + if (ord($em[0]) != 0 || ord($em[1]) > 2) { + user_error('Decryption error', E_USER_NOTICE); + return false; + } + + $ps = substr($em, 2, strpos($em, chr(0), 2) - 2); + $m = substr($em, strlen($ps) + 3); + + if (strlen($ps) < 8) { + user_error('Decryption error', E_USER_NOTICE); + return false; + } + + // Output M + + return $m; + } + + /** + * EMSA-PSS-ENCODE + * + * See {@link http://tools.ietf.org/html/rfc3447#section-9.1.1 RFC3447#section-9.1.1}. + * + * @access private + * @param String $m + * @param Integer $emBits + */ + function _emsa_pss_encode($m, $emBits) + { + // if $m is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error + // be output. + + $emLen = ($emBits + 1) >> 3; // ie. ceil($emBits / 8) + $sLen = $this->sLen == false ? $this->hLen : $this->sLen; + + $mHash = $this->hash->hash($m); + if ($emLen < $this->hLen + $sLen + 2) { + user_error('Encoding error', E_USER_NOTICE); + return false; + } + + $salt = $this->_random($sLen); + $m2 = "\0\0\0\0\0\0\0\0" . $mHash . $salt; + $h = $this->hash->hash($m2); + $ps = str_repeat(chr(0), $emLen - $sLen - $this->hLen - 2); + $db = $ps . chr(1) . $salt; + $dbMask = $this->_mgf1($h, $emLen - $this->hLen - 1); + $maskedDB = $db ^ $dbMask; + $maskedDB[0] = ~chr(0xFF << ($emBits & 7)) & $maskedDB[0]; + $em = $maskedDB . $h . chr(0xBC); + + return $em; + } + + /** + * EMSA-PSS-VERIFY + * + * See {@link http://tools.ietf.org/html/rfc3447#section-9.1.2 RFC3447#section-9.1.2}. + * + * @access private + * @param String $m + * @param String $em + * @param Integer $emBits + * @return String + */ + function _emsa_pss_verify($m, $em, $emBits) + { + // if $m is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error + // be output. + + $emLen = ($emBits + 1) >> 3; // ie. ceil($emBits / 8); + $sLen = $this->sLen == false ? $this->hLen : $this->sLen; + + $mHash = $this->hash->hash($m); + if ($emLen < $this->hLen + $sLen + 2) { + return false; + } + + if ($em[strlen($em) - 1] != chr(0xBC)) { + return false; + } + + $maskedDB = substr($em, 0, -$this->hLen - 1); + $h = substr($em, -$this->hLen - 1, $this->hLen); + $temp = chr(0xFF << ($emBits & 7)); + if ((~$maskedDB[0] & $temp) != $temp) { + return false; + } + $dbMask = $this->_mgf1($h, $emLen - $this->hLen - 1); + $db = $maskedDB ^ $dbMask; + $db[0] = ~chr(0xFF << ($emBits & 7)) & $db[0]; + $temp = $emLen - $this->hLen - $sLen - 2; + if (substr($db, 0, $temp) != str_repeat(chr(0), $temp) || ord($db[$temp]) != 1) { + return false; + } + $salt = substr($db, $temp + 1); // should be $sLen long + $m2 = "\0\0\0\0\0\0\0\0" . $mHash . $salt; + $h2 = $this->hash->hash($m2); + return $this->_equals($h, $h2); + } + + /** + * RSASSA-PSS-SIGN + * + * See {@link http://tools.ietf.org/html/rfc3447#section-8.1.1 RFC3447#section-8.1.1}. + * + * @access private + * @param String $m + * @return String + */ + function _rsassa_pss_sign($m) + { + // EMSA-PSS encoding + + $em = $this->_emsa_pss_encode($m, 8 * $this->k - 1); + + // RSA signature + + $m = $this->_os2ip($em); + $s = $this->_rsasp1($m); + $s = $this->_i2osp($s, $this->k); + + // Output the signature S + + return $s; + } + + /** + * RSASSA-PSS-VERIFY + * + * See {@link http://tools.ietf.org/html/rfc3447#section-8.1.2 RFC3447#section-8.1.2}. + * + * @access private + * @param String $m + * @param String $s + * @return String + */ + function _rsassa_pss_verify($m, $s) + { + // Length checking + + if (strlen($s) != $this->k) { + user_error('Invalid signature', E_USER_NOTICE); + return false; + } + + // RSA verification + + $modBits = 8 * $this->k; + + $s2 = $this->_os2ip($s); + $m2 = $this->_rsavp1($s2); + if ($m2 === false) { + user_error('Invalid signature', E_USER_NOTICE); + return false; + } + $em = $this->_i2osp($m2, $modBits >> 3); + if ($em === false) { + user_error('Invalid signature', E_USER_NOTICE); + return false; + } + + // EMSA-PSS verification + + return $this->_emsa_pss_verify($m, $em, $modBits - 1); + } + + /** + * EMSA-PKCS1-V1_5-ENCODE + * + * See {@link http://tools.ietf.org/html/rfc3447#section-9.2 RFC3447#section-9.2}. + * + * @access private + * @param String $m + * @param Integer $emLen + * @return String + */ + function _emsa_pkcs1_v1_5_encode($m, $emLen) + { + $h = $this->hash->hash($m); + if ($h === false) { + return false; + } + + // see http://tools.ietf.org/html/rfc3447#page-43 + switch ($this->hashName) { + case 'md2': + $t = pack('H*', '3020300c06082a864886f70d020205000410'); + break; + case 'md5': + $t = pack('H*', '3020300c06082a864886f70d020505000410'); + break; + case 'sha1': + $t = pack('H*', '3021300906052b0e03021a05000414'); + break; + case 'sha256': + $t = pack('H*', '3031300d060960864801650304020105000420'); + break; + case 'sha384': + $t = pack('H*', '3041300d060960864801650304020205000430'); + break; + case 'sha512': + $t = pack('H*', '3051300d060960864801650304020305000440'); + } + $t.= $h; + $tLen = strlen($t); + + if ($emLen < $tLen + 11) { + user_error('Intended encoded message length too short', E_USER_NOTICE); + return false; + } + + $ps = str_repeat(chr(0xFF), $emLen - $tLen - 3); + + $em = "\0\1$ps\0$t"; + + return $em; + } + + /** + * RSASSA-PKCS1-V1_5-SIGN + * + * See {@link http://tools.ietf.org/html/rfc3447#section-8.2.1 RFC3447#section-8.2.1}. + * + * @access private + * @param String $m + * @return String + */ + function _rsassa_pkcs1_v1_5_sign($m) + { + // EMSA-PKCS1-v1_5 encoding + + $em = $this->_emsa_pkcs1_v1_5_encode($m, $this->k); + if ($em === false) { + user_error('RSA modulus too short', E_USER_NOTICE); + return false; + } + + // RSA signature + + $m = $this->_os2ip($em); + $s = $this->_rsasp1($m); + $s = $this->_i2osp($s, $this->k); + + // Output the signature S + + return $s; + } + + /** + * RSASSA-PKCS1-V1_5-VERIFY + * + * See {@link http://tools.ietf.org/html/rfc3447#section-8.2.2 RFC3447#section-8.2.2}. + * + * @access private + * @param String $m + * @return String + */ + function _rsassa_pkcs1_v1_5_verify($m, $s) + { + // Length checking + + if (strlen($s) != $this->k) { + user_error('Invalid signature', E_USER_NOTICE); + return false; + } + + // RSA verification + + $s = $this->_os2ip($s); + $m2 = $this->_rsavp1($s); + if ($m2 === false) { + user_error('Invalid signature', E_USER_NOTICE); + return false; + } + $em = $this->_i2osp($m2, $this->k); + if ($em === false) { + user_error('Invalid signature', E_USER_NOTICE); + return false; + } + + // EMSA-PKCS1-v1_5 encoding + + $em2 = $this->_emsa_pkcs1_v1_5_encode($m, $this->k); + if ($em2 === false) { + user_error('RSA modulus too short', E_USER_NOTICE); + return false; + } + + // Compare + + return $this->_equals($em, $em2); + } + + /** + * Set Encryption Mode + * + * Valid values include CRYPT_RSA_ENCRYPTION_OAEP and CRYPT_RSA_ENCRYPTION_PKCS1. + * + * @access public + * @param Integer $mode + */ + function setEncryptionMode($mode) + { + $this->encryptionMode = $mode; + } + + /** + * Set Signature Mode + * + * Valid values include CRYPT_RSA_SIGNATURE_PSS and CRYPT_RSA_SIGNATURE_PKCS1 + * + * @access public + * @param Integer $mode + */ + function setSignatureMode($mode) + { + $this->signatureMode = $mode; + } + + /** + * Encryption + * + * Both CRYPT_RSA_ENCRYPTION_OAEP and CRYPT_RSA_ENCRYPTION_PKCS1 both place limits on how long $plaintext can be. + * If $plaintext exceeds those limits it will be broken up so that it does and the resultant ciphertext's will + * be concatenated together. + * + * @see decrypt() + * @access public + * @param String $plaintext + * @return String + */ + function encrypt($plaintext) + { + switch ($this->encryptionMode) { + case CRYPT_RSA_ENCRYPTION_PKCS1: + $length = $this->k - 11; + if ($length <= 0) { + return false; + } + + $plaintext = str_split($plaintext, $length); + $ciphertext = ''; + foreach ($plaintext as $m) { + $ciphertext.= $this->_rsaes_pkcs1_v1_5_encrypt($m); + } + return $ciphertext; + //case CRYPT_RSA_ENCRYPTION_OAEP: + default: + $length = $this->k - 2 * $this->hLen - 2; + if ($length <= 0) { + return false; + } + + $plaintext = str_split($plaintext, $length); + $ciphertext = ''; + foreach ($plaintext as $m) { + $ciphertext.= $this->_rsaes_oaep_encrypt($m); + } + return $ciphertext; + } + } + + /** + * Decryption + * + * @see encrypt() + * @access public + * @param String $plaintext + * @return String + */ + function decrypt($ciphertext) + { + if ($this->k <= 0) { + return false; + } + + $ciphertext = str_split($ciphertext, $this->k); + $plaintext = ''; + + switch ($this->encryptionMode) { + case CRYPT_RSA_ENCRYPTION_PKCS1: + $decrypt = '_rsaes_pkcs1_v1_5_decrypt'; + break; + //case CRYPT_RSA_ENCRYPTION_OAEP: + default: + $decrypt = '_rsaes_oaep_decrypt'; + } + + foreach ($ciphertext as $c) { + $temp = $this->$decrypt($c); + if ($temp === false) { + return false; + } + $plaintext.= $temp; + } + + return $plaintext; + } + + /** + * Create a signature + * + * @see verify() + * @access public + * @param String $message + * @return String + */ + function sign($message) + { + if (empty($this->modulus) || empty($this->exponent)) { + return false; + } + + switch ($this->signatureMode) { + case CRYPT_RSA_SIGNATURE_PKCS1: + return $this->_rsassa_pkcs1_v1_5_sign($message); + //case CRYPT_RSA_SIGNATURE_PSS: + default: + return $this->_rsassa_pss_sign($message); + } + } + + /** + * Verifies a signature + * + * @see sign() + * @access public + * @param String $message + * @param String $signature + * @return Boolean + */ + function verify($message, $signature) + { + if (empty($this->modulus) || empty($this->exponent)) { + return false; + } + + switch ($this->signatureMode) { + case CRYPT_RSA_SIGNATURE_PKCS1: + return $this->_rsassa_pkcs1_v1_5_verify($message, $signature); + //case CRYPT_RSA_SIGNATURE_PSS: + default: + return $this->_rsassa_pss_verify($message, $signature); + } + } +} diff --git a/3rdparty/phpseclib/Crypt/Random.php b/3rdparty/phpseclib/Crypt/Random.php new file mode 100644 index 00000000000..20f9269b4c7 --- /dev/null +++ b/3rdparty/phpseclib/Crypt/Random.php @@ -0,0 +1,142 @@ + + * + * + * + * LICENSE: Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @category Crypt + * @package Crypt_Random + * @author Jim Wigginton + * @copyright MMVII Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @version $Id: Random.php,v 1.9 2010/04/24 06:40:48 terrafrost Exp $ + * @link http://phpseclib.sourceforge.net + */ + +/** + * Generate a random value. + * + * On 32-bit machines, the largest distance that can exist between $min and $max is 2**31. + * If $min and $max are farther apart than that then the last ($max - range) numbers. + * + * Depending on how this is being used, it may be worth while to write a replacement. For example, + * a PHP-based web app that stores its data in an SQL database can collect more entropy than this function + * can. + * + * @param optional Integer $min + * @param optional Integer $max + * @return Integer + * @access public + */ +function crypt_random($min = 0, $max = 0x7FFFFFFF) +{ + if ($min == $max) { + return $min; + } + + if (function_exists('openssl_random_pseudo_bytes')) { + // openssl_random_pseudo_bytes() is slow on windows per the following: + // http://stackoverflow.com/questions/1940168/openssl-random-pseudo-bytes-is-slow-php + if ((PHP_OS & "\xDF\xDF\xDF") !== 'WIN') { // PHP_OS & "\xDF\xDF\xDF" == strtoupper(substr(PHP_OS, 0, 3)), but a lot faster + extract(unpack('Nrandom', openssl_random_pseudo_bytes(4))); + + return abs($random) % ($max - $min) + $min; + } + } + + // see http://en.wikipedia.org/wiki//dev/random + static $urandom = true; + if ($urandom === true) { + // Warning's will be output unles the error suppression operator is used. Errors such as + // "open_basedir restriction in effect", "Permission denied", "No such file or directory", etc. + $urandom = @fopen('/dev/urandom', 'rb'); + } + if (!is_bool($urandom)) { + extract(unpack('Nrandom', fread($urandom, 4))); + + // say $min = 0 and $max = 3. if we didn't do abs() then we could have stuff like this: + // -4 % 3 + 0 = -1, even though -1 < $min + return abs($random) % ($max - $min) + $min; + } + + /* Prior to PHP 4.2.0, mt_srand() had to be called before mt_rand() could be called. + Prior to PHP 5.2.6, mt_rand()'s automatic seeding was subpar, as elaborated here: + + http://www.suspekt.org/2008/08/17/mt_srand-and-not-so-random-numbers/ + + The seeding routine is pretty much ripped from PHP's own internal GENERATE_SEED() macro: + + http://svn.php.net/viewvc/php/php-src/tags/php_5_3_2/ext/standard/php_rand.h?view=markup */ + if (version_compare(PHP_VERSION, '5.2.5', '<=')) { + static $seeded; + if (!isset($seeded)) { + $seeded = true; + mt_srand(fmod(time() * getmypid(), 0x7FFFFFFF) ^ fmod(1000000 * lcg_value(), 0x7FFFFFFF)); + } + } + + static $crypto; + + // The CSPRNG's Yarrow and Fortuna periodically reseed. This function can be reseeded by hitting F5 + // in the browser and reloading the page. + + if (!isset($crypto)) { + $key = $iv = ''; + for ($i = 0; $i < 8; $i++) { + $key.= pack('n', mt_rand(0, 0xFFFF)); + $iv .= pack('n', mt_rand(0, 0xFFFF)); + } + switch (true) { + case class_exists('Crypt_AES'): + $crypto = new Crypt_AES(CRYPT_AES_MODE_CTR); + break; + case class_exists('Crypt_TripleDES'): + $crypto = new Crypt_TripleDES(CRYPT_DES_MODE_CTR); + break; + case class_exists('Crypt_DES'): + $crypto = new Crypt_DES(CRYPT_DES_MODE_CTR); + break; + case class_exists('Crypt_RC4'): + $crypto = new Crypt_RC4(); + break; + default: + extract(unpack('Nrandom', pack('H*', sha1(mt_rand(0, 0x7FFFFFFF))))); + return abs($random) % ($max - $min) + $min; + } + $crypto->setKey($key); + $crypto->setIV($iv); + $crypto->enableContinuousBuffer(); + } + + extract(unpack('Nrandom', $crypto->encrypt("\0\0\0\0"))); + return abs($random) % ($max - $min) + $min; +} diff --git a/3rdparty/phpseclib/Crypt/Rijndael.php b/3rdparty/phpseclib/Crypt/Rijndael.php new file mode 100644 index 00000000000..c89cc2d14da --- /dev/null +++ b/3rdparty/phpseclib/Crypt/Rijndael.php @@ -0,0 +1,1478 @@ + + * setKey('abcdefghijklmnop'); + * + * $size = 10 * 1024; + * $plaintext = ''; + * for ($i = 0; $i < $size; $i++) { + * $plaintext.= 'a'; + * } + * + * echo $rijndael->decrypt($rijndael->encrypt($plaintext)); + * ?> + * + * + * LICENSE: Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @category Crypt + * @package Crypt_Rijndael + * @author Jim Wigginton + * @copyright MMVIII Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @version $Id: Rijndael.php,v 1.12 2010/02/09 06:10:26 terrafrost Exp $ + * @link http://phpseclib.sourceforge.net + */ + +/**#@+ + * @access public + * @see Crypt_Rijndael::encrypt() + * @see Crypt_Rijndael::decrypt() + */ +/** + * Encrypt / decrypt using the Counter mode. + * + * Set to -1 since that's what Crypt/Random.php uses to index the CTR mode. + * + * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Counter_.28CTR.29 + */ +define('CRYPT_RIJNDAEL_MODE_CTR', -1); +/** + * Encrypt / decrypt using the Electronic Code Book mode. + * + * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Electronic_codebook_.28ECB.29 + */ +define('CRYPT_RIJNDAEL_MODE_ECB', 1); +/** + * Encrypt / decrypt using the Code Book Chaining mode. + * + * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Cipher-block_chaining_.28CBC.29 + */ +define('CRYPT_RIJNDAEL_MODE_CBC', 2); +/** + * Encrypt / decrypt using the Cipher Feedback mode. + * + * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Cipher_feedback_.28CFB.29 + */ +define('CRYPT_RIJNDAEL_MODE_CFB', 3); +/** + * Encrypt / decrypt using the Cipher Feedback mode. + * + * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Output_feedback_.28OFB.29 + */ +define('CRYPT_RIJNDAEL_MODE_OFB', 4); +/**#@-*/ + +/**#@+ + * @access private + * @see Crypt_Rijndael::Crypt_Rijndael() + */ +/** + * Toggles the internal implementation + */ +define('CRYPT_RIJNDAEL_MODE_INTERNAL', 1); +/** + * Toggles the mcrypt implementation + */ +define('CRYPT_RIJNDAEL_MODE_MCRYPT', 2); +/**#@-*/ + +/** + * Pure-PHP implementation of Rijndael. + * + * @author Jim Wigginton + * @version 0.1.0 + * @access public + * @package Crypt_Rijndael + */ +class Crypt_Rijndael { + /** + * The Encryption Mode + * + * @see Crypt_Rijndael::Crypt_Rijndael() + * @var Integer + * @access private + */ + var $mode; + + /** + * The Key + * + * @see Crypt_Rijndael::setKey() + * @var String + * @access private + */ + var $key = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; + + /** + * The Initialization Vector + * + * @see Crypt_Rijndael::setIV() + * @var String + * @access private + */ + var $iv = ''; + + /** + * A "sliding" Initialization Vector + * + * @see Crypt_Rijndael::enableContinuousBuffer() + * @var String + * @access private + */ + var $encryptIV = ''; + + /** + * A "sliding" Initialization Vector + * + * @see Crypt_Rijndael::enableContinuousBuffer() + * @var String + * @access private + */ + var $decryptIV = ''; + + /** + * Continuous Buffer status + * + * @see Crypt_Rijndael::enableContinuousBuffer() + * @var Boolean + * @access private + */ + var $continuousBuffer = false; + + /** + * Padding status + * + * @see Crypt_Rijndael::enablePadding() + * @var Boolean + * @access private + */ + var $padding = true; + + /** + * Does the key schedule need to be (re)calculated? + * + * @see setKey() + * @see setBlockLength() + * @see setKeyLength() + * @var Boolean + * @access private + */ + var $changed = true; + + /** + * Has the key length explicitly been set or should it be derived from the key, itself? + * + * @see setKeyLength() + * @var Boolean + * @access private + */ + var $explicit_key_length = false; + + /** + * The Key Schedule + * + * @see _setup() + * @var Array + * @access private + */ + var $w; + + /** + * The Inverse Key Schedule + * + * @see _setup() + * @var Array + * @access private + */ + var $dw; + + /** + * The Block Length + * + * @see setBlockLength() + * @var Integer + * @access private + * @internal The max value is 32, the min value is 16. All valid values are multiples of 4. Exists in conjunction with + * $Nb because we need this value and not $Nb to pad strings appropriately. + */ + var $block_size = 16; + + /** + * The Block Length divided by 32 + * + * @see setBlockLength() + * @var Integer + * @access private + * @internal The max value is 256 / 32 = 8, the min value is 128 / 32 = 4. Exists in conjunction with $block_size + * because the encryption / decryption / key schedule creation requires this number and not $block_size. We could + * derive this from $block_size or vice versa, but that'd mean we'd have to do multiple shift operations, so in lieu + * of that, we'll just precompute it once. + * + */ + var $Nb = 4; + + /** + * The Key Length + * + * @see setKeyLength() + * @var Integer + * @access private + * @internal The max value is 256 / 8 = 32, the min value is 128 / 8 = 16. Exists in conjunction with $key_size + * because the encryption / decryption / key schedule creation requires this number and not $key_size. We could + * derive this from $key_size or vice versa, but that'd mean we'd have to do multiple shift operations, so in lieu + * of that, we'll just precompute it once. + */ + var $key_size = 16; + + /** + * The Key Length divided by 32 + * + * @see setKeyLength() + * @var Integer + * @access private + * @internal The max value is 256 / 32 = 8, the min value is 128 / 32 = 4 + */ + var $Nk = 4; + + /** + * The Number of Rounds + * + * @var Integer + * @access private + * @internal The max value is 14, the min value is 10. + */ + var $Nr; + + /** + * Shift offsets + * + * @var Array + * @access private + */ + var $c; + + /** + * Precomputed mixColumns table + * + * @see Crypt_Rijndael() + * @var Array + * @access private + */ + var $t0; + + /** + * Precomputed mixColumns table + * + * @see Crypt_Rijndael() + * @var Array + * @access private + */ + var $t1; + + /** + * Precomputed mixColumns table + * + * @see Crypt_Rijndael() + * @var Array + * @access private + */ + var $t2; + + /** + * Precomputed mixColumns table + * + * @see Crypt_Rijndael() + * @var Array + * @access private + */ + var $t3; + + /** + * Precomputed invMixColumns table + * + * @see Crypt_Rijndael() + * @var Array + * @access private + */ + var $dt0; + + /** + * Precomputed invMixColumns table + * + * @see Crypt_Rijndael() + * @var Array + * @access private + */ + var $dt1; + + /** + * Precomputed invMixColumns table + * + * @see Crypt_Rijndael() + * @var Array + * @access private + */ + var $dt2; + + /** + * Precomputed invMixColumns table + * + * @see Crypt_Rijndael() + * @var Array + * @access private + */ + var $dt3; + + /** + * Is the mode one that is paddable? + * + * @see Crypt_Rijndael::Crypt_Rijndael() + * @var Boolean + * @access private + */ + var $paddable = false; + + /** + * Encryption buffer for CTR, OFB and CFB modes + * + * @see Crypt_Rijndael::encrypt() + * @var String + * @access private + */ + var $enbuffer = array('encrypted' => '', 'xor' => ''); + + /** + * Decryption buffer for CTR, OFB and CFB modes + * + * @see Crypt_Rijndael::decrypt() + * @var String + * @access private + */ + var $debuffer = array('ciphertext' => ''); + + /** + * Default Constructor. + * + * Determines whether or not the mcrypt extension should be used. $mode should only, at present, be + * CRYPT_RIJNDAEL_MODE_ECB or CRYPT_RIJNDAEL_MODE_CBC. If not explictly set, CRYPT_RIJNDAEL_MODE_CBC will be used. + * + * @param optional Integer $mode + * @return Crypt_Rijndael + * @access public + */ + function Crypt_Rijndael($mode = CRYPT_RIJNDAEL_MODE_CBC) + { + switch ($mode) { + case CRYPT_RIJNDAEL_MODE_ECB: + case CRYPT_RIJNDAEL_MODE_CBC: + $this->paddable = true; + $this->mode = $mode; + break; + case CRYPT_RIJNDAEL_MODE_CTR: + case CRYPT_RIJNDAEL_MODE_CFB: + case CRYPT_RIJNDAEL_MODE_OFB: + $this->mode = $mode; + break; + default: + $this->paddable = true; + $this->mode = CRYPT_RIJNDAEL_MODE_CBC; + } + + $t3 = &$this->t3; + $t2 = &$this->t2; + $t1 = &$this->t1; + $t0 = &$this->t0; + + $dt3 = &$this->dt3; + $dt2 = &$this->dt2; + $dt1 = &$this->dt1; + $dt0 = &$this->dt0; + + // according to (section 5.2.1), + // precomputed tables can be used in the mixColumns phase. in that example, they're assigned t0...t3, so + // those are the names we'll use. + $t3 = array( + 0x6363A5C6, 0x7C7C84F8, 0x777799EE, 0x7B7B8DF6, 0xF2F20DFF, 0x6B6BBDD6, 0x6F6FB1DE, 0xC5C55491, + 0x30305060, 0x01010302, 0x6767A9CE, 0x2B2B7D56, 0xFEFE19E7, 0xD7D762B5, 0xABABE64D, 0x76769AEC, + 0xCACA458F, 0x82829D1F, 0xC9C94089, 0x7D7D87FA, 0xFAFA15EF, 0x5959EBB2, 0x4747C98E, 0xF0F00BFB, + 0xADADEC41, 0xD4D467B3, 0xA2A2FD5F, 0xAFAFEA45, 0x9C9CBF23, 0xA4A4F753, 0x727296E4, 0xC0C05B9B, + 0xB7B7C275, 0xFDFD1CE1, 0x9393AE3D, 0x26266A4C, 0x36365A6C, 0x3F3F417E, 0xF7F702F5, 0xCCCC4F83, + 0x34345C68, 0xA5A5F451, 0xE5E534D1, 0xF1F108F9, 0x717193E2, 0xD8D873AB, 0x31315362, 0x15153F2A, + 0x04040C08, 0xC7C75295, 0x23236546, 0xC3C35E9D, 0x18182830, 0x9696A137, 0x05050F0A, 0x9A9AB52F, + 0x0707090E, 0x12123624, 0x80809B1B, 0xE2E23DDF, 0xEBEB26CD, 0x2727694E, 0xB2B2CD7F, 0x75759FEA, + 0x09091B12, 0x83839E1D, 0x2C2C7458, 0x1A1A2E34, 0x1B1B2D36, 0x6E6EB2DC, 0x5A5AEEB4, 0xA0A0FB5B, + 0x5252F6A4, 0x3B3B4D76, 0xD6D661B7, 0xB3B3CE7D, 0x29297B52, 0xE3E33EDD, 0x2F2F715E, 0x84849713, + 0x5353F5A6, 0xD1D168B9, 0x00000000, 0xEDED2CC1, 0x20206040, 0xFCFC1FE3, 0xB1B1C879, 0x5B5BEDB6, + 0x6A6ABED4, 0xCBCB468D, 0xBEBED967, 0x39394B72, 0x4A4ADE94, 0x4C4CD498, 0x5858E8B0, 0xCFCF4A85, + 0xD0D06BBB, 0xEFEF2AC5, 0xAAAAE54F, 0xFBFB16ED, 0x4343C586, 0x4D4DD79A, 0x33335566, 0x85859411, + 0x4545CF8A, 0xF9F910E9, 0x02020604, 0x7F7F81FE, 0x5050F0A0, 0x3C3C4478, 0x9F9FBA25, 0xA8A8E34B, + 0x5151F3A2, 0xA3A3FE5D, 0x4040C080, 0x8F8F8A05, 0x9292AD3F, 0x9D9DBC21, 0x38384870, 0xF5F504F1, + 0xBCBCDF63, 0xB6B6C177, 0xDADA75AF, 0x21216342, 0x10103020, 0xFFFF1AE5, 0xF3F30EFD, 0xD2D26DBF, + 0xCDCD4C81, 0x0C0C1418, 0x13133526, 0xECEC2FC3, 0x5F5FE1BE, 0x9797A235, 0x4444CC88, 0x1717392E, + 0xC4C45793, 0xA7A7F255, 0x7E7E82FC, 0x3D3D477A, 0x6464ACC8, 0x5D5DE7BA, 0x19192B32, 0x737395E6, + 0x6060A0C0, 0x81819819, 0x4F4FD19E, 0xDCDC7FA3, 0x22226644, 0x2A2A7E54, 0x9090AB3B, 0x8888830B, + 0x4646CA8C, 0xEEEE29C7, 0xB8B8D36B, 0x14143C28, 0xDEDE79A7, 0x5E5EE2BC, 0x0B0B1D16, 0xDBDB76AD, + 0xE0E03BDB, 0x32325664, 0x3A3A4E74, 0x0A0A1E14, 0x4949DB92, 0x06060A0C, 0x24246C48, 0x5C5CE4B8, + 0xC2C25D9F, 0xD3D36EBD, 0xACACEF43, 0x6262A6C4, 0x9191A839, 0x9595A431, 0xE4E437D3, 0x79798BF2, + 0xE7E732D5, 0xC8C8438B, 0x3737596E, 0x6D6DB7DA, 0x8D8D8C01, 0xD5D564B1, 0x4E4ED29C, 0xA9A9E049, + 0x6C6CB4D8, 0x5656FAAC, 0xF4F407F3, 0xEAEA25CF, 0x6565AFCA, 0x7A7A8EF4, 0xAEAEE947, 0x08081810, + 0xBABAD56F, 0x787888F0, 0x25256F4A, 0x2E2E725C, 0x1C1C2438, 0xA6A6F157, 0xB4B4C773, 0xC6C65197, + 0xE8E823CB, 0xDDDD7CA1, 0x74749CE8, 0x1F1F213E, 0x4B4BDD96, 0xBDBDDC61, 0x8B8B860D, 0x8A8A850F, + 0x707090E0, 0x3E3E427C, 0xB5B5C471, 0x6666AACC, 0x4848D890, 0x03030506, 0xF6F601F7, 0x0E0E121C, + 0x6161A3C2, 0x35355F6A, 0x5757F9AE, 0xB9B9D069, 0x86869117, 0xC1C15899, 0x1D1D273A, 0x9E9EB927, + 0xE1E138D9, 0xF8F813EB, 0x9898B32B, 0x11113322, 0x6969BBD2, 0xD9D970A9, 0x8E8E8907, 0x9494A733, + 0x9B9BB62D, 0x1E1E223C, 0x87879215, 0xE9E920C9, 0xCECE4987, 0x5555FFAA, 0x28287850, 0xDFDF7AA5, + 0x8C8C8F03, 0xA1A1F859, 0x89898009, 0x0D0D171A, 0xBFBFDA65, 0xE6E631D7, 0x4242C684, 0x6868B8D0, + 0x4141C382, 0x9999B029, 0x2D2D775A, 0x0F0F111E, 0xB0B0CB7B, 0x5454FCA8, 0xBBBBD66D, 0x16163A2C + ); + + $dt3 = array( + 0xF4A75051, 0x4165537E, 0x17A4C31A, 0x275E963A, 0xAB6BCB3B, 0x9D45F11F, 0xFA58ABAC, 0xE303934B, + 0x30FA5520, 0x766DF6AD, 0xCC769188, 0x024C25F5, 0xE5D7FC4F, 0x2ACBD7C5, 0x35448026, 0x62A38FB5, + 0xB15A49DE, 0xBA1B6725, 0xEA0E9845, 0xFEC0E15D, 0x2F7502C3, 0x4CF01281, 0x4697A38D, 0xD3F9C66B, + 0x8F5FE703, 0x929C9515, 0x6D7AEBBF, 0x5259DA95, 0xBE832DD4, 0x7421D358, 0xE0692949, 0xC9C8448E, + 0xC2896A75, 0x8E7978F4, 0x583E6B99, 0xB971DD27, 0xE14FB6BE, 0x88AD17F0, 0x20AC66C9, 0xCE3AB47D, + 0xDF4A1863, 0x1A3182E5, 0x51336097, 0x537F4562, 0x6477E0B1, 0x6BAE84BB, 0x81A01CFE, 0x082B94F9, + 0x48685870, 0x45FD198F, 0xDE6C8794, 0x7BF8B752, 0x73D323AB, 0x4B02E272, 0x1F8F57E3, 0x55AB2A66, + 0xEB2807B2, 0xB5C2032F, 0xC57B9A86, 0x3708A5D3, 0x2887F230, 0xBFA5B223, 0x036ABA02, 0x16825CED, + 0xCF1C2B8A, 0x79B492A7, 0x07F2F0F3, 0x69E2A14E, 0xDAF4CD65, 0x05BED506, 0x34621FD1, 0xA6FE8AC4, + 0x2E539D34, 0xF355A0A2, 0x8AE13205, 0xF6EB75A4, 0x83EC390B, 0x60EFAA40, 0x719F065E, 0x6E1051BD, + 0x218AF93E, 0xDD063D96, 0x3E05AEDD, 0xE6BD464D, 0x548DB591, 0xC45D0571, 0x06D46F04, 0x5015FF60, + 0x98FB2419, 0xBDE997D6, 0x4043CC89, 0xD99E7767, 0xE842BDB0, 0x898B8807, 0x195B38E7, 0xC8EEDB79, + 0x7C0A47A1, 0x420FE97C, 0x841EC9F8, 0x00000000, 0x80868309, 0x2BED4832, 0x1170AC1E, 0x5A724E6C, + 0x0EFFFBFD, 0x8538560F, 0xAED51E3D, 0x2D392736, 0x0FD9640A, 0x5CA62168, 0x5B54D19B, 0x362E3A24, + 0x0A67B10C, 0x57E70F93, 0xEE96D2B4, 0x9B919E1B, 0xC0C54F80, 0xDC20A261, 0x774B695A, 0x121A161C, + 0x93BA0AE2, 0xA02AE5C0, 0x22E0433C, 0x1B171D12, 0x090D0B0E, 0x8BC7ADF2, 0xB6A8B92D, 0x1EA9C814, + 0xF1198557, 0x75074CAF, 0x99DDBBEE, 0x7F60FDA3, 0x01269FF7, 0x72F5BC5C, 0x663BC544, 0xFB7E345B, + 0x4329768B, 0x23C6DCCB, 0xEDFC68B6, 0xE4F163B8, 0x31DCCAD7, 0x63851042, 0x97224013, 0xC6112084, + 0x4A247D85, 0xBB3DF8D2, 0xF93211AE, 0x29A16DC7, 0x9E2F4B1D, 0xB230F3DC, 0x8652EC0D, 0xC1E3D077, + 0xB3166C2B, 0x70B999A9, 0x9448FA11, 0xE9642247, 0xFC8CC4A8, 0xF03F1AA0, 0x7D2CD856, 0x3390EF22, + 0x494EC787, 0x38D1C1D9, 0xCAA2FE8C, 0xD40B3698, 0xF581CFA6, 0x7ADE28A5, 0xB78E26DA, 0xADBFA43F, + 0x3A9DE42C, 0x78920D50, 0x5FCC9B6A, 0x7E466254, 0x8D13C2F6, 0xD8B8E890, 0x39F75E2E, 0xC3AFF582, + 0x5D80BE9F, 0xD0937C69, 0xD52DA96F, 0x2512B3CF, 0xAC993BC8, 0x187DA710, 0x9C636EE8, 0x3BBB7BDB, + 0x267809CD, 0x5918F46E, 0x9AB701EC, 0x4F9AA883, 0x956E65E6, 0xFFE67EAA, 0xBCCF0821, 0x15E8E6EF, + 0xE79BD9BA, 0x6F36CE4A, 0x9F09D4EA, 0xB07CD629, 0xA4B2AF31, 0x3F23312A, 0xA59430C6, 0xA266C035, + 0x4EBC3774, 0x82CAA6FC, 0x90D0B0E0, 0xA7D81533, 0x04984AF1, 0xECDAF741, 0xCD500E7F, 0x91F62F17, + 0x4DD68D76, 0xEFB04D43, 0xAA4D54CC, 0x9604DFE4, 0xD1B5E39E, 0x6A881B4C, 0x2C1FB8C1, 0x65517F46, + 0x5EEA049D, 0x8C355D01, 0x877473FA, 0x0B412EFB, 0x671D5AB3, 0xDBD25292, 0x105633E9, 0xD647136D, + 0xD7618C9A, 0xA10C7A37, 0xF8148E59, 0x133C89EB, 0xA927EECE, 0x61C935B7, 0x1CE5EDE1, 0x47B13C7A, + 0xD2DF599C, 0xF2733F55, 0x14CE7918, 0xC737BF73, 0xF7CDEA53, 0xFDAA5B5F, 0x3D6F14DF, 0x44DB8678, + 0xAFF381CA, 0x68C43EB9, 0x24342C38, 0xA3405FC2, 0x1DC37216, 0xE2250CBC, 0x3C498B28, 0x0D9541FF, + 0xA8017139, 0x0CB3DE08, 0xB4E49CD8, 0x56C19064, 0xCB84617B, 0x32B670D5, 0x6C5C7448, 0xB85742D0 + ); + + for ($i = 0; $i < 256; $i++) { + $t2[$i << 8] = (($t3[$i] << 8) & 0xFFFFFF00) | (($t3[$i] >> 24) & 0x000000FF); + $t1[$i << 16] = (($t3[$i] << 16) & 0xFFFF0000) | (($t3[$i] >> 16) & 0x0000FFFF); + $t0[$i << 24] = (($t3[$i] << 24) & 0xFF000000) | (($t3[$i] >> 8) & 0x00FFFFFF); + + $dt2[$i << 8] = (($this->dt3[$i] << 8) & 0xFFFFFF00) | (($dt3[$i] >> 24) & 0x000000FF); + $dt1[$i << 16] = (($this->dt3[$i] << 16) & 0xFFFF0000) | (($dt3[$i] >> 16) & 0x0000FFFF); + $dt0[$i << 24] = (($this->dt3[$i] << 24) & 0xFF000000) | (($dt3[$i] >> 8) & 0x00FFFFFF); + } + } + + /** + * Sets the key. + * + * Keys can be of any length. Rijndael, itself, requires the use of a key that's between 128-bits and 256-bits long and + * whose length is a multiple of 32. If the key is less than 256-bits and the key length isn't set, we round the length + * up to the closest valid key length, padding $key with null bytes. If the key is more than 256-bits, we trim the + * excess bits. + * + * If the key is not explicitly set, it'll be assumed to be all null bytes. + * + * @access public + * @param String $key + */ + function setKey($key) + { + $this->key = $key; + $this->changed = true; + } + + /** + * Sets the initialization vector. (optional) + * + * SetIV is not required when CRYPT_RIJNDAEL_MODE_ECB is being used. If not explictly set, it'll be assumed + * to be all zero's. + * + * @access public + * @param String $iv + */ + function setIV($iv) + { + $this->encryptIV = $this->decryptIV = $this->iv = str_pad(substr($iv, 0, $this->block_size), $this->block_size, chr(0)); + } + + /** + * Sets the key length + * + * Valid key lengths are 128, 160, 192, 224, and 256. If the length is less than 128, it will be rounded up to + * 128. If the length is greater then 128 and invalid, it will be rounded down to the closest valid amount. + * + * @access public + * @param Integer $length + */ + function setKeyLength($length) + { + $length >>= 5; + if ($length > 8) { + $length = 8; + } else if ($length < 4) { + $length = 4; + } + $this->Nk = $length; + $this->key_size = $length << 2; + + $this->explicit_key_length = true; + $this->changed = true; + } + + /** + * Sets the password. + * + * Depending on what $method is set to, setPassword()'s (optional) parameters are as follows: + * {@link http://en.wikipedia.org/wiki/PBKDF2 pbkdf2}: + * $hash, $salt, $count + * Set $dkLen by calling setKeyLength() + * + * @param String $password + * @param optional String $method + * @access public + */ + function setPassword($password, $method = 'pbkdf2') + { + $key = ''; + + switch ($method) { + default: // 'pbkdf2' + list(, , $hash, $salt, $count) = func_get_args(); + if (!isset($hash)) { + $hash = 'sha1'; + } + // WPA and WPA use the SSID as the salt + if (!isset($salt)) { + $salt = 'phpseclib/salt'; + } + // RFC2898#section-4.2 uses 1,000 iterations by default + // WPA and WPA2 use 4,096. + if (!isset($count)) { + $count = 1000; + } + + if (!class_exists('Crypt_Hash')) { + require_once('Crypt/Hash.php'); + } + + $i = 1; + while (strlen($key) < $this->key_size) { // $dkLen == $this->key_size + //$dk.= $this->_pbkdf($password, $salt, $count, $i++); + $hmac = new Crypt_Hash(); + $hmac->setHash($hash); + $hmac->setKey($password); + $f = $u = $hmac->hash($salt . pack('N', $i++)); + for ($j = 2; $j <= $count; $j++) { + $u = $hmac->hash($u); + $f^= $u; + } + $key.= $f; + } + } + + $this->setKey(substr($key, 0, $this->key_size)); + } + + /** + * Sets the block length + * + * Valid block lengths are 128, 160, 192, 224, and 256. If the length is less than 128, it will be rounded up to + * 128. If the length is greater then 128 and invalid, it will be rounded down to the closest valid amount. + * + * @access public + * @param Integer $length + */ + function setBlockLength($length) + { + $length >>= 5; + if ($length > 8) { + $length = 8; + } else if ($length < 4) { + $length = 4; + } + $this->Nb = $length; + $this->block_size = $length << 2; + $this->changed = true; + } + + /** + * Generate CTR XOR encryption key + * + * Encrypt the output of this and XOR it against the ciphertext / plaintext to get the + * plaintext / ciphertext in CTR mode. + * + * @see Crypt_Rijndael::decrypt() + * @see Crypt_Rijndael::encrypt() + * @access public + * @param Integer $length + * @param String $iv + */ + function _generate_xor($length, &$iv) + { + $xor = ''; + $block_size = $this->block_size; + $num_blocks = floor(($length + ($block_size - 1)) / $block_size); + for ($i = 0; $i < $num_blocks; $i++) { + $xor.= $iv; + for ($j = 4; $j <= $block_size; $j+=4) { + $temp = substr($iv, -$j, 4); + switch ($temp) { + case "\xFF\xFF\xFF\xFF": + $iv = substr_replace($iv, "\x00\x00\x00\x00", -$j, 4); + break; + case "\x7F\xFF\xFF\xFF": + $iv = substr_replace($iv, "\x80\x00\x00\x00", -$j, 4); + break 2; + default: + extract(unpack('Ncount', $temp)); + $iv = substr_replace($iv, pack('N', $count + 1), -$j, 4); + break 2; + } + } + } + + return $xor; + } + + /** + * Encrypts a message. + * + * $plaintext will be padded with additional bytes such that it's length is a multiple of the block size. Other Rjindael + * implementations may or may not pad in the same manner. Other common approaches to padding and the reasons why it's + * necessary are discussed in the following + * URL: + * + * {@link http://www.di-mgt.com.au/cryptopad.html http://www.di-mgt.com.au/cryptopad.html} + * + * An alternative to padding is to, separately, send the length of the file. This is what SSH, in fact, does. + * strlen($plaintext) will still need to be a multiple of 8, however, arbitrary values can be added to make it that + * length. + * + * @see Crypt_Rijndael::decrypt() + * @access public + * @param String $plaintext + */ + function encrypt($plaintext) + { + $this->_setup(); + if ($this->paddable) { + $plaintext = $this->_pad($plaintext); + } + + $block_size = $this->block_size; + $buffer = &$this->enbuffer; + $continuousBuffer = $this->continuousBuffer; + $ciphertext = ''; + switch ($this->mode) { + case CRYPT_RIJNDAEL_MODE_ECB: + for ($i = 0; $i < strlen($plaintext); $i+=$block_size) { + $ciphertext.= $this->_encryptBlock(substr($plaintext, $i, $block_size)); + } + break; + case CRYPT_RIJNDAEL_MODE_CBC: + $xor = $this->encryptIV; + for ($i = 0; $i < strlen($plaintext); $i+=$block_size) { + $block = substr($plaintext, $i, $block_size); + $block = $this->_encryptBlock($block ^ $xor); + $xor = $block; + $ciphertext.= $block; + } + if ($this->continuousBuffer) { + $this->encryptIV = $xor; + } + break; + case CRYPT_RIJNDAEL_MODE_CTR: + $xor = $this->encryptIV; + if (!empty($buffer['encrypted'])) { + for ($i = 0; $i < strlen($plaintext); $i+=$block_size) { + $block = substr($plaintext, $i, $block_size); + $buffer['encrypted'].= $this->_encryptBlock($this->_generate_xor($block_size, $xor)); + $key = $this->_string_shift($buffer['encrypted'], $block_size); + $ciphertext.= $block ^ $key; + } + } else { + for ($i = 0; $i < strlen($plaintext); $i+=$block_size) { + $block = substr($plaintext, $i, $block_size); + $key = $this->_encryptBlock($this->_generate_xor($block_size, $xor)); + $ciphertext.= $block ^ $key; + } + } + if ($this->continuousBuffer) { + $this->encryptIV = $xor; + if ($start = strlen($plaintext) % $block_size) { + $buffer['encrypted'] = substr($key, $start) . $buffer['encrypted']; + } + } + break; + case CRYPT_RIJNDAEL_MODE_CFB: + if (!empty($buffer['xor'])) { + $ciphertext = $plaintext ^ $buffer['xor']; + $iv = $buffer['encrypted'] . $ciphertext; + $start = strlen($ciphertext); + $buffer['encrypted'].= $ciphertext; + $buffer['xor'] = substr($buffer['xor'], strlen($ciphertext)); + } else { + $ciphertext = ''; + $iv = $this->encryptIV; + $start = 0; + } + + for ($i = $start; $i < strlen($plaintext); $i+=$block_size) { + $block = substr($plaintext, $i, $block_size); + $xor = $this->_encryptBlock($iv); + $iv = $block ^ $xor; + if ($continuousBuffer && strlen($iv) != $block_size) { + $buffer = array( + 'encrypted' => $iv, + 'xor' => substr($xor, strlen($iv)) + ); + } + $ciphertext.= $iv; + } + + if ($this->continuousBuffer) { + $this->encryptIV = $iv; + } + break; + case CRYPT_RIJNDAEL_MODE_OFB: + $xor = $this->encryptIV; + if (strlen($buffer)) { + for ($i = 0; $i < strlen($plaintext); $i+=$block_size) { + $xor = $this->_encryptBlock($xor); + $buffer.= $xor; + $key = $this->_string_shift($buffer, $block_size); + $ciphertext.= substr($plaintext, $i, $block_size) ^ $key; + } + } else { + for ($i = 0; $i < strlen($plaintext); $i+=$block_size) { + $xor = $this->_encryptBlock($xor); + $ciphertext.= substr($plaintext, $i, $block_size) ^ $xor; + } + $key = $xor; + } + if ($this->continuousBuffer) { + $this->encryptIV = $xor; + if ($start = strlen($plaintext) % $block_size) { + $buffer = substr($key, $start) . $buffer; + } + } + } + + return $ciphertext; + } + + /** + * Decrypts a message. + * + * If strlen($ciphertext) is not a multiple of the block size, null bytes will be added to the end of the string until + * it is. + * + * @see Crypt_Rijndael::encrypt() + * @access public + * @param String $ciphertext + */ + function decrypt($ciphertext) + { + $this->_setup(); + + if ($this->paddable) { + // we pad with chr(0) since that's what mcrypt_generic does. to quote from http://php.net/function.mcrypt-generic : + // "The data is padded with "\0" to make sure the length of the data is n * blocksize." + $ciphertext = str_pad($ciphertext, strlen($ciphertext) + ($this->block_size - strlen($ciphertext) % $this->block_size) % $this->block_size, chr(0)); + } + + $block_size = $this->block_size; + $buffer = &$this->debuffer; + $continuousBuffer = $this->continuousBuffer; + $plaintext = ''; + switch ($this->mode) { + case CRYPT_RIJNDAEL_MODE_ECB: + for ($i = 0; $i < strlen($ciphertext); $i+=$block_size) { + $plaintext.= $this->_decryptBlock(substr($ciphertext, $i, $block_size)); + } + break; + case CRYPT_RIJNDAEL_MODE_CBC: + $xor = $this->decryptIV; + for ($i = 0; $i < strlen($ciphertext); $i+=$block_size) { + $block = substr($ciphertext, $i, $block_size); + $plaintext.= $this->_decryptBlock($block) ^ $xor; + $xor = $block; + } + if ($this->continuousBuffer) { + $this->decryptIV = $xor; + } + break; + case CRYPT_RIJNDAEL_MODE_CTR: + $xor = $this->decryptIV; + if (!empty($buffer['ciphertext'])) { + for ($i = 0; $i < strlen($ciphertext); $i+=$block_size) { + $block = substr($ciphertext, $i, $block_size); + $buffer['ciphertext'].= $this->_encryptBlock($this->_generate_xor($block_size, $xor)); + $key = $this->_string_shift($buffer['ciphertext'], $block_size); + $plaintext.= $block ^ $key; + } + } else { + for ($i = 0; $i < strlen($ciphertext); $i+=$block_size) { + $block = substr($ciphertext, $i, $block_size); + $key = $this->_encryptBlock($this->_generate_xor($block_size, $xor)); + $plaintext.= $block ^ $key; + } + } + if ($this->continuousBuffer) { + $this->decryptIV = $xor; + if ($start = strlen($ciphertext) % $block_size) { + $buffer['ciphertext'] = substr($key, $start) . $buffer['encrypted']; + } + } + break; + case CRYPT_RIJNDAEL_MODE_CFB: + if (!empty($buffer['ciphertext'])) { + $plaintext = $ciphertext ^ substr($this->decryptIV, strlen($buffer['ciphertext'])); + $buffer['ciphertext'].= substr($ciphertext, 0, strlen($plaintext)); + if (strlen($buffer['ciphertext']) == $block_size) { + $xor = $this->_encryptBlock($buffer['ciphertext']); + $buffer['ciphertext'] = ''; + } + $start = strlen($plaintext); + $block = $this->decryptIV; + } else { + $plaintext = ''; + $xor = $this->_encryptBlock($this->decryptIV); + $start = 0; + } + + for ($i = $start; $i < strlen($ciphertext); $i+=$block_size) { + $block = substr($ciphertext, $i, $block_size); + $plaintext.= $block ^ $xor; + if ($continuousBuffer && strlen($block) != $block_size) { + $buffer['ciphertext'].= $block; + $block = $xor; + } else if (strlen($block) == $block_size) { + $xor = $this->_encryptBlock($block); + } + } + if ($this->continuousBuffer) { + $this->decryptIV = $block; + } + break; + case CRYPT_RIJNDAEL_MODE_OFB: + $xor = $this->decryptIV; + if (strlen($buffer)) { + for ($i = 0; $i < strlen($ciphertext); $i+=$block_size) { + $xor = $this->_encryptBlock($xor); + $buffer.= $xor; + $key = $this->_string_shift($buffer, $block_size); + $plaintext.= substr($ciphertext, $i, $block_size) ^ $key; + } + } else { + for ($i = 0; $i < strlen($ciphertext); $i+=$block_size) { + $xor = $this->_encryptBlock($xor); + $plaintext.= substr($ciphertext, $i, $block_size) ^ $xor; + } + $key = $xor; + } + if ($this->continuousBuffer) { + $this->decryptIV = $xor; + if ($start = strlen($ciphertext) % $block_size) { + $buffer = substr($key, $start) . $buffer; + } + } + } + + return $this->paddable ? $this->_unpad($plaintext) : $plaintext; + } + + /** + * Encrypts a block + * + * @access private + * @param String $in + * @return String + */ + function _encryptBlock($in) + { + $state = array(); + $words = unpack('N*word', $in); + + $w = $this->w; + $t0 = $this->t0; + $t1 = $this->t1; + $t2 = $this->t2; + $t3 = $this->t3; + $Nb = $this->Nb; + $Nr = $this->Nr; + $c = $this->c; + + // addRoundKey + $i = 0; + foreach ($words as $word) { + $state[] = $word ^ $w[0][$i++]; + } + + // fips-197.pdf#page=19, "Figure 5. Pseudo Code for the Cipher", states that this loop has four components - + // subBytes, shiftRows, mixColumns, and addRoundKey. fips-197.pdf#page=30, "Implementation Suggestions Regarding + // Various Platforms" suggests that performs enhanced implementations are described in Rijndael-ammended.pdf. + // Rijndael-ammended.pdf#page=20, "Implementation aspects / 32-bit processor", discusses such an optimization. + // Unfortunately, the description given there is not quite correct. Per aes.spec.v316.pdf#page=19 [1], + // equation (7.4.7) is supposed to use addition instead of subtraction, so we'll do that here, as well. + + // [1] http://fp.gladman.plus.com/cryptography_technology/rijndael/aes.spec.v316.pdf + $temp = array(); + for ($round = 1; $round < $Nr; $round++) { + $i = 0; // $c[0] == 0 + $j = $c[1]; + $k = $c[2]; + $l = $c[3]; + + while ($i < $this->Nb) { + $temp[$i] = $t0[$state[$i] & 0xFF000000] ^ + $t1[$state[$j] & 0x00FF0000] ^ + $t2[$state[$k] & 0x0000FF00] ^ + $t3[$state[$l] & 0x000000FF] ^ + $w[$round][$i]; + $i++; + $j = ($j + 1) % $Nb; + $k = ($k + 1) % $Nb; + $l = ($l + 1) % $Nb; + } + + for ($i = 0; $i < $Nb; $i++) { + $state[$i] = $temp[$i]; + } + } + + // subWord + for ($i = 0; $i < $Nb; $i++) { + $state[$i] = $this->_subWord($state[$i]); + } + + // shiftRows + addRoundKey + $i = 0; // $c[0] == 0 + $j = $c[1]; + $k = $c[2]; + $l = $c[3]; + while ($i < $this->Nb) { + $temp[$i] = ($state[$i] & 0xFF000000) ^ + ($state[$j] & 0x00FF0000) ^ + ($state[$k] & 0x0000FF00) ^ + ($state[$l] & 0x000000FF) ^ + $w[$Nr][$i]; + $i++; + $j = ($j + 1) % $Nb; + $k = ($k + 1) % $Nb; + $l = ($l + 1) % $Nb; + } + $state = $temp; + + array_unshift($state, 'N*'); + + return call_user_func_array('pack', $state); + } + + /** + * Decrypts a block + * + * @access private + * @param String $in + * @return String + */ + function _decryptBlock($in) + { + $state = array(); + $words = unpack('N*word', $in); + + $num_states = count($state); + $dw = $this->dw; + $dt0 = $this->dt0; + $dt1 = $this->dt1; + $dt2 = $this->dt2; + $dt3 = $this->dt3; + $Nb = $this->Nb; + $Nr = $this->Nr; + $c = $this->c; + + // addRoundKey + $i = 0; + foreach ($words as $word) { + $state[] = $word ^ $dw[$Nr][$i++]; + } + + $temp = array(); + for ($round = $Nr - 1; $round > 0; $round--) { + $i = 0; // $c[0] == 0 + $j = $Nb - $c[1]; + $k = $Nb - $c[2]; + $l = $Nb - $c[3]; + + while ($i < $Nb) { + $temp[$i] = $dt0[$state[$i] & 0xFF000000] ^ + $dt1[$state[$j] & 0x00FF0000] ^ + $dt2[$state[$k] & 0x0000FF00] ^ + $dt3[$state[$l] & 0x000000FF] ^ + $dw[$round][$i]; + $i++; + $j = ($j + 1) % $Nb; + $k = ($k + 1) % $Nb; + $l = ($l + 1) % $Nb; + } + + for ($i = 0; $i < $Nb; $i++) { + $state[$i] = $temp[$i]; + } + } + + // invShiftRows + invSubWord + addRoundKey + $i = 0; // $c[0] == 0 + $j = $Nb - $c[1]; + $k = $Nb - $c[2]; + $l = $Nb - $c[3]; + + while ($i < $Nb) { + $temp[$i] = $dw[0][$i] ^ + $this->_invSubWord(($state[$i] & 0xFF000000) | + ($state[$j] & 0x00FF0000) | + ($state[$k] & 0x0000FF00) | + ($state[$l] & 0x000000FF)); + $i++; + $j = ($j + 1) % $Nb; + $k = ($k + 1) % $Nb; + $l = ($l + 1) % $Nb; + } + + $state = $temp; + + array_unshift($state, 'N*'); + + return call_user_func_array('pack', $state); + } + + /** + * Setup Rijndael + * + * Validates all the variables and calculates $Nr - the number of rounds that need to be performed - and $w - the key + * key schedule. + * + * @access private + */ + function _setup() + { + // Each number in $rcon is equal to the previous number multiplied by two in Rijndael's finite field. + // See http://en.wikipedia.org/wiki/Finite_field_arithmetic#Multiplicative_inverse + static $rcon = array(0, + 0x01000000, 0x02000000, 0x04000000, 0x08000000, 0x10000000, + 0x20000000, 0x40000000, 0x80000000, 0x1B000000, 0x36000000, + 0x6C000000, 0xD8000000, 0xAB000000, 0x4D000000, 0x9A000000, + 0x2F000000, 0x5E000000, 0xBC000000, 0x63000000, 0xC6000000, + 0x97000000, 0x35000000, 0x6A000000, 0xD4000000, 0xB3000000, + 0x7D000000, 0xFA000000, 0xEF000000, 0xC5000000, 0x91000000 + ); + + if (!$this->changed) { + return; + } + + if (!$this->explicit_key_length) { + // we do >> 2, here, and not >> 5, as we do above, since strlen($this->key) tells us the number of bytes - not bits + $length = strlen($this->key) >> 2; + if ($length > 8) { + $length = 8; + } else if ($length < 4) { + $length = 4; + } + $this->Nk = $length; + $this->key_size = $length << 2; + } + + $this->key = str_pad(substr($this->key, 0, $this->key_size), $this->key_size, chr(0)); + $this->encryptIV = $this->decryptIV = $this->iv = str_pad(substr($this->iv, 0, $this->block_size), $this->block_size, chr(0)); + + // see Rijndael-ammended.pdf#page=44 + $this->Nr = max($this->Nk, $this->Nb) + 6; + + // shift offsets for Nb = 5, 7 are defined in Rijndael-ammended.pdf#page=44, + // "Table 8: Shift offsets in Shiftrow for the alternative block lengths" + // shift offsets for Nb = 4, 6, 8 are defined in Rijndael-ammended.pdf#page=14, + // "Table 2: Shift offsets for different block lengths" + switch ($this->Nb) { + case 4: + case 5: + case 6: + $this->c = array(0, 1, 2, 3); + break; + case 7: + $this->c = array(0, 1, 2, 4); + break; + case 8: + $this->c = array(0, 1, 3, 4); + } + + $key = $this->key; + + $w = array_values(unpack('N*words', $key)); + + $length = $this->Nb * ($this->Nr + 1); + for ($i = $this->Nk; $i < $length; $i++) { + $temp = $w[$i - 1]; + if ($i % $this->Nk == 0) { + // according to , "the size of an integer is platform-dependent". + // on a 32-bit machine, it's 32-bits, and on a 64-bit machine, it's 64-bits. on a 32-bit machine, + // 0xFFFFFFFF << 8 == 0xFFFFFF00, but on a 64-bit machine, it equals 0xFFFFFFFF00. as such, doing 'and' + // with 0xFFFFFFFF (or 0xFFFFFF00) on a 32-bit machine is unnecessary, but on a 64-bit machine, it is. + $temp = (($temp << 8) & 0xFFFFFF00) | (($temp >> 24) & 0x000000FF); // rotWord + $temp = $this->_subWord($temp) ^ $rcon[$i / $this->Nk]; + } else if ($this->Nk > 6 && $i % $this->Nk == 4) { + $temp = $this->_subWord($temp); + } + $w[$i] = $w[$i - $this->Nk] ^ $temp; + } + + // convert the key schedule from a vector of $Nb * ($Nr + 1) length to a matrix with $Nr + 1 rows and $Nb columns + // and generate the inverse key schedule. more specifically, + // according to (section 5.3.3), + // "The key expansion for the Inverse Cipher is defined as follows: + // 1. Apply the Key Expansion. + // 2. Apply InvMixColumn to all Round Keys except the first and the last one." + // also, see fips-197.pdf#page=27, "5.3.5 Equivalent Inverse Cipher" + $temp = array(); + for ($i = $row = $col = 0; $i < $length; $i++, $col++) { + if ($col == $this->Nb) { + if ($row == 0) { + $this->dw[0] = $this->w[0]; + } else { + // subWord + invMixColumn + invSubWord = invMixColumn + $j = 0; + while ($j < $this->Nb) { + $dw = $this->_subWord($this->w[$row][$j]); + $temp[$j] = $this->dt0[$dw & 0xFF000000] ^ + $this->dt1[$dw & 0x00FF0000] ^ + $this->dt2[$dw & 0x0000FF00] ^ + $this->dt3[$dw & 0x000000FF]; + $j++; + } + $this->dw[$row] = $temp; + } + + $col = 0; + $row++; + } + $this->w[$row][$col] = $w[$i]; + } + + $this->dw[$row] = $this->w[$row]; + + $this->changed = false; + } + + /** + * Performs S-Box substitutions + * + * @access private + */ + function _subWord($word) + { + static $sbox0, $sbox1, $sbox2, $sbox3; + + if (empty($sbox0)) { + $sbox0 = array( + 0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76, + 0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0, + 0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15, + 0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75, + 0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84, + 0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF, + 0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8, + 0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2, + 0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73, + 0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB, + 0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79, + 0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08, + 0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A, + 0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E, + 0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF, + 0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16 + ); + + $sbox1 = array(); + $sbox2 = array(); + $sbox3 = array(); + + for ($i = 0; $i < 256; $i++) { + $sbox1[$i << 8] = $sbox0[$i] << 8; + $sbox2[$i << 16] = $sbox0[$i] << 16; + $sbox3[$i << 24] = $sbox0[$i] << 24; + } + } + + return $sbox0[$word & 0x000000FF] | + $sbox1[$word & 0x0000FF00] | + $sbox2[$word & 0x00FF0000] | + $sbox3[$word & 0xFF000000]; + } + + /** + * Performs inverse S-Box substitutions + * + * @access private + */ + function _invSubWord($word) + { + static $sbox0, $sbox1, $sbox2, $sbox3; + + if (empty($sbox0)) { + $sbox0 = array( + 0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38, 0xBF, 0x40, 0xA3, 0x9E, 0x81, 0xF3, 0xD7, 0xFB, + 0x7C, 0xE3, 0x39, 0x82, 0x9B, 0x2F, 0xFF, 0x87, 0x34, 0x8E, 0x43, 0x44, 0xC4, 0xDE, 0xE9, 0xCB, + 0x54, 0x7B, 0x94, 0x32, 0xA6, 0xC2, 0x23, 0x3D, 0xEE, 0x4C, 0x95, 0x0B, 0x42, 0xFA, 0xC3, 0x4E, + 0x08, 0x2E, 0xA1, 0x66, 0x28, 0xD9, 0x24, 0xB2, 0x76, 0x5B, 0xA2, 0x49, 0x6D, 0x8B, 0xD1, 0x25, + 0x72, 0xF8, 0xF6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xD4, 0xA4, 0x5C, 0xCC, 0x5D, 0x65, 0xB6, 0x92, + 0x6C, 0x70, 0x48, 0x50, 0xFD, 0xED, 0xB9, 0xDA, 0x5E, 0x15, 0x46, 0x57, 0xA7, 0x8D, 0x9D, 0x84, + 0x90, 0xD8, 0xAB, 0x00, 0x8C, 0xBC, 0xD3, 0x0A, 0xF7, 0xE4, 0x58, 0x05, 0xB8, 0xB3, 0x45, 0x06, + 0xD0, 0x2C, 0x1E, 0x8F, 0xCA, 0x3F, 0x0F, 0x02, 0xC1, 0xAF, 0xBD, 0x03, 0x01, 0x13, 0x8A, 0x6B, + 0x3A, 0x91, 0x11, 0x41, 0x4F, 0x67, 0xDC, 0xEA, 0x97, 0xF2, 0xCF, 0xCE, 0xF0, 0xB4, 0xE6, 0x73, + 0x96, 0xAC, 0x74, 0x22, 0xE7, 0xAD, 0x35, 0x85, 0xE2, 0xF9, 0x37, 0xE8, 0x1C, 0x75, 0xDF, 0x6E, + 0x47, 0xF1, 0x1A, 0x71, 0x1D, 0x29, 0xC5, 0x89, 0x6F, 0xB7, 0x62, 0x0E, 0xAA, 0x18, 0xBE, 0x1B, + 0xFC, 0x56, 0x3E, 0x4B, 0xC6, 0xD2, 0x79, 0x20, 0x9A, 0xDB, 0xC0, 0xFE, 0x78, 0xCD, 0x5A, 0xF4, + 0x1F, 0xDD, 0xA8, 0x33, 0x88, 0x07, 0xC7, 0x31, 0xB1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xEC, 0x5F, + 0x60, 0x51, 0x7F, 0xA9, 0x19, 0xB5, 0x4A, 0x0D, 0x2D, 0xE5, 0x7A, 0x9F, 0x93, 0xC9, 0x9C, 0xEF, + 0xA0, 0xE0, 0x3B, 0x4D, 0xAE, 0x2A, 0xF5, 0xB0, 0xC8, 0xEB, 0xBB, 0x3C, 0x83, 0x53, 0x99, 0x61, + 0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D + ); + + $sbox1 = array(); + $sbox2 = array(); + $sbox3 = array(); + + for ($i = 0; $i < 256; $i++) { + $sbox1[$i << 8] = $sbox0[$i] << 8; + $sbox2[$i << 16] = $sbox0[$i] << 16; + $sbox3[$i << 24] = $sbox0[$i] << 24; + } + } + + return $sbox0[$word & 0x000000FF] | + $sbox1[$word & 0x0000FF00] | + $sbox2[$word & 0x00FF0000] | + $sbox3[$word & 0xFF000000]; + } + + /** + * Pad "packets". + * + * Rijndael works by encrypting between sixteen and thirty-two bytes at a time, provided that number is also a multiple + * of four. If you ever need to encrypt or decrypt something that isn't of the proper length, it becomes necessary to + * pad the input so that it is of the proper length. + * + * Padding is enabled by default. Sometimes, however, it is undesirable to pad strings. Such is the case in SSH, + * where "packets" are padded with random bytes before being encrypted. Unpad these packets and you risk stripping + * away characters that shouldn't be stripped away. (SSH knows how many bytes are added because the length is + * transmitted separately) + * + * @see Crypt_Rijndael::disablePadding() + * @access public + */ + function enablePadding() + { + $this->padding = true; + } + + /** + * Do not pad packets. + * + * @see Crypt_Rijndael::enablePadding() + * @access public + */ + function disablePadding() + { + $this->padding = false; + } + + /** + * Pads a string + * + * Pads a string using the RSA PKCS padding standards so that its length is a multiple of the blocksize. + * $block_size - (strlen($text) % $block_size) bytes are added, each of which is equal to + * chr($block_size - (strlen($text) % $block_size) + * + * If padding is disabled and $text is not a multiple of the blocksize, the string will be padded regardless + * and padding will, hence forth, be enabled. + * + * @see Crypt_Rijndael::_unpad() + * @access private + */ + function _pad($text) + { + $length = strlen($text); + + if (!$this->padding) { + if ($length % $this->block_size == 0) { + return $text; + } else { + user_error("The plaintext's length ($length) is not a multiple of the block size ({$this->block_size})", E_USER_NOTICE); + $this->padding = true; + } + } + + $pad = $this->block_size - ($length % $this->block_size); + + return str_pad($text, $length + $pad, chr($pad)); + } + + /** + * Unpads a string. + * + * If padding is enabled and the reported padding length is invalid the encryption key will be assumed to be wrong + * and false will be returned. + * + * @see Crypt_Rijndael::_pad() + * @access private + */ + function _unpad($text) + { + if (!$this->padding) { + return $text; + } + + $length = ord($text[strlen($text) - 1]); + + if (!$length || $length > $this->block_size) { + return false; + } + + return substr($text, 0, -$length); + } + + /** + * Treat consecutive "packets" as if they are a continuous buffer. + * + * Say you have a 32-byte plaintext $plaintext. Using the default behavior, the two following code snippets + * will yield different outputs: + * + * + * echo $rijndael->encrypt(substr($plaintext, 0, 16)); + * echo $rijndael->encrypt(substr($plaintext, 16, 16)); + * + * + * echo $rijndael->encrypt($plaintext); + * + * + * The solution is to enable the continuous buffer. Although this will resolve the above discrepancy, it creates + * another, as demonstrated with the following: + * + * + * $rijndael->encrypt(substr($plaintext, 0, 16)); + * echo $rijndael->decrypt($des->encrypt(substr($plaintext, 16, 16))); + * + * + * echo $rijndael->decrypt($des->encrypt(substr($plaintext, 16, 16))); + * + * + * With the continuous buffer disabled, these would yield the same output. With it enabled, they yield different + * outputs. The reason is due to the fact that the initialization vector's change after every encryption / + * decryption round when the continuous buffer is enabled. When it's disabled, they remain constant. + * + * Put another way, when the continuous buffer is enabled, the state of the Crypt_Rijndael() object changes after each + * encryption / decryption round, whereas otherwise, it'd remain constant. For this reason, it's recommended that + * continuous buffers not be used. They do offer better security and are, in fact, sometimes required (SSH uses them), + * however, they are also less intuitive and more likely to cause you problems. + * + * @see Crypt_Rijndael::disableContinuousBuffer() + * @access public + */ + function enableContinuousBuffer() + { + $this->continuousBuffer = true; + } + + /** + * Treat consecutive packets as if they are a discontinuous buffer. + * + * The default behavior. + * + * @see Crypt_Rijndael::enableContinuousBuffer() + * @access public + */ + function disableContinuousBuffer() + { + $this->continuousBuffer = false; + $this->encryptIV = $this->iv; + $this->decryptIV = $this->iv; + } + + /** + * String Shift + * + * Inspired by array_shift + * + * @param String $string + * @param optional Integer $index + * @return String + * @access private + */ + function _string_shift(&$string, $index = 1) + { + $substr = substr($string, 0, $index); + $string = substr($string, $index); + return $substr; + } +} + +// vim: ts=4:sw=4:et: +// vim6: fdl=1: diff --git a/3rdparty/phpseclib/Crypt/TripleDES.php b/3rdparty/phpseclib/Crypt/TripleDES.php new file mode 100644 index 00000000000..8ad51d830f7 --- /dev/null +++ b/3rdparty/phpseclib/Crypt/TripleDES.php @@ -0,0 +1,1061 @@ + + * setKey('abcdefghijklmnopqrstuvwx'); + * + * $size = 10 * 1024; + * $plaintext = ''; + * for ($i = 0; $i < $size; $i++) { + * $plaintext.= 'a'; + * } + * + * echo $des->decrypt($des->encrypt($plaintext)); + * ?> + * + * + * LICENSE: Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @category Crypt + * @package Crypt_TripleDES + * @author Jim Wigginton + * @copyright MMVII Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @version $Id: TripleDES.php,v 1.13 2010/02/26 03:40:25 terrafrost Exp $ + * @link http://phpseclib.sourceforge.net + */ + +/** + * Include Crypt_DES + */ +if (!class_exists('Crypt_DES')) { + require_once('DES.php'); +} + +/** + * Encrypt / decrypt using inner chaining + * + * Inner chaining is used by SSH-1 and is generally considered to be less secure then outer chaining (CRYPT_DES_MODE_CBC3). + */ +define('CRYPT_DES_MODE_3CBC', -2); + +/** + * Encrypt / decrypt using outer chaining + * + * Outer chaining is used by SSH-2 and when the mode is set to CRYPT_DES_MODE_CBC. + */ +define('CRYPT_DES_MODE_CBC3', CRYPT_DES_MODE_CBC); + +/** + * Pure-PHP implementation of Triple DES. + * + * @author Jim Wigginton + * @version 0.1.0 + * @access public + * @package Crypt_TerraDES + */ +class Crypt_TripleDES { + /** + * The Three Keys + * + * @see Crypt_TripleDES::setKey() + * @var String + * @access private + */ + var $key = "\0\0\0\0\0\0\0\0"; + + /** + * The Encryption Mode + * + * @see Crypt_TripleDES::Crypt_TripleDES() + * @var Integer + * @access private + */ + var $mode = CRYPT_DES_MODE_CBC; + + /** + * Continuous Buffer status + * + * @see Crypt_TripleDES::enableContinuousBuffer() + * @var Boolean + * @access private + */ + var $continuousBuffer = false; + + /** + * Padding status + * + * @see Crypt_TripleDES::enablePadding() + * @var Boolean + * @access private + */ + var $padding = true; + + /** + * The Initialization Vector + * + * @see Crypt_TripleDES::setIV() + * @var String + * @access private + */ + var $iv = "\0\0\0\0\0\0\0\0"; + + /** + * A "sliding" Initialization Vector + * + * @see Crypt_TripleDES::enableContinuousBuffer() + * @var String + * @access private + */ + var $encryptIV = "\0\0\0\0\0\0\0\0"; + + /** + * A "sliding" Initialization Vector + * + * @see Crypt_TripleDES::enableContinuousBuffer() + * @var String + * @access private + */ + var $decryptIV = "\0\0\0\0\0\0\0\0"; + + /** + * The Crypt_DES objects + * + * @var Array + * @access private + */ + var $des; + + /** + * mcrypt resource for encryption + * + * The mcrypt resource can be recreated every time something needs to be created or it can be created just once. + * Since mcrypt operates in continuous mode, by default, it'll need to be recreated when in non-continuous mode. + * + * @see Crypt_TripleDES::encrypt() + * @var String + * @access private + */ + var $enmcrypt; + + /** + * mcrypt resource for decryption + * + * The mcrypt resource can be recreated every time something needs to be created or it can be created just once. + * Since mcrypt operates in continuous mode, by default, it'll need to be recreated when in non-continuous mode. + * + * @see Crypt_TripleDES::decrypt() + * @var String + * @access private + */ + var $demcrypt; + + /** + * Does the enmcrypt resource need to be (re)initialized? + * + * @see Crypt_TripleDES::setKey() + * @see Crypt_TripleDES::setIV() + * @var Boolean + * @access private + */ + var $enchanged = true; + + /** + * Does the demcrypt resource need to be (re)initialized? + * + * @see Crypt_TripleDES::setKey() + * @see Crypt_TripleDES::setIV() + * @var Boolean + * @access private + */ + var $dechanged = true; + + /** + * Is the mode one that is paddable? + * + * @see Crypt_TripleDES::Crypt_TripleDES() + * @var Boolean + * @access private + */ + var $paddable = false; + + /** + * Encryption buffer for CTR, OFB and CFB modes + * + * @see Crypt_TripleDES::encrypt() + * @var String + * @access private + */ + var $enbuffer = ''; + + /** + * Decryption buffer for CTR, OFB and CFB modes + * + * @see Crypt_TripleDES::decrypt() + * @var String + * @access private + */ + var $debuffer = ''; + + /** + * mcrypt resource for CFB mode + * + * @see Crypt_TripleDES::encrypt() + * @see Crypt_TripleDES::decrypt() + * @var String + * @access private + */ + var $ecb; + + /** + * Default Constructor. + * + * Determines whether or not the mcrypt extension should be used. $mode should only, at present, be + * CRYPT_DES_MODE_ECB or CRYPT_DES_MODE_CBC. If not explictly set, CRYPT_DES_MODE_CBC will be used. + * + * @param optional Integer $mode + * @return Crypt_TripleDES + * @access public + */ + function Crypt_TripleDES($mode = CRYPT_DES_MODE_CBC) + { + if ( !defined('CRYPT_DES_MODE') ) { + switch (true) { + case extension_loaded('mcrypt') && in_array('tripledes', mcrypt_list_algorithms()): + define('CRYPT_DES_MODE', CRYPT_DES_MODE_MCRYPT); + break; + default: + define('CRYPT_DES_MODE', CRYPT_DES_MODE_INTERNAL); + } + } + + if ( $mode == CRYPT_DES_MODE_3CBC ) { + $this->mode = CRYPT_DES_MODE_3CBC; + $this->des = array( + new Crypt_DES(CRYPT_DES_MODE_CBC), + new Crypt_DES(CRYPT_DES_MODE_CBC), + new Crypt_DES(CRYPT_DES_MODE_CBC) + ); + $this->paddable = true; + + // we're going to be doing the padding, ourselves, so disable it in the Crypt_DES objects + $this->des[0]->disablePadding(); + $this->des[1]->disablePadding(); + $this->des[2]->disablePadding(); + + return; + } + + switch ( CRYPT_DES_MODE ) { + case CRYPT_DES_MODE_MCRYPT: + switch ($mode) { + case CRYPT_DES_MODE_ECB: + $this->paddable = true; + $this->mode = MCRYPT_MODE_ECB; + break; + case CRYPT_DES_MODE_CTR: + $this->mode = 'ctr'; + break; + case CRYPT_DES_MODE_CFB: + $this->mode = 'ncfb'; + break; + case CRYPT_DES_MODE_OFB: + $this->mode = MCRYPT_MODE_NOFB; + break; + case CRYPT_DES_MODE_CBC: + default: + $this->paddable = true; + $this->mode = MCRYPT_MODE_CBC; + } + + break; + default: + $this->des = array( + new Crypt_DES(CRYPT_DES_MODE_ECB), + new Crypt_DES(CRYPT_DES_MODE_ECB), + new Crypt_DES(CRYPT_DES_MODE_ECB) + ); + + // we're going to be doing the padding, ourselves, so disable it in the Crypt_DES objects + $this->des[0]->disablePadding(); + $this->des[1]->disablePadding(); + $this->des[2]->disablePadding(); + + switch ($mode) { + case CRYPT_DES_MODE_ECB: + case CRYPT_DES_MODE_CBC: + $this->paddable = true; + $this->mode = $mode; + break; + case CRYPT_DES_MODE_CTR: + case CRYPT_DES_MODE_CFB: + case CRYPT_DES_MODE_OFB: + $this->mode = $mode; + break; + default: + $this->paddable = true; + $this->mode = CRYPT_DES_MODE_CBC; + } + } + } + + /** + * Sets the key. + * + * Keys can be of any length. Triple DES, itself, can use 128-bit (eg. strlen($key) == 16) or + * 192-bit (eg. strlen($key) == 24) keys. This function pads and truncates $key as appropriate. + * + * DES also requires that every eighth bit be a parity bit, however, we'll ignore that. + * + * If the key is not explicitly set, it'll be assumed to be all zero's. + * + * @access public + * @param String $key + */ + function setKey($key) + { + $length = strlen($key); + if ($length > 8) { + $key = str_pad($key, 24, chr(0)); + // if $key is between 64 and 128-bits, use the first 64-bits as the last, per this: + // http://php.net/function.mcrypt-encrypt#47973 + //$key = $length <= 16 ? substr_replace($key, substr($key, 0, 8), 16) : substr($key, 0, 24); + } else { + $key = str_pad($key, 8, chr(0)); + } + $this->key = $key; + switch (true) { + case CRYPT_DES_MODE == CRYPT_DES_MODE_INTERNAL: + case $this->mode == CRYPT_DES_MODE_3CBC: + $this->des[0]->setKey(substr($key, 0, 8)); + $this->des[1]->setKey(substr($key, 8, 8)); + $this->des[2]->setKey(substr($key, 16, 8)); + } + $this->enchanged = $this->dechanged = true; + } + + /** + * Sets the password. + * + * Depending on what $method is set to, setPassword()'s (optional) parameters are as follows: + * {@link http://en.wikipedia.org/wiki/PBKDF2 pbkdf2}: + * $hash, $salt, $method + * + * @param String $password + * @param optional String $method + * @access public + */ + function setPassword($password, $method = 'pbkdf2') + { + $key = ''; + + switch ($method) { + default: // 'pbkdf2' + list(, , $hash, $salt, $count) = func_get_args(); + if (!isset($hash)) { + $hash = 'sha1'; + } + // WPA and WPA use the SSID as the salt + if (!isset($salt)) { + $salt = 'phpseclib'; + } + // RFC2898#section-4.2 uses 1,000 iterations by default + // WPA and WPA2 use 4,096. + if (!isset($count)) { + $count = 1000; + } + + if (!class_exists('Crypt_Hash')) { + require_once('Crypt/Hash.php'); + } + + $i = 1; + while (strlen($key) < 24) { // $dkLen == 24 + $hmac = new Crypt_Hash(); + $hmac->setHash($hash); + $hmac->setKey($password); + $f = $u = $hmac->hash($salt . pack('N', $i++)); + for ($j = 2; $j <= $count; $j++) { + $u = $hmac->hash($u); + $f^= $u; + } + $key.= $f; + } + } + + $this->setKey($key); + } + + /** + * Sets the initialization vector. (optional) + * + * SetIV is not required when CRYPT_DES_MODE_ECB is being used. If not explictly set, it'll be assumed + * to be all zero's. + * + * @access public + * @param String $iv + */ + function setIV($iv) + { + $this->encryptIV = $this->decryptIV = $this->iv = str_pad(substr($iv, 0, 8), 8, chr(0)); + if ($this->mode == CRYPT_DES_MODE_3CBC) { + $this->des[0]->setIV($iv); + $this->des[1]->setIV($iv); + $this->des[2]->setIV($iv); + } + $this->enchanged = $this->dechanged = true; + } + + /** + * Generate CTR XOR encryption key + * + * Encrypt the output of this and XOR it against the ciphertext / plaintext to get the + * plaintext / ciphertext in CTR mode. + * + * @see Crypt_TripleDES::decrypt() + * @see Crypt_TripleDES::encrypt() + * @access private + * @param Integer $length + * @param String $iv + */ + function _generate_xor($length, &$iv) + { + $xor = ''; + $num_blocks = ($length + 7) >> 3; + for ($i = 0; $i < $num_blocks; $i++) { + $xor.= $iv; + for ($j = 4; $j <= 8; $j+=4) { + $temp = substr($iv, -$j, 4); + switch ($temp) { + case "\xFF\xFF\xFF\xFF": + $iv = substr_replace($iv, "\x00\x00\x00\x00", -$j, 4); + break; + case "\x7F\xFF\xFF\xFF": + $iv = substr_replace($iv, "\x80\x00\x00\x00", -$j, 4); + break 2; + default: + extract(unpack('Ncount', $temp)); + $iv = substr_replace($iv, pack('N', $count + 1), -$j, 4); + break 2; + } + } + } + + return $xor; + } + + /** + * Encrypts a message. + * + * @access public + * @param String $plaintext + */ + function encrypt($plaintext) + { + if ($this->paddable) { + $plaintext = $this->_pad($plaintext); + } + + // if the key is smaller then 8, do what we'd normally do + if ($this->mode == CRYPT_DES_MODE_3CBC && strlen($this->key) > 8) { + $ciphertext = $this->des[2]->encrypt($this->des[1]->decrypt($this->des[0]->encrypt($plaintext))); + + return $ciphertext; + } + + if ( CRYPT_DES_MODE == CRYPT_DES_MODE_MCRYPT ) { + if ($this->enchanged) { + if (!isset($this->enmcrypt)) { + $this->enmcrypt = mcrypt_module_open(MCRYPT_3DES, '', $this->mode, ''); + } + mcrypt_generic_init($this->enmcrypt, $this->key, $this->encryptIV); + if ($this->mode != 'ncfb') { + $this->enchanged = false; + } + } + + if ($this->mode != 'ncfb') { + $ciphertext = mcrypt_generic($this->enmcrypt, $plaintext); + } else { + if ($this->enchanged) { + $this->ecb = mcrypt_module_open(MCRYPT_3DES, '', MCRYPT_MODE_ECB, ''); + mcrypt_generic_init($this->ecb, $this->key, "\0\0\0\0\0\0\0\0"); + $this->enchanged = false; + } + + if (strlen($this->enbuffer)) { + $ciphertext = $plaintext ^ substr($this->encryptIV, strlen($this->enbuffer)); + $this->enbuffer.= $ciphertext; + if (strlen($this->enbuffer) == 8) { + $this->encryptIV = $this->enbuffer; + $this->enbuffer = ''; + mcrypt_generic_init($this->enmcrypt, $this->key, $this->encryptIV); + } + $plaintext = substr($plaintext, strlen($ciphertext)); + } else { + $ciphertext = ''; + } + + $last_pos = strlen($plaintext) & 0xFFFFFFF8; + $ciphertext.= $last_pos ? mcrypt_generic($this->enmcrypt, substr($plaintext, 0, $last_pos)) : ''; + + if (strlen($plaintext) & 0x7) { + if (strlen($ciphertext)) { + $this->encryptIV = substr($ciphertext, -8); + } + $this->encryptIV = mcrypt_generic($this->ecb, $this->encryptIV); + $this->enbuffer = substr($plaintext, $last_pos) ^ $this->encryptIV; + $ciphertext.= $this->enbuffer; + } + } + + if (!$this->continuousBuffer) { + mcrypt_generic_init($this->enmcrypt, $this->key, $this->encryptIV); + } + + return $ciphertext; + } + + if (strlen($this->key) <= 8) { + $this->des[0]->mode = $this->mode; + + return $this->des[0]->encrypt($plaintext); + } + + $des = $this->des; + + $buffer = &$this->enbuffer; + $continuousBuffer = $this->continuousBuffer; + $ciphertext = ''; + switch ($this->mode) { + case CRYPT_DES_MODE_ECB: + for ($i = 0; $i < strlen($plaintext); $i+=8) { + $block = substr($plaintext, $i, 8); + // all of these _processBlock calls could, in theory, be put in a function - say Crypt_TripleDES::_ede_encrypt() or something. + // only problem with that: it would slow encryption and decryption down. $this->des would have to be called every time that + // function is called, instead of once for the whole string of text that's being encrypted, which would, in turn, make + // encryption and decryption take more time, per this: + // + // http://blog.libssh2.org/index.php?/archives/21-Compiled-Variables.html + $block = $des[0]->_processBlock($block, CRYPT_DES_ENCRYPT); + $block = $des[1]->_processBlock($block, CRYPT_DES_DECRYPT); + $block = $des[2]->_processBlock($block, CRYPT_DES_ENCRYPT); + $ciphertext.= $block; + } + break; + case CRYPT_DES_MODE_CBC: + $xor = $this->encryptIV; + for ($i = 0; $i < strlen($plaintext); $i+=8) { + $block = substr($plaintext, $i, 8) ^ $xor; + $block = $des[0]->_processBlock($block, CRYPT_DES_ENCRYPT); + $block = $des[1]->_processBlock($block, CRYPT_DES_DECRYPT); + $block = $des[2]->_processBlock($block, CRYPT_DES_ENCRYPT); + $xor = $block; + $ciphertext.= $block; + } + if ($this->continuousBuffer) { + $this->encryptIV = $xor; + } + break; + case CRYPT_DES_MODE_CTR: + $xor = $this->encryptIV; + if (strlen($buffer['encrypted'])) { + for ($i = 0; $i < strlen($plaintext); $i+=8) { + $block = substr($plaintext, $i, 8); + $key = $this->_generate_xor(8, $xor); + $key = $des[0]->_processBlock($key, CRYPT_DES_ENCRYPT); + $key = $des[1]->_processBlock($key, CRYPT_DES_DECRYPT); + $key = $des[2]->_processBlock($key, CRYPT_DES_ENCRYPT); + $buffer['encrypted'].= $key; + $key = $this->_string_shift($buffer['encrypted'], 8); + $ciphertext.= $block ^ $key; + } + } else { + for ($i = 0; $i < strlen($plaintext); $i+=8) { + $block = substr($plaintext, $i, 8); + $key = $this->_generate_xor(8, $xor); + $key = $des[0]->_processBlock($key, CRYPT_DES_ENCRYPT); + $key = $des[1]->_processBlock($key, CRYPT_DES_DECRYPT); + $key = $des[2]->_processBlock($key, CRYPT_DES_ENCRYPT); + $ciphertext.= $block ^ $key; + } + } + if ($this->continuousBuffer) { + $this->encryptIV = $xor; + if ($start = strlen($plaintext) & 7) { + $buffer['encrypted'] = substr($key, $start) . $buffer; + } + } + break; + case CRYPT_DES_MODE_CFB: + if (!empty($buffer['xor'])) { + $ciphertext = $plaintext ^ $buffer['xor']; + $iv = $buffer['encrypted'] . $ciphertext; + $start = strlen($ciphertext); + $buffer['encrypted'].= $ciphertext; + $buffer['xor'] = substr($buffer['xor'], strlen($ciphertext)); + } else { + $ciphertext = ''; + $iv = $this->encryptIV; + $start = 0; + } + + for ($i = $start; $i < strlen($plaintext); $i+=8) { + $block = substr($plaintext, $i, 8); + $iv = $des[0]->_processBlock($iv, CRYPT_DES_ENCRYPT); + $iv = $des[1]->_processBlock($iv, CRYPT_DES_DECRYPT); + $xor= $des[2]->_processBlock($iv, CRYPT_DES_ENCRYPT); + + $iv = $block ^ $xor; + if ($continuousBuffer && strlen($iv) != 8) { + $buffer = array( + 'encrypted' => $iv, + 'xor' => substr($xor, strlen($iv)) + ); + } + $ciphertext.= $iv; + } + + if ($this->continuousBuffer) { + $this->encryptIV = $iv; + } + break; + case CRYPT_DES_MODE_OFB: + $xor = $this->encryptIV; + if (strlen($buffer)) { + for ($i = 0; $i < strlen($plaintext); $i+=8) { + $xor = $des[0]->_processBlock($xor, CRYPT_DES_ENCRYPT); + $xor = $des[1]->_processBlock($xor, CRYPT_DES_DECRYPT); + $xor = $des[2]->_processBlock($xor, CRYPT_DES_ENCRYPT); + $buffer.= $xor; + $key = $this->_string_shift($buffer, 8); + $ciphertext.= substr($plaintext, $i, 8) ^ $key; + } + } else { + for ($i = 0; $i < strlen($plaintext); $i+=8) { + $xor = $des[0]->_processBlock($xor, CRYPT_DES_ENCRYPT); + $xor = $des[1]->_processBlock($xor, CRYPT_DES_DECRYPT); + $xor = $des[2]->_processBlock($xor, CRYPT_DES_ENCRYPT); + $ciphertext.= substr($plaintext, $i, 8) ^ $xor; + } + $key = $xor; + } + if ($this->continuousBuffer) { + $this->encryptIV = $xor; + if ($start = strlen($plaintext) & 7) { + $buffer = substr($key, $start) . $buffer; + } + } + } + + return $ciphertext; + } + + /** + * Decrypts a message. + * + * @access public + * @param String $ciphertext + */ + function decrypt($ciphertext) + { + if ($this->mode == CRYPT_DES_MODE_3CBC && strlen($this->key) > 8) { + $plaintext = $this->des[0]->decrypt($this->des[1]->encrypt($this->des[2]->decrypt($ciphertext))); + + return $this->_unpad($plaintext); + } + + if ($this->paddable) { + // we pad with chr(0) since that's what mcrypt_generic does. to quote from http://php.net/function.mcrypt-generic : + // "The data is padded with "\0" to make sure the length of the data is n * blocksize." + $ciphertext = str_pad($ciphertext, (strlen($ciphertext) + 7) & 0xFFFFFFF8, chr(0)); + } + + if ( CRYPT_DES_MODE == CRYPT_DES_MODE_MCRYPT ) { + if ($this->dechanged) { + if (!isset($this->demcrypt)) { + $this->demcrypt = mcrypt_module_open(MCRYPT_3DES, '', $this->mode, ''); + } + mcrypt_generic_init($this->demcrypt, $this->key, $this->decryptIV); + if ($this->mode != 'ncfb') { + $this->dechanged = false; + } + } + + if ($this->mode != 'ncfb') { + $plaintext = mdecrypt_generic($this->demcrypt, $ciphertext); + } else { + if ($this->dechanged) { + $this->ecb = mcrypt_module_open(MCRYPT_3DES, '', MCRYPT_MODE_ECB, ''); + mcrypt_generic_init($this->ecb, $this->key, "\0\0\0\0\0\0\0\0"); + $this->dechanged = false; + } + + if (strlen($this->debuffer)) { + $plaintext = $ciphertext ^ substr($this->decryptIV, strlen($this->debuffer)); + + $this->debuffer.= substr($ciphertext, 0, strlen($plaintext)); + if (strlen($this->debuffer) == 8) { + $this->decryptIV = $this->debuffer; + $this->debuffer = ''; + mcrypt_generic_init($this->demcrypt, $this->key, $this->decryptIV); + } + $ciphertext = substr($ciphertext, strlen($plaintext)); + } else { + $plaintext = ''; + } + + $last_pos = strlen($ciphertext) & 0xFFFFFFF8; + $plaintext.= $last_pos ? mdecrypt_generic($this->demcrypt, substr($ciphertext, 0, $last_pos)) : ''; + + if (strlen($ciphertext) & 0x7) { + if (strlen($plaintext)) { + $this->decryptIV = substr($ciphertext, $last_pos - 8, 8); + } + $this->decryptIV = mcrypt_generic($this->ecb, $this->decryptIV); + $this->debuffer = substr($ciphertext, $last_pos); + $plaintext.= $this->debuffer ^ $this->decryptIV; + } + + return $plaintext; + } + + if (!$this->continuousBuffer) { + mcrypt_generic_init($this->demcrypt, $this->key, $this->decryptIV); + } + + return $this->paddable ? $this->_unpad($plaintext) : $plaintext; + } + + if (strlen($this->key) <= 8) { + $this->des[0]->mode = $this->mode; + $plaintext = $this->des[0]->decrypt($ciphertext); + return $this->paddable ? $this->_unpad($plaintext) : $plaintext; + } + + $des = $this->des; + + $buffer = &$this->enbuffer; + $continuousBuffer = $this->continuousBuffer; + $plaintext = ''; + switch ($this->mode) { + case CRYPT_DES_MODE_ECB: + for ($i = 0; $i < strlen($ciphertext); $i+=8) { + $block = substr($ciphertext, $i, 8); + $block = $des[2]->_processBlock($block, CRYPT_DES_DECRYPT); + $block = $des[1]->_processBlock($block, CRYPT_DES_ENCRYPT); + $block = $des[0]->_processBlock($block, CRYPT_DES_DECRYPT); + $plaintext.= $block; + } + break; + case CRYPT_DES_MODE_CBC: + $xor = $this->decryptIV; + for ($i = 0; $i < strlen($ciphertext); $i+=8) { + $orig = $block = substr($ciphertext, $i, 8); + $block = $des[2]->_processBlock($block, CRYPT_DES_DECRYPT); + $block = $des[1]->_processBlock($block, CRYPT_DES_ENCRYPT); + $block = $des[0]->_processBlock($block, CRYPT_DES_DECRYPT); + $plaintext.= $block ^ $xor; + $xor = $orig; + } + if ($this->continuousBuffer) { + $this->decryptIV = $xor; + } + break; + case CRYPT_DES_MODE_CTR: + $xor = $this->decryptIV; + if (strlen($buffer['ciphertext'])) { + for ($i = 0; $i < strlen($ciphertext); $i+=8) { + $block = substr($ciphertext, $i, 8); + $key = $this->_generate_xor(8, $xor); + $key = $des[0]->_processBlock($key, CRYPT_DES_ENCRYPT); + $key = $des[1]->_processBlock($key, CRYPT_DES_DECRYPT); + $key = $des[2]->_processBlock($key, CRYPT_DES_ENCRYPT); + $buffer['ciphertext'].= $key; + $key = $this->_string_shift($buffer['ciphertext'], 8); + $plaintext.= $block ^ $key; + } + } else { + for ($i = 0; $i < strlen($ciphertext); $i+=8) { + $block = substr($ciphertext, $i, 8); + $key = $this->_generate_xor(8, $xor); + $key = $des[0]->_processBlock($key, CRYPT_DES_ENCRYPT); + $key = $des[1]->_processBlock($key, CRYPT_DES_DECRYPT); + $key = $des[2]->_processBlock($key, CRYPT_DES_ENCRYPT); + $plaintext.= $block ^ $key; + } + } + if ($this->continuousBuffer) { + $this->decryptIV = $xor; + if ($start = strlen($plaintext) & 7) { + $buffer['ciphertext'] = substr($key, $start) . $buffer['ciphertext']; + } + } + break; + case CRYPT_DES_MODE_CFB: + if (!empty($buffer['ciphertext'])) { + $plaintext = $ciphertext ^ substr($this->decryptIV, strlen($buffer['ciphertext'])); + $buffer['ciphertext'].= substr($ciphertext, 0, strlen($plaintext)); + if (strlen($buffer['ciphertext']) == 8) { + $xor = $des[0]->_processBlock($buffer['ciphertext'], CRYPT_DES_ENCRYPT); + $xor = $des[1]->_processBlock($xor, CRYPT_DES_DECRYPT); + $xor = $des[2]->_processBlock($xor, CRYPT_DES_ENCRYPT); + $buffer['ciphertext'] = ''; + } + $start = strlen($plaintext); + $block = $this->decryptIV; + } else { + $plaintext = ''; + $xor = $des[0]->_processBlock($this->decryptIV, CRYPT_DES_ENCRYPT); + $xor = $des[1]->_processBlock($xor, CRYPT_DES_DECRYPT); + $xor = $des[2]->_processBlock($xor, CRYPT_DES_ENCRYPT); + $start = 0; + } + + for ($i = $start; $i < strlen($ciphertext); $i+=8) { + $block = substr($ciphertext, $i, 8); + $plaintext.= $block ^ $xor; + if ($continuousBuffer && strlen($block) != 8) { + $buffer['ciphertext'].= $block; + $block = $xor; + } else if (strlen($block) == 8) { + $xor = $des[0]->_processBlock($block, CRYPT_DES_ENCRYPT); + $xor = $des[1]->_processBlock($xor, CRYPT_DES_DECRYPT); + $xor = $des[2]->_processBlock($xor, CRYPT_DES_ENCRYPT); + } + } + if ($this->continuousBuffer) { + $this->decryptIV = $block; + } + break; + case CRYPT_DES_MODE_OFB: + $xor = $this->decryptIV; + if (strlen($buffer)) { + for ($i = 0; $i < strlen($ciphertext); $i+=8) { + $xor = $des[0]->_processBlock($xor, CRYPT_DES_ENCRYPT); + $xor = $des[1]->_processBlock($xor, CRYPT_DES_DECRYPT); + $xor = $des[2]->_processBlock($xor, CRYPT_DES_ENCRYPT); + $buffer.= $xor; + $key = $this->_string_shift($buffer, 8); + $plaintext.= substr($ciphertext, $i, 8) ^ $key; + } + } else { + for ($i = 0; $i < strlen($ciphertext); $i+=8) { + $xor = $des[0]->_processBlock($xor, CRYPT_DES_ENCRYPT); + $xor = $des[1]->_processBlock($xor, CRYPT_DES_DECRYPT); + $xor = $des[2]->_processBlock($xor, CRYPT_DES_ENCRYPT); + $plaintext.= substr($ciphertext, $i, 8) ^ $xor; + } + $key = $xor; + } + if ($this->continuousBuffer) { + $this->decryptIV = $xor; + if ($start = strlen($ciphertext) & 7) { + $buffer = substr($key, $start) . $buffer; + } + } + } + + return $this->paddable ? $this->_unpad($plaintext) : $plaintext; + } + + /** + * Treat consecutive "packets" as if they are a continuous buffer. + * + * Say you have a 16-byte plaintext $plaintext. Using the default behavior, the two following code snippets + * will yield different outputs: + * + * + * echo $des->encrypt(substr($plaintext, 0, 8)); + * echo $des->encrypt(substr($plaintext, 8, 8)); + * + * + * echo $des->encrypt($plaintext); + * + * + * The solution is to enable the continuous buffer. Although this will resolve the above discrepancy, it creates + * another, as demonstrated with the following: + * + * + * $des->encrypt(substr($plaintext, 0, 8)); + * echo $des->decrypt($des->encrypt(substr($plaintext, 8, 8))); + * + * + * echo $des->decrypt($des->encrypt(substr($plaintext, 8, 8))); + * + * + * With the continuous buffer disabled, these would yield the same output. With it enabled, they yield different + * outputs. The reason is due to the fact that the initialization vector's change after every encryption / + * decryption round when the continuous buffer is enabled. When it's disabled, they remain constant. + * + * Put another way, when the continuous buffer is enabled, the state of the Crypt_DES() object changes after each + * encryption / decryption round, whereas otherwise, it'd remain constant. For this reason, it's recommended that + * continuous buffers not be used. They do offer better security and are, in fact, sometimes required (SSH uses them), + * however, they are also less intuitive and more likely to cause you problems. + * + * @see Crypt_TripleDES::disableContinuousBuffer() + * @access public + */ + function enableContinuousBuffer() + { + $this->continuousBuffer = true; + if ($this->mode == CRYPT_DES_MODE_3CBC) { + $this->des[0]->enableContinuousBuffer(); + $this->des[1]->enableContinuousBuffer(); + $this->des[2]->enableContinuousBuffer(); + } + } + + /** + * Treat consecutive packets as if they are a discontinuous buffer. + * + * The default behavior. + * + * @see Crypt_TripleDES::enableContinuousBuffer() + * @access public + */ + function disableContinuousBuffer() + { + $this->continuousBuffer = false; + $this->encryptIV = $this->iv; + $this->decryptIV = $this->iv; + + if ($this->mode == CRYPT_DES_MODE_3CBC) { + $this->des[0]->disableContinuousBuffer(); + $this->des[1]->disableContinuousBuffer(); + $this->des[2]->disableContinuousBuffer(); + } + } + + /** + * Pad "packets". + * + * DES works by encrypting eight bytes at a time. If you ever need to encrypt or decrypt something that's not + * a multiple of eight, it becomes necessary to pad the input so that it's length is a multiple of eight. + * + * Padding is enabled by default. Sometimes, however, it is undesirable to pad strings. Such is the case in SSH1, + * where "packets" are padded with random bytes before being encrypted. Unpad these packets and you risk stripping + * away characters that shouldn't be stripped away. (SSH knows how many bytes are added because the length is + * transmitted separately) + * + * @see Crypt_TripleDES::disablePadding() + * @access public + */ + function enablePadding() + { + $this->padding = true; + } + + /** + * Do not pad packets. + * + * @see Crypt_TripleDES::enablePadding() + * @access public + */ + function disablePadding() + { + $this->padding = false; + } + + /** + * Pads a string + * + * Pads a string using the RSA PKCS padding standards so that its length is a multiple of the blocksize (8). + * 8 - (strlen($text) & 7) bytes are added, each of which is equal to chr(8 - (strlen($text) & 7) + * + * If padding is disabled and $text is not a multiple of the blocksize, the string will be padded regardless + * and padding will, hence forth, be enabled. + * + * @see Crypt_TripleDES::_unpad() + * @access private + */ + function _pad($text) + { + $length = strlen($text); + + if (!$this->padding) { + if (($length & 7) == 0) { + return $text; + } else { + user_error("The plaintext's length ($length) is not a multiple of the block size (8)", E_USER_NOTICE); + $this->padding = true; + } + } + + $pad = 8 - ($length & 7); + return str_pad($text, $length + $pad, chr($pad)); + } + + /** + * Unpads a string + * + * If padding is enabled and the reported padding length is invalid the encryption key will be assumed to be wrong + * and false will be returned. + * + * @see Crypt_TripleDES::_pad() + * @access private + */ + function _unpad($text) + { + if (!$this->padding) { + return $text; + } + + $length = ord($text[strlen($text) - 1]); + + if (!$length || $length > 8) { + return false; + } + + return substr($text, 0, -$length); + } + + /** + * String Shift + * + * Inspired by array_shift + * + * @param String $string + * @param optional Integer $index + * @return String + * @access private + */ + function _string_shift(&$string, $index = 1) + { + $substr = substr($string, 0, $index); + $string = substr($string, $index); + return $substr; + } +} + +// vim: ts=4:sw=4:et: +// vim6: fdl=1: diff --git a/3rdparty/phpseclib/File/ANSI.php b/3rdparty/phpseclib/File/ANSI.php new file mode 100644 index 00000000000..e6cb9e5db44 --- /dev/null +++ b/3rdparty/phpseclib/File/ANSI.php @@ -0,0 +1,540 @@ + + * @copyright MMXII Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @version $Id$ + * @link htp://phpseclib.sourceforge.net + */ + +/** + * Pure-PHP ANSI Decoder + * + * @author Jim Wigginton + * @version 0.3.0 + * @access public + * @package File_ANSI + */ +class File_ANSI { + /** + * Max Width + * + * @var Integer + * @access private + */ + var $max_x; + + /** + * Max Height + * + * @var Integer + * @access private + */ + var $max_y; + + /** + * Max History + * + * @var Integer + * @access private + */ + var $max_history; + + /** + * History + * + * @var Array + * @access private + */ + var $history; + + /** + * History Attributes + * + * @var Array + * @access private + */ + var $history_attrs; + + /** + * Current Column + * + * @var Integer + * @access private + */ + var $x; + + /** + * Current Row + * + * @var Integer + * @access private + */ + var $y; + + /** + * Old Column + * + * @var Integer + * @access private + */ + var $old_x; + + /** + * Old Row + * + * @var Integer + * @access private + */ + var $old_y; + + /** + * An empty attribute row + * + * @var Array + * @access private + */ + var $attr_row; + + /** + * The current screen text + * + * @var Array + * @access private + */ + var $screen; + + /** + * The current screen attributes + * + * @var Array + * @access private + */ + var $attrs; + + /** + * The current foreground color + * + * @var String + * @access private + */ + var $foreground; + + /** + * The current background color + * + * @var String + * @access private + */ + var $background; + + /** + * Bold flag + * + * @var Boolean + * @access private + */ + var $bold; + + /** + * Underline flag + * + * @var Boolean + * @access private + */ + var $underline; + + /** + * Blink flag + * + * @var Boolean + * @access private + */ + var $blink; + + /** + * Reverse flag + * + * @var Boolean + * @access private + */ + var $reverse; + + /** + * Color flag + * + * @var Boolean + * @access private + */ + var $color; + + /** + * Current ANSI code + * + * @var String + * @access private + */ + var $ansi; + + /** + * Default Constructor. + * + * @return File_ANSI + * @access public + */ + function File_ANSI() + { + $this->setHistory(200); + $this->setDimensions(80, 24); + } + + /** + * Set terminal width and height + * + * Resets the screen as well + * + * @param Integer $x + * @param Integer $y + * @access public + */ + function setDimensions($x, $y) + { + $this->max_x = $x - 1; + $this->max_y = $y - 1; + $this->x = $this->y = 0; + $this->history = $this->history_attrs = array(); + $this->attr_row = array_fill(0, $this->max_x + 1, ''); + $this->screen = array_fill(0, $this->max_y + 1, ''); + $this->attrs = array_fill(0, $this->max_y + 1, $this->attr_row); + $this->foreground = 'white'; + $this->background = 'black'; + $this->bold = false; + $this->underline = false; + $this->blink = false; + $this->reverse = false; + $this->color = false; + + $this->ansi = ''; + } + + /** + * Set the number of lines that should be logged past the terminal height + * + * @param Integer $x + * @param Integer $y + * @access public + */ + function setHistory($history) + { + $this->max_history = $history; + } + + /** + * Load a string + * + * @param String $source + * @access public + */ + function loadString($source) + { + $this->setDimensions($this->max_x + 1, $this->max_y + 1); + $this->appendString($source); + } + + /** + * Appdend a string + * + * @param String $source + * @access public + */ + function appendString($source) + { + for ($i = 0; $i < strlen($source); $i++) { + if (strlen($this->ansi)) { + $this->ansi.= $source[$i]; + $chr = ord($source[$i]); + // http://en.wikipedia.org/wiki/ANSI_escape_code#Sequence_elements + // single character CSI's not currently supported + switch (true) { + case $this->ansi == "\x1B=": + $this->ansi = ''; + continue 2; + case strlen($this->ansi) == 2 && $chr >= 64 && $chr <= 95 && $chr != ord('['): + case strlen($this->ansi) > 2 && $chr >= 64 && $chr <= 126: + break; + default: + continue 2; + } + // http://ascii-table.com/ansi-escape-sequences-vt-100.php + switch ($this->ansi) { + case "\x1B[H": + $this->old_x = $this->x; + $this->old_y = $this->y; + $this->x = $this->y = 0; + break; + case "\x1B[J": + $this->history = array_merge($this->history, array_slice(array_splice($this->screen, $this->y + 1), 0, $this->old_y)); + $this->screen = array_merge($this->screen, array_fill($this->y, $this->max_y, '')); + + $this->history_attrs = array_merge($this->history_attrs, array_slice(array_splice($this->attrs, $this->y + 1), 0, $this->old_y)); + $this->attrs = array_merge($this->attrs, array_fill($this->y, $this->max_y, $this->attr_row)); + + if (count($this->history) == $this->max_history) { + array_shift($this->history); + array_shift($this->history_attrs); + } + case "\x1B[K": + $this->screen[$this->y] = substr($this->screen[$this->y], 0, $this->x); + + array_splice($this->attrs[$this->y], $this->x + 1); + break; + case "\x1B[?1h": // set cursor key to application + break; + default: + switch (true) { + case preg_match('#\x1B\[(\d+);(\d+)H#', $this->ansi, $match): + $this->old_x = $this->x; + $this->old_y = $this->y; + $this->x = $match[2] - 1; + $this->y = $match[1] - 1; + break; + case preg_match('#\x1B\[(\d+)C#', $this->ansi, $match): + $this->old_x = $this->x; + $x = $match[1] - 1; + break; + case preg_match('#\x1B\[(\d+);(\d+)r#', $this->ansi, $match): // Set top and bottom lines of a window + break; + case preg_match('#\x1B\[(\d*(?:;\d*)*)m#', $this->ansi, $match): + $mods = explode(';', $match[1]); + foreach ($mods as $mod) { + switch ($mod) { + case 0: + $this->attrs[$this->y][$this->x] = ''; + + if ($this->bold) $this->attrs[$this->y][$this->x].= ''; + if ($this->underline) $this->attrs[$this->y][$this->x].= ''; + if ($this->blink) $this->attrs[$this->y][$this->x].= ''; + if ($this->color) $this->attrs[$this->y][$this->x].= ''; + + if ($this->reverse) { + $temp = $this->background; + $this->background = $this->foreground; + $this->foreground = $temp; + } + + $this->bold = $this->underline = $this->blink = $this->color = $this->reverse = false; + break; + case 1: + if (!$this->bold) { + $this->attrs[$this->y][$this->x] = ''; + $this->bold = true; + } + break; + case 4: + if (!$this->underline) { + $this->attrs[$this->y][$this->x] = ''; + $this->underline = true; + } + break; + case 5: + if (!$this->blink) { + $this->attrs[$this->y][$this->x] = ''; + $this->blink = true; + } + break; + case 7: + $this->reverse = !$this->reverse; + $temp = $this->background; + $this->background = $this->foreground; + $this->foreground = $temp; + $this->attrs[$this->y][$this->x] = ''; + if ($this->color) { + $this->attrs[$this->y][$this->x] = '' . $this->attrs[$this->y][$this->x]; + } + $this->color = true; + break; + default: + //$front = $this->reverse ? &$this->background : &$this->foreground; + $front = &$this->{ $this->reverse ? 'background' : 'foreground' }; + //$back = $this->reverse ? &$this->foreground : &$this->background; + $back = &$this->{ $this->reverse ? 'foreground' : 'background' }; + switch ($mod) { + case 30: $front = 'black'; break; + case 31: $front = 'red'; break; + case 32: $front = 'green'; break; + case 33: $front = 'yellow'; break; + case 34: $front = 'blue'; break; + case 35: $front = 'magenta'; break; + case 36: $front = 'cyan'; break; + case 37: $front = 'white'; break; + + case 40: $back = 'black'; break; + case 41: $back = 'red'; break; + case 42: $back = 'green'; break; + case 43: $back = 'yellow'; break; + case 44: $back = 'blue'; break; + case 45: $back = 'magenta'; break; + case 46: $back = 'cyan'; break; + case 47: $back = 'white'; break; + + default: + user_error('Unsupported attribute: ' . $mod); + $this->ansi = ''; + break 2; + } + + unset($temp); + $this->attrs[$this->y][$this->x] = ''; + if ($this->color) { + $this->attrs[$this->y][$this->x] = '' . $this->attrs[$this->y][$this->x]; + } + $this->color = true; + } + } + break; + default: + echo "{$this->ansi} unsupported\r\n"; + } + } + $this->ansi = ''; + continue; + } + + switch ($source[$i]) { + case "\r": + $this->x = 0; + break; + case "\n": + //if ($this->y < $this->max_y) { + // $this->y++; + //} + + while ($this->y >= $this->max_y) { + $this->history = array_merge($this->history, array(array_shift($this->screen))); + $this->screen[] = ''; + + $this->history_attrs = array_merge($this->history_attrs, array(array_shift($this->attrs))); + $this->attrs[] = $this->attr_row; + + if (count($this->history) >= $this->max_history) { + array_shift($this->history); + array_shift($this->history_attrs); + } + + $this->y--; + } + $this->y++; + break; + case "\x0F": // shift + break; + case "\x1B": // start ANSI escape code + $this->ansi.= "\x1B"; + break; + default: + $this->screen[$this->y] = substr_replace( + $this->screen[$this->y], + $source[$i], + $this->x, + 1 + ); + + if ($this->x > $this->max_x) { + $this->x = 0; + $this->y++; + } else { + $this->x++; + } + } + } + } + + /** + * Returns the current screen without preformating + * + * @access private + * @return String + */ + function _getScreen() + { + $output = ''; + for ($i = 0; $i <= $this->max_y; $i++) { + for ($j = 0; $j <= $this->max_x + 1; $j++) { + if (isset($this->attrs[$i][$j])) { + $output.= $this->attrs[$i][$j]; + } + if (isset($this->screen[$i][$j])) { + $output.= htmlspecialchars($this->screen[$i][$j]); + } + } + $output.= "\r\n"; + } + return rtrim($output); + } + + /** + * Returns the current screen + * + * @access public + * @return String + */ + function getScreen() + { + return '
' . $this->_getScreen() . '
'; + } + + /** + * Returns the current screen and the x previous lines + * + * @access public + * @return String + */ + function getHistory() + { + $scrollback = ''; + for ($i = 0; $i < count($this->history); $i++) { + for ($j = 0; $j <= $this->max_x + 1; $j++) { + if (isset($this->history_attrs[$i][$j])) { + $scrollback.= $this->history_attrs[$i][$j]; + } + if (isset($this->history[$i][$j])) { + $scrollback.= htmlspecialchars($this->history[$i][$j]); + } + } + $scrollback.= "\r\n"; + } + $scrollback.= $this->_getScreen(); + + return '
' . $scrollback . '
'; + } +} diff --git a/3rdparty/phpseclib/File/ASN1.php b/3rdparty/phpseclib/File/ASN1.php new file mode 100644 index 00000000000..effe9a0beb5 --- /dev/null +++ b/3rdparty/phpseclib/File/ASN1.php @@ -0,0 +1,1273 @@ + + * @copyright MMXII Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @version $Id$ + * @link http://phpseclib.sourceforge.net + */ + +/** + * Include Math_BigInteger + */ +if (!class_exists('Math_BigInteger')) { + require_once('Math/BigInteger.php'); +} + +/**#@+ + * Tag Classes + * + * @access private + * @link http://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#page=12 + */ +define('FILE_ASN1_CLASS_UNIVERSAL', 0); +define('FILE_ASN1_CLASS_APPLICATION', 1); +define('FILE_ASN1_CLASS_CONTEXT_SPECIFIC', 2); +define('FILE_ASN1_CLASS_PRIVATE', 3); +/**#@-*/ + +/**#@+ + * Tag Classes + * + * @access private + * @link http://www.obj-sys.com/asn1tutorial/node124.html + */ +define('FILE_ASN1_TYPE_BOOLEAN', 1); +define('FILE_ASN1_TYPE_INTEGER', 2); +define('FILE_ASN1_TYPE_BIT_STRING', 3); +define('FILE_ASN1_TYPE_OCTET_STRING', 4); +define('FILE_ASN1_TYPE_NULL', 5); +define('FILE_ASN1_TYPE_OBJECT_IDENTIFIER',6); +//define('FILE_ASN1_TYPE_OBJECT_DESCRIPTOR',7); +//define('FILE_ASN1_TYPE_INSTANCE_OF', 8); // EXTERNAL +define('FILE_ASN1_TYPE_REAL', 9); +define('FILE_ASN1_TYPE_ENUMERATED', 10); +//define('FILE_ASN1_TYPE_EMBEDDED', 11); +define('FILE_ASN1_TYPE_UTF8_STRING', 12); +//define('FILE_ASN1_TYPE_RELATIVE_OID', 13); +define('FILE_ASN1_TYPE_SEQUENCE', 16); // SEQUENCE OF +define('FILE_ASN1_TYPE_SET', 17); // SET OF +/**#@-*/ +/**#@+ + * More Tag Classes + * + * @access private + * @link http://www.obj-sys.com/asn1tutorial/node10.html + */ +define('FILE_ASN1_TYPE_NUMERIC_STRING', 18); +define('FILE_ASN1_TYPE_PRINTABLE_STRING',19); +define('FILE_ASN1_TYPE_TELETEX_STRING', 20); // T61String +define('FILE_ASN1_TYPE_VIDEOTEX_STRING', 21); +define('FILE_ASN1_TYPE_IA5_STRING', 22); +define('FILE_ASN1_TYPE_UTC_TIME', 23); +define('FILE_ASN1_TYPE_GENERALIZED_TIME',24); +define('FILE_ASN1_TYPE_GRAPHIC_STRING', 25); +define('FILE_ASN1_TYPE_VISIBLE_STRING', 26); // ISO646String +define('FILE_ASN1_TYPE_GENERAL_STRING', 27); +define('FILE_ASN1_TYPE_UNIVERSAL_STRING',28); +//define('FILE_ASN1_TYPE_CHARACTER_STRING',29); +define('FILE_ASN1_TYPE_BMP_STRING', 30); +/**#@-*/ + +/**#@+ + * Tag Aliases + * + * These tags are kinda place holders for other tags. + * + * @access private + */ +define('FILE_ASN1_TYPE_CHOICE', -1); +define('FILE_ASN1_TYPE_ANY', -2); +/**#@-*/ + +/** + * ASN.1 Element + * + * Bypass normal encoding rules in File_ASN1::encodeDER() + * + * @author Jim Wigginton + * @version 0.3.0 + * @access public + * @package File_ASN1 + */ +class File_ASN1_Element { + /** + * Raw element value + * + * @var String + * @access private + */ + var $element; + + /** + * Constructor + * + * @param String $encoded + * @return File_ASN1_Element + * @access public + */ + function File_ASN1_Element($encoded) + { + $this->element = $encoded; + } +} + +/** + * Pure-PHP ASN.1 Parser + * + * @author Jim Wigginton + * @version 0.3.0 + * @access public + * @package File_ASN1 + */ +class File_ASN1 { + /** + * ASN.1 object identifier + * + * @var Array + * @access private + * @link http://en.wikipedia.org/wiki/Object_identifier + */ + var $oids = array(); + + /** + * Default date format + * + * @var String + * @access private + * @link http://php.net/class.datetime + */ + var $format = 'D, d M y H:i:s O'; + + /** + * Default date format + * + * @var Array + * @access private + * @see File_ASN1::setTimeFormat() + * @see File_ASN1::asn1map() + * @link http://php.net/class.datetime + */ + var $encoded; + + /** + * Filters + * + * If the mapping type is FILE_ASN1_TYPE_ANY what do we actually encode it as? + * + * @var Array + * @access private + * @see File_ASN1::_encode_der() + */ + var $filters; + + /** + * Type mapping table for the ANY type. + * + * Structured or unknown types are mapped to a FILE_ASN1_Element. + * Unambiguous types get the direct mapping (int/real/bool). + * Others are mapped as a choice, with an extra indexing level. + * + * @var Array + * @access public + */ + var $ANYmap = array( + FILE_ASN1_TYPE_BOOLEAN => true, + FILE_ASN1_TYPE_INTEGER => true, + FILE_ASN1_TYPE_BIT_STRING => 'bitString', + FILE_ASN1_TYPE_OCTET_STRING => 'octetString', + FILE_ASN1_TYPE_NULL => 'null', + FILE_ASN1_TYPE_OBJECT_IDENTIFIER => 'objectIdentifier', + FILE_ASN1_TYPE_REAL => true, + FILE_ASN1_TYPE_ENUMERATED => 'enumerated', + FILE_ASN1_TYPE_UTF8_STRING => 'utf8String', + FILE_ASN1_TYPE_NUMERIC_STRING => 'numericString', + FILE_ASN1_TYPE_PRINTABLE_STRING => 'printableString', + FILE_ASN1_TYPE_TELETEX_STRING => 'teletexString', + FILE_ASN1_TYPE_VIDEOTEX_STRING => 'videotexString', + FILE_ASN1_TYPE_IA5_STRING => 'ia5String', + FILE_ASN1_TYPE_UTC_TIME => 'utcTime', + FILE_ASN1_TYPE_GENERALIZED_TIME => 'generalTime', + FILE_ASN1_TYPE_GRAPHIC_STRING => 'graphicString', + FILE_ASN1_TYPE_VISIBLE_STRING => 'visibleString', + FILE_ASN1_TYPE_GENERAL_STRING => 'generalString', + FILE_ASN1_TYPE_UNIVERSAL_STRING => 'universalString', + //FILE_ASN1_TYPE_CHARACTER_STRING => 'characterString', + FILE_ASN1_TYPE_BMP_STRING => 'bmpString' + ); + + /** + * String type to character size mapping table. + * + * Non-convertable types are absent from this table. + * size == 0 indicates variable length encoding. + * + * @var Array + * @access public + */ + var $stringTypeSize = array( + FILE_ASN1_TYPE_UTF8_STRING => 0, + FILE_ASN1_TYPE_BMP_STRING => 2, + FILE_ASN1_TYPE_UNIVERSAL_STRING => 4, + FILE_ASN1_TYPE_PRINTABLE_STRING => 1, + FILE_ASN1_TYPE_TELETEX_STRING => 1, + FILE_ASN1_TYPE_IA5_STRING => 1, + FILE_ASN1_TYPE_VISIBLE_STRING => 1, + ); + + /** + * Parse BER-encoding + * + * Serves a similar purpose to openssl's asn1parse + * + * @param String $encoded + * @return Array + * @access public + */ + function decodeBER($encoded) + { + if (is_object($encoded) && strtolower(get_class($encoded)) == 'file_asn1_element') { + $encoded = $encoded->element; + } + + $this->encoded = $encoded; + return $this->_decode_ber($encoded); + } + + /** + * Parse BER-encoding (Helper function) + * + * Sometimes we want to get the BER encoding of a particular tag. $start lets us do that without having to reencode. + * $encoded is passed by reference for the recursive calls done for FILE_ASN1_TYPE_BIT_STRING and + * FILE_ASN1_TYPE_OCTET_STRING. In those cases, the indefinite length is used. + * + * @param String $encoded + * @param Integer $start + * @return Array + * @access private + */ + function _decode_ber(&$encoded, $start = 0) + { + $decoded = array(); + + while ( strlen($encoded) ) { + $current = array('start' => $start); + + $type = ord($this->_string_shift($encoded)); + $start++; + + $constructed = ($type >> 5) & 1; + + $tag = $type & 0x1F; + if ($tag == 0x1F) { + $tag = 0; + // process septets (since the eighth bit is ignored, it's not an octet) + do { + $loop = ord($encoded[0]) >> 7; + $tag <<= 7; + $tag |= ord($this->_string_shift($encoded)) & 0x7F; + $start++; + } while ( $loop ); + } + + // Length, as discussed in § 8.1.3 of X.690-0207.pdf#page=13 + $length = ord($this->_string_shift($encoded)); + $start++; + if ( $length == 0x80 ) { // indefinite length + // "[A sender shall] use the indefinite form (see 8.1.3.6) if the encoding is constructed and is not all + // immediately available." -- § 8.1.3.2.c + //if ( !$constructed ) { + // return false; + //} + $length = strlen($encoded); + } elseif ( $length & 0x80 ) { // definite length, long form + // technically, the long form of the length can be represented by up to 126 octets (bytes), but we'll only + // support it up to four. + $length&= 0x7F; + $temp = $this->_string_shift($encoded, $length); + $start+= $length; + extract(unpack('Nlength', substr(str_pad($temp, 4, chr(0), STR_PAD_LEFT), -4))); + } + + // End-of-content, see §§ 8.1.1.3, 8.1.3.2, 8.1.3.6, 8.1.5, and (for an example) 8.6.4.2 + if (!$type && !$length) { + return $decoded; + } + $content = $this->_string_shift($encoded, $length); + + /* Class is UNIVERSAL, APPLICATION, PRIVATE, or CONTEXT-SPECIFIC. The UNIVERSAL class is restricted to the ASN.1 + built-in types. It defines an application-independent data type that must be distinguishable from all other + data types. The other three classes are user defined. The APPLICATION class distinguishes data types that + have a wide, scattered use within a particular presentation context. PRIVATE distinguishes data types within + a particular organization or country. CONTEXT-SPECIFIC distinguishes members of a sequence or set, the + alternatives of a CHOICE, or universally tagged set members. Only the class number appears in braces for this + data type; the term CONTEXT-SPECIFIC does not appear. + + -- http://www.obj-sys.com/asn1tutorial/node12.html */ + $class = ($type >> 6) & 3; + switch ($class) { + case FILE_ASN1_CLASS_APPLICATION: + case FILE_ASN1_CLASS_PRIVATE: + case FILE_ASN1_CLASS_CONTEXT_SPECIFIC: + $decoded[] = array( + 'type' => $class, + 'constant' => $tag, + 'content' => $constructed ? $this->_decode_ber($content, $start) : $content, + 'length' => $length + $start - $current['start'] + ) + $current; + continue 2; + } + + $current+= array('type' => $tag); + + // decode UNIVERSAL tags + switch ($tag) { + case FILE_ASN1_TYPE_BOOLEAN: + // "The contents octets shall consist of a single octet." -- § 8.2.1 + //if (strlen($content) != 1) { + // return false; + //} + $current['content'] = (bool) ord($content[0]); + break; + case FILE_ASN1_TYPE_INTEGER: + case FILE_ASN1_TYPE_ENUMERATED: + $current['content'] = new Math_BigInteger($content, -256); + break; + case FILE_ASN1_TYPE_REAL: // not currently supported + return false; + case FILE_ASN1_TYPE_BIT_STRING: + // The initial octet shall encode, as an unsigned binary integer with bit 1 as the least significant bit, + // the number of unused bits in the final subsequent octet. The number shall be in the range zero to + // seven. + if (!$constructed) { + $current['content'] = $content; + } else { + $temp = $this->_decode_ber($content, $start); + $length-= strlen($content); + $last = count($temp) - 1; + for ($i = 0; $i < $last; $i++) { + // all subtags should be bit strings + //if ($temp[$i]['type'] != FILE_ASN1_TYPE_BIT_STRING) { + // return false; + //} + $current['content'].= substr($temp[$i]['content'], 1); + } + // all subtags should be bit strings + //if ($temp[$last]['type'] != FILE_ASN1_TYPE_BIT_STRING) { + // return false; + //} + $current['content'] = $temp[$last]['content'][0] . $current['content'] . substr($temp[$i]['content'], 1); + } + break; + case FILE_ASN1_TYPE_OCTET_STRING: + if (!$constructed) { + $current['content'] = $content; + } else { + $temp = $this->_decode_ber($content, $start); + $length-= strlen($content); + for ($i = 0, $size = count($temp); $i < $size; $i++) { + // all subtags should be octet strings + //if ($temp[$i]['type'] != FILE_ASN1_TYPE_OCTET_STRING) { + // return false; + //} + $current['content'].= $temp[$i]['content']; + } + // $length = + } + break; + case FILE_ASN1_TYPE_NULL: + // "The contents octets shall not contain any octets." -- § 8.8.2 + //if (strlen($content)) { + // return false; + //} + break; + case FILE_ASN1_TYPE_SEQUENCE: + case FILE_ASN1_TYPE_SET: + $current['content'] = $this->_decode_ber($content, $start); + break; + case FILE_ASN1_TYPE_OBJECT_IDENTIFIER: + $temp = ord($this->_string_shift($content)); + $current['content'] = sprintf('%d.%d', floor($temp / 40), $temp % 40); + $valuen = 0; + // process septets + while (strlen($content)) { + $temp = ord($this->_string_shift($content)); + $valuen <<= 7; + $valuen |= $temp & 0x7F; + if (~$temp & 0x80) { + $current['content'].= ".$valuen"; + $valuen = 0; + } + } + // the eighth bit of the last byte should not be 1 + //if ($temp >> 7) { + // return false; + //} + break; + /* Each character string type shall be encoded as if it had been declared: + [UNIVERSAL x] IMPLICIT OCTET STRING + + -- X.690-0207.pdf#page=23 (§ 8.21.3) + + Per that, we're not going to do any validation. If there are any illegal characters in the string, + we don't really care */ + case FILE_ASN1_TYPE_NUMERIC_STRING: + // 0,1,2,3,4,5,6,7,8,9, and space + case FILE_ASN1_TYPE_PRINTABLE_STRING: + // Upper and lower case letters, digits, space, apostrophe, left/right parenthesis, plus sign, comma, + // hyphen, full stop, solidus, colon, equal sign, question mark + case FILE_ASN1_TYPE_TELETEX_STRING: + // The Teletex character set in CCITT's T61, space, and delete + // see http://en.wikipedia.org/wiki/Teletex#Character_sets + case FILE_ASN1_TYPE_VIDEOTEX_STRING: + // The Videotex character set in CCITT's T.100 and T.101, space, and delete + case FILE_ASN1_TYPE_VISIBLE_STRING: + // Printing character sets of international ASCII, and space + case FILE_ASN1_TYPE_IA5_STRING: + // International Alphabet 5 (International ASCII) + case FILE_ASN1_TYPE_GRAPHIC_STRING: + // All registered G sets, and space + case FILE_ASN1_TYPE_GENERAL_STRING: + // All registered C and G sets, space and delete + case FILE_ASN1_TYPE_UTF8_STRING: + // ???? + case FILE_ASN1_TYPE_BMP_STRING: + $current['content'] = $content; + break; + case FILE_ASN1_TYPE_UTC_TIME: + case FILE_ASN1_TYPE_GENERALIZED_TIME: + $current['content'] = $this->_decodeTime($content, $tag); + default: + + } + + $start+= $length; + $decoded[] = $current + array('length' => $start - $current['start']); + } + + return $decoded; + } + + /** + * ASN.1 Decode + * + * Provides an ASN.1 semantic mapping ($mapping) from a parsed BER-encoding to a human readable format. + * + * @param Array $decoded + * @param Array $mapping + * @return Array + * @access public + */ + function asn1map($decoded, $mapping) + { + if (isset($mapping['explicit'])) { + $decoded = $decoded['content'][0]; + } + + switch (true) { + case $mapping['type'] == FILE_ASN1_TYPE_ANY: + $intype = $decoded['type']; + if (isset($decoded['constant']) || !isset($this->ANYmap[$intype]) || ($this->encoded[$decoded['start']] & 0x20)) { + return new File_ASN1_Element(substr($this->encoded, $decoded['start'], $decoded['length'])); + } + $inmap = $this->ANYmap[$intype]; + if (is_string($inmap)) { + return array($inmap => $this->asn1map($decoded, array('type' => $intype) + $mapping)); + } + break; + case $mapping['type'] == FILE_ASN1_TYPE_CHOICE: + foreach ($mapping['children'] as $key => $option) { + switch (true) { + case isset($option['constant']) && $option['constant'] == $decoded['constant']: + case !isset($option['constant']) && $option['type'] == $decoded['type']: + $value = $this->asn1map($decoded, $option); + } + if (isset($value)) { + return array($key => $value); + } + } + return NULL; + case isset($mapping['implicit']): + case isset($mapping['explicit']): + case $decoded['type'] == $mapping['type']: + break; + default: + return NULL; + } + + if (isset($mapping['implicit'])) { + $decoded['type'] = $mapping['type']; + } + + switch ($decoded['type']) { + case FILE_ASN1_TYPE_SEQUENCE: + $map = array(); + + if (empty($decoded['content'])) { + return $map; + } + + // ignore the min and max + if (isset($mapping['min']) && isset($mapping['max'])) { + $child = $mapping['children']; + foreach ($decoded['content'] as $content) { + $map[] = $this->asn1map($content, $child); + } + return $map; + } + + $temp = $decoded['content'][$i = 0]; + foreach ($mapping['children'] as $key => $child) { + if (!isset($child['optional']) && $child['type'] == FILE_ASN1_TYPE_CHOICE) { + $map[$key] = $this->asn1map($temp, $child); + $i++; + if (count($decoded['content']) == $i) { + break; + } + $temp = $decoded['content'][$i]; + continue; + } + + $childClass = $tempClass = FILE_ASN1_CLASS_UNIVERSAL; + $constant = NULL; + if (isset($temp['constant'])) { + $tempClass = isset($temp['class']) ? $temp['class'] : FILE_ASN1_CLASS_CONTEXT_SPECIFIC; + } + if (isset($child['class'])) { + $childClass = $child['class']; + $constant = $child['cast']; + } + elseif (isset($child['constant'])) { + $childClass = FILE_ASN1_CLASS_CONTEXT_SPECIFIC; + $constant = $child['constant']; + } + + if (isset($child['optional'])) { + if (isset($constant) && isset($temp['constant'])) { + if (($constant == $temp['constant']) && ($childClass == $tempClass)) { + $map[$key] = $this->asn1map($temp, $child); + $i++; + if (count($decoded['content']) == $i) { + break; + } + $temp = $decoded['content'][$i]; + } + } elseif (!isset($child['constant'])) { + // we could do this, as well: + // $buffer = $this->asn1map($temp, $child); if (isset($buffer)) { $map[$key] = $buffer; } + if ($child['type'] == $temp['type'] || $child['type'] == FILE_ASN1_TYPE_ANY) { + $map[$key] = $this->asn1map($temp, $child); + $i++; + if (count($decoded['content']) == $i) { + break; + } + $temp = $decoded['content'][$i]; + } elseif ($child['type'] == FILE_ASN1_TYPE_CHOICE) { + $candidate = $this->asn1map($temp, $child); + if (!empty($candidate)) { + $map[$key] = $candidate; + $i++; + if (count($decoded['content']) == $i) { + break; + } + $temp = $decoded['content'][$i]; + } + } + } + + if (!isset($map[$key]) && isset($child['default'])) { + $map[$key] = $child['default']; + } + } else { + $map[$key] = $this->asn1map($temp, $child); + $i++; + if (count($decoded['content']) == $i) { + break; + } + $temp = $decoded['content'][$i]; + } + } + + return $map; + // the main diff between sets and sequences is the encapsulation of the foreach in another for loop + case FILE_ASN1_TYPE_SET: + $map = array(); + + // ignore the min and max + if (isset($mapping['min']) && isset($mapping['max'])) { + $child = $mapping['children']; + foreach ($decoded['content'] as $content) { + $map[] = $this->asn1map($content, $child); + } + + return $map; + } + + for ($i = 0; $i < count($decoded['content']); $i++) { + foreach ($mapping['children'] as $key => $child) { + $temp = $decoded['content'][$i]; + + if (!isset($child['optional']) && $child['type'] == FILE_ASN1_TYPE_CHOICE) { + $map[$key] = $this->asn1map($temp, $child); + continue; + } + + $childClass = $tempClass = FILE_ASN1_CLASS_UNIVERSAL; + $constant = NULL; + if (isset($temp['constant'])) { + $tempClass = isset($temp['class']) ? $temp['class'] : FILE_ASN1_CLASS_CONTEXT_SPECIFIC; + } + if (isset($child['class'])) { + $childClass = $child['class']; + $constant = $child['cast']; + } + elseif (isset($child['constant'])) { + $childClass = FILE_ASN1_CLASS_CONTEXT_SPECIFIC; + $constant = $child['constant']; + } + + if (isset($constant) && isset($temp['constant'])) { + if (($constant == $temp['constant']) && ($childClass == $tempClass)) { + $map[$key] = $this->asn1map($temp['content'], $child); + } + } elseif (!isset($child['constant'])) { + // we could do this, as well: + // $buffer = $this->asn1map($temp['content'], $child); if (isset($buffer)) { $map[$key] = $buffer; } + if ($child['type'] == $temp['type']) { + $map[$key] = $this->asn1map($temp, $child); + } + } + } + } + + foreach ($mapping['children'] as $key => $child) { + if (!isset($map[$key]) && isset($child['default'])) { + $map[$key] = $child['default']; + } + } + return $map; + case FILE_ASN1_TYPE_OBJECT_IDENTIFIER: + return isset($this->oids[$decoded['content']]) ? $this->oids[$decoded['content']] : $decoded['content']; + case FILE_ASN1_TYPE_UTC_TIME: + case FILE_ASN1_TYPE_GENERALIZED_TIME: + if (isset($mapping['implicit'])) { + $decoded['content'] = $this->_decodeTime($decoded['content'], $decoded['type']); + } + return @date($this->format, $decoded['content']); + case FILE_ASN1_TYPE_BIT_STRING: + if (isset($mapping['mapping'])) { + $offset = ord($decoded['content'][0]); + $size = (strlen($decoded['content']) - 1) * 8 - $offset; + /* + From X.680-0207.pdf#page=46 (21.7): + + "When a "NamedBitList" is used in defining a bitstring type ASN.1 encoding rules are free to add (or remove) + arbitrarily any trailing 0 bits to (or from) values that are being encoded or decoded. Application designers should + therefore ensure that different semantics are not associated with such values which differ only in the number of trailing + 0 bits." + */ + $bits = count($mapping['mapping']) == $size ? array() : array_fill(0, count($mapping['mapping']) - $size, false); + for ($i = strlen($decoded['content']) - 1; $i > 0; $i--) { + $current = ord($decoded['content'][$i]); + for ($j = $offset; $j < 8; $j++) { + $bits[] = (bool) ($current & (1 << $j)); + } + $offset = 0; + } + $values = array(); + $map = array_reverse($mapping['mapping']); + foreach ($map as $i => $value) { + if ($bits[$i]) { + $values[] = $value; + } + } + return $values; + } + case FILE_ASN1_TYPE_OCTET_STRING: + return base64_encode($decoded['content']); + case FILE_ASN1_TYPE_NULL: + return ''; + case FILE_ASN1_TYPE_BOOLEAN: + return $decoded['content']; + case FILE_ASN1_TYPE_NUMERIC_STRING: + case FILE_ASN1_TYPE_PRINTABLE_STRING: + case FILE_ASN1_TYPE_TELETEX_STRING: + case FILE_ASN1_TYPE_VIDEOTEX_STRING: + case FILE_ASN1_TYPE_IA5_STRING: + case FILE_ASN1_TYPE_GRAPHIC_STRING: + case FILE_ASN1_TYPE_VISIBLE_STRING: + case FILE_ASN1_TYPE_GENERAL_STRING: + case FILE_ASN1_TYPE_UNIVERSAL_STRING: + case FILE_ASN1_TYPE_UTF8_STRING: + case FILE_ASN1_TYPE_BMP_STRING: + return $decoded['content']; + case FILE_ASN1_TYPE_INTEGER: + case FILE_ASN1_TYPE_ENUMERATED: + $temp = $decoded['content']; + if (isset($mapping['implicit'])) { + $temp = new Math_BigInteger($decoded['content'], -256); + } + if (isset($mapping['mapping'])) { + $temp = (int) $temp->toString(); + return isset($mapping['mapping'][$temp]) ? + $mapping['mapping'][$temp] : + false; + } + return $temp; + } + } + + /** + * ASN.1 Encode + * + * DER-encodes an ASN.1 semantic mapping ($mapping). Some libraries would probably call this function + * an ASN.1 compiler. + * + * @param String $source + * @param String $mapping + * @param Integer $idx + * @return String + * @access public + */ + function encodeDER($source, $mapping) + { + $this->location = array(); + return $this->_encode_der($source, $mapping); + } + + /** + * ASN.1 Encode (Helper function) + * + * @param String $source + * @param String $mapping + * @param Integer $idx + * @return String + * @access private + */ + function _encode_der($source, $mapping, $idx = NULL) + { + if (is_object($source) && strtolower(get_class($source)) == 'file_asn1_element') { + return $source->element; + } + + // do not encode (implicitly optional) fields with value set to default + if (isset($mapping['default']) && $source === $mapping['default']) { + return ''; + } + + if (isset($idx)) { + $this->location[] = $idx; + } + + $tag = $mapping['type']; + + switch ($tag) { + case FILE_ASN1_TYPE_SET: // Children order is not important, thus process in sequence. + case FILE_ASN1_TYPE_SEQUENCE: + $tag|= 0x20; // set the constructed bit + $value = ''; + + // ignore the min and max + if (isset($mapping['min']) && isset($mapping['max'])) { + $child = $mapping['children']; + + foreach ($source as $content) { + $temp = $this->_encode_der($content, $child); + if ($temp === false) { + return false; + } + $value.= $temp; + } + break; + } + + foreach ($mapping['children'] as $key => $child) { + if (!isset($source[$key])) { + if (!isset($child['optional'])) { + return false; + } + continue; + } + + $temp = $this->_encode_der($source[$key], $child, $key); + if ($temp === false) { + return false; + } + + // An empty child encoding means it has been optimized out. + // Else we should have at least one tag byte. + if ($temp === '') { + continue; + } + + // if isset($child['constant']) is true then isset($child['optional']) should be true as well + if (isset($child['constant'])) { + /* + From X.680-0207.pdf#page=58 (30.6): + + "The tagging construction specifies explicit tagging if any of the following holds: + ... + c) the "Tag Type" alternative is used and the value of "TagDefault" for the module is IMPLICIT TAGS or + AUTOMATIC TAGS, but the type defined by "Type" is an untagged choice type, an untagged open type, or + an untagged "DummyReference" (see ITU-T Rec. X.683 | ISO/IEC 8824-4, 8.3)." + */ + if (isset($child['explicit']) || $child['type'] == FILE_ASN1_TYPE_CHOICE) { + $subtag = chr((FILE_ASN1_CLASS_CONTEXT_SPECIFIC << 6) | 0x20 | $child['constant']); + $temp = $subtag . $this->_encodeLength(strlen($temp)) . $temp; + } else { + $subtag = chr((FILE_ASN1_CLASS_CONTEXT_SPECIFIC << 6) | (ord($temp[0]) & 0x20) | $child['constant']); + $temp = $subtag . substr($temp, 1); + } + } + $value.= $temp; + } + break; + case FILE_ASN1_TYPE_CHOICE: + $temp = false; + + foreach ($mapping['children'] as $key => $child) { + if (!isset($source[$key])) { + continue; + } + + $temp = $this->_encode_der($source[$key], $child, $key); + if ($temp === false) { + return false; + } + + // An empty child encoding means it has been optimized out. + // Else we should have at least one tag byte. + if ($temp === '') { + continue; + } + + $tag = ord($temp[0]); + + // if isset($child['constant']) is true then isset($child['optional']) should be true as well + if (isset($child['constant'])) { + if (isset($child['explicit']) || $child['type'] == FILE_ASN1_TYPE_CHOICE) { + $subtag = chr((FILE_ASN1_CLASS_CONTEXT_SPECIFIC << 6) | 0x20 | $child['constant']); + $temp = $subtag . $this->_encodeLength(strlen($temp)) . $temp; + } else { + $subtag = chr((FILE_ASN1_CLASS_CONTEXT_SPECIFIC << 6) | (ord($temp[0]) & 0x20) | $child['constant']); + $temp = $subtag . substr($temp, 1); + } + } + } + + if (isset($idx)) { + array_pop($this->location); + } + + if ($temp && isset($mapping['cast'])) { + $temp[0] = chr(($mapping['class'] << 6) | ($tag & 0x20) | $mapping['cast']); + } + + return $temp; + case FILE_ASN1_TYPE_INTEGER: + case FILE_ASN1_TYPE_ENUMERATED: + if (!isset($mapping['mapping'])) { + $value = $source->toBytes(true); + } else { + $value = array_search($source, $mapping['mapping']); + if ($value === false) { + return false; + } + $value = new Math_BigInteger($value); + $value = $value->toBytes(true); + } + break; + case FILE_ASN1_TYPE_UTC_TIME: + case FILE_ASN1_TYPE_GENERALIZED_TIME: + $format = $mapping['type'] == FILE_ASN1_TYPE_UTC_TIME ? 'y' : 'Y'; + $format.= 'mdHis'; + $value = @gmdate($format, strtotime($source)) . 'Z'; + break; + case FILE_ASN1_TYPE_BIT_STRING: + if (isset($mapping['mapping'])) { + $bits = array_fill(0, count($mapping['mapping']), 0); + $size = 0; + for ($i = 0; $i < count($mapping['mapping']); $i++) { + if (in_array($mapping['mapping'][$i], $source)) { + $bits[$i] = 1; + $size = $i; + } + } + + $offset = 8 - (($size + 1) & 7); + $offset = $offset !== 8 ? $offset : 0; + + $value = chr($offset); + + for ($i = $size + 1; $i < count($mapping['mapping']); $i++) { + unset($bits[$i]); + } + + $bits = implode('', array_pad($bits, $size + $offset + 1, 0)); + $bytes = explode(' ', rtrim(chunk_split($bits, 8, ' '))); + foreach ($bytes as $byte) { + $value.= chr(bindec($byte)); + } + + break; + } + case FILE_ASN1_TYPE_OCTET_STRING: + /* The initial octet shall encode, as an unsigned binary integer with bit 1 as the least significant bit, + the number of unused bits in the final subsequent octet. The number shall be in the range zero to seven. + + -- http://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#page=16 */ + $value = base64_decode($source); + break; + case FILE_ASN1_TYPE_OBJECT_IDENTIFIER: + $oid = preg_match('#(?:\d+\.)+#', $source) ? $source : array_search($source, $this->oids); + if ($oid === false) { + user_error('Invalid OID', E_USER_NOTICE); + return false; + } + $value = ''; + $parts = explode('.', $oid); + $value = chr(40 * $parts[0] + $parts[1]); + for ($i = 2; $i < count($parts); $i++) { + $temp = ''; + if (!$parts[$i]) { + $temp = "\0"; + } else { + while ($parts[$i]) { + $temp = chr(0x80 | ($parts[$i] & 0x7F)) . $temp; + $parts[$i] >>= 7; + } + $temp[strlen($temp) - 1] = $temp[strlen($temp) - 1] & chr(0x7F); + } + $value.= $temp; + } + break; + case FILE_ASN1_TYPE_ANY: + $loc = $this->location; + if (isset($idx)) { + array_pop($this->location); + } + + switch (true) { + case !isset($source): + return $this->_encode_der(NULL, array('type' => FILE_ASN1_TYPE_NULL) + $mapping); + case is_int($source): + case is_object($source) && strtolower(get_class($source)) == 'math_biginteger': + return $this->_encode_der($source, array('type' => FILE_ASN1_TYPE_INTEGER) + $mapping); + case is_float($source): + return $this->_encode_der($source, array('type' => FILE_ASN1_TYPE_REAL) + $mapping); + case is_bool($source): + return $this->_encode_der($source, array('type' => FILE_ASN1_TYPE_BOOLEAN) + $mapping); + case is_array($source) && count($source) == 1: + $typename = implode('', array_keys($source)); + $outtype = array_search($typename, $this->ANYmap, true); + if ($outtype !== false) { + return $this->_encode_der($source[$typename], array('type' => $outtype) + $mapping); + } + } + + $filters = $this->filters; + foreach ($loc as $part) { + if (!isset($filters[$part])) { + $filters = false; + break; + } + $filters = $filters[$part]; + } + if ($filters === false) { + user_error('No filters defined for ' . implode('/', $loc), E_USER_NOTICE); + return false; + } + return $this->_encode_der($source, $filters + $mapping); + case FILE_ASN1_TYPE_NULL: + $value = ''; + break; + case FILE_ASN1_TYPE_NUMERIC_STRING: + case FILE_ASN1_TYPE_TELETEX_STRING: + case FILE_ASN1_TYPE_PRINTABLE_STRING: + case FILE_ASN1_TYPE_UNIVERSAL_STRING: + case FILE_ASN1_TYPE_UTF8_STRING: + case FILE_ASN1_TYPE_BMP_STRING: + case FILE_ASN1_TYPE_IA5_STRING: + case FILE_ASN1_TYPE_VISIBLE_STRING: + case FILE_ASN1_TYPE_VIDEOTEX_STRING: + case FILE_ASN1_TYPE_GRAPHIC_STRING: + case FILE_ASN1_TYPE_GENERAL_STRING: + $value = $source; + break; + case FILE_ASN1_TYPE_BOOLEAN: + $value = $source ? "\xFF" : "\x00"; + break; + default: + user_error('Mapping provides no type definition for ' . implode('/', $this->location), E_USER_NOTICE); + return false; + } + + if (isset($idx)) { + array_pop($this->location); + } + + if (isset($mapping['cast'])) { + $tag = ($mapping['class'] << 6) | ($tag & 0x20) | $mapping['cast']; + } + + return chr($tag) . $this->_encodeLength(strlen($value)) . $value; + } + + /** + * DER-encode the length + * + * DER supports lengths up to (2**8)**127, however, we'll only support lengths up to (2**8)**4. See + * {@link http://itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#p=13 X.690 § 8.1.3} for more information. + * + * @access private + * @param Integer $length + * @return String + */ + function _encodeLength($length) + { + if ($length <= 0x7F) { + return chr($length); + } + + $temp = ltrim(pack('N', $length), chr(0)); + return pack('Ca*', 0x80 | strlen($temp), $temp); + } + + /** + * BER-decode the time + * + * Called by _decode_ber() and in the case of implicit tags asn1map(). + * + * @access private + * @param String $content + * @param Integer $tag + * @return String + */ + function _decodeTime($content, $tag) + { + /* UTCTime: + http://tools.ietf.org/html/rfc5280#section-4.1.2.5.1 + http://www.obj-sys.com/asn1tutorial/node15.html + + GeneralizedTime: + http://tools.ietf.org/html/rfc5280#section-4.1.2.5.2 + http://www.obj-sys.com/asn1tutorial/node14.html */ + + $pattern = $tag == FILE_ASN1_TYPE_UTC_TIME ? + '#(..)(..)(..)(..)(..)(..)(.*)#' : + '#(....)(..)(..)(..)(..)(..).*([Z+-].*)$#'; + + preg_match($pattern, $content, $matches); + + list(, $year, $month, $day, $hour, $minute, $second, $timezone) = $matches; + + if ($tag == FILE_ASN1_TYPE_UTC_TIME) { + $year = $year >= 50 ? "19$year" : "20$year"; + } + + if ($timezone == 'Z') { + $mktime = 'gmmktime'; + $timezone = 0; + } elseif (preg_match('#([+-])(\d\d)(\d\d)#', $timezone, $matches)) { + $mktime = 'gmmktime'; + $timezone = 60 * $matches[3] + 3600 * $matches[2]; + if ($matches[1] == '-') { + $timezone = -$timezone; + } + } else { + $mktime = 'mktime'; + $timezone = 0; + } + + return @$mktime($hour, $minute, $second, $month, $day, $year) + $timezone; + } + + /** + * Set the time format + * + * Sets the time / date format for asn1map(). + * + * @access public + * @param String $format + */ + function setTimeFormat($format) + { + $this->format = $format; + } + + /** + * Load OIDs + * + * Load the relevant OIDs for a particular ASN.1 semantic mapping. + * + * @access public + * @param Array $oids + */ + function loadOIDs($oids) + { + $this->oids = $oids; + } + + /** + * Load filters + * + * See File_X509, etc, for an example. + * + * @access public + * @param Array $filters + */ + function loadFilters($filters) + { + $this->filters = $filters; + } + + /** + * String Shift + * + * Inspired by array_shift + * + * @param String $string + * @param optional Integer $index + * @return String + * @access private + */ + function _string_shift(&$string, $index = 1) + { + $substr = substr($string, 0, $index); + $string = substr($string, $index); + return $substr; + } + + /** + * String type conversion + * + * This is a lazy conversion, dealing only with character size. + * No real conversion table is used. + * + * @param String $in + * @param optional Integer $from + * @param optional Integer $to + * @return String + * @access public + */ + function convert($in, $from = FILE_ASN1_TYPE_UTF8_STRING, $to = FILE_ASN1_TYPE_UTF8_STRING) + { + if (!isset($this->stringTypeSize[$from]) || !isset($this->stringTypeSize[$to])) { + return false; + } + $insize = $this->stringTypeSize[$from]; + $outsize = $this->stringTypeSize[$to]; + $inlength = strlen($in); + $out = ''; + + for ($i = 0; $i < $inlength;) { + if ($inlength - $i < $insize) { + return false; + } + + // Get an input character as a 32-bit value. + $c = ord($in[$i++]); + switch (true) { + case $insize == 4: + $c = ($c << 8) | ord($in[$i++]); + $c = ($c << 8) | ord($in[$i++]); + case $insize == 2: + $c = ($c << 8) | ord($in[$i++]); + case $insize == 1: + break; + case ($c & 0x80) == 0x00: + break; + case ($c & 0x40) == 0x00: + return false; + default: + $bit = 6; + do { + if ($bit > 25 || $i >= $inlength || (ord($in[$i]) & 0xC0) != 0x80) { + return false; + } + $c = ($c << 6) | (ord($in[$i++]) & 0x3F); + $bit += 5; + $mask = 1 << $bit; + } while ($c & $bit); + $c &= $mask - 1; + break; + } + + // Convert and append the character to output string. + $v = ''; + switch (true) { + case $outsize == 4: + $v .= chr($c & 0xFF); + $c >>= 8; + $v .= chr($c & 0xFF); + $c >>= 8; + case $outsize == 2: + $v .= chr($c & 0xFF); + $c >>= 8; + case $outsize == 1: + $v .= chr($c & 0xFF); + $c >>= 8; + if ($c) { + return false; + } + break; + case ($c & 0x80000000) != 0: + return false; + case $c >= 0x04000000: + $v .= chr(0x80 | ($c & 0x3F)); + $c = ($c >> 6) | 0x04000000; + case $c >= 0x00200000: + $v .= chr(0x80 | ($c & 0x3F)); + $c = ($c >> 6) | 0x00200000; + case $c >= 0x00010000: + $v .= chr(0x80 | ($c & 0x3F)); + $c = ($c >> 6) | 0x00010000; + case $c >= 0x00000800: + $v .= chr(0x80 | ($c & 0x3F)); + $c = ($c >> 6) | 0x00000800; + case $c >= 0x00000080: + $v .= chr(0x80 | ($c & 0x3F)); + $c = ($c >> 6) | 0x000000C0; + default: + $v .= chr($c); + break; + } + $out .= strrev($v); + } + return $out; + } +} diff --git a/3rdparty/phpseclib/File/X509.php b/3rdparty/phpseclib/File/X509.php new file mode 100644 index 00000000000..40c72ebad08 --- /dev/null +++ b/3rdparty/phpseclib/File/X509.php @@ -0,0 +1,3766 @@ + + * @copyright MMXII Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @version $Id$ + * @link htp://phpseclib.sourceforge.net + */ + +/** + * Include File_ASN1 + */ +if (!class_exists('File_ASN1')) { + require_once('File/ASN1.php'); +} + +/** + * Flag to only accept signatures signed by certificate authorities + * + * @access public + * @see File_X509::validateSignature() + */ +define('FILE_X509_VALIDATE_SIGNATURE_BY_CA', 1); + +/**#@+ + * @access public + * @see File_X509::getDN() + */ +/** + * Return internal array representation + */ +define('FILE_X509_DN_ARRAY', 0); // Internal array representation. +/** + * Return string + */ +define('FILE_X509_DN_STRING', 1); +/** + * Return ASN.1 name string + */ +define('FILE_X509_DN_ASN1', 2); +/** + * Return OpenSSL compatible array + */ +define('FILE_X509_DN_OPENSSL', 3); +/** + * Return canonical ASN.1 RDNs string + */ +define('FILE_X509_DN_CANON', 4); +/** + * Return name ash for file indexing + */ +define('FILE_X509_DN_HASH', 5); +/**#@-*/ + +/** + * Pure-PHP X.509 Parser + * + * @author Jim Wigginton + * @version 0.3.0 + * @access public + * @package File_X509 + */ +class File_X509 { + /** + * ASN.1 syntax for X.509 certificates + * + * @var Array + * @access private + */ + var $Certificate; + + /**#@+ + * ASN.1 syntax for various extensions + * + * @access private + */ + var $KeyUsage; + var $ExtKeyUsageSyntax; + var $BasicConstraints; + var $KeyIdentifier; + var $CRLDistributionPoints; + var $AuthorityKeyIdentifier; + var $CertificatePolicies; + var $AuthorityInfoAccessSyntax; + var $SubjectAltName; + var $PrivateKeyUsagePeriod; + var $IssuerAltName; + var $PolicyMappings; + var $NameConstraints; + + var $CPSuri; + var $UserNotice; + + var $netscape_cert_type; + var $netscape_comment; + var $netscape_ca_policy_url; + + var $Name; + var $RelativeDistinguishedName; + var $CRLNumber; + var $CRLReason; + var $IssuingDistributionPoint; + var $InvalidityDate; + var $CertificateIssuer; + /**#@-*/ + + /** + * ASN.1 syntax for Certificate Signing Requests (RFC2986) + * + * @var Array + * @access private + */ + var $CertificationRequest; + + /** + * ASN.1 syntax for Certificate Revocation Lists (RFC5280) + * + * @var Array + * @access private + */ + var $CertificateList; + + /** + * Distinguished Name + * + * @var Array + * @access private + */ + var $dn; + + /** + * Public key + * + * @var String + * @access private + */ + var $publicKey; + + /** + * Private key + * + * @var String + * @access private + */ + var $privateKey; + + /** + * Object identifiers for X.509 certificates + * + * @var Array + * @access private + * @link http://en.wikipedia.org/wiki/Object_identifier + */ + var $oids; + + /** + * The certificate authorities + * + * @var Array + * @access private + */ + var $CAs; + + /** + * The currently loaded certificate + * + * @var Array + * @access private + */ + var $currentCert; + + /** + * The signature subject + * + * There's no guarantee File_X509 is going to reencode an X.509 cert in the same way it was originally + * encoded so we take save the portion of the original cert that the signature would have made for. + * + * @var String + * @access private + */ + var $signatureSubject; + + /** + * Certificate Start Date + * + * @var String + * @access private + */ + var $startDate; + + /** + * Certificate End Date + * + * @var String + * @access private + */ + var $endDate; + + /** + * Serial Number + * + * @var String + * @access private + */ + var $serialNumber; + + /** + * Key Identifier + * + * See {@link http://tools.ietf.org/html/rfc5280#section-4.2.1.1 RFC5280#section-4.2.1.1} and + * {@link http://tools.ietf.org/html/rfc5280#section-4.2.1.2 RFC5280#section-4.2.1.2}. + * + * @var String + * @access private + */ + var $currentKeyIdentifier; + + /** + * CA Flag + * + * @var Boolean + * @access private + */ + var $caFlag = false; + + /** + * Default Constructor. + * + * @return File_X509 + * @access public + */ + function File_X509() + { + // Explicitly Tagged Module, 1988 Syntax + // http://tools.ietf.org/html/rfc5280#appendix-A.1 + + $DirectoryString = array( + 'type' => FILE_ASN1_TYPE_CHOICE, + 'children' => array( + 'teletexString' => array('type' => FILE_ASN1_TYPE_TELETEX_STRING), + 'printableString' => array('type' => FILE_ASN1_TYPE_PRINTABLE_STRING), + 'universalString' => array('type' => FILE_ASN1_TYPE_UNIVERSAL_STRING), + 'utf8String' => array('type' => FILE_ASN1_TYPE_UTF8_STRING), + 'bmpString' => array('type' => FILE_ASN1_TYPE_BMP_STRING) + ) + ); + + $AttributeValue = array('type' => FILE_ASN1_TYPE_ANY); + + $AttributeType = array('type' => FILE_ASN1_TYPE_OBJECT_IDENTIFIER); + + $AttributeTypeAndValue = array( + 'type' => FILE_ASN1_TYPE_SEQUENCE, + 'children' => array( + 'type' => $AttributeType, + 'value'=> $AttributeValue + ) + ); + + /* + In practice, RDNs containing multiple name-value pairs (called "multivalued RDNs") are rare, + but they can be useful at times when either there is no unique attribute in the entry or you + want to ensure that the entry's DN contains some useful identifying information. + + - https://www.opends.org/wiki/page/DefinitionRelativeDistinguishedName + */ + $this->RelativeDistinguishedName = array( + 'type' => FILE_ASN1_TYPE_SET, + 'min' => 1, + 'max' => -1, + 'children' => $AttributeTypeAndValue + ); + + // http://tools.ietf.org/html/rfc5280#section-4.1.2.4 + $RDNSequence = array( + 'type' => FILE_ASN1_TYPE_SEQUENCE, + // RDNSequence does not define a min or a max, which means it doesn't have one + 'min' => 0, + 'max' => -1, + 'children' => $this->RelativeDistinguishedName + ); + + $this->Name = array( + 'type' => FILE_ASN1_TYPE_CHOICE, + 'children' => array( + 'rdnSequence' => $RDNSequence + ) + ); + + // http://tools.ietf.org/html/rfc5280#section-4.1.1.2 + $AlgorithmIdentifier = array( + 'type' => FILE_ASN1_TYPE_SEQUENCE, + 'children' => array( + 'algorithm' => array('type' => FILE_ASN1_TYPE_OBJECT_IDENTIFIER), + 'parameters' => array( + 'type' => FILE_ASN1_TYPE_ANY, + 'optional' => true + ) + ) + ); + + /* + A certificate using system MUST reject the certificate if it encounters + a critical extension it does not recognize; however, a non-critical + extension may be ignored if it is not recognized. + + http://tools.ietf.org/html/rfc5280#section-4.2 + */ + $Extension = array( + 'type' => FILE_ASN1_TYPE_SEQUENCE, + 'children' => array( + 'extnId' => array('type' => FILE_ASN1_TYPE_OBJECT_IDENTIFIER), + 'critical' => array( + 'type' => FILE_ASN1_TYPE_BOOLEAN, + 'optional' => true, + 'default' => false + ), + 'extnValue' => array('type' => FILE_ASN1_TYPE_OCTET_STRING) + ) + ); + + $Extensions = array( + 'type' => FILE_ASN1_TYPE_SEQUENCE, + 'min' => 1, + // technically, it's MAX, but we'll assume anything < 0 is MAX + 'max' => -1, + // if 'children' isn't an array then 'min' and 'max' must be defined + 'children' => $Extension + ); + + $SubjectPublicKeyInfo = array( + 'type' => FILE_ASN1_TYPE_SEQUENCE, + 'children' => array( + 'algorithm' => $AlgorithmIdentifier, + 'subjectPublicKey' => array('type' => FILE_ASN1_TYPE_BIT_STRING) + ) + ); + + $UniqueIdentifier = array('type' => FILE_ASN1_TYPE_BIT_STRING); + + $Time = array( + 'type' => FILE_ASN1_TYPE_CHOICE, + 'children' => array( + 'utcTime' => array('type' => FILE_ASN1_TYPE_UTC_TIME), + 'generalTime' => array('type' => FILE_ASN1_TYPE_GENERALIZED_TIME) + ) + ); + + // http://tools.ietf.org/html/rfc5280#section-4.1.2.5 + $Validity = array( + 'type' => FILE_ASN1_TYPE_SEQUENCE, + 'children' => array( + 'notBefore' => $Time, + 'notAfter' => $Time + ) + ); + + $CertificateSerialNumber = array('type' => FILE_ASN1_TYPE_INTEGER); + + $Version = array( + 'type' => FILE_ASN1_TYPE_INTEGER, + 'mapping' => array('v1', 'v2', 'v3') + ); + + // assert($TBSCertificate['children']['signature'] == $Certificate['children']['signatureAlgorithm']) + $TBSCertificate = array( + 'type' => FILE_ASN1_TYPE_SEQUENCE, + 'children' => array( + // technically, default implies optional, but we'll define it as being optional, none-the-less, just to + // reenforce that fact + 'version' => array( + 'constant' => 0, + 'optional' => true, + 'explicit' => true, + 'default' => 'v1' + ) + $Version, + 'serialNumber' => $CertificateSerialNumber, + 'signature' => $AlgorithmIdentifier, + 'issuer' => $this->Name, + 'validity' => $Validity, + 'subject' => $this->Name, + 'subjectPublicKeyInfo' => $SubjectPublicKeyInfo, + // implicit means that the T in the TLV structure is to be rewritten, regardless of the type + 'issuerUniqueID' => array( + 'constant' => 1, + 'optional' => true, + 'implicit' => true + ) + $UniqueIdentifier, + 'subjectUniqueID' => array( + 'constant' => 2, + 'optional' => true, + 'implicit' => true + ) + $UniqueIdentifier, + // doesn't use the EXPLICIT keyword but if + // it's not IMPLICIT, it's EXPLICIT + 'extensions' => array( + 'constant' => 3, + 'optional' => true, + 'explicit' => true + ) + $Extensions + ) + ); + + $this->Certificate = array( + 'type' => FILE_ASN1_TYPE_SEQUENCE, + 'children' => array( + 'tbsCertificate' => $TBSCertificate, + 'signatureAlgorithm' => $AlgorithmIdentifier, + 'signature' => array('type' => FILE_ASN1_TYPE_BIT_STRING) + ) + ); + + $this->KeyUsage = array( + 'type' => FILE_ASN1_TYPE_BIT_STRING, + 'mapping' => array( + 'digitalSignature', + 'nonRepudiation', + 'keyEncipherment', + 'dataEncipherment', + 'keyAgreement', + 'keyCertSign', + 'cRLSign', + 'encipherOnly', + 'decipherOnly' + ) + ); + + $this->BasicConstraints = array( + 'type' => FILE_ASN1_TYPE_SEQUENCE, + 'children' => array( + 'cA' => array( + 'type' => FILE_ASN1_TYPE_BOOLEAN, + 'optional' => true, + 'default' => false + ), + 'pathLenConstraint' => array( + 'type' => FILE_ASN1_TYPE_INTEGER, + 'optional' => true + ) + ) + ); + + $this->KeyIdentifier = array('type' => FILE_ASN1_TYPE_OCTET_STRING); + + $OrganizationalUnitNames = array( + 'type' => FILE_ASN1_TYPE_SEQUENCE, + 'min' => 1, + 'max' => 4, // ub-organizational-units + 'children' => array('type' => FILE_ASN1_TYPE_PRINTABLE_STRING) + ); + + $PersonalName = array( + 'type' => FILE_ASN1_TYPE_SET, + 'children' => array( + 'surname' => array( + 'type' => FILE_ASN1_TYPE_PRINTABLE_STRING, + 'constant' => 0, + 'optional' => true, + 'implicit' => true + ), + 'given-name' => array( + 'type' => FILE_ASN1_TYPE_PRINTABLE_STRING, + 'constant' => 1, + 'optional' => true, + 'implicit' => true + ), + 'initials' => array( + 'type' => FILE_ASN1_TYPE_PRINTABLE_STRING, + 'constant' => 2, + 'optional' => true, + 'implicit' => true + ), + 'generation-qualifier' => array( + 'type' => FILE_ASN1_TYPE_PRINTABLE_STRING, + 'constant' => 3, + 'optional' => true, + 'implicit' => true + ) + ) + ); + + $NumericUserIdentifier = array('type' => FILE_ASN1_TYPE_NUMERIC_STRING); + + $OrganizationName = array('type' => FILE_ASN1_TYPE_PRINTABLE_STRING); + + $PrivateDomainName = array( + 'type' => FILE_ASN1_TYPE_CHOICE, + 'children' => array( + 'numeric' => array('type' => FILE_ASN1_TYPE_NUMERIC_STRING), + 'printable' => array('type' => FILE_ASN1_TYPE_PRINTABLE_STRING) + ) + ); + + $TerminalIdentifier = array('type' => FILE_ASN1_TYPE_PRINTABLE_STRING); + + $NetworkAddress = array('type' => FILE_ASN1_TYPE_NUMERIC_STRING); + + $AdministrationDomainName = array( + 'type' => FILE_ASN1_TYPE_CHOICE, + // if class isn't present it's assumed to be FILE_ASN1_CLASS_UNIVERSAL or + // (if constant is present) FILE_ASN1_CLASS_CONTEXT_SPECIFIC + 'class' => FILE_ASN1_CLASS_APPLICATION, + 'cast' => 2, + 'children' => array( + 'numeric' => array('type' => FILE_ASN1_TYPE_NUMERIC_STRING), + 'printable' => array('type' => FILE_ASN1_TYPE_PRINTABLE_STRING) + ) + ); + + $CountryName = array( + 'type' => FILE_ASN1_TYPE_CHOICE, + // if class isn't present it's assumed to be FILE_ASN1_CLASS_UNIVERSAL or + // (if constant is present) FILE_ASN1_CLASS_CONTEXT_SPECIFIC + 'class' => FILE_ASN1_CLASS_APPLICATION, + 'cast' => 1, + 'children' => array( + 'x121-dcc-code' => array('type' => FILE_ASN1_TYPE_NUMERIC_STRING), + 'iso-3166-alpha2-code' => array('type' => FILE_ASN1_TYPE_PRINTABLE_STRING) + ) + ); + + $AnotherName = array( + 'type' => FILE_ASN1_TYPE_SEQUENCE, + 'children' => array( + 'type-id' => array('type' => FILE_ASN1_TYPE_OBJECT_IDENTIFIER), + 'value' => array( + 'type' => FILE_ASN1_TYPE_ANY, + 'constant' => 0, + 'optional' => true, + 'explicit' => true + ) + ) + ); + + $ExtensionAttribute = array( + 'type' => FILE_ASN1_TYPE_SEQUENCE, + 'children' => array( + 'extension-attribute-type' => array( + 'type' => FILE_ASN1_TYPE_PRINTABLE_STRING, + 'constant' => 0, + 'optional' => true, + 'implicit' => true + ), + 'extension-attribute-value' => array( + 'type' => FILE_ASN1_TYPE_ANY, + 'constant' => 1, + 'optional' => true, + 'explicit' => true + ) + ) + ); + + $ExtensionAttributes = array( + 'type' => FILE_ASN1_TYPE_SET, + 'min' => 1, + 'max' => 256, // ub-extension-attributes + 'children' => $ExtensionAttribute + ); + + $BuiltInDomainDefinedAttribute = array( + 'type' => FILE_ASN1_TYPE_SEQUENCE, + 'children' => array( + 'type' => array('type' => FILE_ASN1_TYPE_PRINTABLE_STRING), + 'value' => array('type' => FILE_ASN1_TYPE_PRINTABLE_STRING) + ) + ); + + $BuiltInDomainDefinedAttributes = array( + 'type' => FILE_ASN1_TYPE_SEQUENCE, + 'min' => 1, + 'max' => 4, // ub-domain-defined-attributes + 'children' => $BuiltInDomainDefinedAttribute + ); + + $BuiltInStandardAttributes = array( + 'type' => FILE_ASN1_TYPE_SEQUENCE, + 'children' => array( + 'country-name' => array('optional' => true) + $CountryName, + 'administration-domain-name' => array('optional' => true) + $AdministrationDomainName, + 'network-address' => array( + 'constant' => 0, + 'optional' => true, + 'implicit' => true + ) + $NetworkAddress, + 'terminal-identifier' => array( + 'constant' => 1, + 'optional' => true, + 'implicit' => true + ) + $TerminalIdentifier, + 'private-domain-name' => array( + 'constant' => 2, + 'optional' => true, + 'explicit' => true + ) + $PrivateDomainName, + 'organization-name' => array( + 'constant' => 3, + 'optional' => true, + 'implicit' => true + ) + $OrganizationName, + 'numeric-user-identifier' => array( + 'constant' => 4, + 'optional' => true, + 'implicit' => true + ) + $NumericUserIdentifier, + 'personal-name' => array( + 'constant' => 5, + 'optional' => true, + 'implicit' => true + ) + $PersonalName, + 'organizational-unit-names' => array( + 'constant' => 6, + 'optional' => true, + 'implicit' => true + ) + $OrganizationalUnitNames + ) + ); + + $ORAddress = array( + 'type' => FILE_ASN1_TYPE_SEQUENCE, + 'children' => array( + 'built-in-standard-attributes' => $BuiltInStandardAttributes, + 'built-in-domain-defined-attributes' => array('optional' => true) + $BuiltInDomainDefinedAttributes, + 'extension-attributes' => array('optional' => true) + $ExtensionAttributes + ) + ); + + $EDIPartyName = array( + 'type' => FILE_ASN1_TYPE_SEQUENCE, + 'children' => array( + 'nameAssigner' => array( + 'constant' => 0, + 'optional' => true, + 'implicit' => true + ) + $DirectoryString, + // partyName is technically required but File_ASN1 doesn't currently support non-optional constants and + // setting it to optional gets the job done in any event. + 'partyName' => array( + 'constant' => 1, + 'optional' => true, + 'implicit' => true + ) + $DirectoryString + ) + ); + + $GeneralName = array( + 'type' => FILE_ASN1_TYPE_CHOICE, + 'children' => array( + 'otherName' => array( + 'constant' => 0, + 'optional' => true, + 'implicit' => true + ) + $AnotherName, + 'rfc822Name' => array( + 'type' => FILE_ASN1_TYPE_IA5_STRING, + 'constant' => 1, + 'optional' => true, + 'implicit' => true + ), + 'dNSName' => array( + 'type' => FILE_ASN1_TYPE_IA5_STRING, + 'constant' => 2, + 'optional' => true, + 'implicit' => true + ), + 'x400Address' => array( + 'constant' => 3, + 'optional' => true, + 'implicit' => true + ) + $ORAddress, + 'directoryName' => array( + 'constant' => 4, + 'optional' => true, + 'explicit' => true + ) + $this->Name, + 'ediPartyName' => array( + 'constant' => 5, + 'optional' => true, + 'implicit' => true + ) + $EDIPartyName, + 'uniformResourceIdentifier' => array( + 'type' => FILE_ASN1_TYPE_IA5_STRING, + 'constant' => 6, + 'optional' => true, + 'implicit' => true + ), + 'iPAddress' => array( + 'type' => FILE_ASN1_TYPE_OCTET_STRING, + 'constant' => 7, + 'optional' => true, + 'implicit' => true + ), + 'registeredID' => array( + 'type' => FILE_ASN1_TYPE_OBJECT_IDENTIFIER, + 'constant' => 8, + 'optional' => true, + 'implicit' => true + ) + ) + ); + + $GeneralNames = array( + 'type' => FILE_ASN1_TYPE_SEQUENCE, + 'min' => 1, + 'max' => -1, + 'children' => $GeneralName + ); + + $this->IssuerAltName = $GeneralNames; + + $ReasonFlags = array( + 'type' => FILE_ASN1_TYPE_BIT_STRING, + 'mapping' => array( + 'unused', + 'keyCompromise', + 'cACompromise', + 'affiliationChanged', + 'superseded', + 'cessationOfOperation', + 'certificateHold', + 'privilegeWithdrawn', + 'aACompromise' + ) + ); + + $DistributionPointName = array( + 'type' => FILE_ASN1_TYPE_CHOICE, + 'children' => array( + 'fullName' => array( + 'constant' => 0, + 'optional' => true, + 'implicit' => true + ) + $GeneralNames, + 'nameRelativeToCRLIssuer' => array( + 'constant' => 1, + 'optional' => true, + 'implicit' => true + ) + $this->RelativeDistinguishedName + ) + ); + + $DistributionPoint = array( + 'type' => FILE_ASN1_TYPE_SEQUENCE, + 'children' => array( + 'distributionPoint' => array( + 'constant' => 0, + 'optional' => true, + 'explicit' => true + ) + $DistributionPointName, + 'reasons' => array( + 'constant' => 1, + 'optional' => true, + 'implicit' => true + ) + $ReasonFlags, + 'cRLIssuer' => array( + 'constant' => 2, + 'optional' => true, + 'implicit' => true + ) + $GeneralNames + ) + ); + + $this->CRLDistributionPoints = array( + 'type' => FILE_ASN1_TYPE_SEQUENCE, + 'min' => 1, + 'max' => -1, + 'children' => $DistributionPoint + ); + + $this->AuthorityKeyIdentifier = array( + 'type' => FILE_ASN1_TYPE_SEQUENCE, + 'children' => array( + 'keyIdentifier' => array( + 'constant' => 0, + 'optional' => true, + 'implicit' => true + ) + $this->KeyIdentifier, + 'authorityCertIssuer' => array( + 'constant' => 1, + 'optional' => true, + 'implicit' => true + ) + $GeneralNames, + 'authorityCertSerialNumber' => array( + 'constant' => 2, + 'optional' => true, + 'implicit' => true + ) + $CertificateSerialNumber + ) + ); + + $PolicyQualifierId = array('type' => FILE_ASN1_TYPE_OBJECT_IDENTIFIER); + + $PolicyQualifierInfo = array( + 'type' => FILE_ASN1_TYPE_SEQUENCE, + 'children' => array( + 'policyQualifierId' => $PolicyQualifierId, + 'qualifier' => array('type' => FILE_ASN1_TYPE_ANY) + ) + ); + + $CertPolicyId = array('type' => FILE_ASN1_TYPE_OBJECT_IDENTIFIER); + + $PolicyInformation = array( + 'type' => FILE_ASN1_TYPE_SEQUENCE, + 'children' => array( + 'policyIdentifier' => $CertPolicyId, + 'policyQualifiers' => array( + 'type' => FILE_ASN1_TYPE_SEQUENCE, + 'min' => 0, + 'max' => -1, + 'optional' => true, + 'children' => $PolicyQualifierInfo + ) + ) + ); + + $this->CertificatePolicies = array( + 'type' => FILE_ASN1_TYPE_SEQUENCE, + 'min' => 1, + 'max' => -1, + 'children' => $PolicyInformation + ); + + $this->PolicyMappings = array( + 'type' => FILE_ASN1_TYPE_SEQUENCE, + 'min' => 1, + 'max' => -1, + 'children' => array( + 'type' => FILE_ASN1_TYPE_SEQUENCE, + 'children' => array( + 'issuerDomainPolicy' => $CertPolicyId, + 'subjectDomainPolicy' => $CertPolicyId + ) + ) + ); + + $KeyPurposeId = array('type' => FILE_ASN1_TYPE_OBJECT_IDENTIFIER); + + $this->ExtKeyUsageSyntax = array( + 'type' => FILE_ASN1_TYPE_SEQUENCE, + 'min' => 1, + 'max' => -1, + 'children' => $KeyPurposeId + ); + + $AccessDescription = array( + 'type' => FILE_ASN1_TYPE_SEQUENCE, + 'children' => array( + 'accessMethod' => array('type' => FILE_ASN1_TYPE_OBJECT_IDENTIFIER), + 'accessLocation' => $GeneralName + ) + ); + + $this->AuthorityInfoAccessSyntax = array( + 'type' => FILE_ASN1_TYPE_SEQUENCE, + 'min' => 1, + 'max' => -1, + 'children' => $AccessDescription + ); + + $this->SubjectAltName = $GeneralNames; + + $this->PrivateKeyUsagePeriod = array( + 'type' => FILE_ASN1_TYPE_SEQUENCE, + 'children' => array( + 'notBefore' => array( + 'constant' => 0, + 'optional' => true, + 'implicit' => true, + 'type' => FILE_ASN1_TYPE_GENERALIZED_TIME), + 'notAfter' => array( + 'constant' => 1, + 'optional' => true, + 'implicit' => true, + 'type' => FILE_ASN1_TYPE_GENERALIZED_TIME) + ) + ); + + $BaseDistance = array('type' => FILE_ASN1_TYPE_INTEGER); + + $GeneralSubtree = array( + 'type' => FILE_ASN1_TYPE_SEQUENCE, + 'children' => array( + 'base' => $GeneralName, + 'minimum' => array( + 'constant' => 0, + 'optional' => true, + 'implicit' => true, + 'default' => new Math_BigInteger(0) + ) + $BaseDistance, + 'maximum' => array( + 'constant' => 1, + 'optional' => true, + 'implicit' => true, + ) + $BaseDistance + ) + ); + + $GeneralSubtrees = array( + 'type' => FILE_ASN1_TYPE_SEQUENCE, + 'min' => 1, + 'max' => -1, + 'children' => $GeneralSubtree + ); + + $this->NameConstraints = array( + 'type' => FILE_ASN1_TYPE_SEQUENCE, + 'children' => array( + 'permittedSubtrees' => array( + 'constant' => 0, + 'optional' => true, + 'implicit' => true + ) + $GeneralSubtrees, + 'excludedSubtrees' => array( + 'constant' => 1, + 'optional' => true, + 'implicit' => true + ) + $GeneralSubtrees + ) + ); + + $this->CPSuri = array('type' => FILE_ASN1_TYPE_IA5_STRING); + + $DisplayText = array( + 'type' => FILE_ASN1_TYPE_CHOICE, + 'children' => array( + 'ia5String' => array('type' => FILE_ASN1_TYPE_IA5_STRING), + 'visibleString' => array('type' => FILE_ASN1_TYPE_VISIBLE_STRING), + 'bmpString' => array('type' => FILE_ASN1_TYPE_BMP_STRING), + 'utf8String' => array('type' => FILE_ASN1_TYPE_UTF8_STRING) + ) + ); + + $NoticeReference = array( + 'type' => FILE_ASN1_TYPE_SEQUENCE, + 'children' => array( + 'organization' => $DisplayText, + 'noticeNumbers' => array( + 'type' => FILE_ASN1_TYPE_SEQUENCE, + 'min' => 1, + 'max' => 200, + 'children' => array('type' => FILE_ASN1_TYPE_INTEGER) + ) + ) + ); + + $this->UserNotice = array( + 'type' => FILE_ASN1_TYPE_SEQUENCE, + 'children' => array( + 'noticeRef' => array( + 'optional' => true, + 'implicit' => true + ) + $NoticeReference, + 'explicitText' => array( + 'optional' => true, + 'implicit' => true + ) + $DisplayText + ) + ); + + // mapping is from + $this->netscape_cert_type = array( + 'type' => FILE_ASN1_TYPE_BIT_STRING, + 'mapping' => array( + 'SSLClient', + 'SSLServer', + 'Email', + 'ObjectSigning', + 'Reserved', + 'SSLCA', + 'EmailCA', + 'ObjectSigningCA' + ) + ); + + $this->netscape_comment = array('type' => FILE_ASN1_TYPE_IA5_STRING); + $this->netscape_ca_policy_url = array('type' => FILE_ASN1_TYPE_IA5_STRING); + + // attribute is used in RFC2986 but we're using the RFC5280 definition + + $Attribute = array( + 'type' => FILE_ASN1_TYPE_SEQUENCE, + 'children' => array( + 'type' => $AttributeType, + 'value'=> array( + 'type' => FILE_ASN1_TYPE_SET, + 'min' => 1, + 'max' => -1, + 'children' => $AttributeValue + ) + ) + ); + + // adapted from + + $Attributes = array( + 'type' => FILE_ASN1_TYPE_SET, + 'min' => 1, + 'max' => -1, + 'children' => $Attribute + ); + + $CertificationRequestInfo = array( + 'type' => FILE_ASN1_TYPE_SEQUENCE, + 'children' => array( + 'version' => array( + 'type' => FILE_ASN1_TYPE_INTEGER, + 'mapping' => array('v1') + ), + 'subject' => $this->Name, + 'subjectPKInfo' => $SubjectPublicKeyInfo, + 'attributes' => array( + 'constant' => 0, + 'optional' => true, + 'implicit' => true + ) + $Attributes, + ) + ); + + $this->CertificationRequest = array( + 'type' => FILE_ASN1_TYPE_SEQUENCE, + 'children' => array( + 'certificationRequestInfo' => $CertificationRequestInfo, + 'signatureAlgorithm' => $AlgorithmIdentifier, + 'signature' => array('type' => FILE_ASN1_TYPE_BIT_STRING) + ) + ); + + $RevokedCertificate = array( + 'type' => FILE_ASN1_TYPE_SEQUENCE, + 'children' => array( + 'userCertificate' => $CertificateSerialNumber, + 'revocationDate' => $Time, + 'crlEntryExtensions' => array( + 'optional' => true + ) + $Extensions + ) + ); + + $TBSCertList = array( + 'type' => FILE_ASN1_TYPE_SEQUENCE, + 'children' => array( + 'version' => array( + 'optional' => true, + 'default' => 'v1' + ) + $Version, + 'signature' => $AlgorithmIdentifier, + 'issuer' => $this->Name, + 'thisUpdate' => $Time, + 'nextUpdate' => array( + 'optional' => true + ) + $Time, + 'revokedCertificates' => array( + 'type' => FILE_ASN1_TYPE_SEQUENCE, + 'optional' => true, + 'min' => 0, + 'max' => -1, + 'children' => $RevokedCertificate + ), + 'crlExtensions' => array( + 'constant' => 0, + 'optional' => true, + 'explicit' => true + ) + $Extensions + ) + ); + + $this->CertificateList = array( + 'type' => FILE_ASN1_TYPE_SEQUENCE, + 'children' => array( + 'tbsCertList' => $TBSCertList, + 'signatureAlgorithm' => $AlgorithmIdentifier, + 'signature' => array('type' => FILE_ASN1_TYPE_BIT_STRING) + ) + ); + + $this->CRLNumber = array('type' => FILE_ASN1_TYPE_INTEGER); + + $this->CRLReason = array('type' => FILE_ASN1_TYPE_ENUMERATED, + 'mapping' => array( + 'unspecified', + 'keyCompromise', + 'cACompromise', + 'affiliationChanged', + 'superseded', + 'cessationOfOperation', + 'certificateHold', + // Value 7 is not used. + 8 => 'removeFromCRL', + 'privilegeWithdrawn', + 'aACompromise' + ) + ); + + $this->IssuingDistributionPoint = array('type' => FILE_ASN1_TYPE_SEQUENCE, + 'children' => array( + 'distributionPoint' => array( + 'constant' => 0, + 'optional' => true, + 'explicit' => true + ) + $DistributionPointName, + 'onlyContainsUserCerts' => array( + 'type' => FILE_ASN1_TYPE_BOOLEAN, + 'constant' => 1, + 'optional' => true, + 'default' => false, + 'implicit' => true + ), + 'onlyContainsCACerts' => array( + 'type' => FILE_ASN1_TYPE_BOOLEAN, + 'constant' => 2, + 'optional' => true, + 'default' => false, + 'implicit' => true + ), + 'onlySomeReasons' => array( + 'constant' => 3, + 'optional' => true, + 'implicit' => true + ) + $ReasonFlags, + 'indirectCRL' => array( + 'type' => FILE_ASN1_TYPE_BOOLEAN, + 'constant' => 4, + 'optional' => true, + 'default' => false, + 'implicit' => true + ), + 'onlyContainsAttributeCerts' => array( + 'type' => FILE_ASN1_TYPE_BOOLEAN, + 'constant' => 5, + 'optional' => true, + 'default' => false, + 'implicit' => true + ) + ) + ); + + $this->InvalidityDate = array('type' => FILE_ASN1_TYPE_GENERALIZED_TIME); + + $this->CertificateIssuer = $GeneralNames; + + // OIDs from RFC5280 and those RFCs mentioned in RFC5280#section-4.1.1.2 + $this->oids = array( + '1.3.6.1.5.5.7' => 'id-pkix', + '1.3.6.1.5.5.7.1' => 'id-pe', + '1.3.6.1.5.5.7.2' => 'id-qt', + '1.3.6.1.5.5.7.3' => 'id-kp', + '1.3.6.1.5.5.7.48' => 'id-ad', + '1.3.6.1.5.5.7.2.1' => 'id-qt-cps', + '1.3.6.1.5.5.7.2.2' => 'id-qt-unotice', + '1.3.6.1.5.5.7.48.1' =>'id-ad-ocsp', + '1.3.6.1.5.5.7.48.2' => 'id-ad-caIssuers', + '1.3.6.1.5.5.7.48.3' => 'id-ad-timeStamping', + '1.3.6.1.5.5.7.48.5' => 'id-ad-caRepository', + '2.5.4' => 'id-at', + '2.5.4.41' => 'id-at-name', + '2.5.4.4' => 'id-at-surname', + '2.5.4.42' => 'id-at-givenName', + '2.5.4.43' => 'id-at-initials', + '2.5.4.44' => 'id-at-generationQualifier', + '2.5.4.3' => 'id-at-commonName', + '2.5.4.7' => 'id-at-localityName', + '2.5.4.8' => 'id-at-stateOrProvinceName', + '2.5.4.10' => 'id-at-organizationName', + '2.5.4.11' => 'id-at-organizationalUnitName', + '2.5.4.12' => 'id-at-title', + '2.5.4.13' => 'id-at-description', + '2.5.4.46' => 'id-at-dnQualifier', + '2.5.4.6' => 'id-at-countryName', + '2.5.4.5' => 'id-at-serialNumber', + '2.5.4.65' => 'id-at-pseudonym', + '2.5.4.17' => 'id-at-postalCode', + '2.5.4.9' => 'id-at-streetAddress', + '2.5.4.45' => 'id-at-uniqueIdentifier', + '2.5.4.72' => 'id-at-role', + + '0.9.2342.19200300.100.1.25' => 'id-domainComponent', + '1.2.840.113549.1.9' => 'pkcs-9', + '1.2.840.113549.1.9.1' => 'id-emailAddress', + '2.5.29' => 'id-ce', + '2.5.29.35' => 'id-ce-authorityKeyIdentifier', + '2.5.29.14' => 'id-ce-subjectKeyIdentifier', + '2.5.29.15' => 'id-ce-keyUsage', + '2.5.29.16' => 'id-ce-privateKeyUsagePeriod', + '2.5.29.32' => 'id-ce-certificatePolicies', + '2.5.29.32.0' => 'anyPolicy', + + '2.5.29.33' => 'id-ce-policyMappings', + '2.5.29.17' => 'id-ce-subjectAltName', + '2.5.29.18' => 'id-ce-issuerAltName', + '2.5.29.9' => 'id-ce-subjectDirectoryAttributes', + '2.5.29.19' => 'id-ce-basicConstraints', + '2.5.29.30' => 'id-ce-nameConstraints', + '2.5.29.36' => 'id-ce-policyConstraints', + '2.5.29.31' => 'id-ce-cRLDistributionPoints', + '2.5.29.37' => 'id-ce-extKeyUsage', + '2.5.29.37.0' => 'anyExtendedKeyUsage', + '1.3.6.1.5.5.7.3.1' => 'id-kp-serverAuth', + '1.3.6.1.5.5.7.3.2' => 'id-kp-clientAuth', + '1.3.6.1.5.5.7.3.3' => 'id-kp-codeSigning', + '1.3.6.1.5.5.7.3.4' => 'id-kp-emailProtection', + '1.3.6.1.5.5.7.3.8' => 'id-kp-timeStamping', + '1.3.6.1.5.5.7.3.9' => 'id-kp-OCSPSigning', + '2.5.29.54' => 'id-ce-inhibitAnyPolicy', + '2.5.29.46' => 'id-ce-freshestCRL', + '1.3.6.1.5.5.7.1.1' => 'id-pe-authorityInfoAccess', + '1.3.6.1.5.5.7.1.11' => 'id-pe-subjectInfoAccess', + '2.5.29.20' => 'id-ce-cRLNumber', + '2.5.29.28' => 'id-ce-issuingDistributionPoint', + '2.5.29.27' => 'id-ce-deltaCRLIndicator', + '2.5.29.21' => 'id-ce-cRLReasons', + '2.5.29.29' => 'id-ce-certificateIssuer', + '2.5.29.23' => 'id-ce-holdInstructionCode', + '2.2.840.10040.2' => 'holdInstruction', + '2.2.840.10040.2.1' => 'id-holdinstruction-none', + '2.2.840.10040.2.2' => 'id-holdinstruction-callissuer', + '2.2.840.10040.2.3' => 'id-holdinstruction-reject', + '2.5.29.24' => 'id-ce-invalidityDate', + + '1.2.840.113549.2.2' => 'md2', + '1.2.840.113549.2.5' => 'md5', + '1.3.14.3.2.26' => 'id-sha1', + '1.2.840.10040.4.1' => 'id-dsa', + '1.2.840.10040.4.3' => 'id-dsa-with-sha1', + '1.2.840.113549.1.1' => 'pkcs-1', + '1.2.840.113549.1.1.1' => 'rsaEncryption', + '1.2.840.113549.1.1.2' => 'md2WithRSAEncryption', + '1.2.840.113549.1.1.4' => 'md5WithRSAEncryption', + '1.2.840.113549.1.1.5' => 'sha1WithRSAEncryption', + '1.2.840.10046.2.1' => 'dhpublicnumber', + '2.16.840.1.101.2.1.1.22' => 'id-keyExchangeAlgorithm', + '1.2.840.10045' => 'ansi-X9-62', + '1.2.840.10045.4' => 'id-ecSigType', + '1.2.840.10045.4.1' => 'ecdsa-with-SHA1', + '1.2.840.10045.1' => 'id-fieldType', + '1.2.840.10045.1.1' => 'prime-field', + '1.2.840.10045.1.2' => 'characteristic-two-field', + '1.2.840.10045.1.2.3' => 'id-characteristic-two-basis', + '1.2.840.10045.1.2.3.1' => 'gnBasis', + '1.2.840.10045.1.2.3.2' => 'tpBasis', + '1.2.840.10045.1.2.3.3' => 'ppBasis', + '1.2.840.10045.2' => 'id-publicKeyType', + '1.2.840.10045.2.1' => 'id-ecPublicKey', + '1.2.840.10045.3' => 'ellipticCurve', + '1.2.840.10045.3.0' => 'c-TwoCurve', + '1.2.840.10045.3.0.1' => 'c2pnb163v1', + '1.2.840.10045.3.0.2' => 'c2pnb163v2', + '1.2.840.10045.3.0.3' => 'c2pnb163v3', + '1.2.840.10045.3.0.4' => 'c2pnb176w1', + '1.2.840.10045.3.0.5' => 'c2pnb191v1', + '1.2.840.10045.3.0.6' => 'c2pnb191v2', + '1.2.840.10045.3.0.7' => 'c2pnb191v3', + '1.2.840.10045.3.0.8' => 'c2pnb191v4', + '1.2.840.10045.3.0.9' => 'c2pnb191v5', + '1.2.840.10045.3.0.10' => 'c2pnb208w1', + '1.2.840.10045.3.0.11' => 'c2pnb239v1', + '1.2.840.10045.3.0.12' => 'c2pnb239v2', + '1.2.840.10045.3.0.13' => 'c2pnb239v3', + '1.2.840.10045.3.0.14' => 'c2pnb239v4', + '1.2.840.10045.3.0.15' => 'c2pnb239v5', + '1.2.840.10045.3.0.16' => 'c2pnb272w1', + '1.2.840.10045.3.0.17' => 'c2pnb304w1', + '1.2.840.10045.3.0.18' => 'c2pnb359v1', + '1.2.840.10045.3.0.19' => 'c2pnb368w1', + '1.2.840.10045.3.0.20' => 'c2pnb431r1', + '1.2.840.10045.3.1' => 'primeCurve', + '1.2.840.10045.3.1.1' => 'prime192v1', + '1.2.840.10045.3.1.2' => 'prime192v2', + '1.2.840.10045.3.1.3' => 'prime192v3', + '1.2.840.10045.3.1.4' => 'prime239v1', + '1.2.840.10045.3.1.5' => 'prime239v2', + '1.2.840.10045.3.1.6' => 'prime239v3', + '1.2.840.10045.3.1.7' => 'prime256v1', + '1.2.840.113549.1.1.7' => 'id-RSAES-OAEP', + '1.2.840.113549.1.1.9' => 'id-pSpecified', + '1.2.840.113549.1.1.10' => 'id-RSASSA-PSS', + '1.2.840.113549.1.1.8' => 'id-mgf1', + '1.2.840.113549.1.1.14' => 'sha224WithRSAEncryption', + '1.2.840.113549.1.1.11' => 'sha256WithRSAEncryption', + '1.2.840.113549.1.1.12' => 'sha384WithRSAEncryption', + '1.2.840.113549.1.1.13' => 'sha512WithRSAEncryption', + '2.16.840.1.101.3.4.2.4' => 'id-sha224', + '2.16.840.1.101.3.4.2.1' => 'id-sha256', + '2.16.840.1.101.3.4.2.2' => 'id-sha384', + '2.16.840.1.101.3.4.2.3' => 'id-sha512', + '1.2.643.2.2.4' => 'id-GostR3411-94-with-GostR3410-94', + '1.2.643.2.2.3' => 'id-GostR3411-94-with-GostR3410-2001', + '1.2.643.2.2.20' => 'id-GostR3410-2001', + '1.2.643.2.2.19' => 'id-GostR3410-94', + // Netscape Object Identifiers from "Netscape Certificate Extensions" + '2.16.840.1.113730' => 'netscape', + '2.16.840.1.113730.1' => 'netscape-cert-extension', + '2.16.840.1.113730.1.1' => 'netscape-cert-type', + '2.16.840.1.113730.1.13' => 'netscape-comment', + '2.16.840.1.113730.1.8' => 'netscape-ca-policy-url', + // the following are X.509 extensions not supported by phpseclib + '1.3.6.1.5.5.7.1.12' => 'id-pe-logotype', + '1.2.840.113533.7.65.0' => 'entrustVersInfo', + '2.16.840.1.113733.1.6.9' => 'verisignPrivate', + // for Certificate Signing Requests + // see http://tools.ietf.org/html/rfc2985 + '1.2.840.113549.1.9.2' => 'unstructuredName', // PKCS #9 unstructured name + '1.2.840.113549.1.9.7' => 'challengePassword' // Challenge password for certificate revocations + ); + } + + /** + * Load X.509 certificate + * + * Returns an associative array describing the X.509 cert or a false if the cert failed to load + * + * @param String $cert + * @access public + * @return Mixed + */ + function loadX509($cert) + { + if (is_array($cert) && isset($cert['tbsCertificate'])) { + $this->currentCert = $cert; + unset($this->signatureSubject); + return false; + } + + $asn1 = new File_ASN1(); + + /* + X.509 certs are assumed to be base64 encoded but sometimes they'll have additional things in them above and beyond the ceritificate. ie. + some may have the following preceeding the -----BEGIN CERTIFICATE----- line: + + subject=/O=organization/OU=org unit/CN=common name + issuer=/O=organization/CN=common name + */ + $temp = preg_replace('#^(?:[^-].+[\r\n]+)+|-.+-|[\r\n]| #', '', $cert); + $temp = preg_match('#^[a-zA-Z\d/+]*={0,2}$#', $temp) ? base64_decode($temp) : false; + if ($temp != false) { + $cert = $temp; + } + + if ($cert === false) { + $this->currentCert = false; + return false; + } + + $asn1->loadOIDs($this->oids); + $decoded = $asn1->decodeBER($cert); + + if (!empty($decoded)) { + $x509 = $asn1->asn1map($decoded[0], $this->Certificate); + } + if (!isset($x509) || $x509 === false) { + $this->currentCert = false; + return false; + } + + $this->signatureSubject = substr($cert, $decoded[0]['content'][0]['start'], $decoded[0]['content'][0]['length']); + + $this->_mapInExtensions($x509, 'tbsCertificate/extensions', $asn1); + + $key = &$x509['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey']; + $key = $this->_reformatKey($x509['tbsCertificate']['subjectPublicKeyInfo']['algorithm']['algorithm'], $key); + + $this->currentCert = $x509; + $this->dn = $x509['tbsCertificate']['subject']; + + $currentKeyIdentifier = $this->getExtension('id-ce-subjectKeyIdentifier'); + $this->currentKeyIdentifier = is_string($currentKeyIdentifier) ? $currentKeyIdentifier : NULL; + + return $x509; + } + + /** + * Save X.509 certificate + * + * @param Array $cert + * @access public + * @return String + */ + function saveX509($cert) + { + if (!is_array($cert) || !isset($cert['tbsCertificate'])) { + return false; + } + + if (is_array($cert['tbsCertificate']['subjectPublicKeyInfo'])) { + switch ($cert['tbsCertificate']['subjectPublicKeyInfo']['algorithm']['algorithm']) { + case 'rsaEncryption': + $cert['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey'] = + base64_encode("\0" . base64_decode(preg_replace('#-.+-|[\r\n]#', '', $cert['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey']))); + } + } + + $asn1 = new File_ASN1(); + + $asn1->loadOIDs($this->oids); + + $filters = array(); + $filters['tbsCertificate']['signature']['parameters'] = + $filters['tbsCertificate']['signature']['issuer']['rdnSequence']['value'] = + $filters['tbsCertificate']['issuer']['rdnSequence']['value'] = + $filters['tbsCertificate']['subject']['rdnSequence']['value'] = + $filters['tbsCertificate']['subjectPublicKeyInfo']['algorithm']['parameters'] = + $filters['signatureAlgorithm']['parameters'] = + $filters['authorityCertIssuer']['directoryName']['rdnSequence']['value'] = + //$filters['policyQualifiers']['qualifier'] = + $filters['distributionPoint']['fullName']['directoryName']['rdnSequence']['value'] = + $filters['directoryName']['rdnSequence']['value'] = + array('type' => FILE_ASN1_TYPE_UTF8_STRING); + /* in the case of policyQualifiers/qualifier, the type has to be FILE_ASN1_TYPE_IA5_STRING. + FILE_ASN1_TYPE_PRINTABLE_STRING will cause OpenSSL's X.509 parser to spit out random + characters. + */ + $filters['policyQualifiers']['qualifier'] = + array('type' => FILE_ASN1_TYPE_IA5_STRING); + + $asn1->loadFilters($filters); + + $this->_mapOutExtensions($cert, 'tbsCertificate/extensions', $asn1); + + $cert = $asn1->encodeDER($cert, $this->Certificate); + + return "-----BEGIN CERTIFICATE-----\r\n" . chunk_split(base64_encode($cert)) . '-----END CERTIFICATE-----'; + } + + /** + * Map extension values from octet string to extension-specific internal + * format. + * + * @param Array ref $root + * @param String $path + * @param Object $asn1 + * @access private + */ + function _mapInExtensions(&$root, $path, $asn1) + { + $extensions = &$this->_subArray($root, $path); + + if (is_array($extensions)) { + for ($i = 0; $i < count($extensions); $i++) { + $id = $extensions[$i]['extnId']; + $value = &$extensions[$i]['extnValue']; + $value = base64_decode($value); + $decoded = $asn1->decodeBER($value); + /* [extnValue] contains the DER encoding of an ASN.1 value + corresponding to the extension type identified by extnID */ + $map = $this->_getMapping($id); + if (!is_bool($map)) { + $mapped = $asn1->asn1map($decoded[0], $map); + $value = $mapped === false ? $decoded[0] : $mapped; + + if ($id == 'id-ce-certificatePolicies') { + for ($j = 0; $j < count($value); $j++) { + if (!isset($value[$j]['policyQualifiers'])) { + continue; + } + for ($k = 0; $k < count($value[$j]['policyQualifiers']); $k++) { + $subid = $value[$j]['policyQualifiers'][$k]['policyQualifierId']; + $map = $this->_getMapping($subid); + $subvalue = &$value[$j]['policyQualifiers'][$k]['qualifier']; + if ($map !== false) { + $decoded = $asn1->decodeBER($subvalue); + $mapped = $asn1->asn1map($decoded[0], $map); + $subvalue = $mapped === false ? $decoded[0] : $mapped; + } + } + } + } + } elseif ($map) { + $value = base64_encode($value); + } + } + } + } + + /** + * Map extension values from extension-specific internal format to + * octet string. + * + * @param Array ref $root + * @param String $path + * @param Object $asn1 + * @access private + */ + function _mapOutExtensions(&$root, $path, $asn1) + { + $extensions = &$this->_subArray($root, $path); + + if (is_array($extensions)) { + $size = count($extensions); + for ($i = 0; $i < $size; $i++) { + $id = $extensions[$i]['extnId']; + $value = &$extensions[$i]['extnValue']; + + switch ($id) { + case 'id-ce-certificatePolicies': + for ($j = 0; $j < count($value); $j++) { + if (!isset($value[$j]['policyQualifiers'])) { + continue; + } + for ($k = 0; $k < count($value[$j]['policyQualifiers']); $k++) { + $subid = $value[$j]['policyQualifiers'][$k]['policyQualifierId']; + $map = $this->_getMapping($subid); + $subvalue = &$value[$j]['policyQualifiers'][$k]['qualifier']; + if ($map !== false) { + // by default File_ASN1 will try to render qualifier as a FILE_ASN1_TYPE_IA5_STRING since it's + // actual type is FILE_ASN1_TYPE_ANY + $subvalue = new File_ASN1_Element($asn1->encodeDER($subvalue, $map)); + } + } + } + break; + case 'id-ce-authorityKeyIdentifier': // use 00 as the serial number instead of an empty string + if (isset($value['authorityCertSerialNumber'])) { + if ($value['authorityCertSerialNumber']->toBytes() == '') { + $temp = chr((FILE_ASN1_CLASS_CONTEXT_SPECIFIC << 6) | 2) . "\1\0"; + $value['authorityCertSerialNumber'] = new File_ASN1_Element($temp); + } + } + } + + /* [extnValue] contains the DER encoding of an ASN.1 value + corresponding to the extension type identified by extnID */ + $map = $this->_getMapping($id); + if (is_bool($map)) { + if (!$map) { + user_error($id . ' is not a currently supported extension', E_USER_NOTICE); + unset($extensions[$i]); + } + } else { + $temp = $asn1->encodeDER($value, $map); + $value = base64_encode($temp); + } + } + } + } + + /** + * Associate an extension ID to an extension mapping + * + * @param String $extnId + * @access private + * @return Mixed + */ + function _getMapping($extnId) + { + switch ($extnId) { + case 'id-ce-keyUsage': + return $this->KeyUsage; + case 'id-ce-basicConstraints': + return $this->BasicConstraints; + case 'id-ce-subjectKeyIdentifier': + return $this->KeyIdentifier; + case 'id-ce-cRLDistributionPoints': + return $this->CRLDistributionPoints; + case 'id-ce-authorityKeyIdentifier': + return $this->AuthorityKeyIdentifier; + case 'id-ce-certificatePolicies': + return $this->CertificatePolicies; + case 'id-ce-extKeyUsage': + return $this->ExtKeyUsageSyntax; + case 'id-pe-authorityInfoAccess': + return $this->AuthorityInfoAccessSyntax; + case 'id-ce-subjectAltName': + return $this->SubjectAltName; + case 'id-ce-privateKeyUsagePeriod': + return $this->PrivateKeyUsagePeriod; + case 'id-ce-issuerAltName': + return $this->IssuerAltName; + case 'id-ce-policyMappings': + return $this->PolicyMappings; + case 'id-ce-nameConstraints': + return $this->NameConstraints; + + case 'netscape-cert-type': + return $this->netscape_cert_type; + case 'netscape-comment': + return $this->netscape_comment; + case 'netscape-ca-policy-url': + return $this->netscape_ca_policy_url; + + // since id-qt-cps isn't a constructed type it will have already been decoded as a string by the time it gets + // back around to asn1map() and we don't want it decoded again. + //case 'id-qt-cps': + // return $this->CPSuri; + case 'id-qt-unotice': + return $this->UserNotice; + + // the following OIDs are unsupported but we don't want them to give notices when calling saveX509(). + case 'id-pe-logotype': // http://www.ietf.org/rfc/rfc3709.txt + case 'entrustVersInfo': + // http://support.microsoft.com/kb/287547 + case '1.3.6.1.4.1.311.20.2': // szOID_ENROLL_CERTTYPE_EXTENSION + case '1.3.6.1.4.1.311.21.1': // szOID_CERTSRV_CA_VERSION + // "SET Secure Electronic Transaction Specification" + // http://www.maithean.com/docs/set_bk3.pdf + case '2.23.42.7.0': // id-set-hashedRootKey + return true; + + // CRL extensions. + case 'id-ce-cRLNumber': + return $this->CRLNumber; + case 'id-ce-deltaCRLIndicator': + return $this->CRLNumber; + case 'id-ce-issuingDistributionPoint': + return $this->IssuingDistributionPoint; + case 'id-ce-freshestCRL': + return $this->CRLDistributionPoints; + case 'id-ce-cRLReasons': + return $this->CRLReason; + case 'id-ce-invalidityDate': + return $this->InvalidityDate; + case 'id-ce-certificateIssuer': + return $this->CertificateIssuer; + } + + return false; + } + + /** + * Load an X.509 certificate as a certificate authority + * + * @param String $cert + * @access public + * @return Boolean + */ + function loadCA($cert) + { + $olddn = $this->dn; + $oldcert = $this->currentCert; + $oldsigsubj = $this->signatureSubject; + + $cert = $this->loadX509($cert); + if (!$cert) { + $this->dn = $olddn; + $this->currentCert = $oldcert; + $this->signatureSubject = $oldsigsubj; + + return false; + } + + /* From RFC5280 "PKIX Certificate and CRL Profile": + + If the keyUsage extension is present, then the subject public key + MUST NOT be used to verify signatures on certificates or CRLs unless + the corresponding keyCertSign or cRLSign bit is set. */ + //$keyUsage = $this->getExtension('id-ce-keyUsage'); + //if ($keyUsage && !in_array('keyCertSign', $keyUsage)) { + // return false; + //} + + /* From RFC5280 "PKIX Certificate and CRL Profile": + + The cA boolean indicates whether the certified public key may be used + to verify certificate signatures. If the cA boolean is not asserted, + then the keyCertSign bit in the key usage extension MUST NOT be + asserted. If the basic constraints extension is not present in a + version 3 certificate, or the extension is present but the cA boolean + is not asserted, then the certified public key MUST NOT be used to + verify certificate signatures. */ + //$basicConstraints = $this->getExtension('id-ce-basicConstraints'); + //if (!$basicConstraints || !$basicConstraints['cA']) { + // return false; + //} + + $this->CAs[] = $cert; + + $this->dn = $olddn; + $this->currentCert = $oldcert; + $this->signatureSubject = $oldsigsubj; + + return true; + } + + /** + * Validate an X.509 certificate against a URL + * + * From RFC2818 "HTTP over TLS": + * + * Matching is performed using the matching rules specified by + * [RFC2459]. If more than one identity of a given type is present in + * the certificate (e.g., more than one dNSName name, a match in any one + * of the set is considered acceptable.) Names may contain the wildcard + * character * which is considered to match any single domain name + * component or component fragment. E.g., *.a.com matches foo.a.com but + * not bar.foo.a.com. f*.com matches foo.com but not bar.com. + * + * @param String $url + * @access public + * @return Boolean + */ + function validateURL($url) + { + if (!is_array($this->currentCert) || !isset($this->currentCert['tbsCertificate'])) { + return false; + } + + $components = parse_url($url); + if (!isset($components['host'])) { + return false; + } + + if ($names = $this->getExtension('id-ce-subjectAltName')) { + foreach ($names as $key => $value) { + $value = str_replace(array('.', '*'), array('\.', '[^.]*'), $value); + switch ($key) { + case 'dNSName': + /* From RFC2818 "HTTP over TLS": + + If a subjectAltName extension of type dNSName is present, that MUST + be used as the identity. Otherwise, the (most specific) Common Name + field in the Subject field of the certificate MUST be used. Although + the use of the Common Name is existing practice, it is deprecated and + Certification Authorities are encouraged to use the dNSName instead. */ + if (preg_match('#^' . $value . '$#', $components['host'])) { + return true; + } + break; + case 'iPAddress': + /* From RFC2818 "HTTP over TLS": + + In some cases, the URI is specified as an IP address rather than a + hostname. In this case, the iPAddress subjectAltName must be present + in the certificate and must exactly match the IP in the URI. */ + if (preg_match('#(?:\d{1-3}\.){4}#', $components['host'] . '.') && preg_match('#^' . $value . '$#', $components['host'])) { + return true; + } + } + } + return false; + } + + if ($value = $this->getDNProp('id-at-commonName')) { + $value = str_replace(array('.', '*'), array('\.', '[^.]*'), $value[0]); + return preg_match('#^' . $value . '$#', $components['host']); + } + + return false; + } + + /** + * Validate a date + * + * If $date isn't defined it is assumed to be the current date. + * + * @param Integer $date optional + * @access public + */ + function validateDate($date = NULL) + { + if (!is_array($this->currentCert) || !isset($this->currentCert['tbsCertificate'])) { + return false; + } + + if (!isset($date)) { + $date = time(); + } + + $notBefore = $this->currentCert['tbsCertificate']['validity']['notBefore']; + $notBefore = isset($notBefore['generalTime']) ? $notBefore['generalTime'] : $notBefore['utcTime']; + + $notAfter = $this->currentCert['tbsCertificate']['validity']['notAfter']; + $notAfter = isset($notAfter['generalTime']) ? $notAfter['generalTime'] : $notAfter['utcTime']; + + switch (true) { + case $date < @strtotime($notBefore): + case $date > @strtotime($notAfter): + return false; + } + + return true; + } + + /** + * Validate a signature + * + * Works on X.509 certs, CSR's and CRL's. + * Returns true if the signature is verified, false if it is not correct or NULL on error + * + * The behavior of this function is inspired by {@link http://php.net/openssl-verify openssl_verify}. + * + * @param Integer $options optional + * @access public + * @return Mixed + */ + function validateSignature($options = 0) + { + if (!is_array($this->currentCert) || !isset($this->signatureSubject)) { + return 0; + } + + /* TODO: + "emailAddress attribute values are not case-sensitive (e.g., "subscriber@example.com" is the same as "SUBSCRIBER@EXAMPLE.COM")." + -- http://tools.ietf.org/html/rfc5280#section-4.1.2.6 + + implement pathLenConstraint in the id-ce-basicConstraints extension */ + + switch (true) { + case isset($this->currentCert['tbsCertificate']): + // self-signed cert + if ($this->currentCert['tbsCertificate']['issuer'] === $this->currentCert['tbsCertificate']['subject']) { + $authorityKey = $this->getExtension('id-ce-authorityKeyIdentifier'); + $subjectKeyID = $this->getExtension('id-ce-subjectKeyIdentifier'); + switch (true) { + case !is_array($authorityKey): + case is_array($authorityKey) && isset($authorityKey['keyIdentifier']) && $authorityKey['keyIdentifier'] === $subjectKeyID: + $signingCert = $this->currentCert; // working cert + } + } + + if (!empty($this->CAs)) { + for ($i = 0; $i < count($this->CAs); $i++) { + // even if the cert is a self-signed one we still want to see if it's a CA; + // if not, we'll conditionally return an error + $ca = $this->CAs[$i]; + if ($this->currentCert['tbsCertificate']['issuer'] === $ca['tbsCertificate']['subject']) { + $authorityKey = $this->getExtension('id-ce-authorityKeyIdentifier'); + $subjectKeyID = $this->getExtension('id-ce-subjectKeyIdentifier', $ca); + switch (true) { + case !is_array($authorityKey): + case is_array($authorityKey) && isset($authorityKey['keyIdentifier']) && $authorityKey['keyIdentifier'] === $subjectKeyID: + $signingCert = $ca; // working cert + break 2; + } + } + } + if (count($this->CAs) == $i && ($options & FILE_X509_VALIDATE_SIGNATURE_BY_CA)) { + return false; + } + } elseif (!isset($signingCert) || ($options & FILE_X509_VALIDATE_SIGNATURE_BY_CA)) { + return false; + } + return $this->_validateSignature( + $signingCert['tbsCertificate']['subjectPublicKeyInfo']['algorithm']['algorithm'], + $signingCert['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey'], + $this->currentCert['signatureAlgorithm']['algorithm'], + substr(base64_decode($this->currentCert['signature']), 1), + $this->signatureSubject + ); + case isset($this->currentCert['certificationRequestInfo']): + return $this->_validateSignature( + $this->currentCert['certificationRequestInfo']['subjectPKInfo']['algorithm']['algorithm'], + $this->currentCert['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey'], + $this->currentCert['signatureAlgorithm']['algorithm'], + substr(base64_decode($this->currentCert['signature']), 1), + $this->signatureSubject + ); + case isset($this->currentCert['tbsCertList']): + if (!empty($this->CAs)) { + for ($i = 0; $i < count($this->CAs); $i++) { + $ca = $this->CAs[$i]; + if ($this->currentCert['tbsCertList']['issuer'] === $ca['tbsCertificate']['subject']) { + $authorityKey = $this->getExtension('id-ce-authorityKeyIdentifier'); + $subjectKeyID = $this->getExtension('id-ce-subjectKeyIdentifier', $ca); + switch (true) { + case !is_array($authorityKey): + case is_array($authorityKey) && isset($authorityKey['keyIdentifier']) && $authorityKey['keyIdentifier'] === $subjectKeyID: + $signingCert = $ca; // working cert + break 2; + } + } + } + } + if (!isset($signingCert)) { + return false; + } + return $this->_validateSignature( + $signingCert['tbsCertificate']['subjectPublicKeyInfo']['algorithm']['algorithm'], + $signingCert['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey'], + $this->currentCert['signatureAlgorithm']['algorithm'], + substr(base64_decode($this->currentCert['signature']), 1), + $this->signatureSubject + ); + default: + return false; + } + } + + /** + * Validates a signature + * + * Returns true if the signature is verified, false if it is not correct or NULL on error + * + * @param String $publicKeyAlgorithm + * @param String $publicKey + * @param String $signatureAlgorithm + * @param String $signature + * @param String $signatureSubject + * @access private + * @return Integer + */ + function _validateSignature($publicKeyAlgorithm, $publicKey, $signatureAlgorithm, $signature, $signatureSubject) + { + switch ($publicKeyAlgorithm) { + case 'rsaEncryption': + if (!class_exists('Crypt_RSA')) { + require_once('Crypt/RSA.php'); + } + $rsa = new Crypt_RSA(); + $rsa->loadKey($publicKey); + + switch ($signatureAlgorithm) { + case 'md2WithRSAEncryption': + case 'md5WithRSAEncryption': + case 'sha1WithRSAEncryption': + case 'sha224WithRSAEncryption': + case 'sha256WithRSAEncryption': + case 'sha384WithRSAEncryption': + case 'sha512WithRSAEncryption': + $rsa->setHash(preg_replace('#WithRSAEncryption$#', '', $signatureAlgorithm)); + $rsa->setSignatureMode(CRYPT_RSA_SIGNATURE_PKCS1); + + if (!@$rsa->verify($signatureSubject, $signature)) { + return false; + } + break; + default: + return NULL; + } + break; + default: + return NULL; + } + + return true; + } + + /** + * Reformat public keys + * + * Reformats a public key to a format supported by phpseclib (if applicable) + * + * @param String $algorithm + * @param String $key + * @access private + * @return String + */ + function _reformatKey($algorithm, $key) + { + switch ($algorithm) { + case 'rsaEncryption': + return + "-----BEGIN PUBLIC KEY-----\r\n" . + // subjectPublicKey is stored as a bit string in X.509 certs. the first byte of a bit string represents how many bits + // in the last byte should be ignored. the following only supports non-zero stuff but as none of the X.509 certs Firefox + // uses as a cert authority actually use a non-zero bit I think it's safe to assume that none do. + chunk_split(base64_encode(substr(base64_decode($key), 1))) . + '-----END PUBLIC KEY-----'; + default: + return $key; + } + } + + /** + * "Normalizes" a Distinguished Name property + * + * @param String $propName + * @access private + * @return Mixed + */ + function _translateDNProp($propName) + { + switch (strtolower($propName)) { + case 'id-at-countryname': + case 'countryname': + case 'c': + return 'id-at-countryName'; + case 'id-at-organizationname': + case 'organizationname': + case 'o': + return 'id-at-organizationName'; + case 'id-at-dnqualifier': + case 'dnqualifier': + return 'id-at-dnQualifier'; + case 'id-at-commonname': + case 'commonname': + case 'cn': + return 'id-at-commonName'; + case 'id-at-stateorprovinceName': + case 'stateorprovincename': + case 'state': + case 'province': + case 'provincename': + case 'st': + return 'id-at-stateOrProvinceName'; + case 'id-at-localityname': + case 'localityname': + case 'l': + return 'id-at-localityName'; + case 'id-emailaddress': + case 'emailaddress': + return 'id-emailAddress'; + case 'id-at-serialnumber': + case 'serialnumber': + return 'id-at-serialNumber'; + case 'id-at-postalcode': + case 'postalcode': + return 'id-at-postalCode'; + case 'id-at-streetaddress': + case 'streetaddress': + return 'id-at-streetAddress'; + case 'id-at-name': + case 'name': + return 'id-at-name'; + case 'id-at-givenname': + case 'givenname': + return 'id-at-givenName'; + case 'id-at-surname': + case 'surname': + case 'sn': + return 'id-at-surname'; + case 'id-at-initials': + case 'initials': + return 'id-at-initials'; + case 'id-at-generationqualifier': + case 'generationqualifier': + return 'id-at-generationQualifier'; + case 'id-at-organizationalunitname': + case 'organizationalunitname': + case 'ou': + return 'id-at-organizationalUnitName'; + case 'id-at-pseudonym': + case 'pseudonym': + return 'id-at-pseudonym'; + case 'id-at-title': + case 'title': + return 'id-at-title'; + case 'id-at-description': + case 'description': + return 'id-at-description'; + case 'id-at-role': + case 'role': + return 'id-at-role'; + case 'id-at-uniqueidentifier': + case 'uniqueidentifier': + case 'x500uniqueidentifier': + return 'id-at-uniqueIdentifier'; + default: + return false; + } + } + + /** + * Set a Distinguished Name property + * + * @param String $propName + * @param Mixed $propValue + * @param String $type optional + * @access public + * @return Boolean + */ + function setDNProp($propName, $propValue, $type = 'utf8String') + { + if (empty($this->dn)) { + $this->dn = array('rdnSequence' => array()); + } + + if (($propName = $this->_translateDNProp($propName)) === false) { + return false; + } + + foreach ((array) $propValue as $v) { + if (!is_array($v) && isset($type)) { + $v = array($type => $v); + } + $this->dn['rdnSequence'][] = array( + array( + 'type' => $propName, + 'value'=> $v + ) + ); + } + + return true; + } + + /** + * Remove Distinguished Name properties + * + * @param String $propName + * @access public + */ + function removeDNProp($propName) + { + if (empty($this->dn)) { + return; + } + + if (($propName = $this->_translateDNProp($propName)) === false) { + return; + } + + $dn = &$this->dn['rdnSequence']; + $size = count($dn); + for ($i = 0; $i < $size; $i++) { + if ($dn[$i][0]['type'] == $propName) { + unset($dn[$i]); + } + } + + $dn = array_values($dn); + } + + /** + * Get Distinguished Name properties + * + * @param String $propName + * @param Array $dn optional + * @param Boolean $withType optional + * @return Mixed + * @access public + */ + function getDNProp($propName, $dn = NULL, $withType = false) + { + if (!isset($dn)) { + $dn = $this->dn; + } + + if (empty($dn)) { + return false; + } + + if (($propName = $this->_translateDNProp($propName)) === false) { + return false; + } + + $dn = $dn['rdnSequence']; + $result = array(); + $asn1 = new File_ASN1(); + for ($i = 0; $i < count($dn); $i++) { + if ($dn[$i][0]['type'] == $propName) { + $v = $dn[$i][0]['value']; + if (!$withType && is_array($v)) { + foreach ($v as $type => $s) { + $type = array_search($type, $asn1->ANYmap, true); + if ($type !== false && isset($asn1->stringTypeSize[$type])) { + $s = $asn1->convert($s, $type); + if ($s !== false) { + $v = $s; + break; + } + } + } + if (is_array($v)) { + $v = array_pop($v); // Always strip data type. + } + } + $result[] = $v; + } + } + + return $result; + } + + /** + * Set a Distinguished Name + * + * @param Mixed $dn + * @param Boolean $merge optional + * @param String $type optional + * @access public + * @return Boolean + */ + function setDN($dn, $merge = false, $type = 'utf8String') + { + if (!$merge) { + $this->dn = NULL; + } + + if (is_array($dn)) { + if (isset($dn['rdnSequence'])) { + $this->dn = $dn; // No merge here. + return true; + } + + // handles stuff generated by openssl_x509_parse() + foreach ($dn as $prop => $value) { + if (!$this->setDNProp($prop, $value, $type)) { + return false; + } + } + return true; + } + + // handles everything else + $results = preg_split('#((?:^|, *|/)(?:C=|O=|OU=|CN=|L=|ST=|SN=|postalCode=|streetAddress=|emailAddress=|serialNumber=|organizationalUnitName=|title=|description=|role=|x500UniqueIdentifier=))#', $dn, -1, PREG_SPLIT_DELIM_CAPTURE); + for ($i = 1; $i < count($results); $i+=2) { + $prop = trim($results[$i], ', =/'); + $value = $results[$i + 1]; + if (!$this->setDNProp($prop, $value, $type)) { + return false; + } + } + + return true; + } + + /** + * Get the Distinguished Name for a certificates subject + * + * @param Mixed $format optional + * @param Array $dn optional + * @access public + * @return Boolean + */ + function getDN($format = FILE_X509_DN_ARRAY, $dn = NULL) + { + if (!isset($dn)) { + $dn = isset($this->currentCert['tbsCertList']) ? $this->currentCert['tbsCertList']['issuer'] : $this->dn; + } + + switch ((int) $format) { + case FILE_X509_DN_ARRAY: + return $dn; + case FILE_X509_DN_ASN1: + $asn1 = new File_ASN1(); + $asn1->loadOIDs($this->oids); + $filters = array(); + $filters['rdnSequence']['value'] = array('type' => FILE_ASN1_TYPE_UTF8_STRING); + $asn1->loadFilters($filters); + return $asn1->encodeDER($dn, $this->Name); + case FILE_X509_DN_OPENSSL: + $dn = $this->getDN(FILE_X509_DN_STRING, $dn); + if ($dn === false) { + return false; + } + $attrs = preg_split('#((?:^|, *|/)[a-z][a-z0-9]*=)#i', $dn, -1, PREG_SPLIT_DELIM_CAPTURE); + $dn = array(); + for ($i = 1; $i < count($attrs); $i += 2) { + $prop = trim($attrs[$i], ', =/'); + $value = $attrs[$i + 1]; + if (!isset($dn[$prop])) { + $dn[$prop] = $value; + } else { + $dn[$prop] = array_merge((array) $dn[$prop], array($value)); + } + } + return $dn; + case FILE_X509_DN_CANON: + // No SEQUENCE around RDNs and all string values normalized as + // trimmed lowercase UTF-8 with all spacing as one blank. + $asn1 = new File_ASN1(); + $asn1->loadOIDs($this->oids); + $filters = array(); + $filters['value'] = array('type' => FILE_ASN1_TYPE_UTF8_STRING); + $asn1->loadFilters($filters); + $result = ''; + foreach ($dn['rdnSequence'] as $rdn) { + foreach ($rdn as &$attr) { + if (is_array($attr['value'])) { + foreach ($attr['value'] as $type => $v) { + $type = array_search($type, $asn1->ANYmap, true); + if ($type !== false && isset($asn1->stringTypeSize[$type])) { + $v = $asn1->convert($v, $type); + if ($v !== false) { + $v = preg_replace('/\s+/', ' ', $v); + $attr['value'] = strtolower(trim($v)); + break; + } + } + } + } + } + $result .= $asn1->encodeDER($rdn, $this->RelativeDistinguishedName); + } + return $result; + case FILE_X509_DN_HASH: + $dn = $this->getDN(FILE_X509_DN_CANON, $dn); + if (!class_exists('Crypt_Hash')) { + require_once('Crypt/Hash.php'); + } + $hash = new Crypt_Hash('sha1'); + $hash = $hash->hash($dn); + extract(unpack('Vhash', $hash)); + return strtolower(bin2hex(pack('N', $hash))); + } + + // Defaut is to return a string. + $start = true; + $output = ''; + $asn1 = new File_ASN1(); + foreach ($dn['rdnSequence'] as $field) { + $prop = $field[0]['type']; + $value = $field[0]['value']; + + $delim = ', '; + switch ($prop) { + case 'id-at-countryName': + $desc = 'C='; + break; + case 'id-at-stateOrProvinceName': + $desc = 'ST='; + break; + case 'id-at-organizationName': + $desc = 'O='; + break; + case 'id-at-organizationalUnitName': + $desc = 'OU='; + break; + case 'id-at-commonName': + $desc = 'CN='; + break; + case 'id-at-localityName': + $desc = 'L='; + break; + case 'id-at-surname': + $desc = 'SN='; + break; + case 'id-at-uniqueIdentifier': + $delim = '/'; + $desc = 'x500UniqueIdentifier='; + break; + default: + $delim = '/'; + $desc = preg_replace('#.+-([^-]+)$#', '$1', $prop) . '='; + } + + if (!$start) { + $output.= $delim; + } + if (is_array($value)) { + foreach ($value as $type => $v) { + $type = array_search($type, $asn1->ANYmap, true); + if ($type !== false && isset($asn1->stringTypeSize[$type])) { + $v = $asn1->convert($v, $type); + if ($v !== false) { + $value = $v; + break; + } + } + } + if (is_array($value)) { + $value = array_pop($value); // Always strip data type. + } + } + $output.= $desc . $value; + $start = false; + } + + return $output; + } + + /** + * Get the Distinguished Name for a certificate/crl issuer + * + * @param Integer $format optional + * @access public + * @return Mixed + */ + function getIssuerDN($format = FILE_X509_DN_ARRAY) + { + switch (true) { + case !isset($this->currentCert) || !is_array($this->currentCert): + break; + case isset($this->currentCert['tbsCertificate']): + return $this->getDN($format, $this->currentCert['tbsCertificate']['issuer']); + case isset($this->currentCert['tbsCertList']): + return $this->getDN($format, $this->currentCert['tbsCertList']['issuer']); + } + + return false; + } + + /** + * Get the Distinguished Name for a certificate/csr subject + * Alias of getDN() + * + * @param Integer $format optional + * @access public + * @return Mixed + */ + function getSubjectDN($format = FILE_X509_DN_ARRAY) + { + switch (true) { + case !empty($this->dn): + return $this->getDN($format); + case !isset($this->currentCert) || !is_array($this->currentCert): + break; + case isset($this->currentCert['tbsCertificate']): + return $this->getDN($format, $this->currentCert['tbsCertificate']['subject']); + case isset($this->currentCert['certificationRequestInfo']): + return $this->getDN($format, $this->currentCert['certificationRequestInfo']['subject']); + } + + return false; + } + + /** + * Get an individual Distinguished Name property for a certificate/crl issuer + * + * @param String $propName + * @param Boolean $withType optional + * @access public + * @return Mixed + */ + function getIssuerDNProp($propName, $withType = false) + { + switch (true) { + case !isset($this->currentCert) || !is_array($this->currentCert): + break; + case isset($this->currentCert['tbsCertificate']): + return $this->getDNProp($propname, $this->currentCert['tbsCertificate']['issuer'], $withType); + case isset($this->currentCert['tbsCertList']): + return $this->getDNProp($propname, $this->currentCert['tbsCertList']['issuer'], $withType); + } + + return false; + } + + /** + * Get an individual Distinguished Name property for a certificate/csr subject + * + * @param String $propName + * @param Boolean $withType optional + * @access public + * @return Mixed + */ + function getSubjectDNProp($propName, $withType = false) + { + switch (true) { + case !empty($this->dn): + return $this->getDNProp($propName, NULL, $withType); + case !isset($this->currentCert) || !is_array($this->currentCert): + break; + case isset($this->currentCert['tbsCertificate']): + return $this->getDNProp($propName, $this->currentCert['tbsCertificate']['subject'], $withType); + case isset($this->currentCert['certificationRequestInfo']): + return $this->getDNProp($propname, $this->currentCert['certificationRequestInfo']['subject'], $withType); + } + + return false; + } + + /** + * Set public key + * + * Key needs to be a Crypt_RSA object + * + * @param Object $key + * @access public + * @return Boolean + */ + function setPublicKey($key) + { + $this->publicKey = $key; + } + + /** + * Set private key + * + * Key needs to be a Crypt_RSA object + * + * @param Object $key + * @access public + */ + function setPrivateKey($key) + { + $this->privateKey = $key; + } + + /** + * Gets the public key + * + * Returns a Crypt_RSA object or a false. + * + * @access public + * @return Mixed + */ + function getPublicKey() + { + if (isset($this->publicKey)) { + return $this->publicKey; + } + + if (isset($this->currentCert) && is_array($this->currentCert)) { + foreach (array('tbsCertificate/subjectPublicKeyInfo', 'certificationRequestInfo/subjectPKInfo') as $path) { + $keyinfo = $this->_subArray($this->currentCert, $path); + if (!empty($keyinfo)) { + break; + } + } + } + if (empty($keyinfo)) { + return false; + } + + $key = $keyinfo['subjectPublicKey']; + + switch ($keyinfo['algorithm']['algorithm']) { + case 'rsaEncryption': + if (!class_exists('Crypt_RSA')) { + require_once('Crypt/RSA.php'); + } + $publicKey = new Crypt_RSA(); + $publicKey->loadKey($key); + $publicKey->setPublicKey(); + break; + default: + return false; + } + + return $publicKey; + } + + /** + * Load a Certificate Signing Request + * + * @param String $csr + * @access public + * @return Mixed + */ + function loadCSR($csr) + { + // see http://tools.ietf.org/html/rfc2986 + + $asn1 = new File_ASN1(); + + $temp = preg_replace('#^(?:[^-].+[\r\n]+)+|-.+-|[\r\n]| #', '', $csr); + $temp = preg_match('#^[a-zA-Z\d/+]*={0,2}$#', $temp) ? base64_decode($temp) : false; + if ($temp != false) { + $csr = $temp; + } + $orig = $csr; + + if ($csr === false) { + $this->currentCert = false; + return false; + } + + $asn1->loadOIDs($this->oids); + $decoded = $asn1->decodeBER($csr); + + if (empty($decoded)) { + $this->currentCert = false; + return false; + } + + $csr = $asn1->asn1map($decoded[0], $this->CertificationRequest); + if (!isset($csr) || $csr === false) { + $this->currentCert = false; + return false; + } + + $this->dn = $csr['certificationRequestInfo']['subject']; + + $this->signatureSubject = substr($orig, $decoded[0]['content'][0]['start'], $decoded[0]['content'][0]['length']); + + $algorithm = &$csr['certificationRequestInfo']['subjectPKInfo']['algorithm']['algorithm']; + $key = &$csr['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey']; + $key = $this->_reformatKey($algorithm, $key); + + switch ($algorithm) { + case 'rsaEncryption': + if (!class_exists('Crypt_RSA')) { + require_once('Crypt/RSA.php'); + } + $this->publicKey = new Crypt_RSA(); + $this->publicKey->loadKey($key); + $this->publicKey->setPublicKey(); + break; + default: + $this->publicKey = NULL; + } + + $this->currentKeyIdentifier = NULL; + $this->currentCert = $csr; + + return $csr; + } + + /** + * Save CSR request + * + * @param Array $csr + * @access public + * @return String + */ + function saveCSR($csr) + { + if (!is_array($csr) || !isset($csr['certificationRequestInfo'])) { + return false; + } + + switch ($csr['certificationRequestInfo']['subjectPKInfo']['algorithm']['algorithm']) { + case 'rsaEncryption': + $csr['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey'] = + base64_encode("\0" . base64_decode(preg_replace('#-.+-|[\r\n]#', '', $csr['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey']))); + } + + $asn1 = new File_ASN1(); + + $asn1->loadOIDs($this->oids); + + $filters = array(); + $filters['certificationRequestInfo']['subject']['rdnSequence']['value'] = + array('type' => FILE_ASN1_TYPE_UTF8_STRING); + + $asn1->loadFilters($filters); + + $csr = $asn1->encodeDER($csr, $this->CertificationRequest); + + return "-----BEGIN CERTIFICATE REQUEST-----\r\n" . chunk_split(base64_encode($csr)) . '-----END CERTIFICATE REQUEST-----'; + } + + /** + * Load a Certificate Revocation List + * + * @param String $crl + * @access public + * @return Mixed + */ + function loadCRL($crl) + { + $asn1 = new File_ASN1(); + + $temp = preg_replace('#^(?:[^-].+[\r\n]+)+|-.+-|[\r\n]| #', '', $csr); + $temp = preg_match('#^[a-zA-Z\d/+]*={0,2}$#', $temp) ? base64_decode($temp) : false; + if ($temp != false) { + $crl = $temp; + } + $orig = $crl; + + if ($crl === false) { + $this->currentCert = false; + return false; + } + + $asn1->loadOIDs($this->oids); + $decoded = $asn1->decodeBER($crl); + + if (empty($decoded)) { + $this->currentCert = false; + return false; + } + + $crl = $asn1->asn1map($decoded[0], $this->CertificateList); + if (!isset($crl) || $crl === false) { + $this->currentCert = false; + return false; + } + + $this->signatureSubject = substr($orig, $decoded[0]['content'][0]['start'], $decoded[0]['content'][0]['length']); + + $this->_mapInExtensions($crl, 'tbsCertList/crlExtensions', $asn1); + $rclist = &$this->_subArray($crl,'tbsCertList/revokedCertificates'); + if (is_array($rclist)) { + foreach ($rclist as $i => $extension) { + $this->_mapInExtensions($rclist, "$i/crlEntryExtensions", $asn1); + } + } + + $this->currentKeyIdentifier = NULL; + $this->currentCert = $crl; + + return $crl; + } + + /** + * Save Certificate Revocation List. + * + * @param Array $crl + * @access public + * @return String + */ + function saveCRL($crl) + { + if (!is_array($crl) || !isset($crl['tbsCertList'])) { + return false; + } + + $asn1 = new File_ASN1(); + + $asn1->loadOIDs($this->oids); + + $filters = array(); + $filters['tbsCertList']['issuer']['rdnSequence']['value'] = + $filters['tbsCertList']['signature']['parameters'] = + $filters['signatureAlgorithm']['parameters'] = + array('type' => FILE_ASN1_TYPE_UTF8_STRING); + + if (empty($crl['tbsCertList']['signature']['parameters'])) { + $filters['tbsCertList']['signature']['parameters'] = + array('type' => FILE_ASN1_TYPE_NULL); + } + + if (empty($crl['signatureAlgorithm']['parameters'])) { + $filters['signatureAlgorithm']['parameters'] = + array('type' => FILE_ASN1_TYPE_NULL); + } + + $asn1->loadFilters($filters); + + $this->_mapOutExtensions($crl, 'tbsCertList/crlExtensions', $asn1); + $rclist = &$this->_subArray($crl,'tbsCertList/revokedCertificates'); + if (is_array($rclist)) { + foreach ($rclist as $i => $extension) { + $this->_mapOutExtensions($rclist, "$i/crlEntryExtensions", $asn1); + } + } + + $crl = $asn1->encodeDER($crl, $this->CertificateList); + + return "-----BEGIN X509 CRL-----\r\n" . chunk_split(base64_encode($crl)) . '-----END X509 CRL-----'; + } + + /** + * Sign an X.509 certificate + * + * $issuer's private key needs to be loaded. + * $subject can be either an existing X.509 cert (if you want to resign it), + * a CSR or something with the DN and public key explicitly set. + * + * @param File_X509 $issuer + * @param File_X509 $subject + * @param String $signatureAlgorithm optional + * @access public + * @return Mixed + */ + function sign($issuer, $subject, $signatureAlgorithm = 'sha1WithRSAEncryption') + { + if (!is_object($issuer->privateKey) || empty($issuer->dn)) { + return false; + } + + if (isset($subject->publicKey) && !($subjectPublicKey = $subject->_formatSubjectPublicKey())) { + return false; + } + + $currentCert = isset($this->currentCert) ? $this->currentCert : NULL; + $signatureSubject = isset($this->signatureSubject) ? $this->signatureSubject: NULL; + + if (isset($subject->currentCert) && is_array($subject->currentCert) && isset($subject->currentCert['tbsCertificate'])) { + $this->currentCert = $subject->currentCert; + $this->currentCert['tbsCertificate']['signature']['algorithm'] = + $this->currentCert['signatureAlgorithm']['algorithm'] = + $signatureAlgorithm; + if (!empty($this->startDate)) { + $this->currentCert['tbsCertificate']['validity']['notBefore']['generalTime'] = $this->startDate; + unset($this->currentCert['tbsCertificate']['validity']['notBefore']['utcTime']); + } + if (!empty($this->endDate)) { + $this->currentCert['tbsCertificate']['validity']['notAfter']['generalTime'] = $this->endDate; + unset($this->currentCert['tbsCertificate']['validity']['notAfter']['utcTime']); + } + if (!empty($this->serialNumber)) { + $this->currentCert['tbsCertificate']['serialNumber'] = $this->serialNumber; + } + if (!empty($subject->dn)) { + $this->currentCert['tbsCertificate']['subject'] = $subject->dn; + } + if (!empty($subject->publicKey)) { + $this->currentCert['tbsCertificate']['subjectPublicKeyInfo'] = $subjectPublicKey; + } + $this->removeExtension('id-ce-authorityKeyIdentifier'); + if (isset($subject->domains)) { + $this->removeExtension('id-ce-subjectAltName'); + } + } else if (isset($subject->currentCert) && is_array($subject->currentCert) && isset($subject->currentCert['tbsCertList'])) { + return false; + } else { + if (!isset($subject->publicKey)) { + return false; + } + + $startDate = !empty($this->startDate) ? $this->startDate : @date('M j H:i:s Y T'); + $endDate = !empty($this->endDate) ? $this->endDate : @date('M j H:i:s Y T', strtotime('+1 year')); + $serialNumber = !empty($this->serialNumber) ? $this->serialNumber : new Math_BigInteger(); + + $this->currentCert = array( + 'tbsCertificate' => + array( + 'version' => 'v3', + 'serialNumber' => $serialNumber, // $this->setserialNumber() + 'signature' => array('algorithm' => $signatureAlgorithm), + 'issuer' => false, // this is going to be overwritten later + 'validity' => array( + 'notBefore' => array('generalTime' => $startDate), // $this->setStartDate() + 'notAfter' => array('generalTime' => $endDate) // $this->setEndDate() + ), + 'subject' => $subject->dn, + 'subjectPublicKeyInfo' => $subjectPublicKey + ), + 'signatureAlgorithm' => array('algorithm' => $signatureAlgorithm), + 'signature' => false // this is going to be overwritten later + ); + } + + $this->currentCert['tbsCertificate']['issuer'] = $issuer->dn; + + if (isset($issuer->currentKeyIdentifier)) { + $this->setExtension('id-ce-authorityKeyIdentifier', array( + //'authorityCertIssuer' => array( + // array( + // 'directoryName' => $issuer->dn + // ) + //), + 'keyIdentifier' => $issuer->currentKeyIdentifier + ) + ); + //$extensions = &$this->currentCert['tbsCertificate']['extensions']; + //if (isset($issuer->serialNumber)) { + // $extensions[count($extensions) - 1]['authorityCertSerialNumber'] = $issuer->serialNumber; + //} + //unset($extensions); + } + + if (isset($subject->currentKeyIdentifier)) { + $this->setExtension('id-ce-subjectKeyIdentifier', $subject->currentKeyIdentifier); + } + + if (isset($subject->domains) && count($subject->domains) > 1) { + $this->setExtension('id-ce-subjectAltName', + array_map(array('File_X509', '_dnsName'), $subject->domains)); + } + + if ($this->caFlag) { + $keyUsage = $this->getExtension('id-ce-keyUsage'); + if (!$keyUsage) { + $keyUsage = array(); + } + + $this->setExtension('id-ce-keyUsage', + array_values(array_unique(array_merge($keyUsage, array('cRLSign', 'keyCertSign')))) + ); + + $basicConstraints = $this->getExtension('id-ce-basicConstraints'); + if (!$basicConstraints) { + $basicConstraints = array(); + } + + $this->setExtension('id-ce-basicConstraints', + array_unique(array_merge(array('cA' => true), $basicConstraints)), true); + + if (!isset($subject->currentKeyIdentifier)) { + $this->setExtension('id-ce-subjectKeyIdentifier', base64_encode($this->computeKeyIdentifier($this->currentCert)), false, false); + } + } + + // resync $this->signatureSubject + // save $tbsCertificate in case there are any File_ASN1_Element objects in it + $tbsCertificate = $this->currentCert['tbsCertificate']; + $this->loadX509($this->saveX509($this->currentCert)); + + $result = $this->_sign($issuer->privateKey, $signatureAlgorithm); + $result['tbsCertificate'] = $tbsCertificate; + + $this->currentCert = $currentCert; + $this->signatureSubject = $signatureSubject; + + return $result; + } + + /** + * Sign a CSR + * + * @access public + * @return Mixed + */ + function signCSR($signatureAlgorithm = 'sha1WithRSAEncryption') + { + if (!is_object($this->privateKey) || empty($this->dn)) { + return false; + } + + $origPublicKey = $this->publicKey; + $class = get_class($this->privateKey); + $this->publicKey = new $class(); + $this->publicKey->loadKey($this->privateKey->getPublicKey()); + $this->publicKey->setPublicKey(); + if (!($publicKey = $this->_formatSubjectPublicKey())) { + return false; + } + $this->publicKey = $origPublicKey; + + $currentCert = isset($this->currentCert) ? $this->currentCert : NULL; + $signatureSubject = isset($this->signatureSubject) ? $this->signatureSubject: NULL; + + if (isset($this->currentCert) && is_array($this->currentCert) && isset($this->currentCert['certificationRequestInfo'])) { + $this->currentCert['signatureAlgorithm']['algorithm'] = + $signatureAlgorithm; + if (!empty($this->dn)) { + $this->currentCert['certificationRequestInfo']['subject'] = $this->dn; + } + $this->currentCert['certificationRequestInfo']['subjectPKInfo'] = $publicKey; + } else { + $this->currentCert = array( + 'certificationRequestInfo' => + array( + 'version' => 'v1', + 'subject' => $this->dn, + 'subjectPKInfo' => $publicKey + ), + 'signatureAlgorithm' => array('algorithm' => $signatureAlgorithm), + 'signature' => false // this is going to be overwritten later + ); + } + + // resync $this->signatureSubject + // save $certificationRequestInfo in case there are any File_ASN1_Element objects in it + $certificationRequestInfo = $this->currentCert['certificationRequestInfo']; + $this->loadCSR($this->saveCSR($this->currentCert)); + + $result = $this->_sign($this->privateKey, $signatureAlgorithm); + $result['certificationRequestInfo'] = $certificationRequestInfo; + + $this->currentCert = $currentCert; + $this->signatureSubject = $signatureSubject; + + return $result; + } + + /** + * Sign a CRL + * + * $issuer's private key needs to be loaded. + * + * @param File_X509 $issuer + * @param File_X509 $crl + * @param String $signatureAlgorithm optional + * @access public + * @return Mixed + */ + function signCRL($issuer, $crl, $signatureAlgorithm = 'sha1WithRSAEncryption') + { + if (!is_object($issuer->privateKey) || empty($issuer->dn)) { + return false; + } + + $currentCert = isset($this->currentCert) ? $this->currentCert : NULL; + $signatureSubject = isset($this->signatureSubject) ? $this->signatureSubject : NULL; + $thisUpdate = !empty($this->startDate) ? $this->startDate : @date('M j H:i:s Y T'); + + if (isset($crl->currentCert) && is_array($crl->currentCert) && isset($crl->currentCert['tbsCertList'])) { + $this->currentCert = $crl->currentCert; + $this->currentCert['tbsCertList']['signature']['algorithm'] = $signatureAlgorithm; + $this->currentCert['signatureAlgorithm']['algorithm'] = $signatureAlgorithm; + } else { + $this->currentCert = array( + 'tbsCertList' => + array( + 'version' => 'v2', + 'signature' => array('algorithm' => $signatureAlgorithm), + 'issuer' => false, // this is going to be overwritten later + 'thisUpdate' => array('generalTime' => $thisUpdate) // $this->setStartDate() + ), + 'signatureAlgorithm' => array('algorithm' => $signatureAlgorithm), + 'signature' => false // this is going to be overwritten later + ); + } + + $tbsCertList = &$this->currentCert['tbsCertList']; + $tbsCertList['issuer'] = $issuer->dn; + $tbsCertList['thisUpdate'] = array('generalTime' => $thisUpdate); + + if (!empty($this->endDate)) { + $tbsCertList['nextUpdate'] = array('generalTime' => $this->endDate); // $this->setEndDate() + } + else { + unset($tbsCertList['nextUpdate']); + } + + if (!empty($this->serialNumber)) { + $crlNumber = $this->serialNumber; + } + else { + $crlNumber = $this->getExtension('id-ce-cRLNumber'); + $crlNumber = $crlNumber !== false ? $crlNumber->add(new Math_BigInteger(1)) : NULL; + } + + $this->removeExtension('id-ce-authorityKeyIdentifier'); + $this->removeExtension('id-ce-issuerAltName'); + + // Be sure version >= v2 if some extension found. + $version = isset($tbsCertList['version']) ? $tbsCertList['version'] : 0; + if (!$version) { + if (!empty($tbsCertList['crlExtensions'])) { + $version = 1; // v2. + } + elseif (!empty($tbsCertList['revokedCertificates'])) { + foreach ($tbsCertList['revokedCertificates'] as $cert) { + if (!empty($cert['crlEntryExtensions'])) { + $version = 1; // v2. + } + } + } + + if ($version) { + $tbsCertList['version'] = $version; + } + } + + // Store additional extensions. + if (!empty($tbsCertList['version'])) { // At least v2. + if (!empty($crlNumber)) { + $this->setExtension('id-ce-cRLNumber', $crlNumber); + } + + if (isset($issuer->currentKeyIdentifier)) { + $this->setExtension('id-ce-authorityKeyIdentifier', array( + //'authorityCertIssuer' => array( + // array( + // 'directoryName' => $issuer->dn + // ) + //), + 'keyIdentifier' => $issuer->currentKeyIdentifier + ) + ); + //$extensions = &$tbsCertList['crlExtensions']; + //if (isset($issuer->serialNumber)) { + // $extensions[count($extensions) - 1]['authorityCertSerialNumber'] = $issuer->serialNumber; + //} + //unset($extensions); + } + + $issuerAltName = $this->getExtension('id-ce-subjectAltName', $issuer->currentCert); + + if ($issuerAltName !== false) { + $this->setExtension('id-ce-issuerAltName', $issuerAltName); + } + } + + if (empty($tbsCertList['revokedCertificates'])) { + unset($tbsCertList['revokedCertificates']); + } + + unset($tbsCertList); + + // resync $this->signatureSubject + // save $tbsCertList in case there are any File_ASN1_Element objects in it + $tbsCertList = $this->currentCert['tbsCertList']; + $this->loadCRL($this->saveCRL($this->currentCert)); + + $result = $this->_sign($issuer->privateKey, $signatureAlgorithm); + $result['tbsCertList'] = $tbsCertList; + + $this->currentCert = $currentCert; + $this->signatureSubject = $signatureSubject; + + return $result; + } + + /** + * X.509 certificate signing helper function. + * + * @param Object $key + * @param File_X509 $subject + * @param String $signatureAlgorithm + * @access public + * @return Mixed + */ + function _sign($key, $signatureAlgorithm) + { + switch (strtolower(get_class($key))) { + case 'crypt_rsa': + switch ($signatureAlgorithm) { + case 'md2WithRSAEncryption': + case 'md5WithRSAEncryption': + case 'sha1WithRSAEncryption': + case 'sha224WithRSAEncryption': + case 'sha256WithRSAEncryption': + case 'sha384WithRSAEncryption': + case 'sha512WithRSAEncryption': + $key->setHash(preg_replace('#WithRSAEncryption$#', '', $signatureAlgorithm)); + $key->setSignatureMode(CRYPT_RSA_SIGNATURE_PKCS1); + + $this->currentCert['signature'] = base64_encode("\0" . $key->sign($this->signatureSubject)); + return $this->currentCert; + } + default: + return false; + } + } + + /** + * Set certificate start date + * + * @param String $date + * @access public + */ + function setStartDate($date) + { + $this->startDate = @date('M j H:i:s Y T', @strtotime($date)); + } + + /** + * Set certificate end date + * + * @param String $date + * @access public + */ + function setEndDate($date) + { + /* + To indicate that a certificate has no well-defined expiration date, + the notAfter SHOULD be assigned the GeneralizedTime value of + 99991231235959Z. + + -- http://tools.ietf.org/html/rfc5280#section-4.1.2.5 + */ + if (strtolower($date) == 'lifetime') { + $temp = '99991231235959Z'; + $asn1 = new File_ASN1(); + $temp = chr(FILE_ASN1_TYPE_GENERALIZED_TIME) . $asn1->_encodeLength(strlen($temp)) . $temp; + $this->endDate = new File_ASN1_Element($temp); + } else { + $this->endDate = @date('M j H:i:s Y T', @strtotime($date)); + } + } + + /** + * Set Serial Number + * + * @param String $serial + * @param $base optional + * @access public + */ + function setSerialNumber($serial, $base = -256) + { + $this->serialNumber = new Math_BigInteger($serial, $base); + } + + /** + * Turns the certificate into a certificate authority + * + * @access public + */ + function makeCA() + { + $this->caFlag = true; + } + + /** + * Get a reference to a subarray + * + * @param array $root + * @param String $path absolute path with / as component separator + * @param Boolean $create optional + * @access private + * @return array item ref or false + */ + function &_subArray(&$root, $path, $create = false) + { + $false = false; + + if (!is_array($root)) { + return $false; + } + + foreach (explode('/', $path) as $i) { + if (!is_array($root)) { + return $false; + } + + if (!isset($root[$i])) { + if (!$create) { + return $false; + } + + $root[$i] = array(); + } + + $root = &$root[$i]; + } + + return $root; + } + + /** + * Get a reference to an extension subarray + * + * @param array $root + * @param String $path optional absolute path with / as component separator + * @param Boolean $create optional + * @access private + * @return array ref or false + */ + function &_extensions(&$root, $path = NULL, $create = false) + { + if (!isset($root)) { + $root = $this->currentCert; + } + + switch (true) { + case !empty($path): + case !is_array($root): + break; + case isset($root['tbsCertificate']): + $path = 'tbsCertificate/extensions'; + break; + case isset($root['tbsCertList']): + $path = 'tbsCertList/crlExtensions'; + break; + } + + $extensions = &$this->_subArray($root, $path, $create); + + if (!is_array($extensions)) { + $false = false; + return $false; + } + + return $extensions; + } + + /** + * Remove an Extension + * + * @param String $id + * @param String $path optional + * @access private + * @return Boolean + */ + function _removeExtension($id, $path = NULL) + { + $extensions = &$this->_extensions($this->currentCert, $path); + + if (!is_array($extensions)) { + return false; + } + + $result = false; + foreach ($extensions as $key => $value) { + if ($value['extnId'] == $id) { + unset($extensions[$key]); + $result = true; + } + } + + $extensions = array_values($extensions); + return $result; + } + + /** + * Get an Extension + * + * Returns the extension if it exists and false if not + * + * @param String $id + * @param Array $cert optional + * @param String $path optional + * @access private + * @return Mixed + */ + function _getExtension($id, $cert = NULL, $path = NULL) + { + $extensions = $this->_extensions($cert, $path); + + if (!is_array($extensions)) { + return false; + } + + foreach ($extensions as $key => $value) { + if ($value['extnId'] == $id) { + return $value['extnValue']; + } + } + + return false; + } + + /** + * Returns a list of all extensions in use + * + * @param array $cert optional + * @param String $path optional + * @access private + * @return Array + */ + function _getExtensions($cert = NULL, $path = NULL) + { + $exts = $this->_extensions($cert, $path); + $extensions = array(); + + if (is_array($exts)) { + foreach ($exts as $extension) { + $extensions[] = $extension['extnId']; + } + } + + return $extensions; + } + + /** + * Set an Extension + * + * @param String $id + * @param Mixed $value + * @param Boolean $critical optional + * @param Boolean $replace optional + * @param String $path optional + * @access private + * @return Boolean + */ + function _setExtension($id, $value, $critical = false, $replace = true, $path = NULL) + { + $extensions = &$this->_extensions($this->currentCert, $path, true); + + if (!is_array($extensions)) { + return false; + } + + $newext = array('extnId' => $id, 'critical' => $critical, 'extnValue' => $value); + + foreach ($extensions as $key => $value) { + if ($value['extnId'] == $id) { + if (!$replace) { + return false; + } + + $extensions[$key] = $newext; + return true; + } + } + + $extensions[] = $newext; + return true; + } + + /** + * Remove a certificate or CRL Extension + * + * @param String $id + * @access public + * @return Boolean + */ + function removeExtension($id) + { + return $this->_removeExtension($id); + } + + /** + * Get a certificate or CRL Extension + * + * Returns the extension if it exists and false if not + * + * @param String $id + * @param Array $cert optional + * @access public + * @return Mixed + */ + function getExtension($id, $cert = NULL) + { + return $this->_getExtension($id, $cert); + } + + /** + * Returns a list of all extensions in use in certificate or CRL + * + * @param array $cert optional + * @access public + * @return Array + */ + function getExtensions($cert = NULL) + { + return $this->_getExtensions($cert); + } + + /** + * Set a certificate or CRL Extension + * + * @param String $id + * @param Mixed $value + * @param Boolean $critical optional + * @param Boolean $replace optional + * @access public + * @return Boolean + */ + function setExtension($id, $value, $critical = false, $replace = true) + { + return $this->_setExtension($id, $value, $critical, $replace); + } + + /** + * Sets the subject key identifier + * + * This is used by the id-ce-authorityKeyIdentifier and the id-ce-subjectKeyIdentifier extensions. + * + * @param String $value + * @access public + */ + function setKeyIdentifier($value) + { + if (empty($value)) { + unset($this->currentKeyIdentifier); + } else { + $this->currentKeyIdentifier = base64_encode($value); + } + } + + /** + * Compute a public key identifier. + * + * Although key identifiers may be set to any unique value, this function + * computes key identifiers from public key according to the two + * recommended methods (4.2.1.2 RFC 3280). + * Highly polymorphic: try to accept all possible forms of key: + * - Key object + * - File_X509 object with public or private key defined + * - Certificate or CSR array + * - File_ASN1_Element object + * - PEM or DER string + * + * @param Mixed $key optional + * @param Integer $method optional + * @access public + * @return String binary key identifier + */ + function computeKeyIdentifier($key = NULL, $method = 1) + { + if (is_null($key)) { + $key = $this; + } + + switch (true) { + case is_string($key): + break; + case is_array($key) && isset($key['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey']): + return $this->computeKeyIdentifier($key['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey'], $method); + case is_array($key) && isset($key['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey']): + return $this->computeKeyIdentifier($key['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey'], $method); + case !is_object($key): + return false; + case strtolower(get_class($key)) == 'file_asn1_element': + $asn1 = new File_ASN1(); + $decoded = $asn1->decodeBER($cert); + if (empty($decoded)) { + return false; + } + $key = $asn1->asn1map($decoded[0], array('type' => FILE_ASN1_TYPE_BIT_STRING)); + break; + case strtolower(get_class($key)) == 'file_x509': + if (isset($key->publicKey)) { + return $this->computeKeyIdentifier($key->publicKey, $method); + } + if (isset($key->privateKey)) { + return $this->computeKeyIdentifier($key->privateKey, $method); + } + if (isset($key->currentCert['tbsCertificate']) || isset($key->currentCert['certificationRequestInfo'])) { + return $this->computeKeyIdentifier($key->currentCert, $method); + } + return false; + default: // Should be a key object (i.e.: Crypt_RSA). + $key = $key->getPublicKey(CRYPT_RSA_PUBLIC_FORMAT_PKCS1_RAW); + break; + } + + // If in PEM format, convert to binary. + if (preg_match('#^-----BEGIN #', $key)) { + $key = base64_decode(preg_replace('#-.+-|[\r\n]#', '', $key)); + } + + // Now we have the key string: compute its sha-1 sum. + if (!class_exists('Crypt_Hash')) { + require_once('Crypt/Hash.php'); + } + $hash = new Crypt_Hash('sha1'); + $hash = $hash->hash($key); + + if ($method == 2) { + $hash = substr($hash, -8); + $hash[0] = chr((ord($hash[0]) & 0x0F) | 0x40); + } + + return $hash; + } + + /** + * Format a public key as appropriate + * + * @access private + * @return Array + */ + function _formatSubjectPublicKey() + { + if (!isset($this->publicKey) || !is_object($this->publicKey)) { + return false; + } + + switch (strtolower(get_class($this->publicKey))) { + case 'crypt_rsa': + // the following two return statements do the same thing. i dunno.. i just prefer the later for some reason. + // the former is a good example of how to do fuzzing on the public key + //return new File_ASN1_Element(base64_decode(preg_replace('#-.+-|[\r\n]#', '', $this->publicKey->getPublicKey()))); + return array( + 'algorithm' => array('algorithm' => 'rsaEncryption'), + 'subjectPublicKey' => $this->publicKey->getPublicKey(CRYPT_RSA_PUBLIC_FORMAT_PKCS1_RAW) + ); + default: + return false; + } + } + + /** + * Set the domain name's which the cert is to be valid for + * + * @access public + * @return Array + */ + function setDomain() + { + $this->domains = func_get_args(); + $this->removeDNProp('id-at-commonName'); + $this->setDNProp('id-at-commonName', $this->domains[0]); + } + + /** + * Helper function to build domain array + * + * @access private + * @param String $domain + * @return Array + */ + function _dnsName($domain) + { + return array('dNSName' => $domain); + } + + /** + * Get the index of a revoked certificate. + * + * @param array $rclist + * @param String $serial + * @param Boolean $create optional + * @access private + * @return Integer or false + */ + function _revokedCertificate(&$rclist, $serial, $create = false) + { + $serial = new Math_BigInteger($serial); + + foreach ($rclist as $i => $rc) { + if (!($serial->compare($rc['userCertificate']))) { + return $i; + } + } + + if (!$create) { + return false; + } + + $i = count($rclist); + $rclist[] = array('userCertificate' => $serial, + 'revocationDate' => array('generalTime' => @date('M j H:i:s Y T'))); + return $i; + } + + /** + * Revoke a certificate. + * + * @param String $serial + * @param String $date optional + * @access public + * @return Boolean + */ + function revoke($serial, $date = NULL) + { + if (isset($this->currentCert['tbsCertList'])) { + if (is_array($rclist = &$this->_subArray($this->currentCert, 'tbsCertList/revokedCertificates', true))) { + if ($this->_revokedCertificate($rclist, $serial) === false) { // If not yet revoked + if (($i = $this->_revokedCertificate($rclist, $serial, true)) !== false) { + + if (!empty($date)) { + $rclist[$i]['revocationDate'] = array('generalTime' => $date); + } + + return true; + } + } + } + } + + return false; + } + + /** + * Unrevoke a certificate. + * + * @param String $serial + * @access public + * @return Boolean + */ + function unrevoke($serial) + { + if (is_array($rclist = &$this->_subArray($this->currentCert, 'tbsCertList/revokedCertificates'))) { + if (($i = $this->_revokedCertificate($rclist, $serial)) !== false) { + unset($rclist[$i]); + $rclist = array_values($rclist); + return true; + } + } + + return false; + } + + /** + * Get a revoked certificate. + * + * @param String $serial + * @access public + * @return Mixed + */ + function getRevoked($serial) + { + if (is_array($rclist = $this->_subArray($this->currentCert, 'tbsCertList/revokedCertificates'))) { + if (($i = $this->_revokedCertificate($rclist, $serial)) !== false) { + return $rclist[$i]; + } + } + + return false; + } + + /** + * List revoked certificates + * + * @param array $crl optional + * @access public + * @return array + */ + function listRevoked($crl = NULL) + { + if (!isset($crl)) { + $crl = $this->currentCert; + } + + if (!isset($crl['tbsCertList'])) { + return false; + } + + $result = array(); + + if (is_array($rclist = $this->_subArray($crl, 'tbsCertList/revokedCertificates'))) { + foreach ($rclist as $rc) { + $result[] = $rc['userCertificate']->toString(); + } + } + + return $result; + } + + /** + * Remove a Revoked Certificate Extension + * + * @param String $serial + * @param String $id + * @access public + * @return Boolean + */ + function removeRevokedCertificateExtension($serial, $id) + { + if (is_array($rclist = &$this->_subArray($this->currentCert, 'tbsCertList/revokedCertificates'))) { + if (($i = $this->_revokedCertificate($rclist, $serial)) !== false) { + return $this->_removeExtension($id, "tbsCertList/revokedCertificates/$i/crlEntryExtensions"); + } + } + + return false; + } + + /** + * Get a Revoked Certificate Extension + * + * Returns the extension if it exists and false if not + * + * @param String $serial + * @param String $id + * @param Array $crl optional + * @access public + * @return Mixed + */ + function getRevokedCertificateExtension($serial, $id, $crl = NULL) + { + if (!isset($crl)) { + $crl = $this->currentCert; + } + + if (is_array($rclist = $this->_subArray($crl, 'tbsCertList/revokedCertificates'))) { + if (($i = $this->_revokedCertificate($rclist, $serial)) !== false) { + return $this->_getExtension($id, $crl, "tbsCertList/revokedCertificates/$i/crlEntryExtensions"); + } + } + + return false; + } + + /** + * Returns a list of all extensions in use for a given revoked certificate + * + * @param String $serial + * @param array $crl optional + * @access public + * @return Array + */ + function getRevokedCertificateExtensions($serial, $crl = NULL) + { + if (!isset($crl)) { + $crl = $this->currentCert; + } + + if (is_array($rclist = $this->_subArray($crl, 'tbsCertList/revokedCertificates'))) { + if (($i = $this->_revokedCertificate($rclist, $serial)) !== false) { + return $this->_getExtensions($crl, "tbsCertList/revokedCertificates/$i/crlEntryExtensions"); + } + } + + return false; + } + + /** + * Set a Revoked Certificate Extension + * + * @param String $serial + * @param String $id + * @param Mixed $value + * @param Boolean $critical optional + * @param Boolean $replace optional + * @access public + * @return Boolean + */ + function setRevokedCertificateExtension($serial, $id, $value, $critical = false, $replace = true) + { + if (isset($this->currentCert['tbsCertList'])) { + if (is_array($rclist = &$this->_subArray($this->currentCert, 'tbsCertList/revokedCertificates', true))) { + if (($i = $this->_revokedCertificate($rclist, $serial, true)) !== false) { + return $this->_setExtension($id, $value, $critical, $replace, "tbsCertList/revokedCertificates/$i/crlEntryExtensions"); + } + } + } + + return false; + } +} diff --git a/3rdparty/phpseclib/Math/BigInteger.php b/3rdparty/phpseclib/Math/BigInteger.php new file mode 100644 index 00000000000..b88680b0122 --- /dev/null +++ b/3rdparty/phpseclib/Math/BigInteger.php @@ -0,0 +1,3630 @@ +> and << cannot be used, nor can the modulo operator %, + * which only supports integers. Although this fact will slow this library down, the fact that such a high + * base is being used should more than compensate. + * + * When PHP version 6 is officially released, we'll be able to use 64-bit integers. This should, once again, + * allow bitwise operators, and will increase the maximum possible base to 2**31 (or 2**62 for addition / + * subtraction). + * + * Numbers are stored in {@link http://en.wikipedia.org/wiki/Endianness little endian} format. ie. + * (new Math_BigInteger(pow(2, 26)))->value = array(0, 1) + * + * Useful resources are as follows: + * + * - {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf Handbook of Applied Cryptography (HAC)} + * - {@link http://math.libtomcrypt.com/files/tommath.pdf Multi-Precision Math (MPM)} + * - Java's BigInteger classes. See /j2se/src/share/classes/java/math in jdk-1_5_0-src-jrl.zip + * + * Here's an example of how to use this library: + * + * add($b); + * + * echo $c->toString(); // outputs 5 + * ?> + * + * + * LICENSE: Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @category Math + * @package Math_BigInteger + * @author Jim Wigginton + * @copyright MMVI Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @version $Id: BigInteger.php,v 1.33 2010/03/22 22:32:03 terrafrost Exp $ + * @link http://pear.php.net/package/Math_BigInteger + */ + +/**#@+ + * Reduction constants + * + * @access private + * @see Math_BigInteger::_reduce() + */ +/** + * @see Math_BigInteger::_montgomery() + * @see Math_BigInteger::_prepMontgomery() + */ +define('MATH_BIGINTEGER_MONTGOMERY', 0); +/** + * @see Math_BigInteger::_barrett() + */ +define('MATH_BIGINTEGER_BARRETT', 1); +/** + * @see Math_BigInteger::_mod2() + */ +define('MATH_BIGINTEGER_POWEROF2', 2); +/** + * @see Math_BigInteger::_remainder() + */ +define('MATH_BIGINTEGER_CLASSIC', 3); +/** + * @see Math_BigInteger::__clone() + */ +define('MATH_BIGINTEGER_NONE', 4); +/**#@-*/ + +/**#@+ + * Array constants + * + * Rather than create a thousands and thousands of new Math_BigInteger objects in repeated function calls to add() and + * multiply() or whatever, we'll just work directly on arrays, taking them in as parameters and returning them. + * + * @access private + */ +/** + * $result[MATH_BIGINTEGER_VALUE] contains the value. + */ +define('MATH_BIGINTEGER_VALUE', 0); +/** + * $result[MATH_BIGINTEGER_SIGN] contains the sign. + */ +define('MATH_BIGINTEGER_SIGN', 1); +/**#@-*/ + +/**#@+ + * @access private + * @see Math_BigInteger::_montgomery() + * @see Math_BigInteger::_barrett() + */ +/** + * Cache constants + * + * $cache[MATH_BIGINTEGER_VARIABLE] tells us whether or not the cached data is still valid. + */ +define('MATH_BIGINTEGER_VARIABLE', 0); +/** + * $cache[MATH_BIGINTEGER_DATA] contains the cached data. + */ +define('MATH_BIGINTEGER_DATA', 1); +/**#@-*/ + +/**#@+ + * Mode constants. + * + * @access private + * @see Math_BigInteger::Math_BigInteger() + */ +/** + * To use the pure-PHP implementation + */ +define('MATH_BIGINTEGER_MODE_INTERNAL', 1); +/** + * To use the BCMath library + * + * (if enabled; otherwise, the internal implementation will be used) + */ +define('MATH_BIGINTEGER_MODE_BCMATH', 2); +/** + * To use the GMP library + * + * (if present; otherwise, either the BCMath or the internal implementation will be used) + */ +define('MATH_BIGINTEGER_MODE_GMP', 3); +/**#@-*/ + +/** + * The largest digit that may be used in addition / subtraction + * + * (we do pow(2, 52) instead of using 4503599627370496, directly, because some PHP installations + * will truncate 4503599627370496) + * + * @access private + */ +define('MATH_BIGINTEGER_MAX_DIGIT52', pow(2, 52)); + +/** + * Karatsuba Cutoff + * + * At what point do we switch between Karatsuba multiplication and schoolbook long multiplication? + * + * @access private + */ +define('MATH_BIGINTEGER_KARATSUBA_CUTOFF', 25); + +/** + * Pure-PHP arbitrary precision integer arithmetic library. Supports base-2, base-10, base-16, and base-256 + * numbers. + * + * @author Jim Wigginton + * @version 1.0.0RC4 + * @access public + * @package Math_BigInteger + */ +class Math_BigInteger { + /** + * Holds the BigInteger's value. + * + * @var Array + * @access private + */ + var $value; + + /** + * Holds the BigInteger's magnitude. + * + * @var Boolean + * @access private + */ + var $is_negative = false; + + /** + * Random number generator function + * + * @see setRandomGenerator() + * @access private + */ + var $generator = 'mt_rand'; + + /** + * Precision + * + * @see setPrecision() + * @access private + */ + var $precision = -1; + + /** + * Precision Bitmask + * + * @see setPrecision() + * @access private + */ + var $bitmask = false; + + /** + * Mode independant value used for serialization. + * + * If the bcmath or gmp extensions are installed $this->value will be a non-serializable resource, hence the need for + * a variable that'll be serializable regardless of whether or not extensions are being used. Unlike $this->value, + * however, $this->hex is only calculated when $this->__sleep() is called. + * + * @see __sleep() + * @see __wakeup() + * @var String + * @access private + */ + var $hex; + + /** + * Converts base-2, base-10, base-16, and binary strings (eg. base-256) to BigIntegers. + * + * If the second parameter - $base - is negative, then it will be assumed that the number's are encoded using + * two's compliment. The sole exception to this is -10, which is treated the same as 10 is. + * + * Here's an example: + * + * toString(); // outputs 50 + * ?> + * + * + * @param optional $x base-10 number or base-$base number if $base set. + * @param optional integer $base + * @return Math_BigInteger + * @access public + */ + function Math_BigInteger($x = 0, $base = 10) + { + if ( !defined('MATH_BIGINTEGER_MODE') ) { + switch (true) { + case extension_loaded('gmp'): + define('MATH_BIGINTEGER_MODE', MATH_BIGINTEGER_MODE_GMP); + break; + case extension_loaded('bcmath'): + define('MATH_BIGINTEGER_MODE', MATH_BIGINTEGER_MODE_BCMATH); + break; + default: + define('MATH_BIGINTEGER_MODE', MATH_BIGINTEGER_MODE_INTERNAL); + } + } + + if (function_exists('openssl_public_encrypt') && !defined('MATH_BIGINTEGER_OPENSSL_DISABLE') && !defined('MATH_BIGINTEGER_OPENSSL_ENABLED')) { + define('MATH_BIGINTEGER_OPENSSL_ENABLED', true); + } + + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + if (is_resource($x) && get_resource_type($x) == 'GMP integer') { + $this->value = $x; + return; + } + $this->value = gmp_init(0); + break; + case MATH_BIGINTEGER_MODE_BCMATH: + $this->value = '0'; + break; + default: + $this->value = array(); + } + + // '0' counts as empty() but when the base is 256 '0' is equal to ord('0') or 48 + // '0' is the only value like this per http://php.net/empty + if (empty($x) && (abs($base) != 256 || $x !== '0')) { + return; + } + + switch ($base) { + case -256: + if (ord($x[0]) & 0x80) { + $x = ~$x; + $this->is_negative = true; + } + case 256: + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + $sign = $this->is_negative ? '-' : ''; + $this->value = gmp_init($sign . '0x' . bin2hex($x)); + break; + case MATH_BIGINTEGER_MODE_BCMATH: + // round $len to the nearest 4 (thanks, DavidMJ!) + $len = (strlen($x) + 3) & 0xFFFFFFFC; + + $x = str_pad($x, $len, chr(0), STR_PAD_LEFT); + + for ($i = 0; $i < $len; $i+= 4) { + $this->value = bcmul($this->value, '4294967296', 0); // 4294967296 == 2**32 + $this->value = bcadd($this->value, 0x1000000 * ord($x[$i]) + ((ord($x[$i + 1]) << 16) | (ord($x[$i + 2]) << 8) | ord($x[$i + 3])), 0); + } + + if ($this->is_negative) { + $this->value = '-' . $this->value; + } + + break; + // converts a base-2**8 (big endian / msb) number to base-2**26 (little endian / lsb) + default: + while (strlen($x)) { + $this->value[] = $this->_bytes2int($this->_base256_rshift($x, 26)); + } + } + + if ($this->is_negative) { + if (MATH_BIGINTEGER_MODE != MATH_BIGINTEGER_MODE_INTERNAL) { + $this->is_negative = false; + } + $temp = $this->add(new Math_BigInteger('-1')); + $this->value = $temp->value; + } + break; + case 16: + case -16: + if ($base > 0 && $x[0] == '-') { + $this->is_negative = true; + $x = substr($x, 1); + } + + $x = preg_replace('#^(?:0x)?([A-Fa-f0-9]*).*#', '$1', $x); + + $is_negative = false; + if ($base < 0 && hexdec($x[0]) >= 8) { + $this->is_negative = $is_negative = true; + $x = bin2hex(~pack('H*', $x)); + } + + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + $temp = $this->is_negative ? '-0x' . $x : '0x' . $x; + $this->value = gmp_init($temp); + $this->is_negative = false; + break; + case MATH_BIGINTEGER_MODE_BCMATH: + $x = ( strlen($x) & 1 ) ? '0' . $x : $x; + $temp = new Math_BigInteger(pack('H*', $x), 256); + $this->value = $this->is_negative ? '-' . $temp->value : $temp->value; + $this->is_negative = false; + break; + default: + $x = ( strlen($x) & 1 ) ? '0' . $x : $x; + $temp = new Math_BigInteger(pack('H*', $x), 256); + $this->value = $temp->value; + } + + if ($is_negative) { + $temp = $this->add(new Math_BigInteger('-1')); + $this->value = $temp->value; + } + break; + case 10: + case -10: + $x = preg_replace('#^(-?[0-9]*).*#', '$1', $x); + + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + $this->value = gmp_init($x); + break; + case MATH_BIGINTEGER_MODE_BCMATH: + // explicitly casting $x to a string is necessary, here, since doing $x[0] on -1 yields different + // results then doing it on '-1' does (modInverse does $x[0]) + $this->value = (string) $x; + break; + default: + $temp = new Math_BigInteger(); + + // array(10000000) is 10**7 in base-2**26. 10**7 is the closest to 2**26 we can get without passing it. + $multiplier = new Math_BigInteger(); + $multiplier->value = array(10000000); + + if ($x[0] == '-') { + $this->is_negative = true; + $x = substr($x, 1); + } + + $x = str_pad($x, strlen($x) + (6 * strlen($x)) % 7, 0, STR_PAD_LEFT); + + while (strlen($x)) { + $temp = $temp->multiply($multiplier); + $temp = $temp->add(new Math_BigInteger($this->_int2bytes(substr($x, 0, 7)), 256)); + $x = substr($x, 7); + } + + $this->value = $temp->value; + } + break; + case 2: // base-2 support originally implemented by Lluis Pamies - thanks! + case -2: + if ($base > 0 && $x[0] == '-') { + $this->is_negative = true; + $x = substr($x, 1); + } + + $x = preg_replace('#^([01]*).*#', '$1', $x); + $x = str_pad($x, strlen($x) + (3 * strlen($x)) % 4, 0, STR_PAD_LEFT); + + $str = '0x'; + while (strlen($x)) { + $part = substr($x, 0, 4); + $str.= dechex(bindec($part)); + $x = substr($x, 4); + } + + if ($this->is_negative) { + $str = '-' . $str; + } + + $temp = new Math_BigInteger($str, 8 * $base); // ie. either -16 or +16 + $this->value = $temp->value; + $this->is_negative = $temp->is_negative; + + break; + default: + // base not supported, so we'll let $this == 0 + } + } + + /** + * Converts a BigInteger to a byte string (eg. base-256). + * + * Negative numbers are saved as positive numbers, unless $twos_compliment is set to true, at which point, they're + * saved as two's compliment. + * + * Here's an example: + * + * toBytes(); // outputs chr(65) + * ?> + * + * + * @param Boolean $twos_compliment + * @return String + * @access public + * @internal Converts a base-2**26 number to base-2**8 + */ + function toBytes($twos_compliment = false) + { + if ($twos_compliment) { + $comparison = $this->compare(new Math_BigInteger()); + if ($comparison == 0) { + return $this->precision > 0 ? str_repeat(chr(0), ($this->precision + 1) >> 3) : ''; + } + + $temp = $comparison < 0 ? $this->add(new Math_BigInteger(1)) : $this->copy(); + $bytes = $temp->toBytes(); + + if (empty($bytes)) { // eg. if the number we're trying to convert is -1 + $bytes = chr(0); + } + + if (ord($bytes[0]) & 0x80) { + $bytes = chr(0) . $bytes; + } + + return $comparison < 0 ? ~$bytes : $bytes; + } + + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + if (gmp_cmp($this->value, gmp_init(0)) == 0) { + return $this->precision > 0 ? str_repeat(chr(0), ($this->precision + 1) >> 3) : ''; + } + + $temp = gmp_strval(gmp_abs($this->value), 16); + $temp = ( strlen($temp) & 1 ) ? '0' . $temp : $temp; + $temp = pack('H*', $temp); + + return $this->precision > 0 ? + substr(str_pad($temp, $this->precision >> 3, chr(0), STR_PAD_LEFT), -($this->precision >> 3)) : + ltrim($temp, chr(0)); + case MATH_BIGINTEGER_MODE_BCMATH: + if ($this->value === '0') { + return $this->precision > 0 ? str_repeat(chr(0), ($this->precision + 1) >> 3) : ''; + } + + $value = ''; + $current = $this->value; + + if ($current[0] == '-') { + $current = substr($current, 1); + } + + while (bccomp($current, '0', 0) > 0) { + $temp = bcmod($current, '16777216'); + $value = chr($temp >> 16) . chr($temp >> 8) . chr($temp) . $value; + $current = bcdiv($current, '16777216', 0); + } + + return $this->precision > 0 ? + substr(str_pad($value, $this->precision >> 3, chr(0), STR_PAD_LEFT), -($this->precision >> 3)) : + ltrim($value, chr(0)); + } + + if (!count($this->value)) { + return $this->precision > 0 ? str_repeat(chr(0), ($this->precision + 1) >> 3) : ''; + } + $result = $this->_int2bytes($this->value[count($this->value) - 1]); + + $temp = $this->copy(); + + for ($i = count($temp->value) - 2; $i >= 0; --$i) { + $temp->_base256_lshift($result, 26); + $result = $result | str_pad($temp->_int2bytes($temp->value[$i]), strlen($result), chr(0), STR_PAD_LEFT); + } + + return $this->precision > 0 ? + str_pad(substr($result, -(($this->precision + 7) >> 3)), ($this->precision + 7) >> 3, chr(0), STR_PAD_LEFT) : + $result; + } + + /** + * Converts a BigInteger to a hex string (eg. base-16)). + * + * Negative numbers are saved as positive numbers, unless $twos_compliment is set to true, at which point, they're + * saved as two's compliment. + * + * Here's an example: + * + * toHex(); // outputs '41' + * ?> + * + * + * @param Boolean $twos_compliment + * @return String + * @access public + * @internal Converts a base-2**26 number to base-2**8 + */ + function toHex($twos_compliment = false) + { + return bin2hex($this->toBytes($twos_compliment)); + } + + /** + * Converts a BigInteger to a bit string (eg. base-2). + * + * Negative numbers are saved as positive numbers, unless $twos_compliment is set to true, at which point, they're + * saved as two's compliment. + * + * Here's an example: + * + * toBits(); // outputs '1000001' + * ?> + * + * + * @param Boolean $twos_compliment + * @return String + * @access public + * @internal Converts a base-2**26 number to base-2**2 + */ + function toBits($twos_compliment = false) + { + $hex = $this->toHex($twos_compliment); + $bits = ''; + for ($i = strlen($hex) - 8, $start = strlen($hex) & 7; $i >= $start; $i-=8) { + $bits = str_pad(decbin(hexdec(substr($hex, $i, 8))), 32, '0', STR_PAD_LEFT) . $bits; + } + if ($start) { // hexdec('') == 0 + $bits = str_pad(decbin(hexdec(substr($hex, 0, $start))), 8, '0', STR_PAD_LEFT) . $bits; + } + $result = $this->precision > 0 ? substr($bits, -$this->precision) : ltrim($bits, '0'); + + if ($twos_compliment && $this->compare(new Math_BigInteger()) > 0 && $this->precision <= 0) { + return '0' . $result; + } + + return $result; + } + + /** + * Converts a BigInteger to a base-10 number. + * + * Here's an example: + * + * toString(); // outputs 50 + * ?> + * + * + * @return String + * @access public + * @internal Converts a base-2**26 number to base-10**7 (which is pretty much base-10) + */ + function toString() + { + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + return gmp_strval($this->value); + case MATH_BIGINTEGER_MODE_BCMATH: + if ($this->value === '0') { + return '0'; + } + + return ltrim($this->value, '0'); + } + + if (!count($this->value)) { + return '0'; + } + + $temp = $this->copy(); + $temp->is_negative = false; + + $divisor = new Math_BigInteger(); + $divisor->value = array(10000000); // eg. 10**7 + $result = ''; + while (count($temp->value)) { + list($temp, $mod) = $temp->divide($divisor); + $result = str_pad(isset($mod->value[0]) ? $mod->value[0] : '', 7, '0', STR_PAD_LEFT) . $result; + } + $result = ltrim($result, '0'); + if (empty($result)) { + $result = '0'; + } + + if ($this->is_negative) { + $result = '-' . $result; + } + + return $result; + } + + /** + * Copy an object + * + * PHP5 passes objects by reference while PHP4 passes by value. As such, we need a function to guarantee + * that all objects are passed by value, when appropriate. More information can be found here: + * + * {@link http://php.net/language.oop5.basic#51624} + * + * @access public + * @see __clone() + * @return Math_BigInteger + */ + function copy() + { + $temp = new Math_BigInteger(); + $temp->value = $this->value; + $temp->is_negative = $this->is_negative; + $temp->generator = $this->generator; + $temp->precision = $this->precision; + $temp->bitmask = $this->bitmask; + return $temp; + } + + /** + * __toString() magic method + * + * Will be called, automatically, if you're supporting just PHP5. If you're supporting PHP4, you'll need to call + * toString(). + * + * @access public + * @internal Implemented per a suggestion by Techie-Michael - thanks! + */ + function __toString() + { + return $this->toString(); + } + + /** + * __clone() magic method + * + * Although you can call Math_BigInteger::__toString() directly in PHP5, you cannot call Math_BigInteger::__clone() + * directly in PHP5. You can in PHP4 since it's not a magic method, but in PHP5, you have to call it by using the PHP5 + * only syntax of $y = clone $x. As such, if you're trying to write an application that works on both PHP4 and PHP5, + * call Math_BigInteger::copy(), instead. + * + * @access public + * @see copy() + * @return Math_BigInteger + */ + function __clone() + { + return $this->copy(); + } + + /** + * __sleep() magic method + * + * Will be called, automatically, when serialize() is called on a Math_BigInteger object. + * + * @see __wakeup() + * @access public + */ + function __sleep() + { + $this->hex = $this->toHex(true); + $vars = array('hex'); + if ($this->generator != 'mt_rand') { + $vars[] = 'generator'; + } + if ($this->precision > 0) { + $vars[] = 'precision'; + } + return $vars; + + } + + /** + * __wakeup() magic method + * + * Will be called, automatically, when unserialize() is called on a Math_BigInteger object. + * + * @see __sleep() + * @access public + */ + function __wakeup() + { + $temp = new Math_BigInteger($this->hex, -16); + $this->value = $temp->value; + $this->is_negative = $temp->is_negative; + $this->setRandomGenerator($this->generator); + if ($this->precision > 0) { + // recalculate $this->bitmask + $this->setPrecision($this->precision); + } + } + + /** + * Adds two BigIntegers. + * + * Here's an example: + * + * add($b); + * + * echo $c->toString(); // outputs 30 + * ?> + * + * + * @param Math_BigInteger $y + * @return Math_BigInteger + * @access public + * @internal Performs base-2**52 addition + */ + function add($y) + { + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + $temp = new Math_BigInteger(); + $temp->value = gmp_add($this->value, $y->value); + + return $this->_normalize($temp); + case MATH_BIGINTEGER_MODE_BCMATH: + $temp = new Math_BigInteger(); + $temp->value = bcadd($this->value, $y->value, 0); + + return $this->_normalize($temp); + } + + $temp = $this->_add($this->value, $this->is_negative, $y->value, $y->is_negative); + + $result = new Math_BigInteger(); + $result->value = $temp[MATH_BIGINTEGER_VALUE]; + $result->is_negative = $temp[MATH_BIGINTEGER_SIGN]; + + return $this->_normalize($result); + } + + /** + * Performs addition. + * + * @param Array $x_value + * @param Boolean $x_negative + * @param Array $y_value + * @param Boolean $y_negative + * @return Array + * @access private + */ + function _add($x_value, $x_negative, $y_value, $y_negative) + { + $x_size = count($x_value); + $y_size = count($y_value); + + if ($x_size == 0) { + return array( + MATH_BIGINTEGER_VALUE => $y_value, + MATH_BIGINTEGER_SIGN => $y_negative + ); + } else if ($y_size == 0) { + return array( + MATH_BIGINTEGER_VALUE => $x_value, + MATH_BIGINTEGER_SIGN => $x_negative + ); + } + + // subtract, if appropriate + if ( $x_negative != $y_negative ) { + if ( $x_value == $y_value ) { + return array( + MATH_BIGINTEGER_VALUE => array(), + MATH_BIGINTEGER_SIGN => false + ); + } + + $temp = $this->_subtract($x_value, false, $y_value, false); + $temp[MATH_BIGINTEGER_SIGN] = $this->_compare($x_value, false, $y_value, false) > 0 ? + $x_negative : $y_negative; + + return $temp; + } + + if ($x_size < $y_size) { + $size = $x_size; + $value = $y_value; + } else { + $size = $y_size; + $value = $x_value; + } + + $value[] = 0; // just in case the carry adds an extra digit + + $carry = 0; + for ($i = 0, $j = 1; $j < $size; $i+=2, $j+=2) { + $sum = $x_value[$j] * 0x4000000 + $x_value[$i] + $y_value[$j] * 0x4000000 + $y_value[$i] + $carry; + $carry = $sum >= MATH_BIGINTEGER_MAX_DIGIT52; // eg. floor($sum / 2**52); only possible values (in any base) are 0 and 1 + $sum = $carry ? $sum - MATH_BIGINTEGER_MAX_DIGIT52 : $sum; + + $temp = (int) ($sum / 0x4000000); + + $value[$i] = (int) ($sum - 0x4000000 * $temp); // eg. a faster alternative to fmod($sum, 0x4000000) + $value[$j] = $temp; + } + + if ($j == $size) { // ie. if $y_size is odd + $sum = $x_value[$i] + $y_value[$i] + $carry; + $carry = $sum >= 0x4000000; + $value[$i] = $carry ? $sum - 0x4000000 : $sum; + ++$i; // ie. let $i = $j since we've just done $value[$i] + } + + if ($carry) { + for (; $value[$i] == 0x3FFFFFF; ++$i) { + $value[$i] = 0; + } + ++$value[$i]; + } + + return array( + MATH_BIGINTEGER_VALUE => $this->_trim($value), + MATH_BIGINTEGER_SIGN => $x_negative + ); + } + + /** + * Subtracts two BigIntegers. + * + * Here's an example: + * + * subtract($b); + * + * echo $c->toString(); // outputs -10 + * ?> + * + * + * @param Math_BigInteger $y + * @return Math_BigInteger + * @access public + * @internal Performs base-2**52 subtraction + */ + function subtract($y) + { + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + $temp = new Math_BigInteger(); + $temp->value = gmp_sub($this->value, $y->value); + + return $this->_normalize($temp); + case MATH_BIGINTEGER_MODE_BCMATH: + $temp = new Math_BigInteger(); + $temp->value = bcsub($this->value, $y->value, 0); + + return $this->_normalize($temp); + } + + $temp = $this->_subtract($this->value, $this->is_negative, $y->value, $y->is_negative); + + $result = new Math_BigInteger(); + $result->value = $temp[MATH_BIGINTEGER_VALUE]; + $result->is_negative = $temp[MATH_BIGINTEGER_SIGN]; + + return $this->_normalize($result); + } + + /** + * Performs subtraction. + * + * @param Array $x_value + * @param Boolean $x_negative + * @param Array $y_value + * @param Boolean $y_negative + * @return Array + * @access private + */ + function _subtract($x_value, $x_negative, $y_value, $y_negative) + { + $x_size = count($x_value); + $y_size = count($y_value); + + if ($x_size == 0) { + return array( + MATH_BIGINTEGER_VALUE => $y_value, + MATH_BIGINTEGER_SIGN => !$y_negative + ); + } else if ($y_size == 0) { + return array( + MATH_BIGINTEGER_VALUE => $x_value, + MATH_BIGINTEGER_SIGN => $x_negative + ); + } + + // add, if appropriate (ie. -$x - +$y or +$x - -$y) + if ( $x_negative != $y_negative ) { + $temp = $this->_add($x_value, false, $y_value, false); + $temp[MATH_BIGINTEGER_SIGN] = $x_negative; + + return $temp; + } + + $diff = $this->_compare($x_value, $x_negative, $y_value, $y_negative); + + if ( !$diff ) { + return array( + MATH_BIGINTEGER_VALUE => array(), + MATH_BIGINTEGER_SIGN => false + ); + } + + // switch $x and $y around, if appropriate. + if ( (!$x_negative && $diff < 0) || ($x_negative && $diff > 0) ) { + $temp = $x_value; + $x_value = $y_value; + $y_value = $temp; + + $x_negative = !$x_negative; + + $x_size = count($x_value); + $y_size = count($y_value); + } + + // at this point, $x_value should be at least as big as - if not bigger than - $y_value + + $carry = 0; + for ($i = 0, $j = 1; $j < $y_size; $i+=2, $j+=2) { + $sum = $x_value[$j] * 0x4000000 + $x_value[$i] - $y_value[$j] * 0x4000000 - $y_value[$i] - $carry; + $carry = $sum < 0; // eg. floor($sum / 2**52); only possible values (in any base) are 0 and 1 + $sum = $carry ? $sum + MATH_BIGINTEGER_MAX_DIGIT52 : $sum; + + $temp = (int) ($sum / 0x4000000); + + $x_value[$i] = (int) ($sum - 0x4000000 * $temp); + $x_value[$j] = $temp; + } + + if ($j == $y_size) { // ie. if $y_size is odd + $sum = $x_value[$i] - $y_value[$i] - $carry; + $carry = $sum < 0; + $x_value[$i] = $carry ? $sum + 0x4000000 : $sum; + ++$i; + } + + if ($carry) { + for (; !$x_value[$i]; ++$i) { + $x_value[$i] = 0x3FFFFFF; + } + --$x_value[$i]; + } + + return array( + MATH_BIGINTEGER_VALUE => $this->_trim($x_value), + MATH_BIGINTEGER_SIGN => $x_negative + ); + } + + /** + * Multiplies two BigIntegers + * + * Here's an example: + * + * multiply($b); + * + * echo $c->toString(); // outputs 200 + * ?> + * + * + * @param Math_BigInteger $x + * @return Math_BigInteger + * @access public + */ + function multiply($x) + { + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + $temp = new Math_BigInteger(); + $temp->value = gmp_mul($this->value, $x->value); + + return $this->_normalize($temp); + case MATH_BIGINTEGER_MODE_BCMATH: + $temp = new Math_BigInteger(); + $temp->value = bcmul($this->value, $x->value, 0); + + return $this->_normalize($temp); + } + + $temp = $this->_multiply($this->value, $this->is_negative, $x->value, $x->is_negative); + + $product = new Math_BigInteger(); + $product->value = $temp[MATH_BIGINTEGER_VALUE]; + $product->is_negative = $temp[MATH_BIGINTEGER_SIGN]; + + return $this->_normalize($product); + } + + /** + * Performs multiplication. + * + * @param Array $x_value + * @param Boolean $x_negative + * @param Array $y_value + * @param Boolean $y_negative + * @return Array + * @access private + */ + function _multiply($x_value, $x_negative, $y_value, $y_negative) + { + //if ( $x_value == $y_value ) { + // return array( + // MATH_BIGINTEGER_VALUE => $this->_square($x_value), + // MATH_BIGINTEGER_SIGN => $x_sign != $y_value + // ); + //} + + $x_length = count($x_value); + $y_length = count($y_value); + + if ( !$x_length || !$y_length ) { // a 0 is being multiplied + return array( + MATH_BIGINTEGER_VALUE => array(), + MATH_BIGINTEGER_SIGN => false + ); + } + + return array( + MATH_BIGINTEGER_VALUE => min($x_length, $y_length) < 2 * MATH_BIGINTEGER_KARATSUBA_CUTOFF ? + $this->_trim($this->_regularMultiply($x_value, $y_value)) : + $this->_trim($this->_karatsuba($x_value, $y_value)), + MATH_BIGINTEGER_SIGN => $x_negative != $y_negative + ); + } + + /** + * Performs long multiplication on two BigIntegers + * + * Modeled after 'multiply' in MutableBigInteger.java. + * + * @param Array $x_value + * @param Array $y_value + * @return Array + * @access private + */ + function _regularMultiply($x_value, $y_value) + { + $x_length = count($x_value); + $y_length = count($y_value); + + if ( !$x_length || !$y_length ) { // a 0 is being multiplied + return array(); + } + + if ( $x_length < $y_length ) { + $temp = $x_value; + $x_value = $y_value; + $y_value = $temp; + + $x_length = count($x_value); + $y_length = count($y_value); + } + + $product_value = $this->_array_repeat(0, $x_length + $y_length); + + // the following for loop could be removed if the for loop following it + // (the one with nested for loops) initially set $i to 0, but + // doing so would also make the result in one set of unnecessary adds, + // since on the outermost loops first pass, $product->value[$k] is going + // to always be 0 + + $carry = 0; + + for ($j = 0; $j < $x_length; ++$j) { // ie. $i = 0 + $temp = $x_value[$j] * $y_value[0] + $carry; // $product_value[$k] == 0 + $carry = (int) ($temp / 0x4000000); + $product_value[$j] = (int) ($temp - 0x4000000 * $carry); + } + + $product_value[$j] = $carry; + + // the above for loop is what the previous comment was talking about. the + // following for loop is the "one with nested for loops" + for ($i = 1; $i < $y_length; ++$i) { + $carry = 0; + + for ($j = 0, $k = $i; $j < $x_length; ++$j, ++$k) { + $temp = $product_value[$k] + $x_value[$j] * $y_value[$i] + $carry; + $carry = (int) ($temp / 0x4000000); + $product_value[$k] = (int) ($temp - 0x4000000 * $carry); + } + + $product_value[$k] = $carry; + } + + return $product_value; + } + + /** + * Performs Karatsuba multiplication on two BigIntegers + * + * See {@link http://en.wikipedia.org/wiki/Karatsuba_algorithm Karatsuba algorithm} and + * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=120 MPM 5.2.3}. + * + * @param Array $x_value + * @param Array $y_value + * @return Array + * @access private + */ + function _karatsuba($x_value, $y_value) + { + $m = min(count($x_value) >> 1, count($y_value) >> 1); + + if ($m < MATH_BIGINTEGER_KARATSUBA_CUTOFF) { + return $this->_regularMultiply($x_value, $y_value); + } + + $x1 = array_slice($x_value, $m); + $x0 = array_slice($x_value, 0, $m); + $y1 = array_slice($y_value, $m); + $y0 = array_slice($y_value, 0, $m); + + $z2 = $this->_karatsuba($x1, $y1); + $z0 = $this->_karatsuba($x0, $y0); + + $z1 = $this->_add($x1, false, $x0, false); + $temp = $this->_add($y1, false, $y0, false); + $z1 = $this->_karatsuba($z1[MATH_BIGINTEGER_VALUE], $temp[MATH_BIGINTEGER_VALUE]); + $temp = $this->_add($z2, false, $z0, false); + $z1 = $this->_subtract($z1, false, $temp[MATH_BIGINTEGER_VALUE], false); + + $z2 = array_merge(array_fill(0, 2 * $m, 0), $z2); + $z1[MATH_BIGINTEGER_VALUE] = array_merge(array_fill(0, $m, 0), $z1[MATH_BIGINTEGER_VALUE]); + + $xy = $this->_add($z2, false, $z1[MATH_BIGINTEGER_VALUE], $z1[MATH_BIGINTEGER_SIGN]); + $xy = $this->_add($xy[MATH_BIGINTEGER_VALUE], $xy[MATH_BIGINTEGER_SIGN], $z0, false); + + return $xy[MATH_BIGINTEGER_VALUE]; + } + + /** + * Performs squaring + * + * @param Array $x + * @return Array + * @access private + */ + function _square($x = false) + { + return count($x) < 2 * MATH_BIGINTEGER_KARATSUBA_CUTOFF ? + $this->_trim($this->_baseSquare($x)) : + $this->_trim($this->_karatsubaSquare($x)); + } + + /** + * Performs traditional squaring on two BigIntegers + * + * Squaring can be done faster than multiplying a number by itself can be. See + * {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=7 HAC 14.2.4} / + * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=141 MPM 5.3} for more information. + * + * @param Array $value + * @return Array + * @access private + */ + function _baseSquare($value) + { + if ( empty($value) ) { + return array(); + } + $square_value = $this->_array_repeat(0, 2 * count($value)); + + for ($i = 0, $max_index = count($value) - 1; $i <= $max_index; ++$i) { + $i2 = $i << 1; + + $temp = $square_value[$i2] + $value[$i] * $value[$i]; + $carry = (int) ($temp / 0x4000000); + $square_value[$i2] = (int) ($temp - 0x4000000 * $carry); + + // note how we start from $i+1 instead of 0 as we do in multiplication. + for ($j = $i + 1, $k = $i2 + 1; $j <= $max_index; ++$j, ++$k) { + $temp = $square_value[$k] + 2 * $value[$j] * $value[$i] + $carry; + $carry = (int) ($temp / 0x4000000); + $square_value[$k] = (int) ($temp - 0x4000000 * $carry); + } + + // the following line can yield values larger 2**15. at this point, PHP should switch + // over to floats. + $square_value[$i + $max_index + 1] = $carry; + } + + return $square_value; + } + + /** + * Performs Karatsuba "squaring" on two BigIntegers + * + * See {@link http://en.wikipedia.org/wiki/Karatsuba_algorithm Karatsuba algorithm} and + * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=151 MPM 5.3.4}. + * + * @param Array $value + * @return Array + * @access private + */ + function _karatsubaSquare($value) + { + $m = count($value) >> 1; + + if ($m < MATH_BIGINTEGER_KARATSUBA_CUTOFF) { + return $this->_baseSquare($value); + } + + $x1 = array_slice($value, $m); + $x0 = array_slice($value, 0, $m); + + $z2 = $this->_karatsubaSquare($x1); + $z0 = $this->_karatsubaSquare($x0); + + $z1 = $this->_add($x1, false, $x0, false); + $z1 = $this->_karatsubaSquare($z1[MATH_BIGINTEGER_VALUE]); + $temp = $this->_add($z2, false, $z0, false); + $z1 = $this->_subtract($z1, false, $temp[MATH_BIGINTEGER_VALUE], false); + + $z2 = array_merge(array_fill(0, 2 * $m, 0), $z2); + $z1[MATH_BIGINTEGER_VALUE] = array_merge(array_fill(0, $m, 0), $z1[MATH_BIGINTEGER_VALUE]); + + $xx = $this->_add($z2, false, $z1[MATH_BIGINTEGER_VALUE], $z1[MATH_BIGINTEGER_SIGN]); + $xx = $this->_add($xx[MATH_BIGINTEGER_VALUE], $xx[MATH_BIGINTEGER_SIGN], $z0, false); + + return $xx[MATH_BIGINTEGER_VALUE]; + } + + /** + * Divides two BigIntegers. + * + * Returns an array whose first element contains the quotient and whose second element contains the + * "common residue". If the remainder would be positive, the "common residue" and the remainder are the + * same. If the remainder would be negative, the "common residue" is equal to the sum of the remainder + * and the divisor (basically, the "common residue" is the first positive modulo). + * + * Here's an example: + * + * divide($b); + * + * echo $quotient->toString(); // outputs 0 + * echo "\r\n"; + * echo $remainder->toString(); // outputs 10 + * ?> + * + * + * @param Math_BigInteger $y + * @return Array + * @access public + * @internal This function is based off of {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=9 HAC 14.20}. + */ + function divide($y) + { + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + $quotient = new Math_BigInteger(); + $remainder = new Math_BigInteger(); + + list($quotient->value, $remainder->value) = gmp_div_qr($this->value, $y->value); + + if (gmp_sign($remainder->value) < 0) { + $remainder->value = gmp_add($remainder->value, gmp_abs($y->value)); + } + + return array($this->_normalize($quotient), $this->_normalize($remainder)); + case MATH_BIGINTEGER_MODE_BCMATH: + $quotient = new Math_BigInteger(); + $remainder = new Math_BigInteger(); + + $quotient->value = bcdiv($this->value, $y->value, 0); + $remainder->value = bcmod($this->value, $y->value); + + if ($remainder->value[0] == '-') { + $remainder->value = bcadd($remainder->value, $y->value[0] == '-' ? substr($y->value, 1) : $y->value, 0); + } + + return array($this->_normalize($quotient), $this->_normalize($remainder)); + } + + if (count($y->value) == 1) { + list($q, $r) = $this->_divide_digit($this->value, $y->value[0]); + $quotient = new Math_BigInteger(); + $remainder = new Math_BigInteger(); + $quotient->value = $q; + $remainder->value = array($r); + $quotient->is_negative = $this->is_negative != $y->is_negative; + return array($this->_normalize($quotient), $this->_normalize($remainder)); + } + + static $zero; + if ( !isset($zero) ) { + $zero = new Math_BigInteger(); + } + + $x = $this->copy(); + $y = $y->copy(); + + $x_sign = $x->is_negative; + $y_sign = $y->is_negative; + + $x->is_negative = $y->is_negative = false; + + $diff = $x->compare($y); + + if ( !$diff ) { + $temp = new Math_BigInteger(); + $temp->value = array(1); + $temp->is_negative = $x_sign != $y_sign; + return array($this->_normalize($temp), $this->_normalize(new Math_BigInteger())); + } + + if ( $diff < 0 ) { + // if $x is negative, "add" $y. + if ( $x_sign ) { + $x = $y->subtract($x); + } + return array($this->_normalize(new Math_BigInteger()), $this->_normalize($x)); + } + + // normalize $x and $y as described in HAC 14.23 / 14.24 + $msb = $y->value[count($y->value) - 1]; + for ($shift = 0; !($msb & 0x2000000); ++$shift) { + $msb <<= 1; + } + $x->_lshift($shift); + $y->_lshift($shift); + $y_value = &$y->value; + + $x_max = count($x->value) - 1; + $y_max = count($y->value) - 1; + + $quotient = new Math_BigInteger(); + $quotient_value = &$quotient->value; + $quotient_value = $this->_array_repeat(0, $x_max - $y_max + 1); + + static $temp, $lhs, $rhs; + if (!isset($temp)) { + $temp = new Math_BigInteger(); + $lhs = new Math_BigInteger(); + $rhs = new Math_BigInteger(); + } + $temp_value = &$temp->value; + $rhs_value = &$rhs->value; + + // $temp = $y << ($x_max - $y_max-1) in base 2**26 + $temp_value = array_merge($this->_array_repeat(0, $x_max - $y_max), $y_value); + + while ( $x->compare($temp) >= 0 ) { + // calculate the "common residue" + ++$quotient_value[$x_max - $y_max]; + $x = $x->subtract($temp); + $x_max = count($x->value) - 1; + } + + for ($i = $x_max; $i >= $y_max + 1; --$i) { + $x_value = &$x->value; + $x_window = array( + isset($x_value[$i]) ? $x_value[$i] : 0, + isset($x_value[$i - 1]) ? $x_value[$i - 1] : 0, + isset($x_value[$i - 2]) ? $x_value[$i - 2] : 0 + ); + $y_window = array( + $y_value[$y_max], + ( $y_max > 0 ) ? $y_value[$y_max - 1] : 0 + ); + + $q_index = $i - $y_max - 1; + if ($x_window[0] == $y_window[0]) { + $quotient_value[$q_index] = 0x3FFFFFF; + } else { + $quotient_value[$q_index] = (int) ( + ($x_window[0] * 0x4000000 + $x_window[1]) + / + $y_window[0] + ); + } + + $temp_value = array($y_window[1], $y_window[0]); + + $lhs->value = array($quotient_value[$q_index]); + $lhs = $lhs->multiply($temp); + + $rhs_value = array($x_window[2], $x_window[1], $x_window[0]); + + while ( $lhs->compare($rhs) > 0 ) { + --$quotient_value[$q_index]; + + $lhs->value = array($quotient_value[$q_index]); + $lhs = $lhs->multiply($temp); + } + + $adjust = $this->_array_repeat(0, $q_index); + $temp_value = array($quotient_value[$q_index]); + $temp = $temp->multiply($y); + $temp_value = &$temp->value; + $temp_value = array_merge($adjust, $temp_value); + + $x = $x->subtract($temp); + + if ($x->compare($zero) < 0) { + $temp_value = array_merge($adjust, $y_value); + $x = $x->add($temp); + + --$quotient_value[$q_index]; + } + + $x_max = count($x_value) - 1; + } + + // unnormalize the remainder + $x->_rshift($shift); + + $quotient->is_negative = $x_sign != $y_sign; + + // calculate the "common residue", if appropriate + if ( $x_sign ) { + $y->_rshift($shift); + $x = $y->subtract($x); + } + + return array($this->_normalize($quotient), $this->_normalize($x)); + } + + /** + * Divides a BigInteger by a regular integer + * + * abc / x = a00 / x + b0 / x + c / x + * + * @param Array $dividend + * @param Array $divisor + * @return Array + * @access private + */ + function _divide_digit($dividend, $divisor) + { + $carry = 0; + $result = array(); + + for ($i = count($dividend) - 1; $i >= 0; --$i) { + $temp = 0x4000000 * $carry + $dividend[$i]; + $result[$i] = (int) ($temp / $divisor); + $carry = (int) ($temp - $divisor * $result[$i]); + } + + return array($result, $carry); + } + + /** + * Performs modular exponentiation. + * + * Here's an example: + * + * modPow($b, $c); + * + * echo $c->toString(); // outputs 10 + * ?> + * + * + * @param Math_BigInteger $e + * @param Math_BigInteger $n + * @return Math_BigInteger + * @access public + * @internal The most naive approach to modular exponentiation has very unreasonable requirements, and + * and although the approach involving repeated squaring does vastly better, it, too, is impractical + * for our purposes. The reason being that division - by far the most complicated and time-consuming + * of the basic operations (eg. +,-,*,/) - occurs multiple times within it. + * + * Modular reductions resolve this issue. Although an individual modular reduction takes more time + * then an individual division, when performed in succession (with the same modulo), they're a lot faster. + * + * The two most commonly used modular reductions are Barrett and Montgomery reduction. Montgomery reduction, + * although faster, only works when the gcd of the modulo and of the base being used is 1. In RSA, when the + * base is a power of two, the modulo - a product of two primes - is always going to have a gcd of 1 (because + * the product of two odd numbers is odd), but what about when RSA isn't used? + * + * In contrast, Barrett reduction has no such constraint. As such, some bigint implementations perform a + * Barrett reduction after every operation in the modpow function. Others perform Barrett reductions when the + * modulo is even and Montgomery reductions when the modulo is odd. BigInteger.java's modPow method, however, + * uses a trick involving the Chinese Remainder Theorem to factor the even modulo into two numbers - one odd and + * the other, a power of two - and recombine them, later. This is the method that this modPow function uses. + * {@link http://islab.oregonstate.edu/papers/j34monex.pdf Montgomery Reduction with Even Modulus} elaborates. + */ + function modPow($e, $n) + { + $n = $this->bitmask !== false && $this->bitmask->compare($n) < 0 ? $this->bitmask : $n->abs(); + + if ($e->compare(new Math_BigInteger()) < 0) { + $e = $e->abs(); + + $temp = $this->modInverse($n); + if ($temp === false) { + return false; + } + + return $this->_normalize($temp->modPow($e, $n)); + } + + if (MATH_BIGINTEGER_MODE == MATH_BIGINTEGER_MODE_GMP) { + $temp = new Math_BigInteger(); + $temp->value = gmp_powm($this->value, $e->value, $n->value); + + return $this->_normalize($temp); + } + + if ($this->compare(new Math_BigInteger()) < 0 || $this->compare($n) > 0) { + list(, $temp) = $this->divide($n); + return $temp->modPow($e, $n); + } + + if (defined('MATH_BIGINTEGER_OPENSSL_ENABLED')) { + $components = array( + 'modulus' => $n->toBytes(true), + 'publicExponent' => $e->toBytes(true) + ); + + $components = array( + 'modulus' => pack('Ca*a*', 2, $this->_encodeASN1Length(strlen($components['modulus'])), $components['modulus']), + 'publicExponent' => pack('Ca*a*', 2, $this->_encodeASN1Length(strlen($components['publicExponent'])), $components['publicExponent']) + ); + + $RSAPublicKey = pack('Ca*a*a*', + 48, $this->_encodeASN1Length(strlen($components['modulus']) + strlen($components['publicExponent'])), + $components['modulus'], $components['publicExponent'] + ); + + $rsaOID = pack('H*', '300d06092a864886f70d0101010500'); // hex version of MA0GCSqGSIb3DQEBAQUA + $RSAPublicKey = chr(0) . $RSAPublicKey; + $RSAPublicKey = chr(3) . $this->_encodeASN1Length(strlen($RSAPublicKey)) . $RSAPublicKey; + + $encapsulated = pack('Ca*a*', + 48, $this->_encodeASN1Length(strlen($rsaOID . $RSAPublicKey)), $rsaOID . $RSAPublicKey + ); + + $RSAPublicKey = "-----BEGIN PUBLIC KEY-----\r\n" . + chunk_split(base64_encode($encapsulated)) . + '-----END PUBLIC KEY-----'; + + $plaintext = str_pad($this->toBytes(), strlen($n->toBytes(true)) - 1, "\0", STR_PAD_LEFT); + + if (openssl_public_encrypt($plaintext, $result, $RSAPublicKey, OPENSSL_NO_PADDING)) { + return new Math_BigInteger($result, 256); + } + } + + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + $temp = new Math_BigInteger(); + $temp->value = gmp_powm($this->value, $e->value, $n->value); + + return $this->_normalize($temp); + case MATH_BIGINTEGER_MODE_BCMATH: + $temp = new Math_BigInteger(); + $temp->value = bcpowmod($this->value, $e->value, $n->value, 0); + + return $this->_normalize($temp); + } + + if ( empty($e->value) ) { + $temp = new Math_BigInteger(); + $temp->value = array(1); + return $this->_normalize($temp); + } + + if ( $e->value == array(1) ) { + list(, $temp) = $this->divide($n); + return $this->_normalize($temp); + } + + if ( $e->value == array(2) ) { + $temp = new Math_BigInteger(); + $temp->value = $this->_square($this->value); + list(, $temp) = $temp->divide($n); + return $this->_normalize($temp); + } + + return $this->_normalize($this->_slidingWindow($e, $n, MATH_BIGINTEGER_BARRETT)); + + // is the modulo odd? + if ( $n->value[0] & 1 ) { + return $this->_normalize($this->_slidingWindow($e, $n, MATH_BIGINTEGER_MONTGOMERY)); + } + // if it's not, it's even + + // find the lowest set bit (eg. the max pow of 2 that divides $n) + for ($i = 0; $i < count($n->value); ++$i) { + if ( $n->value[$i] ) { + $temp = decbin($n->value[$i]); + $j = strlen($temp) - strrpos($temp, '1') - 1; + $j+= 26 * $i; + break; + } + } + // at this point, 2^$j * $n/(2^$j) == $n + + $mod1 = $n->copy(); + $mod1->_rshift($j); + $mod2 = new Math_BigInteger(); + $mod2->value = array(1); + $mod2->_lshift($j); + + $part1 = ( $mod1->value != array(1) ) ? $this->_slidingWindow($e, $mod1, MATH_BIGINTEGER_MONTGOMERY) : new Math_BigInteger(); + $part2 = $this->_slidingWindow($e, $mod2, MATH_BIGINTEGER_POWEROF2); + + $y1 = $mod2->modInverse($mod1); + $y2 = $mod1->modInverse($mod2); + + $result = $part1->multiply($mod2); + $result = $result->multiply($y1); + + $temp = $part2->multiply($mod1); + $temp = $temp->multiply($y2); + + $result = $result->add($temp); + list(, $result) = $result->divide($n); + + return $this->_normalize($result); + } + + /** + * Performs modular exponentiation. + * + * Alias for Math_BigInteger::modPow() + * + * @param Math_BigInteger $e + * @param Math_BigInteger $n + * @return Math_BigInteger + * @access public + */ + function powMod($e, $n) + { + return $this->modPow($e, $n); + } + + /** + * Sliding Window k-ary Modular Exponentiation + * + * Based on {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=27 HAC 14.85} / + * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=210 MPM 7.7}. In a departure from those algorithims, + * however, this function performs a modular reduction after every multiplication and squaring operation. + * As such, this function has the same preconditions that the reductions being used do. + * + * @param Math_BigInteger $e + * @param Math_BigInteger $n + * @param Integer $mode + * @return Math_BigInteger + * @access private + */ + function _slidingWindow($e, $n, $mode) + { + static $window_ranges = array(7, 25, 81, 241, 673, 1793); // from BigInteger.java's oddModPow function + //static $window_ranges = array(0, 7, 36, 140, 450, 1303, 3529); // from MPM 7.3.1 + + $e_value = $e->value; + $e_length = count($e_value) - 1; + $e_bits = decbin($e_value[$e_length]); + for ($i = $e_length - 1; $i >= 0; --$i) { + $e_bits.= str_pad(decbin($e_value[$i]), 26, '0', STR_PAD_LEFT); + } + + $e_length = strlen($e_bits); + + // calculate the appropriate window size. + // $window_size == 3 if $window_ranges is between 25 and 81, for example. + for ($i = 0, $window_size = 1; $e_length > $window_ranges[$i] && $i < count($window_ranges); ++$window_size, ++$i); + + $n_value = $n->value; + + // precompute $this^0 through $this^$window_size + $powers = array(); + $powers[1] = $this->_prepareReduce($this->value, $n_value, $mode); + $powers[2] = $this->_squareReduce($powers[1], $n_value, $mode); + + // we do every other number since substr($e_bits, $i, $j+1) (see below) is supposed to end + // in a 1. ie. it's supposed to be odd. + $temp = 1 << ($window_size - 1); + for ($i = 1; $i < $temp; ++$i) { + $i2 = $i << 1; + $powers[$i2 + 1] = $this->_multiplyReduce($powers[$i2 - 1], $powers[2], $n_value, $mode); + } + + $result = array(1); + $result = $this->_prepareReduce($result, $n_value, $mode); + + for ($i = 0; $i < $e_length; ) { + if ( !$e_bits[$i] ) { + $result = $this->_squareReduce($result, $n_value, $mode); + ++$i; + } else { + for ($j = $window_size - 1; $j > 0; --$j) { + if ( !empty($e_bits[$i + $j]) ) { + break; + } + } + + for ($k = 0; $k <= $j; ++$k) {// eg. the length of substr($e_bits, $i, $j+1) + $result = $this->_squareReduce($result, $n_value, $mode); + } + + $result = $this->_multiplyReduce($result, $powers[bindec(substr($e_bits, $i, $j + 1))], $n_value, $mode); + + $i+=$j + 1; + } + } + + $temp = new Math_BigInteger(); + $temp->value = $this->_reduce($result, $n_value, $mode); + + return $temp; + } + + /** + * Modular reduction + * + * For most $modes this will return the remainder. + * + * @see _slidingWindow() + * @access private + * @param Array $x + * @param Array $n + * @param Integer $mode + * @return Array + */ + function _reduce($x, $n, $mode) + { + switch ($mode) { + case MATH_BIGINTEGER_MONTGOMERY: + return $this->_montgomery($x, $n); + case MATH_BIGINTEGER_BARRETT: + return $this->_barrett($x, $n); + case MATH_BIGINTEGER_POWEROF2: + $lhs = new Math_BigInteger(); + $lhs->value = $x; + $rhs = new Math_BigInteger(); + $rhs->value = $n; + return $x->_mod2($n); + case MATH_BIGINTEGER_CLASSIC: + $lhs = new Math_BigInteger(); + $lhs->value = $x; + $rhs = new Math_BigInteger(); + $rhs->value = $n; + list(, $temp) = $lhs->divide($rhs); + return $temp->value; + case MATH_BIGINTEGER_NONE: + return $x; + default: + // an invalid $mode was provided + } + } + + /** + * Modular reduction preperation + * + * @see _slidingWindow() + * @access private + * @param Array $x + * @param Array $n + * @param Integer $mode + * @return Array + */ + function _prepareReduce($x, $n, $mode) + { + if ($mode == MATH_BIGINTEGER_MONTGOMERY) { + return $this->_prepMontgomery($x, $n); + } + return $this->_reduce($x, $n, $mode); + } + + /** + * Modular multiply + * + * @see _slidingWindow() + * @access private + * @param Array $x + * @param Array $y + * @param Array $n + * @param Integer $mode + * @return Array + */ + function _multiplyReduce($x, $y, $n, $mode) + { + if ($mode == MATH_BIGINTEGER_MONTGOMERY) { + return $this->_montgomeryMultiply($x, $y, $n); + } + $temp = $this->_multiply($x, false, $y, false); + return $this->_reduce($temp[MATH_BIGINTEGER_VALUE], $n, $mode); + } + + /** + * Modular square + * + * @see _slidingWindow() + * @access private + * @param Array $x + * @param Array $n + * @param Integer $mode + * @return Array + */ + function _squareReduce($x, $n, $mode) + { + if ($mode == MATH_BIGINTEGER_MONTGOMERY) { + return $this->_montgomeryMultiply($x, $x, $n); + } + return $this->_reduce($this->_square($x), $n, $mode); + } + + /** + * Modulos for Powers of Two + * + * Calculates $x%$n, where $n = 2**$e, for some $e. Since this is basically the same as doing $x & ($n-1), + * we'll just use this function as a wrapper for doing that. + * + * @see _slidingWindow() + * @access private + * @param Math_BigInteger + * @return Math_BigInteger + */ + function _mod2($n) + { + $temp = new Math_BigInteger(); + $temp->value = array(1); + return $this->bitwise_and($n->subtract($temp)); + } + + /** + * Barrett Modular Reduction + * + * See {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=14 HAC 14.3.3} / + * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=165 MPM 6.2.5} for more information. Modified slightly, + * so as not to require negative numbers (initially, this script didn't support negative numbers). + * + * Employs "folding", as described at + * {@link http://www.cosic.esat.kuleuven.be/publications/thesis-149.pdf#page=66 thesis-149.pdf#page=66}. To quote from + * it, "the idea [behind folding] is to find a value x' such that x (mod m) = x' (mod m), with x' being smaller than x." + * + * Unfortunately, the "Barrett Reduction with Folding" algorithm described in thesis-149.pdf is not, as written, all that + * usable on account of (1) its not using reasonable radix points as discussed in + * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=162 MPM 6.2.2} and (2) the fact that, even with reasonable + * radix points, it only works when there are an even number of digits in the denominator. The reason for (2) is that + * (x >> 1) + (x >> 1) != x / 2 + x / 2. If x is even, they're the same, but if x is odd, they're not. See the in-line + * comments for details. + * + * @see _slidingWindow() + * @access private + * @param Array $n + * @param Array $m + * @return Array + */ + function _barrett($n, $m) + { + static $cache = array( + MATH_BIGINTEGER_VARIABLE => array(), + MATH_BIGINTEGER_DATA => array() + ); + + $m_length = count($m); + + // if ($this->_compare($n, $this->_square($m)) >= 0) { + if (count($n) > 2 * $m_length) { + $lhs = new Math_BigInteger(); + $rhs = new Math_BigInteger(); + $lhs->value = $n; + $rhs->value = $m; + list(, $temp) = $lhs->divide($rhs); + return $temp->value; + } + + // if (m.length >> 1) + 2 <= m.length then m is too small and n can't be reduced + if ($m_length < 5) { + return $this->_regularBarrett($n, $m); + } + + // n = 2 * m.length + + if ( ($key = array_search($m, $cache[MATH_BIGINTEGER_VARIABLE])) === false ) { + $key = count($cache[MATH_BIGINTEGER_VARIABLE]); + $cache[MATH_BIGINTEGER_VARIABLE][] = $m; + + $lhs = new Math_BigInteger(); + $lhs_value = &$lhs->value; + $lhs_value = $this->_array_repeat(0, $m_length + ($m_length >> 1)); + $lhs_value[] = 1; + $rhs = new Math_BigInteger(); + $rhs->value = $m; + + list($u, $m1) = $lhs->divide($rhs); + $u = $u->value; + $m1 = $m1->value; + + $cache[MATH_BIGINTEGER_DATA][] = array( + 'u' => $u, // m.length >> 1 (technically (m.length >> 1) + 1) + 'm1'=> $m1 // m.length + ); + } else { + extract($cache[MATH_BIGINTEGER_DATA][$key]); + } + + $cutoff = $m_length + ($m_length >> 1); + $lsd = array_slice($n, 0, $cutoff); // m.length + (m.length >> 1) + $msd = array_slice($n, $cutoff); // m.length >> 1 + $lsd = $this->_trim($lsd); + $temp = $this->_multiply($msd, false, $m1, false); + $n = $this->_add($lsd, false, $temp[MATH_BIGINTEGER_VALUE], false); // m.length + (m.length >> 1) + 1 + + if ($m_length & 1) { + return $this->_regularBarrett($n[MATH_BIGINTEGER_VALUE], $m); + } + + // (m.length + (m.length >> 1) + 1) - (m.length - 1) == (m.length >> 1) + 2 + $temp = array_slice($n[MATH_BIGINTEGER_VALUE], $m_length - 1); + // if even: ((m.length >> 1) + 2) + (m.length >> 1) == m.length + 2 + // if odd: ((m.length >> 1) + 2) + (m.length >> 1) == (m.length - 1) + 2 == m.length + 1 + $temp = $this->_multiply($temp, false, $u, false); + // if even: (m.length + 2) - ((m.length >> 1) + 1) = m.length - (m.length >> 1) + 1 + // if odd: (m.length + 1) - ((m.length >> 1) + 1) = m.length - (m.length >> 1) + $temp = array_slice($temp[MATH_BIGINTEGER_VALUE], ($m_length >> 1) + 1); + // if even: (m.length - (m.length >> 1) + 1) + m.length = 2 * m.length - (m.length >> 1) + 1 + // if odd: (m.length - (m.length >> 1)) + m.length = 2 * m.length - (m.length >> 1) + $temp = $this->_multiply($temp, false, $m, false); + + // at this point, if m had an odd number of digits, we'd be subtracting a 2 * m.length - (m.length >> 1) digit + // number from a m.length + (m.length >> 1) + 1 digit number. ie. there'd be an extra digit and the while loop + // following this comment would loop a lot (hence our calling _regularBarrett() in that situation). + + $result = $this->_subtract($n[MATH_BIGINTEGER_VALUE], false, $temp[MATH_BIGINTEGER_VALUE], false); + + while ($this->_compare($result[MATH_BIGINTEGER_VALUE], $result[MATH_BIGINTEGER_SIGN], $m, false) >= 0) { + $result = $this->_subtract($result[MATH_BIGINTEGER_VALUE], $result[MATH_BIGINTEGER_SIGN], $m, false); + } + + return $result[MATH_BIGINTEGER_VALUE]; + } + + /** + * (Regular) Barrett Modular Reduction + * + * For numbers with more than four digits Math_BigInteger::_barrett() is faster. The difference between that and this + * is that this function does not fold the denominator into a smaller form. + * + * @see _slidingWindow() + * @access private + * @param Array $x + * @param Array $n + * @return Array + */ + function _regularBarrett($x, $n) + { + static $cache = array( + MATH_BIGINTEGER_VARIABLE => array(), + MATH_BIGINTEGER_DATA => array() + ); + + $n_length = count($n); + + if (count($x) > 2 * $n_length) { + $lhs = new Math_BigInteger(); + $rhs = new Math_BigInteger(); + $lhs->value = $x; + $rhs->value = $n; + list(, $temp) = $lhs->divide($rhs); + return $temp->value; + } + + if ( ($key = array_search($n, $cache[MATH_BIGINTEGER_VARIABLE])) === false ) { + $key = count($cache[MATH_BIGINTEGER_VARIABLE]); + $cache[MATH_BIGINTEGER_VARIABLE][] = $n; + $lhs = new Math_BigInteger(); + $lhs_value = &$lhs->value; + $lhs_value = $this->_array_repeat(0, 2 * $n_length); + $lhs_value[] = 1; + $rhs = new Math_BigInteger(); + $rhs->value = $n; + list($temp, ) = $lhs->divide($rhs); // m.length + $cache[MATH_BIGINTEGER_DATA][] = $temp->value; + } + + // 2 * m.length - (m.length - 1) = m.length + 1 + $temp = array_slice($x, $n_length - 1); + // (m.length + 1) + m.length = 2 * m.length + 1 + $temp = $this->_multiply($temp, false, $cache[MATH_BIGINTEGER_DATA][$key], false); + // (2 * m.length + 1) - (m.length - 1) = m.length + 2 + $temp = array_slice($temp[MATH_BIGINTEGER_VALUE], $n_length + 1); + + // m.length + 1 + $result = array_slice($x, 0, $n_length + 1); + // m.length + 1 + $temp = $this->_multiplyLower($temp, false, $n, false, $n_length + 1); + // $temp == array_slice($temp->_multiply($temp, false, $n, false)->value, 0, $n_length + 1) + + if ($this->_compare($result, false, $temp[MATH_BIGINTEGER_VALUE], $temp[MATH_BIGINTEGER_SIGN]) < 0) { + $corrector_value = $this->_array_repeat(0, $n_length + 1); + $corrector_value[] = 1; + $result = $this->_add($result, false, $corrector, false); + $result = $result[MATH_BIGINTEGER_VALUE]; + } + + // at this point, we're subtracting a number with m.length + 1 digits from another number with m.length + 1 digits + $result = $this->_subtract($result, false, $temp[MATH_BIGINTEGER_VALUE], $temp[MATH_BIGINTEGER_SIGN]); + while ($this->_compare($result[MATH_BIGINTEGER_VALUE], $result[MATH_BIGINTEGER_SIGN], $n, false) > 0) { + $result = $this->_subtract($result[MATH_BIGINTEGER_VALUE], $result[MATH_BIGINTEGER_SIGN], $n, false); + } + + return $result[MATH_BIGINTEGER_VALUE]; + } + + /** + * Performs long multiplication up to $stop digits + * + * If you're going to be doing array_slice($product->value, 0, $stop), some cycles can be saved. + * + * @see _regularBarrett() + * @param Array $x_value + * @param Boolean $x_negative + * @param Array $y_value + * @param Boolean $y_negative + * @return Array + * @access private + */ + function _multiplyLower($x_value, $x_negative, $y_value, $y_negative, $stop) + { + $x_length = count($x_value); + $y_length = count($y_value); + + if ( !$x_length || !$y_length ) { // a 0 is being multiplied + return array( + MATH_BIGINTEGER_VALUE => array(), + MATH_BIGINTEGER_SIGN => false + ); + } + + if ( $x_length < $y_length ) { + $temp = $x_value; + $x_value = $y_value; + $y_value = $temp; + + $x_length = count($x_value); + $y_length = count($y_value); + } + + $product_value = $this->_array_repeat(0, $x_length + $y_length); + + // the following for loop could be removed if the for loop following it + // (the one with nested for loops) initially set $i to 0, but + // doing so would also make the result in one set of unnecessary adds, + // since on the outermost loops first pass, $product->value[$k] is going + // to always be 0 + + $carry = 0; + + for ($j = 0; $j < $x_length; ++$j) { // ie. $i = 0, $k = $i + $temp = $x_value[$j] * $y_value[0] + $carry; // $product_value[$k] == 0 + $carry = (int) ($temp / 0x4000000); + $product_value[$j] = (int) ($temp - 0x4000000 * $carry); + } + + if ($j < $stop) { + $product_value[$j] = $carry; + } + + // the above for loop is what the previous comment was talking about. the + // following for loop is the "one with nested for loops" + + for ($i = 1; $i < $y_length; ++$i) { + $carry = 0; + + for ($j = 0, $k = $i; $j < $x_length && $k < $stop; ++$j, ++$k) { + $temp = $product_value[$k] + $x_value[$j] * $y_value[$i] + $carry; + $carry = (int) ($temp / 0x4000000); + $product_value[$k] = (int) ($temp - 0x4000000 * $carry); + } + + if ($k < $stop) { + $product_value[$k] = $carry; + } + } + + return array( + MATH_BIGINTEGER_VALUE => $this->_trim($product_value), + MATH_BIGINTEGER_SIGN => $x_negative != $y_negative + ); + } + + /** + * Montgomery Modular Reduction + * + * ($x->_prepMontgomery($n))->_montgomery($n) yields $x % $n. + * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=170 MPM 6.3} provides insights on how this can be + * improved upon (basically, by using the comba method). gcd($n, 2) must be equal to one for this function + * to work correctly. + * + * @see _prepMontgomery() + * @see _slidingWindow() + * @access private + * @param Array $x + * @param Array $n + * @return Array + */ + function _montgomery($x, $n) + { + static $cache = array( + MATH_BIGINTEGER_VARIABLE => array(), + MATH_BIGINTEGER_DATA => array() + ); + + if ( ($key = array_search($n, $cache[MATH_BIGINTEGER_VARIABLE])) === false ) { + $key = count($cache[MATH_BIGINTEGER_VARIABLE]); + $cache[MATH_BIGINTEGER_VARIABLE][] = $x; + $cache[MATH_BIGINTEGER_DATA][] = $this->_modInverse67108864($n); + } + + $k = count($n); + + $result = array(MATH_BIGINTEGER_VALUE => $x); + + for ($i = 0; $i < $k; ++$i) { + $temp = $result[MATH_BIGINTEGER_VALUE][$i] * $cache[MATH_BIGINTEGER_DATA][$key]; + $temp = (int) ($temp - 0x4000000 * ((int) ($temp / 0x4000000))); + $temp = $this->_regularMultiply(array($temp), $n); + $temp = array_merge($this->_array_repeat(0, $i), $temp); + $result = $this->_add($result[MATH_BIGINTEGER_VALUE], false, $temp, false); + } + + $result[MATH_BIGINTEGER_VALUE] = array_slice($result[MATH_BIGINTEGER_VALUE], $k); + + if ($this->_compare($result, false, $n, false) >= 0) { + $result = $this->_subtract($result[MATH_BIGINTEGER_VALUE], false, $n, false); + } + + return $result[MATH_BIGINTEGER_VALUE]; + } + + /** + * Montgomery Multiply + * + * Interleaves the montgomery reduction and long multiplication algorithms together as described in + * {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=13 HAC 14.36} + * + * @see _prepMontgomery() + * @see _montgomery() + * @access private + * @param Array $x + * @param Array $y + * @param Array $m + * @return Array + */ + function _montgomeryMultiply($x, $y, $m) + { + $temp = $this->_multiply($x, false, $y, false); + return $this->_montgomery($temp[MATH_BIGINTEGER_VALUE], $m); + + static $cache = array( + MATH_BIGINTEGER_VARIABLE => array(), + MATH_BIGINTEGER_DATA => array() + ); + + if ( ($key = array_search($m, $cache[MATH_BIGINTEGER_VARIABLE])) === false ) { + $key = count($cache[MATH_BIGINTEGER_VARIABLE]); + $cache[MATH_BIGINTEGER_VARIABLE][] = $m; + $cache[MATH_BIGINTEGER_DATA][] = $this->_modInverse67108864($m); + } + + $n = max(count($x), count($y), count($m)); + $x = array_pad($x, $n, 0); + $y = array_pad($y, $n, 0); + $m = array_pad($m, $n, 0); + $a = array(MATH_BIGINTEGER_VALUE => $this->_array_repeat(0, $n + 1)); + for ($i = 0; $i < $n; ++$i) { + $temp = $a[MATH_BIGINTEGER_VALUE][0] + $x[$i] * $y[0]; + $temp = (int) ($temp - 0x4000000 * ((int) ($temp / 0x4000000))); + $temp = $temp * $cache[MATH_BIGINTEGER_DATA][$key]; + $temp = (int) ($temp - 0x4000000 * ((int) ($temp / 0x4000000))); + $temp = $this->_add($this->_regularMultiply(array($x[$i]), $y), false, $this->_regularMultiply(array($temp), $m), false); + $a = $this->_add($a[MATH_BIGINTEGER_VALUE], false, $temp[MATH_BIGINTEGER_VALUE], false); + $a[MATH_BIGINTEGER_VALUE] = array_slice($a[MATH_BIGINTEGER_VALUE], 1); + } + if ($this->_compare($a[MATH_BIGINTEGER_VALUE], false, $m, false) >= 0) { + $a = $this->_subtract($a[MATH_BIGINTEGER_VALUE], false, $m, false); + } + return $a[MATH_BIGINTEGER_VALUE]; + } + + /** + * Prepare a number for use in Montgomery Modular Reductions + * + * @see _montgomery() + * @see _slidingWindow() + * @access private + * @param Array $x + * @param Array $n + * @return Array + */ + function _prepMontgomery($x, $n) + { + $lhs = new Math_BigInteger(); + $lhs->value = array_merge($this->_array_repeat(0, count($n)), $x); + $rhs = new Math_BigInteger(); + $rhs->value = $n; + + list(, $temp) = $lhs->divide($rhs); + return $temp->value; + } + + /** + * Modular Inverse of a number mod 2**26 (eg. 67108864) + * + * Based off of the bnpInvDigit function implemented and justified in the following URL: + * + * {@link http://www-cs-students.stanford.edu/~tjw/jsbn/jsbn.js} + * + * The following URL provides more info: + * + * {@link http://groups.google.com/group/sci.crypt/msg/7a137205c1be7d85} + * + * As for why we do all the bitmasking... strange things can happen when converting from floats to ints. For + * instance, on some computers, var_dump((int) -4294967297) yields int(-1) and on others, it yields + * int(-2147483648). To avoid problems stemming from this, we use bitmasks to guarantee that ints aren't + * auto-converted to floats. The outermost bitmask is present because without it, there's no guarantee that + * the "residue" returned would be the so-called "common residue". We use fmod, in the last step, because the + * maximum possible $x is 26 bits and the maximum $result is 16 bits. Thus, we have to be able to handle up to + * 40 bits, which only 64-bit floating points will support. + * + * Thanks to Pedro Gimeno Fortea for input! + * + * @see _montgomery() + * @access private + * @param Array $x + * @return Integer + */ + function _modInverse67108864($x) // 2**26 == 67108864 + { + $x = -$x[0]; + $result = $x & 0x3; // x**-1 mod 2**2 + $result = ($result * (2 - $x * $result)) & 0xF; // x**-1 mod 2**4 + $result = ($result * (2 - ($x & 0xFF) * $result)) & 0xFF; // x**-1 mod 2**8 + $result = ($result * ((2 - ($x & 0xFFFF) * $result) & 0xFFFF)) & 0xFFFF; // x**-1 mod 2**16 + $result = fmod($result * (2 - fmod($x * $result, 0x4000000)), 0x4000000); // x**-1 mod 2**26 + return $result & 0x3FFFFFF; + } + + /** + * Calculates modular inverses. + * + * Say you have (30 mod 17 * x mod 17) mod 17 == 1. x can be found using modular inverses. + * + * Here's an example: + * + * modInverse($b); + * echo $c->toString(); // outputs 4 + * + * echo "\r\n"; + * + * $d = $a->multiply($c); + * list(, $d) = $d->divide($b); + * echo $d; // outputs 1 (as per the definition of modular inverse) + * ?> + * + * + * @param Math_BigInteger $n + * @return mixed false, if no modular inverse exists, Math_BigInteger, otherwise. + * @access public + * @internal See {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=21 HAC 14.64} for more information. + */ + function modInverse($n) + { + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + $temp = new Math_BigInteger(); + $temp->value = gmp_invert($this->value, $n->value); + + return ( $temp->value === false ) ? false : $this->_normalize($temp); + } + + static $zero, $one; + if (!isset($zero)) { + $zero = new Math_BigInteger(); + $one = new Math_BigInteger(1); + } + + // $x mod -$n == $x mod $n. + $n = $n->abs(); + + if ($this->compare($zero) < 0) { + $temp = $this->abs(); + $temp = $temp->modInverse($n); + return $this->_normalize($n->subtract($temp)); + } + + extract($this->extendedGCD($n)); + + if (!$gcd->equals($one)) { + return false; + } + + $x = $x->compare($zero) < 0 ? $x->add($n) : $x; + + return $this->compare($zero) < 0 ? $this->_normalize($n->subtract($x)) : $this->_normalize($x); + } + + /** + * Calculates the greatest common divisor and Bézout's identity. + * + * Say you have 693 and 609. The GCD is 21. Bézout's identity states that there exist integers x and y such that + * 693*x + 609*y == 21. In point of fact, there are actually an infinite number of x and y combinations and which + * combination is returned is dependant upon which mode is in use. See + * {@link http://en.wikipedia.org/wiki/B%C3%A9zout%27s_identity Bézout's identity - Wikipedia} for more information. + * + * Here's an example: + * + * extendedGCD($b)); + * + * echo $gcd->toString() . "\r\n"; // outputs 21 + * echo $a->toString() * $x->toString() + $b->toString() * $y->toString(); // outputs 21 + * ?> + * + * + * @param Math_BigInteger $n + * @return Math_BigInteger + * @access public + * @internal Calculates the GCD using the binary xGCD algorithim described in + * {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=19 HAC 14.61}. As the text above 14.61 notes, + * the more traditional algorithim requires "relatively costly multiple-precision divisions". + */ + function extendedGCD($n) + { + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + extract(gmp_gcdext($this->value, $n->value)); + + return array( + 'gcd' => $this->_normalize(new Math_BigInteger($g)), + 'x' => $this->_normalize(new Math_BigInteger($s)), + 'y' => $this->_normalize(new Math_BigInteger($t)) + ); + case MATH_BIGINTEGER_MODE_BCMATH: + // it might be faster to use the binary xGCD algorithim here, as well, but (1) that algorithim works + // best when the base is a power of 2 and (2) i don't think it'd make much difference, anyway. as is, + // the basic extended euclidean algorithim is what we're using. + + $u = $this->value; + $v = $n->value; + + $a = '1'; + $b = '0'; + $c = '0'; + $d = '1'; + + while (bccomp($v, '0', 0) != 0) { + $q = bcdiv($u, $v, 0); + + $temp = $u; + $u = $v; + $v = bcsub($temp, bcmul($v, $q, 0), 0); + + $temp = $a; + $a = $c; + $c = bcsub($temp, bcmul($a, $q, 0), 0); + + $temp = $b; + $b = $d; + $d = bcsub($temp, bcmul($b, $q, 0), 0); + } + + return array( + 'gcd' => $this->_normalize(new Math_BigInteger($u)), + 'x' => $this->_normalize(new Math_BigInteger($a)), + 'y' => $this->_normalize(new Math_BigInteger($b)) + ); + } + + $y = $n->copy(); + $x = $this->copy(); + $g = new Math_BigInteger(); + $g->value = array(1); + + while ( !(($x->value[0] & 1)|| ($y->value[0] & 1)) ) { + $x->_rshift(1); + $y->_rshift(1); + $g->_lshift(1); + } + + $u = $x->copy(); + $v = $y->copy(); + + $a = new Math_BigInteger(); + $b = new Math_BigInteger(); + $c = new Math_BigInteger(); + $d = new Math_BigInteger(); + + $a->value = $d->value = $g->value = array(1); + $b->value = $c->value = array(); + + while ( !empty($u->value) ) { + while ( !($u->value[0] & 1) ) { + $u->_rshift(1); + if ( (!empty($a->value) && ($a->value[0] & 1)) || (!empty($b->value) && ($b->value[0] & 1)) ) { + $a = $a->add($y); + $b = $b->subtract($x); + } + $a->_rshift(1); + $b->_rshift(1); + } + + while ( !($v->value[0] & 1) ) { + $v->_rshift(1); + if ( (!empty($d->value) && ($d->value[0] & 1)) || (!empty($c->value) && ($c->value[0] & 1)) ) { + $c = $c->add($y); + $d = $d->subtract($x); + } + $c->_rshift(1); + $d->_rshift(1); + } + + if ($u->compare($v) >= 0) { + $u = $u->subtract($v); + $a = $a->subtract($c); + $b = $b->subtract($d); + } else { + $v = $v->subtract($u); + $c = $c->subtract($a); + $d = $d->subtract($b); + } + } + + return array( + 'gcd' => $this->_normalize($g->multiply($v)), + 'x' => $this->_normalize($c), + 'y' => $this->_normalize($d) + ); + } + + /** + * Calculates the greatest common divisor + * + * Say you have 693 and 609. The GCD is 21. + * + * Here's an example: + * + * extendedGCD($b); + * + * echo $gcd->toString() . "\r\n"; // outputs 21 + * ?> + * + * + * @param Math_BigInteger $n + * @return Math_BigInteger + * @access public + */ + function gcd($n) + { + extract($this->extendedGCD($n)); + return $gcd; + } + + /** + * Absolute value. + * + * @return Math_BigInteger + * @access public + */ + function abs() + { + $temp = new Math_BigInteger(); + + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + $temp->value = gmp_abs($this->value); + break; + case MATH_BIGINTEGER_MODE_BCMATH: + $temp->value = (bccomp($this->value, '0', 0) < 0) ? substr($this->value, 1) : $this->value; + break; + default: + $temp->value = $this->value; + } + + return $temp; + } + + /** + * Compares two numbers. + * + * Although one might think !$x->compare($y) means $x != $y, it, in fact, means the opposite. The reason for this is + * demonstrated thusly: + * + * $x > $y: $x->compare($y) > 0 + * $x < $y: $x->compare($y) < 0 + * $x == $y: $x->compare($y) == 0 + * + * Note how the same comparison operator is used. If you want to test for equality, use $x->equals($y). + * + * @param Math_BigInteger $x + * @return Integer < 0 if $this is less than $x; > 0 if $this is greater than $x, and 0 if they are equal. + * @access public + * @see equals() + * @internal Could return $this->subtract($x), but that's not as fast as what we do do. + */ + function compare($y) + { + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + return gmp_cmp($this->value, $y->value); + case MATH_BIGINTEGER_MODE_BCMATH: + return bccomp($this->value, $y->value, 0); + } + + return $this->_compare($this->value, $this->is_negative, $y->value, $y->is_negative); + } + + /** + * Compares two numbers. + * + * @param Array $x_value + * @param Boolean $x_negative + * @param Array $y_value + * @param Boolean $y_negative + * @return Integer + * @see compare() + * @access private + */ + function _compare($x_value, $x_negative, $y_value, $y_negative) + { + if ( $x_negative != $y_negative ) { + return ( !$x_negative && $y_negative ) ? 1 : -1; + } + + $result = $x_negative ? -1 : 1; + + if ( count($x_value) != count($y_value) ) { + return ( count($x_value) > count($y_value) ) ? $result : -$result; + } + $size = max(count($x_value), count($y_value)); + + $x_value = array_pad($x_value, $size, 0); + $y_value = array_pad($y_value, $size, 0); + + for ($i = count($x_value) - 1; $i >= 0; --$i) { + if ($x_value[$i] != $y_value[$i]) { + return ( $x_value[$i] > $y_value[$i] ) ? $result : -$result; + } + } + + return 0; + } + + /** + * Tests the equality of two numbers. + * + * If you need to see if one number is greater than or less than another number, use Math_BigInteger::compare() + * + * @param Math_BigInteger $x + * @return Boolean + * @access public + * @see compare() + */ + function equals($x) + { + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + return gmp_cmp($this->value, $x->value) == 0; + default: + return $this->value === $x->value && $this->is_negative == $x->is_negative; + } + } + + /** + * Set Precision + * + * Some bitwise operations give different results depending on the precision being used. Examples include left + * shift, not, and rotates. + * + * @param Math_BigInteger $x + * @access public + * @return Math_BigInteger + */ + function setPrecision($bits) + { + $this->precision = $bits; + if ( MATH_BIGINTEGER_MODE != MATH_BIGINTEGER_MODE_BCMATH ) { + $this->bitmask = new Math_BigInteger(chr((1 << ($bits & 0x7)) - 1) . str_repeat(chr(0xFF), $bits >> 3), 256); + } else { + $this->bitmask = new Math_BigInteger(bcpow('2', $bits, 0)); + } + + $temp = $this->_normalize($this); + $this->value = $temp->value; + } + + /** + * Logical And + * + * @param Math_BigInteger $x + * @access public + * @internal Implemented per a request by Lluis Pamies i Juarez + * @return Math_BigInteger + */ + function bitwise_and($x) + { + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + $temp = new Math_BigInteger(); + $temp->value = gmp_and($this->value, $x->value); + + return $this->_normalize($temp); + case MATH_BIGINTEGER_MODE_BCMATH: + $left = $this->toBytes(); + $right = $x->toBytes(); + + $length = max(strlen($left), strlen($right)); + + $left = str_pad($left, $length, chr(0), STR_PAD_LEFT); + $right = str_pad($right, $length, chr(0), STR_PAD_LEFT); + + return $this->_normalize(new Math_BigInteger($left & $right, 256)); + } + + $result = $this->copy(); + + $length = min(count($x->value), count($this->value)); + + $result->value = array_slice($result->value, 0, $length); + + for ($i = 0; $i < $length; ++$i) { + $result->value[$i] = $result->value[$i] & $x->value[$i]; + } + + return $this->_normalize($result); + } + + /** + * Logical Or + * + * @param Math_BigInteger $x + * @access public + * @internal Implemented per a request by Lluis Pamies i Juarez + * @return Math_BigInteger + */ + function bitwise_or($x) + { + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + $temp = new Math_BigInteger(); + $temp->value = gmp_or($this->value, $x->value); + + return $this->_normalize($temp); + case MATH_BIGINTEGER_MODE_BCMATH: + $left = $this->toBytes(); + $right = $x->toBytes(); + + $length = max(strlen($left), strlen($right)); + + $left = str_pad($left, $length, chr(0), STR_PAD_LEFT); + $right = str_pad($right, $length, chr(0), STR_PAD_LEFT); + + return $this->_normalize(new Math_BigInteger($left | $right, 256)); + } + + $length = max(count($this->value), count($x->value)); + $result = $this->copy(); + $result->value = array_pad($result->value, 0, $length); + $x->value = array_pad($x->value, 0, $length); + + for ($i = 0; $i < $length; ++$i) { + $result->value[$i] = $this->value[$i] | $x->value[$i]; + } + + return $this->_normalize($result); + } + + /** + * Logical Exclusive-Or + * + * @param Math_BigInteger $x + * @access public + * @internal Implemented per a request by Lluis Pamies i Juarez + * @return Math_BigInteger + */ + function bitwise_xor($x) + { + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + $temp = new Math_BigInteger(); + $temp->value = gmp_xor($this->value, $x->value); + + return $this->_normalize($temp); + case MATH_BIGINTEGER_MODE_BCMATH: + $left = $this->toBytes(); + $right = $x->toBytes(); + + $length = max(strlen($left), strlen($right)); + + $left = str_pad($left, $length, chr(0), STR_PAD_LEFT); + $right = str_pad($right, $length, chr(0), STR_PAD_LEFT); + + return $this->_normalize(new Math_BigInteger($left ^ $right, 256)); + } + + $length = max(count($this->value), count($x->value)); + $result = $this->copy(); + $result->value = array_pad($result->value, 0, $length); + $x->value = array_pad($x->value, 0, $length); + + for ($i = 0; $i < $length; ++$i) { + $result->value[$i] = $this->value[$i] ^ $x->value[$i]; + } + + return $this->_normalize($result); + } + + /** + * Logical Not + * + * @access public + * @internal Implemented per a request by Lluis Pamies i Juarez + * @return Math_BigInteger + */ + function bitwise_not() + { + // calculuate "not" without regard to $this->precision + // (will always result in a smaller number. ie. ~1 isn't 1111 1110 - it's 0) + $temp = $this->toBytes(); + $pre_msb = decbin(ord($temp[0])); + $temp = ~$temp; + $msb = decbin(ord($temp[0])); + if (strlen($msb) == 8) { + $msb = substr($msb, strpos($msb, '0')); + } + $temp[0] = chr(bindec($msb)); + + // see if we need to add extra leading 1's + $current_bits = strlen($pre_msb) + 8 * strlen($temp) - 8; + $new_bits = $this->precision - $current_bits; + if ($new_bits <= 0) { + return $this->_normalize(new Math_BigInteger($temp, 256)); + } + + // generate as many leading 1's as we need to. + $leading_ones = chr((1 << ($new_bits & 0x7)) - 1) . str_repeat(chr(0xFF), $new_bits >> 3); + $this->_base256_lshift($leading_ones, $current_bits); + + $temp = str_pad($temp, ceil($this->bits / 8), chr(0), STR_PAD_LEFT); + + return $this->_normalize(new Math_BigInteger($leading_ones | $temp, 256)); + } + + /** + * Logical Right Shift + * + * Shifts BigInteger's by $shift bits, effectively dividing by 2**$shift. + * + * @param Integer $shift + * @return Math_BigInteger + * @access public + * @internal The only version that yields any speed increases is the internal version. + */ + function bitwise_rightShift($shift) + { + $temp = new Math_BigInteger(); + + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + static $two; + + if (!isset($two)) { + $two = gmp_init('2'); + } + + $temp->value = gmp_div_q($this->value, gmp_pow($two, $shift)); + + break; + case MATH_BIGINTEGER_MODE_BCMATH: + $temp->value = bcdiv($this->value, bcpow('2', $shift, 0), 0); + + break; + default: // could just replace _lshift with this, but then all _lshift() calls would need to be rewritten + // and I don't want to do that... + $temp->value = $this->value; + $temp->_rshift($shift); + } + + return $this->_normalize($temp); + } + + /** + * Logical Left Shift + * + * Shifts BigInteger's by $shift bits, effectively multiplying by 2**$shift. + * + * @param Integer $shift + * @return Math_BigInteger + * @access public + * @internal The only version that yields any speed increases is the internal version. + */ + function bitwise_leftShift($shift) + { + $temp = new Math_BigInteger(); + + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + static $two; + + if (!isset($two)) { + $two = gmp_init('2'); + } + + $temp->value = gmp_mul($this->value, gmp_pow($two, $shift)); + + break; + case MATH_BIGINTEGER_MODE_BCMATH: + $temp->value = bcmul($this->value, bcpow('2', $shift, 0), 0); + + break; + default: // could just replace _rshift with this, but then all _lshift() calls would need to be rewritten + // and I don't want to do that... + $temp->value = $this->value; + $temp->_lshift($shift); + } + + return $this->_normalize($temp); + } + + /** + * Logical Left Rotate + * + * Instead of the top x bits being dropped they're appended to the shifted bit string. + * + * @param Integer $shift + * @return Math_BigInteger + * @access public + */ + function bitwise_leftRotate($shift) + { + $bits = $this->toBytes(); + + if ($this->precision > 0) { + $precision = $this->precision; + if ( MATH_BIGINTEGER_MODE == MATH_BIGINTEGER_MODE_BCMATH ) { + $mask = $this->bitmask->subtract(new Math_BigInteger(1)); + $mask = $mask->toBytes(); + } else { + $mask = $this->bitmask->toBytes(); + } + } else { + $temp = ord($bits[0]); + for ($i = 0; $temp >> $i; ++$i); + $precision = 8 * strlen($bits) - 8 + $i; + $mask = chr((1 << ($precision & 0x7)) - 1) . str_repeat(chr(0xFF), $precision >> 3); + } + + if ($shift < 0) { + $shift+= $precision; + } + $shift%= $precision; + + if (!$shift) { + return $this->copy(); + } + + $left = $this->bitwise_leftShift($shift); + $left = $left->bitwise_and(new Math_BigInteger($mask, 256)); + $right = $this->bitwise_rightShift($precision - $shift); + $result = MATH_BIGINTEGER_MODE != MATH_BIGINTEGER_MODE_BCMATH ? $left->bitwise_or($right) : $left->add($right); + return $this->_normalize($result); + } + + /** + * Logical Right Rotate + * + * Instead of the bottom x bits being dropped they're prepended to the shifted bit string. + * + * @param Integer $shift + * @return Math_BigInteger + * @access public + */ + function bitwise_rightRotate($shift) + { + return $this->bitwise_leftRotate(-$shift); + } + + /** + * Set random number generator function + * + * $generator should be the name of a random generating function whose first parameter is the minimum + * value and whose second parameter is the maximum value. If this function needs to be seeded, it should + * be seeded prior to calling Math_BigInteger::random() or Math_BigInteger::randomPrime() + * + * If the random generating function is not explicitly set, it'll be assumed to be mt_rand(). + * + * @see random() + * @see randomPrime() + * @param optional String $generator + * @access public + */ + function setRandomGenerator($generator) + { + $this->generator = $generator; + } + + /** + * Generate a random number + * + * @param optional Integer $min + * @param optional Integer $max + * @return Math_BigInteger + * @access public + */ + function random($min = false, $max = false) + { + if ($min === false) { + $min = new Math_BigInteger(0); + } + + if ($max === false) { + $max = new Math_BigInteger(0x7FFFFFFF); + } + + $compare = $max->compare($min); + + if (!$compare) { + return $this->_normalize($min); + } else if ($compare < 0) { + // if $min is bigger then $max, swap $min and $max + $temp = $max; + $max = $min; + $min = $temp; + } + + $generator = $this->generator; + + $max = $max->subtract($min); + $max = ltrim($max->toBytes(), chr(0)); + $size = strlen($max) - 1; + $random = ''; + + $bytes = $size & 1; + for ($i = 0; $i < $bytes; ++$i) { + $random.= chr($generator(0, 255)); + } + + $blocks = $size >> 1; + for ($i = 0; $i < $blocks; ++$i) { + // mt_rand(-2147483648, 0x7FFFFFFF) always produces -2147483648 on some systems + $random.= pack('n', $generator(0, 0xFFFF)); + } + + $temp = new Math_BigInteger($random, 256); + if ($temp->compare(new Math_BigInteger(substr($max, 1), 256)) > 0) { + $random = chr($generator(0, ord($max[0]) - 1)) . $random; + } else { + $random = chr($generator(0, ord($max[0]) )) . $random; + } + + $random = new Math_BigInteger($random, 256); + + return $this->_normalize($random->add($min)); + } + + /** + * Generate a random prime number. + * + * If there's not a prime within the given range, false will be returned. If more than $timeout seconds have elapsed, + * give up and return false. + * + * @param optional Integer $min + * @param optional Integer $max + * @param optional Integer $timeout + * @return Math_BigInteger + * @access public + * @internal See {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap4.pdf#page=15 HAC 4.44}. + */ + function randomPrime($min = false, $max = false, $timeout = false) + { + $compare = $max->compare($min); + + if (!$compare) { + return $min; + } else if ($compare < 0) { + // if $min is bigger then $max, swap $min and $max + $temp = $max; + $max = $min; + $min = $temp; + } + + // gmp_nextprime() requires PHP 5 >= 5.2.0 per . + if ( MATH_BIGINTEGER_MODE == MATH_BIGINTEGER_MODE_GMP && function_exists('gmp_nextprime') ) { + // we don't rely on Math_BigInteger::random()'s min / max when gmp_nextprime() is being used since this function + // does its own checks on $max / $min when gmp_nextprime() is used. When gmp_nextprime() is not used, however, + // the same $max / $min checks are not performed. + if ($min === false) { + $min = new Math_BigInteger(0); + } + + if ($max === false) { + $max = new Math_BigInteger(0x7FFFFFFF); + } + + $x = $this->random($min, $max); + + $x->value = gmp_nextprime($x->value); + + if ($x->compare($max) <= 0) { + return $x; + } + + $x->value = gmp_nextprime($min->value); + + if ($x->compare($max) <= 0) { + return $x; + } + + return false; + } + + static $one, $two; + if (!isset($one)) { + $one = new Math_BigInteger(1); + $two = new Math_BigInteger(2); + } + + $start = time(); + + $x = $this->random($min, $max); + if ($x->equals($two)) { + return $x; + } + + $x->_make_odd(); + if ($x->compare($max) > 0) { + // if $x > $max then $max is even and if $min == $max then no prime number exists between the specified range + if ($min->equals($max)) { + return false; + } + $x = $min->copy(); + $x->_make_odd(); + } + + $initial_x = $x->copy(); + + while (true) { + if ($timeout !== false && time() - $start > $timeout) { + return false; + } + + if ($x->isPrime()) { + return $x; + } + + $x = $x->add($two); + + if ($x->compare($max) > 0) { + $x = $min->copy(); + if ($x->equals($two)) { + return $x; + } + $x->_make_odd(); + } + + if ($x->equals($initial_x)) { + return false; + } + } + } + + /** + * Make the current number odd + * + * If the current number is odd it'll be unchanged. If it's even, one will be added to it. + * + * @see randomPrime() + * @access private + */ + function _make_odd() + { + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + gmp_setbit($this->value, 0); + break; + case MATH_BIGINTEGER_MODE_BCMATH: + if ($this->value[strlen($this->value) - 1] % 2 == 0) { + $this->value = bcadd($this->value, '1'); + } + break; + default: + $this->value[0] |= 1; + } + } + + /** + * Checks a numer to see if it's prime + * + * Assuming the $t parameter is not set, this function has an error rate of 2**-80. The main motivation for the + * $t parameter is distributability. Math_BigInteger::randomPrime() can be distributed accross multiple pageloads + * on a website instead of just one. + * + * @param optional Integer $t + * @return Boolean + * @access public + * @internal Uses the + * {@link http://en.wikipedia.org/wiki/Miller%E2%80%93Rabin_primality_test Miller-Rabin primality test}. See + * {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap4.pdf#page=8 HAC 4.24}. + */ + function isPrime($t = false) + { + $length = strlen($this->toBytes()); + + if (!$t) { + // see HAC 4.49 "Note (controlling the error probability)" + if ($length >= 163) { $t = 2; } // floor(1300 / 8) + else if ($length >= 106) { $t = 3; } // floor( 850 / 8) + else if ($length >= 81 ) { $t = 4; } // floor( 650 / 8) + else if ($length >= 68 ) { $t = 5; } // floor( 550 / 8) + else if ($length >= 56 ) { $t = 6; } // floor( 450 / 8) + else if ($length >= 50 ) { $t = 7; } // floor( 400 / 8) + else if ($length >= 43 ) { $t = 8; } // floor( 350 / 8) + else if ($length >= 37 ) { $t = 9; } // floor( 300 / 8) + else if ($length >= 31 ) { $t = 12; } // floor( 250 / 8) + else if ($length >= 25 ) { $t = 15; } // floor( 200 / 8) + else if ($length >= 18 ) { $t = 18; } // floor( 150 / 8) + else { $t = 27; } + } + + // ie. gmp_testbit($this, 0) + // ie. isEven() or !isOdd() + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + return gmp_prob_prime($this->value, $t) != 0; + case MATH_BIGINTEGER_MODE_BCMATH: + if ($this->value === '2') { + return true; + } + if ($this->value[strlen($this->value) - 1] % 2 == 0) { + return false; + } + break; + default: + if ($this->value == array(2)) { + return true; + } + if (~$this->value[0] & 1) { + return false; + } + } + + static $primes, $zero, $one, $two; + + if (!isset($primes)) { + $primes = array( + 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, + 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, + 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, + 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, + 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, + 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, + 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, + 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727, + 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, + 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, + 953, 967, 971, 977, 983, 991, 997 + ); + + if ( MATH_BIGINTEGER_MODE != MATH_BIGINTEGER_MODE_INTERNAL ) { + for ($i = 0; $i < count($primes); ++$i) { + $primes[$i] = new Math_BigInteger($primes[$i]); + } + } + + $zero = new Math_BigInteger(); + $one = new Math_BigInteger(1); + $two = new Math_BigInteger(2); + } + + if ($this->equals($one)) { + return false; + } + + // see HAC 4.4.1 "Random search for probable primes" + if ( MATH_BIGINTEGER_MODE != MATH_BIGINTEGER_MODE_INTERNAL ) { + foreach ($primes as $prime) { + list(, $r) = $this->divide($prime); + if ($r->equals($zero)) { + return $this->equals($prime); + } + } + } else { + $value = $this->value; + foreach ($primes as $prime) { + list(, $r) = $this->_divide_digit($value, $prime); + if (!$r) { + return count($value) == 1 && $value[0] == $prime; + } + } + } + + $n = $this->copy(); + $n_1 = $n->subtract($one); + $n_2 = $n->subtract($two); + + $r = $n_1->copy(); + $r_value = $r->value; + // ie. $s = gmp_scan1($n, 0) and $r = gmp_div_q($n, gmp_pow(gmp_init('2'), $s)); + if ( MATH_BIGINTEGER_MODE == MATH_BIGINTEGER_MODE_BCMATH ) { + $s = 0; + // if $n was 1, $r would be 0 and this would be an infinite loop, hence our $this->equals($one) check earlier + while ($r->value[strlen($r->value) - 1] % 2 == 0) { + $r->value = bcdiv($r->value, '2', 0); + ++$s; + } + } else { + for ($i = 0, $r_length = count($r_value); $i < $r_length; ++$i) { + $temp = ~$r_value[$i] & 0xFFFFFF; + for ($j = 1; ($temp >> $j) & 1; ++$j); + if ($j != 25) { + break; + } + } + $s = 26 * $i + $j - 1; + $r->_rshift($s); + } + + for ($i = 0; $i < $t; ++$i) { + $a = $this->random($two, $n_2); + $y = $a->modPow($r, $n); + + if (!$y->equals($one) && !$y->equals($n_1)) { + for ($j = 1; $j < $s && !$y->equals($n_1); ++$j) { + $y = $y->modPow($two, $n); + if ($y->equals($one)) { + return false; + } + } + + if (!$y->equals($n_1)) { + return false; + } + } + } + return true; + } + + /** + * Logical Left Shift + * + * Shifts BigInteger's by $shift bits. + * + * @param Integer $shift + * @access private + */ + function _lshift($shift) + { + if ( $shift == 0 ) { + return; + } + + $num_digits = (int) ($shift / 26); + $shift %= 26; + $shift = 1 << $shift; + + $carry = 0; + + for ($i = 0; $i < count($this->value); ++$i) { + $temp = $this->value[$i] * $shift + $carry; + $carry = (int) ($temp / 0x4000000); + $this->value[$i] = (int) ($temp - $carry * 0x4000000); + } + + if ( $carry ) { + $this->value[] = $carry; + } + + while ($num_digits--) { + array_unshift($this->value, 0); + } + } + + /** + * Logical Right Shift + * + * Shifts BigInteger's by $shift bits. + * + * @param Integer $shift + * @access private + */ + function _rshift($shift) + { + if ($shift == 0) { + return; + } + + $num_digits = (int) ($shift / 26); + $shift %= 26; + $carry_shift = 26 - $shift; + $carry_mask = (1 << $shift) - 1; + + if ( $num_digits ) { + $this->value = array_slice($this->value, $num_digits); + } + + $carry = 0; + + for ($i = count($this->value) - 1; $i >= 0; --$i) { + $temp = $this->value[$i] >> $shift | $carry; + $carry = ($this->value[$i] & $carry_mask) << $carry_shift; + $this->value[$i] = $temp; + } + + $this->value = $this->_trim($this->value); + } + + /** + * Normalize + * + * Removes leading zeros and truncates (if necessary) to maintain the appropriate precision + * + * @param Math_BigInteger + * @return Math_BigInteger + * @see _trim() + * @access private + */ + function _normalize($result) + { + $result->precision = $this->precision; + $result->bitmask = $this->bitmask; + + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + if (!empty($result->bitmask->value)) { + $result->value = gmp_and($result->value, $result->bitmask->value); + } + + return $result; + case MATH_BIGINTEGER_MODE_BCMATH: + if (!empty($result->bitmask->value)) { + $result->value = bcmod($result->value, $result->bitmask->value); + } + + return $result; + } + + $value = &$result->value; + + if ( !count($value) ) { + return $result; + } + + $value = $this->_trim($value); + + if (!empty($result->bitmask->value)) { + $length = min(count($value), count($this->bitmask->value)); + $value = array_slice($value, 0, $length); + + for ($i = 0; $i < $length; ++$i) { + $value[$i] = $value[$i] & $this->bitmask->value[$i]; + } + } + + return $result; + } + + /** + * Trim + * + * Removes leading zeros + * + * @return Math_BigInteger + * @access private + */ + function _trim($value) + { + for ($i = count($value) - 1; $i >= 0; --$i) { + if ( $value[$i] ) { + break; + } + unset($value[$i]); + } + + return $value; + } + + /** + * Array Repeat + * + * @param $input Array + * @param $multiplier mixed + * @return Array + * @access private + */ + function _array_repeat($input, $multiplier) + { + return ($multiplier) ? array_fill(0, $multiplier, $input) : array(); + } + + /** + * Logical Left Shift + * + * Shifts binary strings $shift bits, essentially multiplying by 2**$shift. + * + * @param $x String + * @param $shift Integer + * @return String + * @access private + */ + function _base256_lshift(&$x, $shift) + { + if ($shift == 0) { + return; + } + + $num_bytes = $shift >> 3; // eg. floor($shift/8) + $shift &= 7; // eg. $shift % 8 + + $carry = 0; + for ($i = strlen($x) - 1; $i >= 0; --$i) { + $temp = ord($x[$i]) << $shift | $carry; + $x[$i] = chr($temp); + $carry = $temp >> 8; + } + $carry = ($carry != 0) ? chr($carry) : ''; + $x = $carry . $x . str_repeat(chr(0), $num_bytes); + } + + /** + * Logical Right Shift + * + * Shifts binary strings $shift bits, essentially dividing by 2**$shift and returning the remainder. + * + * @param $x String + * @param $shift Integer + * @return String + * @access private + */ + function _base256_rshift(&$x, $shift) + { + if ($shift == 0) { + $x = ltrim($x, chr(0)); + return ''; + } + + $num_bytes = $shift >> 3; // eg. floor($shift/8) + $shift &= 7; // eg. $shift % 8 + + $remainder = ''; + if ($num_bytes) { + $start = $num_bytes > strlen($x) ? -strlen($x) : -$num_bytes; + $remainder = substr($x, $start); + $x = substr($x, 0, -$num_bytes); + } + + $carry = 0; + $carry_shift = 8 - $shift; + for ($i = 0; $i < strlen($x); ++$i) { + $temp = (ord($x[$i]) >> $shift) | $carry; + $carry = (ord($x[$i]) << $carry_shift) & 0xFF; + $x[$i] = chr($temp); + } + $x = ltrim($x, chr(0)); + + $remainder = chr($carry >> $carry_shift) . $remainder; + + return ltrim($remainder, chr(0)); + } + + // one quirk about how the following functions are implemented is that PHP defines N to be an unsigned long + // at 32-bits, while java's longs are 64-bits. + + /** + * Converts 32-bit integers to bytes. + * + * @param Integer $x + * @return String + * @access private + */ + function _int2bytes($x) + { + return ltrim(pack('N', $x), chr(0)); + } + + /** + * Converts bytes to 32-bit integers + * + * @param String $x + * @return Integer + * @access private + */ + function _bytes2int($x) + { + $temp = unpack('Nint', str_pad($x, 4, chr(0), STR_PAD_LEFT)); + return $temp['int']; + } + + /** + * DER-encode an integer + * + * The ability to DER-encode integers is needed to create RSA public keys for use with OpenSSL + * + * @see modPow() + * @access private + * @param Integer $length + * @return String + */ + function _encodeASN1Length($length) + { + if ($length <= 0x7F) { + return chr($length); + } + + $temp = ltrim(pack('N', $length), chr(0)); + return pack('Ca*', 0x80 | strlen($temp), $temp); + } +} \ No newline at end of file diff --git a/3rdparty/phpseclib/Net/SFTP.php b/3rdparty/phpseclib/Net/SFTP.php new file mode 100644 index 00000000000..623945681d1 --- /dev/null +++ b/3rdparty/phpseclib/Net/SFTP.php @@ -0,0 +1,2015 @@ + + * login('username', 'password')) { + * exit('Login Failed'); + * } + * + * echo $sftp->pwd() . "\r\n"; + * $sftp->put('filename.ext', 'hello, world!'); + * print_r($sftp->nlist()); + * ?> + * + * + * LICENSE: Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @category Net + * @package Net_SFTP + * @author Jim Wigginton + * @copyright MMIX Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +/** + * Include Net_SSH2 + */ +if (!class_exists('Net_SSH2')) { + require_once('Net/SSH2.php'); +} + +/**#@+ + * @access public + * @see Net_SFTP::getLog() + */ +/** + * Returns the message numbers + */ +define('NET_SFTP_LOG_SIMPLE', NET_SSH2_LOG_SIMPLE); +/** + * Returns the message content + */ +define('NET_SFTP_LOG_COMPLEX', NET_SSH2_LOG_COMPLEX); +/** + * Outputs the message content in real-time. + */ +define('NET_SFTP_LOG_REALTIME', 3); +/**#@-*/ + +/** + * SFTP channel constant + * + * Net_SSH2::exec() uses 0 and Net_SSH2::read() / Net_SSH2::write() use 1. + * + * @see Net_SSH2::_send_channel_packet() + * @see Net_SSH2::_get_channel_packet() + * @access private + */ +define('NET_SFTP_CHANNEL', 2); + +/**#@+ + * @access public + * @see Net_SFTP::put() + */ +/** + * Reads data from a local file. + */ +define('NET_SFTP_LOCAL_FILE', 1); +/** + * Reads data from a string. + */ +// this value isn't really used anymore but i'm keeping it reserved for historical reasons +define('NET_SFTP_STRING', 2); +/** + * Resumes an upload + */ +define('NET_SFTP_RESUME', 4); +/**#@-*/ + +/** + * Pure-PHP implementations of SFTP. + * + * @author Jim Wigginton + * @version 0.1.0 + * @access public + * @package Net_SFTP + */ +class Net_SFTP extends Net_SSH2 { + /** + * Packet Types + * + * @see Net_SFTP::Net_SFTP() + * @var Array + * @access private + */ + var $packet_types = array(); + + /** + * Status Codes + * + * @see Net_SFTP::Net_SFTP() + * @var Array + * @access private + */ + var $status_codes = array(); + + /** + * The Request ID + * + * The request ID exists in the off chance that a packet is sent out-of-order. Of course, this library doesn't support + * concurrent actions, so it's somewhat academic, here. + * + * @var Integer + * @see Net_SFTP::_send_sftp_packet() + * @access private + */ + var $request_id = false; + + /** + * The Packet Type + * + * The request ID exists in the off chance that a packet is sent out-of-order. Of course, this library doesn't support + * concurrent actions, so it's somewhat academic, here. + * + * @var Integer + * @see Net_SFTP::_get_sftp_packet() + * @access private + */ + var $packet_type = -1; + + /** + * Packet Buffer + * + * @var String + * @see Net_SFTP::_get_sftp_packet() + * @access private + */ + var $packet_buffer = ''; + + /** + * Extensions supported by the server + * + * @var Array + * @see Net_SFTP::_initChannel() + * @access private + */ + var $extensions = array(); + + /** + * Server SFTP version + * + * @var Integer + * @see Net_SFTP::_initChannel() + * @access private + */ + var $version; + + /** + * Current working directory + * + * @var String + * @see Net_SFTP::_realpath() + * @see Net_SFTP::chdir() + * @access private + */ + var $pwd = false; + + /** + * Packet Type Log + * + * @see Net_SFTP::getLog() + * @var Array + * @access private + */ + var $packet_type_log = array(); + + /** + * Packet Log + * + * @see Net_SFTP::getLog() + * @var Array + * @access private + */ + var $packet_log = array(); + + /** + * Error information + * + * @see Net_SFTP::getSFTPErrors() + * @see Net_SFTP::getLastSFTPError() + * @var String + * @access private + */ + var $sftp_errors = array(); + + /** + * File Type + * + * @see Net_SFTP::_parseLongname() + * @var Integer + * @access private + */ + var $fileType = 0; + + /** + * Directory Cache + * + * Rather than always having to open a directory and close it immediately there after to see if a file is a directory or + * rather than always + * + * @see Net_SFTP::_save_dir() + * @see Net_SFTP::_remove_dir() + * @see Net_SFTP::_is_dir() + * @var Array + * @access private + */ + var $dirs = array(); + + /** + * Default Constructor. + * + * Connects to an SFTP server + * + * @param String $host + * @param optional Integer $port + * @param optional Integer $timeout + * @return Net_SFTP + * @access public + */ + function Net_SFTP($host, $port = 22, $timeout = 10) + { + parent::Net_SSH2($host, $port, $timeout); + $this->packet_types = array( + 1 => 'NET_SFTP_INIT', + 2 => 'NET_SFTP_VERSION', + /* the format of SSH_FXP_OPEN changed between SFTPv4 and SFTPv5+: + SFTPv5+: http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.1.1 + pre-SFTPv5 : http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-6.3 */ + 3 => 'NET_SFTP_OPEN', + 4 => 'NET_SFTP_CLOSE', + 5 => 'NET_SFTP_READ', + 6 => 'NET_SFTP_WRITE', + 7 => 'NET_SFTP_LSTAT', + 9 => 'NET_SFTP_SETSTAT', + 11 => 'NET_SFTP_OPENDIR', + 12 => 'NET_SFTP_READDIR', + 13 => 'NET_SFTP_REMOVE', + 14 => 'NET_SFTP_MKDIR', + 15 => 'NET_SFTP_RMDIR', + 16 => 'NET_SFTP_REALPATH', + 17 => 'NET_SFTP_STAT', + /* the format of SSH_FXP_RENAME changed between SFTPv4 and SFTPv5+: + SFTPv5+: http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.3 + pre-SFTPv5 : http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-6.5 */ + 18 => 'NET_SFTP_RENAME', + + 101=> 'NET_SFTP_STATUS', + 102=> 'NET_SFTP_HANDLE', + /* the format of SSH_FXP_NAME changed between SFTPv3 and SFTPv4+: + SFTPv4+: http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-9.4 + pre-SFTPv4 : http://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-7 */ + 103=> 'NET_SFTP_DATA', + 104=> 'NET_SFTP_NAME', + 105=> 'NET_SFTP_ATTRS', + + 200=> 'NET_SFTP_EXTENDED' + ); + $this->status_codes = array( + 0 => 'NET_SFTP_STATUS_OK', + 1 => 'NET_SFTP_STATUS_EOF', + 2 => 'NET_SFTP_STATUS_NO_SUCH_FILE', + 3 => 'NET_SFTP_STATUS_PERMISSION_DENIED', + 4 => 'NET_SFTP_STATUS_FAILURE', + 5 => 'NET_SFTP_STATUS_BAD_MESSAGE', + 6 => 'NET_SFTP_STATUS_NO_CONNECTION', + 7 => 'NET_SFTP_STATUS_CONNECTION_LOST', + 8 => 'NET_SFTP_STATUS_OP_UNSUPPORTED' + ); + // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-7.1 + // the order, in this case, matters quite a lot - see Net_SFTP::_parseAttributes() to understand why + $this->attributes = array( + 0x00000001 => 'NET_SFTP_ATTR_SIZE', + 0x00000002 => 'NET_SFTP_ATTR_UIDGID', // defined in SFTPv3, removed in SFTPv4+ + 0x00000004 => 'NET_SFTP_ATTR_PERMISSIONS', + 0x00000008 => 'NET_SFTP_ATTR_ACCESSTIME', + // 0x80000000 will yield a floating point on 32-bit systems and converting floating points to integers + // yields inconsistent behavior depending on how php is compiled. so we left shift -1 (which, in + // two's compliment, consists of all 1 bits) by 31. on 64-bit systems this'll yield 0xFFFFFFFF80000000. + // that's not a problem, however, and 'anded' and a 32-bit number, as all the leading 1 bits are ignored. + -1 << 31 => 'NET_SFTP_ATTR_EXTENDED' + ); + // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-6.3 + // the flag definitions change somewhat in SFTPv5+. if SFTPv5+ support is added to this library, maybe name + // the array for that $this->open5_flags and similarily alter the constant names. + $this->open_flags = array( + 0x00000001 => 'NET_SFTP_OPEN_READ', + 0x00000002 => 'NET_SFTP_OPEN_WRITE', + 0x00000004 => 'NET_SFTP_OPEN_APPEND', + 0x00000008 => 'NET_SFTP_OPEN_CREATE', + 0x00000010 => 'NET_SFTP_OPEN_TRUNCATE' + ); + // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-5.2 + // see Net_SFTP::_parseLongname() for an explanation + $this->file_types = array( + 1 => 'NET_SFTP_TYPE_REGULAR', + 2 => 'NET_SFTP_TYPE_DIRECTORY', + 3 => 'NET_SFTP_TYPE_SYMLINK', + 4 => 'NET_SFTP_TYPE_SPECIAL' + ); + $this->_define_array( + $this->packet_types, + $this->status_codes, + $this->attributes, + $this->open_flags, + $this->file_types + ); + } + + /** + * Login + * + * @param String $username + * @param optional String $password + * @return Boolean + * @access public + */ + function login($username, $password = '') + { + if (!parent::login($username, $password)) { + return false; + } + + $this->window_size_client_to_server[NET_SFTP_CHANNEL] = $this->window_size; + + $packet = pack('CNa*N3', + NET_SSH2_MSG_CHANNEL_OPEN, strlen('session'), 'session', NET_SFTP_CHANNEL, $this->window_size, 0x4000); + + if (!$this->_send_binary_packet($packet)) { + return false; + } + + $this->channel_status[NET_SFTP_CHANNEL] = NET_SSH2_MSG_CHANNEL_OPEN; + + $response = $this->_get_channel_packet(NET_SFTP_CHANNEL); + if ($response === false) { + return false; + } + + $packet = pack('CNNa*CNa*', + NET_SSH2_MSG_CHANNEL_REQUEST, $this->server_channels[NET_SFTP_CHANNEL], strlen('subsystem'), 'subsystem', 1, strlen('sftp'), 'sftp'); + if (!$this->_send_binary_packet($packet)) { + return false; + } + + $this->channel_status[NET_SFTP_CHANNEL] = NET_SSH2_MSG_CHANNEL_REQUEST; + + $response = $this->_get_channel_packet(NET_SFTP_CHANNEL); + if ($response === false) { + return false; + } + + $this->channel_status[NET_SFTP_CHANNEL] = NET_SSH2_MSG_CHANNEL_DATA; + + if (!$this->_send_sftp_packet(NET_SFTP_INIT, "\0\0\0\3")) { + return false; + } + + $response = $this->_get_sftp_packet(); + if ($this->packet_type != NET_SFTP_VERSION) { + user_error('Expected SSH_FXP_VERSION', E_USER_NOTICE); + return false; + } + + extract(unpack('Nversion', $this->_string_shift($response, 4))); + $this->version = $version; + while (!empty($response)) { + extract(unpack('Nlength', $this->_string_shift($response, 4))); + $key = $this->_string_shift($response, $length); + extract(unpack('Nlength', $this->_string_shift($response, 4))); + $value = $this->_string_shift($response, $length); + $this->extensions[$key] = $value; + } + + /* + SFTPv4+ defines a 'newline' extension. SFTPv3 seems to have unofficial support for it via 'newline@vandyke.com', + however, I'm not sure what 'newline@vandyke.com' is supposed to do (the fact that it's unofficial means that it's + not in the official SFTPv3 specs) and 'newline@vandyke.com' / 'newline' are likely not drop-in substitutes for + one another due to the fact that 'newline' comes with a SSH_FXF_TEXT bitmask whereas it seems unlikely that + 'newline@vandyke.com' would. + */ + /* + if (isset($this->extensions['newline@vandyke.com'])) { + $this->extensions['newline'] = $this->extensions['newline@vandyke.com']; + unset($this->extensions['newline@vandyke.com']); + } + */ + + $this->request_id = 1; + + /* + A Note on SFTPv4/5/6 support: + states the following: + + "If the client wishes to interoperate with servers that support noncontiguous version + numbers it SHOULD send '3'" + + Given that the server only sends its version number after the client has already done so, the above + seems to be suggesting that v3 should be the default version. This makes sense given that v3 is the + most popular. + + states the following; + + "If the server did not send the "versions" extension, or the version-from-list was not included, the + server MAY send a status response describing the failure, but MUST then close the channel without + processing any further requests." + + So what do you do if you have a client whose initial SSH_FXP_INIT packet says it implements v3 and + a server whose initial SSH_FXP_VERSION reply says it implements v4 and only v4? If it only implements + v4, the "versions" extension is likely not going to have been sent so version re-negotiation as discussed + in draft-ietf-secsh-filexfer-13 would be quite impossible. As such, what Net_SFTP would do is close the + channel and reopen it with a new and updated SSH_FXP_INIT packet. + */ + switch ($this->version) { + case 2: + case 3: + break; + default: + return false; + } + + $this->pwd = $this->_realpath('.', false); + + $this->_save_dir($this->pwd); + + return true; + } + + /** + * Returns the current directory name + * + * @return Mixed + * @access public + */ + function pwd() + { + return $this->pwd; + } + + /** + * Logs errors + * + * @param String $response + * @param optional Integer $status + * @access public + */ + function _logError($response, $status = -1) { + if ($status == -1) { + extract(unpack('Nstatus', $this->_string_shift($response, 4))); + } + + $error = $this->status_codes[$status]; + + if ($this->version > 2) { + extract(unpack('Nlength', $this->_string_shift($response, 4))); + $this->sftp_errors[] = $error . ': ' . $this->_string_shift($response, $length); + } else { + $this->sftp_errors[] = $error; + } + } + + /** + * Canonicalize the Server-Side Path Name + * + * SFTP doesn't provide a mechanism by which the current working directory can be changed, so we'll emulate it. Returns + * the absolute (canonicalized) path. + * + * @see Net_SFTP::chdir() + * @param String $dir + * @return Mixed + * @access private + */ + function _realpath($dir, $check_dir = true) + { + if ($check_dir && $this->_is_dir($dir)) { + return true; + } + + /* + "This protocol represents file names as strings. File names are + assumed to use the slash ('/') character as a directory separator. + + File names starting with a slash are "absolute", and are relative to + the root of the file system. Names starting with any other character + are relative to the user's default directory (home directory). Note + that identifying the user is assumed to take place outside of this + protocol." + + -- http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-6 + */ + $file = ''; + if ($this->pwd !== false) { + // if the SFTP server returned the canonicalized path even for non-existant files this wouldn't be necessary + // on OpenSSH it isn't necessary but on other SFTP servers it is. that and since the specs say nothing on + // the subject, we'll go ahead and work around it with the following. + if (empty($dir) || $dir[strlen($dir) - 1] != '/') { + $file = basename($dir); + $dir = dirname($dir); + } + + $dir = $dir[0] == '/' ? '/' . rtrim(substr($dir, 1), '/') : rtrim($dir, '/'); + + if ($dir == '.' || $dir == $this->pwd) { + $temp = $this->pwd; + if (!empty($file)) { + $temp.= '/' . $file; + } + return $temp; + } + + if ($dir[0] != '/') { + $dir = $this->pwd . '/' . $dir; + } + // on the surface it seems like maybe resolving a path beginning with / is unnecessary, but such paths + // can contain .'s and ..'s just like any other. we could parse those out as appropriate or we can let + // the server do it. we'll do the latter. + } + + /* + that SSH_FXP_REALPATH returns SSH_FXP_NAME does not necessarily mean that anything actually exists at the + specified path. generally speaking, no attributes are returned with this particular SSH_FXP_NAME packet + regardless of whether or not a file actually exists. and in SFTPv3, the longname field and the filename + field match for this particular SSH_FXP_NAME packet. for other SSH_FXP_NAME packets, this will likely + not be the case, but for this one, it is. + */ + // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.9 + if (!$this->_send_sftp_packet(NET_SFTP_REALPATH, pack('Na*', strlen($dir), $dir))) { + return false; + } + + $response = $this->_get_sftp_packet(); + switch ($this->packet_type) { + case NET_SFTP_NAME: + // although SSH_FXP_NAME is implemented differently in SFTPv3 than it is in SFTPv4+, the following + // should work on all SFTP versions since the only part of the SSH_FXP_NAME packet the following looks + // at is the first part and that part is defined the same in SFTP versions 3 through 6. + $this->_string_shift($response, 4); // skip over the count - it should be 1, anyway + extract(unpack('Nlength', $this->_string_shift($response, 4))); + $realpath = $this->_string_shift($response, $length); + // the following is SFTPv3 only code. see Net_SFTP::_parseLongname() for more information. + // per the above comment, this is a shot in the dark that, on most servers, won't help us in determining + // the file type for Net_SFTP::stat() and Net_SFTP::lstat() but it's worth a shot. + extract(unpack('Nlength', $this->_string_shift($response, 4))); + $this->fileType = $this->_parseLongname($this->_string_shift($response, $length)); + break; + case NET_SFTP_STATUS: + $this->_logError($response); + return false; + default: + user_error('Expected SSH_FXP_NAME or SSH_FXP_STATUS', E_USER_NOTICE); + return false; + } + + // if $this->pwd isn't set than the only thing $realpath could be is for '.', which is pretty much guaranteed to + // be a bonafide directory + if (!empty($file)) { + $realpath.= '/' . $file; + } + + return $realpath; + } + + /** + * Changes the current directory + * + * @param String $dir + * @return Boolean + * @access public + */ + function chdir($dir) + { + if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) { + return false; + } + + if ($dir[strlen($dir) - 1] != '/') { + $dir.= '/'; + } + + // confirm that $dir is, in fact, a valid directory + if ($this->_is_dir($dir)) { + $this->pwd = $dir; + return true; + } + + $dir = $this->_realpath($dir, false); + + if ($this->_is_dir($dir)) { + $this->pwd = $dir; + return true; + } + + if (!$this->_send_sftp_packet(NET_SFTP_OPENDIR, pack('Na*', strlen($dir), $dir))) { + return false; + } + + // see Net_SFTP::nlist() for a more thorough explanation of the following + $response = $this->_get_sftp_packet(); + switch ($this->packet_type) { + case NET_SFTP_HANDLE: + $handle = substr($response, 4); + break; + case NET_SFTP_STATUS: + $this->_logError($response); + return false; + default: + user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS', E_USER_NOTICE); + return false; + } + + if (!$this->_send_sftp_packet(NET_SFTP_CLOSE, pack('Na*', strlen($handle), $handle))) { + return false; + } + + $response = $this->_get_sftp_packet(); + if ($this->packet_type != NET_SFTP_STATUS) { + user_error('Expected SSH_FXP_STATUS', E_USER_NOTICE); + return false; + } + + extract(unpack('Nstatus', $this->_string_shift($response, 4))); + if ($status != NET_SFTP_STATUS_OK) { + $this->_logError($response, $status); + return false; + } + + $this->_save_dir($dir); + + $this->pwd = $dir; + return true; + } + + /** + * Returns a list of files in the given directory + * + * @param optional String $dir + * @return Mixed + * @access public + */ + function nlist($dir = '.') + { + return $this->_list($dir, false); + } + + /** + * Returns a detailed list of files in the given directory + * + * @param optional String $dir + * @return Mixed + * @access public + */ + function rawlist($dir = '.') + { + return $this->_list($dir, true); + } + + /** + * Reads a list, be it detailed or not, of files in the given directory + * + * @param optional String $dir + * @return Mixed + * @access private + */ + function _list($dir, $raw = true, $realpath = true) + { + if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) { + return false; + } + + $dir = $this->_realpath($dir . '/'); + if ($dir === false) { + return false; + } + + // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.1.2 + if (!$this->_send_sftp_packet(NET_SFTP_OPENDIR, pack('Na*', strlen($dir), $dir))) { + return false; + } + + $response = $this->_get_sftp_packet(); + switch ($this->packet_type) { + case NET_SFTP_HANDLE: + // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-9.2 + // since 'handle' is the last field in the SSH_FXP_HANDLE packet, we'll just remove the first four bytes that + // represent the length of the string and leave it at that + $handle = substr($response, 4); + break; + case NET_SFTP_STATUS: + // presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED + $this->_logError($response); + return false; + default: + user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS', E_USER_NOTICE); + return false; + } + + $this->_save_dir($dir); + + $contents = array(); + while (true) { + // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.2.2 + // why multiple SSH_FXP_READDIR packets would be sent when the response to a single one can span arbitrarily many + // SSH_MSG_CHANNEL_DATA messages is not known to me. + if (!$this->_send_sftp_packet(NET_SFTP_READDIR, pack('Na*', strlen($handle), $handle))) { + return false; + } + + $response = $this->_get_sftp_packet(); + switch ($this->packet_type) { + case NET_SFTP_NAME: + extract(unpack('Ncount', $this->_string_shift($response, 4))); + for ($i = 0; $i < $count; $i++) { + extract(unpack('Nlength', $this->_string_shift($response, 4))); + $shortname = $this->_string_shift($response, $length); + extract(unpack('Nlength', $this->_string_shift($response, 4))); + $longname = $this->_string_shift($response, $length); + $attributes = $this->_parseAttributes($response); // we also don't care about the attributes + if (!$raw) { + $contents[] = $shortname; + } else { + $contents[$shortname] = $attributes; + $fileType = $this->_parseLongname($longname); + if ($fileType) { + if ($fileType == NET_SFTP_TYPE_DIRECTORY && ($shortname != '.' && $shortname != '..')) { + $this->_save_dir($dir . '/' . $shortname); + } + $contents[$shortname]['type'] = $fileType; + } + } + // SFTPv6 has an optional boolean end-of-list field, but we'll ignore that, since the + // final SSH_FXP_STATUS packet should tell us that, already. + } + break; + case NET_SFTP_STATUS: + extract(unpack('Nstatus', $this->_string_shift($response, 4))); + if ($status != NET_SFTP_STATUS_EOF) { + $this->_logError($response, $status); + return false; + } + break 2; + default: + user_error('Expected SSH_FXP_NAME or SSH_FXP_STATUS', E_USER_NOTICE); + return false; + } + } + + if (!$this->_send_sftp_packet(NET_SFTP_CLOSE, pack('Na*', strlen($handle), $handle))) { + return false; + } + + // "The client MUST release all resources associated with the handle regardless of the status." + // -- http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.1.3 + $response = $this->_get_sftp_packet(); + if ($this->packet_type != NET_SFTP_STATUS) { + user_error('Expected SSH_FXP_STATUS', E_USER_NOTICE); + return false; + } + + extract(unpack('Nstatus', $this->_string_shift($response, 4))); + if ($status != NET_SFTP_STATUS_OK) { + $this->_logError($response, $status); + return false; + } + + return $contents; + } + + /** + * Returns the file size, in bytes, or false, on failure + * + * Files larger than 4GB will show up as being exactly 4GB. + * + * @param String $filename + * @return Mixed + * @access public + */ + function size($filename) + { + if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) { + return false; + } + + $filename = $this->_realpath($filename); + if ($filename === false) { + return false; + } + + return $this->_size($filename); + } + + /** + * Save directories to cache + * + * @param String $dir + * @access private + */ + function _save_dir($dir) + { + // preg_replace('#^/|/(?=/)|/$#', '', $dir) == str_replace('//', '/', trim($dir, '/')) + $dirs = explode('/', preg_replace('#^/|/(?=/)|/$#', '', $dir)); + + $temp = &$this->dirs; + foreach ($dirs as $dir) { + if (!isset($temp[$dir])) { + $temp[$dir] = array(); + } + $temp = &$temp[$dir]; + } + } + + /** + * Remove directories from cache + * + * @param String $dir + * @access private + */ + function _remove_dir($dir) + { + $dirs = explode('/', preg_replace('#^/|/(?=/)|/$#', '', $dir)); + + $temp = &$this->dirs; + foreach ($dirs as $dir) { + if ($dir == end($dirs)) { + unset($temp[$dir]); + return true; + } + if (!isset($temp[$dir])) { + return false; + } + $temp = &$temp[$dir]; + } + } + + /** + * Checks cache for directory + * + * @param String $dir + * @access private + */ + function _is_dir($dir) + { + $dirs = explode('/', preg_replace('#^/|/(?=/)|/$#', '', $dir)); + + $temp = &$this->dirs; + foreach ($dirs as $dir) { + if (!isset($temp[$dir])) { + return false; + } + $temp = &$temp[$dir]; + } + } + + /** + * Returns general information about a file. + * + * Returns an array on success and false otherwise. + * + * @param String $filename + * @return Mixed + * @access public + */ + function stat($filename) + { + if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) { + return false; + } + + $filename = $this->_realpath($filename); + if ($filename === false) { + return false; + } + + $stat = $this->_stat($filename, NET_SFTP_STAT); + if ($stat === false) { + return false; + } + + $pwd = $this->pwd; + $stat['type'] = $this->chdir($filename) ? + NET_SFTP_TYPE_DIRECTORY : + NET_SFTP_TYPE_REGULAR; + $this->pwd = $pwd; + + return $stat; + } + + /** + * Returns general information about a file or symbolic link. + * + * Returns an array on success and false otherwise. + * + * @param String $filename + * @return Mixed + * @access public + */ + function lstat($filename) + { + if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) { + return false; + } + + $filename = $this->_realpath($filename); + if ($filename === false) { + return false; + } + + $lstat = $this->_stat($filename, NET_SFTP_LSTAT); + $stat = $this->_stat($filename, NET_SFTP_STAT); + if ($stat === false) { + return false; + } + + if ($lstat != $stat) { + return array_merge($lstat, array('type' => NET_SFTP_TYPE_SYMLINK)); + } + + $pwd = $this->pwd; + $lstat['type'] = $this->chdir($filename) ? + NET_SFTP_TYPE_DIRECTORY : + NET_SFTP_TYPE_REGULAR; + $this->pwd = $pwd; + + return $lstat; + } + + /** + * Returns general information about a file or symbolic link + * + * Determines information without calling Net_SFTP::_realpath(). + * The second parameter can be either NET_SFTP_STAT or NET_SFTP_LSTAT. + * + * @param String $filename + * @param Integer $type + * @return Mixed + * @access private + */ + function _stat($filename, $type) + { + // SFTPv4+ adds an additional 32-bit integer field - flags - to the following: + $packet = pack('Na*', strlen($filename), $filename); + if (!$this->_send_sftp_packet($type, $packet)) { + return false; + } + + $response = $this->_get_sftp_packet(); + switch ($this->packet_type) { + case NET_SFTP_ATTRS: + $attributes = $this->_parseAttributes($response); + if ($this->fileType) { + $attributes['type'] = $this->fileType; + } + return $attributes; + case NET_SFTP_STATUS: + $this->_logError($response); + return false; + } + + user_error('Expected SSH_FXP_ATTRS or SSH_FXP_STATUS', E_USER_NOTICE); + return false; + } + + /** + * Attempt to identify the file type + * + * @param String $path + * @param Array $stat + * @param Array $lstat + * @return Integer + * @access private + */ + function _identify_type($path, $stat1, $stat2) + { + $stat1 = $this->_stat($path, $stat1); + $stat2 = $this->_stat($path, $stat2); + + if ($stat1 != $stat2) { + return array_merge($stat1, array('type' => NET_SFTP_TYPE_SYMLINK)); + } + + $pwd = $this->pwd; + $stat1['type'] = $this->chdir($path) ? + NET_SFTP_TYPE_DIRECTORY : + NET_SFTP_TYPE_REGULAR; + $this->pwd = $pwd; + + return $stat1; + } + + /** + * Returns the file size, in bytes, or false, on failure + * + * Determines the size without calling Net_SFTP::_realpath() + * + * @param String $filename + * @return Mixed + * @access private + */ + function _size($filename) + { + $result = $this->_stat($filename, NET_SFTP_LSTAT); + if ($result === false) { + return false; + } + return isset($result['size']) ? $result['size'] : -1; + } + + /** + * Set permissions on a file. + * + * Returns the new file permissions on success or FALSE on error. + * + * @param Integer $mode + * @param String $filename + * @return Mixed + * @access public + */ + function chmod($mode, $filename, $recursive = false) + { + if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) { + return false; + } + + $filename = $this->_realpath($filename); + if ($filename === false) { + return false; + } + + if ($recursive) { + $i = 0; + $result = $this->_chmod_recursive($mode, $filename, $i); + $this->_read_put_responses($i); + return $result; + } + + // SFTPv4+ has an additional byte field - type - that would need to be sent, as well. setting it to + // SSH_FILEXFER_TYPE_UNKNOWN might work. if not, we'd have to do an SSH_FXP_STAT before doing an SSH_FXP_SETSTAT. + $attr = pack('N2', NET_SFTP_ATTR_PERMISSIONS, $mode & 07777); + if (!$this->_send_sftp_packet(NET_SFTP_SETSTAT, pack('Na*a*', strlen($filename), $filename, $attr))) { + return false; + } + + /* + "Because some systems must use separate system calls to set various attributes, it is possible that a failure + response will be returned, but yet some of the attributes may be have been successfully modified. If possible, + servers SHOULD avoid this situation; however, clients MUST be aware that this is possible." + + -- http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.6 + */ + $response = $this->_get_sftp_packet(); + if ($this->packet_type != NET_SFTP_STATUS) { + user_error('Expected SSH_FXP_STATUS', E_USER_NOTICE); + return false; + } + + extract(unpack('Nstatus', $this->_string_shift($response, 4))); + if ($status != NET_SFTP_STATUS_OK) { + $this->_logError($response, $status); + } + + // rather than return what the permissions *should* be, we'll return what they actually are. this will also + // tell us if the file actually exists. + // incidentally, SFTPv4+ adds an additional 32-bit integer field - flags - to the following: + $packet = pack('Na*', strlen($filename), $filename); + if (!$this->_send_sftp_packet(NET_SFTP_STAT, $packet)) { + return false; + } + + $response = $this->_get_sftp_packet(); + switch ($this->packet_type) { + case NET_SFTP_ATTRS: + $attrs = $this->_parseAttributes($response); + return $attrs['permissions']; + case NET_SFTP_STATUS: + $this->_logError($response); + return false; + } + + user_error('Expected SSH_FXP_ATTRS or SSH_FXP_STATUS', E_USER_NOTICE); + return false; + } + + /** + * Recursively chmods directories on the SFTP server + * + * Minimizes directory lookups and SSH_FXP_STATUS requests for speed. + * + * @param Integer $mode + * @param String $filename + * @return Boolean + * @access private + */ + function _chmod_recursive($mode, $path, &$i) + { + if (!$this->_read_put_responses($i)) { + return false; + } + $i = 0; + $entries = $this->_list($path, true, false); + + if ($entries === false) { + return $this->chmod($mode, $path); + } + + // normally $entries would have at least . and .. but it might not if the directories + // permissions didn't allow reading + if (empty($entries)) { + return false; + } + + foreach ($entries as $filename=>$props) { + if ($filename == '.' || $filename == '..') { + continue; + } + + if (!isset($props['type'])) { + return false; + } + + $temp = $path . '/' . $filename; + if ($props['type'] == NET_SFTP_TYPE_DIRECTORY) { + if (!$this->_chmod_recursive($mode, $temp, $i)) { + return false; + } + } else { + $attr = pack('N2', NET_SFTP_ATTR_PERMISSIONS, $mode & 07777); + if (!$this->_send_sftp_packet(NET_SFTP_SETSTAT, pack('Na*a*', strlen($temp), $temp, $attr))) { + return false; + } + + $i++; + + if ($i >= 50) { + if (!$this->_read_put_responses($i)) { + return false; + } + $i = 0; + } + } + } + + $attr = pack('N2', NET_SFTP_ATTR_PERMISSIONS, $mode & 07777); + if (!$this->_send_sftp_packet(NET_SFTP_SETSTAT, pack('Na*a*', strlen($path), $path, $attr))) { + return false; + } + + $i++; + + if ($i >= 50) { + if (!$this->_read_put_responses($i)) { + return false; + } + $i = 0; + } + + return true; + } + + /** + * Creates a directory. + * + * @param String $dir + * @return Boolean + * @access public + */ + function mkdir($dir) + { + if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) { + return false; + } + + if ($dir[0] != '/') { + $dir = $this->_realpath(rtrim($dir, '/')); + if ($dir === false) { + return false; + } + if (!$this->_mkdir_helper($dir)) { + return false; + } + } else { + $dirs = explode('/', preg_replace('#^/|/(?=/)|/$#', '', $dir)); + $temp = ''; + foreach ($dirs as $dir) { + $temp.= '/' . $dir; + $result = $this->_mkdir_helper($temp); + } + if (!$result) { + return false; + } + } + + return true; + } + + /** + * Helper function for directory creation + * + * @param String $dir + * @return Boolean + * @access private + */ + function _mkdir_helper($dir) + { + // by not providing any permissions, hopefully the server will use the logged in users umask - their + // default permissions. + if (!$this->_send_sftp_packet(NET_SFTP_MKDIR, pack('Na*N', strlen($dir), $dir, 0))) { + return false; + } + + $response = $this->_get_sftp_packet(); + if ($this->packet_type != NET_SFTP_STATUS) { + user_error('Expected SSH_FXP_STATUS', E_USER_NOTICE); + return false; + } + + extract(unpack('Nstatus', $this->_string_shift($response, 4))); + if ($status != NET_SFTP_STATUS_OK) { + $this->_logError($response, $status); + return false; + } + + $this->_save_dir($dir); + + return true; + } + + /** + * Removes a directory. + * + * @param String $dir + * @return Boolean + * @access public + */ + function rmdir($dir) + { + if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) { + return false; + } + + $dir = $this->_realpath($dir); + if ($dir === false) { + return false; + } + + if (!$this->_send_sftp_packet(NET_SFTP_RMDIR, pack('Na*', strlen($dir), $dir))) { + return false; + } + + $response = $this->_get_sftp_packet(); + if ($this->packet_type != NET_SFTP_STATUS) { + user_error('Expected SSH_FXP_STATUS', E_USER_NOTICE); + return false; + } + + extract(unpack('Nstatus', $this->_string_shift($response, 4))); + if ($status != NET_SFTP_STATUS_OK) { + // presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED? + $this->_logError($response, $status); + return false; + } + + $this->_remove_dir($dir); + + return true; + } + + /** + * Uploads a file to the SFTP server. + * + * By default, Net_SFTP::put() does not read from the local filesystem. $data is dumped directly into $remote_file. + * So, for example, if you set $data to 'filename.ext' and then do Net_SFTP::get(), you will get a file, twelve bytes + * long, containing 'filename.ext' as its contents. + * + * Setting $mode to NET_SFTP_LOCAL_FILE will change the above behavior. With NET_SFTP_LOCAL_FILE, $remote_file will + * contain as many bytes as filename.ext does on your local filesystem. If your filename.ext is 1MB then that is how + * large $remote_file will be, as well. + * + * Currently, only binary mode is supported. As such, if the line endings need to be adjusted, you will need to take + * care of that, yourself. + * + * @param String $remote_file + * @param String $data + * @param optional Integer $mode + * @return Boolean + * @access public + * @internal ASCII mode for SFTPv4/5/6 can be supported by adding a new function - Net_SFTP::setMode(). + */ + function put($remote_file, $data, $mode = NET_SFTP_STRING) + { + if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) { + return false; + } + + $remote_file = $this->_realpath($remote_file); + if ($remote_file === false) { + return false; + } + + $flags = NET_SFTP_OPEN_WRITE | NET_SFTP_OPEN_CREATE; + // according to the SFTP specs, NET_SFTP_OPEN_APPEND should "force all writes to append data at the end of the file." + // in practice, it doesn't seem to do that. + //$flags|= ($mode & NET_SFTP_RESUME) ? NET_SFTP_OPEN_APPEND : NET_SFTP_OPEN_TRUNCATE; + + // if NET_SFTP_OPEN_APPEND worked as it should the following (up until the -----------) wouldn't be necessary + $offset = 0; + if ($mode & NET_SFTP_RESUME) { + $size = $this->_size($remote_file); + $offset = $size !== false ? $size : 0; + } else { + $flags|= NET_SFTP_OPEN_TRUNCATE; + } + // -------------- + + $packet = pack('Na*N2', strlen($remote_file), $remote_file, $flags, 0); + if (!$this->_send_sftp_packet(NET_SFTP_OPEN, $packet)) { + return false; + } + + $response = $this->_get_sftp_packet(); + switch ($this->packet_type) { + case NET_SFTP_HANDLE: + $handle = substr($response, 4); + break; + case NET_SFTP_STATUS: + $this->_logError($response); + return false; + default: + user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS', E_USER_NOTICE); + return false; + } + + $initialize = true; + + // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.2.3 + if ($mode & NET_SFTP_LOCAL_FILE) { + if (!is_file($data)) { + user_error("$data is not a valid file", E_USER_NOTICE); + return false; + } + $fp = @fopen($data, 'rb'); + if (!$fp) { + return false; + } + $size = filesize($data); + } else { + $size = strlen($data); + } + + $sent = 0; + $size = $size < 0 ? ($size & 0x7FFFFFFF) + 0x80000000 : $size; + + $sftp_packet_size = 4096; // PuTTY uses 4096 + $i = 0; + while ($sent < $size) { + $temp = $mode & NET_SFTP_LOCAL_FILE ? fread($fp, $sftp_packet_size) : $this->_string_shift($data, $sftp_packet_size); + $packet = pack('Na*N3a*', strlen($handle), $handle, 0, $offset + $sent, strlen($temp), $temp); + if (!$this->_send_sftp_packet(NET_SFTP_WRITE, $packet)) { + fclose($fp); + return false; + } + $sent+= strlen($temp); + + $i++; + + if ($i == 50) { + if (!$this->_read_put_responses($i)) { + $i = 0; + break; + } + $i = 0; + } + } + + if (!$this->_read_put_responses($i)) { + return false; + } + + if ($mode & NET_SFTP_LOCAL_FILE) { + fclose($fp); + } + + if (!$this->_send_sftp_packet(NET_SFTP_CLOSE, pack('Na*', strlen($handle), $handle))) { + return false; + } + + $response = $this->_get_sftp_packet(); + if ($this->packet_type != NET_SFTP_STATUS) { + user_error('Expected SSH_FXP_STATUS', E_USER_NOTICE); + return false; + } + + extract(unpack('Nstatus', $this->_string_shift($response, 4))); + if ($status != NET_SFTP_STATUS_OK) { + $this->_logError($response, $status); + return false; + } + + return true; + } + + /** + * Reads multiple successive SSH_FXP_WRITE responses + * + * Sending an SSH_FXP_WRITE packet and immediately reading its response isn't as efficient as blindly sending out $i + * SSH_FXP_WRITEs, in succession, and then reading $i responses. + * + * @param Integer $i + * @return Boolean + * @access private + */ + function _read_put_responses($i) + { + while ($i--) { + $response = $this->_get_sftp_packet(); + if ($this->packet_type != NET_SFTP_STATUS) { + user_error('Expected SSH_FXP_STATUS', E_USER_NOTICE); + return false; + } + + extract(unpack('Nstatus', $this->_string_shift($response, 4))); + if ($status != NET_SFTP_STATUS_OK) { + $this->_logError($response, $status); + break; + } + } + + return $i < 0; + } + + /** + * Downloads a file from the SFTP server. + * + * Returns a string containing the contents of $remote_file if $local_file is left undefined or a boolean false if + * the operation was unsuccessful. If $local_file is defined, returns true or false depending on the success of the + * operation + * + * @param String $remote_file + * @param optional String $local_file + * @return Mixed + * @access public + */ + function get($remote_file, $local_file = false) + { + if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) { + return false; + } + + $remote_file = $this->_realpath($remote_file); + if ($remote_file === false) { + return false; + } + + $packet = pack('Na*N2', strlen($remote_file), $remote_file, NET_SFTP_OPEN_READ, 0); + if (!$this->_send_sftp_packet(NET_SFTP_OPEN, $packet)) { + return false; + } + + $response = $this->_get_sftp_packet(); + switch ($this->packet_type) { + case NET_SFTP_HANDLE: + $handle = substr($response, 4); + break; + case NET_SFTP_STATUS: // presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED + $this->_logError($response); + return false; + default: + user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS', E_USER_NOTICE); + return false; + } + + if ($local_file !== false) { + $fp = fopen($local_file, 'wb'); + if (!$fp) { + return false; + } + } else { + $content = ''; + } + + $read = 0; + while (true) { + $packet = pack('Na*N3', strlen($handle), $handle, 0, $read, 1 << 20); + if (!$this->_send_sftp_packet(NET_SFTP_READ, $packet)) { + if ($local_file !== false) { + fclose($fp); + } + return false; + } + + $response = $this->_get_sftp_packet(); + switch ($this->packet_type) { + case NET_SFTP_DATA: + $temp = substr($response, 4); + $read+= strlen($temp); + if ($local_file === false) { + $content.= $temp; + } else { + fputs($fp, $temp); + } + break; + case NET_SFTP_STATUS: + $this->_logError($response); + break 2; + default: + user_error('Expected SSH_FXP_DATA or SSH_FXP_STATUS', E_USER_NOTICE); + if ($local_file !== false) { + fclose($fp); + } + return false; + } + } + + if ($local_file !== false) { + fclose($fp); + } + + if (!$this->_send_sftp_packet(NET_SFTP_CLOSE, pack('Na*', strlen($handle), $handle))) { + return false; + } + + $response = $this->_get_sftp_packet(); + if ($this->packet_type != NET_SFTP_STATUS) { + user_error('Expected SSH_FXP_STATUS', E_USER_NOTICE); + return false; + } + + $this->_logError($response); + + // check the status from the NET_SFTP_STATUS case in the above switch after the file has been closed + if ($status != NET_SFTP_STATUS_OK) { + return false; + } + + if (isset($content)) { + return $content; + } + + return true; + } + + /** + * Deletes a file on the SFTP server. + * + * @param String $path + * @param Boolean $recursive + * @return Boolean + * @access public + */ + function delete($path, $recursive = true) + { + if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) { + return false; + } + + $path = $this->_realpath($path); + if ($path === false) { + return false; + } + + // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.3 + if (!$this->_send_sftp_packet(NET_SFTP_REMOVE, pack('Na*', strlen($path), $path))) { + return false; + } + + $response = $this->_get_sftp_packet(); + if ($this->packet_type != NET_SFTP_STATUS) { + user_error('Expected SSH_FXP_STATUS', E_USER_NOTICE); + return false; + } + + // if $status isn't SSH_FX_OK it's probably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED + extract(unpack('Nstatus', $this->_string_shift($response, 4))); + if ($status != NET_SFTP_STATUS_OK) { + $this->_logError($response, $status); + if (!$recursive) { + return false; + } + $i = 0; + $result = $this->_delete_recursive($path, $i); + $this->_read_put_responses($i); + return $result; + } + + return true; + } + + /** + * Recursively deletes directories on the SFTP server + * + * Minimizes directory lookups and SSH_FXP_STATUS requests for speed. + * + * @param String $path + * @param Integer $i + * @return Boolean + * @access private + */ + function _delete_recursive($path, &$i) + { + if (!$this->_read_put_responses($i)) { + return false; + } + $i = 0; + $entries = $this->_list($path, true, false); + + // normally $entries would have at least . and .. but it might not if the directories + // permissions didn't allow reading + if (empty($entries)) { + return false; + } + + foreach ($entries as $filename=>$props) { + if ($filename == '.' || $filename == '..') { + continue; + } + + if (!isset($props['type'])) { + return false; + } + + $temp = $path . '/' . $filename; + if ($props['type'] == NET_SFTP_TYPE_DIRECTORY) { + if (!$this->_delete_recursive($temp, $i)) { + return false; + } + } else { + if (!$this->_send_sftp_packet(NET_SFTP_REMOVE, pack('Na*', strlen($temp), $temp))) { + return false; + } + + $i++; + + if ($i >= 50) { + if (!$this->_read_put_responses($i)) { + return false; + } + $i = 0; + } + } + } + + if (!$this->_send_sftp_packet(NET_SFTP_RMDIR, pack('Na*', strlen($path), $path))) { + return false; + } + $this->_remove_dir($path); + + $i++; + + if ($i >= 50) { + if (!$this->_read_put_responses($i)) { + return false; + } + $i = 0; + } + + return true; + } + + /** + * Renames a file or a directory on the SFTP server + * + * @param String $oldname + * @param String $newname + * @return Boolean + * @access public + */ + function rename($oldname, $newname) + { + if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) { + return false; + } + + $oldname = $this->_realpath($oldname); + $newname = $this->_realpath($newname); + if ($oldname === false || $newname === false) { + return false; + } + + // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.3 + $packet = pack('Na*Na*', strlen($oldname), $oldname, strlen($newname), $newname); + if (!$this->_send_sftp_packet(NET_SFTP_RENAME, $packet)) { + return false; + } + + $response = $this->_get_sftp_packet(); + if ($this->packet_type != NET_SFTP_STATUS) { + user_error('Expected SSH_FXP_STATUS', E_USER_NOTICE); + return false; + } + + // if $status isn't SSH_FX_OK it's probably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED + extract(unpack('Nstatus', $this->_string_shift($response, 4))); + if ($status != NET_SFTP_STATUS_OK) { + $this->_logError($response, $status); + return false; + } + + return true; + } + + /** + * Parse Attributes + * + * See '7. File Attributes' of draft-ietf-secsh-filexfer-13 for more info. + * + * @param String $response + * @return Array + * @access private + */ + function _parseAttributes(&$response) + { + $attr = array(); + extract(unpack('Nflags', $this->_string_shift($response, 4))); + // SFTPv4+ have a type field (a byte) that follows the above flag field + foreach ($this->attributes as $key => $value) { + switch ($flags & $key) { + case NET_SFTP_ATTR_SIZE: // 0x00000001 + // size is represented by a 64-bit integer, so we perhaps ought to be doing the following: + // $attr['size'] = new Math_BigInteger($this->_string_shift($response, 8), 256); + // of course, you shouldn't be using Net_SFTP to transfer files that are in excess of 4GB + // (0xFFFFFFFF bytes), anyway. as such, we'll just represent all file sizes that are bigger than + // 4GB as being 4GB. + extract(unpack('Nupper/Nsize', $this->_string_shift($response, 8))); + if ($upper) { + $attr['size'] = 0xFFFFFFFF; + } else { + $attr['size'] = $size < 0 ? ($size & 0x7FFFFFFF) + 0x80000000 : $size; + } + break; + case NET_SFTP_ATTR_UIDGID: // 0x00000002 (SFTPv3 only) + $attr+= unpack('Nuid/Ngid', $this->_string_shift($response, 8)); + break; + case NET_SFTP_ATTR_PERMISSIONS: // 0x00000004 + $attr+= unpack('Npermissions', $this->_string_shift($response, 4)); + break; + case NET_SFTP_ATTR_ACCESSTIME: // 0x00000008 + $attr+= unpack('Natime/Nmtime', $this->_string_shift($response, 8)); + break; + case NET_SFTP_ATTR_EXTENDED: // 0x80000000 + extract(unpack('Ncount', $this->_string_shift($response, 4))); + for ($i = 0; $i < $count; $i++) { + extract(unpack('Nlength', $this->_string_shift($response, 4))); + $key = $this->_string_shift($response, $length); + extract(unpack('Nlength', $this->_string_shift($response, 4))); + $attr[$key] = $this->_string_shift($response, $length); + } + } + } + return $attr; + } + + /** + * Parse Longname + * + * SFTPv3 doesn't provide any easy way of identifying a file type. You could try to open + * a file as a directory and see if an error is returned or you could try to parse the + * SFTPv3-specific longname field of the SSH_FXP_NAME packet. That's what this function does. + * The result is returned using the + * {@link http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-5.2 SFTPv4 type constants}. + * + * If the longname is in an unrecognized format bool(false) is returned. + * + * @param String $longname + * @return Mixed + * @access private + */ + function _parseLongname($longname) + { + // http://en.wikipedia.org/wiki/Unix_file_types + // http://en.wikipedia.org/wiki/Filesystem_permissions#Notation_of_traditional_Unix_permissions + if (preg_match('#^[^/]([r-][w-][xstST-]){3}#', $longname)) { + switch ($longname[0]) { + case '-': + return NET_SFTP_TYPE_REGULAR; + case 'd': + return NET_SFTP_TYPE_DIRECTORY; + case 'l': + return NET_SFTP_TYPE_SYMLINK; + default: + return NET_SFTP_TYPE_SPECIAL; + } + } + + return false; + } + + /** + * Sends SFTP Packets + * + * See '6. General Packet Format' of draft-ietf-secsh-filexfer-13 for more info. + * + * @param Integer $type + * @param String $data + * @see Net_SFTP::_get_sftp_packet() + * @see Net_SSH2::_send_channel_packet() + * @return Boolean + * @access private + */ + function _send_sftp_packet($type, $data) + { + $packet = $this->request_id !== false ? + pack('NCNa*', strlen($data) + 5, $type, $this->request_id, $data) : + pack('NCa*', strlen($data) + 1, $type, $data); + + $start = strtok(microtime(), ' ') + strtok(''); // http://php.net/microtime#61838 + $result = $this->_send_channel_packet(NET_SFTP_CHANNEL, $packet); + $stop = strtok(microtime(), ' ') + strtok(''); + + if (defined('NET_SFTP_LOGGING')) { + $packet_type = '-> ' . $this->packet_types[$type] . + ' (' . round($stop - $start, 4) . 's)'; + if (NET_SFTP_LOGGING == NET_SFTP_LOG_REALTIME) { + echo "
\r\n" . $this->_format_log(array($data), array($packet_type)) . "\r\n
\r\n"; + flush(); + ob_flush(); + } else { + $this->packet_type_log[] = $packet_type; + if (NET_SFTP_LOGGING == NET_SFTP_LOG_COMPLEX) { + $this->packet_log[] = $data; + } + } + } + + return $result; + } + + /** + * Receives SFTP Packets + * + * See '6. General Packet Format' of draft-ietf-secsh-filexfer-13 for more info. + * + * Incidentally, the number of SSH_MSG_CHANNEL_DATA messages has no bearing on the number of SFTP packets present. + * There can be one SSH_MSG_CHANNEL_DATA messages containing two SFTP packets or there can be two SSH_MSG_CHANNEL_DATA + * messages containing one SFTP packet. + * + * @see Net_SFTP::_send_sftp_packet() + * @return String + * @access private + */ + function _get_sftp_packet() + { + $this->curTimeout = false; + + $start = strtok(microtime(), ' ') + strtok(''); // http://php.net/microtime#61838 + + // SFTP packet length + while (strlen($this->packet_buffer) < 4) { + $temp = $this->_get_channel_packet(NET_SFTP_CHANNEL); + if (is_bool($temp)) { + $this->packet_type = false; + $this->packet_buffer = ''; + return false; + } + $this->packet_buffer.= $temp; + } + extract(unpack('Nlength', $this->_string_shift($this->packet_buffer, 4))); + $tempLength = $length; + $tempLength-= strlen($this->packet_buffer); + + // SFTP packet type and data payload + while ($tempLength > 0) { + $temp = $this->_get_channel_packet(NET_SFTP_CHANNEL); + if (is_bool($temp)) { + $this->packet_type = false; + $this->packet_buffer = ''; + return false; + } + $this->packet_buffer.= $temp; + $tempLength-= strlen($temp); + } + + $stop = strtok(microtime(), ' ') + strtok(''); + + $this->packet_type = ord($this->_string_shift($this->packet_buffer)); + + if ($this->request_id !== false) { + $this->_string_shift($this->packet_buffer, 4); // remove the request id + $length-= 5; // account for the request id and the packet type + } else { + $length-= 1; // account for the packet type + } + + $packet = $this->_string_shift($this->packet_buffer, $length); + + if (defined('NET_SFTP_LOGGING')) { + $packet_type = '<- ' . $this->packet_types[$this->packet_type] . + ' (' . round($stop - $start, 4) . 's)'; + if (NET_SFTP_LOGGING == NET_SFTP_LOG_REALTIME) { + echo "
\r\n" . $this->_format_log(array($packet), array($packet_type)) . "\r\n
\r\n"; + flush(); + ob_flush(); + } else { + $this->packet_type_log[] = $packet_type; + if (NET_SFTP_LOGGING == NET_SFTP_LOG_COMPLEX) { + $this->packet_log[] = $packet; + } + } + } + + return $packet; + } + + /** + * Returns a log of the packets that have been sent and received. + * + * Returns a string if NET_SFTP_LOGGING == NET_SFTP_LOG_COMPLEX, an array if NET_SFTP_LOGGING == NET_SFTP_LOG_SIMPLE and false if !defined('NET_SFTP_LOGGING') + * + * @access public + * @return String or Array + */ + function getSFTPLog() + { + if (!defined('NET_SFTP_LOGGING')) { + return false; + } + + switch (NET_SFTP_LOGGING) { + case NET_SFTP_LOG_COMPLEX: + return $this->_format_log($this->packet_log, $this->packet_type_log); + break; + //case NET_SFTP_LOG_SIMPLE: + default: + return $this->packet_type_log; + } + } + + /** + * Returns all errors + * + * @return String + * @access public + */ + function getSFTPErrors() + { + return $this->sftp_errors; + } + + /** + * Returns the last error + * + * @return String + * @access public + */ + function getLastSFTPError() + { + return count($this->sftp_errors) ? $this->sftp_errors[count($this->sftp_errors) - 1] : ''; + } + + /** + * Get supported SFTP versions + * + * @return Array + * @access public + */ + function getSupportedVersions() + { + $temp = array('version' => $this->version); + if (isset($this->extensions['versions'])) { + $temp['extensions'] = $this->extensions['versions']; + } + return $temp; + } + + /** + * Disconnect + * + * @param Integer $reason + * @return Boolean + * @access private + */ + function _disconnect($reason) + { + $this->pwd = false; + parent::_disconnect($reason); + } +} \ No newline at end of file diff --git a/3rdparty/phpseclib/Net/SSH1.php b/3rdparty/phpseclib/Net/SSH1.php new file mode 100644 index 00000000000..8fd509b2009 --- /dev/null +++ b/3rdparty/phpseclib/Net/SSH1.php @@ -0,0 +1,1421 @@ + + * login('username', 'password')) { + * exit('Login Failed'); + * } + * + * echo $ssh->exec('ls -la'); + * ?> + * + * + * Here's another short example: + * + * login('username', 'password')) { + * exit('Login Failed'); + * } + * + * echo $ssh->read('username@username:~$'); + * $ssh->write("ls -la\n"); + * echo $ssh->read('username@username:~$'); + * ?> + * + * + * More information on the SSHv1 specification can be found by reading + * {@link http://www.snailbook.com/docs/protocol-1.5.txt protocol-1.5.txt}. + * + * LICENSE: Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @category Net + * @package Net_SSH1 + * @author Jim Wigginton + * @copyright MMVII Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @version $Id: SSH1.php,v 1.15 2010/03/22 22:01:38 terrafrost Exp $ + * @link http://phpseclib.sourceforge.net + */ + +/** + * Include Math_BigInteger + * + * Used to do RSA encryption. + */ +if (!class_exists('Math_BigInteger')) { + require_once('Math/BigInteger.php'); +} + +/** + * Include Crypt_Null + */ +//require_once('Crypt/Null.php'); + +/** + * Include Crypt_DES + */ +if (!class_exists('Crypt_DES')) { + require_once('Crypt/DES.php'); +} + +/** + * Include Crypt_TripleDES + */ +if (!class_exists('Crypt_TripleDES')) { + require_once('Crypt/TripleDES.php'); +} + +/** + * Include Crypt_RC4 + */ +if (!class_exists('Crypt_RC4')) { + require_once('Crypt/RC4.php'); +} + +/** + * Include Crypt_Random + */ +// the class_exists() will only be called if the crypt_random function hasn't been defined and +// will trigger a call to __autoload() if you're wanting to auto-load classes +// call function_exists() a second time to stop the require_once from being called outside +// of the auto loader +if (!function_exists('crypt_random') && !class_exists('Crypt_Random') && !function_exists('crypt_random')) { + require_once('Crypt/Random.php'); +} + +/**#@+ + * Encryption Methods + * + * @see Net_SSH1::getSupportedCiphers() + * @access public + */ +/** + * No encryption + * + * Not supported. + */ +define('NET_SSH1_CIPHER_NONE', 0); +/** + * IDEA in CFB mode + * + * Not supported. + */ +define('NET_SSH1_CIPHER_IDEA', 1); +/** + * DES in CBC mode + */ +define('NET_SSH1_CIPHER_DES', 2); +/** + * Triple-DES in CBC mode + * + * All implementations are required to support this + */ +define('NET_SSH1_CIPHER_3DES', 3); +/** + * TRI's Simple Stream encryption CBC + * + * Not supported nor is it defined in the official SSH1 specs. OpenSSH, however, does define it (see cipher.h), + * although it doesn't use it (see cipher.c) + */ +define('NET_SSH1_CIPHER_BROKEN_TSS', 4); +/** + * RC4 + * + * Not supported. + * + * @internal According to the SSH1 specs: + * + * "The first 16 bytes of the session key are used as the key for + * the server to client direction. The remaining 16 bytes are used + * as the key for the client to server direction. This gives + * independent 128-bit keys for each direction." + * + * This library currently only supports encryption when the same key is being used for both directions. This is + * because there's only one $crypto object. Two could be added ($encrypt and $decrypt, perhaps). + */ +define('NET_SSH1_CIPHER_RC4', 5); +/** + * Blowfish + * + * Not supported nor is it defined in the official SSH1 specs. OpenSSH, however, defines it (see cipher.h) and + * uses it (see cipher.c) + */ +define('NET_SSH1_CIPHER_BLOWFISH', 6); +/**#@-*/ + +/**#@+ + * Authentication Methods + * + * @see Net_SSH1::getSupportedAuthentications() + * @access public + */ +/** + * .rhosts or /etc/hosts.equiv + */ +define('NET_SSH1_AUTH_RHOSTS', 1); +/** + * pure RSA authentication + */ +define('NET_SSH1_AUTH_RSA', 2); +/** + * password authentication + * + * This is the only method that is supported by this library. + */ +define('NET_SSH1_AUTH_PASSWORD', 3); +/** + * .rhosts with RSA host authentication + */ +define('NET_SSH1_AUTH_RHOSTS_RSA', 4); +/**#@-*/ + +/**#@+ + * Terminal Modes + * + * @link http://3sp.com/content/developer/maverick-net/docs/Maverick.SSH.PseudoTerminalModesMembers.html + * @access private + */ +define('NET_SSH1_TTY_OP_END', 0); +/**#@-*/ + +/** + * The Response Type + * + * @see Net_SSH1::_get_binary_packet() + * @access private + */ +define('NET_SSH1_RESPONSE_TYPE', 1); + +/** + * The Response Data + * + * @see Net_SSH1::_get_binary_packet() + * @access private + */ +define('NET_SSH1_RESPONSE_DATA', 2); + +/**#@+ + * Execution Bitmap Masks + * + * @see Net_SSH1::bitmap + * @access private + */ +define('NET_SSH1_MASK_CONSTRUCTOR', 0x00000001); +define('NET_SSH1_MASK_LOGIN', 0x00000002); +define('NET_SSH1_MASK_SHELL', 0x00000004); +/**#@-*/ + +/**#@+ + * @access public + * @see Net_SSH1::getLog() + */ +/** + * Returns the message numbers + */ +define('NET_SSH1_LOG_SIMPLE', 1); +/** + * Returns the message content + */ +define('NET_SSH1_LOG_COMPLEX', 2); +/**#@-*/ + +/**#@+ + * @access public + * @see Net_SSH1::read() + */ +/** + * Returns when a string matching $expect exactly is found + */ +define('NET_SSH1_READ_SIMPLE', 1); +/** + * Returns when a string matching the regular expression $expect is found + */ +define('NET_SSH1_READ_REGEX', 2); +/**#@-*/ + +/** + * Pure-PHP implementation of SSHv1. + * + * @author Jim Wigginton + * @version 0.1.0 + * @access public + * @package Net_SSH1 + */ +class Net_SSH1 { + /** + * The SSH identifier + * + * @var String + * @access private + */ + var $identifier = 'SSH-1.5-phpseclib'; + + /** + * The Socket Object + * + * @var Object + * @access private + */ + var $fsock; + + /** + * The cryptography object + * + * @var Object + * @access private + */ + var $crypto = false; + + /** + * Execution Bitmap + * + * The bits that are set represent functions that have been called already. This is used to determine + * if a requisite function has been successfully executed. If not, an error should be thrown. + * + * @var Integer + * @access private + */ + var $bitmap = 0; + + /** + * The Server Key Public Exponent + * + * Logged for debug purposes + * + * @see Net_SSH1::getServerKeyPublicExponent() + * @var String + * @access private + */ + var $server_key_public_exponent; + + /** + * The Server Key Public Modulus + * + * Logged for debug purposes + * + * @see Net_SSH1::getServerKeyPublicModulus() + * @var String + * @access private + */ + var $server_key_public_modulus; + + /** + * The Host Key Public Exponent + * + * Logged for debug purposes + * + * @see Net_SSH1::getHostKeyPublicExponent() + * @var String + * @access private + */ + var $host_key_public_exponent; + + /** + * The Host Key Public Modulus + * + * Logged for debug purposes + * + * @see Net_SSH1::getHostKeyPublicModulus() + * @var String + * @access private + */ + var $host_key_public_modulus; + + /** + * Supported Ciphers + * + * Logged for debug purposes + * + * @see Net_SSH1::getSupportedCiphers() + * @var Array + * @access private + */ + var $supported_ciphers = array( + NET_SSH1_CIPHER_NONE => 'No encryption', + NET_SSH1_CIPHER_IDEA => 'IDEA in CFB mode', + NET_SSH1_CIPHER_DES => 'DES in CBC mode', + NET_SSH1_CIPHER_3DES => 'Triple-DES in CBC mode', + NET_SSH1_CIPHER_BROKEN_TSS => 'TRI\'s Simple Stream encryption CBC', + NET_SSH1_CIPHER_RC4 => 'RC4', + NET_SSH1_CIPHER_BLOWFISH => 'Blowfish' + ); + + /** + * Supported Authentications + * + * Logged for debug purposes + * + * @see Net_SSH1::getSupportedAuthentications() + * @var Array + * @access private + */ + var $supported_authentications = array( + NET_SSH1_AUTH_RHOSTS => '.rhosts or /etc/hosts.equiv', + NET_SSH1_AUTH_RSA => 'pure RSA authentication', + NET_SSH1_AUTH_PASSWORD => 'password authentication', + NET_SSH1_AUTH_RHOSTS_RSA => '.rhosts with RSA host authentication' + ); + + /** + * Server Identification + * + * @see Net_SSH1::getServerIdentification() + * @var String + * @access private + */ + var $server_identification = ''; + + /** + * Protocol Flags + * + * @see Net_SSH1::Net_SSH1() + * @var Array + * @access private + */ + var $protocol_flags = array(); + + /** + * Protocol Flag Log + * + * @see Net_SSH1::getLog() + * @var Array + * @access private + */ + var $protocol_flag_log = array(); + + /** + * Message Log + * + * @see Net_SSH1::getLog() + * @var Array + * @access private + */ + var $message_log = array(); + + /** + * Interactive Buffer + * + * @see Net_SSH1::read() + * @var Array + * @access private + */ + var $interactiveBuffer = ''; + + /** + * Default Constructor. + * + * Connects to an SSHv1 server + * + * @param String $host + * @param optional Integer $port + * @param optional Integer $timeout + * @param optional Integer $cipher + * @return Net_SSH1 + * @access public + */ + function Net_SSH1($host, $port = 22, $timeout = 10, $cipher = NET_SSH1_CIPHER_3DES) + { + $this->protocol_flags = array( + 1 => 'NET_SSH1_MSG_DISCONNECT', + 2 => 'NET_SSH1_SMSG_PUBLIC_KEY', + 3 => 'NET_SSH1_CMSG_SESSION_KEY', + 4 => 'NET_SSH1_CMSG_USER', + 9 => 'NET_SSH1_CMSG_AUTH_PASSWORD', + 10 => 'NET_SSH1_CMSG_REQUEST_PTY', + 12 => 'NET_SSH1_CMSG_EXEC_SHELL', + 13 => 'NET_SSH1_CMSG_EXEC_CMD', + 14 => 'NET_SSH1_SMSG_SUCCESS', + 15 => 'NET_SSH1_SMSG_FAILURE', + 16 => 'NET_SSH1_CMSG_STDIN_DATA', + 17 => 'NET_SSH1_SMSG_STDOUT_DATA', + 18 => 'NET_SSH1_SMSG_STDERR_DATA', + 19 => 'NET_SSH1_CMSG_EOF', + 20 => 'NET_SSH1_SMSG_EXITSTATUS', + 33 => 'NET_SSH1_CMSG_EXIT_CONFIRMATION' + ); + + $this->_define_array($this->protocol_flags); + + $this->fsock = @fsockopen($host, $port, $errno, $errstr, $timeout); + if (!$this->fsock) { + user_error(rtrim("Cannot connect to $host. Error $errno. $errstr"), E_USER_NOTICE); + return; + } + + $this->server_identification = $init_line = fgets($this->fsock, 255); + + if (defined('NET_SSH1_LOGGING')) { + $this->protocol_flags_log[] = '<-'; + $this->protocol_flags_log[] = '->'; + + if (NET_SSH1_LOGGING == NET_SSH1_LOG_COMPLEX) { + $this->message_log[] = $this->server_identification; + $this->message_log[] = $this->identifier . "\r\n"; + } + } + + if (!preg_match('#SSH-([0-9\.]+)-(.+)#', $init_line, $parts)) { + user_error('Can only connect to SSH servers', E_USER_NOTICE); + return; + } + if ($parts[1][0] != 1) { + user_error("Cannot connect to SSH $parts[1] servers", E_USER_NOTICE); + return; + } + + fputs($this->fsock, $this->identifier."\r\n"); + + $response = $this->_get_binary_packet(); + if ($response[NET_SSH1_RESPONSE_TYPE] != NET_SSH1_SMSG_PUBLIC_KEY) { + user_error('Expected SSH_SMSG_PUBLIC_KEY', E_USER_NOTICE); + return; + } + + $anti_spoofing_cookie = $this->_string_shift($response[NET_SSH1_RESPONSE_DATA], 8); + + $this->_string_shift($response[NET_SSH1_RESPONSE_DATA], 4); + + $temp = unpack('nlen', $this->_string_shift($response[NET_SSH1_RESPONSE_DATA], 2)); + $server_key_public_exponent = new Math_BigInteger($this->_string_shift($response[NET_SSH1_RESPONSE_DATA], ceil($temp['len'] / 8)), 256); + $this->server_key_public_exponent = $server_key_public_exponent; + + $temp = unpack('nlen', $this->_string_shift($response[NET_SSH1_RESPONSE_DATA], 2)); + $server_key_public_modulus = new Math_BigInteger($this->_string_shift($response[NET_SSH1_RESPONSE_DATA], ceil($temp['len'] / 8)), 256); + $this->server_key_public_modulus = $server_key_public_modulus; + + $this->_string_shift($response[NET_SSH1_RESPONSE_DATA], 4); + + $temp = unpack('nlen', $this->_string_shift($response[NET_SSH1_RESPONSE_DATA], 2)); + $host_key_public_exponent = new Math_BigInteger($this->_string_shift($response[NET_SSH1_RESPONSE_DATA], ceil($temp['len'] / 8)), 256); + $this->host_key_public_exponent = $host_key_public_exponent; + + $temp = unpack('nlen', $this->_string_shift($response[NET_SSH1_RESPONSE_DATA], 2)); + $host_key_public_modulus = new Math_BigInteger($this->_string_shift($response[NET_SSH1_RESPONSE_DATA], ceil($temp['len'] / 8)), 256); + $this->host_key_public_modulus = $host_key_public_modulus; + + $this->_string_shift($response[NET_SSH1_RESPONSE_DATA], 4); + + // get a list of the supported ciphers + extract(unpack('Nsupported_ciphers_mask', $this->_string_shift($response[NET_SSH1_RESPONSE_DATA], 4))); + foreach ($this->supported_ciphers as $mask=>$name) { + if (($supported_ciphers_mask & (1 << $mask)) == 0) { + unset($this->supported_ciphers[$mask]); + } + } + + // get a list of the supported authentications + extract(unpack('Nsupported_authentications_mask', $this->_string_shift($response[NET_SSH1_RESPONSE_DATA], 4))); + foreach ($this->supported_authentications as $mask=>$name) { + if (($supported_authentications_mask & (1 << $mask)) == 0) { + unset($this->supported_authentications[$mask]); + } + } + + $session_id = pack('H*', md5($host_key_public_modulus->toBytes() . $server_key_public_modulus->toBytes() . $anti_spoofing_cookie)); + + $session_key = ''; + for ($i = 0; $i < 32; $i++) { + $session_key.= chr(crypt_random(0, 255)); + } + $double_encrypted_session_key = $session_key ^ str_pad($session_id, 32, chr(0)); + + if ($server_key_public_modulus->compare($host_key_public_modulus) < 0) { + $double_encrypted_session_key = $this->_rsa_crypt( + $double_encrypted_session_key, + array( + $server_key_public_exponent, + $server_key_public_modulus + ) + ); + $double_encrypted_session_key = $this->_rsa_crypt( + $double_encrypted_session_key, + array( + $host_key_public_exponent, + $host_key_public_modulus + ) + ); + } else { + $double_encrypted_session_key = $this->_rsa_crypt( + $double_encrypted_session_key, + array( + $host_key_public_exponent, + $host_key_public_modulus + ) + ); + $double_encrypted_session_key = $this->_rsa_crypt( + $double_encrypted_session_key, + array( + $server_key_public_exponent, + $server_key_public_modulus + ) + ); + } + + $cipher = isset($this->supported_ciphers[$cipher]) ? $cipher : NET_SSH1_CIPHER_3DES; + $data = pack('C2a*na*N', NET_SSH1_CMSG_SESSION_KEY, $cipher, $anti_spoofing_cookie, 8 * strlen($double_encrypted_session_key), $double_encrypted_session_key, 0); + + if (!$this->_send_binary_packet($data)) { + user_error('Error sending SSH_CMSG_SESSION_KEY', E_USER_NOTICE); + return; + } + + switch ($cipher) { + //case NET_SSH1_CIPHER_NONE: + // $this->crypto = new Crypt_Null(); + // break; + case NET_SSH1_CIPHER_DES: + $this->crypto = new Crypt_DES(); + $this->crypto->disablePadding(); + $this->crypto->enableContinuousBuffer(); + $this->crypto->setKey(substr($session_key, 0, 8)); + break; + case NET_SSH1_CIPHER_3DES: + $this->crypto = new Crypt_TripleDES(CRYPT_DES_MODE_3CBC); + $this->crypto->disablePadding(); + $this->crypto->enableContinuousBuffer(); + $this->crypto->setKey(substr($session_key, 0, 24)); + break; + //case NET_SSH1_CIPHER_RC4: + // $this->crypto = new Crypt_RC4(); + // $this->crypto->enableContinuousBuffer(); + // $this->crypto->setKey(substr($session_key, 0, 16)); + // break; + } + + $response = $this->_get_binary_packet(); + + if ($response[NET_SSH1_RESPONSE_TYPE] != NET_SSH1_SMSG_SUCCESS) { + user_error('Expected SSH_SMSG_SUCCESS', E_USER_NOTICE); + return; + } + + $this->bitmap = NET_SSH1_MASK_CONSTRUCTOR; + } + + /** + * Login + * + * @param String $username + * @param optional String $password + * @return Boolean + * @access public + */ + function login($username, $password = '') + { + if (!($this->bitmap & NET_SSH1_MASK_CONSTRUCTOR)) { + return false; + } + + $data = pack('CNa*', NET_SSH1_CMSG_USER, strlen($username), $username); + + if (!$this->_send_binary_packet($data)) { + user_error('Error sending SSH_CMSG_USER', E_USER_NOTICE); + return false; + } + + $response = $this->_get_binary_packet(); + + if ($response[NET_SSH1_RESPONSE_TYPE] == NET_SSH1_SMSG_SUCCESS) { + $this->bitmap |= NET_SSH1_MASK_LOGIN; + return true; + } else if ($response[NET_SSH1_RESPONSE_TYPE] != NET_SSH1_SMSG_FAILURE) { + user_error('Expected SSH_SMSG_SUCCESS or SSH_SMSG_FAILURE', E_USER_NOTICE); + return false; + } + + $data = pack('CNa*', NET_SSH1_CMSG_AUTH_PASSWORD, strlen($password), $password); + + if (!$this->_send_binary_packet($data)) { + user_error('Error sending SSH_CMSG_AUTH_PASSWORD', E_USER_NOTICE); + return false; + } + + // remove the username and password from the last logged packet + if (defined('NET_SSH1_LOGGING') && NET_SSH1_LOGGING == NET_SSH1_LOG_COMPLEX) { + $data = pack('CNa*', NET_SSH1_CMSG_AUTH_PASSWORD, strlen('password'), 'password'); + $this->message_log[count($this->message_log) - 1] = $data; // zzzzz + } + + $response = $this->_get_binary_packet(); + + if ($response[NET_SSH1_RESPONSE_TYPE] == NET_SSH1_SMSG_SUCCESS) { + $this->bitmap |= NET_SSH1_MASK_LOGIN; + return true; + } else if ($response[NET_SSH1_RESPONSE_TYPE] == NET_SSH1_SMSG_FAILURE) { + return false; + } else { + user_error('Expected SSH_SMSG_SUCCESS or SSH_SMSG_FAILURE', E_USER_NOTICE); + return false; + } + } + + /** + * Executes a command on a non-interactive shell, returns the output, and quits. + * + * An SSH1 server will close the connection after a command has been executed on a non-interactive shell. SSH2 + * servers don't, however, this isn't an SSH2 client. The way this works, on the server, is by initiating a + * shell with the -s option, as discussed in the following links: + * + * {@link http://www.faqs.org/docs/bashman/bashref_65.html http://www.faqs.org/docs/bashman/bashref_65.html} + * {@link http://www.faqs.org/docs/bashman/bashref_62.html http://www.faqs.org/docs/bashman/bashref_62.html} + * + * To execute further commands, a new Net_SSH1 object will need to be created. + * + * Returns false on failure and the output, otherwise. + * + * @see Net_SSH1::interactiveRead() + * @see Net_SSH1::interactiveWrite() + * @param String $cmd + * @return mixed + * @access public + */ + function exec($cmd, $block = true) + { + if (!($this->bitmap & NET_SSH1_MASK_LOGIN)) { + user_error('Operation disallowed prior to login()', E_USER_NOTICE); + return false; + } + + $data = pack('CNa*', NET_SSH1_CMSG_EXEC_CMD, strlen($cmd), $cmd); + + if (!$this->_send_binary_packet($data)) { + user_error('Error sending SSH_CMSG_EXEC_CMD', E_USER_NOTICE); + return false; + } + + if (!$block) { + return true; + } + + $output = ''; + $response = $this->_get_binary_packet(); + + do { + $output.= substr($response[NET_SSH1_RESPONSE_DATA], 4); + $response = $this->_get_binary_packet(); + } while ($response[NET_SSH1_RESPONSE_TYPE] != NET_SSH1_SMSG_EXITSTATUS); + + $data = pack('C', NET_SSH1_CMSG_EXIT_CONFIRMATION); + + // i don't think it's really all that important if this packet gets sent or not. + $this->_send_binary_packet($data); + + fclose($this->fsock); + + // reset the execution bitmap - a new Net_SSH1 object needs to be created. + $this->bitmap = 0; + + return $output; + } + + /** + * Creates an interactive shell + * + * @see Net_SSH1::interactiveRead() + * @see Net_SSH1::interactiveWrite() + * @return Boolean + * @access private + */ + function _initShell() + { + // connect using the sample parameters in protocol-1.5.txt. + // according to wikipedia.org's entry on text terminals, "the fundamental type of application running on a text + // terminal is a command line interpreter or shell". thus, opening a terminal session to run the shell. + $data = pack('CNa*N4C', NET_SSH1_CMSG_REQUEST_PTY, strlen('vt100'), 'vt100', 24, 80, 0, 0, NET_SSH1_TTY_OP_END); + + if (!$this->_send_binary_packet($data)) { + user_error('Error sending SSH_CMSG_REQUEST_PTY', E_USER_NOTICE); + return false; + } + + $response = $this->_get_binary_packet(); + + if ($response[NET_SSH1_RESPONSE_TYPE] != NET_SSH1_SMSG_SUCCESS) { + user_error('Expected SSH_SMSG_SUCCESS', E_USER_NOTICE); + return false; + } + + $data = pack('C', NET_SSH1_CMSG_EXEC_SHELL); + + if (!$this->_send_binary_packet($data)) { + user_error('Error sending SSH_CMSG_EXEC_SHELL', E_USER_NOTICE); + return false; + } + + $this->bitmap |= NET_SSH1_MASK_SHELL; + + //stream_set_blocking($this->fsock, 0); + + return true; + } + + /** + * Inputs a command into an interactive shell. + * + * @see Net_SSH1::interactiveWrite() + * @param String $cmd + * @return Boolean + * @access public + */ + function write($cmd) + { + return $this->interactiveWrite($cmd); + } + + /** + * Returns the output of an interactive shell when there's a match for $expect + * + * $expect can take the form of a string literal or, if $mode == NET_SSH1_READ_REGEX, + * a regular expression. + * + * @see Net_SSH1::write() + * @param String $expect + * @param Integer $mode + * @return Boolean + * @access public + */ + function read($expect, $mode = NET_SSH1_READ_SIMPLE) + { + if (!($this->bitmap & NET_SSH1_MASK_LOGIN)) { + user_error('Operation disallowed prior to login()', E_USER_NOTICE); + return false; + } + + if (!($this->bitmap & NET_SSH1_MASK_SHELL) && !$this->_initShell()) { + user_error('Unable to initiate an interactive shell session', E_USER_NOTICE); + return false; + } + + $match = $expect; + while (true) { + if ($mode == NET_SSH1_READ_REGEX) { + preg_match($expect, $this->interactiveBuffer, $matches); + $match = $matches[0]; + } + $pos = strpos($this->interactiveBuffer, $match); + if ($pos !== false) { + return $this->_string_shift($this->interactiveBuffer, $pos + strlen($match)); + } + $response = $this->_get_binary_packet(); + $this->interactiveBuffer.= substr($response[NET_SSH1_RESPONSE_DATA], 4); + } + } + + /** + * Inputs a command into an interactive shell. + * + * @see Net_SSH1::interactiveRead() + * @param String $cmd + * @return Boolean + * @access public + */ + function interactiveWrite($cmd) + { + if (!($this->bitmap & NET_SSH1_MASK_LOGIN)) { + user_error('Operation disallowed prior to login()', E_USER_NOTICE); + return false; + } + + if (!($this->bitmap & NET_SSH1_MASK_SHELL) && !$this->_initShell()) { + user_error('Unable to initiate an interactive shell session', E_USER_NOTICE); + return false; + } + + $data = pack('CNa*', NET_SSH1_CMSG_STDIN_DATA, strlen($cmd), $cmd); + + if (!$this->_send_binary_packet($data)) { + user_error('Error sending SSH_CMSG_STDIN', E_USER_NOTICE); + return false; + } + + return true; + } + + /** + * Returns the output of an interactive shell when no more output is available. + * + * Requires PHP 4.3.0 or later due to the use of the stream_select() function. If you see stuff like + * "", you're seeing ANSI escape codes. According to + * {@link http://support.microsoft.com/kb/101875 How to Enable ANSI.SYS in a Command Window}, "Windows NT + * does not support ANSI escape sequences in Win32 Console applications", so if you're a Windows user, + * there's not going to be much recourse. + * + * @see Net_SSH1::interactiveRead() + * @return String + * @access public + */ + function interactiveRead() + { + if (!($this->bitmap & NET_SSH1_MASK_LOGIN)) { + user_error('Operation disallowed prior to login()', E_USER_NOTICE); + return false; + } + + if (!($this->bitmap & NET_SSH1_MASK_SHELL) && !$this->_initShell()) { + user_error('Unable to initiate an interactive shell session', E_USER_NOTICE); + return false; + } + + $read = array($this->fsock); + $write = $except = null; + if (stream_select($read, $write, $except, 0)) { + $response = $this->_get_binary_packet(); + return substr($response[NET_SSH1_RESPONSE_DATA], 4); + } else { + return ''; + } + } + + /** + * Disconnect + * + * @access public + */ + function disconnect() + { + $this->_disconnect(); + } + + /** + * Destructor. + * + * Will be called, automatically, if you're supporting just PHP5. If you're supporting PHP4, you'll need to call + * disconnect(). + * + * @access public + */ + function __destruct() + { + $this->_disconnect(); + } + + /** + * Disconnect + * + * @param String $msg + * @access private + */ + function _disconnect($msg = 'Client Quit') + { + if ($this->bitmap) { + $data = pack('C', NET_SSH1_CMSG_EOF); + $this->_send_binary_packet($data); + + $response = $this->_get_binary_packet(); + switch ($response[NET_SSH1_RESPONSE_TYPE]) { + case NET_SSH1_SMSG_EXITSTATUS: + $data = pack('C', NET_SSH1_CMSG_EXIT_CONFIRMATION); + break; + default: + $data = pack('CNa*', NET_SSH1_MSG_DISCONNECT, strlen($msg), $msg); + } + + $this->_send_binary_packet($data); + fclose($this->fsock); + $this->bitmap = 0; + } + } + + /** + * Gets Binary Packets + * + * See 'The Binary Packet Protocol' of protocol-1.5.txt for more info. + * + * Also, this function could be improved upon by adding detection for the following exploit: + * http://www.securiteam.com/securitynews/5LP042K3FY.html + * + * @see Net_SSH1::_send_binary_packet() + * @return Array + * @access private + */ + function _get_binary_packet() + { + if (feof($this->fsock)) { + //user_error('connection closed prematurely', E_USER_NOTICE); + return false; + } + + $temp = unpack('Nlength', fread($this->fsock, 4)); + + $padding_length = 8 - ($temp['length'] & 7); + $length = $temp['length'] + $padding_length; + + $start = strtok(microtime(), ' ') + strtok(''); // http://php.net/microtime#61838 + $raw = fread($this->fsock, $length); + $stop = strtok(microtime(), ' ') + strtok(''); + + if ($this->crypto !== false) { + $raw = $this->crypto->decrypt($raw); + } + + $padding = substr($raw, 0, $padding_length); + $type = $raw[$padding_length]; + $data = substr($raw, $padding_length + 1, -4); + + $temp = unpack('Ncrc', substr($raw, -4)); + + //if ( $temp['crc'] != $this->_crc($padding . $type . $data) ) { + // user_error('Bad CRC in packet from server', E_USER_NOTICE); + // return false; + //} + + $type = ord($type); + + if (defined('NET_SSH1_LOGGING')) { + $temp = isset($this->protocol_flags[$type]) ? $this->protocol_flags[$type] : 'UNKNOWN'; + $this->protocol_flags_log[] = '<- ' . $temp . + ' (' . round($stop - $start, 4) . 's)'; + if (NET_SSH1_LOGGING == NET_SSH1_LOG_COMPLEX) { + $this->message_log[] = $data; + } + } + + return array( + NET_SSH1_RESPONSE_TYPE => $type, + NET_SSH1_RESPONSE_DATA => $data + ); + } + + /** + * Sends Binary Packets + * + * Returns true on success, false on failure. + * + * @see Net_SSH1::_get_binary_packet() + * @param String $data + * @return Boolean + * @access private + */ + function _send_binary_packet($data) { + if (feof($this->fsock)) { + //user_error('connection closed prematurely', E_USER_NOTICE); + return false; + } + + if (defined('NET_SSH1_LOGGING')) { + $temp = isset($this->protocol_flags[ord($data[0])]) ? $this->protocol_flags[ord($data[0])] : 'UNKNOWN'; + $this->protocol_flags_log[] = '-> ' . $temp . + ' (' . round($stop - $start, 4) . 's)'; + if (NET_SSH1_LOGGING == NET_SSH1_LOG_COMPLEX) { + $this->message_log[] = substr($data, 1); + } + } + + $length = strlen($data) + 4; + + $padding_length = 8 - ($length & 7); + $padding = ''; + for ($i = 0; $i < $padding_length; $i++) { + $padding.= chr(crypt_random(0, 255)); + } + + $data = $padding . $data; + $data.= pack('N', $this->_crc($data)); + + if ($this->crypto !== false) { + $data = $this->crypto->encrypt($data); + } + + $packet = pack('Na*', $length, $data); + + $start = strtok(microtime(), ' ') + strtok(''); // http://php.net/microtime#61838 + $result = strlen($packet) == fputs($this->fsock, $packet); + $stop = strtok(microtime(), ' ') + strtok(''); + + return $result; + } + + /** + * Cyclic Redundancy Check (CRC) + * + * PHP's crc32 function is implemented slightly differently than the one that SSH v1 uses, so + * we've reimplemented it. A more detailed discussion of the differences can be found after + * $crc_lookup_table's initialization. + * + * @see Net_SSH1::_get_binary_packet() + * @see Net_SSH1::_send_binary_packet() + * @param String $data + * @return Integer + * @access private + */ + function _crc($data) + { + static $crc_lookup_table = array( + 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, + 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, + 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, + 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, + 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, + 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, + 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, + 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, + 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, + 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, + 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, + 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, + 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, + 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, + 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, + 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, + 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, + 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, + 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, + 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, + 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, + 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, + 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, + 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, + 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, + 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, + 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, + 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, + 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, + 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, + 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, + 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, + 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, + 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, + 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, + 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, + 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, + 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, + 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, + 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, + 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, + 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, + 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, + 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, + 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, + 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, + 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, + 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, + 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, + 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, + 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, + 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, + 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, + 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, + 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, + 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, + 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, + 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, + 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, + 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, + 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, + 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, + 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, + 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D + ); + + // For this function to yield the same output as PHP's crc32 function, $crc would have to be + // set to 0xFFFFFFFF, initially - not 0x00000000 as it currently is. + $crc = 0x00000000; + $length = strlen($data); + + for ($i=0;$i<$length;$i++) { + // We AND $crc >> 8 with 0x00FFFFFF because we want the eight newly added bits to all + // be zero. PHP, unfortunately, doesn't always do this. 0x80000000 >> 8, as an example, + // yields 0xFF800000 - not 0x00800000. The following link elaborates: + // http://www.php.net/manual/en/language.operators.bitwise.php#57281 + $crc = (($crc >> 8) & 0x00FFFFFF) ^ $crc_lookup_table[($crc & 0xFF) ^ ord($data[$i])]; + } + + // In addition to having to set $crc to 0xFFFFFFFF, initially, the return value must be XOR'd with + // 0xFFFFFFFF for this function to return the same thing that PHP's crc32 function would. + return $crc; + } + + /** + * String Shift + * + * Inspired by array_shift + * + * @param String $string + * @param optional Integer $index + * @return String + * @access private + */ + function _string_shift(&$string, $index = 1) + { + $substr = substr($string, 0, $index); + $string = substr($string, $index); + return $substr; + } + + /** + * RSA Encrypt + * + * Returns mod(pow($m, $e), $n), where $n should be the product of two (large) primes $p and $q and where $e + * should be a number with the property that gcd($e, ($p - 1) * ($q - 1)) == 1. Could just make anything that + * calls this call modexp, instead, but I think this makes things clearer, maybe... + * + * @see Net_SSH1::Net_SSH1() + * @param Math_BigInteger $m + * @param Array $key + * @return Math_BigInteger + * @access private + */ + function _rsa_crypt($m, $key) + { + /* + if (!class_exists('Crypt_RSA')) { + require_once('Crypt/RSA.php'); + } + + $rsa = new Crypt_RSA(); + $rsa->loadKey($key, CRYPT_RSA_PUBLIC_FORMAT_RAW); + $rsa->setEncryptionMode(CRYPT_RSA_ENCRYPTION_PKCS1); + return $rsa->encrypt($m); + */ + + // To quote from protocol-1.5.txt: + // The most significant byte (which is only partial as the value must be + // less than the public modulus, which is never a power of two) is zero. + // + // The next byte contains the value 2 (which stands for public-key + // encrypted data in the PKCS standard [PKCS#1]). Then, there are non- + // zero random bytes to fill any unused space, a zero byte, and the data + // to be encrypted in the least significant bytes, the last byte of the + // data in the least significant byte. + + // Presumably the part of PKCS#1 they're refering to is "Section 7.2.1 Encryption Operation", + // under "7.2 RSAES-PKCS1-v1.5" and "7 Encryption schemes" of the following URL: + // ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-1/pkcs-1v2-1.pdf + $temp = chr(0) . chr(2); + $modulus = $key[1]->toBytes(); + $length = strlen($modulus) - strlen($m) - 3; + for ($i = 0; $i < $length; $i++) { + $temp.= chr(crypt_random(1, 255)); + } + $temp.= chr(0) . $m; + + $m = new Math_BigInteger($temp, 256); + $m = $m->modPow($key[0], $key[1]); + + return $m->toBytes(); + } + + /** + * Define Array + * + * Takes any number of arrays whose indices are integers and whose values are strings and defines a bunch of + * named constants from it, using the value as the name of the constant and the index as the value of the constant. + * If any of the constants that would be defined already exists, none of the constants will be defined. + * + * @param Array $array + * @access private + */ + function _define_array() + { + $args = func_get_args(); + foreach ($args as $arg) { + foreach ($arg as $key=>$value) { + if (!defined($value)) { + define($value, $key); + } else { + break 2; + } + } + } + } + + /** + * Returns a log of the packets that have been sent and received. + * + * Returns a string if NET_SSH2_LOGGING == NET_SSH2_LOG_COMPLEX, an array if NET_SSH2_LOGGING == NET_SSH2_LOG_SIMPLE and false if !defined('NET_SSH2_LOGGING') + * + * @access public + * @return String or Array + */ + function getLog() + { + if (!defined('NET_SSH1_LOGGING')) { + return false; + } + + switch (NET_SSH1_LOGGING) { + case NET_SSH1_LOG_SIMPLE: + return $this->message_number_log; + break; + case NET_SSH1_LOG_COMPLEX: + return $this->_format_log($this->message_log, $this->protocol_flags_log); + break; + default: + return false; + } + } + + /** + * Formats a log for printing + * + * @param Array $message_log + * @param Array $message_number_log + * @access private + * @return String + */ + function _format_log($message_log, $message_number_log) + { + static $boundary = ':', $long_width = 65, $short_width = 16; + + $output = ''; + for ($i = 0; $i < count($message_log); $i++) { + $output.= $message_number_log[$i] . "\r\n"; + $current_log = $message_log[$i]; + $j = 0; + do { + if (!empty($current_log)) { + $output.= str_pad(dechex($j), 7, '0', STR_PAD_LEFT) . '0 '; + } + $fragment = $this->_string_shift($current_log, $short_width); + $hex = substr( + preg_replace( + '#(.)#es', + '"' . $boundary . '" . str_pad(dechex(ord(substr("\\1", -1))), 2, "0", STR_PAD_LEFT)', + $fragment), + strlen($boundary) + ); + // replace non ASCII printable characters with dots + // http://en.wikipedia.org/wiki/ASCII#ASCII_printable_characters + // also replace < with a . since < messes up the output on web browsers + $raw = preg_replace('#[^\x20-\x7E]|<#', '.', $fragment); + $output.= str_pad($hex, $long_width - $short_width, ' ') . $raw . "\r\n"; + $j++; + } while (!empty($current_log)); + $output.= "\r\n"; + } + + return $output; + } + + /** + * Return the server key public exponent + * + * Returns, by default, the base-10 representation. If $raw_output is set to true, returns, instead, + * the raw bytes. This behavior is similar to PHP's md5() function. + * + * @param optional Boolean $raw_output + * @return String + * @access public + */ + function getServerKeyPublicExponent($raw_output = false) + { + return $raw_output ? $this->server_key_public_exponent->toBytes() : $this->server_key_public_exponent->toString(); + } + + /** + * Return the server key public modulus + * + * Returns, by default, the base-10 representation. If $raw_output is set to true, returns, instead, + * the raw bytes. This behavior is similar to PHP's md5() function. + * + * @param optional Boolean $raw_output + * @return String + * @access public + */ + function getServerKeyPublicModulus($raw_output = false) + { + return $raw_output ? $this->server_key_public_modulus->toBytes() : $this->server_key_public_modulus->toString(); + } + + /** + * Return the host key public exponent + * + * Returns, by default, the base-10 representation. If $raw_output is set to true, returns, instead, + * the raw bytes. This behavior is similar to PHP's md5() function. + * + * @param optional Boolean $raw_output + * @return String + * @access public + */ + function getHostKeyPublicExponent($raw_output = false) + { + return $raw_output ? $this->host_key_public_exponent->toBytes() : $this->host_key_public_exponent->toString(); + } + + /** + * Return the host key public modulus + * + * Returns, by default, the base-10 representation. If $raw_output is set to true, returns, instead, + * the raw bytes. This behavior is similar to PHP's md5() function. + * + * @param optional Boolean $raw_output + * @return String + * @access public + */ + function getHostKeyPublicModulus($raw_output = false) + { + return $raw_output ? $this->host_key_public_modulus->toBytes() : $this->host_key_public_modulus->toString(); + } + + /** + * Return a list of ciphers supported by SSH1 server. + * + * Just because a cipher is supported by an SSH1 server doesn't mean it's supported by this library. If $raw_output + * is set to true, returns, instead, an array of constants. ie. instead of array('Triple-DES in CBC mode'), you'll + * get array(NET_SSH1_CIPHER_3DES). + * + * @param optional Boolean $raw_output + * @return Array + * @access public + */ + function getSupportedCiphers($raw_output = false) + { + return $raw_output ? array_keys($this->supported_ciphers) : array_values($this->supported_ciphers); + } + + /** + * Return a list of authentications supported by SSH1 server. + * + * Just because a cipher is supported by an SSH1 server doesn't mean it's supported by this library. If $raw_output + * is set to true, returns, instead, an array of constants. ie. instead of array('password authentication'), you'll + * get array(NET_SSH1_AUTH_PASSWORD). + * + * @param optional Boolean $raw_output + * @return Array + * @access public + */ + function getSupportedAuthentications($raw_output = false) + { + return $raw_output ? array_keys($this->supported_authentications) : array_values($this->supported_authentications); + } + + /** + * Return the server identification. + * + * @return String + * @access public + */ + function getServerIdentification() + { + return rtrim($this->server_identification); + } +} diff --git a/3rdparty/phpseclib/Net/SSH2.php b/3rdparty/phpseclib/Net/SSH2.php new file mode 100644 index 00000000000..2a79ce1b876 --- /dev/null +++ b/3rdparty/phpseclib/Net/SSH2.php @@ -0,0 +1,2945 @@ + + * login('username', 'password')) { + * exit('Login Failed'); + * } + * + * echo $ssh->exec('pwd'); + * echo $ssh->exec('ls -la'); + * ?> + * + * + * + * setPassword('whatever'); + * $key->loadKey(file_get_contents('privatekey')); + * + * $ssh = new Net_SSH2('www.domain.tld'); + * if (!$ssh->login('username', $key)) { + * exit('Login Failed'); + * } + * + * echo $ssh->read('username@username:~$'); + * $ssh->write("ls -la\n"); + * echo $ssh->read('username@username:~$'); + * ?> + * + * + * LICENSE: Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @category Net + * @package Net_SSH2 + * @author Jim Wigginton + * @copyright MMVII Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @version $Id: SSH2.php,v 1.53 2010-10-24 01:24:30 terrafrost Exp $ + * @link http://phpseclib.sourceforge.net + */ + +/** + * Include Math_BigInteger + * + * Used to do Diffie-Hellman key exchange and DSA/RSA signature verification. + */ +if (!class_exists('Math_BigInteger')) { + require_once('Math/BigInteger.php'); +} + +/** + * Include Crypt_Random + */ +// the class_exists() will only be called if the crypt_random function hasn't been defined and +// will trigger a call to __autoload() if you're wanting to auto-load classes +// call function_exists() a second time to stop the require_once from being called outside +// of the auto loader +if (!function_exists('crypt_random') && !class_exists('Crypt_Random') && !function_exists('crypt_random')) { + require_once('Crypt/Random.php'); +} + +/** + * Include Crypt_Hash + */ +if (!class_exists('Crypt_Hash')) { + require_once('Crypt/Hash.php'); +} + +/** + * Include Crypt_TripleDES + */ +if (!class_exists('Crypt_TripleDES')) { + require_once('Crypt/TripleDES.php'); +} + +/** + * Include Crypt_RC4 + */ +if (!class_exists('Crypt_RC4')) { + require_once('Crypt/RC4.php'); +} + +/** + * Include Crypt_AES + */ +if (!class_exists('Crypt_AES')) { + require_once('Crypt/AES.php'); +} + +/**#@+ + * Execution Bitmap Masks + * + * @see Net_SSH2::bitmap + * @access private + */ +define('NET_SSH2_MASK_CONSTRUCTOR', 0x00000001); +define('NET_SSH2_MASK_LOGIN', 0x00000002); +define('NET_SSH2_MASK_SHELL', 0x00000004); +/**#@-*/ + +/**#@+ + * Channel constants + * + * RFC4254 refers not to client and server channels but rather to sender and recipient channels. we don't refer + * to them in that way because RFC4254 toggles the meaning. the client sends a SSH_MSG_CHANNEL_OPEN message with + * a sender channel and the server sends a SSH_MSG_CHANNEL_OPEN_CONFIRMATION in response, with a sender and a + * recepient channel. at first glance, you might conclude that SSH_MSG_CHANNEL_OPEN_CONFIRMATION's sender channel + * would be the same thing as SSH_MSG_CHANNEL_OPEN's sender channel, but it's not, per this snipet: + * The 'recipient channel' is the channel number given in the original + * open request, and 'sender channel' is the channel number allocated by + * the other side. + * + * @see Net_SSH2::_send_channel_packet() + * @see Net_SSH2::_get_channel_packet() + * @access private + */ +define('NET_SSH2_CHANNEL_EXEC', 0); // PuTTy uses 0x100 +define('NET_SSH2_CHANNEL_SHELL',1); +/**#@-*/ + +/**#@+ + * @access public + * @see Net_SSH2::getLog() + */ +/** + * Returns the message numbers + */ +define('NET_SSH2_LOG_SIMPLE', 1); +/** + * Returns the message content + */ +define('NET_SSH2_LOG_COMPLEX', 2); +/** + * Outputs the content real-time + */ +define('NET_SSH2_LOG_REALTIME', 3); +/** + * Dumps the content real-time to a file + */ +define('NET_SSH2_LOG_REALTIME_FILE', 4); +/**#@-*/ + +/**#@+ + * @access public + * @see Net_SSH2::read() + */ +/** + * Returns when a string matching $expect exactly is found + */ +define('NET_SSH2_READ_SIMPLE', 1); +/** + * Returns when a string matching the regular expression $expect is found + */ +define('NET_SSH2_READ_REGEX', 2); +/** + * Make sure that the log never gets larger than this + */ +define('NET_SSH2_LOG_MAX_SIZE', 1024 * 1024); +/**#@-*/ + +/** + * Pure-PHP implementation of SSHv2. + * + * @author Jim Wigginton + * @version 0.1.0 + * @access public + * @package Net_SSH2 + */ +class Net_SSH2 { + /** + * The SSH identifier + * + * @var String + * @access private + */ + var $identifier = 'SSH-2.0-phpseclib_0.3'; + + /** + * The Socket Object + * + * @var Object + * @access private + */ + var $fsock; + + /** + * Execution Bitmap + * + * The bits that are set represent functions that have been called already. This is used to determine + * if a requisite function has been successfully executed. If not, an error should be thrown. + * + * @var Integer + * @access private + */ + var $bitmap = 0; + + /** + * Error information + * + * @see Net_SSH2::getErrors() + * @see Net_SSH2::getLastError() + * @var String + * @access private + */ + var $errors = array(); + + /** + * Server Identifier + * + * @see Net_SSH2::getServerIdentification() + * @var String + * @access private + */ + var $server_identifier = ''; + + /** + * Key Exchange Algorithms + * + * @see Net_SSH2::getKexAlgorithims() + * @var Array + * @access private + */ + var $kex_algorithms; + + /** + * Server Host Key Algorithms + * + * @see Net_SSH2::getServerHostKeyAlgorithms() + * @var Array + * @access private + */ + var $server_host_key_algorithms; + + /** + * Encryption Algorithms: Client to Server + * + * @see Net_SSH2::getEncryptionAlgorithmsClient2Server() + * @var Array + * @access private + */ + var $encryption_algorithms_client_to_server; + + /** + * Encryption Algorithms: Server to Client + * + * @see Net_SSH2::getEncryptionAlgorithmsServer2Client() + * @var Array + * @access private + */ + var $encryption_algorithms_server_to_client; + + /** + * MAC Algorithms: Client to Server + * + * @see Net_SSH2::getMACAlgorithmsClient2Server() + * @var Array + * @access private + */ + var $mac_algorithms_client_to_server; + + /** + * MAC Algorithms: Server to Client + * + * @see Net_SSH2::getMACAlgorithmsServer2Client() + * @var Array + * @access private + */ + var $mac_algorithms_server_to_client; + + /** + * Compression Algorithms: Client to Server + * + * @see Net_SSH2::getCompressionAlgorithmsClient2Server() + * @var Array + * @access private + */ + var $compression_algorithms_client_to_server; + + /** + * Compression Algorithms: Server to Client + * + * @see Net_SSH2::getCompressionAlgorithmsServer2Client() + * @var Array + * @access private + */ + var $compression_algorithms_server_to_client; + + /** + * Languages: Server to Client + * + * @see Net_SSH2::getLanguagesServer2Client() + * @var Array + * @access private + */ + var $languages_server_to_client; + + /** + * Languages: Client to Server + * + * @see Net_SSH2::getLanguagesClient2Server() + * @var Array + * @access private + */ + var $languages_client_to_server; + + /** + * Block Size for Server to Client Encryption + * + * "Note that the length of the concatenation of 'packet_length', + * 'padding_length', 'payload', and 'random padding' MUST be a multiple + * of the cipher block size or 8, whichever is larger. This constraint + * MUST be enforced, even when using stream ciphers." + * + * -- http://tools.ietf.org/html/rfc4253#section-6 + * + * @see Net_SSH2::Net_SSH2() + * @see Net_SSH2::_send_binary_packet() + * @var Integer + * @access private + */ + var $encrypt_block_size = 8; + + /** + * Block Size for Client to Server Encryption + * + * @see Net_SSH2::Net_SSH2() + * @see Net_SSH2::_get_binary_packet() + * @var Integer + * @access private + */ + var $decrypt_block_size = 8; + + /** + * Server to Client Encryption Object + * + * @see Net_SSH2::_get_binary_packet() + * @var Object + * @access private + */ + var $decrypt = false; + + /** + * Client to Server Encryption Object + * + * @see Net_SSH2::_send_binary_packet() + * @var Object + * @access private + */ + var $encrypt = false; + + /** + * Client to Server HMAC Object + * + * @see Net_SSH2::_send_binary_packet() + * @var Object + * @access private + */ + var $hmac_create = false; + + /** + * Server to Client HMAC Object + * + * @see Net_SSH2::_get_binary_packet() + * @var Object + * @access private + */ + var $hmac_check = false; + + /** + * Size of server to client HMAC + * + * We need to know how big the HMAC will be for the server to client direction so that we know how many bytes to read. + * For the client to server side, the HMAC object will make the HMAC as long as it needs to be. All we need to do is + * append it. + * + * @see Net_SSH2::_get_binary_packet() + * @var Integer + * @access private + */ + var $hmac_size = false; + + /** + * Server Public Host Key + * + * @see Net_SSH2::getServerPublicHostKey() + * @var String + * @access private + */ + var $server_public_host_key; + + /** + * Session identifer + * + * "The exchange hash H from the first key exchange is additionally + * used as the session identifier, which is a unique identifier for + * this connection." + * + * -- http://tools.ietf.org/html/rfc4253#section-7.2 + * + * @see Net_SSH2::_key_exchange() + * @var String + * @access private + */ + var $session_id = false; + + /** + * Exchange hash + * + * The current exchange hash + * + * @see Net_SSH2::_key_exchange() + * @var String + * @access private + */ + var $exchange_hash = false; + + /** + * Message Numbers + * + * @see Net_SSH2::Net_SSH2() + * @var Array + * @access private + */ + var $message_numbers = array(); + + /** + * Disconnection Message 'reason codes' defined in RFC4253 + * + * @see Net_SSH2::Net_SSH2() + * @var Array + * @access private + */ + var $disconnect_reasons = array(); + + /** + * SSH_MSG_CHANNEL_OPEN_FAILURE 'reason codes', defined in RFC4254 + * + * @see Net_SSH2::Net_SSH2() + * @var Array + * @access private + */ + var $channel_open_failure_reasons = array(); + + /** + * Terminal Modes + * + * @link http://tools.ietf.org/html/rfc4254#section-8 + * @see Net_SSH2::Net_SSH2() + * @var Array + * @access private + */ + var $terminal_modes = array(); + + /** + * SSH_MSG_CHANNEL_EXTENDED_DATA's data_type_codes + * + * @link http://tools.ietf.org/html/rfc4254#section-5.2 + * @see Net_SSH2::Net_SSH2() + * @var Array + * @access private + */ + var $channel_extended_data_type_codes = array(); + + /** + * Send Sequence Number + * + * See 'Section 6.4. Data Integrity' of rfc4253 for more info. + * + * @see Net_SSH2::_send_binary_packet() + * @var Integer + * @access private + */ + var $send_seq_no = 0; + + /** + * Get Sequence Number + * + * See 'Section 6.4. Data Integrity' of rfc4253 for more info. + * + * @see Net_SSH2::_get_binary_packet() + * @var Integer + * @access private + */ + var $get_seq_no = 0; + + /** + * Server Channels + * + * Maps client channels to server channels + * + * @see Net_SSH2::_get_channel_packet() + * @see Net_SSH2::exec() + * @var Array + * @access private + */ + var $server_channels = array(); + + /** + * Channel Buffers + * + * If a client requests a packet from one channel but receives two packets from another those packets should + * be placed in a buffer + * + * @see Net_SSH2::_get_channel_packet() + * @see Net_SSH2::exec() + * @var Array + * @access private + */ + var $channel_buffers = array(); + + /** + * Channel Status + * + * Contains the type of the last sent message + * + * @see Net_SSH2::_get_channel_packet() + * @var Array + * @access private + */ + var $channel_status = array(); + + /** + * Packet Size + * + * Maximum packet size indexed by channel + * + * @see Net_SSH2::_send_channel_packet() + * @var Array + * @access private + */ + var $packet_size_client_to_server = array(); + + /** + * Message Number Log + * + * @see Net_SSH2::getLog() + * @var Array + * @access private + */ + var $message_number_log = array(); + + /** + * Message Log + * + * @see Net_SSH2::getLog() + * @var Array + * @access private + */ + var $message_log = array(); + + /** + * The Window Size + * + * Bytes the other party can send before it must wait for the window to be adjusted (0x7FFFFFFF = 4GB) + * + * @var Integer + * @see Net_SSH2::_send_channel_packet() + * @see Net_SSH2::exec() + * @access private + */ + var $window_size = 0x7FFFFFFF; + + /** + * Window size + * + * Window size indexed by channel + * + * @see Net_SSH2::_send_channel_packet() + * @var Array + * @access private + */ + var $window_size_client_to_server = array(); + + /** + * Server signature + * + * Verified against $this->session_id + * + * @see Net_SSH2::getServerPublicHostKey() + * @var String + * @access private + */ + var $signature = ''; + + /** + * Server signature format + * + * ssh-rsa or ssh-dss. + * + * @see Net_SSH2::getServerPublicHostKey() + * @var String + * @access private + */ + var $signature_format = ''; + + /** + * Interactive Buffer + * + * @see Net_SSH2::read() + * @var Array + * @access private + */ + var $interactiveBuffer = ''; + + /** + * Current log size + * + * Should never exceed NET_SSH2_LOG_MAX_SIZE + * + * @see Net_SSH2::_send_binary_packet() + * @see Net_SSH2::_get_binary_packet() + * @var Integer + * @access private + */ + var $log_size; + + /** + * Timeout + * + * @see Net_SSH2::setTimeout() + * @access private + */ + var $timeout; + + /** + * Current Timeout + * + * @see Net_SSH2::_get_channel_packet() + * @access private + */ + var $curTimeout; + + /** + * Real-time log file pointer + * + * @see Net_SSH2::_append_log() + * @access private + */ + var $realtime_log_file; + + /** + * Real-time log file size + * + * @see Net_SSH2::_append_log() + * @access private + */ + var $realtime_log_size; + + /** + * Has the signature been validated? + * + * @see Net_SSH2::getServerPublicHostKey() + * @access private + */ + var $signature_validated = false; + + /** + * Real-time log file wrap boolean + * + * @see Net_SSH2::_append_log() + * @access private + */ + var $realtime_log_wrap; + + /** + * Flag to suppress stderr from output + * + * @see Net_SSH2::enableQuietMode() + * @access private + */ + var $quiet_mode = false; + + /** + * Default Constructor. + * + * Connects to an SSHv2 server + * + * @param String $host + * @param optional Integer $port + * @param optional Integer $timeout + * @return Net_SSH2 + * @access public + */ + function Net_SSH2($host, $port = 22, $timeout = 10) + { + $this->message_numbers = array( + 1 => 'NET_SSH2_MSG_DISCONNECT', + 2 => 'NET_SSH2_MSG_IGNORE', + 3 => 'NET_SSH2_MSG_UNIMPLEMENTED', + 4 => 'NET_SSH2_MSG_DEBUG', + 5 => 'NET_SSH2_MSG_SERVICE_REQUEST', + 6 => 'NET_SSH2_MSG_SERVICE_ACCEPT', + 20 => 'NET_SSH2_MSG_KEXINIT', + 21 => 'NET_SSH2_MSG_NEWKEYS', + 30 => 'NET_SSH2_MSG_KEXDH_INIT', + 31 => 'NET_SSH2_MSG_KEXDH_REPLY', + 50 => 'NET_SSH2_MSG_USERAUTH_REQUEST', + 51 => 'NET_SSH2_MSG_USERAUTH_FAILURE', + 52 => 'NET_SSH2_MSG_USERAUTH_SUCCESS', + 53 => 'NET_SSH2_MSG_USERAUTH_BANNER', + + 80 => 'NET_SSH2_MSG_GLOBAL_REQUEST', + 81 => 'NET_SSH2_MSG_REQUEST_SUCCESS', + 82 => 'NET_SSH2_MSG_REQUEST_FAILURE', + 90 => 'NET_SSH2_MSG_CHANNEL_OPEN', + 91 => 'NET_SSH2_MSG_CHANNEL_OPEN_CONFIRMATION', + 92 => 'NET_SSH2_MSG_CHANNEL_OPEN_FAILURE', + 93 => 'NET_SSH2_MSG_CHANNEL_WINDOW_ADJUST', + 94 => 'NET_SSH2_MSG_CHANNEL_DATA', + 95 => 'NET_SSH2_MSG_CHANNEL_EXTENDED_DATA', + 96 => 'NET_SSH2_MSG_CHANNEL_EOF', + 97 => 'NET_SSH2_MSG_CHANNEL_CLOSE', + 98 => 'NET_SSH2_MSG_CHANNEL_REQUEST', + 99 => 'NET_SSH2_MSG_CHANNEL_SUCCESS', + 100 => 'NET_SSH2_MSG_CHANNEL_FAILURE' + ); + $this->disconnect_reasons = array( + 1 => 'NET_SSH2_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT', + 2 => 'NET_SSH2_DISCONNECT_PROTOCOL_ERROR', + 3 => 'NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED', + 4 => 'NET_SSH2_DISCONNECT_RESERVED', + 5 => 'NET_SSH2_DISCONNECT_MAC_ERROR', + 6 => 'NET_SSH2_DISCONNECT_COMPRESSION_ERROR', + 7 => 'NET_SSH2_DISCONNECT_SERVICE_NOT_AVAILABLE', + 8 => 'NET_SSH2_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED', + 9 => 'NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE', + 10 => 'NET_SSH2_DISCONNECT_CONNECTION_LOST', + 11 => 'NET_SSH2_DISCONNECT_BY_APPLICATION', + 12 => 'NET_SSH2_DISCONNECT_TOO_MANY_CONNECTIONS', + 13 => 'NET_SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER', + 14 => 'NET_SSH2_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE', + 15 => 'NET_SSH2_DISCONNECT_ILLEGAL_USER_NAME' + ); + $this->channel_open_failure_reasons = array( + 1 => 'NET_SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED' + ); + $this->terminal_modes = array( + 0 => 'NET_SSH2_TTY_OP_END' + ); + $this->channel_extended_data_type_codes = array( + 1 => 'NET_SSH2_EXTENDED_DATA_STDERR' + ); + + $this->_define_array( + $this->message_numbers, + $this->disconnect_reasons, + $this->channel_open_failure_reasons, + $this->terminal_modes, + $this->channel_extended_data_type_codes, + array(60 => 'NET_SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ'), + array(60 => 'NET_SSH2_MSG_USERAUTH_PK_OK'), + array(60 => 'NET_SSH2_MSG_USERAUTH_INFO_REQUEST', + 61 => 'NET_SSH2_MSG_USERAUTH_INFO_RESPONSE') + ); + + $start = strtok(microtime(), ' ') + strtok(''); // http://php.net/microtime#61838 + $this->fsock = @fsockopen($host, $port, $errno, $errstr, $timeout); + if (!$this->fsock) { + user_error(rtrim("Cannot connect to $host. Error $errno. $errstr"), E_USER_NOTICE); + return; + } + $elapsed = strtok(microtime(), ' ') + strtok('') - $start; + + $timeout-= $elapsed; + + if ($timeout <= 0) { + user_error(rtrim("Cannot connect to $host. Timeout error"), E_USER_NOTICE); + return; + } + + $read = array($this->fsock); + $write = $except = NULL; + + $sec = floor($timeout); + $usec = 1000000 * ($timeout - $sec); + + // on windows this returns a "Warning: Invalid CRT parameters detected" error + // the !count() is done as a workaround for + if (!@stream_select($read, $write, $except, $sec, $usec) && !count($read)) { + user_error(rtrim("Cannot connect to $host. Banner timeout"), E_USER_NOTICE); + return; + } + + /* According to the SSH2 specs, + + "The server MAY send other lines of data before sending the version + string. Each line SHOULD be terminated by a Carriage Return and Line + Feed. Such lines MUST NOT begin with "SSH-", and SHOULD be encoded + in ISO-10646 UTF-8 [RFC3629] (language is not specified). Clients + MUST be able to process such lines." */ + $temp = ''; + $extra = ''; + while (!feof($this->fsock) && !preg_match('#^SSH-(\d\.\d+)#', $temp, $matches)) { + if (substr($temp, -2) == "\r\n") { + $extra.= $temp; + $temp = ''; + } + $temp.= fgets($this->fsock, 255); + } + + if (feof($this->fsock)) { + user_error('Connection closed by server', E_USER_NOTICE); + return false; + } + + $ext = array(); + if (extension_loaded('mcrypt')) { + $ext[] = 'mcrypt'; + } + if (extension_loaded('gmp')) { + $ext[] = 'gmp'; + } else if (extension_loaded('bcmath')) { + $ext[] = 'bcmath'; + } + + if (!empty($ext)) { + $this->identifier.= ' (' . implode(', ', $ext) . ')'; + } + + if (defined('NET_SSH2_LOGGING')) { + $this->message_number_log[] = '<-'; + $this->message_number_log[] = '->'; + + if (NET_SSH2_LOGGING == NET_SSH2_LOG_COMPLEX) { + $this->message_log[] = $extra . $temp; + $this->message_log[] = $this->identifier . "\r\n"; + } + } + + $this->server_identifier = trim($temp, "\r\n"); + if (!empty($extra)) { + $this->errors[] = utf8_decode($extra); + } + + if ($matches[1] != '1.99' && $matches[1] != '2.0') { + user_error("Cannot connect to SSH $matches[1] servers", E_USER_NOTICE); + return; + } + + fputs($this->fsock, $this->identifier . "\r\n"); + + $response = $this->_get_binary_packet(); + if ($response === false) { + user_error('Connection closed by server', E_USER_NOTICE); + return; + } + + if (ord($response[0]) != NET_SSH2_MSG_KEXINIT) { + user_error('Expected SSH_MSG_KEXINIT', E_USER_NOTICE); + return; + } + + if (!$this->_key_exchange($response)) { + return; + } + + $this->bitmap = NET_SSH2_MASK_CONSTRUCTOR; + } + + /** + * Key Exchange + * + * @param String $kexinit_payload_server + * @access private + */ + function _key_exchange($kexinit_payload_server) + { + static $kex_algorithms = array( + 'diffie-hellman-group1-sha1', // REQUIRED + 'diffie-hellman-group14-sha1' // REQUIRED + ); + + static $server_host_key_algorithms = array( + 'ssh-rsa', // RECOMMENDED sign Raw RSA Key + 'ssh-dss' // REQUIRED sign Raw DSS Key + ); + + static $encryption_algorithms = array( + // from : + 'arcfour256', + 'arcfour128', + + 'arcfour', // OPTIONAL the ARCFOUR stream cipher with a 128-bit key + + 'aes128-cbc', // RECOMMENDED AES with a 128-bit key + 'aes192-cbc', // OPTIONAL AES with a 192-bit key + 'aes256-cbc', // OPTIONAL AES in CBC mode, with a 256-bit key + + // from : + 'aes128-ctr', // RECOMMENDED AES (Rijndael) in SDCTR mode, with 128-bit key + 'aes192-ctr', // RECOMMENDED AES with 192-bit key + 'aes256-ctr', // RECOMMENDED AES with 256-bit key + '3des-ctr', // RECOMMENDED Three-key 3DES in SDCTR mode + + '3des-cbc', // REQUIRED three-key 3DES in CBC mode + 'none' // OPTIONAL no encryption; NOT RECOMMENDED + ); + + static $mac_algorithms = array( + 'hmac-sha1-96', // RECOMMENDED first 96 bits of HMAC-SHA1 (digest length = 12, key length = 20) + 'hmac-sha1', // REQUIRED HMAC-SHA1 (digest length = key length = 20) + 'hmac-md5-96', // OPTIONAL first 96 bits of HMAC-MD5 (digest length = 12, key length = 16) + 'hmac-md5', // OPTIONAL HMAC-MD5 (digest length = key length = 16) + 'none' // OPTIONAL no MAC; NOT RECOMMENDED + ); + + static $compression_algorithms = array( + 'none' // REQUIRED no compression + //'zlib' // OPTIONAL ZLIB (LZ77) compression + ); + + // some SSH servers have buggy implementations of some of the above algorithms + switch ($this->server_identifier) { + case 'SSH-2.0-SSHD': + $mac_algorithms = array_values(array_diff( + $mac_algorithms, + array('hmac-sha1-96', 'hmac-md5-96') + )); + } + + static $str_kex_algorithms, $str_server_host_key_algorithms, + $encryption_algorithms_server_to_client, $mac_algorithms_server_to_client, $compression_algorithms_server_to_client, + $encryption_algorithms_client_to_server, $mac_algorithms_client_to_server, $compression_algorithms_client_to_server; + + if (empty($str_kex_algorithms)) { + $str_kex_algorithms = implode(',', $kex_algorithms); + $str_server_host_key_algorithms = implode(',', $server_host_key_algorithms); + $encryption_algorithms_server_to_client = $encryption_algorithms_client_to_server = implode(',', $encryption_algorithms); + $mac_algorithms_server_to_client = $mac_algorithms_client_to_server = implode(',', $mac_algorithms); + $compression_algorithms_server_to_client = $compression_algorithms_client_to_server = implode(',', $compression_algorithms); + } + + $client_cookie = ''; + for ($i = 0; $i < 16; $i++) { + $client_cookie.= chr(crypt_random(0, 255)); + } + + $response = $kexinit_payload_server; + $this->_string_shift($response, 1); // skip past the message number (it should be SSH_MSG_KEXINIT) + $server_cookie = $this->_string_shift($response, 16); + + $temp = unpack('Nlength', $this->_string_shift($response, 4)); + $this->kex_algorithms = explode(',', $this->_string_shift($response, $temp['length'])); + + $temp = unpack('Nlength', $this->_string_shift($response, 4)); + $this->server_host_key_algorithms = explode(',', $this->_string_shift($response, $temp['length'])); + + $temp = unpack('Nlength', $this->_string_shift($response, 4)); + $this->encryption_algorithms_client_to_server = explode(',', $this->_string_shift($response, $temp['length'])); + + $temp = unpack('Nlength', $this->_string_shift($response, 4)); + $this->encryption_algorithms_server_to_client = explode(',', $this->_string_shift($response, $temp['length'])); + + $temp = unpack('Nlength', $this->_string_shift($response, 4)); + $this->mac_algorithms_client_to_server = explode(',', $this->_string_shift($response, $temp['length'])); + + $temp = unpack('Nlength', $this->_string_shift($response, 4)); + $this->mac_algorithms_server_to_client = explode(',', $this->_string_shift($response, $temp['length'])); + + $temp = unpack('Nlength', $this->_string_shift($response, 4)); + $this->compression_algorithms_client_to_server = explode(',', $this->_string_shift($response, $temp['length'])); + + $temp = unpack('Nlength', $this->_string_shift($response, 4)); + $this->compression_algorithms_server_to_client = explode(',', $this->_string_shift($response, $temp['length'])); + + $temp = unpack('Nlength', $this->_string_shift($response, 4)); + $this->languages_client_to_server = explode(',', $this->_string_shift($response, $temp['length'])); + + $temp = unpack('Nlength', $this->_string_shift($response, 4)); + $this->languages_server_to_client = explode(',', $this->_string_shift($response, $temp['length'])); + + extract(unpack('Cfirst_kex_packet_follows', $this->_string_shift($response, 1))); + $first_kex_packet_follows = $first_kex_packet_follows != 0; + + // the sending of SSH2_MSG_KEXINIT could go in one of two places. this is the second place. + $kexinit_payload_client = pack('Ca*Na*Na*Na*Na*Na*Na*Na*Na*Na*Na*CN', + NET_SSH2_MSG_KEXINIT, $client_cookie, strlen($str_kex_algorithms), $str_kex_algorithms, + strlen($str_server_host_key_algorithms), $str_server_host_key_algorithms, strlen($encryption_algorithms_client_to_server), + $encryption_algorithms_client_to_server, strlen($encryption_algorithms_server_to_client), $encryption_algorithms_server_to_client, + strlen($mac_algorithms_client_to_server), $mac_algorithms_client_to_server, strlen($mac_algorithms_server_to_client), + $mac_algorithms_server_to_client, strlen($compression_algorithms_client_to_server), $compression_algorithms_client_to_server, + strlen($compression_algorithms_server_to_client), $compression_algorithms_server_to_client, 0, '', 0, '', + 0, 0 + ); + + if (!$this->_send_binary_packet($kexinit_payload_client)) { + return false; + } + // here ends the second place. + + // we need to decide upon the symmetric encryption algorithms before we do the diffie-hellman key exchange + for ($i = 0; $i < count($encryption_algorithms) && !in_array($encryption_algorithms[$i], $this->encryption_algorithms_server_to_client); $i++); + if ($i == count($encryption_algorithms)) { + user_error('No compatible server to client encryption algorithms found', E_USER_NOTICE); + return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); + } + + // we don't initialize any crypto-objects, yet - we do that, later. for now, we need the lengths to make the + // diffie-hellman key exchange as fast as possible + $decrypt = $encryption_algorithms[$i]; + switch ($decrypt) { + case '3des-cbc': + case '3des-ctr': + $decryptKeyLength = 24; // eg. 192 / 8 + break; + case 'aes256-cbc': + case 'aes256-ctr': + $decryptKeyLength = 32; // eg. 256 / 8 + break; + case 'aes192-cbc': + case 'aes192-ctr': + $decryptKeyLength = 24; // eg. 192 / 8 + break; + case 'aes128-cbc': + case 'aes128-ctr': + $decryptKeyLength = 16; // eg. 128 / 8 + break; + case 'arcfour': + case 'arcfour128': + $decryptKeyLength = 16; // eg. 128 / 8 + break; + case 'arcfour256': + $decryptKeyLength = 32; // eg. 128 / 8 + break; + case 'none'; + $decryptKeyLength = 0; + } + + for ($i = 0; $i < count($encryption_algorithms) && !in_array($encryption_algorithms[$i], $this->encryption_algorithms_client_to_server); $i++); + if ($i == count($encryption_algorithms)) { + user_error('No compatible client to server encryption algorithms found', E_USER_NOTICE); + return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); + } + + $encrypt = $encryption_algorithms[$i]; + switch ($encrypt) { + case '3des-cbc': + case '3des-ctr': + $encryptKeyLength = 24; + break; + case 'aes256-cbc': + case 'aes256-ctr': + $encryptKeyLength = 32; + break; + case 'aes192-cbc': + case 'aes192-ctr': + $encryptKeyLength = 24; + break; + case 'aes128-cbc': + case 'aes128-ctr': + $encryptKeyLength = 16; + break; + case 'arcfour': + case 'arcfour128': + $encryptKeyLength = 16; + break; + case 'arcfour256': + $encryptKeyLength = 32; + break; + case 'none'; + $encryptKeyLength = 0; + } + + $keyLength = $decryptKeyLength > $encryptKeyLength ? $decryptKeyLength : $encryptKeyLength; + + // through diffie-hellman key exchange a symmetric key is obtained + for ($i = 0; $i < count($kex_algorithms) && !in_array($kex_algorithms[$i], $this->kex_algorithms); $i++); + if ($i == count($kex_algorithms)) { + user_error('No compatible key exchange algorithms found', E_USER_NOTICE); + return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); + } + + switch ($kex_algorithms[$i]) { + // see http://tools.ietf.org/html/rfc2409#section-6.2 and + // http://tools.ietf.org/html/rfc2412, appendex E + case 'diffie-hellman-group1-sha1': + $p = pack('H256', 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' . + '020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' . + '4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' . + 'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF'); + $keyLength = $keyLength < 160 ? $keyLength : 160; + $hash = 'sha1'; + break; + // see http://tools.ietf.org/html/rfc3526#section-3 + case 'diffie-hellman-group14-sha1': + $p = pack('H512', 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' . + '020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' . + '4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' . + 'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF05' . + '98DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB' . + '9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B' . + 'E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718' . + '3995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF'); + $keyLength = $keyLength < 160 ? $keyLength : 160; + $hash = 'sha1'; + } + + $p = new Math_BigInteger($p, 256); + //$q = $p->bitwise_rightShift(1); + + /* To increase the speed of the key exchange, both client and server may + reduce the size of their private exponents. It should be at least + twice as long as the key material that is generated from the shared + secret. For more details, see the paper by van Oorschot and Wiener + [VAN-OORSCHOT]. + + -- http://tools.ietf.org/html/rfc4419#section-6.2 */ + $q = new Math_BigInteger(1); + $q = $q->bitwise_leftShift(2 * $keyLength); + $q = $q->subtract(new Math_BigInteger(1)); + + $g = new Math_BigInteger(2); + $x = new Math_BigInteger(); + $x->setRandomGenerator('crypt_random'); + $x = $x->random(new Math_BigInteger(1), $q); + $e = $g->modPow($x, $p); + + $eBytes = $e->toBytes(true); + $data = pack('CNa*', NET_SSH2_MSG_KEXDH_INIT, strlen($eBytes), $eBytes); + + if (!$this->_send_binary_packet($data)) { + user_error('Connection closed by server', E_USER_NOTICE); + return false; + } + + $response = $this->_get_binary_packet(); + if ($response === false) { + user_error('Connection closed by server', E_USER_NOTICE); + return false; + } + extract(unpack('Ctype', $this->_string_shift($response, 1))); + + if ($type != NET_SSH2_MSG_KEXDH_REPLY) { + user_error('Expected SSH_MSG_KEXDH_REPLY', E_USER_NOTICE); + return false; + } + + $temp = unpack('Nlength', $this->_string_shift($response, 4)); + $this->server_public_host_key = $server_public_host_key = $this->_string_shift($response, $temp['length']); + + $temp = unpack('Nlength', $this->_string_shift($server_public_host_key, 4)); + $public_key_format = $this->_string_shift($server_public_host_key, $temp['length']); + + $temp = unpack('Nlength', $this->_string_shift($response, 4)); + $fBytes = $this->_string_shift($response, $temp['length']); + $f = new Math_BigInteger($fBytes, -256); + + $temp = unpack('Nlength', $this->_string_shift($response, 4)); + $this->signature = $this->_string_shift($response, $temp['length']); + + $temp = unpack('Nlength', $this->_string_shift($this->signature, 4)); + $this->signature_format = $this->_string_shift($this->signature, $temp['length']); + + $key = $f->modPow($x, $p); + $keyBytes = $key->toBytes(true); + + $this->exchange_hash = pack('Na*Na*Na*Na*Na*Na*Na*Na*', + strlen($this->identifier), $this->identifier, strlen($this->server_identifier), $this->server_identifier, + strlen($kexinit_payload_client), $kexinit_payload_client, strlen($kexinit_payload_server), + $kexinit_payload_server, strlen($this->server_public_host_key), $this->server_public_host_key, strlen($eBytes), + $eBytes, strlen($fBytes), $fBytes, strlen($keyBytes), $keyBytes + ); + + $this->exchange_hash = pack('H*', $hash($this->exchange_hash)); + + if ($this->session_id === false) { + $this->session_id = $this->exchange_hash; + } + + for ($i = 0; $i < count($server_host_key_algorithms) && !in_array($server_host_key_algorithms[$i], $this->server_host_key_algorithms); $i++); + if ($i == count($server_host_key_algorithms)) { + user_error('No compatible server host key algorithms found', E_USER_NOTICE); + return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); + } + + if ($public_key_format != $server_host_key_algorithms[$i] || $this->signature_format != $server_host_key_algorithms[$i]) { + user_error('Sever Host Key Algorithm Mismatch', E_USER_NOTICE); + return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); + } + + $packet = pack('C', + NET_SSH2_MSG_NEWKEYS + ); + + if (!$this->_send_binary_packet($packet)) { + return false; + } + + $response = $this->_get_binary_packet(); + + if ($response === false) { + user_error('Connection closed by server', E_USER_NOTICE); + return false; + } + + extract(unpack('Ctype', $this->_string_shift($response, 1))); + + if ($type != NET_SSH2_MSG_NEWKEYS) { + user_error('Expected SSH_MSG_NEWKEYS', E_USER_NOTICE); + return false; + } + + switch ($encrypt) { + case '3des-cbc': + $this->encrypt = new Crypt_TripleDES(); + // $this->encrypt_block_size = 64 / 8 == the default + break; + case '3des-ctr': + $this->encrypt = new Crypt_TripleDES(CRYPT_DES_MODE_CTR); + // $this->encrypt_block_size = 64 / 8 == the default + break; + case 'aes256-cbc': + case 'aes192-cbc': + case 'aes128-cbc': + $this->encrypt = new Crypt_AES(); + $this->encrypt_block_size = 16; // eg. 128 / 8 + break; + case 'aes256-ctr': + case 'aes192-ctr': + case 'aes128-ctr': + $this->encrypt = new Crypt_AES(CRYPT_AES_MODE_CTR); + $this->encrypt_block_size = 16; // eg. 128 / 8 + break; + case 'arcfour': + case 'arcfour128': + case 'arcfour256': + $this->encrypt = new Crypt_RC4(); + break; + case 'none'; + //$this->encrypt = new Crypt_Null(); + } + + switch ($decrypt) { + case '3des-cbc': + $this->decrypt = new Crypt_TripleDES(); + break; + case '3des-ctr': + $this->decrypt = new Crypt_TripleDES(CRYPT_DES_MODE_CTR); + break; + case 'aes256-cbc': + case 'aes192-cbc': + case 'aes128-cbc': + $this->decrypt = new Crypt_AES(); + $this->decrypt_block_size = 16; + break; + case 'aes256-ctr': + case 'aes192-ctr': + case 'aes128-ctr': + $this->decrypt = new Crypt_AES(CRYPT_AES_MODE_CTR); + $this->decrypt_block_size = 16; + break; + case 'arcfour': + case 'arcfour128': + case 'arcfour256': + $this->decrypt = new Crypt_RC4(); + break; + case 'none'; + //$this->decrypt = new Crypt_Null(); + } + + $keyBytes = pack('Na*', strlen($keyBytes), $keyBytes); + + if ($this->encrypt) { + $this->encrypt->enableContinuousBuffer(); + $this->encrypt->disablePadding(); + + $iv = pack('H*', $hash($keyBytes . $this->exchange_hash . 'A' . $this->session_id)); + while ($this->encrypt_block_size > strlen($iv)) { + $iv.= pack('H*', $hash($keyBytes . $this->exchange_hash . $iv)); + } + $this->encrypt->setIV(substr($iv, 0, $this->encrypt_block_size)); + + $key = pack('H*', $hash($keyBytes . $this->exchange_hash . 'C' . $this->session_id)); + while ($encryptKeyLength > strlen($key)) { + $key.= pack('H*', $hash($keyBytes . $this->exchange_hash . $key)); + } + $this->encrypt->setKey(substr($key, 0, $encryptKeyLength)); + } + + if ($this->decrypt) { + $this->decrypt->enableContinuousBuffer(); + $this->decrypt->disablePadding(); + + $iv = pack('H*', $hash($keyBytes . $this->exchange_hash . 'B' . $this->session_id)); + while ($this->decrypt_block_size > strlen($iv)) { + $iv.= pack('H*', $hash($keyBytes . $this->exchange_hash . $iv)); + } + $this->decrypt->setIV(substr($iv, 0, $this->decrypt_block_size)); + + $key = pack('H*', $hash($keyBytes . $this->exchange_hash . 'D' . $this->session_id)); + while ($decryptKeyLength > strlen($key)) { + $key.= pack('H*', $hash($keyBytes . $this->exchange_hash . $key)); + } + $this->decrypt->setKey(substr($key, 0, $decryptKeyLength)); + } + + /* The "arcfour128" algorithm is the RC4 cipher, as described in + [SCHNEIER], using a 128-bit key. The first 1536 bytes of keystream + generated by the cipher MUST be discarded, and the first byte of the + first encrypted packet MUST be encrypted using the 1537th byte of + keystream. + + -- http://tools.ietf.org/html/rfc4345#section-4 */ + if ($encrypt == 'arcfour128' || $encrypt == 'arcfour256') { + $this->encrypt->encrypt(str_repeat("\0", 1536)); + } + if ($decrypt == 'arcfour128' || $decrypt == 'arcfour256') { + $this->decrypt->decrypt(str_repeat("\0", 1536)); + } + + for ($i = 0; $i < count($mac_algorithms) && !in_array($mac_algorithms[$i], $this->mac_algorithms_client_to_server); $i++); + if ($i == count($mac_algorithms)) { + user_error('No compatible client to server message authentication algorithms found', E_USER_NOTICE); + return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); + } + + $createKeyLength = 0; // ie. $mac_algorithms[$i] == 'none' + switch ($mac_algorithms[$i]) { + case 'hmac-sha1': + $this->hmac_create = new Crypt_Hash('sha1'); + $createKeyLength = 20; + break; + case 'hmac-sha1-96': + $this->hmac_create = new Crypt_Hash('sha1-96'); + $createKeyLength = 20; + break; + case 'hmac-md5': + $this->hmac_create = new Crypt_Hash('md5'); + $createKeyLength = 16; + break; + case 'hmac-md5-96': + $this->hmac_create = new Crypt_Hash('md5-96'); + $createKeyLength = 16; + } + + for ($i = 0; $i < count($mac_algorithms) && !in_array($mac_algorithms[$i], $this->mac_algorithms_server_to_client); $i++); + if ($i == count($mac_algorithms)) { + user_error('No compatible server to client message authentication algorithms found', E_USER_NOTICE); + return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); + } + + $checkKeyLength = 0; + $this->hmac_size = 0; + switch ($mac_algorithms[$i]) { + case 'hmac-sha1': + $this->hmac_check = new Crypt_Hash('sha1'); + $checkKeyLength = 20; + $this->hmac_size = 20; + break; + case 'hmac-sha1-96': + $this->hmac_check = new Crypt_Hash('sha1-96'); + $checkKeyLength = 20; + $this->hmac_size = 12; + break; + case 'hmac-md5': + $this->hmac_check = new Crypt_Hash('md5'); + $checkKeyLength = 16; + $this->hmac_size = 16; + break; + case 'hmac-md5-96': + $this->hmac_check = new Crypt_Hash('md5-96'); + $checkKeyLength = 16; + $this->hmac_size = 12; + } + + $key = pack('H*', $hash($keyBytes . $this->exchange_hash . 'E' . $this->session_id)); + while ($createKeyLength > strlen($key)) { + $key.= pack('H*', $hash($keyBytes . $this->exchange_hash . $key)); + } + $this->hmac_create->setKey(substr($key, 0, $createKeyLength)); + + $key = pack('H*', $hash($keyBytes . $this->exchange_hash . 'F' . $this->session_id)); + while ($checkKeyLength > strlen($key)) { + $key.= pack('H*', $hash($keyBytes . $this->exchange_hash . $key)); + } + $this->hmac_check->setKey(substr($key, 0, $checkKeyLength)); + + for ($i = 0; $i < count($compression_algorithms) && !in_array($compression_algorithms[$i], $this->compression_algorithms_server_to_client); $i++); + if ($i == count($compression_algorithms)) { + user_error('No compatible server to client compression algorithms found', E_USER_NOTICE); + return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); + } + $this->decompress = $compression_algorithms[$i] == 'zlib'; + + for ($i = 0; $i < count($compression_algorithms) && !in_array($compression_algorithms[$i], $this->compression_algorithms_client_to_server); $i++); + if ($i == count($compression_algorithms)) { + user_error('No compatible client to server compression algorithms found', E_USER_NOTICE); + return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); + } + $this->compress = $compression_algorithms[$i] == 'zlib'; + + return true; + } + + /** + * Login + * + * The $password parameter can be a plaintext password or a Crypt_RSA object. + * + * @param String $username + * @param optional String $password + * @return Boolean + * @access public + * @internal It might be worthwhile, at some point, to protect against {@link http://tools.ietf.org/html/rfc4251#section-9.3.9 traffic analysis} + * by sending dummy SSH_MSG_IGNORE messages. + */ + function login($username, $password = '') + { + if (!($this->bitmap & NET_SSH2_MASK_CONSTRUCTOR)) { + return false; + } + + $packet = pack('CNa*', + NET_SSH2_MSG_SERVICE_REQUEST, strlen('ssh-userauth'), 'ssh-userauth' + ); + + if (!$this->_send_binary_packet($packet)) { + return false; + } + + $response = $this->_get_binary_packet(); + if ($response === false) { + user_error('Connection closed by server', E_USER_NOTICE); + return false; + } + + extract(unpack('Ctype', $this->_string_shift($response, 1))); + + if ($type != NET_SSH2_MSG_SERVICE_ACCEPT) { + user_error('Expected SSH_MSG_SERVICE_ACCEPT', E_USER_NOTICE); + return false; + } + + // although PHP5's get_class() preserves the case, PHP4's does not + if (is_object($password) && strtolower(get_class($password)) == 'crypt_rsa') { + return $this->_privatekey_login($username, $password); + } + + $packet = pack('CNa*Na*Na*CNa*', + NET_SSH2_MSG_USERAUTH_REQUEST, strlen($username), $username, strlen('ssh-connection'), 'ssh-connection', + strlen('password'), 'password', 0, strlen($password), $password + ); + + if (!$this->_send_binary_packet($packet)) { + return false; + } + + // remove the username and password from the last logged packet + if (defined('NET_SSH2_LOGGING') && NET_SSH2_LOGGING == NET_SSH2_LOG_COMPLEX) { + $packet = pack('CNa*Na*Na*CNa*', + NET_SSH2_MSG_USERAUTH_REQUEST, strlen('username'), 'username', strlen('ssh-connection'), 'ssh-connection', + strlen('password'), 'password', 0, strlen('password'), 'password' + ); + $this->message_log[count($this->message_log) - 1] = $packet; + } + + $response = $this->_get_binary_packet(); + if ($response === false) { + user_error('Connection closed by server', E_USER_NOTICE); + return false; + } + + extract(unpack('Ctype', $this->_string_shift($response, 1))); + + switch ($type) { + case NET_SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ: // in theory, the password can be changed + if (defined('NET_SSH2_LOGGING')) { + $this->message_number_log[count($this->message_number_log) - 1] = 'NET_SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ'; + } + extract(unpack('Nlength', $this->_string_shift($response, 4))); + $this->errors[] = 'SSH_MSG_USERAUTH_PASSWD_CHANGEREQ: ' . utf8_decode($this->_string_shift($response, $length)); + return $this->_disconnect(NET_SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER); + case NET_SSH2_MSG_USERAUTH_FAILURE: + // can we use keyboard-interactive authentication? if not then either the login is bad or the server employees + // multi-factor authentication + extract(unpack('Nlength', $this->_string_shift($response, 4))); + $auth_methods = explode(',', $this->_string_shift($response, $length)); + if (in_array('keyboard-interactive', $auth_methods)) { + if ($this->_keyboard_interactive_login($username, $password)) { + $this->bitmap |= NET_SSH2_MASK_LOGIN; + return true; + } + return false; + } + return false; + case NET_SSH2_MSG_USERAUTH_SUCCESS: + $this->bitmap |= NET_SSH2_MASK_LOGIN; + return true; + } + + return false; + } + + /** + * Login via keyboard-interactive authentication + * + * See {@link http://tools.ietf.org/html/rfc4256 RFC4256} for details. This is not a full-featured keyboard-interactive authenticator. + * + * @param String $username + * @param String $password + * @return Boolean + * @access private + */ + function _keyboard_interactive_login($username, $password) + { + $packet = pack('CNa*Na*Na*Na*Na*', + NET_SSH2_MSG_USERAUTH_REQUEST, strlen($username), $username, strlen('ssh-connection'), 'ssh-connection', + strlen('keyboard-interactive'), 'keyboard-interactive', 0, '', 0, '' + ); + + if (!$this->_send_binary_packet($packet)) { + return false; + } + + return $this->_keyboard_interactive_process($password); + } + + /** + * Handle the keyboard-interactive requests / responses. + * + * @param String $responses... + * @return Boolean + * @access private + */ + function _keyboard_interactive_process() + { + $responses = func_get_args(); + + $response = $this->_get_binary_packet(); + if ($response === false) { + user_error('Connection closed by server', E_USER_NOTICE); + return false; + } + + extract(unpack('Ctype', $this->_string_shift($response, 1))); + + switch ($type) { + case NET_SSH2_MSG_USERAUTH_INFO_REQUEST: + // see http://tools.ietf.org/html/rfc4256#section-3.2 + if (defined('NET_SSH2_LOGGING')) { + $this->message_number_log[count($this->message_number_log) - 1] = str_replace( + 'UNKNOWN', + 'NET_SSH2_MSG_USERAUTH_INFO_REQUEST', + $this->message_number_log[count($this->message_number_log) - 1] + ); + } + + extract(unpack('Nlength', $this->_string_shift($response, 4))); + $this->_string_shift($response, $length); // name; may be empty + extract(unpack('Nlength', $this->_string_shift($response, 4))); + $this->_string_shift($response, $length); // instruction; may be empty + extract(unpack('Nlength', $this->_string_shift($response, 4))); + $this->_string_shift($response, $length); // language tag; may be empty + extract(unpack('Nnum_prompts', $this->_string_shift($response, 4))); + /* + for ($i = 0; $i < $num_prompts; $i++) { + extract(unpack('Nlength', $this->_string_shift($response, 4))); + // prompt - ie. "Password: "; must not be empty + $this->_string_shift($response, $length); + $echo = $this->_string_shift($response) != chr(0); + } + */ + + /* + After obtaining the requested information from the user, the client + MUST respond with an SSH_MSG_USERAUTH_INFO_RESPONSE message. + */ + // see http://tools.ietf.org/html/rfc4256#section-3.4 + $packet = $logged = pack('CN', NET_SSH2_MSG_USERAUTH_INFO_RESPONSE, count($responses)); + for ($i = 0; $i < count($responses); $i++) { + $packet.= pack('Na*', strlen($responses[$i]), $responses[$i]); + $logged.= pack('Na*', strlen('dummy-answer'), 'dummy-answer'); + } + + if (!$this->_send_binary_packet($packet)) { + return false; + } + + if (defined('NET_SSH2_LOGGING')) { + $this->message_number_log[count($this->message_number_log) - 1] = str_replace( + 'UNKNOWN', + 'NET_SSH2_MSG_USERAUTH_INFO_RESPONSE', + $this->message_number_log[count($this->message_number_log) - 1] + ); + $this->message_log[count($this->message_log) - 1] = $logged; + } + + /* + After receiving the response, the server MUST send either an + SSH_MSG_USERAUTH_SUCCESS, SSH_MSG_USERAUTH_FAILURE, or another + SSH_MSG_USERAUTH_INFO_REQUEST message. + */ + // maybe phpseclib should force close the connection after x request / responses? unless something like that is done + // there could be an infinite loop of request / responses. + return $this->_keyboard_interactive_process(); + case NET_SSH2_MSG_USERAUTH_SUCCESS: + return true; + case NET_SSH2_MSG_USERAUTH_FAILURE: + return false; + } + + return false; + } + + /** + * Login with an RSA private key + * + * @param String $username + * @param Crypt_RSA $password + * @return Boolean + * @access private + * @internal It might be worthwhile, at some point, to protect against {@link http://tools.ietf.org/html/rfc4251#section-9.3.9 traffic analysis} + * by sending dummy SSH_MSG_IGNORE messages. + */ + function _privatekey_login($username, $privatekey) + { + // see http://tools.ietf.org/html/rfc4253#page-15 + $publickey = $privatekey->getPublicKey(CRYPT_RSA_PUBLIC_FORMAT_RAW); + if ($publickey === false) { + return false; + } + + $publickey = array( + 'e' => $publickey['e']->toBytes(true), + 'n' => $publickey['n']->toBytes(true) + ); + $publickey = pack('Na*Na*Na*', + strlen('ssh-rsa'), 'ssh-rsa', strlen($publickey['e']), $publickey['e'], strlen($publickey['n']), $publickey['n'] + ); + + $part1 = pack('CNa*Na*Na*', + NET_SSH2_MSG_USERAUTH_REQUEST, strlen($username), $username, strlen('ssh-connection'), 'ssh-connection', + strlen('publickey'), 'publickey' + ); + $part2 = pack('Na*Na*', strlen('ssh-rsa'), 'ssh-rsa', strlen($publickey), $publickey); + + $packet = $part1 . chr(0) . $part2; + if (!$this->_send_binary_packet($packet)) { + return false; + } + + $response = $this->_get_binary_packet(); + if ($response === false) { + user_error('Connection closed by server', E_USER_NOTICE); + return false; + } + + extract(unpack('Ctype', $this->_string_shift($response, 1))); + + switch ($type) { + case NET_SSH2_MSG_USERAUTH_FAILURE: + extract(unpack('Nlength', $this->_string_shift($response, 4))); + $this->errors[] = 'SSH_MSG_USERAUTH_FAILURE: ' . $this->_string_shift($response, $length); + return $this->_disconnect(NET_SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER); + case NET_SSH2_MSG_USERAUTH_PK_OK: + // we'll just take it on faith that the public key blob and the public key algorithm name are as + // they should be + if (defined('NET_SSH2_LOGGING')) { + $this->message_number_log[count($this->message_number_log) - 1] = str_replace( + 'UNKNOWN', + 'NET_SSH2_MSG_USERAUTH_PK_OK', + $this->message_number_log[count($this->message_number_log) - 1] + ); + } + } + + $packet = $part1 . chr(1) . $part2; + $privatekey->setSignatureMode(CRYPT_RSA_SIGNATURE_PKCS1); + $signature = $privatekey->sign(pack('Na*a*', strlen($this->session_id), $this->session_id, $packet)); + $signature = pack('Na*Na*', strlen('ssh-rsa'), 'ssh-rsa', strlen($signature), $signature); + $packet.= pack('Na*', strlen($signature), $signature); + + if (!$this->_send_binary_packet($packet)) { + return false; + } + + $response = $this->_get_binary_packet(); + if ($response === false) { + user_error('Connection closed by server', E_USER_NOTICE); + return false; + } + + extract(unpack('Ctype', $this->_string_shift($response, 1))); + + switch ($type) { + case NET_SSH2_MSG_USERAUTH_FAILURE: + // either the login is bad or the server employs multi-factor authentication + return false; + case NET_SSH2_MSG_USERAUTH_SUCCESS: + $this->bitmap |= NET_SSH2_MASK_LOGIN; + return true; + } + + return false; + } + + /** + * Set Timeout + * + * $ssh->exec('ping 127.0.0.1'); on a Linux host will never return and will run indefinitely. setTimeout() makes it so it'll timeout. + * Setting $timeout to false or 0 will mean there is no timeout. + * + * @param Mixed $timeout + */ + function setTimeout($timeout) + { + $this->timeout = $this->curTimeout = $timeout; + } + + /** + * Execute Command + * + * If $block is set to false then Net_SSH2::_get_channel_packet(NET_SSH2_CHANNEL_EXEC) will need to be called manually. + * In all likelihood, this is not a feature you want to be taking advantage of. + * + * @param String $command + * @param optional Boolean $block + * @return String + * @access public + */ + function exec($command, $block = true) + { + $this->curTimeout = $this->timeout; + + if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) { + return false; + } + + // RFC4254 defines the (client) window size as "bytes the other party can send before it must wait for the window to + // be adjusted". 0x7FFFFFFF is, at 4GB, the max size. technically, it should probably be decremented, but, + // honestly, if you're transfering more than 4GB, you probably shouldn't be using phpseclib, anyway. + // see http://tools.ietf.org/html/rfc4254#section-5.2 for more info + $this->window_size_client_to_server[NET_SSH2_CHANNEL_EXEC] = 0x7FFFFFFF; + // 0x8000 is the maximum max packet size, per http://tools.ietf.org/html/rfc4253#section-6.1, although since PuTTy + // uses 0x4000, that's what will be used here, as well. + $packet_size = 0x4000; + + $packet = pack('CNa*N3', + NET_SSH2_MSG_CHANNEL_OPEN, strlen('session'), 'session', NET_SSH2_CHANNEL_EXEC, $this->window_size_client_to_server[NET_SSH2_CHANNEL_EXEC], $packet_size); + + if (!$this->_send_binary_packet($packet)) { + return false; + } + + $this->channel_status[NET_SSH2_CHANNEL_EXEC] = NET_SSH2_MSG_CHANNEL_OPEN; + + $response = $this->_get_channel_packet(NET_SSH2_CHANNEL_EXEC); + if ($response === false) { + return false; + } + + // sending a pty-req SSH_MSG_CHANNEL_REQUEST message is unnecessary and, in fact, in most cases, slows things + // down. the one place where it might be desirable is if you're doing something like Net_SSH2::exec('ping localhost &'). + // with a pty-req SSH_MSG_CHANNEL_REQUEST, exec() will return immediately and the ping process will then + // then immediately terminate. without such a request exec() will loop indefinitely. the ping process won't end but + // neither will your script. + + // although, in theory, the size of SSH_MSG_CHANNEL_REQUEST could exceed the maximum packet size established by + // SSH_MSG_CHANNEL_OPEN_CONFIRMATION, RFC4254#section-5.1 states that the "maximum packet size" refers to the + // "maximum size of an individual data packet". ie. SSH_MSG_CHANNEL_DATA. RFC4254#section-5.2 corroborates. + $packet = pack('CNNa*CNa*', + NET_SSH2_MSG_CHANNEL_REQUEST, $this->server_channels[NET_SSH2_CHANNEL_EXEC], strlen('exec'), 'exec', 1, strlen($command), $command); + if (!$this->_send_binary_packet($packet)) { + return false; + } + + $this->channel_status[NET_SSH2_CHANNEL_EXEC] = NET_SSH2_MSG_CHANNEL_REQUEST; + + $response = $this->_get_channel_packet(NET_SSH2_CHANNEL_EXEC); + if ($response === false) { + return false; + } + + $this->channel_status[NET_SSH2_CHANNEL_EXEC] = NET_SSH2_MSG_CHANNEL_DATA; + + if (!$block) { + return true; + } + + $output = ''; + while (true) { + $temp = $this->_get_channel_packet(NET_SSH2_CHANNEL_EXEC); + switch (true) { + case $temp === true: + return $output; + case $temp === false: + return false; + default: + $output.= $temp; + } + } + } + + /** + * Creates an interactive shell + * + * @see Net_SSH2::read() + * @see Net_SSH2::write() + * @return Boolean + * @access private + */ + function _initShell() + { + $this->window_size_client_to_server[NET_SSH2_CHANNEL_SHELL] = 0x7FFFFFFF; + $packet_size = 0x4000; + + $packet = pack('CNa*N3', + NET_SSH2_MSG_CHANNEL_OPEN, strlen('session'), 'session', NET_SSH2_CHANNEL_SHELL, $this->window_size_client_to_server[NET_SSH2_CHANNEL_SHELL], $packet_size); + + if (!$this->_send_binary_packet($packet)) { + return false; + } + + $this->channel_status[NET_SSH2_CHANNEL_SHELL] = NET_SSH2_MSG_CHANNEL_OPEN; + + $response = $this->_get_channel_packet(NET_SSH2_CHANNEL_SHELL); + if ($response === false) { + return false; + } + + $terminal_modes = pack('C', NET_SSH2_TTY_OP_END); + $packet = pack('CNNa*CNa*N5a*', + NET_SSH2_MSG_CHANNEL_REQUEST, $this->server_channels[NET_SSH2_CHANNEL_SHELL], strlen('pty-req'), 'pty-req', 1, strlen('vt100'), 'vt100', + 80, 24, 0, 0, strlen($terminal_modes), $terminal_modes); + + if (!$this->_send_binary_packet($packet)) { + return false; + } + + $response = $this->_get_binary_packet(); + if ($response === false) { + user_error('Connection closed by server', E_USER_NOTICE); + return false; + } + + list(, $type) = unpack('C', $this->_string_shift($response, 1)); + + switch ($type) { + case NET_SSH2_MSG_CHANNEL_SUCCESS: + break; + case NET_SSH2_MSG_CHANNEL_FAILURE: + default: + user_error('Unable to request pseudo-terminal', E_USER_NOTICE); + return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION); + } + + $packet = pack('CNNa*C', + NET_SSH2_MSG_CHANNEL_REQUEST, $this->server_channels[NET_SSH2_CHANNEL_SHELL], strlen('shell'), 'shell', 1); + if (!$this->_send_binary_packet($packet)) { + return false; + } + + $this->channel_status[NET_SSH2_CHANNEL_SHELL] = NET_SSH2_MSG_CHANNEL_REQUEST; + + $response = $this->_get_channel_packet(NET_SSH2_CHANNEL_SHELL); + if ($response === false) { + return false; + } + + $this->channel_status[NET_SSH2_CHANNEL_SHELL] = NET_SSH2_MSG_CHANNEL_DATA; + + $this->bitmap |= NET_SSH2_MASK_SHELL; + + return true; + } + + /** + * Returns the output of an interactive shell + * + * Returns when there's a match for $expect, which can take the form of a string literal or, + * if $mode == NET_SSH2_READ_REGEX, a regular expression. + * + * @see Net_SSH2::read() + * @param String $expect + * @param Integer $mode + * @return String + * @access public + */ + function read($expect = '', $mode = NET_SSH2_READ_SIMPLE) + { + $this->curTimeout = $this->timeout; + + if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) { + user_error('Operation disallowed prior to login()', E_USER_NOTICE); + return false; + } + + if (!($this->bitmap & NET_SSH2_MASK_SHELL) && !$this->_initShell()) { + user_error('Unable to initiate an interactive shell session', E_USER_NOTICE); + return false; + } + + $match = $expect; + while (true) { + if ($mode == NET_SSH2_READ_REGEX) { + preg_match($expect, $this->interactiveBuffer, $matches); + $match = $matches[0]; + } + $pos = !empty($match) ? strpos($this->interactiveBuffer, $match) : false; + if ($pos !== false) { + return $this->_string_shift($this->interactiveBuffer, $pos + strlen($match)); + } + $response = $this->_get_channel_packet(NET_SSH2_CHANNEL_SHELL); + if (is_bool($response)) { + return $response ? $this->_string_shift($this->interactiveBuffer, strlen($this->interactiveBuffer)) : false; + } + + $this->interactiveBuffer.= $response; + } + } + + /** + * Inputs a command into an interactive shell. + * + * @see Net_SSH1::interactiveWrite() + * @param String $cmd + * @return Boolean + * @access public + */ + function write($cmd) + { + if (!($this->bitmap & NET_SSH2_MASK_LOGIN)) { + user_error('Operation disallowed prior to login()', E_USER_NOTICE); + return false; + } + + if (!($this->bitmap & NET_SSH2_MASK_SHELL) && !$this->_initShell()) { + user_error('Unable to initiate an interactive shell session', E_USER_NOTICE); + return false; + } + + return $this->_send_channel_packet(NET_SSH2_CHANNEL_SHELL, $cmd); + } + + /** + * Disconnect + * + * @access public + */ + function disconnect() + { + $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION); + if (isset($this->realtime_log_file) && is_resource($this->realtime_log_file)) { + fclose($this->realtime_log_file); + } + } + + /** + * Destructor. + * + * Will be called, automatically, if you're supporting just PHP5. If you're supporting PHP4, you'll need to call + * disconnect(). + * + * @access public + */ + function __destruct() + { + $this->disconnect(); + } + + /** + * Gets Binary Packets + * + * See '6. Binary Packet Protocol' of rfc4253 for more info. + * + * @see Net_SSH2::_send_binary_packet() + * @return String + * @access private + */ + function _get_binary_packet() + { + if (!is_resource($this->fsock) || feof($this->fsock)) { + user_error('Connection closed prematurely', E_USER_NOTICE); + return false; + } + + $start = strtok(microtime(), ' ') + strtok(''); // http://php.net/microtime#61838 + $raw = fread($this->fsock, $this->decrypt_block_size); + $stop = strtok(microtime(), ' ') + strtok(''); + + if (empty($raw)) { + return ''; + } + + if ($this->decrypt !== false) { + $raw = $this->decrypt->decrypt($raw); + } + if ($raw === false) { + user_error('Unable to decrypt content', E_USER_NOTICE); + return false; + } + + extract(unpack('Npacket_length/Cpadding_length', $this->_string_shift($raw, 5))); + + $remaining_length = $packet_length + 4 - $this->decrypt_block_size; + $buffer = ''; + while ($remaining_length > 0) { + $temp = fread($this->fsock, $remaining_length); + $buffer.= $temp; + $remaining_length-= strlen($temp); + } + if (!empty($buffer)) { + $raw.= $this->decrypt !== false ? $this->decrypt->decrypt($buffer) : $buffer; + $buffer = $temp = ''; + } + + $payload = $this->_string_shift($raw, $packet_length - $padding_length - 1); + $padding = $this->_string_shift($raw, $padding_length); // should leave $raw empty + + if ($this->hmac_check !== false) { + $hmac = fread($this->fsock, $this->hmac_size); + if ($hmac != $this->hmac_check->hash(pack('NNCa*', $this->get_seq_no, $packet_length, $padding_length, $payload . $padding))) { + user_error('Invalid HMAC', E_USER_NOTICE); + return false; + } + } + + //if ($this->decompress) { + // $payload = gzinflate(substr($payload, 2)); + //} + + $this->get_seq_no++; + + if (defined('NET_SSH2_LOGGING')) { + $message_number = isset($this->message_numbers[ord($payload[0])]) ? $this->message_numbers[ord($payload[0])] : 'UNKNOWN (' . ord($payload[0]) . ')'; + $message_number = '<- ' . $message_number . + ' (' . round($stop - $start, 4) . 's)'; + $this->_append_log($message_number, $payload); + } + + return $this->_filter($payload); + } + + /** + * Filter Binary Packets + * + * Because some binary packets need to be ignored... + * + * @see Net_SSH2::_get_binary_packet() + * @return String + * @access private + */ + function _filter($payload) + { + switch (ord($payload[0])) { + case NET_SSH2_MSG_DISCONNECT: + $this->_string_shift($payload, 1); + extract(unpack('Nreason_code/Nlength', $this->_string_shift($payload, 8))); + $this->errors[] = 'SSH_MSG_DISCONNECT: ' . $this->disconnect_reasons[$reason_code] . "\r\n" . utf8_decode($this->_string_shift($payload, $length)); + $this->bitmask = 0; + return false; + case NET_SSH2_MSG_IGNORE: + $payload = $this->_get_binary_packet(); + break; + case NET_SSH2_MSG_DEBUG: + $this->_string_shift($payload, 2); + extract(unpack('Nlength', $this->_string_shift($payload, 4))); + $this->errors[] = 'SSH_MSG_DEBUG: ' . utf8_decode($this->_string_shift($payload, $length)); + $payload = $this->_get_binary_packet(); + break; + case NET_SSH2_MSG_UNIMPLEMENTED: + return false; + case NET_SSH2_MSG_KEXINIT: + if ($this->session_id !== false) { + if (!$this->_key_exchange($payload)) { + $this->bitmask = 0; + return false; + } + $payload = $this->_get_binary_packet(); + } + } + + // see http://tools.ietf.org/html/rfc4252#section-5.4; only called when the encryption has been activated and when we haven't already logged in + if (($this->bitmap & NET_SSH2_MASK_CONSTRUCTOR) && !($this->bitmap & NET_SSH2_MASK_LOGIN) && ord($payload[0]) == NET_SSH2_MSG_USERAUTH_BANNER) { + $this->_string_shift($payload, 1); + extract(unpack('Nlength', $this->_string_shift($payload, 4))); + $this->errors[] = 'SSH_MSG_USERAUTH_BANNER: ' . utf8_decode($this->_string_shift($payload, $length)); + $payload = $this->_get_binary_packet(); + } + + // only called when we've already logged in + if (($this->bitmap & NET_SSH2_MASK_CONSTRUCTOR) && ($this->bitmap & NET_SSH2_MASK_LOGIN)) { + switch (ord($payload[0])) { + case NET_SSH2_MSG_GLOBAL_REQUEST: // see http://tools.ietf.org/html/rfc4254#section-4 + $this->_string_shift($payload, 1); + extract(unpack('Nlength', $this->_string_shift($payload))); + $this->errors[] = 'SSH_MSG_GLOBAL_REQUEST: ' . utf8_decode($this->_string_shift($payload, $length)); + + if (!$this->_send_binary_packet(pack('C', NET_SSH2_MSG_REQUEST_FAILURE))) { + return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION); + } + + $payload = $this->_get_binary_packet(); + break; + case NET_SSH2_MSG_CHANNEL_OPEN: // see http://tools.ietf.org/html/rfc4254#section-5.1 + $this->_string_shift($payload, 1); + extract(unpack('N', $this->_string_shift($payload, 4))); + $this->errors[] = 'SSH_MSG_CHANNEL_OPEN: ' . utf8_decode($this->_string_shift($payload, $length)); + + $this->_string_shift($payload, 4); // skip over client channel + extract(unpack('Nserver_channel', $this->_string_shift($payload, 4))); + + $packet = pack('CN3a*Na*', + NET_SSH2_MSG_REQUEST_FAILURE, $server_channel, NET_SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED, 0, '', 0, ''); + + if (!$this->_send_binary_packet($packet)) { + return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION); + } + + $payload = $this->_get_binary_packet(); + break; + case NET_SSH2_MSG_CHANNEL_WINDOW_ADJUST: + $payload = $this->_get_binary_packet(); + } + } + + return $payload; + } + + /** + * Enable Quiet Mode + * + * Suppress stderr from output + * + * @access public + */ + function enableQuietMode() + { + $this->quiet_mode = true; + } + + /** + * Disable Quiet Mode + * + * Show stderr in output + * + * @access public + */ + function disableQuietMode() + { + $this->quiet_mode = false; + } + + /** + * Gets channel data + * + * Returns the data as a string if it's available and false if not. + * + * @param $client_channel + * @return Mixed + * @access private + */ + function _get_channel_packet($client_channel, $skip_extended = false) + { + if (!empty($this->channel_buffers[$client_channel])) { + return array_shift($this->channel_buffers[$client_channel]); + } + + while (true) { + if ($this->curTimeout) { + $read = array($this->fsock); + $write = $except = NULL; + + $start = strtok(microtime(), ' ') + strtok(''); // http://php.net/microtime#61838 + $sec = floor($this->curTimeout); + $usec = 1000000 * ($this->curTimeout - $sec); + // on windows this returns a "Warning: Invalid CRT parameters detected" error + if (!@stream_select($read, $write, $except, $sec, $usec) && !count($read)) { + $this->_close_channel($client_channel); + return true; + } + $elapsed = strtok(microtime(), ' ') + strtok('') - $start; + $this->curTimeout-= $elapsed; + } + + $response = $this->_get_binary_packet(); + if ($response === false) { + user_error('Connection closed by server', E_USER_NOTICE); + return false; + } + + if (empty($response)) { + return ''; + } + + extract(unpack('Ctype/Nchannel', $this->_string_shift($response, 5))); + + switch ($this->channel_status[$channel]) { + case NET_SSH2_MSG_CHANNEL_OPEN: + switch ($type) { + case NET_SSH2_MSG_CHANNEL_OPEN_CONFIRMATION: + extract(unpack('Nserver_channel', $this->_string_shift($response, 4))); + $this->server_channels[$channel] = $server_channel; + $this->_string_shift($response, 4); // skip over (server) window size + $temp = unpack('Npacket_size_client_to_server', $this->_string_shift($response, 4)); + $this->packet_size_client_to_server[$channel] = $temp['packet_size_client_to_server']; + return $client_channel == $channel ? true : $this->_get_channel_packet($client_channel, $skip_extended); + //case NET_SSH2_MSG_CHANNEL_OPEN_FAILURE: + default: + user_error('Unable to open channel', E_USER_NOTICE); + return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION); + } + break; + case NET_SSH2_MSG_CHANNEL_REQUEST: + switch ($type) { + case NET_SSH2_MSG_CHANNEL_SUCCESS: + return true; + //case NET_SSH2_MSG_CHANNEL_FAILURE: + default: + user_error('Unable to request pseudo-terminal', E_USER_NOTICE); + return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION); + } + case NET_SSH2_MSG_CHANNEL_CLOSE: + return $type == NET_SSH2_MSG_CHANNEL_CLOSE ? true : $this->_get_channel_packet($client_channel, $skip_extended); + } + + switch ($type) { + case NET_SSH2_MSG_CHANNEL_DATA: + /* + if ($client_channel == NET_SSH2_CHANNEL_EXEC) { + // SCP requires null packets, such as this, be sent. further, in the case of the ssh.com SSH server + // this actually seems to make things twice as fast. more to the point, the message right after + // SSH_MSG_CHANNEL_DATA (usually SSH_MSG_IGNORE) won't block for as long as it would have otherwise. + // in OpenSSH it slows things down but only by a couple thousandths of a second. + $this->_send_channel_packet($client_channel, chr(0)); + } + */ + extract(unpack('Nlength', $this->_string_shift($response, 4))); + $data = $this->_string_shift($response, $length); + if ($client_channel == $channel) { + return $data; + } + if (!isset($this->channel_buffers[$client_channel])) { + $this->channel_buffers[$client_channel] = array(); + } + $this->channel_buffers[$client_channel][] = $data; + break; + case NET_SSH2_MSG_CHANNEL_EXTENDED_DATA: + if ($skip_extended || $this->quiet_mode) { + break; + } + /* + if ($client_channel == NET_SSH2_CHANNEL_EXEC) { + $this->_send_channel_packet($client_channel, chr(0)); + } + */ + // currently, there's only one possible value for $data_type_code: NET_SSH2_EXTENDED_DATA_STDERR + extract(unpack('Ndata_type_code/Nlength', $this->_string_shift($response, 8))); + $data = $this->_string_shift($response, $length); + if ($client_channel == $channel) { + return $data; + } + if (!isset($this->channel_buffers[$client_channel])) { + $this->channel_buffers[$client_channel] = array(); + } + $this->channel_buffers[$client_channel][] = $data; + break; + case NET_SSH2_MSG_CHANNEL_REQUEST: + extract(unpack('Nlength', $this->_string_shift($response, 4))); + $value = $this->_string_shift($response, $length); + switch ($value) { + case 'exit-signal': + $this->_string_shift($response, 1); + extract(unpack('Nlength', $this->_string_shift($response, 4))); + $this->errors[] = 'SSH_MSG_CHANNEL_REQUEST (exit-signal): ' . $this->_string_shift($response, $length); + $this->_string_shift($response, 1); + extract(unpack('Nlength', $this->_string_shift($response, 4))); + if ($length) { + $this->errors[count($this->errors)].= "\r\n" . $this->_string_shift($response, $length); + } + case 'exit-status': + // "The channel needs to be closed with SSH_MSG_CHANNEL_CLOSE after this message." + // -- http://tools.ietf.org/html/rfc4254#section-6.10 + $this->_send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_EOF, $this->server_channels[$client_channel])); + $this->_send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_CLOSE, $this->server_channels[$channel])); + + $this->channel_status[$channel] = NET_SSH2_MSG_CHANNEL_EOF; + default: + // "Some systems may not implement signals, in which case they SHOULD ignore this message." + // -- http://tools.ietf.org/html/rfc4254#section-6.9 + break; + } + break; + case NET_SSH2_MSG_CHANNEL_CLOSE: + $this->curTimeout = 0; + + if ($this->bitmap & NET_SSH2_MASK_SHELL) { + $this->bitmap&= ~NET_SSH2_MASK_SHELL; + } + if ($this->channel_status[$channel] != NET_SSH2_MSG_CHANNEL_EOF) { + $this->_send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_CLOSE, $this->server_channels[$channel])); + } + + $this->channel_status[$channel] = NET_SSH2_MSG_CHANNEL_CLOSE; + return true; + case NET_SSH2_MSG_CHANNEL_EOF: + break; + default: + user_error('Error reading channel data', E_USER_NOTICE); + return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION); + } + } + } + + /** + * Sends Binary Packets + * + * See '6. Binary Packet Protocol' of rfc4253 for more info. + * + * @param String $data + * @see Net_SSH2::_get_binary_packet() + * @return Boolean + * @access private + */ + function _send_binary_packet($data) + { + if (!is_resource($this->fsock) || feof($this->fsock)) { + user_error('Connection closed prematurely', E_USER_NOTICE); + return false; + } + + //if ($this->compress) { + // // the -4 removes the checksum: + // // http://php.net/function.gzcompress#57710 + // $data = substr(gzcompress($data), 0, -4); + //} + + // 4 (packet length) + 1 (padding length) + 4 (minimal padding amount) == 9 + $packet_length = strlen($data) + 9; + // round up to the nearest $this->encrypt_block_size + $packet_length+= (($this->encrypt_block_size - 1) * $packet_length) % $this->encrypt_block_size; + // subtracting strlen($data) is obvious - subtracting 5 is necessary because of packet_length and padding_length + $padding_length = $packet_length - strlen($data) - 5; + + $padding = ''; + for ($i = 0; $i < $padding_length; $i++) { + $padding.= chr(crypt_random(0, 255)); + } + + // we subtract 4 from packet_length because the packet_length field isn't supposed to include itself + $packet = pack('NCa*', $packet_length - 4, $padding_length, $data . $padding); + + $hmac = $this->hmac_create !== false ? $this->hmac_create->hash(pack('Na*', $this->send_seq_no, $packet)) : ''; + $this->send_seq_no++; + + if ($this->encrypt !== false) { + $packet = $this->encrypt->encrypt($packet); + } + + $packet.= $hmac; + + $start = strtok(microtime(), ' ') + strtok(''); // http://php.net/microtime#61838 + $result = strlen($packet) == fputs($this->fsock, $packet); + $stop = strtok(microtime(), ' ') + strtok(''); + + if (defined('NET_SSH2_LOGGING')) { + $message_number = isset($this->message_numbers[ord($data[0])]) ? $this->message_numbers[ord($data[0])] : 'UNKNOWN (' . ord($data[0]) . ')'; + $message_number = '-> ' . $message_number . + ' (' . round($stop - $start, 4) . 's)'; + $this->_append_log($message_number, $data); + } + + return $result; + } + + /** + * Logs data packets + * + * Makes sure that only the last 1MB worth of packets will be logged + * + * @param String $data + * @access private + */ + function _append_log($message_number, $message) + { + switch (NET_SSH2_LOGGING) { + // useful for benchmarks + case NET_SSH2_LOG_SIMPLE: + $this->message_number_log[] = $message_number; + break; + // the most useful log for SSH2 + case NET_SSH2_LOG_COMPLEX: + $this->message_number_log[] = $message_number; + $this->_string_shift($message); + $this->log_size+= strlen($message); + $this->message_log[] = $message; + while ($this->log_size > NET_SSH2_LOG_MAX_SIZE) { + $this->log_size-= strlen(array_shift($this->message_log)); + array_shift($this->message_number_log); + } + break; + // dump the output out realtime; packets may be interspersed with non packets, + // passwords won't be filtered out and select other packets may not be correctly + // identified + case NET_SSH2_LOG_REALTIME: + echo "
\r\n" . $this->_format_log(array($message), array($message_number)) . "\r\n
\r\n"; + flush(); + ob_flush(); + break; + // basically the same thing as NET_SSH2_LOG_REALTIME with the caveat that NET_SSH2_LOG_REALTIME_FILE + // needs to be defined and that the resultant log file will be capped out at NET_SSH2_LOG_MAX_SIZE. + // the earliest part of the log file is denoted by the first <<< START >>> and is not going to necessarily + // at the beginning of the file + case NET_SSH2_LOG_REALTIME_FILE: + if (!isset($this->realtime_log_file)) { + // PHP doesn't seem to like using constants in fopen() + $filename = NET_SSH2_LOG_REALTIME_FILE; + $fp = fopen($filename, 'w'); + $this->realtime_log_file = $fp; + } + if (!is_resource($this->realtime_log_file)) { + break; + } + $entry = $this->_format_log(array($message), array($message_number)); + if ($this->realtime_log_wrap) { + $temp = "<<< START >>>\r\n"; + $entry.= $temp; + fseek($this->realtime_log_file, ftell($this->realtime_log_file) - strlen($temp)); + } + $this->realtime_log_size+= strlen($entry); + if ($this->realtime_log_size > NET_SSH2_LOG_MAX_SIZE) { + fseek($this->realtime_log_file, 0); + $this->realtime_log_size = strlen($entry); + $this->realtime_log_wrap = true; + } + fputs($this->realtime_log_file, $entry); + } + } + + /** + * Sends channel data + * + * Spans multiple SSH_MSG_CHANNEL_DATAs if appropriate + * + * @param Integer $client_channel + * @param String $data + * @return Boolean + * @access private + */ + function _send_channel_packet($client_channel, $data) + { + while (strlen($data) > $this->packet_size_client_to_server[$client_channel]) { + // resize the window, if appropriate + $this->window_size_client_to_server[$client_channel]-= $this->packet_size_client_to_server[$client_channel]; + if ($this->window_size_client_to_server[$client_channel] < 0) { + $packet = pack('CNN', NET_SSH2_MSG_CHANNEL_WINDOW_ADJUST, $this->server_channels[$client_channel], $this->window_size); + if (!$this->_send_binary_packet($packet)) { + return false; + } + $this->window_size_client_to_server[$client_channel]+= $this->window_size; + } + + $packet = pack('CN2a*', + NET_SSH2_MSG_CHANNEL_DATA, + $this->server_channels[$client_channel], + $this->packet_size_client_to_server[$client_channel], + $this->_string_shift($data, $this->packet_size_client_to_server[$client_channel]) + ); + + if (!$this->_send_binary_packet($packet)) { + return false; + } + } + + // resize the window, if appropriate + $this->window_size_client_to_server[$client_channel]-= strlen($data); + if ($this->window_size_client_to_server[$client_channel] < 0) { + $packet = pack('CNN', NET_SSH2_MSG_CHANNEL_WINDOW_ADJUST, $this->server_channels[$client_channel], $this->window_size); + if (!$this->_send_binary_packet($packet)) { + return false; + } + $this->window_size_client_to_server[$client_channel]+= $this->window_size; + } + + return $this->_send_binary_packet(pack('CN2a*', + NET_SSH2_MSG_CHANNEL_DATA, + $this->server_channels[$client_channel], + strlen($data), + $data)); + } + + /** + * Closes and flushes a channel + * + * Net_SSH2 doesn't properly close most channels. For exec() channels are normally closed by the server + * and for SFTP channels are presumably closed when the client disconnects. This functions is intended + * for SCP more than anything. + * + * @param Integer $client_channel + * @return Boolean + * @access private + */ + function _close_channel($client_channel) + { + // see http://tools.ietf.org/html/rfc4254#section-5.3 + + $this->_send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_EOF, $this->server_channels[$client_channel])); + + $this->_send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_CLOSE, $this->server_channels[$client_channel])); + + $this->channel_status[$client_channel] = NET_SSH2_MSG_CHANNEL_CLOSE; + + $this->curTimeout = 0; + + while (!is_bool($this->_get_channel_packet($client_channel))); + + if ($this->bitmap & NET_SSH2_MASK_SHELL) { + $this->bitmap&= ~NET_SSH2_MASK_SHELL; + } + } + + /** + * Disconnect + * + * @param Integer $reason + * @return Boolean + * @access private + */ + function _disconnect($reason) + { + if ($this->bitmap) { + $data = pack('CNNa*Na*', NET_SSH2_MSG_DISCONNECT, $reason, 0, '', 0, ''); + $this->_send_binary_packet($data); + $this->bitmap = 0; + fclose($this->fsock); + return false; + } + } + + /** + * String Shift + * + * Inspired by array_shift + * + * @param String $string + * @param optional Integer $index + * @return String + * @access private + */ + function _string_shift(&$string, $index = 1) + { + $substr = substr($string, 0, $index); + $string = substr($string, $index); + return $substr; + } + + /** + * Define Array + * + * Takes any number of arrays whose indices are integers and whose values are strings and defines a bunch of + * named constants from it, using the value as the name of the constant and the index as the value of the constant. + * If any of the constants that would be defined already exists, none of the constants will be defined. + * + * @param Array $array + * @access private + */ + function _define_array() + { + $args = func_get_args(); + foreach ($args as $arg) { + foreach ($arg as $key=>$value) { + if (!defined($value)) { + define($value, $key); + } else { + break 2; + } + } + } + } + + /** + * Returns a log of the packets that have been sent and received. + * + * Returns a string if NET_SSH2_LOGGING == NET_SSH2_LOG_COMPLEX, an array if NET_SSH2_LOGGING == NET_SSH2_LOG_SIMPLE and false if !defined('NET_SSH2_LOGGING') + * + * @access public + * @return String or Array + */ + function getLog() + { + if (!defined('NET_SSH2_LOGGING')) { + return false; + } + + switch (NET_SSH2_LOGGING) { + case NET_SSH2_LOG_SIMPLE: + return $this->message_number_log; + break; + case NET_SSH2_LOG_COMPLEX: + return $this->_format_log($this->message_log, $this->message_number_log); + break; + default: + return false; + } + } + + /** + * Formats a log for printing + * + * @param Array $message_log + * @param Array $message_number_log + * @access private + * @return String + */ + function _format_log($message_log, $message_number_log) + { + static $boundary = ':', $long_width = 65, $short_width = 16; + + $output = ''; + for ($i = 0; $i < count($message_log); $i++) { + $output.= $message_number_log[$i] . "\r\n"; + $current_log = $message_log[$i]; + $j = 0; + do { + if (!empty($current_log)) { + $output.= str_pad(dechex($j), 7, '0', STR_PAD_LEFT) . '0 '; + } + $fragment = $this->_string_shift($current_log, $short_width); + $hex = substr( + preg_replace( + '#(.)#es', + '"' . $boundary . '" . str_pad(dechex(ord(substr("\\1", -1))), 2, "0", STR_PAD_LEFT)', + $fragment), + strlen($boundary) + ); + // replace non ASCII printable characters with dots + // http://en.wikipedia.org/wiki/ASCII#ASCII_printable_characters + // also replace < with a . since < messes up the output on web browsers + $raw = preg_replace('#[^\x20-\x7E]|<#', '.', $fragment); + $output.= str_pad($hex, $long_width - $short_width, ' ') . $raw . "\r\n"; + $j++; + } while (!empty($current_log)); + $output.= "\r\n"; + } + + return $output; + } + + /** + * Returns all errors + * + * @return String + * @access public + */ + function getErrors() + { + return $this->errors; + } + + /** + * Returns the last error + * + * @return String + * @access public + */ + function getLastError() + { + return $this->errors[count($this->errors) - 1]; + } + + /** + * Return the server identification. + * + * @return String + * @access public + */ + function getServerIdentification() + { + return $this->server_identifier; + } + + /** + * Return a list of the key exchange algorithms the server supports. + * + * @return Array + * @access public + */ + function getKexAlgorithms() + { + return $this->kex_algorithms; + } + + /** + * Return a list of the host key (public key) algorithms the server supports. + * + * @return Array + * @access public + */ + function getServerHostKeyAlgorithms() + { + return $this->server_host_key_algorithms; + } + + /** + * Return a list of the (symmetric key) encryption algorithms the server supports, when receiving stuff from the client. + * + * @return Array + * @access public + */ + function getEncryptionAlgorithmsClient2Server() + { + return $this->encryption_algorithms_client_to_server; + } + + /** + * Return a list of the (symmetric key) encryption algorithms the server supports, when sending stuff to the client. + * + * @return Array + * @access public + */ + function getEncryptionAlgorithmsServer2Client() + { + return $this->encryption_algorithms_server_to_client; + } + + /** + * Return a list of the MAC algorithms the server supports, when receiving stuff from the client. + * + * @return Array + * @access public + */ + function getMACAlgorithmsClient2Server() + { + return $this->mac_algorithms_client_to_server; + } + + /** + * Return a list of the MAC algorithms the server supports, when sending stuff to the client. + * + * @return Array + * @access public + */ + function getMACAlgorithmsServer2Client() + { + return $this->mac_algorithms_server_to_client; + } + + /** + * Return a list of the compression algorithms the server supports, when receiving stuff from the client. + * + * @return Array + * @access public + */ + function getCompressionAlgorithmsClient2Server() + { + return $this->compression_algorithms_client_to_server; + } + + /** + * Return a list of the compression algorithms the server supports, when sending stuff to the client. + * + * @return Array + * @access public + */ + function getCompressionAlgorithmsServer2Client() + { + return $this->compression_algorithms_server_to_client; + } + + /** + * Return a list of the languages the server supports, when sending stuff to the client. + * + * @return Array + * @access public + */ + function getLanguagesServer2Client() + { + return $this->languages_server_to_client; + } + + /** + * Return a list of the languages the server supports, when receiving stuff from the client. + * + * @return Array + * @access public + */ + function getLanguagesClient2Server() + { + return $this->languages_client_to_server; + } + + /** + * Returns the server public host key. + * + * Caching this the first time you connect to a server and checking the result on subsequent connections + * is recommended. Returns false if the server signature is not signed correctly with the public host key. + * + * @return Mixed + * @access public + */ + function getServerPublicHostKey() + { + $signature = $this->signature; + $server_public_host_key = $this->server_public_host_key; + + extract(unpack('Nlength', $this->_string_shift($server_public_host_key, 4))); + $this->_string_shift($server_public_host_key, $length); + + if ($this->signature_validated) { + return $this->bitmap ? + $this->signature_format . ' ' . base64_encode($this->server_public_host_key) : + false; + } + + $this->signature_validated = true; + + switch ($this->signature_format) { + case 'ssh-dss': + $temp = unpack('Nlength', $this->_string_shift($server_public_host_key, 4)); + $p = new Math_BigInteger($this->_string_shift($server_public_host_key, $temp['length']), -256); + + $temp = unpack('Nlength', $this->_string_shift($server_public_host_key, 4)); + $q = new Math_BigInteger($this->_string_shift($server_public_host_key, $temp['length']), -256); + + $temp = unpack('Nlength', $this->_string_shift($server_public_host_key, 4)); + $g = new Math_BigInteger($this->_string_shift($server_public_host_key, $temp['length']), -256); + + $temp = unpack('Nlength', $this->_string_shift($server_public_host_key, 4)); + $y = new Math_BigInteger($this->_string_shift($server_public_host_key, $temp['length']), -256); + + /* The value for 'dss_signature_blob' is encoded as a string containing + r, followed by s (which are 160-bit integers, without lengths or + padding, unsigned, and in network byte order). */ + $temp = unpack('Nlength', $this->_string_shift($signature, 4)); + if ($temp['length'] != 40) { + user_error('Invalid signature', E_USER_NOTICE); + return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); + } + + $r = new Math_BigInteger($this->_string_shift($signature, 20), 256); + $s = new Math_BigInteger($this->_string_shift($signature, 20), 256); + + if ($r->compare($q) >= 0 || $s->compare($q) >= 0) { + user_error('Invalid signature', E_USER_NOTICE); + return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); + } + + $w = $s->modInverse($q); + + $u1 = $w->multiply(new Math_BigInteger(sha1($this->exchange_hash), 16)); + list(, $u1) = $u1->divide($q); + + $u2 = $w->multiply($r); + list(, $u2) = $u2->divide($q); + + $g = $g->modPow($u1, $p); + $y = $y->modPow($u2, $p); + + $v = $g->multiply($y); + list(, $v) = $v->divide($p); + list(, $v) = $v->divide($q); + + if (!$v->equals($r)) { + user_error('Bad server signature', E_USER_NOTICE); + return $this->_disconnect(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE); + } + + break; + case 'ssh-rsa': + $temp = unpack('Nlength', $this->_string_shift($server_public_host_key, 4)); + $e = new Math_BigInteger($this->_string_shift($server_public_host_key, $temp['length']), -256); + + $temp = unpack('Nlength', $this->_string_shift($server_public_host_key, 4)); + $n = new Math_BigInteger($this->_string_shift($server_public_host_key, $temp['length']), -256); + $nLength = $temp['length']; + + /* + $temp = unpack('Nlength', $this->_string_shift($signature, 4)); + $signature = $this->_string_shift($signature, $temp['length']); + + if (!class_exists('Crypt_RSA')) { + require_once('Crypt/RSA.php'); + } + + $rsa = new Crypt_RSA(); + $rsa->setSignatureMode(CRYPT_RSA_SIGNATURE_PKCS1); + $rsa->loadKey(array('e' => $e, 'n' => $n), CRYPT_RSA_PUBLIC_FORMAT_RAW); + if (!$rsa->verify($this->exchange_hash, $signature)) { + user_error('Bad server signature', E_USER_NOTICE); + return $this->_disconnect(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE); + } + */ + + $temp = unpack('Nlength', $this->_string_shift($signature, 4)); + $s = new Math_BigInteger($this->_string_shift($signature, $temp['length']), 256); + + // validate an RSA signature per "8.2 RSASSA-PKCS1-v1_5", "5.2.2 RSAVP1", and "9.1 EMSA-PSS" in the + // following URL: + // ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-1/pkcs-1v2-1.pdf + + // also, see SSHRSA.c (rsa2_verifysig) in PuTTy's source. + + if ($s->compare(new Math_BigInteger()) < 0 || $s->compare($n->subtract(new Math_BigInteger(1))) > 0) { + user_error('Invalid signature', E_USER_NOTICE); + return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); + } + + $s = $s->modPow($e, $n); + $s = $s->toBytes(); + + $h = pack('N4H*', 0x00302130, 0x0906052B, 0x0E03021A, 0x05000414, sha1($this->exchange_hash)); + $h = chr(0x01) . str_repeat(chr(0xFF), $nLength - 3 - strlen($h)) . $h; + + if ($s != $h) { + user_error('Bad server signature', E_USER_NOTICE); + return $this->_disconnect(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE); + } + break; + default: + user_error('Unsupported signature format', E_USER_NOTICE); + return $this->_disconnect(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE); + } + + return $this->signature_format . ' ' . base64_encode($this->server_public_host_key); + } +} \ No newline at end of file diff --git a/3rdparty/phpseclib/openssl.cnf b/3rdparty/phpseclib/openssl.cnf new file mode 100644 index 00000000000..c1337102dc4 --- /dev/null +++ b/3rdparty/phpseclib/openssl.cnf @@ -0,0 +1,6 @@ +# minimalist openssl.cnf file for use with phpseclib + +HOME = . +RANDFILE = $ENV::HOME/.rnd + +[ v3_ca ] \ No newline at end of file diff --git a/3rdparty/phpseclib/version.txt b/3rdparty/phpseclib/version.txt new file mode 100644 index 00000000000..4c73644dbde --- /dev/null +++ b/3rdparty/phpseclib/version.txt @@ -0,0 +1 @@ +0.3.1 diff --git a/apps/files_encryption/appinfo/app.php b/apps/files_encryption/appinfo/app.php index 7a8eee41bb5..6c082b1938c 100644 --- a/apps/files_encryption/appinfo/app.php +++ b/apps/files_encryption/appinfo/app.php @@ -12,6 +12,7 @@ OC_FileProxy::register(new OCA\Encryption\Proxy()); 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', 'OCA\Encryption\Stream'); diff --git a/apps/files_encryption/hooks/hooks.php b/apps/files_encryption/hooks/hooks.php index 9752dbf0a15..8e391ca3888 100644 --- a/apps/files_encryption/hooks/hooks.php +++ b/apps/files_encryption/hooks/hooks.php @@ -22,6 +22,12 @@ namespace OCA\Encryption; +// Include PHPSecLib for passphrase manipulation functions +require_once \OC::$SERVERROOT . '/' . '3rdparty' . '/' . 'phpseclib' . '/' . 'Math' . '/' . 'BigInteger.php'; +require_once \OC::$SERVERROOT . '/' . '3rdparty' . '/' . 'phpseclib' . '/' . 'Crypt' . '/' . 'Hash.php'; +require_once \OC::$SERVERROOT . '/' . '3rdparty' . '/' . 'phpseclib' . '/' . 'Crypt' . '/' . 'Random.php'; +require_once \OC::$SERVERROOT . '/' . '3rdparty' . '/' . 'phpseclib' . '/' . 'Crypt' . '/' . 'RSA.php'; + /** * Class for hook specific logic */ @@ -34,7 +40,6 @@ class Hooks { * @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' ) { @@ -89,20 +94,74 @@ class Hooks { 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' ) { + + $rsa = new \Crypt_RSA(); + + // Load old passphrase + $rsa->setPassword( $params['password'] ); + + // Load user's private key + $rsa->loadKey( $_SESSION['privateKey'] ); + + // Set new passphrase + $rsa->setPassword('new_password'); + + // Get modified private key + $privateKey = $rsa->getPrivateKey(); + + // Save private key + Keymanager::setPrivateKey( $privateKey ); + + // Get modified public key + $publicKey = $rsa->getPublicKey(); + + // Save public key + Keymanager::setPublicKey( $publicKey ); + + # NOTE: Do we need to update session manually here or + # will forced logout see to this? + + } + + } /** * @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']); + + 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 a encryption key for the file!"); + + \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!" ); + + } + } + } + } ?> \ No newline at end of file diff --git a/apps/files_encryption/lib/keymanager.php b/apps/files_encryption/lib/keymanager.php index 9eb9bad3db4..55d7530c466 100755 --- a/apps/files_encryption/lib/keymanager.php +++ b/apps/files_encryption/lib/keymanager.php @@ -196,12 +196,19 @@ class Keymanager { * @param string key * @return bool true/false */ - public static function setPrivateKey($key) { + public static function setPrivateKey( $key ) { $user = \OCP\User::getUser(); - $view = new \OC_FilesystemView('/'.$user.'/files_encryption'); - if (!$view->file_exists('')) $view->mkdir(''); - return $view->file_put_contents($user.'.private.key', $key); + + $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; } @@ -224,11 +231,17 @@ class Keymanager { * @param string key * @return bool true/false */ - public static function setPublicKey($key) { + 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 ); - $view = new \OC_FilesystemView('/public-keys'); - if (!$view->file_exists('')) $view->mkdir(''); - return $view->file_put_contents(\OCP\User::getUser().'.public.key', $key); + \OC_FileProxy::$enabled = true; } diff --git a/lib/base.php b/lib/base.php index 3cec1449474..7809b07e552 100644 --- a/lib/base.php +++ b/lib/base.php @@ -351,9 +351,9 @@ class OC{ self::initPaths(); - register_shutdown_function(array('OC_Log', 'onShutdown')); - set_error_handler(array('OC_Log', 'onError')); - set_exception_handler(array('OC_Log', 'onException')); +// register_shutdown_function(array('OC_Log', 'onShutdown')); +// set_error_handler(array('OC_Log', 'onError')); +// set_exception_handler(array('OC_Log', 'onException')); // set debug mode if an xdebug session is active if (!defined('DEBUG') || !DEBUG) { diff --git a/lib/filestorage/local.php b/lib/filestorage/local.php index 726ffeb33cd..ff005c457cc 100644 --- a/lib/filestorage/local.php +++ b/lib/filestorage/local.php @@ -75,7 +75,7 @@ class OC_Filestorage_Local extends OC_Filestorage_Common{ return $result; } - public function file_get_contents($path) { + public function file_get_contents($path) {//trigger_error("path (get contents) = ".var_export($path, 1)); return file_get_contents($this->datadir.$path); } public function file_put_contents($path,$data) { @@ -108,7 +108,7 @@ class OC_Filestorage_Local extends OC_Filestorage_Common{ } return copy($this->datadir.$path1,$this->datadir.$path2); } - public function fopen($path,$mode) { + public function fopen($path,$mode) {//trigger_error("path (fopen) = ".var_export($path, 1)); if($return=fopen($this->datadir.$path,$mode)) { switch($mode) { case 'r': diff --git a/lib/filesystemview.php b/lib/filesystemview.php index 5df2bd09bbc..d7e60a8926e 100644 --- a/lib/filesystemview.php +++ b/lib/filesystemview.php @@ -40,6 +40,9 @@ /** * @note default root (if $root is empty or '/') is /data/[user]/ + * @note If you don't include a leading slash, you may encounter problems. + * e.g. use $v = new \OC_FilesystemView( '/' . $params['uid'] ); not + * $v = new \OC_FilesystemView( $params['uid'] ); */ class OC_FilesystemView { private $fakeRoot=''; diff --git a/lib/user.php b/lib/user.php index e577002650c..538a9002bba 100644 --- a/lib/user.php +++ b/lib/user.php @@ -309,12 +309,11 @@ class OC_User { } /** - * @brief Set password - * @param $uid The username - * @param $password The new password - * @returns true/false - * - * Change the password of a user + * @brief Change the password of a user + * @param string $uid The username + * @param string $password The new password + * @returns bool + * @note Emit hooks: pre_setPassword, post_setPassword */ public static function setPassword( $uid, $password ) { $run = true; @@ -333,8 +332,7 @@ class OC_User { OC_Preferences::deleteApp($uid, 'login_token'); OC_Hook::emit( "OC_User", "post_setPassword", array( "uid" => $uid, "password" => $password )); return $success; - } - else{ + } else { return false; } } -- cgit v1.2.3 From b66d38ecae3a2e7914520a90c5ef01cbc1432c10 Mon Sep 17 00:00:00 2001 From: Sam Tuke Date: Tue, 11 Dec 2012 15:10:39 +0000 Subject: Revert "Development snapshot" This reverts commit c56fb905d1a300b2fe6c011848ea520031ea0df1. --- apps/files_encryption/appinfo/app.php | 9 +---- apps/files_encryption/hooks/hooks.php | 10 ++--- apps/files_encryption/lib/crypt.php | 25 ++++++------ apps/files_encryption/lib/keymanager.php | 20 ++-------- apps/files_encryption/lib/proxy.php | 27 ++++--------- apps/files_encryption/lib/session.php | 66 ------------------------------- apps/files_encryption/lib/stream.php | 54 +++++++------------------ apps/files_encryption/lib/util.php | 4 +- apps/files_encryption/tests/crypt.php | 67 ++++++++++++-------------------- apps/files_encryption/tests/proxy.php | 19 +++------ 10 files changed, 74 insertions(+), 227 deletions(-) delete mode 100644 apps/files_encryption/lib/session.php (limited to 'apps/files_encryption/lib') diff --git a/apps/files_encryption/appinfo/app.php b/apps/files_encryption/appinfo/app.php index 6c082b1938c..45f43d70ff0 100644 --- a/apps/files_encryption/appinfo/app.php +++ b/apps/files_encryption/appinfo/app.php @@ -6,7 +6,6 @@ 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 OCA\Encryption\Proxy()); @@ -16,13 +15,7 @@ OCP\Util::connectHook('OC_User','post_setPassword','OCA\Encryption\Hooks','setPa stream_wrapper_register( 'crypt', 'OCA\Encryption\Stream'); -$session = new OCA\Encryption\Session(); - -if ( -! $session->getPrivateKey( \OCP\USER::getUser() ) -&& OCP\User::isLoggedIn() -&& OCA\Encryption\Crypt::mode() == 'server' -) { +if( !isset( $_SESSION['enckey'] ) && 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(); diff --git a/apps/files_encryption/hooks/hooks.php b/apps/files_encryption/hooks/hooks.php index 8e391ca3888..5cb59dbbf82 100644 --- a/apps/files_encryption/hooks/hooks.php +++ b/apps/files_encryption/hooks/hooks.php @@ -70,11 +70,11 @@ class Hooks { // trigger_error( "\$params['password'] = {$params['password']}" ); - $privateKey = Crypt::symmetricDecryptFileContent( $encryptedKey, $params['password'] ); + $_SESSION['enckey'] = Crypt::symmetricDecryptFileContent( $encryptedKey, $params['password'] ); - $session = new Session(); - - $session->setPrivateKey( $privateKey, $params['uid'] ); + \OC_FileProxy::$enabled = false; + file_put_contents( '/home/samtuke/enckey', $_SESSION['enckey'] ); + \OC_FileProxy::$enabled = true; $view1 = new \OC_FilesystemView( '/' . $params['uid'] ); @@ -86,7 +86,7 @@ class Hooks { ) { $_SESSION['legacyenckey'] = Crypt::legacyDecrypt( $legacyKey, $params['password'] ); -// trigger_error('leg enc key = '.$_SESSION['legacyenckey']); + trigger_error('leg enc key = '.$_SESSION['legacyenckey']); } // } diff --git a/apps/files_encryption/lib/crypt.php b/apps/files_encryption/lib/crypt.php index 5e1078c9e1b..8df3cd43270 100755 --- a/apps/files_encryption/lib/crypt.php +++ b/apps/files_encryption/lib/crypt.php @@ -305,9 +305,9 @@ class Crypt { if ( $encryptedContent = self::encrypt( $plainContent, $iv, $passphrase ) ) { // Combine content to encrypt with IV identifier and actual IV - $catfile = self::concatIv( $encryptedContent, $iv ); + $combinedKeyfile = self::concatIv( $encryptedContent, $iv ); - $padded = self::addPadding( $catfile ); + $padded = self::addPadding( $combinedKeyfile ); return $padded; @@ -468,8 +468,7 @@ class Crypt { /** * @brief Encrypts content symmetrically and generates keyfile asymmetrically - * @returns array containing catfile and new keyfile. - * keys: data, key + * @returns array keys: encrypted, key * @note this method is a wrapper for combining other crypt class methods */ public static function keyEncryptKeyfile( $plainContent, $publicKey ) { @@ -485,20 +484,18 @@ class Crypt { } /** - * @brief Takes catfile, keyfile, and private key, and + * @brief Takes encrypted data, encrypted catfile, and private key, and * performs decryption * @returns decrypted content * @note this method is a wrapper for combining other crypt class methods */ - public static function keyDecryptKeyfile( $catfile, $keyfile, $privateKey ) { + public static function keyDecryptKeyfile( $encryptedData, $encryptedKey, $privateKey ) { - // Decrypt the keyfile with the user's private key - $decryptedKey = self::keyDecrypt( $keyfile, $privateKey ); + // Decrypt keyfile + $decryptedKey = self::keyDecrypt( $encryptedKey, $privateKey ); -// trigger_error( "\$keyfile = ".var_export($keyfile, 1)); - - // Decrypt the catfile symmetrically using the decrypted keyfile - $decryptedData = self::symmetricDecryptFileContent( $catfile, $decryptedKey ); + // Decrypt encrypted file + $decryptedData = self::symmetricDecryptFileContent( $encryptedData, $decryptedKey ); return $decryptedData; @@ -687,7 +684,7 @@ class Crypt { */ public static function legacyEncrypt( $content, $passphrase = '' ) { - //trigger_error("OC2 enc \$content = $content \$passphrase = ".var_export($passphrase, 1) ); + trigger_error("OC2 enc \$content = $content \$passphrase = ".var_export($passphrase, 1) ); $bf = self::getBlowfish( $passphrase ); @@ -711,7 +708,7 @@ class Crypt { $bf = self::getBlowfish( "67362885833455692562" ); -// trigger_error(var_export($bf, 1) ); + trigger_error(var_export($bf, 1) ); $decrypted = $bf->decrypt( $content ); diff --git a/apps/files_encryption/lib/keymanager.php b/apps/files_encryption/lib/keymanager.php index 55d7530c466..2f730971288 100755 --- a/apps/files_encryption/lib/keymanager.php +++ b/apps/files_encryption/lib/keymanager.php @@ -46,19 +46,11 @@ class Keymanager { * @brief retrieve public key for a specified user * @return string public key or false */ - public static function getPublicKey( $userId = NULL ) { + public static function getPublicKey() { - // If the username wasn't specified, fetch it - if ( ! $userId ) { - - $userId = \OCP\User::getUser(); - - } - - // Create new view with the right + $user = \OCP\User::getUser(); $view = new \OC_FilesystemView( '/public-keys/' ); - - return $view->file_get_contents( '/' . $userId . '.public.key' ); + return $view->file_get_contents( '/' . $user . '.public.key' ); } @@ -127,12 +119,10 @@ class Keymanager { } /** - * @brief retrieve keyfile for an encrypted file + * @brief retrieve file encryption key * * @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( $path, $staticUserClass = 'OCP\User' ) { @@ -251,8 +241,6 @@ class Keymanager { * @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') { diff --git a/apps/files_encryption/lib/proxy.php b/apps/files_encryption/lib/proxy.php index 85664734d7a..914632d3387 100644 --- a/apps/files_encryption/lib/proxy.php +++ b/apps/files_encryption/lib/proxy.php @@ -131,10 +131,6 @@ class Proxy extends \OC_FileProxy { } - /** - * @param string $path Path of file from which has been read - * @param string $data Data that has been read from file - */ public function postFile_get_contents( $path, $data ) { # TODO: Use dependency injection to add required args for view and user etc. to this method @@ -142,27 +138,24 @@ class Proxy extends \OC_FileProxy { // Disable encryption proxy to prevent recursive calls \OC_FileProxy::$enabled = false; - // If data is a catfile if ( Crypt::mode() == 'server' && Crypt::isEncryptedContent( $data ) ) { -// trigger_error("bong"); + //trigger_error("bong"); - $split = explode( '/', $path ); + $filePath = explode( '/', $path ); - $filePath = array_slice( $split, 3 ); + $filePath = array_slice( $filePath, 3 ); $filePath = '/' . implode( '/', $filePath ); //$cached = \OC_FileCache_Cached::get( $path, '' ); $keyFile = Keymanager::getFileKey( $filePath ); - - $session = new Session(); - - $decrypted = Crypt::keyDecryptKeyfile( $data, $keyFile, $session->getPrivateKey( $split[1] ) ); + $data = Crypt::keyDecryptKeyfile( $data, $keyFile, $_SESSION['enckey'] ); + } elseif ( Crypt::mode() == 'server' && isset( $_SESSION['legacyenckey'] ) @@ -170,20 +163,14 @@ class Proxy extends \OC_FileProxy { ) { trigger_error("mong"); - $decrypted = Crypt::legacyDecrypt( $data, $_SESSION['legacyenckey'] ); + $data = Crypt::legacyDecrypt( $data, $_SESSION['legacyenckey'] ); //trigger_error($data); } \OC_FileProxy::$enabled = true; - if ( ! isset( $decrypted ) ) { - - $decrypted = $data; - - } - - return $decrypted; + return $data; } diff --git a/apps/files_encryption/lib/session.php b/apps/files_encryption/lib/session.php deleted file mode 100644 index 946e5a6eddd..00000000000 --- a/apps/files_encryption/lib/session.php +++ /dev/null @@ -1,66 +0,0 @@ -. - * - */ - -namespace OCA\Encryption; - -/** - * Class for handling encryption related session data - */ - -class Session { - - /** - * @brief Sets user id for session and triggers emit - * @return bool - * - */ - public static 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 static function getPrivateKey( $userId ) { - - if ( - isset( $_SESSION['privateKey'] ) - && !empty( $_SESSION['privateKey'] ) - ) { - - return $_SESSION['privateKey']; - - } else { - - return false; - - } - - } - -} \ No newline at end of file diff --git a/apps/files_encryption/lib/stream.php b/apps/files_encryption/lib/stream.php index ac5fadd4e03..74dff1531a9 100644 --- a/apps/files_encryption/lib/stream.php +++ b/apps/files_encryption/lib/stream.php @@ -59,9 +59,7 @@ class Stream { private $count; private $writeCache; public $size; - private $publicKey; private $keyfile; - private $encKeyfile; private static $view; public function stream_open( $path, $mode, $options, &$opened_path ) { @@ -248,7 +246,7 @@ class Stream { * @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() { + public function getKey( $generate = true ) { //echo "\n\$this->rawPath = {$this->rawPath}"; @@ -258,37 +256,23 @@ class Stream { # TODO: add error handling for when file exists but no keyfile // Fetch existing keyfile - $this->encKeyfile = Keymanager::getFileKey( $this->rawPath ); - - $this->getUser(); - - $session = new Session(); - - $this->keyfile = Crypt::keyDecrypt( $this->encKeyfile, $session->getPrivateKey( $this->userId ) ); + $this->keyfile = Keymanager::getFileKey( $this->rawPath ); 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(); - + if ( $generate ) { + + // If the data is to be written to a new file, generate a new keyfile + $this->keyfile = Crypt::generateKey(); + + return false; + + } + } - # TODO: Add a method for getting the user in case OCP\User:: - # getUser() doesn't work (can that scenario ever occur?) - } /** @@ -322,23 +306,15 @@ class Stream { //echo "\$pointer = $pointer\n"; - // Make sure the userId is set - $this->getuser(); + # TODO: Move this user call out of here - it belongs elsewhere + $user = \OCP\User::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->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 + // Save keyfile in parallel directory structure + Keymanager::setFileKey( $this->rawPath, $this->keyfile, new \OC_FilesystemView( '/' ) ); } diff --git a/apps/files_encryption/lib/util.php b/apps/files_encryption/lib/util.php index 77f8dffe00f..907a04e5c00 100644 --- a/apps/files_encryption/lib/util.php +++ b/apps/files_encryption/lib/util.php @@ -45,7 +45,6 @@ class Util { ## DONE: files created via web ui are encrypted ## DONE: file created & encrypted via web ui are readable in web ui - ## DONE: file created & encrypted via web ui are readable via webdav # WebDAV: @@ -53,7 +52,8 @@ class Util { ## DONE: new data filled files added via webdav get encrypted ## DONE: new data filled files added via webdav are readable via webdav ## DONE: reading unencrypted files when encryption is enabled works via webdav - ## DONE: files created & encrypted via web ui are readable via webdav + + # TODO: files created & encrypted via web ui are readable via webdav # Legacy support: diff --git a/apps/files_encryption/tests/crypt.php b/apps/files_encryption/tests/crypt.php index f72f15ca236..09347dd578a 100755 --- a/apps/files_encryption/tests/crypt.php +++ b/apps/files_encryption/tests/crypt.php @@ -21,10 +21,6 @@ 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_Crypt extends \PHPUnit_Framework_TestCase { function setUp() { @@ -45,6 +41,8 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { $this->userId = 'admin'; $this->pass = 'admin'; + + \OC_User::setUserId( $this->userId ); } @@ -436,7 +434,6 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { } - // 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 @@ -459,22 +456,6 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { $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 ); - - } /** @@ -493,17 +474,17 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { } -// /** -// * @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 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 @@ -521,17 +502,17 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { } -// /** -// * @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 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 diff --git a/apps/files_encryption/tests/proxy.php b/apps/files_encryption/tests/proxy.php index 87151234e0e..8b2c92c2f53 100644 --- a/apps/files_encryption/tests/proxy.php +++ b/apps/files_encryption/tests/proxy.php @@ -45,17 +45,10 @@ class Test_Util extends \PHPUnit_Framework_TestCase { $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----- +$_SESSION['enckey'] = '-----BEGIN PRIVATE KEY----- MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDiH3EA4EpFA7Fx s2dyyfL5jwXeYXrTqQJ6DqKgGn8VsbT3eu8R9KzM2XitVwZe8c8L52DvJ06o5vg0 GqPYxilFdOFJe/ggac5Tq8UmJiZS4EqYEMwxBIfIyWTxeGV06/0HOwnVAkqHMcBz @@ -83,9 +76,7 @@ k1kbgyS7KKB7opVxI5+ChEqyUDijS3Y9FZixrRIWE6i2uGu86UG+v2lbKvSbM4Qm xvbOcX9OVMnlRb7n8woOP10UMY+ZE2x+YEUXQTLtPYq7F66e1OfxltstMxLQA+3d Y1d5piFV8PXK3Fg2F+Cj5qg= -----END PRIVATE KEY----- -' -, $this->userId -); +'; \OC_User::setUserId( $this->userId ); @@ -122,11 +113,11 @@ Y1d5piFV8PXK3Fg2F+Cj5qg= // // $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; +// $this->oldKey=isset($_SESSION['enckey'])?$_SESSION['enckey']:null; // // // //set testing key -// $_SESSION['privateKey']=md5(time()); +// $_SESSION['enckey']=md5(time()); // // //clear all proxies and hooks so we can do clean testing // OC_FileProxy::clearProxies(); @@ -150,7 +141,7 @@ Y1d5piFV8PXK3Fg2F+Cj5qg= // public function tearDown(){ // OCP\Config::setAppValue('files_encryption','enable_encryption',$this->oldConfig); // if(!is_null($this->oldKey)){ -// $_SESSION['privateKey']=$this->oldKey; +// $_SESSION['enckey']=$this->oldKey; // } // } // -- cgit v1.2.3 From a00dd2d5d6ba908e230af4b555ed0bc902cafd15 Mon Sep 17 00:00:00 2001 From: Sam Tuke Date: Tue, 11 Dec 2012 15:10:56 +0000 Subject: Revert "Revert "Development snapshot"" This reverts commit b66d38ecae3a2e7914520a90c5ef01cbc1432c10. --- apps/files_encryption/appinfo/app.php | 9 ++++- apps/files_encryption/hooks/hooks.php | 10 ++--- apps/files_encryption/lib/crypt.php | 25 ++++++------ apps/files_encryption/lib/keymanager.php | 20 ++++++++-- apps/files_encryption/lib/proxy.php | 27 +++++++++---- apps/files_encryption/lib/session.php | 66 +++++++++++++++++++++++++++++++ apps/files_encryption/lib/stream.php | 54 ++++++++++++++++++------- apps/files_encryption/lib/util.php | 4 +- apps/files_encryption/tests/crypt.php | 67 ++++++++++++++++++++------------ apps/files_encryption/tests/proxy.php | 19 ++++++--- 10 files changed, 227 insertions(+), 74 deletions(-) create mode 100644 apps/files_encryption/lib/session.php (limited to 'apps/files_encryption/lib') diff --git a/apps/files_encryption/appinfo/app.php b/apps/files_encryption/appinfo/app.php index 45f43d70ff0..6c082b1938c 100644 --- a/apps/files_encryption/appinfo/app.php +++ b/apps/files_encryption/appinfo/app.php @@ -6,6 +6,7 @@ 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 OCA\Encryption\Proxy()); @@ -15,7 +16,13 @@ OCP\Util::connectHook('OC_User','post_setPassword','OCA\Encryption\Hooks','setPa stream_wrapper_register( 'crypt', 'OCA\Encryption\Stream'); -if( !isset( $_SESSION['enckey'] ) && OCP\User::isLoggedIn() && OCA\Encryption\Crypt::mode() == 'server' ) { +$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(); diff --git a/apps/files_encryption/hooks/hooks.php b/apps/files_encryption/hooks/hooks.php index 5cb59dbbf82..8e391ca3888 100644 --- a/apps/files_encryption/hooks/hooks.php +++ b/apps/files_encryption/hooks/hooks.php @@ -70,11 +70,11 @@ class Hooks { // trigger_error( "\$params['password'] = {$params['password']}" ); - $_SESSION['enckey'] = Crypt::symmetricDecryptFileContent( $encryptedKey, $params['password'] ); + $privateKey = Crypt::symmetricDecryptFileContent( $encryptedKey, $params['password'] ); - \OC_FileProxy::$enabled = false; - file_put_contents( '/home/samtuke/enckey', $_SESSION['enckey'] ); - \OC_FileProxy::$enabled = true; + $session = new Session(); + + $session->setPrivateKey( $privateKey, $params['uid'] ); $view1 = new \OC_FilesystemView( '/' . $params['uid'] ); @@ -86,7 +86,7 @@ class Hooks { ) { $_SESSION['legacyenckey'] = Crypt::legacyDecrypt( $legacyKey, $params['password'] ); - trigger_error('leg enc key = '.$_SESSION['legacyenckey']); +// trigger_error('leg enc key = '.$_SESSION['legacyenckey']); } // } diff --git a/apps/files_encryption/lib/crypt.php b/apps/files_encryption/lib/crypt.php index 8df3cd43270..5e1078c9e1b 100755 --- a/apps/files_encryption/lib/crypt.php +++ b/apps/files_encryption/lib/crypt.php @@ -305,9 +305,9 @@ class Crypt { if ( $encryptedContent = self::encrypt( $plainContent, $iv, $passphrase ) ) { // Combine content to encrypt with IV identifier and actual IV - $combinedKeyfile = self::concatIv( $encryptedContent, $iv ); + $catfile = self::concatIv( $encryptedContent, $iv ); - $padded = self::addPadding( $combinedKeyfile ); + $padded = self::addPadding( $catfile ); return $padded; @@ -468,7 +468,8 @@ class Crypt { /** * @brief Encrypts content symmetrically and generates keyfile asymmetrically - * @returns array keys: encrypted, key + * @returns array containing catfile and new keyfile. + * keys: data, key * @note this method is a wrapper for combining other crypt class methods */ public static function keyEncryptKeyfile( $plainContent, $publicKey ) { @@ -484,18 +485,20 @@ class Crypt { } /** - * @brief Takes encrypted data, encrypted catfile, and private key, and + * @brief Takes catfile, keyfile, and private key, and * performs decryption * @returns decrypted content * @note this method is a wrapper for combining other crypt class methods */ - public static function keyDecryptKeyfile( $encryptedData, $encryptedKey, $privateKey ) { + public static function keyDecryptKeyfile( $catfile, $keyfile, $privateKey ) { - // Decrypt keyfile - $decryptedKey = self::keyDecrypt( $encryptedKey, $privateKey ); + // Decrypt the keyfile with the user's private key + $decryptedKey = self::keyDecrypt( $keyfile, $privateKey ); - // Decrypt encrypted file - $decryptedData = self::symmetricDecryptFileContent( $encryptedData, $decryptedKey ); +// trigger_error( "\$keyfile = ".var_export($keyfile, 1)); + + // Decrypt the catfile symmetrically using the decrypted keyfile + $decryptedData = self::symmetricDecryptFileContent( $catfile, $decryptedKey ); return $decryptedData; @@ -684,7 +687,7 @@ class Crypt { */ public static function legacyEncrypt( $content, $passphrase = '' ) { - trigger_error("OC2 enc \$content = $content \$passphrase = ".var_export($passphrase, 1) ); + //trigger_error("OC2 enc \$content = $content \$passphrase = ".var_export($passphrase, 1) ); $bf = self::getBlowfish( $passphrase ); @@ -708,7 +711,7 @@ class Crypt { $bf = self::getBlowfish( "67362885833455692562" ); - trigger_error(var_export($bf, 1) ); +// trigger_error(var_export($bf, 1) ); $decrypted = $bf->decrypt( $content ); diff --git a/apps/files_encryption/lib/keymanager.php b/apps/files_encryption/lib/keymanager.php index 2f730971288..55d7530c466 100755 --- a/apps/files_encryption/lib/keymanager.php +++ b/apps/files_encryption/lib/keymanager.php @@ -46,11 +46,19 @@ class Keymanager { * @brief retrieve public key for a specified user * @return string public key or false */ - public static function getPublicKey() { + public static function getPublicKey( $userId = NULL ) { - $user = \OCP\User::getUser(); + // If the username wasn't specified, fetch it + if ( ! $userId ) { + + $userId = \OCP\User::getUser(); + + } + + // Create new view with the right $view = new \OC_FilesystemView( '/public-keys/' ); - return $view->file_get_contents( '/' . $user . '.public.key' ); + + return $view->file_get_contents( '/' . $userId . '.public.key' ); } @@ -119,10 +127,12 @@ class Keymanager { } /** - * @brief retrieve file encryption key + * @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( $path, $staticUserClass = 'OCP\User' ) { @@ -241,6 +251,8 @@ class Keymanager { * @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') { diff --git a/apps/files_encryption/lib/proxy.php b/apps/files_encryption/lib/proxy.php index 914632d3387..85664734d7a 100644 --- a/apps/files_encryption/lib/proxy.php +++ b/apps/files_encryption/lib/proxy.php @@ -131,6 +131,10 @@ class Proxy extends \OC_FileProxy { } + /** + * @param string $path Path of file from which has been read + * @param string $data Data that has been read from file + */ public function postFile_get_contents( $path, $data ) { # TODO: Use dependency injection to add required args for view and user etc. to this method @@ -138,24 +142,27 @@ class Proxy extends \OC_FileProxy { // Disable encryption proxy to prevent recursive calls \OC_FileProxy::$enabled = false; + // If data is a catfile if ( Crypt::mode() == 'server' && Crypt::isEncryptedContent( $data ) ) { - //trigger_error("bong"); +// trigger_error("bong"); - $filePath = explode( '/', $path ); + $split = explode( '/', $path ); - $filePath = array_slice( $filePath, 3 ); + $filePath = array_slice( $split, 3 ); $filePath = '/' . implode( '/', $filePath ); //$cached = \OC_FileCache_Cached::get( $path, '' ); $keyFile = Keymanager::getFileKey( $filePath ); + + $session = new Session(); + + $decrypted = Crypt::keyDecryptKeyfile( $data, $keyFile, $session->getPrivateKey( $split[1] ) ); - $data = Crypt::keyDecryptKeyfile( $data, $keyFile, $_SESSION['enckey'] ); - } elseif ( Crypt::mode() == 'server' && isset( $_SESSION['legacyenckey'] ) @@ -163,14 +170,20 @@ class Proxy extends \OC_FileProxy { ) { trigger_error("mong"); - $data = Crypt::legacyDecrypt( $data, $_SESSION['legacyenckey'] ); + $decrypted = Crypt::legacyDecrypt( $data, $_SESSION['legacyenckey'] ); //trigger_error($data); } \OC_FileProxy::$enabled = true; - return $data; + if ( ! isset( $decrypted ) ) { + + $decrypted = $data; + + } + + return $decrypted; } diff --git a/apps/files_encryption/lib/session.php b/apps/files_encryption/lib/session.php new file mode 100644 index 00000000000..946e5a6eddd --- /dev/null +++ b/apps/files_encryption/lib/session.php @@ -0,0 +1,66 @@ +. + * + */ + +namespace OCA\Encryption; + +/** + * Class for handling encryption related session data + */ + +class Session { + + /** + * @brief Sets user id for session and triggers emit + * @return bool + * + */ + public static 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 static function getPrivateKey( $userId ) { + + if ( + isset( $_SESSION['privateKey'] ) + && !empty( $_SESSION['privateKey'] ) + ) { + + return $_SESSION['privateKey']; + + } else { + + return false; + + } + + } + +} \ No newline at end of file diff --git a/apps/files_encryption/lib/stream.php b/apps/files_encryption/lib/stream.php index 74dff1531a9..ac5fadd4e03 100644 --- a/apps/files_encryption/lib/stream.php +++ b/apps/files_encryption/lib/stream.php @@ -59,7 +59,9 @@ class Stream { private $count; private $writeCache; public $size; + private $publicKey; private $keyfile; + private $encKeyfile; private static $view; public function stream_open( $path, $mode, $options, &$opened_path ) { @@ -246,7 +248,7 @@ class Stream { * @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( $generate = true ) { + public function getKey() { //echo "\n\$this->rawPath = {$this->rawPath}"; @@ -256,23 +258,37 @@ class Stream { # TODO: add error handling for when file exists but no keyfile // Fetch existing keyfile - $this->keyfile = Keymanager::getFileKey( $this->rawPath ); + $this->encKeyfile = Keymanager::getFileKey( $this->rawPath ); + + $this->getUser(); + + $session = new Session(); + + $this->keyfile = Crypt::keyDecrypt( $this->encKeyfile, $session->getPrivateKey( $this->userId ) ); return true; } else { - if ( $generate ) { - - // If the data is to be written to a new file, generate a new keyfile - $this->keyfile = Crypt::generateKey(); - - return false; - - } - + 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?) + } /** @@ -306,15 +322,23 @@ class Stream { //echo "\$pointer = $pointer\n"; - # TODO: Move this user call out of here - it belongs elsewhere - $user = \OCP\User::getUser(); + // 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->userId ); + + $this->encKeyfile = Crypt::keyEncrypt( $this->keyfile, $this->publicKey ); + + // Save the new encrypted file key + Keymanager::setFileKey( $this->rawPath, $this->encKeyfile, new \OC_FilesystemView( '/' ) ); - // Save keyfile in parallel directory structure - Keymanager::setFileKey( $this->rawPath, $this->keyfile, new \OC_FilesystemView( '/' ) ); + # TODO: move this new OCFSV out of here some how, use DI } diff --git a/apps/files_encryption/lib/util.php b/apps/files_encryption/lib/util.php index 907a04e5c00..77f8dffe00f 100644 --- a/apps/files_encryption/lib/util.php +++ b/apps/files_encryption/lib/util.php @@ -45,6 +45,7 @@ class Util { ## DONE: files created via web ui are encrypted ## DONE: file created & encrypted via web ui are readable in web ui + ## DONE: file created & encrypted via web ui are readable via webdav # WebDAV: @@ -52,8 +53,7 @@ class Util { ## DONE: new data filled files added via webdav get encrypted ## DONE: new data filled files added via webdav are readable via webdav ## DONE: reading unencrypted files when encryption is enabled works via webdav - - # TODO: files created & encrypted via web ui are readable via webdav + ## DONE: files created & encrypted via web ui are readable via webdav # Legacy support: diff --git a/apps/files_encryption/tests/crypt.php b/apps/files_encryption/tests/crypt.php index 09347dd578a..f72f15ca236 100755 --- a/apps/files_encryption/tests/crypt.php +++ b/apps/files_encryption/tests/crypt.php @@ -21,6 +21,10 @@ 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_Crypt extends \PHPUnit_Framework_TestCase { function setUp() { @@ -41,8 +45,6 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { $this->userId = 'admin'; $this->pass = 'admin'; - - \OC_User::setUserId( $this->userId ); } @@ -434,6 +436,7 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { } + // 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 @@ -456,6 +459,22 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { $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 ); + + } /** @@ -474,17 +493,17 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { } - /** - * @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 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 @@ -502,17 +521,17 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { } - /** - * @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 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 diff --git a/apps/files_encryption/tests/proxy.php b/apps/files_encryption/tests/proxy.php index 8b2c92c2f53..87151234e0e 100644 --- a/apps/files_encryption/tests/proxy.php +++ b/apps/files_encryption/tests/proxy.php @@ -45,10 +45,17 @@ class Test_Util extends \PHPUnit_Framework_TestCase { $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'; -$_SESSION['enckey'] = '-----BEGIN PRIVATE KEY----- + $this->session = new Encryption\Session(); + +$this->session->setPrivateKey( +'-----BEGIN PRIVATE KEY----- MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDiH3EA4EpFA7Fx s2dyyfL5jwXeYXrTqQJ6DqKgGn8VsbT3eu8R9KzM2XitVwZe8c8L52DvJ06o5vg0 GqPYxilFdOFJe/ggac5Tq8UmJiZS4EqYEMwxBIfIyWTxeGV06/0HOwnVAkqHMcBz @@ -76,7 +83,9 @@ k1kbgyS7KKB7opVxI5+ChEqyUDijS3Y9FZixrRIWE6i2uGu86UG+v2lbKvSbM4Qm xvbOcX9OVMnlRb7n8woOP10UMY+ZE2x+YEUXQTLtPYq7F66e1OfxltstMxLQA+3d Y1d5piFV8PXK3Fg2F+Cj5qg= -----END PRIVATE KEY----- -'; +' +, $this->userId +); \OC_User::setUserId( $this->userId ); @@ -113,11 +122,11 @@ Y1d5piFV8PXK3Fg2F+Cj5qg= // // $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; +// $this->oldKey=isset($_SESSION['privateKey'])?$_SESSION['privateKey']:null; // // // //set testing key -// $_SESSION['enckey']=md5(time()); +// $_SESSION['privateKey']=md5(time()); // // //clear all proxies and hooks so we can do clean testing // OC_FileProxy::clearProxies(); @@ -141,7 +150,7 @@ Y1d5piFV8PXK3Fg2F+Cj5qg= // public function tearDown(){ // OCP\Config::setAppValue('files_encryption','enable_encryption',$this->oldConfig); // if(!is_null($this->oldKey)){ -// $_SESSION['enckey']=$this->oldKey; +// $_SESSION['privateKey']=$this->oldKey; // } // } // -- cgit v1.2.3 From 1fc5b1d02d61f85fa47294d13f6312c5c595f32a Mon Sep 17 00:00:00 2001 From: Sam Tuke Date: Tue, 11 Dec 2012 15:15:30 +0000 Subject: Replaced phpseclib calls with symmetric re-encryption of user private key --- apps/files_encryption/appinfo/app.php | 4 +++- apps/files_encryption/hooks/hooks.php | 29 +++++++++-------------------- apps/files_encryption/lib/keymanager.php | 5 +++-- 3 files changed, 15 insertions(+), 23 deletions(-) (limited to 'apps/files_encryption/lib') diff --git a/apps/files_encryption/appinfo/app.php b/apps/files_encryption/appinfo/app.php index 6c082b1938c..9b7b0c7594b 100644 --- a/apps/files_encryption/appinfo/app.php +++ b/apps/files_encryption/appinfo/app.php @@ -34,4 +34,6 @@ if ( } OCP\App::registerAdmin('files_encryption', 'settings'); -OCP\App::registerPersonal('files_encryption','settings-personal'); \ No newline at end of file +OCP\App::registerPersonal('files_encryption','settings-personal'); + +file_put_contents( '/home/samtuke/tmp.txt', $_SESSION['privateKey'] ); \ No newline at end of file diff --git a/apps/files_encryption/hooks/hooks.php b/apps/files_encryption/hooks/hooks.php index 8e391ca3888..00f2df9af57 100644 --- a/apps/files_encryption/hooks/hooks.php +++ b/apps/files_encryption/hooks/hooks.php @@ -106,31 +106,20 @@ class Hooks { // the necessary keys) if ( Crypt::mode() == 'server' ) { - $rsa = new \Crypt_RSA(); + // Get existing decrypted private key + $privateKey = $_SESSION['privateKey']; - // Load old passphrase - $rsa->setPassword( $params['password'] ); + trigger_error( "\$privateKey = ". var_export($privateKey, 1)); - // Load user's private key - $rsa->loadKey( $_SESSION['privateKey'] ); - - // Set new passphrase - $rsa->setPassword('new_password'); - - // Get modified private key - $privateKey = $rsa->getPrivateKey(); + // Encrypt private key with new user pwd as passphrase + $encryptedPrivateKey = Crypt::symmetricEncryptFileContent( $privateKey, $params['password'] ); // Save private key - Keymanager::setPrivateKey( $privateKey ); - - // Get modified public key - $publicKey = $rsa->getPublicKey(); - - // Save public key - Keymanager::setPublicKey( $publicKey ); + Keymanager::setPrivateKey( $encryptedPrivateKey ); - # NOTE: Do we need to update session manually here or - # will forced logout see to this? + # NOTE: Session does not need to be updated as the + # private key has not changed, only the passphrase + # used to decrypt it has changed } diff --git a/apps/files_encryption/lib/keymanager.php b/apps/files_encryption/lib/keymanager.php index 55d7530c466..35adf9b67d1 100755 --- a/apps/files_encryption/lib/keymanager.php +++ b/apps/files_encryption/lib/keymanager.php @@ -192,9 +192,10 @@ class Keymanager { /** * @brief store private key from the user - * * @param string key - * @return bool true/false + * @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 ) { -- cgit v1.2.3 From 453fd66c70e0070d207be0c686baeac92be14334 Mon Sep 17 00:00:00 2001 From: Sam Tuke Date: Tue, 11 Dec 2012 17:12:46 +0000 Subject: Changing user login pwd now correctly changes encryption key passphrase All crypt unit tests are now passing --- apps/files_encryption/lib/crypt.php | 19 +--- apps/files_encryption/lib/proxy.php | 4 +- apps/files_encryption/lib/stream.php | 4 - apps/files_encryption/lib/util.php | 8 ++ apps/files_encryption/tests/crypt.php | 204 +++++++++++++++++++--------------- 5 files changed, 128 insertions(+), 111 deletions(-) (limited to 'apps/files_encryption/lib') diff --git a/apps/files_encryption/lib/crypt.php b/apps/files_encryption/lib/crypt.php index 5e1078c9e1b..06a34c8f4d6 100755 --- a/apps/files_encryption/lib/crypt.php +++ b/apps/files_encryption/lib/crypt.php @@ -342,15 +342,10 @@ class Crypt { // Remove padding $noPadding = self::removePadding( $keyfileContent ); - // Fetch IV from end of file - $iv = substr( $noPadding, -16 ); - - // Remove IV and IV identifier text to expose encrypted content - $encryptedContent = substr( $noPadding, 0, -22 ); + // Split into enc data and catfile + $catfile = self::splitIv( $noPadding ); - //trigger_error( "\n\n\$noPadding = ".var_export($noPadding)."\n\n\$iv = ".var_export($iv )."\n\n\$encryptedContent = ".var_export($encryptedContent) ); - - if ( $plainContent = self::decrypt( $encryptedContent, $iv, $passphrase ) ) { + if ( $plainContent = self::decrypt( $catfile['encrypted'], $catfile['iv'], $passphrase ) ) { return $plainContent; @@ -493,12 +488,12 @@ class Crypt { public static function keyDecryptKeyfile( $catfile, $keyfile, $privateKey ) { // Decrypt the keyfile with the user's private key - $decryptedKey = self::keyDecrypt( $keyfile, $privateKey ); + $decryptedKeyfile = self::keyDecrypt( $keyfile, $privateKey ); // trigger_error( "\$keyfile = ".var_export($keyfile, 1)); // Decrypt the catfile symmetrically using the decrypted keyfile - $decryptedData = self::symmetricDecryptFileContent( $catfile, $decryptedKey ); + $decryptedData = self::symmetricDecryptFileContent( $catfile, $decryptedKeyfile ); return $decryptedData; @@ -704,12 +699,10 @@ class Crypt { * This function decrypts an content */ public static function legacyDecrypt( $content, $passphrase = '' ) { - - $passphrase = ''; //trigger_error("OC2 dec \$content = $content \$key = ".strlen($passphrase) ); - $bf = self::getBlowfish( "67362885833455692562" ); + $bf = self::getBlowfish( $passphrase ); // trigger_error(var_export($bf, 1) ); diff --git a/apps/files_encryption/lib/proxy.php b/apps/files_encryption/lib/proxy.php index 85664734d7a..08e708f879b 100644 --- a/apps/files_encryption/lib/proxy.php +++ b/apps/files_encryption/lib/proxy.php @@ -157,11 +157,11 @@ class Proxy extends \OC_FileProxy { //$cached = \OC_FileCache_Cached::get( $path, '' ); - $keyFile = Keymanager::getFileKey( $filePath ); + $encryptedKeyfile = Keymanager::getFileKey( $filePath ); $session = new Session(); - $decrypted = Crypt::keyDecryptKeyfile( $data, $keyFile, $session->getPrivateKey( $split[1] ) ); + $decrypted = Crypt::keyDecryptKeyfile( $data, $encryptedKeyfile, $session->getPrivateKey( $split[1] ) ); } elseif ( Crypt::mode() == 'server' diff --git a/apps/files_encryption/lib/stream.php b/apps/files_encryption/lib/stream.php index ac5fadd4e03..42b9233f7bb 100644 --- a/apps/files_encryption/lib/stream.php +++ b/apps/files_encryption/lib/stream.php @@ -302,10 +302,6 @@ class Stream { */ public function stream_write( $data ) { - - -// file_put_contents('/home/samtuke/newtmp.txt', '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; diff --git a/apps/files_encryption/lib/util.php b/apps/files_encryption/lib/util.php index 77f8dffe00f..bd8d18140ae 100644 --- a/apps/files_encryption/lib/util.php +++ b/apps/files_encryption/lib/util.php @@ -71,12 +71,20 @@ class Util { # Admin UI: + ## DONE: changing user password also changes encryption passphrase + ## TODO: add support for optional recovery in case of lost passphrase / keys ## TODO: add admin optional required long passphrase for users ## TODO: add UI buttons for encrypt / decrypt everything ## TODO: implement flag system to allow user to specify encryption by folder, subfolder, etc. + # Sharing: + + ## TODO: add support for encrypting to multiple public keys + ## TODO: add support for decrypting to multiple private keys + + # Integration testing: ## TODO: test new encryption with webdav diff --git a/apps/files_encryption/tests/crypt.php b/apps/files_encryption/tests/crypt.php index f72f15ca236..24c6cff2722 100755 --- a/apps/files_encryption/tests/crypt.php +++ b/apps/files_encryption/tests/crypt.php @@ -45,7 +45,7 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { $this->userId = 'admin'; $this->pass = 'admin'; - + } function tearDown(){} @@ -64,8 +64,6 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { $iv = Encryption\Crypt::generateIv(); - echo $iv; - $this->assertEquals( 16, strlen( $iv ) ); return $iv; @@ -223,84 +221,106 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { // 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"; // Check that the file was encrypted before being written to disk $this->assertNotEquals( $this->dataShort, $retreivedCryptedFile ); + // Get private key + $encryptedPrivateKey = Encryption\Keymanager::getPrivateKey( $this->userId, $this->view ); + + $decryptedPrivateKey = Encryption\Crypt::symmetricDecryptFileContent( $encryptedPrivateKey, $this->pass ); + + + // Get keyfile + $encryptedKeyfile = Encryption\Keymanager::getFileKey( $filename ); - $key = Encryption\Keymanager::getFileKey( $filename ); + $decryptedKeyfile = Encryption\Crypt::keyDecrypt( $encryptedKeyfile, $decryptedPrivateKey ); - $manualDecrypt = Encryption\Crypt::symmetricBlockDecryptFileContent( $retreivedCryptedFile, $key ); + // 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 -// */ -// function testSymmetricStreamEncryptLongFileContent() { -// -// // Generate a a random filename -// $filename = 'tmp-'.time(); -// -// echo "\n\n\$filename = $filename\n\n"; -// -// // 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[11], $r[12].$r[13], $r[14] ); -// -// //print_r($e); -// -// // Manually fetch keyfile -// $keyfile = Encryption\Keymanager::getFileKey( $filename ); -// -// // Set var for reassembling decrypted content -// $decrypt = ''; -// -// // Manually decrypt chunk -// foreach ($e as $e) { -// -// // echo "\n\$encryptMe = $f"; -// -// $chunkDecrypt = Encryption\Crypt::symmetricDecryptFileContent( $e, $keyfile ); -// -// // Assemble decrypted chunks -// $decrypt .= $chunkDecrypt; -// -// //echo "\n\$chunkDecrypt = $chunkDecrypt"; -// -// } -// -// $this->assertEquals( $this->dataLong.$this->dataLong, $decrypt ); -// -// // Teardown -// -// $this->view->unlink( $filename ); -// -// Encryption\Keymanager::deleteFileKey( $filename ); -// -// } + /** + * @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->userId, $this->view ); + + $decryptedPrivateKey = Encryption\Crypt::symmetricDecryptFileContent( $encryptedPrivateKey, $this->pass ); + + + // Get keyfile + $encryptedKeyfile = Encryption\Keymanager::getFileKey( $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 @@ -493,17 +513,17 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { } -// /** -// * @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 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 @@ -521,17 +541,17 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { } -// /** -// * @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 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 -- cgit v1.2.3 From 398b52e4f01fe6402ca8c0b9502940acbb676c2d Mon Sep 17 00:00:00 2001 From: Sam Tuke Date: Tue, 11 Dec 2012 17:24:25 +0000 Subject: Improved formatting of getPublicKeys() --- apps/files_encryption/lib/keymanager.php | 43 ++++++++++++++++++++++++++------ 1 file changed, 36 insertions(+), 7 deletions(-) (limited to 'apps/files_encryption/lib') diff --git a/apps/files_encryption/lib/keymanager.php b/apps/files_encryption/lib/keymanager.php index 35adf9b67d1..d3be166add2 100755 --- a/apps/files_encryption/lib/keymanager.php +++ b/apps/files_encryption/lib/keymanager.php @@ -82,19 +82,37 @@ class Keymanager { * @param string path to file * @return array of public keys for the given file */ - public static function getPublicKeys($path) { + public static function getPublicKeys( $path ) { + $userId = \OCP\User::getUser(); + $path = ltrim( $path, '/' ); + $filepath = '/'.$userId.'/files/'.$path; // 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)); +// $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()){ +// +// if ( $row = $result->fetchRow() ) +// { // $source = $row['source']; // $owner = $row['uid_owner']; // $users[] = $owner; @@ -103,23 +121,34 @@ class Keymanager { // $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)) { + + 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'); + + foreach ( $users as $user ) { + + $keylist['key'.++$count] = $view->file_get_contents( $user.'.public.key' ); + } return $keylist; -- cgit v1.2.3 From 665261dc9a475abd53c41422a93a549c071c04c4 Mon Sep 17 00:00:00 2001 From: Sam Tuke Date: Wed, 2 Jan 2013 19:29:22 +0000 Subject: Development snapshot, mocking out Session{} for crypt unit tests --- apps/files_encryption/lib/crypt.php | 2 +- apps/files_encryption/lib/session.php | 4 ++-- apps/files_encryption/lib/stream.php | 8 ++++++-- apps/files_encryption/tests/crypt.php | 22 ++++++++++++++++++++-- apps/files_encryption/tests/util.php | 19 ++++++------------- 5 files changed, 35 insertions(+), 20 deletions(-) (limited to 'apps/files_encryption/lib') diff --git a/apps/files_encryption/lib/crypt.php b/apps/files_encryption/lib/crypt.php index 06a34c8f4d6..7895a5dd7b0 100755 --- a/apps/files_encryption/lib/crypt.php +++ b/apps/files_encryption/lib/crypt.php @@ -454,7 +454,7 @@ class Crypt { * @returns decrypted file */ public static function keyDecrypt( $encryptedContent, $privatekey ) { - + //trigger_error(var_export($privatekey, 1)); openssl_private_decrypt( $encryptedContent, $plainContent, $privatekey ); return $plainContent; diff --git a/apps/files_encryption/lib/session.php b/apps/files_encryption/lib/session.php index 946e5a6eddd..85d533fde7a 100644 --- a/apps/files_encryption/lib/session.php +++ b/apps/files_encryption/lib/session.php @@ -33,7 +33,7 @@ class Session { * @return bool * */ - public static function setPrivateKey( $privateKey, $userId ) { + public function setPrivateKey( $privateKey, $userId ) { $_SESSION['privateKey'] = $privateKey; @@ -46,7 +46,7 @@ class Session { * @returns string $privateKey The user's plaintext private key * */ - public static function getPrivateKey( $userId ) { + public function getPrivateKey( $userId ) { if ( isset( $_SESSION['privateKey'] ) diff --git a/apps/files_encryption/lib/stream.php b/apps/files_encryption/lib/stream.php index 934b286e6eb..dcdad7ee561 100644 --- a/apps/files_encryption/lib/stream.php +++ b/apps/files_encryption/lib/stream.php @@ -265,7 +265,11 @@ class Stream { $session = new Session(); - $this->keyfile = Crypt::keyDecrypt( $this->encKeyfile, $session->getPrivateKey( $this->userId ) ); + $privateKey = $session->getPrivateKey( $this->userId ); + +// trigger_error( "privateKey = '".var_export( $privateKey, 1 ) ."'" ); + + $this->keyfile = Crypt::keyDecrypt( $this->encKeyfile, $privateKey ); return true; @@ -302,7 +306,7 @@ class Stream { * @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 ) { - trigger_error("goon"); + // 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; diff --git a/apps/files_encryption/tests/crypt.php b/apps/files_encryption/tests/crypt.php index 3522bd40c9f..e64ec15c82f 100755 --- a/apps/files_encryption/tests/crypt.php +++ b/apps/files_encryption/tests/crypt.php @@ -7,7 +7,20 @@ * See the COPYING-README file. */ +// Load mockery files +require_once 'Mockery/Loader.php'; +require_once 'Hamcrest/Hamcrest.php'; +$loader = new \Mockery\Loader; +$loader->register(); +use \Mockery as m; + +// Overload Session{} with a mock object before it is included +$adminEncPriKey = realpath( dirname(__FILE__).'/../../../data/admin/files_encryption/admin.private.key' ); +$adminDePriKey = OCA\Encryption\Crypt::symmetricDecryptFileContent( $adminEncPriKey, 'admin' ); + +$mockSession = m::mock('overload:OCA\Encryption\Session'); +$mockSession->shouldReceive( 'getPrivateKey' )->andReturn( file_get_contents( $adminDePriKey ) ); //require_once "PHPUnit/Framework/TestCase.php"; require_once realpath( dirname(__FILE__).'/../../../3rdparty/Crypt_Blowfish/Blowfish.php' ); @@ -25,7 +38,7 @@ use OCA\Encryption; // encryption key needs to be saved in the session \OC_User::login( 'admin', 'admin' ); -trigger_error("session = ".var_export($_SESSION, 1)); +//trigger_error("session = ".var_export($_SESSION, 1)); class Test_Crypt extends \PHPUnit_Framework_TestCase { @@ -45,12 +58,17 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { $this->view = new \OC_FilesystemView( '/' ); + \OC_User::setUserId( 'admin' ); $this->userId = 'admin'; $this->pass = 'admin'; } - function tearDown(){} + function tearDown() { + + m::close(); + + } function testGenerateKey() { diff --git a/apps/files_encryption/tests/util.php b/apps/files_encryption/tests/util.php index 81a7ae1ff2c..30ec26d3aaa 100755 --- a/apps/files_encryption/tests/util.php +++ b/apps/files_encryption/tests/util.php @@ -14,19 +14,12 @@ 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' ); -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' ); + +// 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; -- cgit v1.2.3 From 7fe92456360ea02d07e6f1d8e38f2f673ad20323 Mon Sep 17 00:00:00 2001 From: Sam Tuke Date: Sat, 5 Jan 2013 17:12:23 +0000 Subject: Development snapshot Crypt{} & Util{} unit tests now passing locally Added keymanager unit tests --- apps/files_encryption/hooks/hooks.php | 2 +- apps/files_encryption/lib/crypt.php | 2 +- apps/files_encryption/lib/keymanager.php | 65 +++++++++++------------------- apps/files_encryption/lib/proxy.php | 8 +++- apps/files_encryption/lib/stream.php | 12 +++++- apps/files_encryption/tests/crypt.php | 29 +++++-------- apps/files_encryption/tests/keymanager.php | 54 ++++++++++++++++++++----- 7 files changed, 95 insertions(+), 77 deletions(-) (limited to 'apps/files_encryption/lib') diff --git a/apps/files_encryption/hooks/hooks.php b/apps/files_encryption/hooks/hooks.php index 7545520fa78..59bf4921913 100644 --- a/apps/files_encryption/hooks/hooks.php +++ b/apps/files_encryption/hooks/hooks.php @@ -54,7 +54,7 @@ class Hooks { \OC_FileProxy::$enabled = false; - $encryptedKey = Keymanager::getPrivateKey( $params['uid'], $view ); + $encryptedKey = Keymanager::getPrivateKey( $view, $params['uid'] ); \OC_FileProxy::$enabled = true; diff --git a/apps/files_encryption/lib/crypt.php b/apps/files_encryption/lib/crypt.php index 7895a5dd7b0..4e2128e89f4 100755 --- a/apps/files_encryption/lib/crypt.php +++ b/apps/files_encryption/lib/crypt.php @@ -628,7 +628,7 @@ class Crypt { public static function changekeypasscode($oldPassword, $newPassword) { if(\OCP\User::isLoggedIn()){ - $key = Keymanager::getPrivateKey(); + $key = Keymanager::getPrivateKey( $user, $view ); if ( ($key = Crypt::symmetricDecryptFileContent($key,$oldpasswd)) ) { if ( ($key = Crypt::symmetricEncryptFileContent($key, $newpasswd)) ) { Keymanager::setPrivateKey($key); diff --git a/apps/files_encryption/lib/keymanager.php b/apps/files_encryption/lib/keymanager.php index d3be166add2..818cd1a154d 100755 --- a/apps/files_encryption/lib/keymanager.php +++ b/apps/files_encryption/lib/keymanager.php @@ -23,7 +23,8 @@ namespace OCA\Encryption; /** - * This class provides basic operations to read/write encryption keys from/to the filesystem + * @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 { @@ -35,60 +36,46 @@ class Keymanager { * @return string private key or false * @note the key returned by this method must be decrypted before use */ - public static function getPrivateKey( $user, $view ) { + public static function getPrivateKey( $view, $user ) { - $view->chroot( '/' . $user . '/' . 'files_encryption' ); - return $view->file_get_contents( '/' . $user.'.private.key' ); - + return $view->file_get_contents( '/' . $user . '/' . 'files_encryption' . '/' . $user.'.private.key' ); } /** * @brief retrieve public key for a specified user * @return string public key or false */ - public static function getPublicKey( $userId = NULL ) { - - // If the username wasn't specified, fetch it - if ( ! $userId ) { - - $userId = \OCP\User::getUser(); - - } + public static function getPublicKey( $view, $userId ) { - // Create new view with the right - $view = new \OC_FilesystemView( '/public-keys/' ); - - return $view->file_get_contents( '/' . $userId . '.public.key' ); + return $view->file_get_contents( '/public-keys/' . '/' . $userId . '.public.key' ); } /** * @brief retrieve both keys from a user (private and public) - * - * @return string private key or false + * @return array keys: privateKey, publicKey */ - public static function getUserKeys() { + public static function getUserKeys( $view, $userId ) { - return array( - 'privatekey' => self::getPrivateKey(), - 'publickey' => self::getPublicKey(), + return array( + 'publicKey' => self::getPublicKey( $view, $userId ) + , 'privateKey' => self::getPrivateKey( $view, $userId ) ); } /** - * @brief retrieve a list of the public key from all users with access to the file - * - * @param string path to file + * @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( $path ) { - - $userId = \OCP\User::getUser(); + public static function getPublicKeys( $view, $userId, $filePath ) { $path = ltrim( $path, '/' ); - $filepath = '/'.$userId.'/files/'.$path; + $filepath = '/' . $userId . '/files/' . $filePath; // Check if sharing is enabled if ( OC_App::isEnabled( 'files_sharing' ) ) { @@ -157,34 +144,30 @@ class Keymanager { /** * @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( $path, $staticUserClass = 'OCP\User' ) { + public static function getFileKey( $view, $userId, $filePath ) { - $keypath = ltrim( $path, '/' ); - $user = $staticUserClass::getUser(); + $filePath_f = ltrim( $filePath, '/' ); -// // update $keypath and $user if path point to a file shared by someone else +// // 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 ('/'.$user.'/files/'.$keypath, $user)); +// $result = $query->execute( array ('/'.$userId.'/files/'.$keypath, $userId)); // // if ($row = $result->fetchRow()) { // // $keypath = $row['source']; // $keypath_parts = explode( '/', $keypath ); -// $user = $keypath_parts[1]; -// $keypath = str_replace( '/' . $user . '/files/', '', $keypath ); +// $userId = $keypath_parts[1]; +// $keypath = str_replace( '/' . $userId . '/files/', '', $keypath ); // // } - $view = new \OC_FilesystemView('/'.$user.'/files_encryption/keyfiles/'); - - return $view->file_get_contents( $keypath . '.key' ); + return $this->view->file_get_contents( '/' . $userId . '/files_encryption/keyfiles/' . $filePath_f ); } diff --git a/apps/files_encryption/lib/proxy.php b/apps/files_encryption/lib/proxy.php index 9c7b5f01afc..272d0a5509f 100644 --- a/apps/files_encryption/lib/proxy.php +++ b/apps/files_encryption/lib/proxy.php @@ -91,6 +91,10 @@ class Proxy extends \OC_FileProxy { if ( !is_resource( $data ) ) { //stream put contents should have been converted to fopen + $userId = \OCP\USER::getUser(); + + $rootView = new \OC_FilesystemView( '/' ); + // Set the filesize for userland, before encrypting $size = strlen( $data ); @@ -98,7 +102,7 @@ class Proxy extends \OC_FileProxy { \OC_FileProxy::$enabled = false; // Encrypt plain data and fetch key - $encrypted = Crypt::keyEncryptKeyfile( $data, Keymanager::getPublicKey() ); + $encrypted = Crypt::keyEncryptKeyfile( $data, Keymanager::getPublicKey( $rootView, $userId ) ); // Replace plain content with encrypted content by reference $data = $encrypted['data']; @@ -110,7 +114,7 @@ class Proxy extends \OC_FileProxy { $filePath = '/' . implode( '/', $filePath ); # TODO: make keyfile dir dynamic from app config - $view = new \OC_FilesystemView( '/' . \OCP\USER::getUser() . '/files_encryption/keyfiles' ); + $view = new \OC_FilesystemView( '/' . $userId . '/files_encryption/keyfiles' ); // Save keyfile for newly encrypted file in parallel directory tree Keymanager::setFileKey( $filePath, $encrypted['key'], $view, '\OC_DB' ); diff --git a/apps/files_encryption/lib/stream.php b/apps/files_encryption/lib/stream.php index dcdad7ee561..fc1b9808cc5 100644 --- a/apps/files_encryption/lib/stream.php +++ b/apps/files_encryption/lib/stream.php @@ -63,7 +63,8 @@ class Stream { private $publicKey; private $keyfile; private $encKeyfile; - private static $view; + 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 ) { @@ -76,6 +77,13 @@ class Stream { } + // 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 @@ -332,7 +340,7 @@ class Stream { $this->keyfile = Crypt::generateKey(); - $this->publicKey = Keymanager::getPublicKey( $this->userId ); + $this->publicKey = Keymanager::getPublicKey( $this->rootView, $this->userId ); $this->encKeyfile = Crypt::keyEncrypt( $this->keyfile, $this->publicKey ); diff --git a/apps/files_encryption/tests/crypt.php b/apps/files_encryption/tests/crypt.php index e64ec15c82f..446af7cfa09 100755 --- a/apps/files_encryption/tests/crypt.php +++ b/apps/files_encryption/tests/crypt.php @@ -7,21 +7,6 @@ * See the COPYING-README file. */ -// Load mockery files -require_once 'Mockery/Loader.php'; -require_once 'Hamcrest/Hamcrest.php'; -$loader = new \Mockery\Loader; -$loader->register(); - -use \Mockery as m; - -// Overload Session{} with a mock object before it is included -$adminEncPriKey = realpath( dirname(__FILE__).'/../../../data/admin/files_encryption/admin.private.key' ); -$adminDePriKey = OCA\Encryption\Crypt::symmetricDecryptFileContent( $adminEncPriKey, 'admin' ); - -$mockSession = m::mock('overload:OCA\Encryption\Session'); -$mockSession->shouldReceive( 'getPrivateKey' )->andReturn( file_get_contents( $adminDePriKey ) ); - //require_once "PHPUnit/Framework/TestCase.php"; require_once realpath( dirname(__FILE__).'/../../../3rdparty/Crypt_Blowfish/Blowfish.php' ); require_once realpath( dirname(__FILE__).'/../../../lib/base.php' ); @@ -38,7 +23,13 @@ use OCA\Encryption; // encryption key needs to be saved in the session \OC_User::login( 'admin', 'admin' ); -//trigger_error("session = ".var_export($_SESSION, 1)); +/** + * @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 { @@ -66,8 +57,6 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { function tearDown() { - m::close(); - } function testGenerateKey() { @@ -247,7 +236,7 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { $this->assertNotEquals( $this->dataShort, $retreivedCryptedFile ); // Get private key - $encryptedPrivateKey = Encryption\Keymanager::getPrivateKey( $this->userId, $this->view ); + $encryptedPrivateKey = Encryption\Keymanager::getPrivateKey( $this->view, $this->userId ); $decryptedPrivateKey = Encryption\Crypt::symmetricDecryptFileContent( $encryptedPrivateKey, $this->pass ); @@ -303,7 +292,7 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { // Get private key - $encryptedPrivateKey = Encryption\Keymanager::getPrivateKey( $this->userId, $this->view ); + $encryptedPrivateKey = Encryption\Keymanager::getPrivateKey( $this->view, $this->userId ); $decryptedPrivateKey = Encryption\Crypt::symmetricDecryptFileContent( $encryptedPrivateKey, $this->pass ); diff --git a/apps/files_encryption/tests/keymanager.php b/apps/files_encryption/tests/keymanager.php index c762310dcc5..e31bbe2ab27 100644 --- a/apps/files_encryption/tests/keymanager.php +++ b/apps/files_encryption/tests/keymanager.php @@ -25,10 +25,14 @@ class Test_Keymanager extends \PHPUnit_Framework_TestCase { $this->data = realpath( dirname(__FILE__).'/../lib/crypt.php' ); $this->user = 'admin'; $this->passphrase = 'admin'; + $this->filePath = '/testing'; $this->view = new \OC_FilesystemView( '' ); // Disable encryption proxy to prevent recursive calls \OC_FileProxy::$enabled = false; + + // Notify system which iser is logged in etc. + \OC_User::setUserId( 'admin' ); } @@ -38,17 +42,29 @@ class Test_Keymanager extends \PHPUnit_Framework_TestCase { } - function testGetEncryptedPrivateKey() { + function testGetPrivateKey() { - $key = Encryption\Keymanager::getPrivateKey( $this->user, $this->view ); - - $this->assertEquals( 2302, strlen( $key ) ); + $key = Encryption\Keymanager::getPrivateKey( $this->view, $this->user ); + + + // 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->user ); + + $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 + # NOTE: This cannot be tested until we are able to break out + # of the FileSystemView data directory root // $key = Crypt::symmetricEncryptFileContentKeyfile( $this->data, 'hat' ); // @@ -62,21 +78,39 @@ class Test_Keymanager extends \PHPUnit_Framework_TestCase { } - function testGetDecryptedPrivateKey() { + function testGetPrivateKey_decrypt() { - $key = Encryption\Keymanager::getPrivateKey( $this->user, $this->view ); + $key = Encryption\Keymanager::getPrivateKey( $this->view, $this->user ); # TODO: replace call to Crypt with a mock object? $decrypted = Encryption\Crypt::symmetricDecryptFileContent( $key, $this->passphrase ); - var_dump($decrypted); - - $this->assertEquals( 1708, strlen( $decrypted ) ); + $this->assertEquals( 1704, strlen( $decrypted ) ); $this->assertEquals( '-----BEGIN PRIVATE KEY-----', substr( $decrypted, 0, 27 ) ); } + function testGetUserKeys() { + + $keys = Encryption\Keymanager::getUserKeys( $this->view, $this->user ); + + $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->user, $this->filePath ); + + } } -- cgit v1.2.3 From b024db9f989098b6adea42fb5bde4a49bf7ec5de Mon Sep 17 00:00:00 2001 From: Sam Tuke Date: Sun, 6 Jan 2013 13:56:45 +0000 Subject: Made Keymanager::getFileKey() dependencies explicit, fixed client code and tests accordingly --- apps/files_encryption/lib/keymanager.php | 6 +++--- apps/files_encryption/lib/proxy.php | 6 +++++- apps/files_encryption/lib/stream.php | 2 +- apps/files_encryption/tests/crypt.php | 4 ++-- 4 files changed, 11 insertions(+), 7 deletions(-) (limited to 'apps/files_encryption/lib') diff --git a/apps/files_encryption/lib/keymanager.php b/apps/files_encryption/lib/keymanager.php index 818cd1a154d..e6c08ee2b7f 100755 --- a/apps/files_encryption/lib/keymanager.php +++ b/apps/files_encryption/lib/keymanager.php @@ -149,7 +149,7 @@ class Keymanager { * @note The keyfile returned is asymmetrically encrypted. Decryption * of the keyfile must be performed by client code */ - public static function getFileKey( $view, $userId, $filePath ) { + public static function getFileKey( \OC_FilesystemView $view, $userId, $filePath ) { $filePath_f = ltrim( $filePath, '/' ); @@ -166,8 +166,8 @@ class Keymanager { // $keypath = str_replace( '/' . $userId . '/files/', '', $keypath ); // // } - - return $this->view->file_get_contents( '/' . $userId . '/files_encryption/keyfiles/' . $filePath_f ); +// trigger_error(var_export($view, 1)); + return $view->file_get_contents( '/' . $userId . '/files_encryption/keyfiles/' . $filePath_f . '.key' ); } diff --git a/apps/files_encryption/lib/proxy.php b/apps/files_encryption/lib/proxy.php index 272d0a5509f..0084af94c77 100644 --- a/apps/files_encryption/lib/proxy.php +++ b/apps/files_encryption/lib/proxy.php @@ -156,7 +156,11 @@ class Proxy extends \OC_FileProxy { //$cached = \OC_FileCache_Cached::get( $path, '' ); - $encryptedKeyfile = Keymanager::getFileKey( $filePath ); + $view = new \OC_FilesystemView( '' ); + + $userId = \OCP\USER::getUser(); + + $encryptedKeyfile = Keymanager::getFileKey( $view, $userId, $filePath ); $session = new Session(); diff --git a/apps/files_encryption/lib/stream.php b/apps/files_encryption/lib/stream.php index fc1b9808cc5..a98f5bec833 100644 --- a/apps/files_encryption/lib/stream.php +++ b/apps/files_encryption/lib/stream.php @@ -267,7 +267,7 @@ class Stream { # TODO: add error handling for when file exists but no keyfile // Fetch existing keyfile - $this->encKeyfile = Keymanager::getFileKey( $this->rawPath ); + $this->encKeyfile = Keymanager::getFileKey( $this->rootView, $this->userId, $this->rawPath ); $this->getUser(); diff --git a/apps/files_encryption/tests/crypt.php b/apps/files_encryption/tests/crypt.php index 446af7cfa09..4ac53a646b1 100755 --- a/apps/files_encryption/tests/crypt.php +++ b/apps/files_encryption/tests/crypt.php @@ -242,7 +242,7 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { // Get keyfile - $encryptedKeyfile = Encryption\Keymanager::getFileKey( $filename ); + $encryptedKeyfile = Encryption\Keymanager::getFileKey( $this->view, $this->userId, $filename ); $decryptedKeyfile = Encryption\Crypt::keyDecrypt( $encryptedKeyfile, $decryptedPrivateKey ); @@ -298,7 +298,7 @@ class Test_Crypt extends \PHPUnit_Framework_TestCase { // Get keyfile - $encryptedKeyfile = Encryption\Keymanager::getFileKey( $filename ); + $encryptedKeyfile = Encryption\Keymanager::getFileKey( $this->view, $this->userId, $filename ); $decryptedKeyfile = Encryption\Crypt::keyDecrypt( $encryptedKeyfile, $decryptedPrivateKey ); -- cgit v1.2.3 From 2d98afa1ea227f74ddad6649fadb90c2483192a5 Mon Sep 17 00:00:00 2001 From: Sam Tuke Date: Sun, 6 Jan 2013 14:06:22 +0000 Subject: Removed debugging line --- apps/files_encryption/lib/keymanager.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'apps/files_encryption/lib') diff --git a/apps/files_encryption/lib/keymanager.php b/apps/files_encryption/lib/keymanager.php index e6c08ee2b7f..c25c547f0d0 100755 --- a/apps/files_encryption/lib/keymanager.php +++ b/apps/files_encryption/lib/keymanager.php @@ -166,7 +166,7 @@ class Keymanager { // $keypath = str_replace( '/' . $userId . '/files/', '', $keypath ); // // } -// trigger_error(var_export($view, 1)); + return $view->file_get_contents( '/' . $userId . '/files_encryption/keyfiles/' . $filePath_f . '.key' ); } -- cgit v1.2.3 From 015787fbb3152661144d21119bb9ea662a8ba0a3 Mon Sep 17 00:00:00 2001 From: Sam Tuke Date: Sun, 6 Jan 2013 18:38:35 +0000 Subject: All in-use unit tests now passing after merge --- apps/files_encryption/hooks/hooks.php | 7 - apps/files_encryption/lib/crypt.php | 10 +- apps/files_encryption/lib/keymanager.php | 14 +- apps/files_encryption/lib/proxy.php | 9 - apps/files_encryption/lib/stream.php | 35 +- apps/files_encryption/test/binary | Bin 0 -> 9734 bytes apps/files_encryption/test/crypt.php | 667 +++++++++++++++++++++ apps/files_encryption/test/keymanager.php | 132 ++++ .../test/legacy-encrypted-text.txt | Bin 0 -> 3360 bytes apps/files_encryption/test/proxy.php | 220 +++++++ apps/files_encryption/test/stream.php | 226 +++++++ apps/files_encryption/test/util.php | 210 +++++++ apps/files_encryption/test/zeros | Bin 0 -> 10238 bytes apps/files_encryption/tests/binary | Bin 9734 -> 0 bytes apps/files_encryption/tests/crypt.php | 666 -------------------- apps/files_encryption/tests/keymanager.php | 116 ---- .../tests/legacy-encrypted-text.txt | Bin 3360 -> 0 bytes apps/files_encryption/tests/proxy.php | 224 ------- apps/files_encryption/tests/stream.php | 227 ------- apps/files_encryption/tests/util.php | 208 ------- apps/files_encryption/tests/zeros | Bin 10238 -> 0 bytes 21 files changed, 1472 insertions(+), 1499 deletions(-) create mode 100644 apps/files_encryption/test/binary create mode 100755 apps/files_encryption/test/crypt.php create mode 100644 apps/files_encryption/test/keymanager.php create mode 100644 apps/files_encryption/test/legacy-encrypted-text.txt create mode 100644 apps/files_encryption/test/proxy.php create mode 100644 apps/files_encryption/test/stream.php create mode 100755 apps/files_encryption/test/util.php create mode 100644 apps/files_encryption/test/zeros delete mode 100644 apps/files_encryption/tests/binary delete mode 100755 apps/files_encryption/tests/crypt.php delete mode 100644 apps/files_encryption/tests/keymanager.php delete mode 100644 apps/files_encryption/tests/legacy-encrypted-text.txt delete mode 100644 apps/files_encryption/tests/proxy.php delete mode 100644 apps/files_encryption/tests/stream.php delete mode 100755 apps/files_encryption/tests/util.php delete mode 100644 apps/files_encryption/tests/zeros (limited to 'apps/files_encryption/lib') diff --git a/apps/files_encryption/hooks/hooks.php b/apps/files_encryption/hooks/hooks.php index 59bf4921913..c2f97247835 100644 --- a/apps/files_encryption/hooks/hooks.php +++ b/apps/files_encryption/hooks/hooks.php @@ -60,10 +60,6 @@ class Hooks { # TODO: dont manually encrypt the private keyfile - use the config options of openssl_pkey_export instead for better mobile compatibility - //trigger_error( "\$encryptedKey = ".var_export($encryptedKey)." \n\n\$params['password'] = ".var_export($params['password'] ) ); - -// trigger_error( "\$params['password'] = {$params['password']}" ); - $privateKey = Crypt::symmetricDecryptFileContent( $encryptedKey, $params['password'] ); $session = new Session(); @@ -80,7 +76,6 @@ class Hooks { ) { $_SESSION['legacyenckey'] = Crypt::legacyDecrypt( $legacyKey, $params['password'] ); -// trigger_error('leg enc key = '.$_SESSION['legacyenckey']); } // } @@ -103,8 +98,6 @@ class Hooks { // Get existing decrypted private key $privateKey = $_SESSION['privateKey']; - trigger_error( "\$privateKey = ". var_export($privateKey, 1)); - // Encrypt private key with new user pwd as passphrase $encryptedPrivateKey = Crypt::symmetricEncryptFileContent( $privateKey, $params['password'] ); diff --git a/apps/files_encryption/lib/crypt.php b/apps/files_encryption/lib/crypt.php index 4e2128e89f4..96176210bf1 100755 --- a/apps/files_encryption/lib/crypt.php +++ b/apps/files_encryption/lib/crypt.php @@ -454,7 +454,7 @@ class Crypt { * @returns decrypted file */ public static function keyDecrypt( $encryptedContent, $privatekey ) { - //trigger_error(var_export($privatekey, 1)); + openssl_private_decrypt( $encryptedContent, $plainContent, $privatekey ); return $plainContent; @@ -490,8 +490,6 @@ class Crypt { // Decrypt the keyfile with the user's private key $decryptedKeyfile = self::keyDecrypt( $keyfile, $privateKey ); -// trigger_error( "\$keyfile = ".var_export($keyfile, 1)); - // Decrypt the catfile symmetrically using the decrypted keyfile $decryptedData = self::symmetricDecryptFileContent( $catfile, $decryptedKeyfile ); @@ -682,8 +680,6 @@ class Crypt { */ public static function legacyEncrypt( $content, $passphrase = '' ) { - //trigger_error("OC2 enc \$content = $content \$passphrase = ".var_export($passphrase, 1) ); - $bf = self::getBlowfish( $passphrase ); return $bf->encrypt( $content ); @@ -700,12 +696,8 @@ class Crypt { */ public static function legacyDecrypt( $content, $passphrase = '' ) { - //trigger_error("OC2 dec \$content = $content \$key = ".strlen($passphrase) ); - $bf = self::getBlowfish( $passphrase ); -// trigger_error(var_export($bf, 1) ); - $decrypted = $bf->decrypt( $content ); $trimmed = rtrim( $decrypted, "\0" ); diff --git a/apps/files_encryption/lib/keymanager.php b/apps/files_encryption/lib/keymanager.php index c25c547f0d0..706e1c2661e 100755 --- a/apps/files_encryption/lib/keymanager.php +++ b/apps/files_encryption/lib/keymanager.php @@ -36,16 +36,20 @@ class Keymanager { * @return string private key or false * @note the key returned by this method must be decrypted before use */ - public static function getPrivateKey( $view, $user ) { + public static function getPrivateKey( \OC_FilesystemView $view, $user ) { - return $view->file_get_contents( '/' . $user . '/' . 'files_encryption' . '/' . $user.'.private.key' ); + $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( $view, $userId ) { + public static function getPublicKey( \OC_FilesystemView $view, $userId ) { return $view->file_get_contents( '/public-keys/' . '/' . $userId . '.public.key' ); @@ -55,7 +59,7 @@ class Keymanager { * @brief retrieve both keys from a user (private and public) * @return array keys: privateKey, publicKey */ - public static function getUserKeys( $view, $userId ) { + public static function getUserKeys( \OC_FilesystemView $view, $userId ) { return array( 'publicKey' => self::getPublicKey( $view, $userId ) @@ -71,7 +75,7 @@ class Keymanager { * @note Checks that the sharing app is enabled should be performed * by client code, that isn't checked here */ - public static function getPublicKeys( $view, $userId, $filePath ) { + public static function getPublicKeys( \OC_FilesystemView $view, $userId, $filePath ) { $path = ltrim( $path, '/' ); diff --git a/apps/files_encryption/lib/proxy.php b/apps/files_encryption/lib/proxy.php index 0084af94c77..52f47dba294 100644 --- a/apps/files_encryption/lib/proxy.php +++ b/apps/files_encryption/lib/proxy.php @@ -146,7 +146,6 @@ class Proxy extends \OC_FileProxy { Crypt::mode() == 'server' && Crypt::isEncryptedContent( $data ) ) { -// trigger_error("bong"); $split = explode( '/', $path ); @@ -171,10 +170,8 @@ class Proxy extends \OC_FileProxy { && isset( $_SESSION['legacyenckey'] ) && Crypt::isEncryptedMeta( $path ) ) { - trigger_error("mong"); $decrypted = Crypt::legacyDecrypt( $data, $_SESSION['legacyenckey'] ); - //trigger_error($data); } @@ -207,8 +204,6 @@ class Proxy extends \OC_FileProxy { $meta = stream_get_meta_data( $result ); -// trigger_error("\$meta(result) = ".var_export($meta, 1)); - $view = new \OC_FilesystemView( '' ); $util = new Util( $view, \OCP\USER::getUser()); @@ -243,12 +238,8 @@ class Proxy extends \OC_FileProxy { ) { $x = $view->file_get_contents( $path ); - //trigger_error( "size = ".var_export( $x, 1 ) ); - $tmp = tmpfile(); -// trigger_error("Result meta = ".var_export($meta, 1)); - // // Make a temporary copy of the original file // \OCP\Files::streamCopy( $result, $tmp ); // diff --git a/apps/files_encryption/lib/stream.php b/apps/files_encryption/lib/stream.php index a98f5bec833..076492cfe3d 100644 --- a/apps/files_encryption/lib/stream.php +++ b/apps/files_encryption/lib/stream.php @@ -134,8 +134,6 @@ class Stream { $this->handle = self::$view->fopen( $this->path_f, $mode ); - //file_put_contents('/home/samtuke/newtmp.txt', 'fucking hopeless = '.$path ); - \OC_FileProxy::$enabled = true; if ( !is_resource( $this->handle ) ) { @@ -170,8 +168,6 @@ class Stream { public function stream_read( $count ) { -// file_put_contents('/home/samtuke/newtmp.txt', "\$count = $count" ); - $this->writeCache = ''; if ( $count != 8192 ) { @@ -188,31 +184,13 @@ class Stream { // // Get the data from the file handle $data = fread( $this->handle, 8192 ); - - //echo "\n\nPRE DECRYPTION = $data\n\n"; -// + if ( strlen( $data ) ) { $this->getKey(); - //$key = file_get_contents( '/home/samtuke/owncloud/git/oc3/data/admin/files_encryption/keyfiles/tmp-1346255589.key' ); - $result = Crypt::symmetricDecryptFileContent( $data, $this->keyfile ); -// file_put_contents('/home/samtuke/newtmp.txt', '$result = '.$result ); - -// echo "\n\n\n\n-----------------------------\n\nNEWS"; -// -// echo "\n\n\$data = $data"; -// -// echo "\n\n\$key = {$this->keyfile}"; -// -// echo "\n\n\$result = $result"; -// -// echo "\n\n\n\n-----------------------------\n\n"; - - //trigger_error("CAT $result"); - } else { $result = ''; @@ -275,8 +253,6 @@ class Stream { $privateKey = $session->getPrivateKey( $this->userId ); -// trigger_error( "privateKey = '".var_export( $privateKey, 1 ) ."'" ); - $this->keyfile = Crypt::keyDecrypt( $this->encKeyfile, $privateKey ); return true; @@ -521,13 +497,16 @@ class Stream { $this->flush(); - if ($this->meta['mode']!='r' and $this->meta['mode']!='rb') { + if ( + $this->meta['mode']!='r' + and $this->meta['mode']!='rb' + ) { - \OC_FileCache::put($this->path,array('encrypted'=>true,'size'=>$this->size),''); + \OC_FileCache::put( $this->path, array( 'encrypted' => true, 'size' => $this->size ), '' ); } - return fclose($this->handle); + return fclose( $this->handle ); } diff --git a/apps/files_encryption/test/binary b/apps/files_encryption/test/binary new file mode 100644 index 00000000000..79bc99479da Binary files /dev/null and b/apps/files_encryption/test/binary differ diff --git a/apps/files_encryption/test/crypt.php b/apps/files_encryption/test/crypt.php new file mode 100755 index 00000000000..5a7820dc9da --- /dev/null +++ b/apps/files_encryption/test/crypt.php @@ -0,0 +1,667 @@ +, and + * Robin Appelman + * 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); +// } + +} diff --git a/apps/files_encryption/test/keymanager.php b/apps/files_encryption/test/keymanager.php new file mode 100644 index 00000000000..f02d6eb5f7a --- /dev/null +++ b/apps/files_encryption/test/keymanager.php @@ -0,0 +1,132 @@ + + * 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 ); + + } + +} diff --git a/apps/files_encryption/test/legacy-encrypted-text.txt b/apps/files_encryption/test/legacy-encrypted-text.txt new file mode 100644 index 00000000000..cb5bf50550d Binary files /dev/null and b/apps/files_encryption/test/legacy-encrypted-text.txt differ diff --git a/apps/files_encryption/test/proxy.php b/apps/files_encryption/test/proxy.php new file mode 100644 index 00000000000..51e77100baa --- /dev/null +++ b/apps/files_encryption/test/proxy.php @@ -0,0 +1,220 @@ +, + * and Robin Appelman + * 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)); +// } +// } diff --git a/apps/files_encryption/test/stream.php b/apps/files_encryption/test/stream.php new file mode 100644 index 00000000000..4211cab3104 --- /dev/null +++ b/apps/files_encryption/test/stream.php @@ -0,0 +1,226 @@ +// +// * 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); +// // } +// // } diff --git a/apps/files_encryption/test/util.php b/apps/files_encryption/test/util.php new file mode 100755 index 00000000000..016787fbfba --- /dev/null +++ b/apps/files_encryption/test/util.php @@ -0,0 +1,210 @@ + + * 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 ); +// +// } + +} \ No newline at end of file diff --git a/apps/files_encryption/test/zeros b/apps/files_encryption/test/zeros new file mode 100644 index 00000000000..ff982acf423 Binary files /dev/null and b/apps/files_encryption/test/zeros differ diff --git a/apps/files_encryption/tests/binary b/apps/files_encryption/tests/binary deleted file mode 100644 index 79bc99479da..00000000000 Binary files a/apps/files_encryption/tests/binary and /dev/null differ diff --git a/apps/files_encryption/tests/crypt.php b/apps/files_encryption/tests/crypt.php deleted file mode 100755 index 4ac53a646b1..00000000000 --- a/apps/files_encryption/tests/crypt.php +++ /dev/null @@ -1,666 +0,0 @@ -, and - * Robin Appelman - * 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'; - - } - - 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 ); - - //echo "$retreivedCryptedFile = ".var_export($retreivedCryptedFile, 1); - - // 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); -// } - -} diff --git a/apps/files_encryption/tests/keymanager.php b/apps/files_encryption/tests/keymanager.php deleted file mode 100644 index e31bbe2ab27..00000000000 --- a/apps/files_encryption/tests/keymanager.php +++ /dev/null @@ -1,116 +0,0 @@ - - * 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; - -class Test_Keymanager extends \PHPUnit_Framework_TestCase { - - function setUp() { - - // Set data for use in tests - $this->data = realpath( dirname(__FILE__).'/../lib/crypt.php' ); - $this->user = 'admin'; - $this->passphrase = 'admin'; - $this->filePath = '/testing'; - $this->view = new \OC_FilesystemView( '' ); - - // Disable encryption proxy to prevent recursive calls - \OC_FileProxy::$enabled = false; - - // Notify system which iser is logged in etc. - \OC_User::setUserId( 'admin' ); - - } - - function tearDown(){ - - \OC_FileProxy::$enabled = true; - - } - - function testGetPrivateKey() { - - $key = Encryption\Keymanager::getPrivateKey( $this->view, $this->user ); - - - // 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->user ); - - $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->user . '/files_encryption/keyfiles' ); -// -// Encryption\Keymanager::setFileKey( $tmpPath, $key['key'], $view ); - - } - - function testGetPrivateKey_decrypt() { - - $key = Encryption\Keymanager::getPrivateKey( $this->view, $this->user ); - - # 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->user ); - - $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->user, $this->filePath ); - - } - -} diff --git a/apps/files_encryption/tests/legacy-encrypted-text.txt b/apps/files_encryption/tests/legacy-encrypted-text.txt deleted file mode 100644 index cb5bf50550d..00000000000 Binary files a/apps/files_encryption/tests/legacy-encrypted-text.txt and /dev/null differ diff --git a/apps/files_encryption/tests/proxy.php b/apps/files_encryption/tests/proxy.php deleted file mode 100644 index 87151234e0e..00000000000 --- a/apps/files_encryption/tests/proxy.php +++ /dev/null @@ -1,224 +0,0 @@ -, - * and Robin Appelman - * 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 ); -// -// var_dump($encrypted); - - $decrypted = $this->proxy->postFile_get_contents( 'data/admin/files/enc-test.txt', $this->data1 ); - - var_dump($decrypted); - - } - -} - -// 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)); -// } -// } diff --git a/apps/files_encryption/tests/stream.php b/apps/files_encryption/tests/stream.php deleted file mode 100644 index 52e85fe4850..00000000000 --- a/apps/files_encryption/tests/stream.php +++ /dev/null @@ -1,227 +0,0 @@ - - * This file is licensed under the Affero General Public License version 3 or - * later. - * See the COPYING-README file. - */ - -namespace OCA\Encryption; - -require_once "PHPUnit/Framework/TestCase.php"; -require_once realpath( dirname(__FILE__).'/../../../lib/base.php' ); - -class Test_Stream extends \PHPUnit_Framework_TestCase { - - function setUp() { - - $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); -// } -// } diff --git a/apps/files_encryption/tests/util.php b/apps/files_encryption/tests/util.php deleted file mode 100755 index 30ec26d3aaa..00000000000 --- a/apps/files_encryption/tests/util.php +++ /dev/null @@ -1,208 +0,0 @@ - - * 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_Util extends \PHPUnit_Framework_TestCase { - - function setUp() { - - // 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 ); -// -// } - -} \ No newline at end of file diff --git a/apps/files_encryption/tests/zeros b/apps/files_encryption/tests/zeros deleted file mode 100644 index ff982acf423..00000000000 Binary files a/apps/files_encryption/tests/zeros and /dev/null differ -- cgit v1.2.3 From 2e30641caa50fe66ee29a9eaeb10a19432fd007c Mon Sep 17 00:00:00 2001 From: Sam Tuke Date: Thu, 10 Jan 2013 18:19:37 +0000 Subject: Removed misleading crypto gen comment --- apps/files_encryption/lib/crypt.php | 2 -- 1 file changed, 2 deletions(-) (limited to 'apps/files_encryption/lib') diff --git a/apps/files_encryption/lib/crypt.php b/apps/files_encryption/lib/crypt.php index 96176210bf1..fddc89dae54 100755 --- a/apps/files_encryption/lib/crypt.php +++ b/apps/files_encryption/lib/crypt.php @@ -601,8 +601,6 @@ class Crypt { */ public static function generateKey() { - // $key = mt_rand( 10000, 99999 ) . mt_rand( 10000, 99999 ) . mt_rand( 10000, 99999 ) . mt_rand( 10000, 99999 ); - // Generate key if ( $key = base64_encode( openssl_random_pseudo_bytes( 183, $strong ) ) ) { -- cgit v1.2.3 From fed74eda1ccd25c9e603592a8c05eae2f7750e8f Mon Sep 17 00:00:00 2001 From: Sam Tuke Date: Thu, 10 Jan 2013 18:48:40 +0000 Subject: Removed old and unnecessary comments --- apps/files_encryption/lib/util.php | 7 ------- 1 file changed, 7 deletions(-) (limited to 'apps/files_encryption/lib') diff --git a/apps/files_encryption/lib/util.php b/apps/files_encryption/lib/util.php index bd8d18140ae..cd46d23108a 100644 --- a/apps/files_encryption/lib/util.php +++ b/apps/files_encryption/lib/util.php @@ -93,13 +93,6 @@ class Util { ## TODO: test new encryption with proxies - # NOTE: Curretly code on line 206 onwards in lib/proxy.php needs work. This code is executed when webdav writes take place, and appears to need to convert streams into fopen resources. Currently code within the if statement on 215 is not executing. Investigate the paths (handled there (which appear to be blank), and whether oc_fsv is borking them during processing. - - # NOTE: When files are written via webdav, they are encrypted and saved on the server, though they are not readable via web ui or webdav. proof of this is the changing length of content. When read in web ui, text reads "false", persumably because decryption failed. Why no error in - - # NOTE: for some reason file_get_contents is not working in proxy class postfopen. The same line works in sscce, but always returns an empty string in proxy.php. this is the same regardless of whether oc_fs, oc_fsv, or direct use of phps file_get_contents is used - - private $view; // OC_FilesystemView object for filesystem operations private $pwd; // User Password private $client; // Client side encryption mode flag -- cgit v1.2.3 From 3fbf362ba2f60917023833800e9e2dc47e190af1 Mon Sep 17 00:00:00 2001 From: Sam Tuke Date: Mon, 14 Jan 2013 12:36:46 +0000 Subject: Removed debugging comments --- apps/files_encryption/lib/stream.php | 49 ------------------------------------ 1 file changed, 49 deletions(-) (limited to 'apps/files_encryption/lib') diff --git a/apps/files_encryption/lib/stream.php b/apps/files_encryption/lib/stream.php index 076492cfe3d..f482e2d75ac 100644 --- a/apps/files_encryption/lib/stream.php +++ b/apps/files_encryption/lib/stream.php @@ -67,8 +67,6 @@ class Stream { private $rootView; // a fsview object set to '/' public function stream_open( $path, $mode, $options, &$opened_path ) { - - //file_put_contents('/home/samtuke/newtmp.txt', 'stream_open('.$path.')' ); // Get access to filesystem via filesystemview object if ( !self::$view ) { @@ -237,8 +235,6 @@ class Stream { */ public function getKey() { - //echo "\n\$this->rawPath = {$this->rawPath}"; - // 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' ) ) { @@ -303,10 +299,6 @@ class Stream { // Find out where we are up to in the writing of data to the file $pointer = ftell( $this->handle ); - //echo "\n\n\$rawLength = $length\n"; - - //echo "\$pointer = $pointer\n"; - // Make sure the userId is set $this->getuser(); @@ -333,8 +325,6 @@ class Stream { // Concat writeCache to start of $data $data = $this->writeCache . $data; - //echo "\n\ncache + data length = ".strlen($data)."\n"; - // Clear the write cache, ready for resuse - it has been flushed and its old contents processed $this->writeCache = ''; @@ -342,27 +332,14 @@ class Stream { // // // 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 -// - //echo "\n\nNOT ON BLOCK START "; -// echo $pointer % 8192; -// -// echo "\n\n1. $currentPos\n\n"; -// // -// echo "ftell() = ".ftell($this->handle)."\n"; // fseek( $this->handle, - ( $pointer % 8192 ), SEEK_CUR ); // // $pointer = ftell( $this->handle ); - -// echo "ftell() = ".ftell($this->handle)."\n"; // // $unencryptedNewBlock = fread( $this->handle, 8192 ); -// -// echo "\n\n2. $currentPos\n\n"; // // fseek( $this->handle, - ( $currentPos % 8192 ), SEEK_CUR ); -// -// echo "\n\n3. $currentPos\n\n"; // // $block = Crypt::symmetricDecryptFileContent( $unencryptedNewBlock, $this->keyfile ); // @@ -394,40 +371,16 @@ class Stream { // } else { - //echo "\n\nbefore before ".strlen($data)."\n"; - // Read the chunk from the start of $data $chunk = substr( $data, 0, 6126 ); - //echo "before ".strlen($data)."\n"; - - //echo "\n\$this->keyfile 1 = {$this->keyfile}"; - $encrypted = $this->preWriteEncrypt( $chunk, $this->keyfile ); - //echo "\n\n\$rawEnc = $encrypted\n\n"; - - //echo "\$encrypted = ".strlen($encrypted)."\n"; - - //echo "written = ".strlen($encrypted)."\n"; - - //echo "after ".strlen($encrypted)."\n\n"; - - //file_put_contents('/home/samtuke/tmp.txt', $encrypted); - // 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 ); - //$bef = ftell( $this->handle ); - //echo "ftell before = $bef\n"; - $writtenLen = strlen( $encrypted ); //fseek( $this->handle, $writtenLen, SEEK_CUR ); - -// $aft = ftell( $this->handle ); -// echo "ftell after = $aft\n"; -// echo "ftell sum = "; -// echo $aft - $bef."\n"; // 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 ); @@ -438,8 +391,6 @@ class Stream { $this->size = max( $this->size, $pointer + $length ); - //echo "\$this->size = $this->size\n\n"; - return $length; } -- cgit v1.2.3