diff --git a/lib/Db.php b/lib/Db.php index ec5791a..b16ec64 100644 --- a/lib/Db.php +++ b/lib/Db.php @@ -121,6 +121,7 @@ class Db public function connect(){ if (! $this->db_conn = mysql_connect($this->host, $this->user, $this->pwd)) { error_log('Could not connect: ' . mysql_error()); + $this->db_conn=Null; return false; } if (! mysql_select_db($this->db_name)) { @@ -180,7 +181,7 @@ class Db { foreach ($values as $key=>$value){ - if ($value != null) $query = $query . " " . $key . "='" . $value . "',"; + $query = $query . " " . $key . "='" . $value . "',"; } if (! $query) { log("no values to set in query. Not updating DB"); @@ -190,6 +191,7 @@ class Db $query = rtrim($query, ",") . " WHERE id = " . $id . " and " . $condition; // Insert UPDATE statement at beginning $query = "UPDATE " . $table . " SET " . $query; + error_log("query is " . $query); if (! mysql_query($query)){ error_log('Query failed: ' . mysql_error()); error_log('Query was: ' . $query); diff --git a/tests/syncLibTest.php b/tests/syncLibTest.php index 9e631eb..08076da 100644 --- a/tests/syncLibTest.php +++ b/tests/syncLibTest.php @@ -102,6 +102,27 @@ class SyncLibTest extends PHPUnit_Framework_TestCase $this->assertTrue($sl->countersHigherThanOrEqual($otpParams, $localParams)); } + public function testCountersEqual() + { + $sl = new SyncLib(); + $localParams=array('yk_counter'=>100, + 'yk_use'=>10); + $otpParams=array('yk_counter'=>100, + 'yk_use'=>10); + + $this->assertTrue($sl->countersEqual($otpParams, $localParams)); + $otpParams['yk_use']=8; + $this->assertFalse($sl->countersEqual($otpParams, $localParams)); + $otpParams['yk_use']=9; + $this->assertFalse($sl->countersEqual($otpParams, $localParams)); + $otpParams['yk_use']=-11; + $this->assertFalse($sl->countersEqual($otpParams, $localParams)); + $otpParams['yk_use']=10; + $otpParams['yk_counter']=101; + $this->assertFalse($sl->countersEqual($otpParams, $localParams)); + + } + public function testSync1() { diff --git a/ykval-common.php b/ykval-common.php index 23b76f3..1449646 100644 --- a/ykval-common.php +++ b/ykval-common.php @@ -10,6 +10,7 @@ define('S_NO_SUCH_CLIENT', 'NO_SUCH_CLIENT'); define('S_OPERATION_NOT_ALLOWED', 'OPERATION_NOT_ALLOWED'); define('S_BACKEND_ERROR', 'BACKEND_ERROR'); define('S_NOT_ENOUGH_ANSWERS', 'NOT_ENOUGH_ANSWERS'); +define('S_REPLAYED_REQUEST', 'REPLAYED_REQUEST'); define('TS_SEC', 1/8); diff --git a/ykval-db.sql b/ykval-db.sql index 61aa9f4..5621049 100644 --- a/ykval-db.sql +++ b/ykval-db.sql @@ -41,7 +41,7 @@ CREATE TABLE queue ( -- DROP USER 'ykval_verifier'@'localhost'; CREATE USER 'ykval_verifier'@'localhost'; -GRANT SELECT,INSERT,UPDATE(accessed, counter, low, high, sessionUse) +GRANT SELECT,INSERT,UPDATE(accessed, counter, low, high, sessionUse, nonce) ON ykval.yubikeys to 'ykval_verifier'@'localhost'; GRANT SELECT(id, secret, active) ON ykval.clients to 'ykval_verifier'@'localhost'; diff --git a/ykval-sync.php b/ykval-sync.php index 9f45081..ffef789 100644 --- a/ykval-sync.php +++ b/ykval-sync.php @@ -1,6 +1,7 @@ isConnected()) { sendResp(S_BACKEND_ERROR, $apiKey); exit; -} -if (!mysql_select_db($baseParams['__YKVAL_DB_NAME__'], $conn)) { - sendResp(S_BACKEND_ERROR, $apiKey); - exit; -} + } # # Define requirements on protocoll # -$syncParams=array("modified"=>Null, - "otp"=>Null, - "yk_identity"=>Null, - "yk_counter"=>Null, - "yk_use"=>Null, - "yk_high"=>Null, - "yk_low"=>Null); +$syncParams=array('modified'=>Null, + 'otp'=>Null, + 'nonce'=>Null, + 'yk_identity'=>Null, + 'yk_counter'=>Null, + 'yk_use'=>Null, + 'yk_high'=>Null, + 'yk_low'=>Null); # # Extract values from HTTP request @@ -53,34 +49,20 @@ debug($tmp_log); # Get local counter data # -$devId = $syncParams['yk_identity']; -$ad = getAuthData($conn, $devId); -if (!is_array($ad)) { - debug('Discovered Yubikey ' . $devId); - addNewKey($conn, $devId); - $ad = getAuthData($conn, $devId); - if (!is_array($ad)) { - debug('Invalid Yubikey ' . $devId); - sendResp(S_BACKEND_ERROR, $apiKey); - exit; - } +$yk_identity = $syncParams['yk_identity']; +$localParams = $sync->getLocalParams($yk_identity); +if (!$localParams) { + debug('Invalid Yubikey ' . $yk_identity); + sendResp(S_BACKEND_ERROR, $apiKey); + exit; } -debug("Auth data:", $ad); -if ($ad['active'] != 1) { - debug('De-activated Yubikey ' . $devId); + +if ($localParams['active'] != 1) { + debug('De-activated Yubikey ' . $yk_identity); sendResp(S_BAD_OTP, $apiKey); exit; } -# Note: AD comes directly from the DB response. Since we want to separate -# DB-dependencies longterm, we parse out the values we want from the response -# in order to keep naming consistent in the remaining code. This could be -# considered inefficent in terms of computing power. -$localParams=array('modified'=>DbTimeToUnix($ad['accessed']), - 'yk_counter'=>$ad['counter'], - 'yk_use'=>$ad['sessionUse'], - 'yk_low'=>$ad['low'], - 'yk_high'=>$ad['high']); # # Compare sync and local counters and generate warnings according to @@ -88,57 +70,42 @@ $localParams=array('modified'=>DbTimeToUnix($ad['accessed']), # http://code.google.com/p/yubikey-val-server-php/wiki/ServerReplicationProtocol # -if ($syncParams['yk_counter'] > $localParams['yk_counter'] || - ($syncParams['yk_counter'] == $localParams['yk_counter'] && - $syncParams['yk_use'] > $localParams['yk_use'])) { -# sync counters are higher than local counters. We should update database - -#TODO: Take care of accessed field. What format should be used. seconds since epoch? - $stmt = 'UPDATE yubikeys SET ' . - 'accessed=\'' . UnixToDbTime($syncParams['modified']) . '\'' . - ', counter=' . $syncParams['yk_counter'] . - ', sessionUse=' . $syncParams['yk_use'] . - ', low=' . $syncParams['yk_low'] . - ', high=' . $syncParams['yk_high'] . - ' WHERE id=' . $ad['id']; - query($conn, $stmt); - - } else { - if ($syncParams['yk_counter']==$localParams['yk_counter'] && - $syncParams['yk_use']==$localParams['yk_use']) { -# sync counters are equal to local counters. - if ($syncParams['modified']==$localParams['modified']) { -# sync modified is equal to local modified. Sync request is unnessecarily sent, we log a "light" warning - error_log("ykval-sync:notice:Sync request unnessecarily sent"); - } else { -# sync modified is not equal to local modified. We have an OTP replay attempt somewhere in the system - error_log("ykval-sync:warning:Replayed OTP attempt. " . - " identity=" . $syncParams['yk_identity'] . - " otp=" . $syncParams['otp'] . - " syncCounter=" . $syncParams['yk_counter'] . - " syncUse=" . $syncParams['yk_use'] . - " syncModified=" . $syncParams['modified'] . - " localModified=" . $localParams['modified']); - } +/* Conditional update local database */ +$sync->updateDbCounters($syncParams); + +if ($sync->countersHigherThan($localParams, $syncParams)) { + /* sync counters are lower than local counters */ + $sync->log('warning', 'Remote server out of sync. Local params ' , $localParams); + $sync->log('warning', 'Remote server out of sync. Sync params ' , $syncParams); + } + +if ($sync->countersEqual($localParams, $syncParams)) { + /* sync counters are equal to local counters. */ + if ($syncParams['modified']==$localParams['modified']) { + /* sync modified is equal to local modified. + Sync request is unnessecarily sent, we log a "light" warning */ + $sync->log('warning', 'Sync request unnessecarily sent'); } else { -# sync counters are lower than local counters - error_log("ykval-sync:warning:Remote server is out of sync." . - " identity=" . $syncParams['yk_identity'] . - " syncCounter=" . $syncParams['yk_counter'] . - " syncUse=" . $syncParams['yk_use']. - " localCounter=" . $localParams['yk_counter'] . - " localUse=" . $localParams['yk_use']); + /* sync modified is not equal to local modified. + We have an OTP replay attempt somewhere in the system */ + $sync->log('warning', 'Replayed OTP attempt. Modified differs. Local ', $localParams); + $sync->log('warning', 'Replayed OTP attempt. Modified differs. Sync ', $syncParams); + } + if ($syncParams['nonce']!=$localParams['nonce']) { + $sync->log('warning', 'Replayed OTP attempt. Nonce differs. Local ', $localParams); + $sync->log('warning', 'Replayed OTP attempt. Nonce differs. Sync ', $syncParams); } } - $extra=array('modified'=>$localParams['modified'], - 'yk_identity'=>$syncParams['yk_identity'], #NOTE: Identity is never picked out from local db - 'yk_counter'=>$localParams['yk_counter'], - 'yk_use'=>$localParams['yk_use'], - 'yk_high'=>$localParams['yk_high'], - 'yk_low'=>$localParams['yk_low']); +$extra=array('modified'=>$localParams['modified'], + 'nonce'=>$localParams['nonce'], + 'yk_identity'=>$yk_identity, + 'yk_counter'=>$localParams['yk_counter'], + 'yk_use'=>$localParams['yk_use'], + 'yk_high'=>$localParams['yk_high'], + 'yk_low'=>$localParams['yk_low']); - sendResp(S_OK, '', $extra); +sendResp(S_OK, '', $extra); ?> diff --git a/ykval-synclib.php b/ykval-synclib.php index 131502c..a708b2a 100644 --- a/ykval-synclib.php +++ b/ykval-synclib.php @@ -9,21 +9,27 @@ class SyncLib public $syncServers = null; public $dbConn = null; - function __construct() + function __construct($logname='ykval-synclib') { + $this->logname=$logname; global $baseParams; $this->syncServers = explode(";", $baseParams['__YKVAL_SYNC_POOL__']); $this->db=new Db($baseParams['__YKVAL_DB_HOST__'], $baseParams['__YKVAL_DB_USER__'], $baseParams['__YKVAL_DB_PW__'], $baseParams['__YKVAL_DB_NAME__']); - $this->db->connect(); + $this->isConnected=$this->db->connect(); $this->random_key=rand(0,1<<16); $this->max_url_chunk=$baseParams['__YKVAL_SYNC_MAX_SIMUL__']; $this->resync_timeout=$baseParams['__YKVAL_SYNC_RESYNC_TIMEOUT__']; } + + function isConnected() + { + return $this->isConnected; + } function DbTimeToUnix($db_time) { $unix=strptime($db_time, '%F %H:%M:%S'); @@ -39,13 +45,24 @@ class SyncLib if (isset($this->syncServers[$index])) return $this->syncServers[$index]; else return ""; } + + function getClientData($client) + { + $res=$this->db->customQuery('SELECT id, secret FROM clients WHERE active AND id='.mysql_quote($client)); + if(mysql_num_rows($res)>0) { + $row = mysql_fetch_assoc($res); + mysql_free_result($res); + return $row; + } else return false; + } function getLast() { $res=$this->db->last('queue', 1); $info=$this->otpParamsFromInfoString($res['info']); return array('modified'=>$this->DbTimeToUnix($res['modified_time']), 'otp'=>$res['otp'], - 'server'=>$res['server'], + 'server'=>$res['server'], + 'nonce'=>$info['nonce'], 'yk_identity'=>$info['yk_identity'], 'yk_counter'=>$info['yk_counter'], 'yk_use'=>$info['yk_use'], @@ -64,6 +81,7 @@ class SyncLib '&yk_use=' . $otpParams['yk_use'] . '&yk_high=' . $otpParams['yk_high'] . '&yk_low=' . $otpParams['yk_low'] . + '&nonce=' . $otpParams['nonce'] . ',local_counter=' . $localParams['yk_counter'] . '&local_use=' . $localParams['yk_use']; } @@ -108,33 +126,61 @@ class SyncLib else return 0; } - private function log($level, $msg, $params=NULL) + public function log($level, $msg, $params=NULL) { - $logMsg="ykval-synclib:" . $level . ":" . $msg; - if ($params) $logMsg .= " modified=" . $params['modified'] . - " yk_identity=" . $params['yk_identity'] . - " yk_counter=" . $params['yk_counter'] . - " yk_use=" . $params['yk_use'] . - " yk_high=" . $params['yk_high'] . - " yk_low=" . $params['yk_low']; + $logMsg=$this->logname . ':' . $level . ':' . $msg; + if ($params) $logMsg .= ' modified=' . $params['modified'] . + ' nonce=' . $params['nonce'] . + ' yk_identity=' . $params['yk_identity'] . + ' yk_counter=' . $params['yk_counter'] . + ' yk_use=' . $params['yk_use'] . + ' yk_high=' . $params['yk_high'] . + ' yk_low=' . $params['yk_low']; error_log($logMsg); } - private function getLocalParams($yk_identity) + function updateLocalParams($id,$params) + { + return $this->db->update('yubikeys', + $id, + array('accessed'=>UnixToDbTime($params['modified']), + 'nonce'=>$params['nonce'], + 'counter'=>$params['yk_counter'], + 'sessionUse'=>$params['yk_use'], + 'high'=>$params['yk_high'], + 'low'=>$params['yk_low'])); + } + function getLocalParams($yk_identity) { $this->log("notice", "searching for " . $yk_identity . " (" . modhex2b64($yk_identity) . ") in local db"); - $res = $this->db->lastBy('yubikeys', 'publicName', modhex2b64($yk_identity)); - $localParams=array('modified'=>$this->DbTimeToUnix($res['accessed']), - 'otp'=>$res['otp'], - 'yk_identity'=>$yk_identity, - 'yk_counter'=>$res['counter'], - 'yk_use'=>$res['sessionUse'], - 'yk_high'=>$res['high'], - 'yk_low'=>$res['low']); - - $this->log("notice", "counter found in db ", $localParams); - - return $localParams; + $res = $this->db->findBy('yubikeys', 'publicName', modhex2b64($yk_identity),1); + if (!$res) { + $this->log('notice', 'Discovered new identity ' . $yk_identity); + $this->db->save('yubikeys', array('publicName'=>modhex2b64($yk_identity), + 'active'=>1, + 'counter'=>0, + 'sessionUse'=>0, + 'nonce'=>0)); + $res=$this->db->findBy('yubikeys', 'publicName', modhex2b64($yk_identity), 1); + } + if ($res) { + $localParams=array('id'=>$res['id'], + 'modified'=>$this->DbTimeToUnix($res['accessed']), + 'otp'=>$res['otp'], + 'nonce'=>$res['nonce'], + 'active'=>$res['active'], + 'yk_identity'=>$yk_identity, + 'yk_counter'=>$res['counter'], + 'yk_use'=>$res['sessionUse'], + 'yk_high'=>$res['high'], + 'yk_low'=>$res['low']); + + $this->log("notice", "counter found in db ", $localParams); + return $localParams; + } else { + $this->log('notice', 'params for identity ' . $yk_identity . ' not found in database'); + return false; + } } private function parseParamsFromMultiLineString($str) @@ -151,6 +197,8 @@ class SyncLib $resParams['yk_high']=$out[1]; preg_match("/^yk_low=([0-9]*)/m", $str, $out); $resParams['yk_low']=$out[1]; + preg_match("/^nonce=([[:alpha:]]*)/m", $str, $out); + $resParams['nonce']=$out[1]; return $resParams; } @@ -167,7 +215,8 @@ class SyncLib 'counter'=>$params['yk_counter'], 'sessionUse'=>$params['yk_use'], 'low'=>$params['yk_low'], - 'high'=>$params['yk_high']), + 'high'=>$params['yk_high'], + 'nonce'=>$params['nonce']), $condition)) { error_log("ykval-synclib:critical: failed to update internal DB with new counters"); @@ -195,7 +244,9 @@ class SyncLib $p1['yk_use'] >= $p2['yk_use'])) return true; else return false; } - + public function countersEqual($p1, $p2) { + return ($p1['yk_counter']==$p2['yk_counter']) && ($p1['yk_use']==$p2['yk_use']); + } public function deleteQueueEntry($answer) { @@ -285,16 +336,18 @@ class SyncLib $this->log("warning", "queued:Local server out of sync, local counters ", $localParams); $this->log("warning", "queued:Local server out of sync, remote counters ", $resParams); } - - /* If received sync response have higher counters than OTP counters - (indicating REPLAYED_OTP) - */ - if ($this->countersHigherThanOrEqual($resParams, $otpParams)) { - $this->log('critical', 'queued:replayed OTP, queued sync request indicated OTP as invalid'); + + if ($this->countersHigherThan($resParams, $otpParams) || + ($this->countersEqual($resParams, $otpParams) && + $resParams['nonce']!=$otpParams['nonce'])) { + + /* If received sync response have higher counters than OTP or same counters with different nonce + (indicating REPLAYED_OTP) + */ + $this->log("warning", "queued:replayed OTP, remote counters " , $resParams); $this->log("warning", "queued:replayed OTP, otp counters", $otpParams); } - /* Deletion */ $this->log('notice', 'deleting queue entry with id=' . $entry['id']); @@ -349,33 +402,41 @@ class SyncLib /* Check for warnings - If received sync response have lower counters than locally saved - last counters (indicating that remote server wasn't synced) + If received sync response have lower counters than local db + (indicating that remote server wasn't synced) */ if ($this->countersHigherThan($localParams, $resParams)) { $this->log("warning", "Remote server out of sync, local counters ", $localParams); $this->log("warning", "Remote server out of sync, remote counters ", $resParams); } - /* If received sync response have higher counters than locally saved - last counters (indicating that local server wasn't synced) + /* If received sync response have higher counters than local db + (indicating that local server wasn't synced) */ if ($this->countersHigherThan($resParams, $localParams)) { $this->log("warning", "Local server out of sync, local counters ", $localParams); $this->log("warning", "Local server out of sync, remote counters ", $resParams); } - /* If received sync response have higher counters than OTP counters - (indicating REPLAYED_OTP) - */ - if ($this->countersHigherThanOrEqual($resParams, $this->otpParams)) { + if ($this->countersHigherThan($resParams, $this->otpParams) || + ($this->countersEqual($resParams, $this->otpParams) && + $resParams['nonce']!=$this->otpParams['nonce'])) { + + /* If received sync response have higher counters than OTP or same counters with different nonce + (indicating REPLAYED_OTP) + */ + $this->log("warning", "replayed OTP, remote counters " , $resParams); $this->log("warning", "replayed OTP, otp counters", $this->otpParams); + } else { + + /* The answer is ok since a REPLAY was not indicated */ + + $this->valid_answers++; } - /* Check if answer marks OTP as valid */ - if (!$this->countersHigherThanOrEqual($resParams, $this->otpParams)) $this->valid_answers++; + /* Delete entry from table */ $this->deleteQueueEntry($answer); diff --git a/ykval-verify.php b/ykval-verify.php index 5af682e..352f08f 100644 --- a/ykval-verify.php +++ b/ykval-verify.php @@ -9,34 +9,47 @@ header("content-type: text/plain"); debug("Request: " . $_SERVER['QUERY_STRING']); -$protocol_version=2.0; +if (preg_match("/\/wsapi\/([0-9]*)\.([0-9]*)\//", $_SERVER['REQUEST_URI'], $out)) { + $protocol_version=$out[1]+$out[2]*0.1; + } else { + $protocol_version=1.0; + } +debug("found protocol version " . $protocol_version); -$conn = mysql_connect($baseParams['__YKVAL_DB_HOST__'], - $baseParams['__YKVAL_DB_USER__'], - $baseParams['__YKVAL_DB_PW__']); -if (!$conn) { +/* Initialize the sync library. Strive to use this instead of custom DB requests, + custom comparisons etc */ +$sync = new SyncLib(); +if (! $sync->isConnected()) { sendResp(S_BACKEND_ERROR, $apiKey); exit; -} -if (!mysql_select_db($baseParams['__YKVAL_DB_NAME__'], $conn)) { - sendResp(S_BACKEND_ERROR, $apiKey); - exit; -} + } -//// Extract values from HTTP request -// +/* Extract values from HTTP request + */ $h = getHttpVal('h', ''); $client = getHttpVal('id', 0); $otp = getHttpVal('otp', ''); $otp = strtolower($otp); $timestamp = getHttpVal('timestamp', 0); if ($protocol_version>=2.0) { - + $sl = getHttpVal('sl', ''); $timeout = getHttpVal('timeout', ''); + $nonce = getHttpVal('nonce', ''); + /* Nonce is required from protocol 2.0 */ + if(!$nonce || strlen($nonce)<16) { + debug('Protocol version >= 2.0. Nonce is missing'); + sendResp(S_MISSING_PARAMETER, $apiKey); + exit; + } } +if ($protocol_version<2.0) { + /* We need to create a nonce manually here */ + $nonce = md5(uniqid(rand())); + debug('protocol version below 2.0. Created nonce ' . $nonce); + } //// Get Client info from DB // if ($client <= 0) { @@ -45,12 +58,12 @@ if ($client <= 0) { exit; } -$cd = getClientData($conn, $client); -if ($cd == null) { +$cd=$sync->getClientData($client); +if(!$cd) { debug('Invalid client id ' . $client); sendResp(S_NO_SUCH_CLIENT); exit; -} + } debug("Client data:", $cd); //// Check client signature @@ -66,7 +79,8 @@ if ($h != '') { if ($timestamp) $a['timestamp'] = $timestamp; if ($sl) $a['sl'] = $sl; if ($timeout) $a['timeout'] = $timeout; - + if ($nonce) $a['nonce'] = $nonce; + $hmac = sign($a, $apiKey); // Compare it if ($hmac != $h) { @@ -109,89 +123,58 @@ debug("Decrypted OTP:", $otpinfo); //// Get Yubikey from DB // $devId = substr($otp, 0, strlen ($otp) - TOKEN_LEN); -$ad = getAuthData($conn, $devId); -if (!is_array($ad)) { - debug('Discovered Yubikey ' . $devId); - addNewKey($conn, $devId); - $ad = getAuthData($conn, $devId); - if (!is_array($ad)) { - debug('Invalid Yubikey ' . $devId); - sendResp(S_BACKEND_ERROR, $apiKey); - exit; - } -} -debug("Auth data:", $ad); -if ($ad['active'] != 1) { +$yk_identity=$devId; +$localParams = $sync->getLocalParams($yk_identity); +if (!$localParams) { + debug('Invalid Yubikey ' . $yk_identity); + sendResp(S_BACKEND_ERROR, $apiKey); + exit; + } + +debug("Auth data:", $localParams); +if ($localParams['active'] != 1) { debug('De-activated Yubikey ' . $devId); sendResp(S_BAD_OTP, $apiKey); exit; } -//// Check the session counter -// -$sessionCounter = $otpinfo["session_counter"]; // From the req -$seenSessionCounter = $ad['counter']; // From DB -if ($sessionCounter < $seenSessionCounter) { - debug("Replay, session counter, seen=" . $seenSessionCounter . - " this=" . $sessionCounter); - sendResp(S_REPLAYED_OTP, $apiKey); - exit; -} +/* Build OTP params */ -//// Check the session use -// -$sessionUse = $otpinfo["session_use"]; // From the req -$seenSessionUse = $ad['sessionUse']; // From DB -if ($sessionCounter == $seenSessionCounter && $sessionUse <= $seenSessionUse) { - debug("Replay, session use, seen=" . $seenSessionUse . - ' this=' . $sessionUse); - sendResp(S_REPLAYED_OTP, $apiKey); - exit; -} - -//// Valid OTP, update database -// -$stmt = 'UPDATE yubikeys SET accessed=NOW()' . - ', counter=' .$otpinfo['session_counter'] . - ', sessionUse=' . $otpinfo['session_use'] . - ', low=' . $otpinfo['low'] . - ', high=' . $otpinfo['high'] . - ' WHERE id=' . $ad['id']; -$r=query($conn, $stmt); - -$stmt = 'SELECT accessed FROM yubikeys WHERE id=' . $ad['id']; -$r=query($conn, $stmt); -if (mysql_num_rows($r) > 0) { - $row = mysql_fetch_assoc($r); - mysql_free_result($r); - $modified=DbTimeToUnix($row['accessed']); - } - else { - $modified=0; - } - -//// Queue sync requests -$sync = new SyncLib(); -// We need the modifed value from the DB -$stmp = 'SELECT accessed FROM yubikeys WHERE id=' . $ad['id']; -query($conn, $stmt); - -$otpParams=array('modified'=>$modified, +$otpParams=array('modified'=>time(), 'otp'=>$otp, + 'nonce'=>$nonce, 'yk_identity'=>$devId, 'yk_counter'=>$otpinfo['session_counter'], 'yk_use'=>$otpinfo['session_use'], 'yk_high'=>$otpinfo['high'], 'yk_low'=>$otpinfo['low']); -$localParams=array('modified'=>DbTimeToUnix($ad['accessed']), - 'otp'=>'', - 'yk_identity'=>$devId, - 'yk_counter'=>$ad['counter'], - 'yk_use'=>$ad['sessionUse'], - 'yk_high'=>$ad['high'], - 'yk_low'=>$ad['low']); +/* First check if OTP is seen with the same nonce, in such case we have an replayed request */ +if ($sync->countersEqual($localParams, $otpParams) && + $localParams['nonce']==$otpParams['nonce']) { + debug('Replayed request'); + sendResp(S_REPLAYED_REQUEST, $apikey); + exit; + } + +/* Check the OTP counters against local db */ +if ($sync->countersHigherThanOrEqual($localParams, $otpParams)) { + $sync->log('warning', 'replayed OTP: Local counters higher'); + $sync->log('warning', 'replayed OTP: Local counters ', $localParams); + $sync->log('warning', 'replayed OTP: Otp counters ', $otpParams); + sendResp(S_REPLAYED_OTP, $apiKey); + exit; + } + +/* Valid OTP, update database. */ + +if(!$sync->updateDbCounters($otpParams)) { + sendResp(S_BACKEND_ERROR, $apiKey); + exit; + } + +/* Queue sync requests */ if (!$sync->queue($otpParams, $localParams)) { debug("ykval-verify:critical:failed to queue sync requests"); @@ -241,6 +224,16 @@ if($syncres==False) { } } +/* Recreate parameters to make phising test work out + TODO: use timefunctionality in deltatime library instead */ +$sessionCounter = $otpParams['yk_counter']; +$sessionUse = $otpParams['yk_use']; +$seenSessionCounter = $localParams['yk_counter']; +$seenSessionUse = $localParams['yk_use']; + +$ad['high']=$localParams['yk_high']; +$ad['low']=$localParams['yk_low']; +$ad['accessed']=$sync->unixToDbTime($localParams['modified']); //// Check the time stamp // @@ -285,6 +278,7 @@ $extra=array(); if ($protocol_version>=2.0) { $extra['otp']=$otp; $extra['sl'] = $sl_success_rate; + $extra['nonce']= $nonce; } if ($timestamp==1){ $extra['timestamp'] = ($otpinfo['high'] << 16) + $otpinfo['low'];