* [MOD] Improved API auth security. There is no need to provide the user's password, it will ask for a token password when generating it.

This commit is contained in:
nuxsmin
2017-02-23 11:26:44 +01:00
parent 526ca3c9e9
commit adb891687d
13 changed files with 46 additions and 71 deletions

View File

@@ -29,6 +29,7 @@ defined('APP_ROOT') || die();
use Defuse\Crypto\Exception\CryptoException;
use SP\Core\Crypt\Crypt;
use SP\Core\Crypt\Hash;
use SP\Core\Crypt\Vault;
use SP\Core\Exceptions\InvalidArgumentException;
use SP\Core\Exceptions\SPException;
use SP\Core\Session;
@@ -182,9 +183,9 @@ abstract class ApiBase implements ApiInterface
protected function getMPass()
{
try {
$key = $this->getParam('pass') . $this->getParam('authToken');
$securedKey = Crypt::unlockSecuredKey($this->ApiTokenData->getAuthtokenKey(), $key);
return Crypt::decrypt($this->ApiTokenData->getAuthtokenPass(), $securedKey, $key);
/** @var Vault $Vault */
$Vault = unserialize($this->ApiTokenData->getAuthtokenVault());
return $Vault->getData($this->getParam('pass') . $this->getParam('authToken'));
} catch (CryptoException $e) {
throw new SPException(SPException::SP_ERROR, __('Error interno', false), $e->getMessage());
}

View File

@@ -511,8 +511,6 @@ class ConfigActionController implements ItemControllerInterface
return;
}
$hashMPass = Hash::hashKey($newMasterPass);
if (!$noAccountPassChange) {
Util::lockApp();
@@ -554,7 +552,7 @@ class ConfigActionController implements ItemControllerInterface
Util::unlockApp();
}
ConfigDB::setCacheConfigValue('masterPwd', $hashMPass);
ConfigDB::setCacheConfigValue('masterPwd', Hash::hashKey($newMasterPass));
ConfigDB::setCacheConfigValue('lastupdatempass', time());
$this->LogMessage->setAction(__('Actualizar Clave Maestra', false));

View File

@@ -28,7 +28,6 @@ defined('APP_ROOT') || die();
use SP\Account\AccountHistoryUtil;
use SP\Account\AccountUtil;
use SP\Api\ApiTokensUtil;
use SP\Config\Config;
use SP\Controller\Grids\Items;
use SP\Core\ActionsInterface;
@@ -36,7 +35,6 @@ use SP\Core\Exceptions\SPException;
use SP\Core\Template;
use SP\DataModel\ItemSearchData;
use SP\Http\Request;
use SP\Mgmt\ApiTokens\ApiToken;
use SP\Mgmt\ApiTokens\ApiTokenSearch;
use SP\Mgmt\Categories\CategorySearch;
use SP\Mgmt\Customers\CustomerSearch;

View File

@@ -28,7 +28,6 @@ defined('APP_ROOT') || die();
use SP\Account\AccountHistoryUtil;
use SP\Account\AccountUtil;
use SP\Api\ApiTokensUtil;
use SP\Config\Config;
use SP\Controller\Grids\Items;
use SP\Core\ActionsInterface;

View File

