summaryrefslogtreecommitdiff
path: root/framework/Security/TSecurityManager.php
blob: b0ea4e956782d22368d5399cd8ce3bd7e4298616 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
<?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';
	const STATE_INIT_VECTOR='prado:securitymanager:initvector';
	private $_validationKey=null;
	private $_encryptionKey=null;
	private $_initVector=null;
	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()
	{
		return rand().rand().rand().rand();
	}

	/**
	 * @return string the private key used to generate HMAC.
	 * If the key is not explicitly set, a random one is generated and returned.
	 */
	public function getValidationKey()
	{
		if($this->_validationKey===null)
		{
			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 empty
	 */
	public function setValidationKey($value)
	{
		if($value!=='')
			$this->_validationKey=$value;
		else
			throw new TInvalidDataValueException('securitymanager_validationkey_invalid');
	}

	/**
	 * @return string the private key used to encrypt/decrypt data.
	 * If the key is not explicitly set, a random one is generated and returned.
	 */
	public function getEncryptionKey()
	{
		if($this->_encryptionKey===null)
		{
			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 empty
	 */
	public function setEncryptionKey($value)
	{
		if($value!=='')
			$this->_encryptionKey=$value;
		else
			throw new TInvalidDataValueException('securitymanager_encryptionkey_invalid');
	}

	/**
	 * @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'))
		{
			$module=mcrypt_module_open(MCRYPT_3DES, '', MCRYPT_MODE_CBC, '');
			$key=substr(md5($this->getEncryptionKey()),0,mcrypt_enc_get_key_size($module));
			srand();
			$iv=mcrypt_create_iv(mcrypt_enc_get_iv_size($module), MCRYPT_RAND);
			mcrypt_generic_init($module,$key,$iv);
			$encrypted=$iv.mcrypt_generic($module,$data);
			mcrypt_generic_deinit($module);
			mcrypt_module_close($module);
			return $encrypted;
		}
		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'))
		{
			$module=mcrypt_module_open(MCRYPT_3DES, '', MCRYPT_MODE_CBC, '');
			$key=substr(md5($this->getEncryptionKey()),0,mcrypt_enc_get_key_size($module));
			$ivSize=mcrypt_enc_get_iv_size($module);
			$iv=substr($data,0,$ivSize);
			mcrypt_generic_init($module,$key,$iv);
			$decrypted=mdecrypt_generic($module,substr($data,$ivSize));
			mcrypt_generic_deinit($module);
			mcrypt_module_close($module);
			return rtrim($decrypted,"\0");
		}
		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. False 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:false;
		}
		else
			return false;
	}

	/**
	 * 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();
		$key=str_pad($func($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)));
	}
}

?>