| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475 | 
							- <?php
 
-   /**
 
-    * Class for verifying Yubico One-Time-Passcodes
 
-    *
 
-    * @category    Auth
 
-    * @package     Auth_Yubico
 
-    * @author      Simon Josefsson <simon@yubico.com>, Olov Danielson <olov@yubico.com>
 
-    * @copyright   2007-2015 Yubico AB
 
-    * @license     http://opensource.org/licenses/bsd-license.php New BSD License
 
-    * @version     2.0
 
-    * @link        http://www.yubico.com/
 
-    */
 
- require_once 'PEAR.php';
 
- /**
 
-  * Class for verifying Yubico One-Time-Passcodes
 
-  *
 
-  * Simple example:
 
-  * <code>
 
-  * require_once 'Auth/Yubico.php';
 
-  * $otp = "ccbbddeertkrctjkkcglfndnlihhnvekchkcctif";
 
-  *
 
-  * # Generate a new id+key from https://api.yubico.com/get-api-key/
 
-  * $yubi = new Auth_Yubico('42', 'FOOBAR=');
 
-  * $auth = $yubi->verify($otp);
 
-  * if (PEAR::isError($auth)) {
 
-  *    print "<p>Authentication failed: " . $auth->getMessage();
 
-  *    print "<p>Debug output from server: " . $yubi->getLastResponse();
 
-  * } else {
 
-  *    print "<p>You are authenticated!";
 
-  * }
 
-  * </code>
 
-  */
 
- class Auth_Yubico
 
