1
0
mirror of https://github.com/Yubico/yubikey-val.git synced 2025-02-01 19:52:20 +01:00
yubikey-val/ykval-common.php

365 lines
8.5 KiB
PHP
Raw Normal View History

2013-04-17 17:24:50 +02:00
<?php
2015-07-20 20:01:16 +00:00
# Copyright (c) 2009-2015 Yubico AB
2013-04-17 17:24:50 +02:00
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials provided
# with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
define('S_OK', 'OK');
define('S_BAD_OTP', 'BAD_OTP');
define('S_REPLAYED_OTP', 'REPLAYED_OTP');
define('S_DELAYED_OTP', 'DELAYED_OTP');
define('S_BAD_SIGNATURE', 'BAD_SIGNATURE');
define('S_MISSING_PARAMETER', 'MISSING_PARAMETER');
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);
define('TS_REL_TOLERANCE', 0.3);
define('TS_ABS_TOLERANCE', 20);
define('TOKEN_LEN', 32);
define('OTP_MAX_LEN', 48); // TOKEN_LEN plus public identity of 0..16
function logdie ($logger, $str)
{
2015-07-22 19:38:32 +00:00
$logger->log(LOG_INFO, $str);
die($str . "\n");
2013-04-17 17:24:50 +02:00
}
2015-07-21 21:38:37 +00:00
function getHttpVal ($key, $default)
{
if (array_key_exists($key, $_GET))
{
2013-04-17 17:24:50 +02:00
$val = $_GET[$key];
2015-07-21 21:38:37 +00:00
}
elseif (array_key_exists($key, $_POST))
{
$val = $_POST[$key];
}
else
{
$val = $default;
}
$val = trim($val);
$val = str_replace('\\', '', $val);
return $val;
2013-04-17 17:24:50 +02:00
}
2015-07-22 19:38:32 +00:00
function log_format()
{
$str = "";
foreach (func_get_args() as $msg)
{
if (is_array($msg))
{
foreach ($msg as $key => $value)
{
$str .= "$key=$value ";
}
}
else
{
$str .= $msg . " ";
}
2013-04-17 17:24:50 +02:00
}
2015-07-22 19:38:32 +00:00
return $str;
2013-04-17 17:24:50 +02:00
}
// Sign a http query string in the array of key-value pairs
// return b64 encoded hmac hash
function sign($a, $apiKey, $logger)
{
2013-04-17 17:24:50 +02:00
ksort($a);
$qs = http_build_query($a);
$qs = urldecode($qs);
$qs = utf8_encode($qs);
// base64 encoded binary digest
$hmac = hash_hmac('sha1', $qs, $apiKey, TRUE);
2013-04-17 17:24:50 +02:00
$hmac = base64_encode($hmac);
$logger->log(LOG_DEBUG, "SIGN: $qs H=$hmac");
2013-04-17 17:24:50 +02:00
return $hmac;
}
2013-04-17 17:24:50 +02:00
2015-07-22 19:48:43 +00:00
function curl_settings($logger, $ident, $ch, $url, $timeout, $opts)
2015-07-16 22:26:10 +02:00
{
2015-07-22 19:44:35 +00:00
$logger->log(LOG_DEBUG, "$ident adding URL : $url");
2015-07-16 22:26:10 +02:00
2015-07-22 19:43:06 +00:00
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
curl_setopt($ch, CURLOPT_USERAGENT, 'YK-VAL');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
curl_setopt($ch, CURLOPT_FAILONERROR, TRUE);
2015-07-16 22:26:10 +02:00
2015-07-22 19:48:43 +00:00
if (is_array($opts) === FALSE)
2015-07-16 22:26:10 +02:00
{
$logger->log(LOG_WARN, $ident . 'curl options must be an array');
return;
}
2015-07-22 19:48:43 +00:00
foreach ($opts as $key => $val)
2015-07-22 19:43:06 +00:00
if (curl_setopt($ch, $key, $val) === FALSE)
2015-07-22 19:41:25 +00:00
$logger->log(LOG_WARN, "$ident failed to set " . curl_opt_name($key));
}
2015-07-16 22:26:10 +02:00
// returns the string name of a curl constant,
// or "curl option" if constant not found.
// e.g.
// curl_opt_name(CURLOPT_URL) returns "CURLOPT_URL"
// curl_opt_name(CURLOPT_BLABLA) returns "curl option"
2015-07-16 22:26:10 +02:00
function curl_opt_name($opt)
{
$consts = get_defined_constants(true);
$consts = $consts['curl'];
2015-07-16 22:26:10 +02:00
$name = array_search($opt, $consts, TRUE);
2015-07-16 22:26:10 +02:00
// array_search may return either on failure...
if ($name === FALSE || $name === NULL)
return 'curl option';
2015-07-16 22:26:10 +02:00
return $name;
}
2013-04-17 17:24:50 +02:00
// This function takes a list of URLs. It will return the content of
// the first successfully retrieved URL, whose content matches ^OK.
// The request are sent asynchronously. Some of the URLs can fail
// with unknown host, connection errors, or network timeout, but as
// long as one of the URLs given work, data will be returned. If all
// URLs fail, data from some URL that did not match parameter $match
// (defaults to ^OK) is returned, or if all URLs failed, false.
2015-07-17 21:54:51 +02:00
function retrieveURLasync($ident, $urls, $logger, $ans_req=1, $match="^OK", $returl=False, $timeout=10, $curlopts)
{
$mh = curl_multi_init();
$ch = array();
2013-04-17 17:24:50 +02:00
2015-07-17 22:03:49 +02:00
foreach ($urls as $url)
2015-07-17 21:54:51 +02:00
{
$handle = curl_init();
curl_settings($logger, $ident, $handle, $url, $timeout, $curlopts);
curl_multi_add_handle($mh, $handle);
$ch[$handle] = $handle;
2013-04-17 17:24:50 +02:00
}
2015-07-17 21:54:51 +02:00
$ans_arr = array();
2013-04-17 17:24:50 +02:00
2015-07-17 21:54:51 +02:00
do
{
2015-07-17 21:55:46 +02:00
while (curl_multi_exec($mh, $active) == CURLM_CALL_MULTI_PERFORM);
2013-04-17 17:24:50 +02:00
2015-07-17 21:54:51 +02:00
while ($info = curl_multi_info_read($mh))
{
$logger->log(LOG_DEBUG, "$ident curl multi info : ", $info);
2015-07-17 21:54:51 +02:00
if ($info['result'] == CURLE_OK)
{
$str = curl_multi_getcontent($info['handle']);
$logger->log(LOG_DEBUG, "$ident curl multi content : $str");
2015-07-17 21:57:16 +02:00
if (preg_match("/$match/", $str))
2015-07-17 21:57:16 +02:00
{
$logger->log(LOG_DEBUG, "$ident response matches $match");
2015-07-17 21:57:16 +02:00
$error = curl_error($info['handle']);
$errno = curl_errno($info['handle']);
$cinfo = curl_getinfo($info['handle']);
$logger->log(LOG_INFO, "$ident errno/error: $errno/$error", $cinfo);
2015-07-17 22:04:09 +02:00
if ($returl)
$ans_arr[] = "url=" . $cinfo['url'] . "\n" . $str;
else
$ans_arr[] = $str;
2015-07-17 21:54:51 +02:00
}
if (count($ans_arr) >= $ans_req)
2015-07-17 21:57:16 +02:00
{
foreach ($ch as $h)
{
curl_multi_remove_handle($mh, $h);
curl_close($h);
2015-07-17 21:54:51 +02:00
}
2015-07-17 21:57:16 +02:00
curl_multi_close($mh);
2015-07-17 21:54:51 +02:00
return $ans_arr;
}
2015-07-17 21:57:16 +02:00
curl_multi_remove_handle($mh, $info['handle']);
curl_close($info['handle']);
unset($ch[$info['handle']]);
2015-07-17 21:54:51 +02:00
}
2015-07-17 21:57:16 +02:00
curl_multi_select($mh);
2015-07-17 21:54:51 +02:00
}
}
while($active);
2013-04-17 17:24:50 +02:00
2015-07-17 21:54:51 +02:00
foreach ($ch as $h)
{
2015-07-17 21:57:16 +02:00
curl_multi_remove_handle($mh, $h);
curl_close($h);
2015-07-17 21:54:51 +02:00
}
2015-07-17 21:57:16 +02:00
curl_multi_close($mh);
2015-07-17 21:54:51 +02:00
if (count($ans_arr) > 0)
2015-07-17 22:04:09 +02:00
return $ans_arr;
return false;
2013-04-17 17:24:50 +02:00
}
2015-07-17 21:38:00 +02:00
function KSMdecryptOTP($urls, $logger, $curlopts)
{
if (!is_array($urls))
{
$urls = array($urls);
}
$response = retrieveURLasync('YK-KSM', $urls, $logger, $ans_req=1, $match='^OK', $returl=False, $timeout=10, $curlopts);
2015-07-17 21:38:00 +02:00
if ($response === FALSE)
2015-07-17 21:38:00 +02:00
{
return false;
2015-07-17 21:38:00 +02:00
}
$response = array_shift($response);
$logger->log(LOG_DEBUG, log_format('YK-KSM response: ', $response));
2015-07-17 21:38:00 +02:00
$ret = array();
if (sscanf($response,
'OK counter=%04x low=%04x high=%02x use=%02x',
$ret['session_counter'],
$ret['low'],
$ret['high'],
$ret['session_use']) !== 4)
2015-07-17 21:38:00 +02:00
{
return false;
}
return $ret;
2015-07-16 22:26:10 +02:00
}
2013-04-17 17:24:50 +02:00
2015-07-22 19:21:45 +00:00
function sendResp($status, $logger, $apiKey = '', $extra = null)
{
$a['status'] = $status;
2015-07-22 19:21:45 +00:00
// 2008-11-21T06:11:55Z0711
$t = substr(microtime(false), 2, 3);
$t = gmdate('Y-m-d\TH:i:s\Z0') . $t;
2015-07-22 19:21:45 +00:00
$a['t'] = $t;
2015-07-17 00:29:22 +02:00
2015-07-22 19:21:45 +00:00
if ($extra)
foreach ($extra as $param => $value)
$a[$param] = $value;
2015-07-17 00:29:22 +02:00
2015-07-22 19:21:45 +00:00
$h = sign($a, $apiKey, $logger);
2013-04-17 17:24:50 +02:00
2015-07-22 19:21:45 +00:00
$str = "";
$str .= "h=" . $h . "\r\n";
$str .= "t=" . $a['t'] . "\r\n";
2015-07-17 00:33:53 +02:00
2015-07-22 19:21:45 +00:00
if ($extra)
foreach ($extra as $param => $value)
$str .= $param . "=" . $value . "\r\n";
2015-07-17 00:33:53 +02:00
2015-07-22 19:21:45 +00:00
$str .= "status=" . $a['status'] . "\r\n";
$str .= "\r\n";
2013-04-17 17:24:50 +02:00
2015-07-22 19:21:45 +00:00
$logger->log(LOG_INFO, "Response: " . $str . " (at " . gmdate("c") . " " . microtime() . ")");
2013-04-17 17:24:50 +02:00
2015-07-22 19:21:45 +00:00
echo $str;
exit;
2013-04-17 17:24:50 +02:00
}
2015-07-16 22:19:47 +02:00
// backport from PHP 5.6
if (function_exists('hash_equals') === FALSE)
{
function hash_equals($a, $b)
{
// hashes are a (known) fixed length,
// so this doesn't leak anything.
if (strlen($a) != strlen($b))
return false;
2015-07-16 22:19:47 +02:00
$result = 0;
for ($i = 0; $i < strlen($a); $i++)
$result |= ord($a[$i]) ^ ord($b[$i]);
return (0 === $result);
}
}
2015-09-08 09:17:34 +02:00
/**
* Return the total time taken to receive a response from a URL.
*
* @argument $url string
*
* @return float|bool seconds or false on failure
*/
function total_time ($url)
2015-09-08 09:17:34 +02:00
{
$opts = array(
CURLOPT_URL => $url,
CURLOPT_TIMEOUT => 3,
CURLOPT_FORBID_REUSE => TRUE,
CURLOPT_FRESH_CONNECT => TRUE,
CURLOPT_RETURNTRANSFER => TRUE,
CURLOPT_USERAGENT => 'ykval-munin-vallatency/1.0',
);
if (($ch = curl_init()) === FALSE)
return false;
if (curl_setopt_array($ch, $opts) === FALSE)
return false;
// we don't care about the actual response
if (curl_exec($ch) === FALSE)
return false;
$total_time = curl_getinfo($ch, CURLINFO_TOTAL_TIME);
curl_close($ch);
if (is_float($total_time) === FALSE)
return false;
return $total_time;
}