Merge pull request #57 from LDAPAccountManager/yubiKey2FA

YubiKey 2FA
This commit is contained in:
gruberroland 2019-01-01 11:53:49 +01:00 committed by GitHub
commit 5682245739
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 755 additions and 46 deletions

View File

@ -25,4 +25,4 @@ There are two modules. Usually, you only need the files inside "lam".
LAM is published under the GNU General Public License.
The complete list of licenses can be found in the copyright file.
Copyright (C) 2003 - 2018 Roland Gruber <post@rolandgruber.de>
Copyright (C) 2003 - 2019 Roland Gruber <post@rolandgruber.de>

View File

@ -1,4 +1,4 @@
This software is copyright (c) 2003 - 2018 by Roland Gruber
This software is copyright (c) 2003 - 2019 by Roland Gruber
If you purchased a copy of LDAP Account Manager Pro then the following
files are licensed under the conditions which you accepted at purchase
@ -199,6 +199,29 @@ B:
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
C:
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list
of conditions and the following disclaimer.
2. 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 HOLDER 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.
Programs and licenses with other licenses and/or authors than the
main license and authors:
@ -206,6 +229,7 @@ main license and authors:
lib/3rdParty/tcpdf/fonts/DejaVu*.ttf A Public Domain, Bitstream, Inc., Tavmjong Bah
lib/3rdParty/tcpdf/fonts/DejaVu*.z A Public Domain, Bitstream, Inc., Tavmjong Bah
lib/3rdParty/phpseclib B Jim Wigginton
lib/3rdParty/yubico/Yubico.php C 2015 Yubico AB
templates/lib/*jquery*.js B 2018 jQuery Foundation and other contributors
style/120_jquery-ui*.css B 2016 jQuery Foundation and other contributors
templates/lib/*jquery-dropmenu-*.js B 2010 Fred Heusschen
@ -221,4 +245,3 @@ style/610_magnific-popup.css B 2016 Dmitry Semenov
style/responsive/105_normalize.css B Nicolas Gallagher and Jonathan Neal
style/responsive/110_grid.css B

View File

@ -1,3 +1,6 @@
March 2019
- Added YubiKey as 2-factor authentication provider
28.12.2018 6.6
- New import/export in tools menu
- YubiKey support

View File

@ -15,7 +15,7 @@ LAM - Readme
https://www.ldap-account-manager.org/
Copyright (C) 2003 - 2018 Roland Gruber <post@rolandgruber.de>
Copyright (C) 2003 - 2019 Roland Gruber <post@rolandgruber.de>
Installation and documentation:
Please see the LAM manual in docs/manual/index.html.

View File

@ -1,4 +1,4 @@
This software is copyright (c) 2003 - 2018 by Roland Gruber
This software is copyright (c) 2003 - 2019 by Roland Gruber
If you purchased a copy of LDAP Account Manager Pro then the following
files are licensed under the conditions which you accepted at purchase
@ -198,6 +198,29 @@ B:
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
C:
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list
of conditions and the following disclaimer.
2. 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 HOLDER 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.
Programs and licenses with other licenses and/or authors than the
main license and authors:
@ -205,6 +228,7 @@ main license and authors:
lib/3rdParty/tcpdf/fonts/DejaVu*.ttf A Public Domain, Bitstream, Inc., Tavmjong Bah
lib/3rdParty/tcpdf/fonts/DejaVu*.z A Public Domain, Bitstream, Inc., Tavmjong Bah
lib/3rdParty/phpseclib B Jim Wigginton
lib/3rdParty/yubico/Yubico.php C 2015 Yubico AB
templates/lib/*jquery*.js B 2018 jQuery Foundation and other contributors
style/120_jquery-ui*.css B 2016 jQuery Foundation and other contributors
templates/lib/*jquery-dropmenu-*.js B 2010 Fred Heusschen

View File

@ -596,11 +596,53 @@
<para><ulink
url="https://www.privacyidea.org/">privacyIdea</ulink></para>
</listitem>
<listitem>
<para><ulink url="https://www.yubico.com/">YubiKey</ulink></para>
</listitem>
</itemizedlist>
<para>By default LAM will enforce to use a token and reject users that
did not setup one. You can set this check to optional. But if a user
has setup a token then this will always be required.</para>
<para>Configuration options:</para>
<para>privacyIDEA:</para>
<itemizedlist>
<listitem>
<para>Base URL: please enter the URL of your privacyIDEA
instance</para>
</listitem>
</itemizedlist>
<para>YubiKey:</para>
<itemizedlist>
<listitem>
<para>Base URL: please enter the URL of your YubiKey verfication
server. For YubiKey cloud this is
"https://api.yubico.com/wsapi/2.0/verify". If you run a custom
verification API such as yubiserver then enter its URL (e.g.
http://www.example.com:8000/wsapi/2.0/verify). The URL needs to
end with "/wsapi/2.0/verify".</para>
</listitem>
<listitem>
<para>Client id: this is only required for YubiKey cloud. You can
register here: https://upgrade.yubico.com/getapikey/</para>
</listitem>
<listitem>
<para>Secret key: this is only required for YubiKey cloud. You can
register here: https://upgrade.yubico.com/getapikey/</para>
</listitem>
</itemizedlist>
<para>Optional: By default LAM will enforce to use a token and reject
users that did not setup one. You can set this check to optional. But
if a user has setup a token then this will always be required.</para>
<para>Disable certificate check: This should be used on development
instances only. It skips the certificate check when connecting to
verification server.</para>
<screenshot>
<mediaobject>

View File

@ -279,11 +279,51 @@
<para><ulink
url="https://www.privacyidea.org/">privacyIdea</ulink></para>
</listitem>
<listitem>
<para><ulink url="https://www.yubico.com/">YubiKey</ulink></para>
</listitem>
</itemizedlist>
<para>By default LAM will enforce to use a token and reject users that
did not setup one. You can set this check to optional. But if a user
has setup a token then this will always be required.</para>
<para>privacyIDEA:</para>
<itemizedlist>
<listitem>
<para>Base URL: please enter the URL of your privacyIDEA
instance</para>
</listitem>
</itemizedlist>
<para>YubiKey:</para>
<itemizedlist>
<listitem>
<para>Base URL: please enter the URL of your YubiKey verfication
server. For YubiKey cloud this is
"https://api.yubico.com/wsapi/2.0/verify". If you run a custom
verification API such as yubiserver then enter its URL (e.g.
http://www.example.com:8000/wsapi/2.0/verify). The URL needs to
end with "/wsapi/2.0/verify".</para>
</listitem>
<listitem>
<para>Client id: this is only required for YubiKey cloud. You can
register here: https://upgrade.yubico.com/getapikey/</para>
</listitem>
<listitem>
<para>Secret key: this is only required for YubiKey cloud. You can
register here: https://upgrade.yubico.com/getapikey/</para>
</listitem>
</itemizedlist>
<para>Optional: By default LAM will enforce to use a token and reject
users that did not setup one. You can set this check to optional. But
if a user has setup a token then this will always be required.</para>
<para>Disable certificate check: This should be used on development
instances only. It skips the certificate check when connecting to
verification server.</para>
<screenshot>
<mediaobject>

View File

@ -16,7 +16,7 @@
<para><ulink
url="https://www.ldap-account-manager.org/">https://www.ldap-account-manager.org/</ulink></para>
<para>Copyright (C) 2003 - 2018 Roland Gruber
<para>Copyright (C) 2003 - 2019 Roland Gruber
&lt;post@rolandgruber.de&gt;</para>
<para><emphasis role="bold">Key features:</emphasis></para>

View File

@ -311,6 +311,10 @@ $helpArray = array (
"Text" => _('Protect the self service login with a captcha.')),
"523" => array ("Headline" => _('Base color'),
"Text" => _('Background color for self service pages.')),
"524" => array ("Headline" => _('Client id'),
"Text" => _('Please enter your client id for the verification API.')),
"525" => array ("Headline" => _('Secret key'),
"Text" => _('Please enter your secret key for the verification API.')),
"550" => array ("Headline" => _("From address"),
"Text" => _("This email address will be set as sender address of all password mails. If empty the system default (php.ini) will be used.")),
"551" => array ("Headline" => _("Subject"),

View File

@ -5,7 +5,7 @@ use \LAMConfig;
/*
This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/)
Copyright (C) 2017 - 2018 Roland Gruber
Copyright (C) 2017 - 2019 Roland Gruber
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
@ -53,6 +53,8 @@ interface TwoFactorProvider {
*/
public function verify2ndFactor($user, $password, $serial, $twoFactorInput);
}
/**
@ -223,6 +225,76 @@ class PrivacyIDEAProvider implements TwoFactorProvider {
}
/**
* Authentication via YubiKeys.
*
* @author Roland Gruber
*/
class YubicoProvider implements TwoFactorProvider {
private $config;
/**
* Constructor.
*
* @param TwoFactorConfiguration $config configuration
*/
public function __construct(&$config) {
$this->config = $config;
}
/**
* {@inheritDoc}
* @see \LAM\LIB\TWO_FACTOR\TwoFactorProvider::getSerials()
*/
public function getSerials($user, $password) {
$keyAttributeName = strtolower($this->config->twoFactorAuthenticationSerialAttributeName);
if (isset($_SESSION['selfService_clientDN'])) {
$loginDn = lamDecrypt($_SESSION['selfService_clientDN'], 'SelfService');
}
else {
$loginDn = $_SESSION['ldap']->getUserName();
}
$handle = getLDAPServerHandle();
$ldapData = ldapGetDN($loginDn, array($keyAttributeName), $handle);
if (empty($ldapData[$keyAttributeName])) {
return array();
}
return array(implode(', ', $ldapData[$keyAttributeName]));
}
/**
* {@inheritDoc}
* @see \LAM\LIB\TWO_FACTOR\TwoFactorProvider::verify2ndFactor()
*/
public function verify2ndFactor($user, $password, $serial, $twoFactorInput) {
include_once(__DIR__ . "/3rdParty/yubico/Yubico.php");
$serialData = $this->getSerials($user, $password);
if (empty($serialData)) {
return false;
}
$serials = explode(', ', $serialData[0]);
$serialMatched = false;
foreach ($serials as $serial) {
if (strpos($twoFactorInput, $serial) === 0) {
$serialMatched = true;
break;
}
}
if (!$serialMatched) {
throw new \Exception(_('YubiKey id does not match allowed list of key ids.'));
}
$url = $this->config->twoFactorAuthenticationURL;
$httpsverify = !$this->config->twoFactorAuthenticationInsecure;
$clientId = $this->config->twoFactorAuthenticationClientId;
$secretKey = $this->config->twoFactorAuthenticationSecretKey;
$auth = new \Auth_Yubico($clientId, $secretKey, $url, $httpsverify);
$auth->verify($twoFactorInput);
return true;
}
}
/**
* Returns the correct 2 factor provider.
*/
@ -232,6 +304,8 @@ class TwoFactorProviderService {
const TWO_FACTOR_NONE = 'none';
/** 2factor authentication via privacyIDEA */
const TWO_FACTOR_PRIVACYIDEA = 'privacyidea';
/** 2factor authentication via YubiKey */
const TWO_FACTOR_YUBICO = 'yubico';
private $config;
@ -260,6 +334,9 @@ class TwoFactorProviderService {
if ($this->config->twoFactorAuthentication == TwoFactorProviderService::TWO_FACTOR_PRIVACYIDEA) {
return new PrivacyIDEAProvider($this->config);
}
elseif ($this->config->twoFactorAuthentication == TwoFactorProviderService::TWO_FACTOR_YUBICO) {
return new YubicoProvider($this->config);
}
throw new \Exception('Invalid provider: ' . $this->config->twoFactorAuthentication);
}
@ -270,11 +347,22 @@ class TwoFactorProviderService {
* @return TwoFactorConfiguration configuration
*/
private function getConfigSelfService(&$profile) {
$config = new TwoFactorConfiguration();
$config->twoFactorAuthentication = $profile->twoFactorAuthentication;
$config->twoFactorAuthenticationInsecure = $profile->twoFactorAuthenticationInsecure;
$config->twoFactorAuthenticationURL = $profile->twoFactorAuthenticationURL;
return $config;
$tfConfig = new TwoFactorConfiguration();
$tfConfig->twoFactorAuthentication = $profile->twoFactorAuthentication;
$tfConfig->twoFactorAuthenticationInsecure = $profile->twoFactorAuthenticationInsecure;
$tfConfig->twoFactorAuthenticationURL = $profile->twoFactorAuthenticationURL;
$tfConfig->twoFactorAuthenticationClientId = $profile->twoFactorAuthenticationClientId;
$tfConfig->twoFactorAuthenticationSecretKey = $profile->twoFactorAuthenticationSecretKey;
if ($tfConfig->twoFactorAuthentication == TwoFactorProviderService::TWO_FACTOR_YUBICO) {
$moduleSettings = $profile->moduleSettings;
if (!empty($moduleSettings['yubiKeyUser_attributeName'][0])) {
$tfConfig->twoFactorAuthenticationSerialAttributeName = $moduleSettings['yubiKeyUser_attributeName'][0];
}
else {
$tfConfig->twoFactorAuthenticationSerialAttributeName = 'yubiKeyId';
}
}
return $tfConfig;
}
/**
@ -284,11 +372,22 @@ class TwoFactorProviderService {
* @return TwoFactorConfiguration configuration
*/
private function getConfigAdmin($conf) {
$config = new TwoFactorConfiguration();
$config->twoFactorAuthentication = $conf->getTwoFactorAuthentication();
$config->twoFactorAuthenticationInsecure = $conf->getTwoFactorAuthenticationInsecure();
$config->twoFactorAuthenticationURL = $conf->getTwoFactorAuthenticationURL();
return $config;
$tfConfig = new TwoFactorConfiguration();
$tfConfig->twoFactorAuthentication = $conf->getTwoFactorAuthentication();
$tfConfig->twoFactorAuthenticationInsecure = $conf->getTwoFactorAuthenticationInsecure();
$tfConfig->twoFactorAuthenticationURL = $conf->getTwoFactorAuthenticationURL();
$tfConfig->twoFactorAuthenticationClientId = $conf->getTwoFactorAuthenticationClientId();
$tfConfig->twoFactorAuthenticationSecretKey = $conf->getTwoFactorAuthenticationSecretKey();
if ($tfConfig->twoFactorAuthentication == TwoFactorProviderService::TWO_FACTOR_YUBICO) {
$moduleSettings = $conf->get_moduleSettings();
if (!empty($moduleSettings['yubiKeyUser_attributeName'][0])) {
$tfConfig->twoFactorAuthenticationSerialAttributeName = $moduleSettings['yubiKeyUser_attributeName'][0];
}
else {
$tfConfig->twoFactorAuthenticationSerialAttributeName = 'yubiKeyId';
}
}
return $tfConfig;
}
}
@ -299,7 +398,35 @@ class TwoFactorProviderService {
* @author Roland Gruber
*/
class TwoFactorConfiguration {
/**
* @var string provider id
*/
public $twoFactorAuthentication = null;
/**
* @var service URL
*/
public $twoFactorAuthenticationURL = null;
/**
* @var disable certificate check
*/
public $twoFactorAuthenticationInsecure = false;
/**
* @var client ID for API access
*/
public $twoFactorAuthenticationClientId = null;
/**
* @var secret key for API access
*/
public $twoFactorAuthenticationSecretKey = null;
/**
* @var LDAP attribute name that stores the token serials
*/
public $twoFactorAuthenticationSerialAttributeName = null;
}

252
lam/lib/3rdParty/yubico/Yubico.php vendored Normal file
View File

@ -0,0 +1,252 @@
<?php
/**
* Class for verifying Yubico One-Time-Passcodes
*
* @category Auth
* @package Auth_Yubico
* @author Simon Josefsson <simon@yubico.com>, Olov Danielson <olov@yubico.com>
* @author Roland Gruber
* @copyright 2007-2015 Yubico AB
* @copyright 2018 Roland Gruber
* @license https://opensource.org/licenses/bsd-license.php New BSD License
* @version 2.0
* @link https://www.yubico.com/
*
* Adapted for LAM.
*/
/**
* Class for verifying Yubico One-Time-Passcodes
*/
class Auth_Yubico {
/**
* Yubico client ID
*
* @var string
*/
private $clientId;
/**
* Yubico client key
*
* @var string
*/
private $clientKey;
/**
* URL part of validation server
*
* @var string
*/
private $url;
/**
* Flag whether to verify HTTPS server certificates or not.
*
* @var boolean
*/
private $httpsVerify;
/**
* Constructor
*
* Sets up the object
*
* @param string $id The client identity
* @param string $key The client MAC key
* @param string $url URL
* @param boolean $httpsverify Flag whether to use verify HTTPS
* server certificates
*/
public function __construct($id, $key, $url, $httpsverify) {
$this->clientId = $id;
$this->clientKey = base64_decode($key);
$this->httpsVerify = $httpsverify;
$this->url = $url;
}
/**
* 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
*/
private function parsePasswordOTP($str, $delim = '[:]') {
if (!preg_match("/^((.*)" . $delim . ")?(([cbdefghijklnrtuv]{0,12})([cbdefghijklnrtuv]{32}))$/i", $str, $matches)) {
/* Dvorak? */
if (!preg_match("/^((.*)" . $delim . ")?(([jxe\\.uidchtnbpygk]{0,12})([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;
}
/**
* 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 &timestamp=1 to
* get timestamp and session information
* in the response
* @throws LAMException if verification failed
*/
public function verify($token, $use_timestamp = null) {
$timeout = 10;
/* Construct parameters string */
$ret = $this->parsePasswordOTP($token);
if (!$ret) {
throw new LAMException(_('Error'), 'Could not parse Yubikey OTP');
}
$params = array(
'id' => $this->clientId,
'otp' => $ret['otp'],
'nonce' => md5(uniqid(getRandomNumber()))
);
/* Take care of protocol version 2 parameters */
if ($use_timestamp) {
$params['timestamp'] = 1;
}
$params['timeout'] = $timeout;
ksort($params);
$parameters = '';
foreach ($params as $p => $v) {
$parameters .= "&" . $p . "=" . $v;
}
$parameters = ltrim($parameters, "&");
/* Generate signature. */
if ($this->clientKey != "") {
$signature = base64_encode(hash_hmac('sha1', $parameters, $this->clientKey, true));
$signature = preg_replace('/\+/', '%2B', $signature);
$parameters .= '&h=' . $signature;
}
$query = $this->url . "?" . $parameters;
logNewMessage(LOG_DEBUG, 'Yubico url: ' . $query);
$handle = curl_init($query);
curl_setopt($handle, CURLOPT_USERAGENT, "LAM 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);
curl_setopt($handle, CURLOPT_TIMEOUT, $timeout);
/* Execute and read request. */
$this->response = null;
$str = curl_exec($handle);
$httpCode = curl_getinfo($handle, CURLINFO_HTTP_CODE);
curl_close($handle);
logNewMessage(LOG_DEBUG, 'Server answer: ' . $str);
if (is_string($str) && ($httpCode == 200) && 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/REPLAYED_OTP/BAD_OTP.
*
* 3. Return if status=OK or status=REPLAYED_OTP.
*/
if (!preg_match("/otp=" . $params['otp'] . "/", $str) || !preg_match("/nonce=" . $params['nonce'] . "/", $str)) {
if ($status == 'BAD_OTP') {
throw new LAMException(_('Error'), 'OTP not accepted. Maybe key is not registered.');
}
throw new LAMException(_('Error'), 'Invalid answer ' . $str);
}
elseif ($this->clientKey != "") {
/* Case 2. Verify signature first */
$rows = explode("\r\n", trim($str));
$response = array();
foreach ($rows as $val) {
/*
* '=' 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->clientKey, true));
if ($response['h'] == $checksignature) {
$this->checkStatus($status);
return;
}
else {
throw new LAMException(_('Error'), 'Invalid signature, expected ' . $checksignature);
}
}
else {
/* Case 3. We check the status directly */
$this->checkStatus($status);
return;
}
}
throw new LAMException(_('Error'), 'Call to verification service failed with ' . $httpCode);
}
/**
* Checks if the status is ok.
*
* @param string $status status
* @throws LAMException invalid status
*/
private function checkStatus($status) {
if ($status == 'REPLAYED_OTP') {
throw new LAMException(_('Error'), 'OTP replay detected.');
}
elseif ($status == 'BAD_OTP') {
throw new LAMException(_('Error'), 'OTP not accepted. Maybe key is not registered.');
}
elseif ($status == 'OK') {
return;
}
throw new LAMException(_('Error'), 'Invalid status: ' . $status);
}
}
?>

View File

@ -591,6 +591,8 @@ class LAMConfig {
private $twoFactorAuthentication = TwoFactorProviderService::TWO_FACTOR_NONE;
private $twoFactorAuthenticationURL = 'https://localhost';
private $twoFactorAuthenticationClientId = null;
private $twoFactorAuthenticationSecretKey = null;
private $twoFactorAuthenticationInsecure = false;
private $twoFactorAuthenticationLabel = null;
private $twoFactorAuthenticationOptional = false;
@ -607,7 +609,7 @@ class LAMConfig {
'pwdResetAllowScreenPassword', 'pwdResetForcePasswordChange', 'pwdResetDefaultPasswordOutput',
'scriptUserName', 'scriptSSHKey', 'scriptSSHKeyPassword', 'twoFactorAuthentication', 'twoFactorAuthenticationURL',
'twoFactorAuthenticationInsecure', 'twoFactorAuthenticationLabel', 'twoFactorAuthenticationOptional',
'twoFactorAuthenticationCaption', 'referentialIntegrityOverlay'
'twoFactorAuthenticationCaption', 'twoFactorAuthenticationClientId', 'twoFactorAuthenticationSecretKey', 'referentialIntegrityOverlay'
);
@ -867,6 +869,8 @@ class LAMConfig {
if (!in_array("pwdResetDefaultPasswordOutput", $saved)) array_push($file_array, "\n" . "pwdResetDefaultPasswordOutput: " . $this->pwdResetDefaultPasswordOutput . "\n");
if (!in_array("twoFactorAuthentication", $saved)) array_push($file_array, "\n" . "twoFactorAuthentication: " . $this->twoFactorAuthentication . "\n");
if (!in_array("twoFactorAuthenticationURL", $saved)) array_push($file_array, "\n" . "twoFactorAuthenticationURL: " . $this->twoFactorAuthenticationURL . "\n");
if (!in_array("twoFactorAuthenticationClientId", $saved)) array_push($file_array, "\n" . "twoFactorAuthenticationClientId: " . $this->twoFactorAuthenticationClientId . "\n");
if (!in_array("twoFactorAuthenticationSecretKey", $saved)) array_push($file_array, "\n" . "twoFactorAuthenticationSecretKey: " . $this->twoFactorAuthenticationSecretKey . "\n");
if (!in_array("twoFactorAuthenticationInsecure", $saved)) array_push($file_array, "\n" . "twoFactorAuthenticationInsecure: " . $this->twoFactorAuthenticationInsecure . "\n");
if (!in_array("twoFactorAuthenticationLabel", $saved)) array_push($file_array, "\n" . "twoFactorAuthenticationLabel: " . $this->twoFactorAuthenticationLabel . "\n");
if (!in_array("twoFactorAuthenticationOptional", $saved)) array_push($file_array, "\n" . "twoFactorAuthenticationOptional: " . $this->twoFactorAuthenticationOptional . "\n");
@ -2151,7 +2155,7 @@ class LAMConfig {
/**
* Returns the authentication URL.
*
* @return string $twoFactorAuthenticationURL authentication URL
* @return string authentication URL
*/
public function getTwoFactorAuthenticationURL() {
return $this->twoFactorAuthenticationURL;
@ -2166,10 +2170,46 @@ class LAMConfig {
$this->twoFactorAuthenticationURL = $twoFactorAuthenticationURL;
}
/**
* Sets the client id.
*
* @param string $clientId client id
*/
public function setTwoFactorAuthenticationClientId($clientId) {
$this->twoFactorAuthenticationClientId = $clientId;
}
/**
* Returns the client id.
*
* @return string client id
*/
public function getTwoFactorAuthenticationClientId() {
return $this->twoFactorAuthenticationClientId;
}
/**
* Sets the secret key.
*
* @param string $secretKey secret key
*/
public function setTwoFactorAuthenticationSecretKey($secretKey) {
$this->twoFactorAuthenticationSecretKey = $secretKey;
}
/**
* Returns the secret key.
*
* @return string secret key
*/
public function getTwoFactorAuthenticationSecretKey() {
return $this->twoFactorAuthenticationSecretKey;
}
/**
* Returns if SSL certificate verification is turned off.
*
* @return bool $twoFactorAuthenticationInsecure SSL certificate verification is turned off
* @return bool SSL certificate verification is turned off
*/
public function getTwoFactorAuthenticationInsecure() {
return $this->twoFactorAuthenticationInsecure;
@ -2187,7 +2227,7 @@ class LAMConfig {
/**
* Returns the authentication label.
*
* @return string $twoFactorAuthenticationLabel authentication label
* @return string authentication label
*/
public function getTwoFactorAuthenticationLabel() {
return $this->twoFactorAuthenticationLabel;
@ -2205,7 +2245,7 @@ class LAMConfig {
/**
* Returns if 2nd factor is optional.
*
* @return bool $twoFactorAuthenticationOptional 2nd factor is optional
* @return bool 2nd factor is optional
*/
public function getTwoFactorAuthenticationOptional() {
return $this->twoFactorAuthenticationOptional;
@ -2223,7 +2263,7 @@ class LAMConfig {
/**
* Returns the caption HTML.
*
* @return string $twoFactorAuthenticationCaption caption HTML
* @return string caption HTML
*/
public function getTwoFactorAuthenticationCaption() {
return $this->twoFactorAuthenticationCaption;

View File

@ -3,7 +3,7 @@
$Id$
This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/)
Copyright (C) 2003 - 2017 Roland Gruber
Copyright (C) 2003 - 2018 Roland Gruber
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
@ -57,7 +57,7 @@ class Ldap{
*
* @param object $config an object of class Config
*/
function __construct($config) {
public function __construct($config) {
if (is_object($config)) {
$this->conf = $config;
}
@ -75,7 +75,7 @@ class Ldap{
* @param boolean $allowAnonymous specifies if anonymous binds are allowed
* @return mixed if connect succeeds the 0 is returned, else false or error number
*/
function connect($user, $passwd, $allowAnonymous=false) {
public function connect($user, $passwd, $allowAnonymous=false) {
// close any prior connection
@$this->close();
// do not allow anonymous bind
@ -105,7 +105,7 @@ class Ldap{
}
/** Closes connection to server */
function close() {
public function close() {
if ($this->server != null) {
@ldap_close($this->server);
}
@ -116,7 +116,7 @@ class Ldap{
*
* @return object connection handle
*/
function server() {
public function server() {
if (!$this->is_connected) {
$data = $this->decrypt_login();
$this->connect($data[0], $data[1]);
@ -126,14 +126,14 @@ class Ldap{
}
/** Closes connection to LDAP server before serialization */
function __sleep() {
public function __sleep() {
$this->close();
// define which attributes to save
return array("conf", "username", "password");
}
/** Reconnects to LDAP server when deserialized */
function __wakeup() {
public function __wakeup() {
$this->is_connected = false;
// delete PDF files and images which are older than 15 min
$tmpDir = dirname(__FILE__) . '/../tmp/';
@ -173,7 +173,7 @@ class Ldap{
* @param string $username LDAP user name
* @param string $password LDAP password
*/
function encrypt_login($username, $password) {
public function encrypt_login($username, $password) {
// encrypt username and password
$this->username = base64_encode(lamEncrypt($username));
$this->password = base64_encode(lamEncrypt($password));
@ -184,7 +184,7 @@ class Ldap{
*
* @return array array(user name, password)
*/
function decrypt_login() {
public function decrypt_login() {
// decrypt username and password
$username = lamDecrypt(base64_decode($this->username));
$password = lamDecrypt(base64_decode($this->password));
@ -192,8 +192,26 @@ class Ldap{
return $ret;
}
/**
* Returns the LDAP user name.
*
* @return string user name
*/
public function getUserName() {
return lamDecrypt(base64_decode($this->username));
}
/**
* Returns the LDAP password.
*
* @return string password
*/
public function getPassword() {
return lamDecrypt(base64_decode($this->password));
}
/** Closes connection to LDAP server and deletes encrypted username/password */
function destroy() {
public function destroy() {
$this->close();
$this->username="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
$this->password="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";

View File

@ -5,7 +5,7 @@ use \LAM\PDF\PDFTableRow;
/*
This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/)
Copyright (C) 2018 Roland Gruber
Copyright (C) 2018 - 2019 Roland Gruber
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
@ -308,7 +308,7 @@ class yubiKeyUser extends baseModule {
for ($i = 0; $i < sizeof($keys); $i++) {
$group = new htmlGroup();
$keyInput = new htmlInputField('yubiKeyId_' . $i, $keys[$i]);
$keyInput->setFieldMaxLength(16384);
$keyInput->setFieldMaxLength(12);
$group->addElement($keyInput);
$delLink = new htmlLink('', '#', '../../graphics/del.png');
$delLink->setTitle(_('Delete'));

View File

@ -284,7 +284,7 @@ function installPDFTemplates() {
$entry = $templateDir->read();
while ($entry){
$path = $basePath . '/logos/' . $entry;
if ((strpos($entry, '.') !== 1) && !is_file($path)) {
if ((strpos($entry, '.') !== 0) && !is_file($path)) {
$template = $templatePath . '/' . $entry;
logNewMessage(LOG_DEBUG, 'Copy template ' . $template . ' to ' . $path);
@copy($template, $path);

View File

@ -3,7 +3,7 @@ use \LAM\LIB\TWO_FACTOR\TwoFactorProviderService;
/*
This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/)
Copyright (C) 2006 - 2018 Roland Gruber
Copyright (C) 2006 - 2019 Roland Gruber
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
@ -300,6 +300,38 @@ function isSelfService() {
return session_name() == 'SELFSERVICE';
}
/**
* Opens the LDAP connection and returns the handle. No bind is done.
*
* @param selfServiceProfile $profile profile
* @return handle LDAP handle or null if connection failed
*/
function openSelfServiceLdapConnection($profile) {
$server = connectToLDAP($profile->serverURL, $profile->useTLS);
if ($server != null) {
// follow referrals
ldap_set_option($server, LDAP_OPT_REFERRALS, $profile->followReferrals);
}
return $server;
}
/**
* Binds the LDAP connections with given user and password.
*
* @param handle $handle LDAP handle
* @param selfServiceProfile profile
* @param string $userDn bind DN
* @param string $password bind password
* @return boolean binding successful
*/
function bindLdapUser($handle, $profile, $userDn, $password) {
if ($profile->useForAllOperations) {
$userDn = $profile->LDAPUser;
$password = deobfuscateText($profile->LDAPPassword);
}
return @ldap_bind($handle, $userDn, $password);
}
/**
* Includes all settings of a self service profile.
@ -391,6 +423,8 @@ class selfServiceProfile {
public $twoFactorAuthenticationLabel = null;
public $twoFactorAuthenticationOptional = false;
public $twoFactorAuthenticationCaption = '';
public $twoFactorAuthenticationClientId = '';
public $twoFactorAuthenticationSecretKey = '';
/** provider for captcha (-/google) */
public $captchaProvider = '-';
@ -445,6 +479,8 @@ class selfServiceProfile {
$this->twoFactorAuthenticationLabel = null;
$this->twoFactorAuthenticationOptional = false;
$this->twoFactorAuthenticationCaption = '';
$this->twoFactorAuthenticationClientId = '';
$this->twoFactorAuthenticationSecretKey = '';
$this->captchaProvider = '-';
$this->reCaptchaSiteKey = '';
$this->reCaptchaSecretKey = '';

View File

@ -22,7 +22,7 @@ use \htmlGroup;
/*
This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/)
Copyright (C) 2003 - 2017 Roland Gruber
Copyright (C) 2003 - 2018 Roland Gruber
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
@ -460,19 +460,29 @@ if (extension_loaded('curl')) {
$twoFactorOptions = array(
_('None') => TwoFactorProviderService::TWO_FACTOR_NONE,
'privacyIDEA' => TwoFactorProviderService::TWO_FACTOR_PRIVACYIDEA,
'YubiKey' => TwoFactorProviderService::TWO_FACTOR_YUBICO,
);
$twoFactorSelect = new htmlResponsiveSelect('twoFactor', $twoFactorOptions, array($conf->getTwoFactorAuthentication()), _('Provider'), '514');
$twoFactorSelect->setHasDescriptiveElements(true);
$twoFactorSelect->setTableRowsToHide(array(
TwoFactorProviderService::TWO_FACTOR_NONE => array('twoFactorURL', 'twoFactorInsecure', 'twoFactorLabel', 'twoFactorOptional', 'twoFactorCaption')
TwoFactorProviderService::TWO_FACTOR_NONE => array('twoFactorURL', 'twoFactorInsecure', 'twoFactorLabel',
'twoFactorOptional', 'twoFactorCaption', 'twoFactorClientId', 'twoFactorSecretKey'),
TwoFactorProviderService::TWO_FACTOR_PRIVACYIDEA => array('twoFactorClientId', 'twoFactorSecretKey')
));
$twoFactorSelect->setTableRowsToShow(array(
TwoFactorProviderService::TWO_FACTOR_PRIVACYIDEA => array('twoFactorURL', 'twoFactorInsecure', 'twoFactorLabel', 'twoFactorOptional', 'twoFactorCaption')
TwoFactorProviderService::TWO_FACTOR_PRIVACYIDEA => array('twoFactorURL', 'twoFactorInsecure', 'twoFactorLabel',
'twoFactorOptional', 'twoFactorCaption'),
TwoFactorProviderService::TWO_FACTOR_YUBICO => array('twoFactorURL', 'twoFactorInsecure', 'twoFactorLabel',
'twoFactorOptional', 'twoFactorCaption', 'twoFactorClientId', 'twoFactorSecretKey'),
));
$row->add($twoFactorSelect, 12);
$twoFactorUrl = new htmlResponsiveInputField(_("Base URL"), 'twoFactorURL', $conf->getTwoFactorAuthenticationURL(), '515');
$twoFactorUrl->setRequired(true);
$row->add($twoFactorUrl, 12);
$twoFactorClientId = new htmlResponsiveInputField(_("Client id"), 'twoFactorClientId', $conf->getTwoFactorAuthenticationClientId(), '524');
$row->add($twoFactorClientId, 12);
$twoFactorSecretKey = new htmlResponsiveInputField(_("Secret key"), 'twoFactorSecretKey', $conf->getTwoFactorAuthenticationSecretKey(), '525');
$row->add($twoFactorSecretKey, 12);
$twoFactorLabel = new htmlResponsiveInputField(_("Label"), 'twoFactorLabel', $conf->getTwoFactorAuthenticationLabel(), '517');
$row->add($twoFactorLabel, 12);
$row->add(new htmlResponsiveInputCheckbox('twoFactorOptional', $conf->getTwoFactorAuthenticationOptional(), _('Optional'), '519'), 12);
@ -677,6 +687,8 @@ function checkInput() {
if (extension_loaded('curl')) {
$conf->setTwoFactorAuthentication($_POST['twoFactor']);
$conf->setTwoFactorAuthenticationURL($_POST['twoFactorURL']);
$conf->setTwoFactorAuthenticationClientId($_POST['twoFactorClientId']);
$conf->setTwoFactorAuthenticationSecretKey($_POST['twoFactorSecretKey']);
$conf->setTwoFactorAuthenticationInsecure(isset($_POST['twoFactorInsecure']) && ($_POST['twoFactorInsecure'] == 'on'));
$conf->setTwoFactorAuthenticationLabel($_POST['twoFactorLabel']);
$conf->setTwoFactorAuthenticationOptional(isset($_POST['twoFactorOptional']) && ($_POST['twoFactorOptional'] == 'on'));

View File

@ -499,6 +499,94 @@ class LAMConfigTest extends PHPUnit_Framework_TestCase {
$this->assertEquals($val, $this->lAMConfig->getHttpAuthentication());
}
/**
* Tests LAMConfig->getTwoFactorAuthentication() and LAMConfig->setTwoFactorAuthentication()
*/
public function testTwoFactorAuthentication() {
$val = '2fid';
$this->lAMConfig->setTwoFactorAuthentication($val);
$this->assertEquals($val, $this->lAMConfig->getTwoFactorAuthentication());
$this->doSave();
$this->assertEquals($val, $this->lAMConfig->getTwoFactorAuthentication());
}
/**
* Tests LAMConfig->getTwoFactorAuthenticationURL() and LAMConfig->setTwoFactorAuthenticationURL()
*/
public function testTwoFactorAuthenticationURL() {
$val = 'http://example.com';
$this->lAMConfig->setTwoFactorAuthenticationURL($val);
$this->assertEquals($val, $this->lAMConfig->getTwoFactorAuthenticationURL());
$this->doSave();
$this->assertEquals($val, $this->lAMConfig->getTwoFactorAuthenticationURL());
}
/**
* Tests LAMConfig->getTwoFactorAuthenticationClientId() and LAMConfig->setTwoFactorAuthenticationClientId()
*/
public function testTwoFactorAuthenticationClientId() {
$val = '1234';
$this->lAMConfig->setTwoFactorAuthenticationClientId($val);
$this->assertEquals($val, $this->lAMConfig->getTwoFactorAuthenticationClientId());
$this->doSave();
$this->assertEquals($val, $this->lAMConfig->getTwoFactorAuthenticationClientId());
}
/**
* Tests LAMConfig->getTwoFactorAuthenticationSecretKey() and LAMConfig->setTwoFactorAuthenticationSecretKey()
*/
public function testTwoFactorAuthenticationSecretKey() {
$val = '3333key';
$this->lAMConfig->setTwoFactorAuthenticationSecretKey($val);
$this->assertEquals($val, $this->lAMConfig->getTwoFactorAuthenticationSecretKey());
$this->doSave();
$this->assertEquals($val, $this->lAMConfig->getTwoFactorAuthenticationSecretKey());
}
/**
* Tests LAMConfig->getTwoFactorAuthenticationInsecure() and LAMConfig->setTwoFactorAuthenticationInsecure()
*/
public function testTwoFactorAuthenticationInsecure() {
$val = true;
$this->lAMConfig->setTwoFactorAuthenticationInsecure($val);
$this->assertEquals($val, $this->lAMConfig->getTwoFactorAuthenticationInsecure());
$this->doSave();
$this->assertEquals($val, $this->lAMConfig->getTwoFactorAuthenticationInsecure());
}
/**
* Tests LAMConfig->getTwoFactorAuthenticationLabel() and LAMConfig->setTwoFactorAuthenticationLabel()
*/
public function testTwoFactorAuthenticationLabel() {
$val = '2falabel';
$this->lAMConfig->setTwoFactorAuthenticationLabel($val);
$this->assertEquals($val, $this->lAMConfig->getTwoFactorAuthenticationLabel());
$this->doSave();
$this->assertEquals($val, $this->lAMConfig->getTwoFactorAuthenticationLabel());
}
/**
* Tests LAMConfig->getTwoFactorAuthenticationOptional() and LAMConfig->setTwoFactorAuthenticationOptional()
*/
public function testTwoFactorAuthenticationOptional() {
$val = true;
$this->lAMConfig->setTwoFactorAuthenticationOptional($val);
$this->assertEquals($val, $this->lAMConfig->getTwoFactorAuthenticationOptional());
$this->doSave();
$this->assertEquals($val, $this->lAMConfig->getTwoFactorAuthenticationOptional());
}
/**
* Tests LAMConfig->getTwoFactorAuthenticationCaption() and LAMConfig->setTwoFactorAuthenticationCaption()
*/
public function testTwoFactorAuthenticationCaption() {
$val = '2facaption';
$this->lAMConfig->setTwoFactorAuthenticationCaption($val);
$this->assertEquals($val, $this->lAMConfig->getTwoFactorAuthenticationCaption());
$this->doSave();
$this->assertEquals($val, $this->lAMConfig->getTwoFactorAuthenticationCaption());
}
/**
* Tests LAMConfig->getLamProMailFrom() and LAMConfig->setLamProMailFrom()
*/