<?php require_once($INC . "/util.inc.php"); require_once($INC . "/log.inc.php"); require_once($INC . "/sql.inc.php"); require_once($INC . "/cache.inc.php"); require_once($INC . "/prof.inc.php"); $rg_token_error = ""; function rg_token_set_error($str) { global $rg_token_error; $rg_token_error = $str; rg_log($str); } function rg_token_error() { global $rg_token_error; return $rg_token_error; } /* * Delete a token */ function rg_token_delete($db, $sid, $token) { rg_prof_start("token_delete"); rg_log_enter("token_delete: sid=$sid token=$token"); $ret = array(); $ret['ok'] = 0; while (1) { $params = array("sid" => $sid); $add_token = ""; if (!empty($token)) { $add_token = " AND token = @@token@@"; $params['token'] = $token; } $sql = "DELETE FROM tokens" . " WHERE sid = @@sid@@" . $add_token; $res = rg_sql_query_params($db, $sql, $params); if ($res === FALSE) { rg_token_set_error("cannot delete token (" . rg_sql_error() . ")"); break; } rg_sql_free_result($res); $ret['ok'] = 1; break; } rg_log_exit(); rg_prof_end("token_delete"); return $ret; } /* * This function will get the master key from db */ function rg_token_get_master($db) { rg_prof_start("token_get_master"); rg_log_enter("token_get_master"); $ret = FALSE; while (1) { $key = rg_state_get($db, 'token_key'); if ($key === FALSE) { rg_token_set_error("cannot get token_key:" . " " . rg_state_error()); break; } if (empty($key)) { $key = rg_id(32); $r = rg_state_set($db, 'token_key', $key); if ($r !== TRUE) { rg_token_set_error("cannot set state:" . " " . rg_state_error()); break; } } $ret = $key; break; } rg_log_exit(); rg_prof_end("token_get_master"); return $ret; } /* * Returns TRUE if the token is valid * @double_allowed - if TRUE, we will not mark the token as used * (for example, logout token does not have to be marked as used) */ function rg_token_valid($db, $rg, $tag, $double_allowed) { rg_prof_start("token_valid"); rg_log_enter("token_valid: sid=" . $rg['sid'] . " token=" . $rg['token'] . " ua=" . $rg['ua'] . ' tag=' . $tag); $ret = FALSE; while (1) { $len = strlen($rg['token']); if ($len < 32) { rg_token_set_error("invalid token"); rg_security_violation_no_exit("invalid token ($len != 32)"); break; } $rg['token'] = substr($rg['token'], 0, 32); // We have to check first because of ua $key = rg_token_get_master($db); if ($key === FALSE) break; $rand = substr($rg['token'], 0, 16); $sign = substr($rg['token'], 16, 16); $data = $rand . $rg['sid'] . $rg['ua'] . $tag; $hash = hash_hmac('sha512', $data, $key); if ($hash === FALSE) { rg_token_set_error("cannot compute hmac"); break; } $hash = substr($hash, 0, 16); if (strcmp($sign, $hash) != 0) { rg_log("DEBUG: sign=$sign != hash=$hash data=$data"); rg_token_set_error("token invalid"); rg_security_violation_no_exit("invalid token (sign)"); break; } $ukey = 'sess' . '::' . $rg['sid'] . '::' . 'used_tokens' . '::' . $rg['token']; $c = rg_cache_get($ukey); if ($c === '1') { rg_token_set_error("token already used"); break; } $params = array("sid" => $rg['sid'], "token" => $rg['token'], "expire" => time() + 24 * 3600); if ($c === FALSE) { // We check to see if token was already used $sql = "SELECT 1 FROM tokens" . " WHERE sid = @@sid@@" . " AND token = @@token@@"; $res = rg_sql_query_params($db, $sql, $params); if ($res === FALSE) { rg_token_set_error("cannot check if token is used" . " (" . rg_sql_error() . ")"); break; } $rows = rg_sql_num_rows($res); rg_sql_free_result($res); if ($rows == 1) { rg_token_set_error("token already used"); break; } } if (strncmp($rg['sid'], "X", 1) == 0) { // We have a pre-login session: we do not have to mark // the token as used. $ret = TRUE; break; } if ($double_allowed) { $ret = TRUE; break; } // Unset cached token to generate a new one for this tag $tkey = 'sess' . '::' . $rg['sid'] . '::' . 'token' . '::' . $tag; rg_cache_unset($tkey, RG_SOCKET_NO_WAIT); $sql = "INSERT INTO tokens (sid, token, expire)" . " VALUES (@@sid@@, @@token@@, @@expire@@)"; $res = rg_sql_query_params($db, $sql, $params); if ($res === FALSE) { rg_token_set_error("cannot insert token" . " (" . rg_sql_error() . ")"); break; } rg_sql_free_result($res); // This is an optimization to not look next time in db rg_cache_set($ukey, '1', RG_SOCKET_NO_WAIT); $ret = TRUE; break; } rg_log_exit(); rg_prof_end("token_valid"); return $ret; } /* * Returns a token to be used on a form/url * We generate only one per form (tag is the id), but multiple per session. */ function rg_token_get($db, $rg, $tag) { rg_log_enter('token_get: sid=' . $rg['sid'] . ' tag=' . $tag . ' ua=' . $rg['ua']); $ret = FALSE; while (1) { if (empty($rg['sid'])) break; $key = 'sess' . '::' . $rg['sid'] . '::' . 'token' . '::' . $tag; $c = rg_cache_get($key); if ($c !== FALSE) { $ret = $c; break; } $sign_key = rg_token_get_master($db); if ($sign_key === FALSE) break; // Add a random string to protect against BREACH attack $rand = rg_id(16); $data = $rand . $rg['sid'] . $rg['ua'] . $tag; $sign = hash_hmac('sha512', $data, $sign_key); if ($sign === FALSE) { rg_token_set_error("cannot compute hmac"); break; } $sign = substr($sign, 0, 16); $ret = $rand . $sign; $ret2 = $ret; if ($rg['debug']) $ret2 .= ':' . $tag; rg_cache_set($key, $ret2, RG_SOCKET_NO_WAIT); // Optimization to not look in database next time $key = 'sess' . '::' . $rg['sid'] . '::' . 'used_tokens' . '::' . $ret; rg_cache_set($key, '0', RG_SOCKET_NO_WAIT); $ret = $ret2; break; } rg_log_exit(); return $ret; } ?>