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