From d66cec70173651699ece42f1011fcd600da5fed9 Mon Sep 17 00:00:00 2001 From: f0o Date: Wed, 24 Dec 2014 21:22:02 +0000 Subject: [PATCH] Added TwoFactor Authentication (RFC4226) Tested against Google-Authenticator app on Android 4.4.4 Made `verify_hotp` more efficient. Added autofocus on twofactor input Added GUI Unlock and Remove for TwoFactor credentials in /edituser/ Allow additional tries after elapsed time from last try exceeds configured parameter `$config['twofactor_lock']`. If `$config['twofactor_lock']` is not defined or is set to `0`, administrators have to unlock accounts that exceed 3 failures via GUI. Added Documentation Moved TwoFactor form to logon.inc.php Disabled autocomplete on twofactor input field Updated Docs to include link to Google-Authenticator's install-guides Moved authentication logic from authenticate.inc.php to twofactor.lib.php typo in docblock for `twofactor_auth()` Fixed scrutinizer bugs To please scrutinizer --- doc/TwoFactor.md | 72 ++++++ html/includes/authenticate.inc.php | 12 +- .../includes/authentication/twofactor.lib.php | 231 ++++++++++++++++++ html/js/jquery.qrcode.min.js | 2 + html/pages/edituser.inc.php | 49 +++- html/pages/logon.inc.php | 8 +- html/pages/preferences.inc.php | 102 ++++++++ sql-schema/038.sql | 1 + 8 files changed, 472 insertions(+), 5 deletions(-) create mode 100644 doc/TwoFactor.md create mode 100644 html/includes/authentication/twofactor.lib.php create mode 100644 html/js/jquery.qrcode.min.js create mode 100644 sql-schema/038.sql diff --git a/doc/TwoFactor.md b/doc/TwoFactor.md new file mode 100644 index 0000000000..ccc9dd0594 --- /dev/null +++ b/doc/TwoFactor.md @@ -0,0 +1,72 @@ +Table of Content: +- [About](#about) +- [Types](#types) + - [Timebased One-Time-Password (TOTP)](#totp) + - [Counterbased One-Time-Password (HOTP)](#hotp) +- [Configuration](#config) +- [Usage](#usage) + - [Google Authenticator](#usage-google) + +# About + +Over the last couple of years, the primary attack vector for internet accounts has been static passwords. +Therefore static passwords are no longer suffient to protect unauthorized access to accounts. +Two Factor Authentication adds a variable part in authentication procedures. +A user is now required to supply a changing 6-digit passcode in addition to it's password to obtain access to the account. + +LibreNMS has a RFC4226 conform implementation of both Time and Counter based One-Time-Passwords. +It also allows the administrator to configure a throttle time to enforce after 3 failures exceeded. Unlike RFC4226 suggestions, this throttle time will not stack on the amount of failures. + +# Types + +In general, these two types do not differ in algorithmic terms. +The types only differ in the variable being used to derive the passcodes from. +The underlying HMAC-SHA1 remains the same for both types, security advantages or disadvantages of each are discussed further down. + +## Timebased One-Time-Password (TOTP) + +Like the name suggests, this type uses the current Time or a subset of it to generate the passcodes. +These passcodes solely rely on the secrecy of their Secretkey in order to provide passcodes. +An attacker only needs to guess that Secretkey and the other variable part is any given time, presumably the time upon login. +RFC4226 suggests a resynchronization attempt in case the passcode mismatches, providing the attacker a range of upto +/- 3 Minutes to create passcodes. + + +## Counterbased One-Time-Password (TOTP) + +This type uses an internal counter that needs to be in-synch with the server's counter to successfully authenticate the passcodes. +The main advantage over timebased OTP is the attacker doesnt only need to know the Secretkey but also the server's Counter in order to create valid passcodes. +RFC4226 suggests a resynchronization attempt in case the passcode mismatches, providing the attacker a range of upto +4 increments from the actual counter to create passcodes. + +# Configuration + +Enable Two-Factor: +```php +$config['twofactor'] = true; +``` + +Set throttle-time (in secconds): +```php +$config['twofactor_lock'] = 300; +``` + +# Usage + +These steps imply that TwoFactor has been enabled in your `config.php` + +Create a Two-Factor key: +- Go to 'My Settings' (/preferences/) +- Choose TwoFactor type +- Click on 'Generate TwoFactor Secret Key' +- If your browser didnt reload, reload manually +- Scan provided QR or click on 'Manual' to see the Key + +## Google Authenticator + +__Note__: Google Authenticator only allows counterbased OTP when scanned via QR codes. + +Installation guides for Google Authneticator can be found [here](https://support.google.com/accounts/answer/1066447?hl=en). + +Usage: +- Create a key like described above +- Scan provided QR or click on 'Manual' and type down the Secret +- On next login, enter the passcode that the App provides diff --git a/html/includes/authenticate.inc.php b/html/includes/authenticate.inc.php index d9eafee9ef..f71f2e6146 100644 --- a/html/includes/authenticate.inc.php +++ b/html/includes/authenticate.inc.php @@ -75,9 +75,15 @@ if ((isset($_SESSION['username'])) || (isset($_COOKIE['sess_id'],$_COOKIE['token $_SESSION['user_id'] = get_userid($_SESSION['username']); if (!$_SESSION['authenticated']) { - $_SESSION['authenticated'] = true; - dbInsert(array('user' => $_SESSION['username'], 'address' => $_SERVER["REMOTE_ADDR"], 'result' => 'Logged In'), 'authlog'); - header("Location: ".$_SERVER['REQUEST_URI']); + if( $config['twofactor'] === true && !isset($_SESSION['twofactor']) ) { + require_once($config['install_dir'].'/html/includes/authentication/twofactor.lib.php'); + twofactor_auth(); + } + if( !$config['twofactor'] || $_SESSION['twofactor'] ) { + $_SESSION['authenticated'] = true; + dbInsert(array('user' => $_SESSION['username'], 'address' => $_SERVER["REMOTE_ADDR"], 'result' => 'Logged In'), 'authlog'); + header("Location: ".$_SERVER['REQUEST_URI']); + } } if (isset($_POST['remember'])) { diff --git a/html/includes/authentication/twofactor.lib.php b/html/includes/authentication/twofactor.lib.php new file mode 100644 index 0000000000..0d54628c9f --- /dev/null +++ b/html/includes/authentication/twofactor.lib.php @@ -0,0 +1,231 @@ + + * 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. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . */ + +/** + * Two-Factor Authentication Library + * @author f0o + * @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. + */ +const keyInterval = 30; + +/** + * Size of the OTP. + * Set to 6 for the same reasons as above. + */ +const otpSize = 6; + +/** + * Window to honour whilest verifying OTP. + */ +const otpWindow = 4; + +/** + * Base32 Decoding dictionary + */ +$base32 = array( + "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 +); + +/** + * Base32 Encoding dictionary + */ +$base32_enc = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; + +/** + * Generate Secret Key + * @return string + */ +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; +} + +/** + * Generate HOTP (RFC 4226) + * @param string $key Secret Key + * @param int|boolean $counter Optional Counter, Defaults to Timestamp + * @return int + */ +function oath_hotp($key, $counter=false) { + global $base32; + if( $counter === false ) { + $counter = floor(microtime(true)/keyInterval); + } + $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, otpSize); + return str_pad($truncated, otpSize, '0', STR_PAD_LEFT); +} + +/** + * 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 + */ +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)/keyInterval); + $initcount = $counter-((otpWindow+1)*keyInterval); + $endcount = $counter+(otpWindow*keyInterval); + $totp = true; + } else { + //Counter based HOTP only has lookahead, not lookbehind. + $initcount = $counter-1; + $endcount = $counter+otpWindow; + $totp = false; + } + while( ++$initcount <= $endcount ) { + if( oath_hotp($key,$initcount) == $otp ) { + if( !$totp ) { + return $initcount; + } else { + return true; + } + } + } + } + return false; +} + +/** + * Print TwoFactor Input-Form + * @param boolean $form Include FORM-tags + * @return void|string + */ +function twofactor_form($form=true){ + global $config; + $ret = ""; + if( $form ) { + $ret .= ' +
'; + } + $ret .= ' +
+
+

