Max. number of children tokens * ---------------------------------------------------------*/ public function __construct($max_step=100){ /* (1) Check argument */ if( abs(intval($max_step)) !== $max_step || $max_step < 0 ) throw new \Exception('Invalid argument type.'); /* (2) Manage session */ if( session_status() != PHP_SESSION_ACTIVE ) \session_start(); /* (3) Set attributes */ $this->max_step = $max_step+1; } /* (2) Gets the existing parent * * @return status TRUE: right parent + token * FALSE: Invalid token * NULL: No parent found * ---------------------------------------------------------*/ private function check_parent(){ /* (1) Check system state ---------------------------------------------------------*/ $has_token = isset($_COOKIE['_PUBLIC_']) && preg_match( '/^[a-z0-9]{128}$/i', $_COOKIE['_PUBLIC_'] ); $has_parent = isset($_SESSION['_PRIVATE_']) && preg_match( '/^([a-z0-9]{128})\.(\d+)$/i', $_SESSION['_PRIVATE_'], $p_match ); /* (1) If no parent -> NULL */ if( !$has_parent ) return null; /* (2) But if no token -> FALSE */ if( !$has_token ) return false; if( self::$DEBUG ){ echo "* PARENT RECEIVED *\n"; echo 'sess_id: '.session_id()."\n"; echo 'pub: '.$_COOKIE['_PUBLIC_']."\n"; echo 'priv: '.$_SESSION['_PRIVATE_']."\n"; } /* (2) Check parent token ---------------------------------------------------------*/ /* (1) Check public token */ if( self::tgen($p_match[1], $this->max_step) !== $_COOKIE['_PUBLIC_'] ){ if( self::$DEBUG ) echo "/!\ invalid parent pub (token)\n"; return false; } /* (2) Inherit parent properties */ $this->secret = $p_match[1]; $this->step = $p_match[2]; /* (3) If valid token */ if( self::$DEBUG ) echo "[ Valid parent pub (token) ]\n"; return true; } /* (3) Create or regenerate a parent * * @inName inDesc * * @return outName outDesc * ---------------------------------------------------------*/ public function init_parent(){ /* (1) Check parent ---------------------------------------------------------*/ /* (1) Process check */ $valid_parent = $this->check_parent(); /* (2) If invalid parent -> destroy session */ if( $valid_parent === false ){ if( self::$DEBUG ) echo "-> new session <-\n"; \session_regenerate_id(true); // true: delete old session \session_unset(); \session_destroy(); \session_start(); } /* (2) Init new parent ---------------------------------------------------------*/ /* (1) Choose new secret */ $this->secret = self::tgen(uniqid()); /* (2) Set step = max */ $this->step = $this->max_step; /* (3) Generate PRIVATE */ $_SESSION['_PRIVATE_'] = $this->secret.'.'.$this->max_step; /* (4) Generate PUBLIC */ $_COOKIE['_PUBLIC_'] = self::tgen($this->secret, $this->max_step); setcookie('_PUBLIC_', $_COOKIE['_PUBLIC_'], time()+30*60, '/'); if( self::$DEBUG ){ echo "\n* PARENT UPDATED *\n"; echo 'sess_id: '.session_id()."\n"; echo 'pub: '.$_COOKIE['_PUBLIC_']."\n"; echo 'priv: '.$_SESSION['_PRIVATE_']."\n"; } /* (5) Granted */ return $valid_parent !== false; } /* (4) Checks a child * * @return status TRUE: Valid child * FALSE: Invalid token * ---------------------------------------------------------*/ private function check_child(){ /* (1) Check the parent ---------------------------------------------------------*/ /* (1) Process parent check */ $valid_parent = $this->check_parent(); /* (2) Manage missing OR invalid parent */ if( $valid_parent !== true ) return false; /* (2) Check system state ---------------------------------------------------------*/ $has_token = !is_null(self::getTreeToken()) && preg_match( '/^[a-z0-9]{128}$/i', self::getTreeToken() ); /* (1) If no token -> false */ if( !$has_token ) return false; /* (3) Check child token ---------------------------------------------------------*/ /* (1) If no more steps -> false */ if( $this->step <= 1 ){ if( self::$DEBUG ) echo "/!\ no more token available\n"; return false; } if( self::$DEBUG ){ echo "* CHILD RECEIVED *\n"; echo 'sess_id: '.session_id()."\n"; echo 'token: '.self::getTreeToken()."\n"; echo 'priv: '.$this->secret.'.'.$this->step."\n"; } /* (2) Check child token */ if( self::tgen($this->secret, $this->step) !== self::getTreeToken() ){ if( self::$DEBUG ) echo "/!\ invalid child token\n"; return false; } /* (3) If valid token */ if( self::$DEBUG ) echo "[ Valid child token ]\n"; return true; } /* (5) Updates a child * * @inName inDesc * * @return outName outDesc * ---------------------------------------------------------*/ public function init_child(){ /* (1) Check child ---------------------------------------------------------*/ /* (1) Process check */ $valid_child = $this->check_child(); /* (2) If invalid child -> destroy session */ if( $valid_child !== true ) return false; /* (2) Update parent for other children ---------------------------------------------------------*/ /* (1) Decrement step */ $this->step--; if( $this->step > 1 ){ // only if it is not the last step /* (2) Update step in session */ $_SESSION['_PRIVATE_'] = $this->secret.'.'.$this->step; /* (3) Generate child-specific PUBLIC */ header('X-Tree-Token: '.self::tgen($this->secret, $this->step)); if( self::$DEBUG ){ echo "\n* CHILD UPDATED *\n"; echo 'sess_id: '.session_id()."\n"; echo 'token: '.self::getTreeToken()."\n"; echo 'priv: '.$_SESSION['_PRIVATE_']."\n"; } } /* (2) Granted */ return true; } /* (6) Get custom TreeToken header * * @return token TreeToken fetched from header list * NULL on error * ---------------------------------------------------------*/ private static function getTreeToken(){ /* (1) Check if exsits */ if( !isset($_SERVER['HTTP_X_TREE_TOKEN'])) return null; /* (2) Return result */ return $_SERVER['HTTP_X_TREE_TOKEN']; } /* (x) Generates a pseudo-rdm token * * @data Seed to use * @depth Token depth * * @return token @data hashed @depth times * ---------------------------------------------------------*/ private static function tgen($data, $depth=1){ /* (0) If depth < 1 -> depth=1 */ $depth = ( $depth < 1 ) ? 1 : $depth; /* (1) Apply salt */ $hash = self::$salt.$data; /* (2) Hash @depth times */ for( $d = 0 ; $d < $depth ; $d++ ) $hash = ( $d == $depth-1 ) ? hash('sha512', $hash.self::$pepper) : hash('sha512', $hash); /* (3) Return hash */ return $hash; } }