- {
 
- 	/**#@+
 
- 	 * @access private
 
- 	 */
 
- 	/**
 
- 	 * Yubico client ID
 
- 	 * @var string
 
- 	 */
 
- 	var $_id;
 
- 	/**
 
- 	 * Yubico client key
 
- 	 * @var string
 
- 	 */
 
- 	var $_key;
 
- 	/**
 
- 	 * URL part of validation server
 
- 	 * @var string
 
- 	 */
 
- 	var $_url;
 
- 	/**
 
- 	 * List with URL part of validation servers
 
- 	 * @var array
 
- 	 */
 
- 	var $_url_list;
 
- 	/**
 
- 	 * index to _url_list
 
- 	 * @var int
 
- 	 */
 
- 	var $_url_index;
 
- 	/**
 
- 	 * Last query to server
 
- 	 * @var string
 
- 	 */
 
- 	var $_lastquery;
 
- 	/**
 
- 	 * Response from server
 
- 	 * @var string
 
- 	 */
 
- 	var $_response;
 
- 	/**
 
- 	 * Flag whether to use https or not.
 
- 	 * @var boolean
 
- 	 */
 
- 	var $_https;
 
- 	/**
 
- 	 * Flag whether to verify HTTPS server certificates or not.
 
- 	 * @var boolean
 
- 	 */
 
- 	var $_httpsverify;
 
- 	/**
 
- 	 * Constructor
 
- 	 *
 
- 	 * Sets up the object
 
- 	 * @param    string  $id     The client identity
 
- 	 * @param    string  $key    The client MAC key (optional)
 
- 	 * @param    boolean $https  Flag whether to use https (optional)
 
- 	 * @param    boolean $httpsverify  Flag whether to use verify HTTPS
 
- 	 *                                 server certificates (optional,
 
- 	 *                                 default true)
 
- 	 * @access public
 
- 	 */
 
- 	function __construct($id, $key = '', $https = 0, $httpsverify = 1)
 
- 	{
 
- 		$this->_id =  $id;
 
- 		$this->_key = base64_decode($key);
 
- 		$this->_https = $https;
 
- 		$this->_httpsverify = $httpsverify;
 
- 	}
 
-   
 
-   function Auth_Yubico($id, $key = '', $https = 0, $httpsverify = 1)
 
- 	{
 
-     self::__construct();
 
- 	}
 
- 	/**
 
- 	 * Specify to use a different URL part for verification.
 
- 	 * The default is "api.yubico.com/wsapi/verify".
 
- 	 *
 
- 	 * @param  string $url  New server URL part to use
 
- 	 * @access public
 
- 	 */
 
- 	function setURLpart($url)
 
- 	{
 
- 		$this->_url = $url;
 
- 	}
 
- 	/**
 
- 	 * Get URL part to use for validation.
 
- 	 *
 
- 	 * @return string  Server URL part
 
- 	 * @access public
 
- 	 */
 
- 	function getURLpart()
 
- 	{
 
- 		if ($this->_url) {
 
- 			return $this->_url;
 
- 		} else {
 
- 			return "api.yubico.com/wsapi/verify";
 
- 		}
 
- 	}
 
- 	/**
 
- 	 * Get next URL part from list to use for validation.
 
- 	 *
 
- 	 * @return mixed string with URL part of false if no more URLs in list
 
- 	 * @access public
 
- 	 */
 
- 	function getNextURLpart()
 
- 	{
 
- 	  if ($this->_url_list) $url_list=$this->_url_list;
 
- 	  else $url_list=array('api.yubico.com/wsapi/2.0/verify',
 
- 			       'api2.yubico.com/wsapi/2.0/verify', 
 
- 			       'api3.yubico.com/wsapi/2.0/verify', 
 
- 			       'api4.yubico.com/wsapi/2.0/verify',
 
- 			       'api5.yubico.com/wsapi/2.0/verify');
 
- 	  
 
- 	  if ($this->_url_index>=count($url_list)) return false;
 
- 	  else return $url_list[$this->_url_index++];
 
- 	}
 
- 	/**
 
- 	 * Resets index to URL list
 
- 	 *
 
- 	 * @access public
 
- 	 */
 
- 	function URLreset()
 
- 	{
 
- 	  $this->_url_index=0;
 
- 	}
 
- 	/**
 
- 	 * Add another URLpart.
 
- 	 *
 
- 	 * @access public
 
- 	 */
 
- 	function addURLpart($URLpart) 
 
- 	{
 
- 	  $this->_url_list[]=$URLpart;
 
- 	}
 
- 	
 
- 	/**
 
- 	 * Return the last query sent to the server, if any.
 
- 	 *
 
- 	 * @return string  Request to server
 
- 	 * @access public
 
- 	 */
 
- 	function getLastQuery()
 
- 	{
 
- 		return $this->_lastquery;
 
- 	}
 
- 	/**
 
- 	 * Return the last data received from the server, if any.
 
- 	 *
 
- 	 * @return string  Output from server
 
- 	 * @access public
 
- 	 */
 
- 	function getLastResponse()
 
- 	{
 
- 		return $this->_response;
 
- 	}
 
- 	/**
 
- 	 * Parse input string into password, yubikey prefix,
 
- 	 * ciphertext, and OTP.
 
- 	 *
 
- 	 * @param  string    Input string to parse
 
- 	 * @param  string    Optional delimiter re-class, default is '[:]'
 
- 	 * @return array     Keyed array with fields
 
- 	 * @access public
 
- 	 */
 
- 	function parsePasswordOTP($str, $delim = '[:]')
 
- 	{
 
- 	  if (!preg_match("/^((.*)" . $delim . ")?" .
 
- 			  "(([cbdefghijklnrtuv]{0,16})" .
 
- 			  "([cbdefghijklnrtuv]{32}))$/i",
 
- 			  $str, $matches)) {
 
- 	    /* Dvorak? */
 
- 	    if (!preg_match("/^((.*)" . $delim . ")?" .
 
- 			    "(([jxe\.uidchtnbpygk]{0,16})" .
 
- 			    "([jxe\.uidchtnbpygk]{32}))$/i",
 
- 			    $str, $matches)) {
 
- 	      return false;
 
- 	    } else {
 
- 	      $ret['otp'] = strtr($matches[3], "jxe.uidchtnbpygk", "cbdefghijklnrtuv");
 
- 	    }
 
- 	  } else {
 
- 	    $ret['otp'] = $matches[3];
 
- 	  }
 
- 	  $ret['password'] = $matches[2];
 
- 	  $ret['prefix'] = $matches[4];
 
- 	  $ret['ciphertext'] = $matches[5];
 
- 	  return $ret;
 
- 	}
 
- 	/* TODO? Add functions to get parsed parts of server response? */
 
- 	/**
 
- 	 * 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
 
- 	 * @access public
 
- 	 */
 
- 	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)) {
 
- 	      return PEAR::raiseError('Could not parse parameter ' . $param . ' from response');
 
- 	    }
 
