Completed
Push — master ( ac11d6...c4c91f )
by Fabio
07:06
created

TSecurityManager   A

Complexity

Total Complexity 31

Size/Duplication

Total Lines 223
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Importance

Changes 0
Metric Value
dl 0
loc 223
rs 9.8
c 0
b 0
f 0
wmc 31
lcom 1
cbo 5

17 Methods

Rating   Name   Duplication   Size   Complexity  
A init() 0 5 1
A generateRandomKey() 0 4 1
A getValidationKey() 0 10 3
A setValidationKey() 0 7 2
A getEncryptionKey() 0 10 3
A setEncryptionKey() 0 7 2
A getHashAlgorithm() 0 4 1
A setHashAlgorithm() 0 6 2
A getCryptAlgorithm() 0 4 1
A setCryptAlgorithm() 0 6 2
A encrypt() 0 11 2
A decrypt() 0 11 2
A hashData() 0 5 1
A validateData() 0 11 3
A computeHMAC() 0 4 1
A strlen() 0 4 2
A substr() 0 4 2
1
<?php
2
/**
3
 * TSecurityManager class file
4
 *
5
 * @author Qiang Xue <[email protected]>
6
 * @author LANDWEHR Computer und Software GmbH <[email protected]>
7
 * @link https://github.com/pradosoft/prado4
8
 * @copyright Copyright &copy; 2005-2016 The PRADO Group
9
 * @license https://github.com/pradosoft/prado4/blob/master/LICENSE
10
 * @package Prado\Security
11
 */
12
13
namespace Prado\Security;
14
use Prado\Exceptions\TInvalidDataValueException;
15
use Prado\Exceptions\TNotSupportedException;
16
use Prado\TPropertyValue;
17
18
/**
19
 * TSecurityManager class
20
 *
21
 * TSecurityManager provides private keys, hashing and encryption
22
 * functionalities that may be used by other PRADO components,
23
 * such as viewstate persister, cookies.
24
 *
25
 * TSecurityManager is mainly used to protect data from being tampered
26
 * and viewed. It can generate HMAC and encrypt the data.
27
 * The private key used to generate HMAC is set by {@link setValidationKey ValidationKey}.
28
 * The key used to encrypt data is specified by {@link setEncryptionKey EncryptionKey}.
29
 * If the above keys are not explicitly set, random keys will be generated
30
 * and used.
31
 *
32
 * To prefix data with an HMAC, call {@link hashData()}.
33
 * To validate if data is tampered, call {@link validateData()}, which will
34
 * return the real data if it is not tampered.
35
 * The algorithm used to generated HMAC is specified by {@link setHashAlgorithm HashAlgorithm}.
36
 *
37
 * To encrypt and decrypt data, call {@link encrypt()} and {@link decrypt()}
38
 * respectively. The encryption algorithm can be set by {@link setCryptAlgorithm CryptAlgorithm}.
39
 *
40
 * Note, to use encryption, the PHP OpenSSL extension must be loaded. This was introduced in
41
 * Prado4, older versions used the deprecated mcrypt extension with rijndael-256 cipher as
42
 * default, which does not have an equivalent in OpenSSL. Developers should keep that in mind
43
 * when migrating from Prado3 to Prado4.
44
 *
45
 * @author Qiang Xue <[email protected]>
46
 * @author LANDWEHR Computer und Software GmbH <[email protected]>
47
 * @package Prado\Security
48
 * @since 3.0
49
 */