@@ -29,7 +29,7 @@ defined('APP_ROOT') || die();
use SP\Account\Account;
use SP\Account\AccountAcl;
use SP\Account\AccountHistory;
use SP\Api\ApiTokensUtil;
use SP\Mgmt\ApiTokens\ApiTokensUtil;
use SP\Core\ActionsInterface;
use SP\Core\Crypt\Crypt;
use SP\Core\Crypt\Session as CryptSession;
@@ -48,7 +48,6 @@ use SP\DataModel\GroupData;
use SP\DataModel\ProfileData;
use SP\DataModel\TagData;
use SP\DataModel\UserData;
use SP\DataModel\UserPassData;
use SP\Http\Request;
use SP\Log\Email;
use SP\Log\Log;
@@ -72,7 +71,6 @@ use SP\Mgmt\Users\UserUtil;
use SP\Util\Checks;
use SP\Util\ImageUtil;
use SP\Util\Json;
use SP\Util\Util;
/**
* Class AccItemMgmt

View File

@@ -54,18 +54,20 @@ class Vault
/**
* Regenerar la clave de sesión
*
* @param string $key
* @throws \Defuse\Crypto\Exception\BadFormatException
* @throws \Defuse\Crypto\Exception\CryptoException
* @throws \Defuse\Crypto\Exception\EnvironmentIsBrokenException
* @return Vault
*/
public function reKey()
public function reKey($key = null)
{
$this->timeUpdated = time();
$sessionMPass = $this->getData();
$sessionMPass = $this->getData($key);
SessionUtil::regenerate();
$this->saveData($sessionMPass);
$this->saveData($sessionMPass, $key);
return $this;
}
@@ -73,16 +75,18 @@ class Vault
/**
* Devolver la clave maestra de la sesión
*
* @param string $key
* @return string
* @throws \Defuse\Crypto\Exception\CryptoException
* @throws \Defuse\Crypto\Exception\EnvironmentIsBrokenException
* @throws \Defuse\Crypto\Exception\BadFormatException
*/
public function getData()
public function getData($key = null)
{
$securedKey = Crypt::unlockSecuredKey($this->key, $this->getKey());
$key = $key ?: $this->getKey();
$securedKey = Crypt::unlockSecuredKey($this->key, $key);
return Crypt::decrypt($this->data, $securedKey, $this->getKey());
return Crypt::decrypt($this->data, $securedKey, $key);
}
/**
@@ -99,19 +103,21 @@ class Vault
* Guardar la clave maestra en la sesión
*
* @param $data
* @param string $key
* @return $this
* @throws \Defuse\Crypto\Exception\EnvironmentIsBrokenException
* @throws \Defuse\Crypto\Exception\BadFormatException
* @throws \Defuse\Crypto\Exception\CryptoException
*/
public function saveData($data)
public function saveData($data, $key = null)
{
if ($this->timeSet === 0) {
$this->timeSet = time();
}
$this->key = Crypt::makeSecuredKey($this->getKey());
$this->data = Crypt::encrypt($data, $this->key, $this->getKey());
$key = $key ?: $this->getKey();
$this->key = Crypt::makeSecuredKey($key);
$this->data = Crypt::encrypt($data, $this->key, $key);
return $this;
}

View File

@@ -24,6 +24,8 @@
namespace SP\DataModel;
use SP\Core\Crypt\Vault;
/**
* Class ApiTokenData
*
@@ -36,13 +38,9 @@ class ApiTokenData extends DataModelBase implements DataModelInterface
*/
public $authtoken_id;
/**
* @var string
* @var Vault
*/
public $authtoken_key;
/**
* @var string
*/
public $authtoken_pass;
public $authtoken_vault;
/**
* @var int
*/
@@ -85,19 +83,19 @@ class ApiTokenData extends DataModelBase implements DataModelInterface
}
/**
* @return mixed
* @return Vault
*/
public function getAuthtokenKey()
public function getAuthtokenVault()
{
return $this->authtoken_key;
return $this->authtoken_vault;
}
/**
* @param mixed $authtoken_key
* @param Vault $authtoken_vault
*/
public function setAuthtokenKey($authtoken_key)
public function setAuthtokenVault(Vault $authtoken_vault)
{
$this->authtoken_key = $authtoken_key;
$this->authtoken_vault = $authtoken_vault;
}
/**
@@ -211,20 +209,4 @@ class ApiTokenData extends DataModelBase implements DataModelInterface
{
$this->authtoken_hash = $authtoken_hash;
}
/**
* @return string
*/
public function getAuthtokenPass()
{
return $this->authtoken_pass;
}
/**
* @param string $authtoken_pass
*/
public function setAuthtokenPass($authtoken_pass)
{
$this->authtoken_pass = $authtoken_pass;
}
}

View File

@@ -84,7 +84,7 @@ class ApiTokenForm extends FormBase implements FormInterface
throw new ValidationException(__('Usuario no indicado', false));
} elseif ($this->ApiTokenData->getAuthtokenActionId() === 0) {
throw new ValidationException(__('Acción no indicada', false));
} elseif ($this->ApiTokenData->getAuthtokenKey() === '') {
} elseif ($this->ApiTokenData->getAuthtokenHash() === '') {
throw new ValidationException(__('La clave no puede estar en blanco', false));
}
}

