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