50
class TSecurityManager extends \Prado\TModule
51
{
52
	const STATE_VALIDATION_KEY = 'prado:securitymanager:validationkey';
53
	const STATE_ENCRYPTION_KEY = 'prado:securitymanager:encryptionkey';
54
55
	private $_validationKey = null;
56
	private $_encryptionKey = null;
57
	private $_hashAlgorithm = 'sha256';
58
	private $_cryptAlgorithm = 'aes-256-cbc';
59
	private $_mbstring;
60
61
	/**
62
	 * Initializes the module.
63
	 * The security module is registered with the application.
64
	 * @param TXmlElement initial module configuration
65
	 */
66
	public function init($config)
67
	{
68
		$this->_mbstring=extension_loaded('mbstring');
69
		$this->getApplication()->setSecurityManager($this);
70
	}
71
72
	/**
73
	 * Generates a random key.
74
	 */
75
	protected function generateRandomKey()
76
	{
77
		return sprintf('%08x%08x%08x%08x',mt_rand(),mt_rand(),mt_rand(),mt_rand());
78
	}
79
80
	/**
81
	 * @return string the private key used to generate HMAC.
82
	 * If the key is not explicitly set, a random one is generated and returned.
83
	 */
84
	public function getValidationKey()
85
	{
86
		if(null === $this->_validationKey) {
87
			if(null === ($this->_validationKey = $this->getApplication()->getGlobalState(self::STATE_VALIDATION_KEY))) {
88
				$this->_validationKey = $this->generateRandomKey();
89
				$this->getApplication()->setGlobalState(self::STATE_VALIDATION_KEY, $this->_validationKey, null, true);
90
			}
91
		}
92
		return $this->_validationKey;
93
	}
94
95
	/**
96
	 * @param string the key used to generate HMAC
97
	 * @throws TInvalidDataValueException if the key is empty
98
	 */
99
	public function setValidationKey($value)
100
	{
101
		if('' === $value)
102
			throw new TInvalidDataValueException('securitymanager_validationkey_invalid');
103
104
		$this->_validationKey = $value;
105
	}
106
107
	/**
108
	 * @return string the private key used to encrypt/decrypt data.
109
	 * If the key is not explicitly set, a random one is generated and returned.
110
	 */
111
	public function getEncryptionKey()
112
	{
113
		if(null === $this->_encryptionKey) {
114
			if(null === ($this->_encryptionKey = $this->getApplication()->getGlobalState(self::STATE_ENCRYPTION_KEY))) {
115
				$this->_encryptionKey = $this->generateRandomKey();
116
				$this->getApplication()->setGlobalState(self::STATE_ENCRYPTION_KEY, $this->_encryptionKey, null, true);
117
			}
118
		}
119
		return $this->_encryptionKey;
120
	}
121
122
	/**
123
	 * @param string the key used to encrypt/decrypt data.
124
	 * @throws TInvalidDataValueException if the key is empty
125
	 */
126
	public function setEncryptionKey($value)
127
	{
128
		if('' === $value)
129
			throw new TInvalidDataValueException('securitymanager_encryptionkey_invalid');
130
131
		$this->_encryptionKey = $value;
132
	}
133
134
	/**
135
	 * @return string hashing algorithm used to generate HMAC. Defaults to 'sha256'.
136
	 */
137
	public function getHashAlgorithm()
138
	{
139
		return $this->_hashAlgorithm;
140
	}
141
142
	/**
143
	 * This method accepts all hash algorithms returned by hash_algos().
144
	 * @param string hashing algorithm used to generate HMAC.
145
	 * @throws TInvalidDataValueException if the hash algorithm is not supported.
146
	 */
147
	public function setHashAlgorithm($value)
148
	{
149
		$this->_hashAlgorithm = TPropertyValue::ensureString($value);
150
		if(!in_array($this->_hashAlgorithm, hash_algos()))
151
			throw new TInvalidDataValueException('securitymanager_hash_algorithm_invalid');
152
	}
153
154
	/**
155
	 * @return mixed the algorithm used to encrypt/decrypt data. Defaults to the string 'aes-256-cbc'.
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use string.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
156
	 */
157
	public function getCryptAlgorithm()
158
	{
159
		return $this->_cryptAlgorithm;
160
	}
161
162
	/**
163
	 * Sets the crypt algorithm (also known as cipher or cypher) that will be used for {@link encrypt} and {@link decrypt}.
164
	 * @param mixed either a string containing the cipther name.
165
	 */
166
	public function setCryptAlgorithm($value)
167
	{
168
		$this->_cryptAlgorithm = TPropertyValue::ensureString($value);
169
		if(!in_array($this->_hashAlgorithm, openssl_get_cipher_methods()))
170
			throw new TInvalidDataValueException('securitymanager_crypt_algorithm_invalid');
171
	}
172
173
	/**
174
	 * Encrypts data with {@link getEncryptionKey EncryptionKey}.
175
	 * @param string data to be encrypted.
176
	 * @return string the encrypted data
177
	 * @throws TNotSupportedException if PHP OpenSSL extension is not loaded
178
	 */
179
	public function encrypt($data)
180
	{
181
		if(extension_loaded('openssl'))
182
		{
183
			$key = md5($this->getEncryptionKey());
184
			$iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length($this->_cryptAlgorithm));
185
			return $iv.openssl_encrypt($data, $this->_cryptAlgorithm, $key, null, $iv);
186
		}
187
		else
188
			throw new TNotSupportedException('securitymanager_openssl_required');
189
	}
