Revision 2137
Added by darkviper about 9 years ago
SecureTokens.php | ||
---|---|---|
45 | 45 |
|
46 | 46 |
class SecureTokens |
47 | 47 |
{ |
48 |
/** character string for 64char encoding */ |
|
49 |
const CHAR64 = 'Zabcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZa'; |
|
50 |
/** possible settings for TokenLifeTime in seconds */ |
|
51 |
const LIFETIMES = [1800, 2700, 3600, 7200]; // seconds for 30min / 45min / 1h / 2h |
|
48 |
/** |
|
49 |
* possible settings for TokenLifeTime in seconds |
|
50 |
* @description seconds for 30min / 45min / 1h / 75min / 90min / 105min / 2h |
|
51 |
*/ |
|
52 |
/** minimum lifetime in seconds */ |
|
53 |
const LIFETIME_MIN = 1800; // 30min |
|
54 |
/** maximum lifetime in seconds */ |
|
55 |
const LIFETIME_MAX = 7200; // 120min (2h) |
|
56 |
/** stepwidth between min and max */ |
|
57 |
const LIFETIME_STEP = 900; // 15min |
|
52 | 58 |
/** lifetime in seconds to use in DEBUG mode if negative value is given (-1) */ |
53 |
const DEBUG_LIFETIME = 300; |
|
54 |
|
|
59 |
const DEBUG_LIFETIME = 300; // 5 |
|
55 | 60 |
/** array to hold all tokens from the session */ |
56 | 61 |
private $aTokens = array( |
57 |
'default' => array('value' => 0, 'expire' => 0) |
|
62 |
'default' => array('value' => 0, 'expire' => 0, 'instance' => 0)
|
|
58 | 63 |
); |
59 |
/** the salt for this run */
|
|
64 |
/** the salt for this instance */
|
|
60 | 65 |
private $sSalt = ''; |
61 | 66 |
/** fingerprint of the current connection */ |
62 | 67 |
private $sFingerprint = ''; |
63 |
/** the FTAN token which is valid for this run */
|
|
68 |
/** the FTAN token which is valid for this instance */
|
|
64 | 69 |
private $aLastCreatedFtan = null; |
70 |
/** the time when tokens expired if they created in this instance */ |
|
71 |
private $iExpireTime = 0; |
|
72 |
/** remove selected tokens only and update all others */ |
|
73 |
private $bPreserveAllOtherTokens = false; |
|
74 |
/** id of the current instance */ |
|
75 |
private $sCurrentInstance = null; |
|
76 |
/** id of the instance to remove */ |
|
77 |
private $sInstanceToDelete = null; |
|
78 |
/** id of the instance to update expire time */ |
|
79 |
private $sInstanceToUpdate = null; |
|
65 | 80 |
/* --- settings for SecureTokens ------------------------------------------------------ */ |
66 | 81 |
/** use fingerprinting to encode */ |
67 | 82 |
private $bUseFingerprint = true; |
68 | 83 |
/** maximum lifetime of a token in seconds */ |
69 |
private $iTokenLifeTime = 1800; // 30min / 45min / 1h / 2h (default = 30min)
|
|
84 |
private $iTokenLifeTime = 1800; // between LIFETIME_MIN and LIFETIME_MAX (default = 30min)
|
|
70 | 85 |
/** bit length of the IPv4 Netmask (0-32 // 0 = off default = 24) */ |
71 | 86 |
private $iNetmaskLengthV4 = 0; |
72 | 87 |
/** bit length of the IPv6 Netmask (0-128 // 0 = off default = 64) */ |
... | ... | |
76 | 91 |
* constructor |
77 | 92 |
* @param (void) |
78 | 93 |
*/ |
79 |
protected function __construct() { |
|
94 |
protected function __construct() |
|
95 |
{ |
|
80 | 96 |
// load settings if available |
81 | 97 |
$this->getSettings(); |
98 |
// generate salt for calculations in this instance |
|
99 |
$this->sSalt = $this->generateSalt(); |
|
100 |
// generate fingerprint for the current connection |
|
101 |
$this->sFingerprint = $this->buildFingerprint(); |
|
102 |
// define the expiretime for this instance |
|
103 |
$this->iExpireTime = time() + $this->iTokenLifeTime; |
|
104 |
// calculate the instance id for this instance |
|
105 |
$this->sCurrentInstance = $this->encode64(md5($this->iExpireTime.$this->sSalt)); |
|
82 | 106 |
// load array of tokens from session |
83 | 107 |
$this->loadTokens(); |
84 | 108 |
// at first of all remove expired tokens |
85 | 109 |
$this->removeExpiredTokens(); |
86 |
// generate salt for calculations in this run |
|
87 |
$this->sSalt = $this->generateSalt(); |
|
88 |
// generate fingerprint for the current connection |
|
89 |
$this->sFingerprint = $this->buildFingerprint(); |
|
90 | 110 |
} |
91 | 111 |
|
92 | 112 |
/** |
93 |
* @param (void) |
|
94 |
* @return integer |
|
113 |
* destructor |
|
95 | 114 |
*/ |
96 |
public function getTokenLifeTime()
|
|
115 |
final public function __destruct()
|
|
97 | 116 |
{ |
98 |
// return 5 seconds less then lifetime to compensate delay of transfer to the client |
|
99 |
return $this->iTokenLifeTime - 5; |
|
117 |
foreach ($this->aTokens as $sKey => $aToken) { |
|
118 |
if ($aToken['instance'] == $this->sInstanceToUpdate) { |
|
119 |
$this->aTokens[$sKey]['instance'] = $this->sCurrentInstance; |
|
120 |
$this->aTokens[$sKey]['expire'] = $this->iExpireTime; |
|
121 |
} elseif ($aToken['instance'] == $this->sInstanceToDelete) { |
|
122 |
unset($this->aTokens[$sKey]); |
|
123 |
} |
|
124 |
} |
|
125 |
$this->saveTokens(); |
|
100 | 126 |
} |
127 |
|
|
101 | 128 |
/** |
129 |
* returns all TokenLifeTime values |
|
130 |
* @return array |
|
131 |
*/ |
|
132 |
final public function getTokenLifeTime() |
|
133 |
{ |
|
134 |
return array( |
|
135 |
'min' => self::LIFETIME_MIN, |
|
136 |
'max' => self::LIFETIME_MAX, |
|
137 |
'step' => self::LIFETIME_STEP, |
|
138 |
'value' => $this->iTokenLifeTime |
|
139 |
); |
|
140 |
} |
|
141 |
|
|
142 |
/** |
|
102 | 143 |
* Dummy method for backward compatibility |
103 | 144 |
* @return void |
104 | 145 |
* @deprecated from WB-2.8.3-SP5 |
... | ... | |
153 | 194 |
/** |
154 | 195 |
* checks received form-transactionnumbers against session-stored one |
155 | 196 |
* @param string $mode: requestmethode POST(default) or GET |
197 |
* @param bool $bPreserve (default=false) |
|
156 | 198 |
* @return bool: true if numbers matches against stored ones |
157 | 199 |
* |
158 | 200 |
* requirements: an active session must be available |
159 | 201 |
* this check will prevent from multiple sending a form. history.back() also will never work |
160 | 202 |
*/ |
161 |
final public function checkFTAN($mMode = 'POST') |
|
203 |
final public function checkFTAN($mMode = 'POST', $bPreserve = false)
|
|
162 | 204 |
{ |
163 | 205 |
$bRetval = false; |
206 |
$this->bPreserveAllOtherTokens = $bPreserve ?: $this->bPreserveAllOtherTokens; |
|
164 | 207 |
// get the POST/GET arguments |
165 | 208 |
$aArguments = (strtoupper($mMode) == 'POST' ? $_POST : $_GET); |
166 | 209 |
// encode the value of all matching tokens |
... | ... | |
205 | 248 |
* @param string $sFieldname: name of the POST/GET-Field containing the key or hex-key itself |
206 | 249 |
* @param mixed $mDefault: returnvalue if key not exist (default 0) |
207 | 250 |
* @param string $sRequest: requestmethode can be POST or GET or '' (default POST) |
251 |
* @param bool $bPreserve (default=false) |
|
208 | 252 |
* @return mixed: the original value (string, numeric, array) or DEFAULT if request fails |
209 | 253 |
* @description: each IDKEY can be checked only once. Unused Keys stay in list until they expire |
210 | 254 |
*/ |
211 |
final public function checkIDKEY( $sFieldname, $mDefault = 0, $sRequest = 'POST' )
|
|
255 |
final public function checkIDKEY($sFieldname, $mDefault = 0, $sRequest = 'POST', $bPreserve = false)
|
|
212 | 256 |
{ |
213 | 257 |
$mReturnValue = $mDefault; // set returnvalue to default |
258 |
$this->bPreserveAllOtherTokens = $bPreserve ?: $this->bPreserveAllOtherTokens; |
|
214 | 259 |
$sRequest = strtoupper($sRequest); |
215 | 260 |
switch ($sRequest) { |
216 | 261 |
case 'POST': |
... | ... | |
234 | 279 |
} |
235 | 280 |
return $mReturnValue; |
236 | 281 |
} |
237 |
// *** from here private methods only **************************************************** |
|
238 | 282 |
|
239 | 283 |
/** |
284 |
* make a valid LifeTime value from given integer on the rules of class SecureTokens |
|
285 |
* @param integer $iLifeTime |
|
286 |
* @return integer |
|
287 |
*/ |
|
288 |
final public function sanitizeLifeTime($iLifeTime) |
|
289 |
{ |
|
290 |
$iLifeTime = intval($iLifeTime); |
|
291 |
for ($i = self::LIFETIME_MIN; $i <= self::LIFETIME_MAX; $i += self::LIFETIME_STEP) { |
|
292 |
$aLifeTimes[] = $i; |
|
293 |
} |
|
294 |
$iRetval = array_pop($aLifeTimes); |
|
295 |
foreach ($aLifeTimes as $iValue) { |
|
296 |
if ($iLifeTime <= $iValue) { |
|
297 |
$iRetval = $iValue; |
|
298 |
break; |
|
299 |
} |
|
300 |
} |
|
301 |
return $iRetval; |
|
302 |
} |
|
303 |
/* ************************************************************************************ */ |
|
304 |
/* *** from here private methods only *** */ |
|
305 |
/* ************************************************************************************ */ |
|
306 |
/** |
|
240 | 307 |
* load all tokens from session |
241 | 308 |
*/ |
242 | 309 |
private function loadTokens() |
... | ... | |
270 | 337 |
$sTokenName = sprintf('%16x', hexdec($sTokenName)+1); |
271 | 338 |
} |
272 | 339 |
$this->aTokens[$sTokenName] = array( |
273 |
'value' => $sValue, |
|
274 |
'expire' => time()+$this->iTokenLifeTime |
|
340 |
'value' => $sValue, |
|
341 |
'expire' => $this->iExpireTime, |
|
342 |
'instance' => $this->sCurrentInstance |
|
275 | 343 |
); |
276 |
$this->saveTokens(); |
|
277 | 344 |
return $sTokenName; |
278 | 345 |
} |
279 | 346 |
|
... | ... | |
284 | 351 |
private function removeToken($sTokenName) |
285 | 352 |
{ |
286 | 353 |
if (isset($this->aTokens[$sTokenName])) { |
354 |
if ($this->bPreserveAllOtherTokens) { |
|
355 |
$this->sInstanceToUpdate = $this->aTokens[$sTokenName]['instance']; |
|
356 |
} else { |
|
357 |
$this->sInstanceToDelete = $this->aTokens[$sTokenName]['instance']; |
|
358 |
} |
|
287 | 359 |
unset($this->aTokens[$sTokenName]); |
288 | 360 |
} |
289 |
$this->saveTokens(); |
|
290 | 361 |
} |
291 | 362 |
|
292 | 363 |
/** |
... | ... | |
300 | 371 |
unset($this->aTokens[$sTokenName]); |
301 | 372 |
} |
302 | 373 |
} |
303 |
$this->saveTokens(); |
|
304 | 374 |
} |
305 | 375 |
|
306 | 376 |
/** |
... | ... | |
323 | 393 |
private function buildFingerprint() |
324 | 394 |
{ |
325 | 395 |
if (!$this->bUseFingerprint) { return md5('dummy'); } |
326 |
$sClientIp = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '0.0.0.0'; |
|
396 |
$sClientIp = '127.0.0.1'; |
|
397 |
if (array_key_exists('HTTP_X_FORWARDED_FOR', $_SERVER)){ |
|
398 |
$sClientIp = array_pop(preg_split('/\s*?,\s*?/', $_SERVER['HTTP_X_FORWARDED_FOR'], null, PREG_SPLIT_NO_EMPTY)); |
|
399 |
}else if (array_key_exists('REMOTE_ADDR', $_SERVER)) { |
|
400 |
$sClientIp = $_SERVER['REMOTE_ADDR']; |
|
401 |
}else if (array_key_exists('HTTP_CLIENT_IP', $_SERVER)) { |
|
402 |
$sClientIp = $_SERVER['HTTP_CLIENT_IP']; |
|
403 |
} |
|
327 | 404 |
$sFingerprint = __FILE__.PHP_VERSION |
328 | 405 |
. isset($_SERVER['SERVER_SIGNATURE']) ? $_SERVER['SERVER_SIGNATURE'] : 'unknown' |
329 | 406 |
. isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : 'AGENT' |
... | ... | |
390 | 467 |
* encode a hex string into a 64char based string |
391 | 468 |
* @param string $sMd5Hash |
392 | 469 |
* @return string |
393 |
* @description reduce the 32char length of a MD5 to 21 chars
|
|
470 |
* @description reduce the 32char length of a MD5 to 22 chars
|
|
394 | 471 |
*/ |
395 | 472 |
private function encode64($sMd5Hash) |
396 | 473 |
{ |
397 |
$sCharList = self::CHAR64; |
|
398 |
$sBin = implode( |
|
399 |
'', |
|
400 |
array_map( |
|
401 |
function($sPart) { |
|
402 |
return sprintf('%032b', hexdec($sPart)); |
|
403 |
}, |
|
404 |
str_split($sMd5Hash, 8) |
|
405 |
) |
|
406 |
); |
|
407 |
$sResult = implode( |
|
408 |
'', |
|
409 |
array_map( |
|
410 |
function($sPart) use ($sCharList) { |
|
411 |
$sPart = str_pad($sPart, 6, '0', STR_PAD_RIGHT); |
|
412 |
return $sCharList[bindec($sPart)]; |
|
413 |
}, |
|
414 |
str_split($sBin, 6) |
|
415 |
) |
|
416 |
); |
|
417 |
return $sResult; |
|
474 |
return rtrim(base64_encode(pack('h*',$sMd5Hash)), '+-= '); |
|
418 | 475 |
} |
419 | 476 |
|
420 | 477 |
/** |
... | ... | |
422 | 479 |
*/ |
423 | 480 |
private function getSettings() |
424 | 481 |
{ |
425 |
$this->bUseFingerprint = isset($this->oReg->SecTokenFingerprint) |
|
426 |
? $this->oReg->SecTokenFingerprint |
|
427 |
: $this->bUseFingerprint; |
|
428 |
$this->iNetmaskLengthV4 = isset($this->oReg->SecTokenNetmask4) |
|
429 |
? $this->oReg->SecTokenNetmask4 |
|
430 |
: $this->iNetmaskLengthV4; |
|
431 |
$this->iNetmaskLengthV6 = isset($this->oReg->SecTokenNetmask6) |
|
432 |
? $this->oReg->SecTokenNetmask6 |
|
433 |
: $this->iNetmaskLengthV6; |
|
434 |
$this->iTokenLifeTime = isset($this->oReg->SecTokenLifeTime) |
|
435 |
? $this->oReg->SecTokenLifeTime |
|
436 |
: $this->iTokenLifeTime; |
|
482 |
if (!class_exists('WbAdaptor', false)) { |
|
483 |
// for WB before 2.8.4 |
|
484 |
$this->bUseFingerprint = defined('SEC_TOKEN_FINGERPRINT') |
|
485 |
? SEC_TOKEN_FINGERPRINT |
|
486 |
: $this->bUseFingerprint; |
|
487 |
$this->iNetmaskLengthV4 = defined('SEC_TOKEN_NETMASK4') |
|
488 |
? SEC_TOKEN_NETMASK4 |
|
489 |
: $this->iNetmaskLengthV4; |
|
490 |
$this->iNetmaskLengthV6 = defined('SEC_TOKEN_NETMASK6') |
|
491 |
? SEC_TOKEN_NETMASK6 |
|
492 |
: $this->iNetmaskLengthV6; |
|
493 |
$this->iTokenLifeTime = defined('SEC_TOKEN_LIFE_TIME') |
|
494 |
? SEC_TOKEN_LIFE_TIME |
|
495 |
: $this->iTokenLifeTime; |
|
496 |
} else { |
|
497 |
// for WB from 2.8.4 and up |
|
498 |
$this->bUseFingerprint = isset($this->oReg->SecTokenFingerprint) |
|
499 |
? $this->oReg->SecTokenFingerprint |
|
500 |
: $this->bUseFingerprint; |
|
501 |
$this->iNetmaskLengthV4 = isset($this->oReg->SecTokenNetmask4) |
|
502 |
? $this->oReg->SecTokenNetmask4 |
|
503 |
: $this->iNetmaskLengthV4; |
|
504 |
$this->iNetmaskLengthV6 = isset($this->oReg->SecTokenNetmask6) |
|
505 |
? $this->oReg->SecTokenNetmask6 |
|
506 |
: $this->iNetmaskLengthV6; |
|
507 |
$this->iTokenLifeTime = isset($this->oReg->SecTokenLifeTime) |
|
508 |
? $this->oReg->SecTokenLifeTime |
|
509 |
: $this->iTokenLifeTime; |
|
510 |
} |
|
437 | 511 |
$this->iNetmaskLengthV4 = ($this->iNetmaskLengthV4 < 1 || $this->iNetmaskLengthV4 > 32) |
438 | 512 |
? 0 :$this->iNetmaskLengthV4; |
439 | 513 |
$this->iNetmaskLengthV6 = ($this->iNetmaskLengthV6 < 1 || $this->iNetmaskLengthV6 > 128) |
440 | 514 |
? 0 :$this->iNetmaskLengthV6; |
441 |
$aLifeTimes = self::LIFETIMES; |
|
442 |
sort($aLifeTimes); |
|
443 |
if ($this->iTokenLifeTime < 0 && DEBUG) { |
|
515 |
$this->iTokenLifeTime = $this->sanitizeLifeTime($this->iTokenLifeTime); |
|
516 |
if ($this->iTokenLifeTime <= self::LIFETIME_MIN && DEBUG) { |
|
444 | 517 |
$this->iTokenLifeTime = self::DEBUG_LIFETIME; |
445 |
} else { |
|
446 |
$iRetval = array_pop($aLifeTimes); |
|
447 |
foreach ($aLifeTimes as $iValue) { |
|
448 |
if ($this->iTokenLifeTime <= $iValue) { |
|
449 |
$iRetval = $iValue; |
|
450 |
break; |
|
451 |
} |
|
452 |
} |
|
453 |
$this->iTokenLifeTime = $iRetval; |
|
454 | 518 |
} |
455 |
|
|
456 | 519 |
} |
457 | 520 |
|
521 |
|
|
458 | 522 |
} // end of class SecureTokens |
Also available in: Unified diff
! class SecureTokens: added easy handling of service requests on checkFTAN()/checkIDKEY()