| 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
|
! class SecureTokens: added easy handling of service requests on checkFTAN()/checkIDKEY()