1
0
mirror of https://github.com/Yubico/yubikey-val.git synced 2025-02-01 01:52:18 +01:00

1. Nonce introduced in protocol. This required changes in the chain from client->verify->sync.

2. ykval-verify is modified a bit. It now acts more as a flow controller and relies on ykval-synclib 
to do details on DB-calls and counterlogic. The "system" decision making is still located in ykval-verify.
This commit is contained in:
Olov Danielson 2009-12-15 10:17:51 +00:00
parent 7be831db12
commit 6788e5effa
7 changed files with 262 additions and 216 deletions

View File

@ -121,6 +121,7 @@ class Db
public function connect(){ public function connect(){
if (! $this->db_conn = mysql_connect($this->host, $this->user, $this->pwd)) { if (! $this->db_conn = mysql_connect($this->host, $this->user, $this->pwd)) {
error_log('Could not connect: ' . mysql_error()); error_log('Could not connect: ' . mysql_error());
$this->db_conn=Null;
return false; return false;
} }
if (! mysql_select_db($this->db_name)) { if (! mysql_select_db($this->db_name)) {
@ -180,7 +181,7 @@ class Db
{ {
foreach ($values as $key=>$value){ foreach ($values as $key=>$value){
if ($value != null) $query = $query . " " . $key . "='" . $value . "',"; $query = $query . " " . $key . "='" . $value . "',";
} }
if (! $query) { if (! $query) {
log("no values to set in query. Not updating DB"); log("no values to set in query. Not updating DB");
@ -190,6 +191,7 @@ class Db
$query = rtrim($query, ",") . " WHERE id = " . $id . " and " . $condition; $query = rtrim($query, ",") . " WHERE id = " . $id . " and " . $condition;
// Insert UPDATE statement at beginning // Insert UPDATE statement at beginning
$query = "UPDATE " . $table . " SET " . $query; $query = "UPDATE " . $table . " SET " . $query;
error_log("query is " . $query);
if (! mysql_query($query)){ if (! mysql_query($query)){
error_log('Query failed: ' . mysql_error()); error_log('Query failed: ' . mysql_error());
error_log('Query was: ' . $query); error_log('Query was: ' . $query);

View File

@ -102,6 +102,27 @@ class SyncLibTest extends PHPUnit_Framework_TestCase
$this->assertTrue($sl->countersHigherThanOrEqual($otpParams, $localParams)); $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() public function testSync1()
{ {

View File

@ -10,6 +10,7 @@ define('S_NO_SUCH_CLIENT', 'NO_SUCH_CLIENT');
define('S_OPERATION_NOT_ALLOWED', 'OPERATION_NOT_ALLOWED'); define('S_OPERATION_NOT_ALLOWED', 'OPERATION_NOT_ALLOWED');
define('S_BACKEND_ERROR', 'BACKEND_ERROR'); define('S_BACKEND_ERROR', 'BACKEND_ERROR');
define('S_NOT_ENOUGH_ANSWERS', 'NOT_ENOUGH_ANSWERS'); define('S_NOT_ENOUGH_ANSWERS', 'NOT_ENOUGH_ANSWERS');
define('S_REPLAYED_REQUEST', 'REPLAYED_REQUEST');
define('TS_SEC', 1/8); define('TS_SEC', 1/8);

View File

@ -41,7 +41,7 @@ CREATE TABLE queue (
-- DROP USER 'ykval_verifier'@'localhost'; -- DROP USER 'ykval_verifier'@'localhost';
CREATE 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'; ON ykval.yubikeys to 'ykval_verifier'@'localhost';
GRANT SELECT(id, secret, active) GRANT SELECT(id, secret, active)
ON ykval.clients to 'ykval_verifier'@'localhost'; ON ykval.clients to 'ykval_verifier'@'localhost';

View File

@ -1,6 +1,7 @@
<?php <?php
require_once 'ykval-common.php'; require_once 'ykval-common.php';
require_once 'ykval-config.php'; require_once 'ykval-config.php';
require_once 'ykval-synclib.php';
$apiKey = ''; $apiKey = '';
@ -8,29 +9,24 @@ header("content-type: text/plain");
debug("Request: " . $_SERVER['QUERY_STRING']); debug("Request: " . $_SERVER['QUERY_STRING']);
$conn = mysql_connect($baseParams['__YKVAL_DB_HOST__'], $sync = new SyncLib('ykval-sync');
$baseParams['__YKVAL_DB_USER__'], if (! $sync->isConnected()) {
$baseParams['__YKVAL_DB_PW__']);
if (!$conn) {
sendResp(S_BACKEND_ERROR, $apiKey); sendResp(S_BACKEND_ERROR, $apiKey);
exit; exit;
} }
if (!mysql_select_db($baseParams['__YKVAL_DB_NAME__'], $conn)) {
sendResp(S_BACKEND_ERROR, $apiKey);
exit;
}
# #
# Define requirements on protocoll # Define requirements on protocoll
# #
$syncParams=array("modified"=>Null, $syncParams=array('modified'=>Null,
"otp"=>Null, 'otp'=>Null,
"yk_identity"=>Null, 'nonce'=>Null,
"yk_counter"=>Null, 'yk_identity'=>Null,
"yk_use"=>Null, 'yk_counter'=>Null,
"yk_high"=>Null, 'yk_use'=>Null,
"yk_low"=>Null); 'yk_high'=>Null,
'yk_low'=>Null);
# #
# Extract values from HTTP request # Extract values from HTTP request
@ -53,34 +49,20 @@ debug($tmp_log);
# Get local counter data # Get local counter data
# #
$devId = $syncParams['yk_identity']; $yk_identity = $syncParams['yk_identity'];
$ad = getAuthData($conn, $devId); $localParams = $sync->getLocalParams($yk_identity);
if (!is_array($ad)) { if (!$localParams) {
debug('Discovered Yubikey ' . $devId); debug('Invalid Yubikey ' . $yk_identity);
addNewKey($conn, $devId);
$ad = getAuthData($conn, $devId);
if (!is_array($ad)) {
debug('Invalid Yubikey ' . $devId);
sendResp(S_BACKEND_ERROR, $apiKey); sendResp(S_BACKEND_ERROR, $apiKey);
exit; exit;
} }
}
debug("Auth data:", $ad); if ($localParams['active'] != 1) {
if ($ad['active'] != 1) { debug('De-activated Yubikey ' . $yk_identity);
debug('De-activated Yubikey ' . $devId);
sendResp(S_BAD_OTP, $apiKey); sendResp(S_BAD_OTP, $apiKey);
exit; 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 # 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 # http://code.google.com/p/yubikey-val-server-php/wiki/ServerReplicationProtocol
# #
if ($syncParams['yk_counter'] > $localParams['yk_counter'] || /* Conditional update local database */
($syncParams['yk_counter'] == $localParams['yk_counter'] && $sync->updateDbCounters($syncParams);
$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? if ($sync->countersHigherThan($localParams, $syncParams)) {
$stmt = 'UPDATE yubikeys SET ' . /* sync counters are lower than local counters */
'accessed=\'' . UnixToDbTime($syncParams['modified']) . '\'' . $sync->log('warning', 'Remote server out of sync. Local params ' , $localParams);
', counter=' . $syncParams['yk_counter'] . $sync->log('warning', 'Remote server out of sync. Sync params ' , $syncParams);
', sessionUse=' . $syncParams['yk_use'] . }
', low=' . $syncParams['yk_low'] .
', high=' . $syncParams['yk_high'] .
' WHERE id=' . $ad['id'];
query($conn, $stmt);
} else { if ($sync->countersEqual($localParams, $syncParams)) {
if ($syncParams['yk_counter']==$localParams['yk_counter'] && /* sync counters are equal to local counters. */
$syncParams['yk_use']==$localParams['yk_use']) {
# sync counters are equal to local counters.
if ($syncParams['modified']==$localParams['modified']) { if ($syncParams['modified']==$localParams['modified']) {
# sync modified is equal to local modified. Sync request is unnessecarily sent, we log a "light" warning /* sync modified is equal to local modified.
error_log("ykval-sync:notice:Sync request unnessecarily sent"); Sync request is unnessecarily sent, we log a "light" warning */
$sync->log('warning', 'Sync request unnessecarily sent');
} else { } else {
# sync modified is not equal to local modified. We have an OTP replay attempt somewhere in the system /* sync modified is not equal to local modified.
error_log("ykval-sync:warning:Replayed OTP attempt. " . We have an OTP replay attempt somewhere in the system */
" identity=" . $syncParams['yk_identity'] . $sync->log('warning', 'Replayed OTP attempt. Modified differs. Local ', $localParams);
" otp=" . $syncParams['otp'] . $sync->log('warning', 'Replayed OTP attempt. Modified differs. Sync ', $syncParams);
" syncCounter=" . $syncParams['yk_counter'] .
" syncUse=" . $syncParams['yk_use'] .
" syncModified=" . $syncParams['modified'] .
" localModified=" . $localParams['modified']);
} }
} else { if ($syncParams['nonce']!=$localParams['nonce']) {
# sync counters are lower than local counters $sync->log('warning', 'Replayed OTP attempt. Nonce differs. Local ', $localParams);
error_log("ykval-sync:warning:Remote server is out of sync." . $sync->log('warning', 'Replayed OTP attempt. Nonce differs. Sync ', $syncParams);
" identity=" . $syncParams['yk_identity'] .
" syncCounter=" . $syncParams['yk_counter'] .
" syncUse=" . $syncParams['yk_use'].
" localCounter=" . $localParams['yk_counter'] .
" localUse=" . $localParams['yk_use']);
} }
} }
$extra=array('modified'=>$localParams['modified'], $extra=array('modified'=>$localParams['modified'],
'yk_identity'=>$syncParams['yk_identity'], #NOTE: Identity is never picked out from local db 'nonce'=>$localParams['nonce'],
'yk_identity'=>$yk_identity,
'yk_counter'=>$localParams['yk_counter'], 'yk_counter'=>$localParams['yk_counter'],
'yk_use'=>$localParams['yk_use'], 'yk_use'=>$localParams['yk_use'],
'yk_high'=>$localParams['yk_high'], 'yk_high'=>$localParams['yk_high'],
'yk_low'=>$localParams['yk_low']); 'yk_low'=>$localParams['yk_low']);
sendResp(S_OK, '', $extra); sendResp(S_OK, '', $extra);
?> ?>

View File

@ -9,21 +9,27 @@ class SyncLib
public $syncServers = null; public $syncServers = null;
public $dbConn = null; public $dbConn = null;
function __construct() function __construct($logname='ykval-synclib')
{ {
$this->logname=$logname;
global $baseParams; global $baseParams;
$this->syncServers = explode(";", $baseParams['__YKVAL_SYNC_POOL__']); $this->syncServers = explode(";", $baseParams['__YKVAL_SYNC_POOL__']);
$this->db=new Db($baseParams['__YKVAL_DB_HOST__'], $this->db=new Db($baseParams['__YKVAL_DB_HOST__'],
$baseParams['__YKVAL_DB_USER__'], $baseParams['__YKVAL_DB_USER__'],
$baseParams['__YKVAL_DB_PW__'], $baseParams['__YKVAL_DB_PW__'],
$baseParams['__YKVAL_DB_NAME__']); $baseParams['__YKVAL_DB_NAME__']);
$this->db->connect(); $this->isConnected=$this->db->connect();
$this->random_key=rand(0,1<<16); $this->random_key=rand(0,1<<16);
$this->max_url_chunk=$baseParams['__YKVAL_SYNC_MAX_SIMUL__']; $this->max_url_chunk=$baseParams['__YKVAL_SYNC_MAX_SIMUL__'];
$this->resync_timeout=$baseParams['__YKVAL_SYNC_RESYNC_TIMEOUT__']; $this->resync_timeout=$baseParams['__YKVAL_SYNC_RESYNC_TIMEOUT__'];
} }
function isConnected()
{
return $this->isConnected;
}
function DbTimeToUnix($db_time) function DbTimeToUnix($db_time)
{ {
$unix=strptime($db_time, '%F %H:%M:%S'); $unix=strptime($db_time, '%F %H:%M:%S');
@ -39,6 +45,16 @@ class SyncLib
if (isset($this->syncServers[$index])) return $this->syncServers[$index]; if (isset($this->syncServers[$index])) return $this->syncServers[$index];
else return ""; 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() function getLast()
{ {
$res=$this->db->last('queue', 1); $res=$this->db->last('queue', 1);
@ -46,6 +62,7 @@ class SyncLib
return array('modified'=>$this->DbTimeToUnix($res['modified_time']), return array('modified'=>$this->DbTimeToUnix($res['modified_time']),
'otp'=>$res['otp'], 'otp'=>$res['otp'],
'server'=>$res['server'], 'server'=>$res['server'],
'nonce'=>$info['nonce'],
'yk_identity'=>$info['yk_identity'], 'yk_identity'=>$info['yk_identity'],
'yk_counter'=>$info['yk_counter'], 'yk_counter'=>$info['yk_counter'],
'yk_use'=>$info['yk_use'], 'yk_use'=>$info['yk_use'],
@ -64,6 +81,7 @@ class SyncLib
'&yk_use=' . $otpParams['yk_use'] . '&yk_use=' . $otpParams['yk_use'] .
'&yk_high=' . $otpParams['yk_high'] . '&yk_high=' . $otpParams['yk_high'] .
'&yk_low=' . $otpParams['yk_low'] . '&yk_low=' . $otpParams['yk_low'] .
'&nonce=' . $otpParams['nonce'] .
',local_counter=' . $localParams['yk_counter'] . ',local_counter=' . $localParams['yk_counter'] .
'&local_use=' . $localParams['yk_use']; '&local_use=' . $localParams['yk_use'];
} }
@ -108,23 +126,49 @@ class SyncLib
else return 0; else return 0;
} }
private function log($level, $msg, $params=NULL) public function log($level, $msg, $params=NULL)
{ {
$logMsg="ykval-synclib:" . $level . ":" . $msg; $logMsg=$this->logname . ':' . $level . ':' . $msg;
if ($params) $logMsg .= " modified=" . $params['modified'] . if ($params) $logMsg .= ' modified=' . $params['modified'] .
" yk_identity=" . $params['yk_identity'] . ' nonce=' . $params['nonce'] .
" yk_counter=" . $params['yk_counter'] . ' yk_identity=' . $params['yk_identity'] .
" yk_use=" . $params['yk_use'] . ' yk_counter=' . $params['yk_counter'] .
" yk_high=" . $params['yk_high'] . ' yk_use=' . $params['yk_use'] .
" yk_low=" . $params['yk_low']; ' yk_high=' . $params['yk_high'] .
' yk_low=' . $params['yk_low'];
error_log($logMsg); 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"); $this->log("notice", "searching for " . $yk_identity . " (" . modhex2b64($yk_identity) . ") in local db");
$res = $this->db->lastBy('yubikeys', 'publicName', modhex2b64($yk_identity)); $res = $this->db->findBy('yubikeys', 'publicName', modhex2b64($yk_identity),1);
$localParams=array('modified'=>$this->DbTimeToUnix($res['accessed']),
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'], 'otp'=>$res['otp'],
'nonce'=>$res['nonce'],
'active'=>$res['active'],
'yk_identity'=>$yk_identity, 'yk_identity'=>$yk_identity,
'yk_counter'=>$res['counter'], 'yk_counter'=>$res['counter'],
'yk_use'=>$res['sessionUse'], 'yk_use'=>$res['sessionUse'],
@ -132,9 +176,11 @@ class SyncLib
'yk_low'=>$res['low']); 'yk_low'=>$res['low']);
$this->log("notice", "counter found in db ", $localParams); $this->log("notice", "counter found in db ", $localParams);
return $localParams; return $localParams;
} else {
$this->log('notice', 'params for identity ' . $yk_identity . ' not found in database');
return false;
}
} }
private function parseParamsFromMultiLineString($str) private function parseParamsFromMultiLineString($str)
@ -151,6 +197,8 @@ class SyncLib
$resParams['yk_high']=$out[1]; $resParams['yk_high']=$out[1];
preg_match("/^yk_low=([0-9]*)/m", $str, $out); preg_match("/^yk_low=([0-9]*)/m", $str, $out);
$resParams['yk_low']=$out[1]; $resParams['yk_low']=$out[1];
preg_match("/^nonce=([[:alpha:]]*)/m", $str, $out);
$resParams['nonce']=$out[1];
return $resParams; return $resParams;
} }
@ -167,7 +215,8 @@ class SyncLib
'counter'=>$params['yk_counter'], 'counter'=>$params['yk_counter'],
'sessionUse'=>$params['yk_use'], 'sessionUse'=>$params['yk_use'],
'low'=>$params['yk_low'], 'low'=>$params['yk_low'],
'high'=>$params['yk_high']), 'high'=>$params['yk_high'],
'nonce'=>$params['nonce']),
$condition)) $condition))
{ {
error_log("ykval-synclib:critical: failed to update internal DB with new counters"); 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; $p1['yk_use'] >= $p2['yk_use'])) return true;
else return false; 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) public function deleteQueueEntry($answer)
{ {
@ -286,16 +337,18 @@ class SyncLib
$this->log("warning", "queued:Local server out of sync, remote counters ", $resParams); $this->log("warning", "queued:Local server out of sync, remote counters ", $resParams);
} }
/* If received sync response have higher counters than OTP counters 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) (indicating REPLAYED_OTP)
*/ */
if ($this->countersHigherThanOrEqual($resParams, $otpParams)) {
$this->log('critical', 'queued:replayed OTP, queued sync request indicated OTP as invalid');
$this->log("warning", "queued:replayed OTP, remote counters " , $resParams); $this->log("warning", "queued:replayed OTP, remote counters " , $resParams);
$this->log("warning", "queued:replayed OTP, otp counters", $otpParams); $this->log("warning", "queued:replayed OTP, otp counters", $otpParams);
} }
/* Deletion */ /* Deletion */
$this->log('notice', 'deleting queue entry with id=' . $entry['id']); $this->log('notice', 'deleting queue entry with id=' . $entry['id']);
$this->db->deleteByMultiple('queue', array('id'=>$entry['id'])); $this->db->deleteByMultiple('queue', array('id'=>$entry['id']));
@ -349,33 +402,41 @@ class SyncLib
/* Check for warnings /* Check for warnings
If received sync response have lower counters than locally saved If received sync response have lower counters than local db
last counters (indicating that remote server wasn't synced) (indicating that remote server wasn't synced)
*/ */
if ($this->countersHigherThan($localParams, $resParams)) { if ($this->countersHigherThan($localParams, $resParams)) {
$this->log("warning", "Remote server out of sync, local counters ", $localParams); $this->log("warning", "Remote server out of sync, local counters ", $localParams);
$this->log("warning", "Remote server out of sync, remote counters ", $resParams); $this->log("warning", "Remote server out of sync, remote counters ", $resParams);
} }
/* If received sync response have higher counters than locally saved /* If received sync response have higher counters than local db
last counters (indicating that local server wasn't synced) (indicating that local server wasn't synced)
*/ */
if ($this->countersHigherThan($resParams, $localParams)) { if ($this->countersHigherThan($resParams, $localParams)) {
$this->log("warning", "Local server out of sync, local counters ", $localParams); $this->log("warning", "Local server out of sync, local counters ", $localParams);
$this->log("warning", "Local server out of sync, remote counters ", $resParams); $this->log("warning", "Local server out of sync, remote counters ", $resParams);
} }
/* If received sync response have higher counters than OTP counters 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) (indicating REPLAYED_OTP)
*/ */
if ($this->countersHigherThanOrEqual($resParams, $this->otpParams)) {
$this->log("warning", "replayed OTP, remote counters " , $resParams); $this->log("warning", "replayed OTP, remote counters " , $resParams);
$this->log("warning", "replayed OTP, otp counters", $this->otpParams); $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 */ /* Delete entry from table */
$this->deleteQueueEntry($answer); $this->deleteQueueEntry($answer);

View File

@ -9,22 +9,23 @@ header("content-type: text/plain");
debug("Request: " . $_SERVER['QUERY_STRING']); 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__'], /* Initialize the sync library. Strive to use this instead of custom DB requests,
$baseParams['__YKVAL_DB_USER__'], custom comparisons etc */
$baseParams['__YKVAL_DB_PW__']); $sync = new SyncLib();
if (!$conn) { if (! $sync->isConnected()) {
sendResp(S_BACKEND_ERROR, $apiKey); sendResp(S_BACKEND_ERROR, $apiKey);
exit; 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', ''); $h = getHttpVal('h', '');
$client = getHttpVal('id', 0); $client = getHttpVal('id', 0);
$otp = getHttpVal('otp', ''); $otp = getHttpVal('otp', '');
@ -34,9 +35,21 @@ if ($protocol_version>=2.0) {
$sl = getHttpVal('sl', ''); $sl = getHttpVal('sl', '');
$timeout = getHttpVal('timeout', ''); $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 //// Get Client info from DB
// //
if ($client <= 0) { if ($client <= 0) {
@ -45,12 +58,12 @@ if ($client <= 0) {
exit; exit;
} }
$cd = getClientData($conn, $client); $cd=$sync->getClientData($client);
if ($cd == null) { if(!$cd) {
debug('Invalid client id ' . $client); debug('Invalid client id ' . $client);
sendResp(S_NO_SUCH_CLIENT); sendResp(S_NO_SUCH_CLIENT);
exit; exit;
} }
debug("Client data:", $cd); debug("Client data:", $cd);
//// Check client signature //// Check client signature
@ -66,6 +79,7 @@ if ($h != '') {
if ($timestamp) $a['timestamp'] = $timestamp; if ($timestamp) $a['timestamp'] = $timestamp;
if ($sl) $a['sl'] = $sl; if ($sl) $a['sl'] = $sl;
if ($timeout) $a['timeout'] = $timeout; if ($timeout) $a['timeout'] = $timeout;
if ($nonce) $a['nonce'] = $nonce;
$hmac = sign($a, $apiKey); $hmac = sign($a, $apiKey);
// Compare it // Compare it
@ -109,89 +123,58 @@ debug("Decrypted OTP:", $otpinfo);
//// Get Yubikey from DB //// Get Yubikey from DB
// //
$devId = substr($otp, 0, strlen ($otp) - TOKEN_LEN); $devId = substr($otp, 0, strlen ($otp) - TOKEN_LEN);
$ad = getAuthData($conn, $devId); $yk_identity=$devId;
if (!is_array($ad)) { $localParams = $sync->getLocalParams($yk_identity);
debug('Discovered Yubikey ' . $devId); if (!$localParams) {
addNewKey($conn, $devId); debug('Invalid Yubikey ' . $yk_identity);
$ad = getAuthData($conn, $devId);
if (!is_array($ad)) {
debug('Invalid Yubikey ' . $devId);
sendResp(S_BACKEND_ERROR, $apiKey); sendResp(S_BACKEND_ERROR, $apiKey);
exit; exit;
} }
}
debug("Auth data:", $ad); debug("Auth data:", $localParams);
if ($ad['active'] != 1) { if ($localParams['active'] != 1) {
debug('De-activated Yubikey ' . $devId); debug('De-activated Yubikey ' . $devId);
sendResp(S_BAD_OTP, $apiKey); sendResp(S_BAD_OTP, $apiKey);
exit; exit;
} }
//// Check the session counter /* Build OTP params */
//
$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;
}
//// Check the session use $otpParams=array('modified'=>time(),
//
$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,
'otp'=>$otp, 'otp'=>$otp,
'nonce'=>$nonce,
'yk_identity'=>$devId, 'yk_identity'=>$devId,
'yk_counter'=>$otpinfo['session_counter'], 'yk_counter'=>$otpinfo['session_counter'],
'yk_use'=>$otpinfo['session_use'], 'yk_use'=>$otpinfo['session_use'],
'yk_high'=>$otpinfo['high'], 'yk_high'=>$otpinfo['high'],
'yk_low'=>$otpinfo['low']); '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)) { if (!$sync->queue($otpParams, $localParams)) {
debug("ykval-verify:critical:failed to queue sync requests"); 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 //// Check the time stamp
// //
@ -285,6 +278,7 @@ $extra=array();
if ($protocol_version>=2.0) { if ($protocol_version>=2.0) {
$extra['otp']=$otp; $extra['otp']=$otp;
$extra['sl'] = $sl_success_rate; $extra['sl'] = $sl_success_rate;
$extra['nonce']= $nonce;
} }
if ($timestamp==1){ if ($timestamp==1){
$extra['timestamp'] = ($otpinfo['high'] << 16) + $otpinfo['low']; $extra['timestamp'] = ($otpinfo['high'] << 16) + $otpinfo['low'];