diff --git a/build/token/core/TreeToken.php b/build/token/core/TreeToken.php new file mode 100644 index 0000000..fbb7421 --- /dev/null +++ b/build/token/core/TreeToken.php @@ -0,0 +1,302 @@ + 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; + } + + + } \ No newline at end of file