View File

@@ -24,9 +24,9 @@
namespace SP\Mgmt\ApiTokens;
use SP\Core\Crypt\Crypt;
use SP\Core\Crypt\Hash;
use SP\Core\Crypt\Session as CryptSession;
use SP\Core\Crypt\Vault;
use SP\Core\Exceptions\SPException;
use SP\Core\Session;
use SP\DataModel\ApiTokenData;
@@ -67,8 +67,7 @@ class ApiToken extends ApiTokenBase implements ItemInterface
authtoken_actionId = ?,
authtoken_createdBy = ?,
authtoken_token = ?,
authtoken_key = ?,
authtoken_pass = ?,
authtoken_vault = ?,
authtoken_hash = ?,
authtoken_startDate = UNIX_TIMESTAMP()';
@@ -78,8 +77,7 @@ class ApiToken extends ApiTokenBase implements ItemInterface
$Data->addParam($this->itemData->getAuthtokenActionId());
$Data->addParam(Session::getUserData()->getUserId());
$Data->addParam($token);
$Data->addParam($this->itemData->getAuthtokenKey());
$Data->addParam($this->itemData->getAuthtokenPass());
$Data->addParam(serialize($this->itemData->getAuthtokenVault()));
$Data->addParam(Hash::hashKey($this->itemData->getAuthtokenHash()));
$Data->setOnErrorMessage(__('Error interno', false));
@@ -185,8 +183,7 @@ class ApiToken extends ApiTokenBase implements ItemInterface
authtoken_actionId = ?,
authtoken_createdBy = ?,
authtoken_token = ?,
authtoken_key = ?,
authtoken_pass = ?,
authtoken_vault = ?,
authtoken_hash = ?,
authtoken_startDate = UNIX_TIMESTAMP()
WHERE authtoken_id = ? LIMIT 1';
@@ -197,8 +194,7 @@ class ApiToken extends ApiTokenBase implements ItemInterface
$Data->addParam($this->itemData->getAuthtokenActionId());
$Data->addParam(Session::getUserData()->getUserId());
$Data->addParam($token);
$Data->addParam($this->itemData->getAuthtokenKey());
$Data->addParam($this->itemData->getAuthtokenPass());
$Data->addParam(serialize($this->itemData->getAuthtokenVault()));
$Data->addParam(Hash::hashKey($this->itemData->getAuthtokenHash()));
$Data->addParam($this->itemData->getAuthtokenId());
$Data->setOnErrorMessage(__('Error interno', false));
@@ -247,7 +243,7 @@ class ApiToken extends ApiTokenBase implements ItemInterface
'UPDATE authTokens
SET authtoken_token = ?,
authtoken_hash = ?,
authtoken_key = ?,
authtoken_vault = ?,
authtoken_pass = ?,
authtoken_startDate = UNIX_TIMESTAMP()
WHERE authtoken_userId = ? LIMIT 1';
@@ -256,8 +252,7 @@ class ApiToken extends ApiTokenBase implements ItemInterface
$Data->setQuery($query);
$Data->addParam($this->generateToken());
$Data->addParam(Hash::hashKey($this->itemData->getAuthtokenHash()));
$Data->addParam($this->itemData->getAuthtokenKey());
$Data->addParam($this->itemData->getAuthtokenPass());
$Data->addParam(serialize($this->itemData->getAuthtokenVault()));
$Data->addParam($this->itemData->getAuthtokenUserId());
$Data->setOnErrorMessage(__('Error interno', false));
@@ -274,11 +269,10 @@ class ApiToken extends ApiTokenBase implements ItemInterface
*/
private function setSecureData($token)
{
$key = $this->itemData->getAuthtokenHash() . $token;
$securedKey = Crypt::makeSecuredKey($key);
$Vault = new Vault();
$Vault->saveData(CryptSession::getSessionKey(), $this->itemData->getAuthtokenHash() . $token);
$this->itemData->setAuthtokenKey($securedKey);
$this->itemData->setAuthtokenPass(Crypt::encrypt(CryptSession::getSessionKey(), $securedKey, $key));
$this->itemData->setAuthtokenVault($Vault);
}
/**

View File

@@ -22,7 +22,7 @@
* along with sysPass. If not, see <http://www.gnu.org/licenses/>.
*/
namespace SP\Api;
namespace SP\Mgmt\ApiTokens;
use SP\Core\Acl;
use SP\Core\ActionsInterface;

