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()