Please Enter TwoFactor Token:

+
+
+
+ +
+ +
+
+
+
+ +
+
'; + $ret .= ''; + if( $form ) { + $ret .= ' +
'; + } + return $ret; +} + +/** + * Authentication logic + * @return void + */ +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']++; + } + } + $twofactor['fails'] = 0; + $_SESSION['twofactor'] = true; + } + dbUpdate(array('twofactor' => json_encode($twofactor)),'users','username = ?',array($_SESSION['username'])); + } + } + } +} +?> diff --git a/html/js/jquery.qrcode.min.js b/html/js/jquery.qrcode.min.js new file mode 100644 index 0000000000..6e2dbcde1e --- /dev/null +++ b/html/js/jquery.qrcode.min.js @@ -0,0 +1,2 @@ +/* jQuery.qrcode 0.11.0 - http://larsjung.de/jquery-qrcode/ - uses //github.com/kazuhikoarase/qrcode-generator (MIT) */ +!function(){"use strict";function r(r,t,e,n){function o(r,t){return r-=n,t-=n,0>r||r>=a||0>t||t>=a?!1:i.isDark(r,t)}var i=y(e,t);i.addData(r),i.make(),n=n||0;var a=i.getModuleCount(),u=i.getModuleCount()+2*n,f=function(r,t,e,n){var o=this.isDark,i=1/u;this.isDark=function(a,u){var f=u*i,l=a*i,c=f+i,g=l+i;return o(a,u)&&(r>c||f>e||t>g||l>n)}};this.text=r,this.level=t,this.version=e,this.moduleCount=u,this.isDark=o,this.addBlank=f}function t(t,e,n,o,i){n=Math.max(1,n||1),o=Math.min(40,o||40);for(var a=n;o>=a;a+=1)try{return new r(t,e,a,i)}catch(u){}}function e(r,t,e){var n=e.size,o="bold "+e.mSize*n+"px "+e.fontname,i=d("")[0].getContext("2d");i.font=o;var a=i.measureText(e.label).width,u=e.mSize,f=a/n,l=(1-f)*e.mPosX,c=(1-u)*e.mPosY,g=l+f,s=c+u,h=.01;1===e.mode?r.addBlank(0,c-h,n,s+h):r.addBlank(l-h,c-h,g+h,s+h),t.fillStyle=e.fontcolor,t.font=o,t.fillText(e.label,l*n,c*n+.75*e.mSize*n)}function n(r,t,e){var n=e.size,o=e.image.naturalWidth||1,i=e.image.naturalHeight||1,a=e.mSize,u=a*o/i,f=(1-u)*e.mPosX,l=(1-a)*e.mPosY,c=f+u,g=l+a,s=.01;3===e.mode?r.addBlank(0,l-s,n,g+s):r.addBlank(f-s,l-s,c+s,g+s),t.drawImage(e.image,f*n,l*n,u*n,a*n)}function o(r,t,o){d(o.background).is("img")?t.drawImage(o.background,0,0,o.size,o.size):o.background&&(t.fillStyle=o.background,t.fillRect(o.left,o.top,o.size,o.size));var i=o.mode;1===i||2===i?e(r,t,o):(3===i||4===i)&&n(r,t,o)}function i(r,t,e,n,o,i,a,u){r.isDark(a,u)&&t.rect(n,o,i,i)}function a(r,t,e,n,o,i,a,u,f,l){a?r.moveTo(t+i,e):r.moveTo(t,e),u?(r.lineTo(n-i,e),r.arcTo(n,e,n,o,i)):r.lineTo(n,e),f?(r.lineTo(n,o-i),r.arcTo(n,o,t,o,i)):r.lineTo(n,o),l?(r.lineTo(t+i,o),r.arcTo(t,o,t,e,i)):r.lineTo(t,o),a?(r.lineTo(t,e+i),r.arcTo(t,e,n,e,i)):r.lineTo(t,e)}function u(r,t,e,n,o,i,a,u,f,l){a&&(r.moveTo(t+i,e),r.lineTo(t,e),r.lineTo(t,e+i),r.arcTo(t,e,t+i,e,i)),u&&(r.moveTo(n-i,e),r.lineTo(n,e),r.lineTo(n,e+i),r.arcTo(n,e,n-i,e,i)),f&&(r.moveTo(n-i,o),r.lineTo(n,o),r.lineTo(n,o-i),r.arcTo(n,o,n-i,o,i)),l&&(r.moveTo(t+i,o),r.lineTo(t,o),r.lineTo(t,o-i),r.arcTo(t,o,t+i,o,i))}function f(r,t,e,n,o,i,f,l){var c=r.isDark,g=n+i,s=o+i,h=e.radius*i,v=f-1,d=f+1,w=l-1,m=l+1,T=c(f,l),y=c(v,w),p=c(v,l),B=c(v,m),E=c(f,m),k=c(d,m),A=c(d,l),M=c(d,w),D=c(f,w);T?a(t,n,o,g,s,h,!p&&!D,!p&&!E,!A&&!E,!A&&!D):u(t,n,o,g,s,h,p&&D&&y,p&&E&&B,A&&E&&k,A&&D&&M)}function l(r,t,e){var n,o,a=r.moduleCount,u=e.size/a,l=i;for(m&&e.radius>0&&e.radius<=.5&&(l=f),t.beginPath(),n=0;a>n;n+=1)for(o=0;a>o;o+=1){var c=e.left+o*u,g=e.top+n*u,s=u;l(r,t,e,c,g,s,n,o)}if(d(e.fill).is("img")){t.strokeStyle="rgba(0,0,0,0.5)",t.lineWidth=2,t.stroke();var h=t.globalCompositeOperation;t.globalCompositeOperation="destination-out",t.fill(),t.globalCompositeOperation=h,t.clip(),t.drawImage(e.fill,0,0,e.size,e.size),t.restore()}else t.fillStyle=e.fill,t.fill()}function c(r,e){var n=t(e.text,e.ecLevel,e.minVersion,e.maxVersion,e.quiet);if(!n)return null;var i=d(r).data("qrcode",n),a=i[0].getContext("2d");return o(n,a,e),l(n,a,e),i}function g(r){var t=d("").attr("width",r.size).attr("height",r.size);return c(t,r)}function s(r){return d("").attr("src",g(r)[0].toDataURL("image/png"))}function h(r){var e=t(r.text,r.ecLevel,r.minVersion,r.maxVersion,r.quiet);if(!e)return null;var n,o,i=r.size,a=r.background,u=Math.floor,f=e.moduleCount,l=u(i/f),c=u(.5*(i-l*f)),g={position:"relative",left:0,top:0,padding:0,margin:0,width:i,height:i},s={position:"absolute",padding:0,margin:0,width:l,height:l,"background-color":r.fill},h=d("
").data("qrcode",e).css(g);for(a&&h.css("background-color",a),n=0;f>n;n+=1)for(o=0;f>o;o+=1)e.isDark(n,o)&&d("
").css(s).css({left:c+o*l,top:c+n*l}).appendTo(h);return h}function v(r){return w&&"canvas"===r.render?g(r):w&&"image"===r.render?s(r):h(r)}var d=jQuery,w=function(){var r=document.createElement("canvas");return!(!r.getContext||!r.getContext("2d"))}(),m="[object Opera]"!==Object.prototype.toString.call(window.opera),T={render:"canvas",minVersion:1,maxVersion:40,ecLevel:"L",left:0,top:0,size:200,fill:"#000",background:null,text:"no text",radius:0,quiet:0,mode:0,mSize:.1,mPosX:.5,mPosY:.5,label:"no label",fontname:"sans",fontcolor:"#000",image:null};d.fn.qrcode=function(r){var t=d.extend({},T,r);return this.each(function(){"canvas"===this.nodeName.toLowerCase()?c(this,t):d(this).append(v(t))})};var y=function(){function r(t,e){if("undefined"==typeof t.length)throw new Error(t.length+"/"+e);var n=function(){for(var r=0;re;e+=1){t[e]=new Array(r);for(var n=0;r>n;n+=1)t[e][n]=null}return t}(h),y(0,0),y(h-7,0),y(0,h-7),E(),B(),A(r,t),c>=7&&k(r),null==d&&(d=C(c,g,w)),M(d,t)},y=function(r,t){for(var e=-1;7>=e;e+=1)if(!(-1>=r+e||r+e>=h))for(var n=-1;7>=n;n+=1)-1>=t+n||t+n>=h||(s[r+e][t+n]=e>=0&&6>=e&&(0==n||6==n)||n>=0&&6>=n&&(0==e||6==e)||e>=2&&4>=e&&n>=2&&4>=n?!0:!1)},p=function(){for(var r=0,t=0,e=0;8>e;e+=1){T(!0,e);var n=i.getLostPoint(m);(0==e||r>n)&&(r=n,t=e)}return t},B=function(){for(var r=8;h-8>r;r+=1)null==s[r][6]&&(s[r][6]=r%2==0);for(var t=8;h-8>t;t+=1)null==s[6][t]&&(s[6][t]=t%2==0)},E=function(){for(var r=i.getPatternPosition(c),t=0;t=a;a+=1)for(var u=-2;2>=u;u+=1)s[n+a][o+u]=-2==a||2==a||-2==u||2==u||0==a&&0==u?!0:!1}},k=function(r){for(var t=i.getBCHTypeNumber(c),e=0;18>e;e+=1){var n=!r&&1==(t>>e&1);s[Math.floor(e/3)][e%3+h-8-3]=n}for(var e=0;18>e;e+=1){var n=!r&&1==(t>>e&1);s[e%3+h-8-3][Math.floor(e/3)]=n}},A=function(r,t){for(var e=g<<3|t,n=i.getBCHTypeInfo(e),o=0;15>o;o+=1){var a=!r&&1==(n>>o&1);6>o?s[o][8]=a:8>o?s[o+1][8]=a:s[h-15+o][8]=a}for(var o=0;15>o;o+=1){var a=!r&&1==(n>>o&1);8>o?s[8][h-o-1]=a:9>o?s[8][15-o-1+1]=a:s[8][15-o-1]=a}s[h-8][8]=!r},M=function(r,t){for(var e=-1,n=h-1,o=7,a=0,u=i.getMaskFunction(t),f=h-1;f>0;f-=2)for(6==f&&(f-=1);;){for(var l=0;2>l;l+=1)if(null==s[n][f-l]){var c=!1;a>>o&1));var g=u(n,f-l);g&&(c=!c),s[n][f-l]=c,o-=1,-1==o&&(a+=1,o=7)}if(n+=e,0>n||n>=h){n-=e,e=-e;break}}},D=function(t,e){for(var n=0,o=0,a=0,u=new Array(e.length),f=new Array(e.length),l=0;l=0?d.get(w):0}}for(var m=0,s=0;ss;s+=1)for(var l=0;ls;s+=1)for(var l=0;l8*s)throw new Error("code length overflow. ("+l.getLengthInBits()+">"+8*s+")");for(l.getLengthInBits()+4<=8*s&&l.put(0,4);l.getLengthInBits()%8!=0;)l.putBit(!1);for(;;){if(l.getLengthInBits()>=8*s)break;if(l.put(o,8),l.getLengthInBits()>=8*s)break;l.put(a,8)}return D(l,n)};return m.addData=function(r){var t=l(r);w.push(t),d=null},m.isDark=function(r,t){if(0>r||r>=h||0>t||t>=h)throw new Error(r+","+t);return s[r][t]},m.getModuleCount=function(){return h},m.make=function(){T(!1,p())},m.createTableTag=function(r,t){r=r||2,t="undefined"==typeof t?4*r:t;var e="";e+='";for(var o=0;o';e+=""}return e+="",e+="
"},m.createImgTag=function(r,t){r=r||2,t="undefined"==typeof t?4*r:t;var e=m.getModuleCount()*r+2*t,n=t,o=e-t;return v(e,e,function(t,e){if(t>=n&&o>t&&e>=n&&o>e){var i=Math.floor((t-n)/r),a=Math.floor((e-n)/r);return m.isDark(a,i)?0:1}return 1})},m};t.stringToBytes=function(r){for(var t=new Array,e=0;ei)t.push(i);else{var a=e[r.charAt(o)];"number"==typeof a?(255&a)==a?t.push(a):(t.push(a>>>8),t.push(255&a)):t.push(n)}}return t}};var e={MODE_NUMBER:1,MODE_ALPHA_NUM:2,MODE_8BIT_BYTE:4,MODE_KANJI:8},n={L:1,M:0,Q:3,H:2},o={PATTERN000:0,PATTERN001:1,PATTERN010:2,PATTERN011:3,PATTERN100:4,PATTERN101:5,PATTERN110:6,PATTERN111:7},i=function(){var t=[[],[6,18],[6,22],[6,26],[6,30],[6,34],[6,22,38],[6,24,42],[6,26,46],[6,28,50],[6,30,54],[6,32,58],[6,34,62],[6,26,46,66],[6,26,48,70],[6,26,50,74],[6,30,54,78],[6,30,56,82],[6,30,58,86],[6,34,62,90],[6,28,50,72,94],[6,26,50,74,98],[6,30,54,78,102],[6,28,54,80,106],[6,32,58,84,110],[6,30,58,86,114],[6,34,62,90,118],[6,26,50,74,98,122],[6,30,54,78,102,126],[6,26,52,78,104,130],[6,30,56,82,108,134],[6,34,60,86,112,138],[6,30,58,86,114,142],[6,34,62,90,118,146],[6,30,54,78,102,126,150],[6,24,50,76,102,128,154],[6,28,54,80,106,132,158],[6,32,58,84,110,136,162],[6,26,54,82,110,138,166],[6,30,58,86,114,142,170]],n=1335,i=7973,u=21522,f={},l=function(r){for(var t=0;0!=r;)t+=1,r>>>=1;return t};return f.getBCHTypeInfo=function(r){for(var t=r<<10;l(t)-l(n)>=0;)t^=n<=0;)t^=i<n;n+=1)e=e.multiply(r([1,a.gexp(n)],0));return e},f.getLengthInBits=function(r,t){if(t>=1&&10>t)switch(r){case e.MODE_NUMBER:return 10;case e.MODE_ALPHA_NUM:return 9;case e.MODE_8BIT_BYTE:return 8;case e.MODE_KANJI:return 8;default:throw new Error("mode:"+r)}else if(27>t)switch(r){case e.MODE_NUMBER:return 12;case e.MODE_ALPHA_NUM:return 11;case e.MODE_8BIT_BYTE:return 16;case e.MODE_KANJI:return 10;default:throw new Error("mode:"+r)}else{if(!(41>t))throw new Error("type:"+t);switch(r){case e.MODE_NUMBER:return 14;case e.MODE_ALPHA_NUM:return 13;case e.MODE_8BIT_BYTE:return 16;case e.MODE_KANJI:return 12;default:throw new Error("mode:"+r)}}},f.getLostPoint=function(r){for(var t=r.getModuleCount(),e=0,n=0;t>n;n+=1)for(var o=0;t>o;o+=1){for(var i=0,a=r.isDark(n,o),u=-1;1>=u;u+=1)if(!(0>n+u||n+u>=t))for(var f=-1;1>=f;f+=1)0>o+f||o+f>=t||(0!=u||0!=f)&&a==r.isDark(n+u,o+f)&&(i+=1);i>5&&(e+=3+i-5)}for(var n=0;t-1>n;n+=1)for(var o=0;t-1>o;o+=1){var l=0;r.isDark(n,o)&&(l+=1),r.isDark(n+1,o)&&(l+=1),r.isDark(n,o+1)&&(l+=1),r.isDark(n+1,o+1)&&(l+=1),(0==l||4==l)&&(e+=3)}for(var n=0;t>n;n+=1)for(var o=0;t-6>o;o+=1)r.isDark(n,o)&&!r.isDark(n,o+1)&&r.isDark(n,o+2)&&r.isDark(n,o+3)&&r.isDark(n,o+4)&&!r.isDark(n,o+5)&&r.isDark(n,o+6)&&(e+=40);for(var o=0;t>o;o+=1)for(var n=0;t-6>n;n+=1)r.isDark(n,o)&&!r.isDark(n+1,o)&&r.isDark(n+2,o)&&r.isDark(n+3,o)&&r.isDark(n+4,o)&&!r.isDark(n+5,o)&&r.isDark(n+6,o)&&(e+=40);for(var c=0,o=0;t>o;o+=1)for(var n=0;t>n;n+=1)r.isDark(n,o)&&(c+=1);var g=Math.abs(100*c/t/t-50)/5;return e+=10*g},f}(),a=function(){for(var r=new Array(256),t=new Array(256),e=0;8>e;e+=1)r[e]=1<e;e+=1)r[e]=r[e-4]^r[e-5]^r[e-6]^r[e-8];for(var e=0;255>e;e+=1)t[r[e]]=e;var n={};return n.glog=function(r){if(1>r)throw new Error("glog("+r+")");return t[r]},n.gexp=function(t){for(;0>t;)t+=255;for(;t>=256;)t-=255;return r[t]},n}(),u=function(){var r=[[1,26,19],[1,26,16],[1,26,13],[1,26,9],[1,44,34],[1,44,28],[1,44,22],[1,44,16],[1,70,55],[1,70,44],[2,35,17],[2,35,13],[1,100,80],[2,50,32],[2,50,24],[4,25,9],[1,134,108],[2,67,43],[2,33,15,2,34,16],[2,33,11,2,34,12],[2,86,68],[4,43,27],[4,43,19],[4,43,15],[2,98,78],[4,49,31],[2,32,14,4,33,15],[4,39,13,1,40,14],[2,121,97],[2,60,38,2,61,39],[4,40,18,2,41,19],[4,40,14,2,41,15],[2,146,116],[3,58,36,2,59,37],[4,36,16,4,37,17],[4,36,12,4,37,13],[2,86,68,2,87,69],[4,69,43,1,70,44],[6,43,19,2,44,20],[6,43,15,2,44,16],[4,101,81],[1,80,50,4,81,51],[4,50,22,4,51,23],[3,36,12,8,37,13],[2,116,92,2,117,93],[6,58,36,2,59,37],[4,46,20,6,47,21],[7,42,14,4,43,15],[4,133,107],[8,59,37,1,60,38],[8,44,20,4,45,21],[12,33,11,4,34,12],[3,145,115,1,146,116],[4,64,40,5,65,41],[11,36,16,5,37,17],[11,36,12,5,37,13],[5,109,87,1,110,88],[5,65,41,5,66,42],[5,54,24,7,55,25],[11,36,12],[5,122,98,1,123,99],[7,73,45,3,74,46],[15,43,19,2,44,20],[3,45,15,13,46,16],[1,135,107,5,136,108],[10,74,46,1,75,47],[1,50,22,15,51,23],[2,42,14,17,43,15],[5,150,120,1,151,121],[9,69,43,4,70,44],[17,50,22,1,51,23],[2,42,14,19,43,15],[3,141,113,4,142,114],[3,70,44,11,71,45],[17,47,21,4,48,22],[9,39,13,16,40,14],[3,135,107,5,136,108],[3,67,41,13,68,42],[15,54,24,5,55,25],[15,43,15,10,44,16],[4,144,116,4,145,117],[17,68,42],[17,50,22,6,51,23],[19,46,16,6,47,17],[2,139,111,7,140,112],[17,74,46],[7,54,24,16,55,25],[34,37,13],[4,151,121,5,152,122],[4,75,47,14,76,48],[11,54,24,14,55,25],[16,45,15,14,46,16],[6,147,117,4,148,118],[6,73,45,14,74,46],[11,54,24,16,55,25],[30,46,16,2,47,17],[8,132,106,4,133,107],[8,75,47,13,76,48],[7,54,24,22,55,25],[22,45,15,13,46,16],[10,142,114,2,143,115],[19,74,46,4,75,47],[28,50,22,6,51,23],[33,46,16,4,47,17],[8,152,122,4,153,123],[22,73,45,3,74,46],[8,53,23,26,54,24],[12,45,15,28,46,16],[3,147,117,10,148,118],[3,73,45,23,74,46],[4,54,24,31,55,25],[11,45,15,31,46,16],[7,146,116,7,147,117],[21,73,45,7,74,46],[1,53,23,37,54,24],[19,45,15,26,46,16],[5,145,115,10,146,116],[19,75,47,10,76,48],[15,54,24,25,55,25],[23,45,15,25,46,16],[13,145,115,3,146,116],[2,74,46,29,75,47],[42,54,24,1,55,25],[23,45,15,28,46,16],[17,145,115],[10,74,46,23,75,47],[10,54,24,35,55,25],[19,45,15,35,46,16],[17,145,115,1,146,116],[14,74,46,21,75,47],[29,54,24,19,55,25],[11,45,15,46,46,16],[13,145,115,6,146,116],[14,74,46,23,75,47],[44,54,24,7,55,25],[59,46,16,1,47,17],[12,151,121,7,152,122],[12,75,47,26,76,48],[39,54,24,14,55,25],[22,45,15,41,46,16],[6,151,121,14,152,122],[6,75,47,34,76,48],[46,54,24,10,55,25],[2,45,15,64,46,16],[17,152,122,4,153,123],[29,74,46,14,75,47],[49,54,24,10,55,25],[24,45,15,46,46,16],[4,152,122,18,153,123],[13,74,46,32,75,47],[48,54,24,14,55,25],[42,45,15,32,46,16],[20,147,117,4,148,118],[40,75,47,7,76,48],[43,54,24,22,55,25],[10,45,15,67,46,16],[19,148,118,6,149,119],[18,75,47,31,76,48],[34,54,24,34,55,25],[20,45,15,61,46,16]],t=function(r,t){var e={};return e.totalCount=r,e.dataCount=t,e},e={},o=function(t,e){switch(e){case n.L:return r[4*(t-1)+0];case n.M:return r[4*(t-1)+1];case n.Q:return r[4*(t-1)+2];case n.H:return r[4*(t-1)+3];default:return void 0}};return e.getRSBlocks=function(r,e){var n=o(r,e);if("undefined"==typeof n)throw new Error("bad rs block @ typeNumber:"+r+"/errorCorrectLevel:"+e);for(var i=n.length/3,a=new Array,u=0;i>u;u+=1)for(var f=n[3*u+0],l=n[3*u+1],c=n[3*u+2],g=0;f>g;g+=1)a.push(t(l,c));return a},e}(),f=function(){var r=new Array,t=0,e={};return e.getBuffer=function(){return r},e.get=function(t){var e=Math.floor(t/8);return 1==(r[e]>>>7-t%8&1)},e.put=function(r,t){for(var n=0;t>n;n+=1)e.putBit(1==(r>>>t-n-1&1))},e.getLengthInBits=function(){return t},e.putBit=function(e){var n=Math.floor(t/8);r.length<=n&&r.push(0),e&&(r[n]|=128>>>t%8),t+=1},e},l=function(r){var n=e.MODE_8BIT_BYTE,o=t.stringToBytes(r),i={};return i.getMode=function(){return n},i.getLength=function(){return o.length},i.write=function(r){for(var t=0;t>>8)},t.writeBytes=function(r,e,n){e=e||0,n=n||r.length;for(var o=0;n>o;o+=1)t.writeByte(r[o+e])},t.writeString=function(r){for(var e=0;e0&&(t+=","),t+=r[e];return t+="]"},t},g=function(){var r=0,t=0,e=0,n="",o={},i=function(r){n+=String.fromCharCode(a(63&r))},a=function(r){if(0>r);else{if(26>r)return 65+r;if(52>r)return 97+(r-26);if(62>r)return 48+(r-52);if(62==r)return 43;if(63==r)return 47}throw new Error("n:"+r)};return o.writeByte=function(n){for(r=r<<8|255&n,t+=8,e+=1;t>=6;)i(r>>>t-6),t-=6},o.flush=function(){if(t>0&&(i(r<<6-t),r=0,t=0),e%3!=0)for(var o=3-e%3,a=0;o>a;a+=1)n+="="},o.toString=function(){return n},o},s=function(r){var t=r,e=0,n=0,o=0,i={};i.read=function(){for(;8>o;){if(e>=t.length){if(0==o)return-1;throw new Error("unexpected end of file./"+o)}var r=t.charAt(e);if(e+=1,"="==r)return o=0,-1;r.match(/^\s$/)||(n=n<<6|a(r.charCodeAt(0)),o+=6)}var i=n>>>o-8&255;return o-=8,i};var a=function(r){if(r>=65&&90>=r)return r-65;if(r>=97&&122>=r)return r-97+26;if(r>=48&&57>=r)return r-48+52;if(43==r)return 62;if(47==r)return 63;throw new Error("c:"+r)};return i},h=function(r,t){var e=r,n=t,o=new Array(r*t),i={};i.setPixel=function(r,t,n){o[t*e+r]=n},i.write=function(r){r.writeString("GIF87a"),r.writeShort(e),r.writeShort(n),r.writeByte(128),r.writeByte(0),r.writeByte(0),r.writeByte(0),r.writeByte(0),r.writeByte(0),r.writeByte(255),r.writeByte(255),r.writeByte(255),r.writeString(","),r.writeShort(0),r.writeShort(0),r.writeShort(e),r.writeShort(n),r.writeByte(0);var t=2,o=u(t);r.writeByte(t);for(var i=0;o.length-i>255;)r.writeByte(255),r.writeBytes(o,i,255),i+=255;r.writeByte(o.length-i),r.writeBytes(o,i,o.length-i),r.writeByte(0),r.writeString(";")};var a=function(r){var t=r,e=0,n=0,o={};return o.write=function(r,o){if(r>>>o!=0)throw new Error("length over");for(;e+o>=8;)t.writeByte(255&(r<>>=8-e,n=0,e=0;n=r<0&&t.writeByte(n)},o},u=function(r){for(var t=1<u;u+=1)i.add(String.fromCharCode(u));i.add(String.fromCharCode(t)),i.add(String.fromCharCode(e));var l=c(),g=a(l);g.write(t,n);var s=0,h=String.fromCharCode(o[s]);for(s+=1;si;i+=1)for(var a=0;r>a;a+=1)o.setPixel(a,i,e(a,i));var u=c();o.write(u);for(var f=g(),l=u.toByteArray(),s=0;s''),users,'user_id = ?',array($vars['user_id'])) ) { + echo "
TwoFactor credentials removed.
"; + } else { + echo "
Couldnt remove user's TwoFactor credentials.
"; + } + } + if( $vars['twofactorunlock'] ) { + $twofactor = dbFetchRow("SELECT twofactor FROM users WHERE user_id = ?",array($vars['user_id'])); + $twofactor = json_decode($twofactor['twofactor'],true); + $twofactor['fails'] = 0; + if( dbUpdate(array('twofactor'=>json_encode($twofactor)),users,'user_id = ?',array($vars['user_id'])) ) { + echo "
User unlocked.
"; + } else { + echo "
Couldnt reset user's TwoFactor failures.
"; + } + } + } + echo("
@@ -313,7 +333,34 @@ if ($_SESSION['userlevel'] != '10') { include("includes/error-no-perm.inc.php");
- "); + "); + if( $config['twofactor'] ) { + echo "

Two-Factor Authentication

"; + $twofactor = dbFetchRow("SELECT twofactor FROM users WHERE user_id = ?",array($vars['user_id'])); + $twofactor = json_decode($twofactor['twofactor'],true); + if( $twofactor['fails'] >= 3 && (!$config['twofactor_lock'] || (time()-$twofactor['last']) < $config['twofactor_lock']) ) { + echo "
+ + +
+ + + +
+
"; + } + if( $twofactor['key'] ) { + echo "
+ + + + +
+
"; + } else { + echo "

No TwoFactor key generated for this user, Nothing to do.

"; + } + } } else { echo print_error("Error getting user details"); } diff --git a/html/pages/logon.inc.php b/html/pages/logon.inc.php index fe525f4349..bd6a8ede13 100644 --- a/html/pages/logon.inc.php +++ b/html/pages/logon.inc.php @@ -1,3 +1,7 @@ +
@@ -56,6 +60,8 @@ if (isset($config['login_message'])) document.logonform.username.focus(); // --> - +
diff --git a/html/pages/preferences.inc.php b/html/pages/preferences.inc.php index fffd129182..845b3198e4 100644 --- a/html/pages/preferences.inc.php +++ b/html/pages/preferences.inc.php @@ -65,6 +65,108 @@ if (passwordscanchange($_SESSION['username'])) echo(""); } +if( $config['twofactor'] === true ) { + if( $_POST['twofactorremove'] == 1 ) { + require_once($config['install_dir']."/html/includes/authentication/twofactor.lib.php"); + if( !isset($_POST['twofactor']) ) { + echo '
'; + echo ''; + echo twofactor_form(false); + echo '
'; + } else{ + $twofactor = dbFetchRow('SELECT twofactor FROM users WHERE username = ?', array($_SESSION['username'])); + if( empty($twofactor['twofactor']) ) { + echo '
Error: How did you even get here?!
'; + } else { + $twofactor = json_decode($twofactor['twofactor'],true); + } + if( verify_hotp($twofactor['key'],$_POST['twofactor'],$twofactor['counter']) ) { + if( !dbUpdate(array('twofactor' => ''),'users','username = ?',array($_SESSION['username'])) ) { + echo '
Error while disabling TwoFactor.
'; + } else { + echo '
TwoFactor Disabled.
'; + } + } else { + session_destroy(); + echo '
Error: Supplied TwoFactor Token is wrong, you\'ve been logged out.
'; + } + } + } else { + $twofactor = dbFetchRow("SELECT twofactor FROM users WHERE username = ?", array($_SESSION['username'])); + echo ''; + echo '

Two-Factor Authentication

'; + if( !empty($twofactor['twofactor']) ) { + $twofactor = json_decode($twofactor['twofactor'],true); + $twofactor['text'] = "
+ +
+ +
+
"; + if( $twofactor['counter'] !== false ) { + $twofactor['uri'] = "otpauth://hotp/".$_SESSION['username']."?issuer=LibreNMS&counter=".$twofactor['counter']."&secret=".$twofactor['key']; + $twofactor['text'] .= "
+ +
+ +
+
"; + } else { + $twofactor['uri'] = "otpauth://totp/".$_SESSION['username']."?issuer=LibreNMS&secret=".$twofactor['key']; + } + echo '
+
+ +
'; + echo '
+
'.$twofactor['text'].'
+ +
'; + echo ''; + echo '
+ + +
'; + } else { + if( isset($_POST['gentwofactorkey']) && isset($_POST['twofactortype']) ) { + require_once($config['install_dir']."/html/includes/authentication/twofactor.lib.php"); + $chk = dbFetchRow("SELECT twofactor FROM users WHERE username = ?", array($_SESSION['username'])); + if( empty($chk['twofactor']) ) { + $twofactor = array('key' => twofactor_genkey()); + if( $_POST['twofactortype'] == "counter" ) { + $twofactor['counter'] = 1; + } else { + $twofactor['counter'] = false; + } + if( !dbUpdate(array('twofactor' => json_encode($twofactor)),'users','username = ?',array($_SESSION['username'])) ) { + echo '
Error inserting TwoFactor details. Please try again later and contact Administrator if error persists.
'; + } else { + echo '
Added TwoFactor credentials. Please reload page.
'; + } + } else { + echo '
TwoFactor credentials already exists.
'; + } + } else { + echo '
+ +
+ +
+ +
+
+ +
'; + } + } + echo '
'; + } +} + echo("
"); echo("
Device Permissions
"); diff --git a/sql-schema/038.sql b/sql-schema/038.sql new file mode 100644 index 0000000000..b32bab4c1f --- /dev/null +++ b/sql-schema/038.sql @@ -0,0 +1 @@ +ALTER TABLE `users` ADD `twofactor` VARCHAR( 255 ) NOT NULL;