Skip to content

Commit 1a2a9e5

Browse files
makerlabssotebuy
authored andcommitted
- Phalcon\Security is using now Phalcon\Security\Random
- Enforced that Phalcon\Security::getToken() and Phalcon\Security::getTokenKey() return a random value per request not per call - Phalcon\Security::getToken() and Phalcon\Security::getTokenKey() are using now Phalcon\Security::_numberBytes instead of passed as a argument or hardcoded value - Phalcon\Security::hash() corrected not working CRYPT_STD_DES, CRYPT_EXT_DES, MD5, CRYPT_SHA256 - Phalcon\Security::hash() CRYPT_SHA512 fixed wrong salt length - Added missing unit-tests for Phalcon\Security
1 parent b024e3d commit 1a2a9e5

File tree

3 files changed

+292
-61
lines changed

3 files changed

+292
-61
lines changed

CHANGELOG.md

+6
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,12 @@
6666
- `Phalcon\Mvc\Model\Manager::load()` now can load models from aliased namespaces
6767
- `Phalcon\Mvc\Model\Transaction\Manager` now correctly keeps account of transactions [#11554](https://github.com/phalcon/cphalcon/issues/11554)
6868
- `Phalcon\Db\Dialect\Sqlite` now maps additional column types to SQLite columns equivalents.
69+
- `Phalcon\Security` is using now `Phalcon\Security\Random`
70+
- Enforced that `Phalcon\Security::getToken()` and `Phalcon\Security::getTokenKey()` return a random value per request not per call
71+
- `Phalcon\Security::getToken()` and `Phalcon\Security::getTokenKey()` are using now `Phalcon\Security::_numberBytes` instead of passed as a argument or hardcoded value
72+
- `Phalcon\Security::hash()` corrected not working CRYPT_STD_DES, CRYPT_EXT_DES, MD5, CRYPT_SHA256
73+
- `Phalcon\Security::hash()` CRYPT_SHA512 fixed wrong salt length
74+
- Added missing unit-tests for `Phalcon\Security`
6975

7076
# [2.0.11](https://github.com/phalcon/cphalcon/releases/tag/phalcon-v2.0.11) (????-??-??)
7177
- Fix Model magic set functionality to maintain variable visibility and utilize setter methods.[#11286](https://github.com/phalcon/cphalcon/issues/11286)

phalcon/security.zep

+87-61
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
namespace Phalcon;
2121

2222
use Phalcon\DiInterface;
23+
use Phalcon\Security\Random;
2324
use Phalcon\Security\Exception;
2425
use Phalcon\Di\InjectionAwareInterface;
2526
use Phalcon\Session\AdapterInterface as SessionInterface;
@@ -54,7 +55,11 @@ class Security implements InjectionAwareInterface
5455

5556
protected _tokenValueSessionID = "$PHALCON/CSRF$";
5657

57-
protected _csrf;
58+
protected _token;
59+
60+
protected _tokenKey;
61+
62+
protected _random;
5863

5964
protected _defaultHash;
6065

@@ -78,6 +83,14 @@ class Security implements InjectionAwareInterface
7883

7984
const CRYPT_SHA512 = 9;
8085

86+
/**
87+
* Phalcon\Security constructor
88+
*/
89+
public function __construct()
90+
{
91+
let this->_random = new Random();
92+
}
93+
8194
/**
8295
* Sets the dependency injector
8396
*/
@@ -96,10 +109,13 @@ class Security implements InjectionAwareInterface
96109

97110
/**
98111
* Sets a number of bytes to be generated by the openssl pseudo random generator
112+
* @return Phalcon\Security
99113
*/
100-
public function setRandomBytes(long! randomBytes) -> void
114+
public function setRandomBytes(long! randomBytes) -> <Security>
101115
{
102116
let this->_numberBytes = randomBytes;
117+
118+
return this;
103119
}
104120

105121
/**
@@ -110,34 +126,30 @@ class Security implements InjectionAwareInterface
110126
return this->_numberBytes;
111127
}
112128

129+
/**
130+
* Returns a secure random number generator instance
131+
* @return Phalcon\Security\Random
132+
*/
133+
public function getRandom() -> <Random>
134+
{
135+
return this->_random;
136+
}
137+
113138
/**
114139
* Generate a >22-length pseudo random string to be used as salt for passwords
115140
*/
116141
public function getSaltBytes(int numberBytes = 0) -> string
117142
{
118143
var safeBytes;
119144

120-
if !function_exists("openssl_random_pseudo_bytes") {
121-
throw new Exception("Openssl extension must be loaded");
122-
}
123-
124145
if !numberBytes {
125146
let numberBytes = (int) this->_numberBytes;
126147
}
127148

128149
loop {
150+
let safeBytes = this->_random->base64Safe(numberBytes);
129151

130-
/**
131-
* Produce random bytes using openssl
132-
* Filter alpha numeric characters
133-
*/
134-
let safeBytes = phalcon_filter_alphanum(base64_encode(openssl_random_pseudo_bytes(numberBytes)));
135-
136-
if !safeBytes {
137-
continue;
138-
}
139-
140-
if strlen(safeBytes) < numberBytes {
152+
if !safeBytes || strlen(safeBytes) < numberBytes {
141153
continue;
142154
}
143155

@@ -176,6 +188,10 @@ class Security implements InjectionAwareInterface
176188
let variant = "y";
177189
break;
178190

191+
case self::CRYPT_MD5:
192+
let variant = "1";
193+
break;
194+
179195
case self::CRYPT_SHA256:
180196
let variant = "5";
181197
break;
@@ -193,22 +209,38 @@ class Security implements InjectionAwareInterface
193209
switch hash {
194210

195211
case self::CRYPT_STD_DES:
212+
case self::CRYPT_EXT_DES:
196213

197214
/* Standard DES-based hash with a two character salt from the alphabet "./0-9A-Za-z". */
198215

199-
let saltBytes = this->getSaltBytes(2);
216+
if (hash == self::CRYPT_EXT_DES) {
217+
let saltBytes = "_".this->getSaltBytes(8);
218+
} else {
219+
let saltBytes = this->getSaltBytes(2);
220+
}
221+
200222
if typeof saltBytes != "string" {
201223
throw new Exception("Unable to get random bytes for the salt");
202224
}
203225

204-
break;
226+
return crypt(password, saltBytes);
205227

228+
case self::CRYPT_MD5:
229+
case self::CRYPT_SHA256:
206230
case self::CRYPT_SHA512:
207-
let saltBytes = this->getSaltBytes(8);
231+
232+
/*
233+
* MD5 hashing with a twelve character salt
234+
* SHA-256/SHA-512 hash with a sixteen character salt.
235+
*/
236+
237+
let saltBytes = this->getSaltBytes(hash == self::CRYPT_MD5 ? 12 : 16);
238+
208239
if typeof saltBytes != "string" {
209240
throw new Exception("Unable to get random bytes for the salt");
210241
}
211-
return crypt(password, "$" . variant . "$" . saltBytes);
242+
243+
return crypt(password, "$" . variant . "$" . saltBytes . "$");
212244

213245
case self::CRYPT_DEFAULT:
214246
case self::CRYPT_BLOWFISH:
@@ -239,7 +271,7 @@ class Security implements InjectionAwareInterface
239271
}
240272
}
241273

242-
return crypt(password, "$2" . variant . "$" . sprintf("%02s", workFactor) . "$" . saltBytes);
274+
return crypt(password, "$2" . variant . "$" . sprintf("%02s", workFactor) . "$" . saltBytes . "$");
243275
}
244276

245277
return "";
@@ -286,59 +318,45 @@ class Security implements InjectionAwareInterface
286318
/**
287319
* Generates a pseudo random token key to be used as input's name in a CSRF check
288320
*/
289-
public function getTokenKey(int numberBytes = null) -> string
321+
public function getTokenKey() -> string
290322
{
291-
var safeBytes, dependencyInjector, session;
292-
293-
if !numberBytes {
294-
let numberBytes = 12;
295-
}
323+
var dependencyInjector, session;
296324

297-
if !function_exists("openssl_random_pseudo_bytes") {
298-
throw new Exception("Openssl extension must be loaded");
299-
}
325+
if null === this->_tokenKey {
326+
let dependencyInjector = <DiInterface> this->_dependencyInjector;
327+
if typeof dependencyInjector != "object" {
328+
throw new Exception("A dependency injection container is required to access the 'session' service");
329+
}
300330

301-
let dependencyInjector = <DiInterface> this->_dependencyInjector;
302-
if typeof dependencyInjector != "object" {
303-
throw new Exception("A dependency injection container is required to access the 'session' service");
331+
let this->_tokenKey = this->_random->base64Safe(this->_numberBytes);
332+
let session = <SessionInterface> dependencyInjector->getShared("session");
333+
session->set(this->_tokenKeySessionID, this->_tokenKey);
304334
}
305335

306-
let safeBytes = phalcon_filter_alphanum(base64_encode(openssl_random_pseudo_bytes(numberBytes)));
307-
let session = <SessionInterface> dependencyInjector->getShared("session");
308-
session->set(this->_tokenKeySessionID, safeBytes);
309-
310-
return safeBytes;
336+
return this->_tokenKey;
311337
}
312338

313339
/**
314340
* Generates a pseudo random token value to be used as input's value in a CSRF check
315341
*/
316-
public function getToken(int numberBytes = null) -> string
342+
public function getToken() -> string
317343
{
318-
var token, dependencyInjector, session;
319-
320-
if !numberBytes {
321-
let numberBytes = 12;
322-
}
344+
var dependencyInjector, session;
323345

324-
if !function_exists("openssl_random_pseudo_bytes") {
325-
throw new Exception("Openssl extension must be loaded");
326-
}
346+
if null === this->_token {
347+
let this->_token = this->_random->base64Safe(this->_numberBytes);
327348

328-
let token = openssl_random_pseudo_bytes(numberBytes);
329-
let token = base64_encode(token);
330-
let token = phalcon_filter_alphanum(token);
349+
let dependencyInjector = <DiInterface> this->_dependencyInjector;
331350

332-
let dependencyInjector = <DiInterface> this->_dependencyInjector;
351+
if typeof dependencyInjector != "object" {
352+
throw new Exception("A dependency injection container is required to access the 'session' service");
353+
}
333354

334-
if typeof dependencyInjector != "object" {
335-
throw new Exception("A dependency injection container is required to access the 'session' service");
355+
let session = <SessionInterface> dependencyInjector->getShared("session");
356+
session->set(this->_tokenValueSessionID, this->_token);
336357
}
337358

338-
let session = <SessionInterface> dependencyInjector->getShared("session");
339-
session->set(this->_tokenValueSessionID, token);
340-
341-
return token;
359+
return this->_token;
342360
}
343361

344362
/**
@@ -387,8 +405,7 @@ class Security implements InjectionAwareInterface
387405
* Remove the key and value of the CSRF token in session
388406
*/
389407
if returnValue && destroyIfValid {
390-
session->remove(this->_tokenKeySessionID);
391-
session->remove(this->_tokenValueSessionID);
408+
this->destroyToken();
392409
}
393410

394411
return returnValue;
@@ -408,13 +425,14 @@ class Security implements InjectionAwareInterface
408425
}
409426

410427
let session = <SessionInterface> dependencyInjector->getShared("session");
428+
411429
return session->get(this->_tokenValueSessionID);
412430
}
413431

414432
/**
415433
* Removes the value of the CSRF token and key from session
416434
*/
417-
public function destroyToken()
435+
public function destroyToken() -> <Security>
418436
{
419437
var dependencyInjector, session;
420438

@@ -428,6 +446,11 @@ class Security implements InjectionAwareInterface
428446

429447
session->remove(this->_tokenKeySessionID);
430448
session->remove(this->_tokenValueSessionID);
449+
450+
let this->_token = null;
451+
let this->_tokenKey = null;
452+
453+
return this;
431454
}
432455

433456
/**
@@ -447,10 +470,13 @@ class Security implements InjectionAwareInterface
447470

448471
/**
449472
* Sets the default hash
473+
* @return Phalcon\Security
450474
*/
451475
public function setDefaultHash(int defaultHash)
452476
{
453477
let this->_defaultHash = defaultHash;
478+
479+
return this;
454480
}
455481

456482
/**

0 commit comments

Comments
 (0)