- 	    $param_array[$param]=$out[1];
 
- 	  }
 
- 	  return $param_array;
 
- 	}
 
- 	/**
 
- 	 * Verify Yubico OTP against multiple URLs
 
- 	 * Protocol specification 2.0 is used to construct validation requests
 
- 	 *
 
- 	 * @param string $token        Yubico OTP
 
- 	 * @param int $use_timestamp   1=>send request with ×tamp=1 to
 
- 	 *                             get timestamp and session information
 
- 	 *                             in the response
 
- 	 * @param boolean $wait_for_all  If true, wait until all
 
- 	 *                               servers responds (for debugging)
 
- 	 * @param string $sl           Sync level in percentage between 0
 
- 	 *                             and 100 or "fast" or "secure".
 
- 	 * @param int $timeout         Max number of seconds to wait
 
- 	 *                             for responses
 
- 	 * @return mixed               PEAR error on error, true otherwise
 
- 	 * @access public
 
- 	 */
 
- 	function verify($token, $use_timestamp=null, $wait_for_all=False,
 
- 			$sl=null, $timeout=null)
 
- 	{
 
- 	  /* Construct parameters string */
 
- 	  $ret = $this->parsePasswordOTP($token);
 
- 	  if (!$ret) {
 
- 	    return PEAR::raiseError('Could not parse Yubikey OTP');
 
- 	  }
 
- 	  $params = array('id'=>$this->_id, 
 
- 			  'otp'=>$ret['otp'],
 
- 			  'nonce'=>md5(uniqid(rand())));
 
- 	  /* Take care of protocol version 2 parameters */
 
- 	  if ($use_timestamp) $params['timestamp'] = 1;
 
- 	  if ($sl) $params['sl'] = $sl;
 
- 	  if ($timeout) $params['timeout'] = $timeout;
 
- 	  ksort($params);
 
- 	  $parameters = '';
 
- 	  foreach($params as $p=>$v) $parameters .= "&" . $p . "=" . $v;
 
- 	  $parameters = ltrim($parameters, "&");
 
- 	  
 
- 	  /* Generate signature. */
 
- 	  if($this->_key <> "") {
 
- 	    $signature = base64_encode(hash_hmac('sha1', $parameters,
 
- 						 $this->_key, true));
 
- 	    $signature = preg_replace('/\+/', '%2B', $signature);
 
- 	    $parameters .= '&h=' . $signature;
 
- 	  }
 
- 	  /* Generate and prepare request. */
 
- 	  $this->_lastquery=null;
 
- 	  $this->URLreset();
 
- 	  $mh = curl_multi_init();
 
- 	  $ch = array();
 
- 	  while($URLpart=$this->getNextURLpart()) 
 
- 	    {
 
- 	      /* Support https. */
 
- 	      if ($this->_https) {
 
- 		$query = "https://";
 
- 	      } else {
 
- 		$query = "http://";
 
- 	      }
 
- 	      $query .= $URLpart . "?" . $parameters;
 
- 	      if ($this->_lastquery) { $this->_lastquery .= " "; }
 
- 	      $this->_lastquery .= $query;
 
- 	      
 
- 	      $handle = curl_init($query);
 
- 	      curl_setopt($handle, CURLOPT_USERAGENT, "PEAR Auth_Yubico");
 
- 	      curl_setopt($handle, CURLOPT_RETURNTRANSFER, 1);
 
- 	      if (!$this->_httpsverify) {
 
- 		curl_setopt($handle, CURLOPT_SSL_VERIFYPEER, 0);
 
- 		curl_setopt($handle, CURLOPT_SSL_VERIFYHOST, 0);
 
- 	      }
 
- 	      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_multi_add_handle($mh, $handle);
 
- 	      
 
- 	      $ch[(int)$handle] = $handle;
 
- 	    }
 
- 	  /* Execute and read request. */
 
- 	  $this->_response=null;
 
- 	  $replay=False;
 
- 	  $valid=False;
 
- 	  do {
 
- 	    /* Let curl do its work. */
 
- 	    while (($mrc = curl_multi_exec($mh, $active))
 
- 		   == CURLM_CALL_MULTI_PERFORM)
 
- 	      ;
 
- 	    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']);
 
- 		$cinfo = curl_getinfo ($info['handle']);
 
- 		
 
- 		if ($wait_for_all) { # Better debug info
 
- 		  $this->_response .= 'URL=' . $cinfo['url'] ."\n"
 
- 		    . $str . "\n";
 
- 		}
 
- 		if (preg_match("/status=([a-zA-Z0-9_]+)/", $str, $out)) {
 
- 		  $status = $out[1];
 
- 		  /* 
 
- 		   * There are 3 cases.
 
- 		   *
 
- 		   * 1. OTP or Nonce values doesn't match - ignore
 
- 		   * response.
 
- 		   *
 
- 		   * 2. We have a HMAC key.  If signature is invalid -
 
- 		   * ignore response.  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)) {
 
- 		    /* Case 1. Ignore response. */
 
- 		  } 
 
- 		  elseif ($this->_key <> "") {
 
- 		    /* Case 2. Verify signature first */
 
- 		    $rows = explode("\r\n", trim($str));
 
- 		    $response=array();
 
- 		    while (list($key, $val) = each($rows)) {
 
- 		      /* = is also used in BASE64 encoding so we only replace the first = by # which is not used in BASE64 */
 
- 		      $val = preg_replace('/=/', '#', $val, 1);
 
- 		      $row = explode("#", $val);
 
- 		      $response[$row[0]] = $row[1];
 
- 		    }
 
- 		    
 
- 		    $parameters=array('nonce','otp', 'sessioncounter', 'sessionuse', 'sl', 'status', 't', 'timeout', 'timestamp');
 
- 		    sort($parameters);
 
- 		    $check=Null;
 
- 		    foreach ($parameters as $param) {
 
- 		      if (array_key_exists($param, $response)) {
 
- 			if ($check) $check = $check . '&';
 
- 			$check = $check . $param . '=' . $response[$param];
 
- 		      }
 
- 		    }
 
- 		    $checksignature =
 
- 		      base64_encode(hash_hmac('sha1', utf8_encode($check),
 
- 					      $this->_key, true));
 
- 		    if($response['h'] == $checksignature) {
 
- 		      if ($status == 'REPLAYED_OTP') {
 
- 			if (!$wait_for_all) { $this->_response = $str; }
 
- 			$replay=True;
 
- 		      } 
 
- 		      if ($status == 'OK') {
 
- 			if (!$wait_for_all) { $this->_response = $str; }
 
- 			$valid=True;
 
- 		      }
 
- 		    }
 
- 		  } else {
 
- 		    /* Case 3. We check the status directly */
 
- 		    if ($status == 'REPLAYED_OTP') {
 
- 		      if (!$wait_for_all) { $this->_response = $str; }
 
- 		      $replay=True;
 
- 		    } 
 
- 		    if ($status == 'OK') {
 
- 		      if (!$wait_for_all) { $this->_response = $str; }
 
- 		      $valid=True;
 
- 		    }
 
- 		  }
 
- 		}
 
- 		if (!$wait_for_all && ($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) return PEAR::raiseError('REPLAYED_OTP');
 
- 		    if ($valid) return true;
 
- 		    return PEAR::raiseError($status);
 
- 		  }
 
- 		
 
- 		curl_multi_remove_handle($mh, $info['handle']);
 
- 		curl_close($info['handle']);
 
- 		unset ($ch[(int)$info['handle']]);
 
- 	      }
 
- 	      curl_multi_select($mh);
 
- 	    }
 
- 	  } while ($active);
 
- 	  /* Typically this is only reached for wait_for_all=true or
 
- 	   * when the timeout is reached and there is no
 
- 	   * OK/REPLAYED_REQUEST answer (think firewall).
 
- 	   */
 
- 	  foreach ($ch as $h) {
 
- 	    curl_multi_remove_handle ($mh, $h);
 
- 	    curl_close ($h);
 
- 	  }
 
- 	  curl_multi_close ($mh);
 
- 	  
 
- 	  if ($replay) return PEAR::raiseError('REPLAYED_OTP');
 
- 	  if ($valid) return true;
 
- 	  return PEAR::raiseError('NO_VALID_ANSWER');
 
- 	}
 
- }
 
- ?>
 
 
  |