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