View File

@@ -8,7 +8,6 @@ ALTER TABLE `usrData`
CHANGE COLUMN `user_mPass` `user_mPass` VARBINARY(1000) NULL DEFAULT NULL,
CHANGE COLUMN `user_mIV` `user_mKey` VARBINARY(1000) NULL DEFAULT NULL;
ALTER TABLE `authTokens`
ADD COLUMN `authtoken_pass` VARBINARY(1000) NULL,
ADD COLUMN `authtoken_key` VARBINARY(1000) NULL,
ADD COLUMN `authtoken_vault` VARBINARY(2000) NULL,
ADD COLUMN `authtoken_hash` VARBINARY(100) NULL;

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +1,5 @@
var $jscomp={scope:{},findInternal:function(a,g,e){a instanceof String&&(a=String(a));for(var h=a.length,k=0;k<h;k++){var l=a[k];if(g.call(e,l,k,a))return{i:k,v:l}}return{i:-1,v:void 0}}};$jscomp.defineProperty="function"==typeof Object.defineProperties?Object.defineProperty:function(a,g,e){if(e.get||e.set)throw new TypeError("ES3 does not support getters and setters.");a!=Array.prototype&&a!=Object.prototype&&(a[g]=e.value)};
$jscomp.getGlobal=function(a){return"undefined"!=typeof window&&window===a?a:"undefined"!=typeof global?global:a};$jscomp.global=$jscomp.getGlobal(this);$jscomp.polyfill=function(a,g,e,h){if(g){e=$jscomp.global;a=a.split(".");for(h=0;h<a.length-1;h++){var k=a[h];k in e||(e[k]={});e=e[k]}a=a[a.length-1];h=e[a];g=g(h);g!=h&&null!=g&&$jscomp.defineProperty(e,a,{configurable:!0,writable:!0,value:g})}};
$jscomp.getGlobal=function(a){return"undefined"!=typeof window&&window===a?a:"undefined"!=typeof global&&null!=global?global:a};$jscomp.global=$jscomp.getGlobal(this);$jscomp.polyfill=function(a,g,e,h){if(g){e=$jscomp.global;a=a.split(".");for(h=0;h<a.length-1;h++){var k=a[h];k in e||(e[k]={});e=e[k]}a=a[a.length-1];h=e[a];g=g(h);g!=h&&null!=g&&$jscomp.defineProperty(e,a,{configurable:!0,writable:!0,value:g})}};
$jscomp.polyfill("Array.prototype.find",function(a){return a?a:function(a,e){return $jscomp.findInternal(this,a,e).v}},"es6-impl","es3");
sysPass.Theme=function(a){var g=a.log,e={elems:{$wrap:$("#wrap-loading"),$loading:$("#loading")},show:function(a){void 0!==a&&!0===a&&e.elems.$wrap.addClass("overlay-full");e.elems.$wrap.show();e.elems.$loading.addClass("is-active")},hide:function(){e.elems.$wrap.removeClass("overlay-full").hide();e.elems.$loading.removeClass("is-active")},upgradeFull:function(){e.elems.$wrap.addClass("overlay-full")}},h=function(b){for(var d=0,f="",c;d<a.passwordData.complexity.numlength;)c=Math.floor(100*Math.random())%
94+33,!a.passwordData.complexity.symbols&&(33<=c&&47>=c||58<=c&&64>=c||91<=c&&96>=c||123<=c&&126>=c)||!a.passwordData.complexity.numbers&&48<=c&&57>=c||!a.passwordData.complexity.uppercase&&65<=c&&90>=c||(d++,f+=String.fromCharCode(c));$("#viewPass").attr("title",f);var e=zxcvbn(f);a.passwordData.passLength=f.length;b?(d=b.parent(),c=$("#"+b.attr("id")+"R"),a.outputResult(e,b),b=new MaterialTextfield,d.find("input:password").val(f),d.addClass(b.CssClasses_.IS_DIRTY).removeClass(b.CssClasses_.IS_INVALID),