mirror of
https://github.com/Yubico/yubikey-val.git
synced 2025-01-19 16:52:15 +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:
parent
7be831db12
commit
6788e5effa
@ -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);
|
||||
|
@ -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()
|
||||
{
|
||||
|
@ -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);
|
||||
|
@ -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';
|
||||
|
115
ykval-sync.php
115
ykval-sync.php
@ -1,6 +1,7 @@
|
||||
<?php
|
||||
require_once 'ykval-common.php';
|
||||
require_once 'ykval-config.php';
|
||||
require_once 'ykval-synclib.php';
|
||||
|
||||
$apiKey = '';
|
||||
|
||||
@ -8,14 +9,8 @@ header("content-type: text/plain");
|
||||
|
||||
debug("Request: " . $_SERVER['QUERY_STRING']);
|
||||
|
||||
$conn = mysql_connect($baseParams['__YKVAL_DB_HOST__'],
|
||||
$baseParams['__YKVAL_DB_USER__'],
|
||||
$baseParams['__YKVAL_DB_PW__']);
|
||||
if (!$conn) {
|
||||
sendResp(S_BACKEND_ERROR, $apiKey);
|
||||
exit;
|
||||
}
|
||||
if (!mysql_select_db($baseParams['__YKVAL_DB_NAME__'], $conn)) {
|
||||
$sync = new SyncLib('ykval-sync');
|
||||
if (! $sync->isConnected()) {
|
||||
sendResp(S_BACKEND_ERROR, $apiKey);
|
||||
exit;
|
||||
}
|
||||
@ -24,13 +19,14 @@ if (!mysql_select_db($baseParams['__YKVAL_DB_NAME__'], $conn)) {
|
||||
# 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);
|
||||
$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,52 +70,37 @@ $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
|
||||
/* Conditional update local database */
|
||||
$sync->updateDbCounters($syncParams);
|
||||
|
||||
#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']);
|
||||
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
|
||||
'nonce'=>$localParams['nonce'],
|
||||
'yk_identity'=>$yk_identity,
|
||||
'yk_counter'=>$localParams['yk_counter'],
|
||||
'yk_use'=>$localParams['yk_use'],
|
||||
'yk_high'=>$localParams['yk_high'],
|
||||
|
@ -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,6 +45,16 @@ 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);
|
||||
@ -46,6 +62,7 @@ class SyncLib
|
||||
return array('modified'=>$this->DbTimeToUnix($res['modified_time']),
|
||||
'otp'=>$res['otp'],
|
||||
'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,23 +126,49 @@ 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']),
|
||||
$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'],
|
||||
@ -132,9 +176,11 @@ class SyncLib
|
||||
'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)
|
||||
{
|
||||
@ -286,16 +337,18 @@ class SyncLib
|
||||
$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)
|
||||
*/
|
||||
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, otp counters", $otpParams);
|
||||
}
|
||||
|
||||
|
||||
/* Deletion */
|
||||
$this->log('notice', 'deleting queue entry with id=' . $entry['id']);
|
||||
$this->db->deleteByMultiple('queue', array('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
|
||||
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)
|
||||
*/
|
||||
if ($this->countersHigherThanOrEqual($resParams, $this->otpParams)) {
|
||||
|
||||
$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);
|
||||
|
152
ykval-verify.php
152
ykval-verify.php
@ -9,22 +9,23 @@ header("content-type: text/plain");
|
||||
|
||||
debug("Request: " . $_SERVER['QUERY_STRING']);
|
||||
|
||||
$protocol_version=2.0;
|
||||
|
||||
$conn = mysql_connect($baseParams['__YKVAL_DB_HOST__'],
|
||||
$baseParams['__YKVAL_DB_USER__'],
|
||||
$baseParams['__YKVAL_DB_PW__']);
|
||||
if (!$conn) {
|
||||
sendResp(S_BACKEND_ERROR, $apiKey);
|
||||
exit;
|
||||
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;
|
||||
}
|
||||
if (!mysql_select_db($baseParams['__YKVAL_DB_NAME__'], $conn)) {
|
||||
debug("found protocol version " . $protocol_version);
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
//// Extract values from HTTP request
|
||||
//
|
||||
/* Extract values from HTTP request
|
||||
*/
|
||||
$h = getHttpVal('h', '');
|
||||
$client = getHttpVal('id', 0);
|
||||
$otp = getHttpVal('otp', '');
|
||||
@ -34,9 +35,21 @@ 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,8 +58,8 @@ 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;
|
||||
@ -66,6 +79,7 @@ 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
|
||||
@ -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);
|
||||
$yk_identity=$devId;
|
||||
$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("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'];
|
||||
|
Loading…
x
Reference in New Issue
Block a user