_settingExists("verified", $user) && $this->getConf('enable') === 1); } /** * This module does provide authentication functionality at the main login screen. */ public function canAuthLogin() { return true; } /** * This user will need to insert an OTP in order to configure YA. */ public function renderProfileForm(){ global $conf,$USERINFO; $elements = array(); if ($this->_settingExists("publicID")) { // The user has a revokable YA Public ID. // Show saved Public ID $elements[] = form_makeTextField('yubiauth_showPublicID', $this->_settingGet("publicID"), $this->getLang('currentPublicID') , '', 'block', array('size'=>'12', 'readonly'=>'true')); // User can remove Yubi Key here $elements[] = form_makeCheckboxField('yubiauth_disable', '1', $this->getLang('killmodule'), '', 'block'); } else { // The user may opt in using YA. // Provide a checkbox to create a personal secret. $elements[] = form_makeCheckboxField('yubiauth_enable', '1', $this->getLang('enablemodule'), '', 'block'); // Provide text field for setup OTP $elements[] = form_makeTextField('yubiauth_setup', '', $this->getLang('needsetup'), '', 'block', array('size'=>'44')); } return $elements; } /** * Process any user configuration. */ public function processProfileForm(){ global $INPUT; if ($this->_settingExists("publicID")) { if ($INPUT->bool('yubiauth_disable', false)) { $this->_settingDelete("publicID"); $this->_settingDelete("verified"); return true; } } else { if ($INPUT->bool('yubiauth_enable', false)) { $otp = $INPUT->str('yubiauth_setup', ''); $checkResult = $this->processLogin($otp); if ($checkResult) { $this->_settingSet("publicID", substr($otp, 0, 12)); $this->_settingSet("verified", true); return 'verified'; } else { return 'failed'; } } } return null; } /** * This module cannot send messages. */ public function canTransmitMessage() { return false; } /** * Transmit the message via email to the address on file. * As a special case, configure the mail settings to send only via text. */ //public function transmitMessage($subject, $message); /** * This module authenticates against the configured API Server. */ public function processLogin($code, $user = null){ if (strlen($code) < 44) { # wrong length (12 Chars static + 32 Chars dynamic) return false; } if ($this->_settingExists("publicID", $user)) { if (!$this->validateUser($code, $user)) { return false; } } $yubiAuthenticator = new PHP_YubiAuthenticator(); $response = array(); $auth = $yubiAuthenticator->verifyCode($this->generateYubiURL($code),$response); if($auth) { if($code != $response["otp"]) // otp is not the one we used return false; if(empty($this->getConf("clientSecret"))) { return true; } else { return $this->checkSignature($response); } } else { return false; } } /** * Check if provided $code uses the users Public ID * @param $code OTP generated by Yubi Key * @param $user current user * @return boolean */ public function validateUser($code, $user) { $publicID = $this->_settingGet("publicID",'', $user); $static = substr($code,0,12); return $publicID==$static; } /** * Generate URL for API Request * @param $code OTP generated by Yubi Key * @return string url */ public function generateYubiURL($code) { $validationServer = $this->getConf("validationServer"); $url = $validationServer; $data = array(); $data["id"] = $this->getConf("clientID"); if(empty($data["id"])) $data["id"]=87; $data["otp"] = $code; $data["nonce"] = $this->generateNonce(); //https://api.yubico.com/wsapi/2.0/verify?otp=vvvvvvcucrlcietctckflvnncdgckubflugerlnr&id=87&timeout=8&sl=50&nonce=askjdnkajsndjkasndkjsnad return sprintf("%s?%s", $url, http_build_query($data)); } /** * Generate random nonce * @param int $size length of desired nonce, must be between 16 and 40, otherwise goes for 32 * @return string */ private function generateNonce($size = 32) { $chars = array("a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","0","1","2","3","4","5","6","7","8","9"); $nonce = ""; if($size >= 16 && $size <= 40) { } else {$size = 32;} for($i=0; $i < $size; $i++) { $rand = rand(0,count($chars)-1); $nonce .= $chars[$rand]; } return $nonce; } /** * Generate signature over response values and compare to the servers signature * @param $response array with key=>value pairs responded by the API server * @return boolean */ private function checkSignature($response) { $secret = base64_decode($this->getConf("clientSecret")); $their_hash = $response["h"]; unset($response["h"]); ksort($response); $sorted = ""; foreach ($response as $k => $v) { $sorted .= '&' . $k . '=' . $v; } $sorted = substr($sorted,1); $hash = base64_encode(hash_hmac('sha1',$sorted,$secret, true)); return ($hash == $their_hash); } }