summaryrefslogtreecommitdiff
path: root/framework
diff options
context:
space:
mode:
authorxue <>2006-02-11 21:30:59 +0000
committerxue <>2006-02-11 21:30:59 +0000
commitc56b1bdb0065b752930f74eabe20f985722268ac (patch)
treed223bf62103c8c4a222e5b643e7177cfe490281a /framework
parent7670361a4fb7d660352ba998da821c51da18c448 (diff)
Added TSecurityManager and state encryption method.
Diffstat (limited to 'framework')
-rw-r--r--framework/Security/TSecurityManager.php249
-rw-r--r--framework/TApplication.php31
-rw-r--r--framework/Web/Services/TPageService.php26
-rw-r--r--framework/Web/UI/TPage.php81
-rw-r--r--framework/Web/UI/TPageStatePersister.php116
5 files changed, 384 insertions, 119 deletions
diff --git a/framework/Security/TSecurityManager.php b/framework/Security/TSecurityManager.php
new file mode 100644
index 00000000..bc77c1b6
--- /dev/null
+++ b/framework/Security/TSecurityManager.php
@@ -0,0 +1,249 @@
+<?php
+/**
+ * TSecurityManager class file
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @link http://www.pradosoft.com/
+ * @copyright Copyright &copy; 2005 PradoSoft
+ * @license http://www.pradosoft.com/license/
+ * @version $Revision: $ $Date: $
+ * @package System.Security
+ */
+
+/**
+ * TSecurityManager class
+ *
+ * TSecurityManager provides private keys, hashing and encryption
+ * functionalities that may be used by other PRADO components,
+ * such as viewstate persister, cookies.
+ *
+ * TSecurityManager is mainly used to protect data from being tampered
+ * and viewed. It can generate HMAC and encrypt the data.
+ * The private key used to generate HMAC is set by {@link setValidationKey ValidationKey}.
+ * The key used to encrypt data is specified by {@link setEncryptionKey EncryptionKey}.
+ * If the above keys are not explicitly set, random keys will be generated
+ * and used.
+ *
+ * To prefix data with an HMAC, call {@link hashData()}.
+ * To validate if data is tampered, call {@link validateData()}, which will
+ * return the real data if it is not tampered.
+ * The algorithm used to generated HMAC is specified by {@link setValidation Validation}.
+ *
+ * To encrypt and decrypt data, call {@link encrypt()} and {@link decrypt()}
+ * respectively. The encryption algorithm can be set by {@link setEncryption Encryption}.
+ *
+ * Note, to use encryption, the PHP Mcrypt extension must be loaded.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @version $Revision: $ $Date: $
+ * @package System.Security
+ * @since 3.0
+ */
+class TSecurityManager extends TModule
+{
+ const STATE_VALIDATION_KEY='prado:securitymanager:validationkey';
+ const STATE_ENCRYPTION_KEY='prado:securitymanager:encryptionkey';
+ private $_validationKey;
+ private $_encryptionKey;
+ private $_validation='SHA1';
+ private $_encryption='3DES';
+
+ /**
+ * Initializes the module.
+ * The security module is registered with the application.
+ * @param TXmlElement initial module configuration
+ */
+ public function init($config)
+ {
+ $this->getApplication()->setSecurityManager($this);
+ }
+
+ /**
+ * Generates a random key.
+ */
+ protected function generateRandomKey()
+ {
+ $v1=rand();
+ $v2=rand();
+ $v3=rand();
+ return md5("$v1$v2$v3");
+ }
+
+ /**
+ * @return string the private key used to generate HMAC.
+ * If the key is not explicitly set, a random one is generated and used.
+ */
+ public function getValidationKey()
+ {
+ if(empty($this->_validationKey))
+ {
+ if(($this->_validationKey=$this->getApplication()->getGlobalState(self::STATE_VALIDATION_KEY))===null)
+ {
+ $this->_validationKey=$this->generateRandomKey();
+ $this->getApplication()->setGlobalState(self::STATE_VALIDATION_KEY,$this->_validationKey,null);
+ }
+ }
+ return $this->_validationKey;
+ }
+
+ /**
+ * @param string the key used to generate HMAC
+ * @throws TInvalidDataValueException if the key is shorter than 8 characters.
+ */
+ public function setValidationKey($value)
+ {
+ if(strlen($value)<8)
+ throw new TInvalidDataValueException('securitymanager_validationkey_invalid');
+ $this->_validationKey=$value;
+ }
+
+ /**
+ * @return string the private key used to encrypt/decrypt data.
+ * If the key is not explicitly set, a random one is generated and used.
+ */
+ public function getEncryptionKey()
+ {
+ if(empty($this->_encryptionKey))
+ {
+ if(($this->_encryptionKey=$this->getApplication()->getGlobalState(self::STATE_ENCRYPTION_KEY))===null)
+ {
+ $this->_encryptionKey=$this->generateRandomKey();
+ $this->getApplication()->setGlobalState(self::STATE_ENCRYPTION_KEY,$this->_encryptionKey,null);
+ }
+ }
+ return $this->_encryptionKey;
+ }
+
+ /**
+ * @param string the key used to encrypt/decrypt data.
+ * @throws TInvalidDataValueException if the key is shorter than 8 characters.
+ */
+ public function setEncryptionKey($value)
+ {
+ if(strlen($value)<8)
+ throw new TInvalidDataValueException('securitymanager_encryptionkey_invalid');
+ $this->_encryptionKey=$value;
+ }
+
+ /**
+ * @return string hashing algorithm used to generate HMAC. Defaults to 'SHA1'.
+ */
+ public function getValidation()
+ {
+ return $this->_validation;
+ }
+
+ /**
+ * @param string hashing algorithm used to generate HMAC. Valid values include 'SHA1' and 'MD5'.
+ */
+ public function setValidation($value)
+ {
+ $this->_validation=TPropertyValue::ensureEnum($value,'SHA1','MD5');
+ }
+
+ /**
+ * @return string the algorithm used to encrypt/decrypt data. Defaults to '3DES'.
+ */
+ public function getEncryption()
+ {
+ return $this->_encryption;
+ }
+
+ /**
+ * @throws TNotSupportedException Do not call this method presently.
+ */
+ public function setEncryption($value)
+ {
+ throw new TNotSupportedException('Currently only 3DES encryption is supported');
+ }
+
+ /**
+ * Encrypts data with {@link getEncryptionKey EncryptionKey}.
+ * @param string data to be encrypted.
+ * @return string the encrypted data
+ * @throws TNotSupportedException if PHP Mcrypt extension is not loaded
+ */
+ public function encrypt($data)
+ {
+ if(function_exists('mcrypt_encrypt'))
+ {
+ return mcrypt_encrypt(MCRYPT_3DES, $this->getEncryptionKey(), $data, MCRYPT_MODE_CBC);
+ }
+ else
+ throw new TNotSupportedException('securitymanager_mcryptextension_required');
+ }
+
+ /**
+ * Decrypts data with {@link getEncryptionKey EncryptionKey}.
+ * @param string data to be decrypted.
+ * @return string the decrypted data
+ * @throws TNotSupportedException if PHP Mcrypt extension is not loaded
+ */
+ public function decrypt($data)
+ {
+ if(function_exists('mcrypt_decrypt'))
+ {
+ return mcrypt_decrypt(MCRYPT_3DES, $this->getEncryptionKey(), $data, MCRYPT_MODE_CBC);
+ }
+ else
+ throw new TNotSupportedException('securitymanager_mcryptextension_required');
+ }
+
+ /**
+ * Prefixes data with an HMAC.
+ * @param string data to be hashed.
+ * @return string data prefixed with HMAC
+ */
+ public function hashData($data)
+ {
+ $hmac=$this->computeHMAC($data);
+ return $hmac.$data;
+ }
+
+ /**
+ * Validates if data is tampered.
+ * @param string data to be validated. The data must be previously
+ * generated using {@link hashData()}.
+ * @return string the real data with HMAC stripped off. Null if the data
+ * is tampered.
+ */
+ public function validateData($data)
+ {
+ $len=$this->_validation==='SHA1'?40:32;
+ if(strlen($data)>=$len)
+ {
+ $hmac=substr($data,0,$len);
+ $data2=substr($data,$len);
+ return $hmac===$this->computeHMAC($data2)?$data2:null;
+ }
+ else
+ return null;
+ }
+
+ /**
+ * Computes the HMAC for the data with {@link getValidationKey ValidationKey}.
+ * @param string data to be generated HMAC
+ * @return string the HMAC for the data
+ */
+ protected function computeHMAC($data)
+ {
+ if($this->_validation==='SHA1')
+ {
+ $pack='H40';
+ $func='sha1';
+ }
+ else
+ {
+ $pack='H32';
+ $func='md5';
+ }
+ $key=$this->getValidationKey();
+ if (strlen($key) > 64)
+ $key = pack($pack, $func($key));
+ if (strlen($key) < 64)
+ $key = str_pad($key, 64, chr(0));
+ return $func((str_repeat(chr(0x5C), 64) ^ substr($key, 0, 64)) . pack($pack, $func((str_repeat(chr(0x36), 64) ^ substr($key, 0, 64)) . $data)));
+ }
+}
+
+?> \ No newline at end of file
diff --git a/framework/TApplication.php b/framework/TApplication.php
index 1a982488..2f25925d 100644
--- a/framework/TApplication.php
+++ b/framework/TApplication.php
@@ -31,6 +31,10 @@ require_once(PRADO_DIR.'/Web/THttpSession.php');
*/
require_once(PRADO_DIR.'/Security/TAuthorizationRule.php');
/**
+ * Includes TSecurityManager class
+ */
+require_once(PRADO_DIR.'/Security/TSecurityManager.php');
+/**
* Includes TPageService class (default service)
*/
require_once(PRADO_DIR.'/Web/Services/TPageService.php');
@@ -240,12 +244,14 @@ class TApplication extends TComponent
* @var IUser user instance, could be null
*/
private $_user=null;
-
/**
* @var TGlobalization module, could be null
*/
private $_globalization=null;
-
+ /**
+ * @var TSecurityManager security manager module
+ */
+ private $_security=null;
/**
* @var TAuthorizationRuleCollection collection of authorization rules
*/
@@ -651,6 +657,27 @@ class TApplication extends TComponent
}
/**
+ * @return TSecurityManager the security manager module
+ */
+ public function getSecurityManager()
+ {
+ if(!$this->_security)
+ {
+ $this->_security=new TSecurityManager;
+ $this->_security->init(null);
+ }
+ return $this->_security;
+ }
+
+ /**
+ * @param TSecurityManager the security manager module
+ */
+ public function setSecurityManager(TSecurityManager $sm)
+ {
+ $this->_security=$sm;
+ }
+
+ /**
* @return IStatePersister application state persister
*/
public function getApplicationStatePersister()
diff --git a/framework/Web/Services/TPageService.php b/framework/Web/Services/TPageService.php
index 463f2dec..393194a1 100644
--- a/framework/Web/Services/TPageService.php
+++ b/framework/Web/Services/TPageService.php
@@ -17,7 +17,6 @@ Prado::using('System.Web.UI.TPage');
Prado::using('System.Web.UI.TTemplateManager');
Prado::using('System.Web.UI.TThemeManager');
Prado::using('System.Web.UI.TAssetManager');
-Prado::using('System.Web.UI.TPageStatePersister');
/**
* TPageService class.
@@ -134,10 +133,6 @@ class TPageService extends TService
* @var TTemplateManager template manager
*/
private $_templateManager=null;
- /**
- * @var IStatePersister page state persister
- */
- private $_pageStatePersister=null;
/**
* Initializes the service.
@@ -344,27 +339,6 @@ class TPageService extends TService
}
/**
- * @return IStatePersister page state persister
- */
- public function getPageStatePersister()
- {
- if(!$this->_pageStatePersister)
- {
- $this->_pageStatePersister=new TPageStatePersister;
- $this->_pageStatePersister->init(null);
- }
- return $this->_pageStatePersister;
- }
-
- /**
- * @param IStatePersister page state persister
- */
- public function setPageStatePersister(IStatePersister $value)
- {
- $this->_pageStatePersister=$value;
- }
-
- /**
* @return string the requested page path
*/
public function getRequestedPagePath()
diff --git a/framework/Web/UI/TPage.php b/framework/Web/UI/TPage.php
index 11f89ab5..78d9115e 100644
--- a/framework/Web/UI/TPage.php
+++ b/framework/Web/UI/TPage.php
@@ -129,6 +129,11 @@ class TPage extends TTemplateControl
private $_isCrossPagePostBack=false;
private $_previousPagePath='';
+ private $_statePersisterClass='System.Web.UI.TPageStatePersister';
+ private $_statePersister=null;
+ private $_enableStateHMAC=true;
+ private $_enableStateEncryption=false;
+
/**
* Constructor.
* Sets the page object to itself.
@@ -514,14 +519,6 @@ class TPage extends TTemplateControl
}
/**
- * @return IStatePersister page state persister
- */
- protected function getPageStatePersister()
- {
- return $this->getService()->getPageStatePersister();
- }
-
- /**
* This method is invoked when control state is to be saved.
* You can override this method to do last step state saving.
* Parent implementation must be invoked.
@@ -548,7 +545,7 @@ class TPage extends TTemplateControl
*/
protected function loadPageState()
{
- $state=$this->getPageStatePersister()->load();
+ $state=$this->getStatePersister()->load();
$this->loadStateRecursive($state,$this->getEnableViewState());
}
@@ -558,7 +555,7 @@ class TPage extends TTemplateControl
protected function savePageState()
{
$state=&$this->saveStateRecursive($this->getEnableViewState());
- $this->getPageStatePersister()->save($state);
+ $this->getStatePersister()->save($state);
}
/**
@@ -806,6 +803,70 @@ class TPage extends TTemplateControl
{
$this->setViewState('Title',$value,'');
}
+
+ public function getStatePersisterClass()
+ {
+ return $this->_statePersisterClass;
+ }
+
+ public function setStatePersisterClass($value)
+ {
+ $this->_statePersisterClass=$value;
+ }
+
+ public function getStatePersister()
+ {
+ if($this->_statePersister===null)
+ {
+ $this->_statePersister=Prado::createComponent($this->_statePersisterClass);
+ if(!($this->_statePersister instanceof IPageStatePersister))
+ throw new TInvalidDataTypeException('page_statepersister_invalid');
+ $this->_statePersister->setPage($this);
+ }
+ return $this->_statePersister;
+ }
+
+ public function getEnableStateHMAC()
+ {
+ return $this->_enableStateHMAC;
+ }
+
+ public function setEnableStateHMAC($value)
+ {
+ $this->_enableStateHMAC=TPropertyValue::ensureBoolean($value);
+ }
+
+ public function getEnableStateEncryption()
+ {
+ return $this->_enableStateEncryption;
+ }
+
+ public function setEnableStateEncryption($value)
+ {
+ $this->_enableStateEncryption=TPropertyValue::ensureBoolean($value);
+ }
+}
+
+interface IPageStatePersister
+{
+ /**
+ * @param TPage the page that this persister works for
+ */
+ public function getPage();
+ /**
+ * @param TPage the page that this persister works for
+ */
+ public function setPage(TPage $page);
+ /**
+ * Saves state to persistent storage.
+ * @param string state to be stored
+ */
+ public function save($state);
+ /**
+ * Loads page state from persistent storage
+ * @return string the restored state
+ */
+ public function load();
}
?> \ No newline at end of file
diff --git a/framework/Web/UI/TPageStatePersister.php b/framework/Web/UI/TPageStatePersister.php
index 4ece9a09..746d93c8 100644
--- a/framework/Web/UI/TPageStatePersister.php
+++ b/framework/Web/UI/TPageStatePersister.php
@@ -14,36 +14,38 @@
* TPageStatePersister class
*
* TPageStatePersister implements a page state persistent method based on
- * form hidden fields. It is the default way of storing page state.
- * Should you need to access this module, you may get it via
- * {@link TPageService::getPageStatePersister}.
+ * form hidden fields.
*
- * TPageStatePersister uses a private key to generate a private unique hash
- * code to prevent the page state from being tampered.
- * By default, the private key is a randomly generated string.
- * You may specify it explicitly by setting the {@link setPrivateKey PrivateKey} property.
- * This may be useful if your application is running on a server farm.
+ * Depending on the {@link TPage::getEnableStateHMAC() EnableStateHMAC}
+ * and {@link TPage::getEnableStateEncryption() EnableStateEncryption},
+ * TPageStatePersister may do HMAC validation and encryption to prevent
+ * the state data from being tampered or viewed.
+ * The private keys and hashing/encryption methods are determined by
+ * {@link TApplication::getSecurityManager() SecurityManager}.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @version $Revision: $ $Date: $
* @package System.Web.UI
* @since 3.0
*/
-class TPageStatePersister extends TModule implements IStatePersister
+class TPageStatePersister extends TComponent implements IPageStatePersister
{
+ private $_page;
+
/**
- * @var string private key
+ * @param TPage the page that this persister works for
*/
- private $_privateKey=null;
+ public function getPage()
+ {
+ return $this->_page;
+ }
/**
- * Registers the module with the page service.
- * This method is required by IModule interface and is invoked when the module is initialized.
- * @param TXmlElement module configuration
+ * @param TPage the page that this persister works for
*/
- public function init($config)
+ public function setPage(TPage $page)
{
- $this->getService()->setPageStatePersister($this);
+ $this->_page=$page;
}
/**
@@ -53,13 +55,15 @@ class TPageStatePersister extends TModule implements IStatePersister
public function save($state)
{
Prado::trace("Saving state",'System.Web.UI.TPageStatePersister');
- $data=Prado::serialize($state);
- $hmac=$this->computeHMAC($data,$this->getPrivateKey());
- if(extension_loaded('zlib'))
- $data=gzcompress($hmac.$data);
+ if($this->_page->getEnableStateHMAC())
+ $data=$this->getApplication()->getSecurityManager()->hashData(Prado::serialize($state));
else
- $data=$hmac.$data;
- $this->getService()->getRequestedPage()->getClientScript()->registerHiddenField(TPage::FIELD_PAGESTATE,base64_encode($data));
+ $data=Prado::serialize($state);
+ if($this->_page->getEnableStateEncryption())
+ $data=$this->getApplication()->getSecurityManager()->encrypt($data);
+ if(extension_loaded('zlib'))
+ $data=gzcompress($data);
+ $this->_page->getClientScript()->registerHiddenField(TPage::FIELD_PAGESTATE,base64_encode($data));
}
/**
@@ -77,69 +81,19 @@ class TPageStatePersister extends TModule implements IStatePersister
$data=gzuncompress($str);
else
$data=$str;
- if($data!==false && strlen($data)>32)
+ if($data!==false)
{
- $hmac=substr($data,0,32);
- $state=substr($data,32);
- if($hmac===$this->computeHMAC($state,$this->getPrivateKey()))
- return Prado::unserialize($state);
- }
- throw new THttpException(400,'pagestatepersister_pagestate_corrupted');
- }
-
- /**
- * Generates a random private key used for hashing the state.
- * You may override this method to provide your own way of private key generation.
- * @return string the rondomly generated private key
- */
- protected function generatePrivateKey()
- {
- $v1=rand();
- $v2=rand();
- $v3=rand();
- return md5("$v1$v2$v3");
- }
-
- /**
- * @return string private key used for hashing the state.
- */
- public function getPrivateKey()
- {
- if(empty($this->_privateKey))
- {
- if(($this->_privateKey=$this->getApplication()->getGlobalState('prado:pagestatepersister:privatekey'))===null)
+ if($this->_page->getEnableStateEncryption())
+ $data=$this->getApplication()->getSecurityManager()->decrypt($data);
+ if($this->_page->getEnableStateHMAC())
{
- $this->_privateKey=$this->generatePrivateKey();
- $this->getApplication()->setGlobalState('prado:pagestatepersister:privatekey',$this->_privateKey,null);
+ if(($data=$this->getApplication()->getSecurityManager()->validateData($data))!==null)
+ return Prado::unserialize($data);
}
+ else
+ return $data;
}
- return $this->_privateKey;
- }
-
- /**
- * @param string private key used for hashing the state.
- * @throws TInvalidDataValueException if the length of the private key is shorter than 8.
- */
- public function setPrivateKey($value)
- {
- if(strlen($value)<8)
- throw new TInvalidDataValueException('pagestatepersister_privatekey_invalid');
- $this->_privateKey=$value;
- }
-
- /**
- * Computes a hashing code based on the input data and the private key.
- * @param string input data
- * @param string the private key
- * @return string the hashing code
- */
- private function computeHMAC($data,$key)
- {
- if (strlen($key) > 64)
- $key = pack('H32', md5($key));
- else if (strlen($key) < 64)
- $key = str_pad($key, 64, "\0");
- return md5((str_repeat("\x5c", 64) ^ substr($key, 0, 64)) . pack('H32', md5((str_repeat("\x36", 64) ^ substr($key, 0, 64)) . $data)));
+ throw new THttpException(400,'pagestatepersister_pagestate_corrupted');
}
}