Revision 2137
Added by darkviper about 10 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()