| 
									
										
										
										
											2014-12-24 21:22:02 +00:00
										 |  |  | <?php | 
					
						
							|  |  |  | /* Copyright (C) 2014 Daniel Preussker <f0o@devilcode.org> | 
					
						
							|  |  |  |  * This program is free software: you can redistribute it and/or modify | 
					
						
							|  |  |  |  * it under the terms of the GNU General Public License as published by | 
					
						
							|  |  |  |  * the Free Software Foundation, either version 3 of the License, or | 
					
						
							|  |  |  |  * (at your option) any later version. | 
					
						
							| 
									
										
										
										
											2015-07-13 20:10:26 +02:00
										 |  |  |  * | 
					
						
							| 
									
										
										
										
											2014-12-24 21:22:02 +00:00
										 |  |  |  * This program is distributed in the hope that it will be useful, | 
					
						
							|  |  |  |  * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
					
						
							|  |  |  |  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the | 
					
						
							|  |  |  |  * GNU General Public License for more details. | 
					
						
							| 
									
										
										
										
											2015-07-13 20:10:26 +02:00
										 |  |  |  * | 
					
						
							| 
									
										
										
										
											2014-12-24 21:22:02 +00:00
										 |  |  |  * You should have received a copy of the GNU General Public License | 
					
						
							| 
									
										
										
										
											2015-07-13 20:10:26 +02:00
										 |  |  |  * along with this program.  If not, see <http://www.gnu.org/licenses/>. | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2014-12-24 21:22:02 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Two-Factor Authentication Library | 
					
						
							|  |  |  |  * @author f0o <f0o@devilcode.org> | 
					
						
							|  |  |  |  * @copyright 2014 f0o, LibreNMS | 
					
						
							|  |  |  |  * @license GPL | 
					
						
							|  |  |  |  * @package LibreNMS | 
					
						
							|  |  |  |  * @subpackage Authentication | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Key Interval in seconds. | 
					
						
							|  |  |  |  * Set to 30s due to Google-Authenticator limitation. | 
					
						
							|  |  |  |  * Sadly Google-Auth is the most used Non-Physical OTP app. | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2016-08-18 20:28:22 -05:00
										 |  |  | const KEY_INTERVAL = 30; | 
					
						
							| 
									
										
										
										
											2014-12-24 21:22:02 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Size of the OTP. | 
					
						
							|  |  |  |  * Set to 6 for the same reasons as above. | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2016-08-18 20:28:22 -05:00
										 |  |  | const OTP_SIZE = 6; | 
					
						
							| 
									
										
										
										
											2014-12-24 21:22:02 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Window to honour whilest verifying OTP. | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2016-08-18 20:28:22 -05:00
										 |  |  | const OTP_WINDOW = 4; | 
					
						
							| 
									
										
										
										
											2014-12-24 21:22:02 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Base32 Decoding dictionary | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | $base32 = array( | 
					
						
							| 
									
										
										
										
											2016-08-18 20:28:22 -05:00
										 |  |  |     "A" => 0,   "B" => 1,   "C" => 2,   "D" => 3, | 
					
						
							|  |  |  |     "E" => 4,   "F" => 5,   "G" => 6,   "H" => 7, | 
					
						
							|  |  |  |     "I" => 8,   "J" => 9,   "K" => 10,  "L" => 11, | 
					
						
							|  |  |  |     "M" => 12,  "N" => 13,  "O" => 14,  "P" => 15, | 
					
						
							|  |  |  |     "Q" => 16,  "R" => 17,  "S" => 18,  "T" => 19, | 
					
						
							|  |  |  |     "U" => 20,  "V" => 21,  "W" => 22,  "X" => 23, | 
					
						
							|  |  |  |     "Y" => 24,  "Z" => 25,  "2" => 26,  "3" => 27, | 
					
						
							|  |  |  |     "4" => 28,  "5" => 29,  "6" => 30,  "7" => 31 | 
					
						
							| 
									
										
										
										
											2014-12-24 21:22:02 +00:00
										 |  |  | ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Base32 Encoding dictionary | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | $base32_enc = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Generate Secret Key | 
					
						
							|  |  |  |  * @return string | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2016-08-18 20:28:22 -05:00
										 |  |  | function twofactor_genkey() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     global $base32_enc; | 
					
						
							|  |  |  |     // RFC 4226 recommends 160bits Secret Keys, that's 20 Bytes for the lazy ones.
 | 
					
						
							|  |  |  |     $crypto = false; | 
					
						
							|  |  |  |     $raw = ""; | 
					
						
							|  |  |  |     $x = -1; | 
					
						
							|  |  |  |     while ($crypto == false || ++$x < 10) { | 
					
						
							|  |  |  |         $raw = openssl_random_pseudo_bytes(20, $crypto); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     // RFC 4648 Base32 Encoding without padding
 | 
					
						
							|  |  |  |     $len = strlen($raw); | 
					
						
							|  |  |  |     $bin = ""; | 
					
						
							|  |  |  |     $x = -1; | 
					
						
							|  |  |  |     while (++$x < $len) { | 
					
						
							|  |  |  |         $bin .= str_pad(base_convert(ord($raw[$x]), 10, 2), 8, '0', STR_PAD_LEFT); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     $bin = str_split($bin, 5); | 
					
						
							|  |  |  |     $ret = ""; | 
					
						
							|  |  |  |     $x = -1; | 
					
						
							|  |  |  |     while (++$x < sizeof($bin)) { | 
					
						
							|  |  |  |         $ret .= $base32_enc[base_convert(str_pad($bin[$x], 5, '0'), 2, 10)]; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return $ret; | 
					
						
							| 
									
										
										
										
											2014-12-24 21:22:02 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Generate HOTP (RFC 4226) | 
					
						
							|  |  |  |  * @param string $key Secret Key | 
					
						
							|  |  |  |  * @param int|boolean $counter Optional Counter, Defaults to Timestamp | 
					
						
							|  |  |  |  * @return int | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2016-08-18 20:28:22 -05:00
										 |  |  | function oath_hotp($key, $counter = false) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     global $base32; | 
					
						
							|  |  |  |     if ($counter === false) { | 
					
						
							|  |  |  |         $counter = floor(microtime(true)/KEY_INTERVAL); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     $length = strlen($key); | 
					
						
							|  |  |  |     $x = -1; | 
					
						
							|  |  |  |     $y = $z = 0; | 
					
						
							|  |  |  |     $kbin = ""; | 
					
						
							|  |  |  |     while (++$x < $length) { | 
					
						
							|  |  |  |         $y <<= 5; | 
					
						
							|  |  |  |         $y += $base32[$key[$x]]; | 
					
						
							|  |  |  |         $z += 5; | 
					
						
							|  |  |  |         if ($z >= 8) { | 
					
						
							|  |  |  |             $z -= 8; | 
					
						
							|  |  |  |             $kbin .= chr(($y & (0xFF << $z)) >> $z); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     $hash = hash_hmac('sha1', pack('N*', 0).pack('N*', $counter), $kbin, true); | 
					
						
							|  |  |  |     $offset = ord($hash[19]) & 0xf; | 
					
						
							|  |  |  |     $truncated = (((ord($hash[$offset+0]) & 0x7f) << 24 ) | ((ord($hash[$offset+1]) & 0xff) << 16 ) | ((ord($hash[$offset+2]) & 0xff) << 8 ) | (ord($hash[$offset+3]) & 0xff)) % pow(10, OTP_SIZE); | 
					
						
							|  |  |  |     return str_pad($truncated, OTP_SIZE, '0', STR_PAD_LEFT); | 
					
						
							| 
									
										
										
										
											2014-12-24 21:22:02 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Verify HOTP token honouring window | 
					
						
							|  |  |  |  * @param string $key Secret Key | 
					
						
							|  |  |  |  * @param int $otp OTP supplied by user | 
					
						
							|  |  |  |  * @param int|boolean $counter Counter, if false timestamp is used | 
					
						
							|  |  |  |  * @return boolean|int | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2016-08-18 20:28:22 -05:00
										 |  |  | function verify_hotp($key, $otp, $counter = false) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     if (oath_hotp($key, $counter) == $otp) { | 
					
						
							|  |  |  |         return true; | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |         if ($counter === false) { | 
					
						
							|  |  |  |             //TimeBased HOTP requires lookbehind and lookahead.
 | 
					
						
							|  |  |  |             $counter   = floor(microtime(true)/KEY_INTERVAL); | 
					
						
							|  |  |  |             $initcount = $counter-((OTP_WINDOW+1)*KEY_INTERVAL); | 
					
						
							|  |  |  |             $endcount  = $counter+(OTP_WINDOW*KEY_INTERVAL); | 
					
						
							|  |  |  |             $totp      = true; | 
					
						
							|  |  |  |         } else { | 
					
						
							|  |  |  |             //Counter based HOTP only has lookahead, not lookbehind.
 | 
					
						
							|  |  |  |             $initcount = $counter-1; | 
					
						
							|  |  |  |             $endcount  = $counter+OTP_WINDOW; | 
					
						
							|  |  |  |             $totp      = false; | 
					
						
							| 
									
										
										
										
											2015-07-13 20:10:26 +02:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2016-08-18 20:28:22 -05:00
										 |  |  |         while (++$initcount <= $endcount) { | 
					
						
							|  |  |  |             if (oath_hotp($key, $initcount) == $otp) { | 
					
						
							|  |  |  |                 if (!$totp) { | 
					
						
							|  |  |  |                     return $initcount; | 
					
						
							|  |  |  |                 } else { | 
					
						
							|  |  |  |                     return true; | 
					
						
							| 
									
										
										
										
											2015-07-13 20:10:26 +02:00
										 |  |  |                 } | 
					
						
							| 
									
										
										
										
											2016-08-18 20:28:22 -05:00
										 |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return false; | 
					
						
							| 
									
										
										
										
											2014-12-24 21:22:02 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Print TwoFactor Input-Form | 
					
						
							|  |  |  |  * @param boolean $form Include FORM-tags | 
					
						
							| 
									
										
										
										
											2017-02-01 02:03:07 -06:00
										 |  |  |  * @return string | 
					
						
							| 
									
										
										
										
											2014-12-24 21:22:02 +00:00
										 |  |  |  */ | 
					
						
							| 
									
										
										
										
											2016-08-18 20:28:22 -05:00
										 |  |  | function twofactor_form($form = true) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     global $config; | 
					
						
							|  |  |  |     $ret = ""; | 
					
						
							|  |  |  |     if ($form) { | 
					
						
							|  |  |  |         $ret .= ' | 
					
						
							| 
									
										
										
										
											2015-10-15 19:21:09 +02:00
										 |  |  |       <div class="row"> | 
					
						
							|  |  |  |         <div class="col-md-offset-4 col-md-4"> | 
					
						
							|  |  |  |           <div class="panel panel-default"> | 
					
						
							|  |  |  |             <div class="panel-heading"> | 
					
						
							|  |  |  |               <h3 class="panel-title"> | 
					
						
							| 
									
										
										
										
											2017-02-01 02:03:07 -06:00
										 |  |  |                 <img src="' . $config['title_image'] . '"> | 
					
						
							| 
									
										
										
										
											2015-10-15 19:21:09 +02:00
										 |  |  |               </h3> | 
					
						
							|  |  |  |             </div> | 
					
						
							|  |  |  |             <div class="panel-body"> | 
					
						
							|  |  |  |               <div class="container-fluid"> | 
					
						
							|  |  |  |                   <form class="form-horizontal" role="form" action="" method="post" name="twofactorform">'; | 
					
						
							| 
									
										
										
										
											2016-08-18 20:28:22 -05:00
										 |  |  |     } | 
					
						
							|  |  |  |     $ret .= ' | 
					
						
							| 
									
										
										
										
											2014-12-24 21:22:02 +00:00
										 |  |  |         <div class="form-group"> | 
					
						
							| 
									
										
										
										
											2015-10-15 19:21:09 +02:00
										 |  |  |           <div class="col-md-12"> | 
					
						
							|  |  |  |             <input type="text" name="twofactor" id="twofactor" class="form-control" autocomplete="off" placeholder="Please enter auth token" /> | 
					
						
							| 
									
										
										
										
											2014-12-24 21:22:02 +00:00
										 |  |  |           </div> | 
					
						
							|  |  |  |         </div> | 
					
						
							|  |  |  |         <div class="form-group"> | 
					
						
							| 
									
										
										
										
											2015-10-15 19:21:09 +02:00
										 |  |  |           <div class="col-md-12"> | 
					
						
							| 
									
										
										
										
											2017-02-01 02:03:07 -06:00
										 |  |  |             <button type="submit" class="btn btn-default btn-block" name="submit">Login</button> | 
					
						
							| 
									
										
										
										
											2014-12-24 21:22:02 +00:00
										 |  |  |           </div> | 
					
						
							| 
									
										
										
										
											2015-10-15 19:21:09 +02:00
										 |  |  |          </div> | 
					
						
							| 
									
										
										
										
											2014-12-24 21:22:02 +00:00
										 |  |  |         </div>'; | 
					
						
							| 
									
										
										
										
											2016-08-18 20:28:22 -05:00
										 |  |  |     $ret .= '<script>document.twofactorform.twofactor.focus();</script>'; | 
					
						
							|  |  |  |     if ($form) { | 
					
						
							|  |  |  |         $ret .= ' | 
					
						
							| 
									
										
										
										
											2014-12-24 21:22:02 +00:00
										 |  |  |       </form>'; | 
					
						
							| 
									
										
										
										
											2016-08-18 20:28:22 -05:00
										 |  |  |     } | 
					
						
							|  |  |  |     return $ret; | 
					
						
							| 
									
										
										
										
											2014-12-24 21:22:02 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Authentication logic | 
					
						
							|  |  |  |  * @return void | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2016-08-18 20:28:22 -05:00
										 |  |  | function twofactor_auth() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     global $auth_message, $twofactorform, $config; | 
					
						
							|  |  |  |     $twofactor = dbFetchRow('SELECT twofactor FROM users WHERE username = ?', array($_SESSION['username'])); | 
					
						
							|  |  |  |     if (empty($twofactor['twofactor'])) { | 
					
						
							|  |  |  |         $_SESSION['twofactor'] = true; | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |         $twofactor = json_decode($twofactor['twofactor'], true); | 
					
						
							|  |  |  |         if ($twofactor['fails'] >= 3 && (!$config['twofactor_lock'] || (time()-$twofactor['last']) < $config['twofactor_lock'])) { | 
					
						
							|  |  |  |             $auth_message = "Too many failures, please ".($config['twofactor_lock'] ? "wait ".$config['twofactor_lock']." seconds" : "contact administrator")."."; | 
					
						
							|  |  |  |         } else { | 
					
						
							|  |  |  |             if (!$_POST['twofactor']) { | 
					
						
							|  |  |  |                 $twofactorform = true; | 
					
						
							|  |  |  |             } else { | 
					
						
							|  |  |  |                 if (($server_c = verify_hotp($twofactor['key'], $_POST['twofactor'], $twofactor['counter'])) === false) { | 
					
						
							|  |  |  |                     $twofactor['fails']++; | 
					
						
							|  |  |  |                     $twofactor['last'] = time(); | 
					
						
							|  |  |  |                     $auth_message = "Wrong Two-Factor Token."; | 
					
						
							|  |  |  |                 } else { | 
					
						
							|  |  |  |                     if ($twofactor['counter'] !== false) { | 
					
						
							|  |  |  |                         if ($server_c !== true && $server_c !== $twofactor['counter']) { | 
					
						
							|  |  |  |                             $twofactor['counter'] = $server_c+1; | 
					
						
							|  |  |  |                         } else { | 
					
						
							|  |  |  |                             $twofactor['counter']++; | 
					
						
							| 
									
										
										
										
											2015-07-13 20:10:26 +02:00
										 |  |  |                         } | 
					
						
							| 
									
										
										
										
											2016-08-18 20:28:22 -05:00
										 |  |  |                     } | 
					
						
							|  |  |  |                     $twofactor['fails'] = 0; | 
					
						
							|  |  |  |                     $_SESSION['twofactor'] = true; | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 dbUpdate(array('twofactor' => json_encode($twofactor)), 'users', 'username = ?', array($_SESSION['username'])); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2014-12-24 21:22:02 +00:00
										 |  |  | } |