190
191
	/**
192
	 * Decrypts data with {@link getEncryptionKey EncryptionKey}.
193
	 * @param string data to be decrypted.
194
	 * @return string the decrypted data
195
	 * @throws TNotSupportedException if PHP OpenSSL extension is not loaded
196
	 */
197
	public function decrypt($data)
198
	{
199
		if(extension_loaded('openssl'))
200
		{
201
			$key = md5($this->getEncryptionKey());
202
			$iv = $this->substr($data, 0, openssl_cipher_iv_length($this->_cryptAlgorithm));
203
			return openssl_decrypt($this->substr($data, $this->strlen($iv), $this->strlen($data)), $this->_cryptAlgorithm, $key, null, $iv);
204
		}
205
		else
206
			throw new TNotSupportedException('securitymanager_openssl_required');
207
	}
208
209
	/**
210
	 * Prefixes data with an HMAC.
211
	 * @param string data to be hashed.
212
	 * @return string data prefixed with HMAC
213
	 */
214
	public function hashData($data)
215
	{
216
		$hmac = $this->computeHMAC($data);
217
		return $hmac.$data;
218
	}
219
220
	/**
221
	 * Validates if data is tampered.
222
	 * @param string data to be validated. The data must be previously
223
	 * generated using {@link hashData()}.
224
	 * @return string the real data with HMAC stripped off. False if the data
225
	 * is tampered.
226
	 */
227
	public function validateData($data)
228
	{
229
		$len=$this->strlen($this->computeHMAC('test'));
230
231
		if($this->strlen($data) < $len)
232
			return false;
233
234
		$hmac = $this->substr($data, 0, $len);
235
		$data2=$this->substr($data, $len, $this->strlen($data));
236
		return $hmac === $this->computeHMAC($data2) ? $data2 : false;
237
	}
238
239
	/**
240
	 * Computes the HMAC for the data with {@link getValidationKey ValidationKey}.
241
	 * @param string data to be generated HMAC
242
	 * @return string the HMAC for the data
243
	 */
244
	protected function computeHMAC($data)
245
	{
246
		return hash_hmac($this->_hashAlgorithm, $data, $this->getValidationKey());
247
	}
248
249
	/**
250
	 * Returns the length of the given string.
251
	 * If available uses the multibyte string function mb_strlen.
252
	 * @param string $string the string being measured for length
253
	 * @return int the length of the string
254
	 */
255
	private function strlen($string)
256
	{
257
		return $this->_mbstring ? mb_strlen($string,'8bit') : strlen($string);
258
	}
259
260
	/**
261
	 * Returns the portion of string specified by the start and length parameters.
262
	 * If available uses the multibyte string function mb_substr
263
	 * @param string $string the input string. Must be one character or longer.
264
	 * @param int $start the starting position
265
	 * @param int $length the desired portion length
266
	 * @return string the extracted part of string, or FALSE on failure or an empty string.
267
	 */
268
	private function substr($string,$start,$length)
269
	{
270
		return $this->_mbstring ? mb_substr($string,$start,$length,'8bit') : substr($string,$start,$length);
271
	}
272
}