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:
parent
7be831db12
commit
6788e5effa
@ -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);
|
||||||
|
@ -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()
|
||||||
{
|
{
|
||||||
|
@ -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);
|
||||||
|
@ -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';
|
||||||
|
115
ykval-sync.php
115
ykval-sync.php
@ -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,14 +9,8 @@ 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);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
if (!mysql_select_db($baseParams['__YKVAL_DB_NAME__'], $conn)) {
|
|
||||||
sendResp(S_BACKEND_ERROR, $apiKey);
|
sendResp(S_BACKEND_ERROR, $apiKey);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
@ -24,13 +19,14 @@ if (!mysql_select_db($baseParams['__YKVAL_DB_NAME__'], $conn)) {
|
|||||||
# 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,52 +70,37 @@ $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 ($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->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 {
|
} else {
|
||||||
# sync counters are lower than local counters
|
/* sync modified is not equal to local modified.
|
||||||
error_log("ykval-sync:warning:Remote server is out of sync." .
|
We have an OTP replay attempt somewhere in the system */
|
||||||
" identity=" . $syncParams['yk_identity'] .
|
$sync->log('warning', 'Replayed OTP attempt. Modified differs. Local ', $localParams);
|
||||||
" syncCounter=" . $syncParams['yk_counter'] .
|
$sync->log('warning', 'Replayed OTP attempt. Modified differs. Sync ', $syncParams);
|
||||||
" syncUse=" . $syncParams['yk_use'].
|
}
|
||||||
" localCounter=" . $localParams['yk_counter'] .
|
if ($syncParams['nonce']!=$localParams['nonce']) {
|
||||||
" localUse=" . $localParams['yk_use']);
|
$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'],
|
$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'],
|
||||||
|
@ -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);
|
||||||
|
152
ykval-verify.php
152
ykval-verify.php
@ -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;
|
||||||
$conn = mysql_connect($baseParams['__YKVAL_DB_HOST__'],
|
} else {
|
||||||
$baseParams['__YKVAL_DB_USER__'],
|
$protocol_version=1.0;
|
||||||
$baseParams['__YKVAL_DB_PW__']);
|
|
||||||
if (!$conn) {
|
|
||||||
sendResp(S_BACKEND_ERROR, $apiKey);
|
|
||||||
exit;
|
|
||||||
}
|
}
|
||||||
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);
|
sendResp(S_BACKEND_ERROR, $apiKey);
|
||||||
exit;
|
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,8 +58,8 @@ 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;
|
||||||
@ -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'];
|
||||||
|
Loading…
x
Reference in New Issue
Block a user