refactoring

This commit is contained in:
Roland Gruber 2018-12-31 11:42:20 +01:00
parent 098618704a
commit 4fea8155c8
1 changed files with 96 additions and 195 deletions

View File

@ -41,20 +41,6 @@ class Auth_Yubico {
*/ */
private $url; private $url;
/**
* Last query to server
*
* @var string
*/
private $lastquery;
/**
* Response from server
*
* @var string
*/
private $response;
/** /**
* Flag whether to verify HTTPS server certificates or not. * Flag whether to verify HTTPS server certificates or not.
* *
@ -107,33 +93,6 @@ class Auth_Yubico {
return $ret; return $ret;
} }
/**
* Parse parameters from last response
*
* example: getParameters("timestamp", "sessioncounter", "sessionuse");
*
* @param array @parameters Array with strings representing
* parameters to parse
* @return array parameter array from last response
*/
private function getParameters($parameters) {
if ($parameters == null) {
$parameters = array(
'timestamp',
'sessioncounter',
'sessionuse'
);
}
$param_array = array();
foreach ($parameters as $param) {
if (!preg_match("/" . $param . "=([0-9]+)/", $this->response, $out)) {
throw new LAMException(_('Error'), 'Could not parse parameter ' . $param . ' from response');
}
$param_array[$param] = $out[1];
}
return $param_array;
}
/** /**
* Verify Yubico OTP against multiple URLs * Verify Yubico OTP against multiple URLs
* Protocol specification 2.0 is used to construct validation requests * Protocol specification 2.0 is used to construct validation requests
@ -142,12 +101,10 @@ class Auth_Yubico {
* @param int $use_timestamp 1=>send request with &timestamp=1 to * @param int $use_timestamp 1=>send request with &timestamp=1 to
* get timestamp and session information * get timestamp and session information
* in the response * in the response
* @param string $sl Sync level in percentage between 0 * @throws LAMException if verification failed
* and 100 or "fast" or "secure".
* @param int $timeout Max number of seconds to wait
* for responses
*/ */
public function verify($token, $use_timestamp = null, $sl = null, $timeout = null) { public function verify($token, $use_timestamp = null) {
$timeout = 10;
/* Construct parameters string */ /* Construct parameters string */
$ret = $this->parsePasswordOTP($token); $ret = $this->parsePasswordOTP($token);
if (!$ret) { if (!$ret) {
@ -162,12 +119,7 @@ class Auth_Yubico {
if ($use_timestamp) { if ($use_timestamp) {
$params['timestamp'] = 1; $params['timestamp'] = 1;
} }
if ($sl) {
$params['sl'] = $sl;
}
if ($timeout) {
$params['timeout'] = $timeout; $params['timeout'] = $timeout;
}
ksort($params); ksort($params);
$parameters = ''; $parameters = '';
foreach ($params as $p => $v) { foreach ($params as $p => $v) {
@ -182,12 +134,8 @@ class Auth_Yubico {
$parameters .= '&h=' . $signature; $parameters .= '&h=' . $signature;
} }
/* Generate and prepare request. */
$mh = curl_multi_init();
$ch = array();
$query = $this->url . "?" . $parameters; $query = $this->url . "?" . $parameters;
$this->lastquery = $query;
logNewMessage(LOG_DEBUG, 'Yubico url: ' . $query); logNewMessage(LOG_DEBUG, 'Yubico url: ' . $query);
$handle = curl_init($query); $handle = curl_init($query);
@ -198,36 +146,15 @@ class Auth_Yubico {
curl_setopt($handle, CURLOPT_SSL_VERIFYHOST, 0); curl_setopt($handle, CURLOPT_SSL_VERIFYHOST, 0);
} }
curl_setopt($handle, CURLOPT_FAILONERROR, true); curl_setopt($handle, CURLOPT_FAILONERROR, true);
/*
* If timeout is set, we better apply it here as well
* in case the validation server fails to follow it.
*/
if ($timeout) {
curl_setopt($handle, CURLOPT_TIMEOUT, $timeout); curl_setopt($handle, CURLOPT_TIMEOUT, $timeout);
}
// TODO single curl call
curl_multi_add_handle($mh, $handle);
$ch[(int) $handle] = $handle;
/* Execute and read request. */ /* Execute and read request. */
$this->response = null; $this->response = null;
$replay = False; $str = curl_exec($handle);
$valid = False; $httpCode = curl_getinfo($handle, CURLINFO_HTTP_CODE);
do { curl_close($handle);
/* Let curl do its work. */ logNewMessage(LOG_DEBUG, 'Server answer: ' . $str);
while (($mrc = curl_multi_exec($mh, $active)) == CURLM_CALL_MULTI_PERFORM); if (is_string($str) && ($httpCode == 200) && preg_match("/status=([a-zA-Z0-9_]+)/", $str, $out)) {
while ($info = curl_multi_info_read($mh)) {
if ($info['result'] == CURLE_OK) {
/* We have a complete response from one server. */
$str = curl_multi_getcontent($info['handle']);
logNewMessage(LOG_DEBUG, 'Yubico answer: ' . $str);
$cinfo = curl_getinfo($info['handle']);
if (preg_match("/status=([a-zA-Z0-9_]+)/", $str, $out)) {
$status = $out[1]; $status = $out[1];
/* /*
@ -242,15 +169,18 @@ class Auth_Yubico {
* 3. Return if status=OK or status=REPLAYED_OTP. * 3. Return if status=OK or status=REPLAYED_OTP.
*/ */
if (!preg_match("/otp=" . $params['otp'] . "/", $str) || !preg_match("/nonce=" . $params['nonce'] . "/", $str)) { if (!preg_match("/otp=" . $params['otp'] . "/", $str) || !preg_match("/nonce=" . $params['nonce'] . "/", $str)) {
/* Case 1. Ignore response. */ if ($status == 'BAD_OTP') {
throw new LAMException(_('Error'), 'OTP not accepted. Maybe key is not registered.');
}
throw new LAMException(_('Error'), 'Invalid answer ' . $str);
} }
elseif ($this->clientKey != "") { elseif ($this->clientKey != "") {
/* Case 2. Verify signature first */ /* Case 2. Verify signature first */
$rows = explode("\r\n", trim($str)); $rows = explode("\r\n", trim($str));
$response = array(); $response = array();
foreach ($rows as $key => $val) { foreach ($rows as $val) {
/* /*
* = is also used in BASE64 encoding so we only replace the first = by # which is not * '=' is also used in BASE64 encoding so we only replace the first = by # which is not
* used in BASE64 * used in BASE64
*/ */
$val = preg_replace('/=/', '#', $val, 1); $val = preg_replace('/=/', '#', $val, 1);
@ -273,7 +203,9 @@ class Auth_Yubico {
$check = Null; $check = Null;
foreach ($parameters as $param) { foreach ($parameters as $param) {
if (array_key_exists($param, $response)) { if (array_key_exists($param, $response)) {
if ($check) $check = $check . '&'; if ($check) {
$check = $check . '&';
}
$check = $check . $param . '=' . $response[$param]; $check = $check . $param . '=' . $response[$param];
} }
} }
@ -281,70 +213,39 @@ class Auth_Yubico {
$checksignature = base64_encode(hash_hmac('sha1', utf8_encode($check), $this->clientKey, true)); $checksignature = base64_encode(hash_hmac('sha1', utf8_encode($check), $this->clientKey, true));
if ($response['h'] == $checksignature) { if ($response['h'] == $checksignature) {
if ($status == 'REPLAYED_OTP') { $this->checkStatus($status);
$this->response = $str; return;
$replay = True;
}
if ($status == 'OK') {
$this->response = $str;
$valid = True;
}
// TODO status BAD_OTP
} }
else { else {
// TODO throw invalid signature exception throw new LAMException(_('Error'), 'Invalid signature, expected ' . $checksignature);
} }
} }
else { else {
/* Case 3. We check the status directly */ /* Case 3. We check the status directly */
if ($status == 'REPLAYED_OTP') { $this->checkStatus($status);
$this->response = $str;
$replay = True;
}
if ($status == 'OK') {
$this->response = $str;
$valid = True;
}
// TODO status BAD_OTP
}
}
if ($valid || $replay) {
/* We have status=OK or status=REPLAYED_OTP, return. */
foreach ($ch as $h) {
curl_multi_remove_handle($mh, $h);
curl_close($h);
}
curl_multi_close($mh);
if ($replay) {
throw new LAMException(_('Error'), 'OTP replay detected.');
}
if ($valid) {
return; return;
} }
} }
throw new LAMException(_('Error'), 'Call to verification service failed with ' . $httpCode);
}
curl_multi_remove_handle($mh, $info['handle']); /**
curl_close($info['handle']); * Checks if the status is ok.
unset($ch[(int) $info['handle']]); *
} * @param string $status status
curl_multi_select($mh); * @throws LAMException invalid status
}
}
while ($active);
/*
* Typically this is only reached
* when the timeout is reached and there is no
* OK/REPLAYED_REQUEST answer (think firewall).
*/ */
private function checkStatus($status) {
foreach ($ch as $h) { if ($status == 'REPLAYED_OTP') {
curl_multi_remove_handle($mh, $h); throw new LAMException(_('Error'), 'OTP replay detected.');
curl_close($h);
} }
curl_multi_close($mh); elseif ($status == 'BAD_OTP') {
throw new LAMException(_('Error'), 'OTP not accepted. Maybe key is not registered.');
throw new LAMException(_('Error'), 'Invalid answer: ' . print_r($this->response, true)); }
elseif ($status == 'OK') {
return;
}
throw new LAMException(_('Error'), 'Invalid status: ' . $status);
} }
} }