move unvalid totp message to exception

This commit is contained in:
Daniel Seifert 2018-10-20 23:39:22 +02:00
parent 644ceaeca3
commit 19b3dc7881
2 changed files with 278 additions and 268 deletions

View File

@ -1,32 +1,42 @@
<?php <?php
/** /**
* This Software is the property of Data Development and is protected * This Software is the property of Data Development and is protected
* by copyright law - it is NOT Freeware. * by copyright law - it is NOT Freeware.
* *
* Any unauthorized use of this software without a valid license * Any unauthorized use of this software without a valid license
* is a violation of the license agreement and will be prosecuted by * is a violation of the license agreement and will be prosecuted by
* civil and criminal law. * civil and criminal law.
* *
* http://www.shopmodule.com * http://www.shopmodule.com
* *
* @copyright (C) D3 Data Development (Inh. Thomas Dartsch) * @copyright (C) D3 Data Development (Inh. Thomas Dartsch)
* @author D3 Data Development - Daniel Seifert <support@shopmodule.com> * @author D3 Data Development - Daniel Seifert <support@shopmodule.com>
* @link http://www.oxidmodule.com * @link http://www.oxidmodule.com
*/ */
namespace D3\Totp\Application\Model\Exceptions; namespace D3\Totp\Application\Model\Exceptions;
use D3\ModCfg\Application\Model\DependencyInjectionContainer\d3DicHandler; use D3\ModCfg\Application\Model\DependencyInjectionContainer\d3DicHandler;
use D3\ModCfg\Application\Model\Exception\d3_cfg_mod_exception; use D3\ModCfg\Application\Model\Exception\d3_cfg_mod_exception;
use D3\ModCfg\Application\Model\Exception\d3ShopCompatibilityAdapterException; use D3\ModCfg\Application\Model\Exception\d3ShopCompatibilityAdapterException;
use D3\ModCfg\Application\Model\Log\d3log; use D3\ModCfg\Application\Model\Log\d3log;
use Doctrine\DBAL\DBALException; use Doctrine\DBAL\DBALException;
use OxidEsales\Eshop\Core\Exception\DatabaseConnectionException; use OxidEsales\Eshop\Core\Exception\DatabaseConnectionException;
use OxidEsales\Eshop\Core\Exception\DatabaseErrorException; use OxidEsales\Eshop\Core\Exception\DatabaseErrorException;
use OxidEsales\Eshop\Core\Exception\StandardException; use OxidEsales\Eshop\Core\Exception\StandardException;
class d3totp_wrongOtpException extends StandardException class d3totp_wrongOtpException extends StandardException
{ {
/**
} * Default constructor
*
* @param string $sMessage exception message
* @param integer $iCode exception code
* @param \Exception|null $previous previous exception
*/
public function __construct($sMessage = "D3_TOTP_ERROR_UNVALID", $iCode = 0, \Exception $previous = null)
{
parent::__construct($sMessage, $iCode, $previous);
}
}

View File

@ -1,237 +1,237 @@
<?php <?php
/** /**
* This Software is the property of Data Development and is protected * This Software is the property of Data Development and is protected
* by copyright law - it is NOT Freeware. * by copyright law - it is NOT Freeware.
* Any unauthorized use of this software without a valid license * Any unauthorized use of this software without a valid license
* is a violation of the license agreement and will be prosecuted by * is a violation of the license agreement and will be prosecuted by
* civil and criminal law. * civil and criminal law.
* http://www.shopmodule.com * http://www.shopmodule.com
* *
* @copyright (C) D3 Data Development (Inh. Thomas Dartsch) * @copyright (C) D3 Data Development (Inh. Thomas Dartsch)
* @author D3 Data Development - Daniel Seifert <support@shopmodule.com> * @author D3 Data Development - Daniel Seifert <support@shopmodule.com>
* @link http://www.oxidmodule.com * @link http://www.oxidmodule.com
*/ */
namespace D3\Totp\Application\Model; namespace D3\Totp\Application\Model;
use D3\ModCfg\Application\Model\d3database; use D3\ModCfg\Application\Model\d3database;
use D3\Totp\Application\Model\Exceptions\d3totp_wrongOtpException; use D3\Totp\Application\Model\Exceptions\d3totp_wrongOtpException;
use Doctrine\DBAL\DBALException; use Doctrine\DBAL\DBALException;
use OTPHP\TOTP; use OTPHP\TOTP;
use OxidEsales\Eshop\Application\Model\User; use OxidEsales\Eshop\Application\Model\User;
use OxidEsales\Eshop\Core\DatabaseProvider; use OxidEsales\Eshop\Core\DatabaseProvider;
use OxidEsales\Eshop\Core\Exception\DatabaseConnectionException; use OxidEsales\Eshop\Core\Exception\DatabaseConnectionException;
use OxidEsales\Eshop\Core\Model\BaseModel; use OxidEsales\Eshop\Core\Model\BaseModel;
use OxidEsales\Eshop\Core\Registry; use OxidEsales\Eshop\Core\Registry;
class d3totp extends BaseModel class d3totp extends BaseModel
{ {
const TOTP_SESSION_VARNAME = 'totp_auth'; const TOTP_SESSION_VARNAME = 'totp_auth';
public $tableName = 'd3totp'; public $tableName = 'd3totp';
public $userId; public $userId;
public $totp; public $totp;
/** /**
* d3totp constructor. * d3totp constructor.
*/ */
public function __construct() public function __construct()
{ {
$this->init($this->tableName); $this->init($this->tableName);
return parent::__construct(); return parent::__construct();
} }
/** /**
* @param $userId * @param $userId
* @return bool * @return bool
* @throws DBALException * @throws DBALException
* @throws DatabaseConnectionException * @throws DatabaseConnectionException
*/ */
public function loadByUserId($userId) public function loadByUserId($userId)
{ {
$this->userId = $userId; $this->userId = $userId;
$oQB = d3database::getInstance()->getQueryBuilder(); $oQB = d3database::getInstance()->getQueryBuilder();
if (DatabaseProvider::getDb(DatabaseProvider::FETCH_MODE_ASSOC)->getOne("SHOW TABLES LIKE 'd3totp'")) { if (DatabaseProvider::getDb(DatabaseProvider::FETCH_MODE_ASSOC)->getOne("SHOW TABLES LIKE '".$this->tableName."'")) {
$oQB->select('oxid') $oQB->select('oxid')
->from($this->getViewName()) ->from($this->getViewName())
->where("oxuserid = " . $oQB->createNamedParameter($userId)) ->where("oxuserid = " . $oQB->createNamedParameter($userId))
->setMaxResults(1); ->setMaxResults(1);
return $this->load(DatabaseProvider::getDb(DatabaseProvider::FETCH_MODE_ASSOC)->getOne($oQB->getSQL(), $oQB->getParameters())); return $this->load(DatabaseProvider::getDb(DatabaseProvider::FETCH_MODE_ASSOC)->getOne($oQB->getSQL(), $oQB->getParameters()));
} }
return false; return false;
} }
/** /**
* @return User * @return User
*/ */
public function getUser() public function getUser()
{ {
$userId = $this->userId ? $this->userId : $this->getFieldData('oxuserid'); $userId = $this->userId ? $this->userId : $this->getFieldData('oxuserid');
$user = oxNew(User::class); $user = oxNew(User::class);
$user->load($userId); $user->load($userId);
return $user; return $user;
} }
/** /**
* @param $userId * @param $userId
* @return bool * @return bool
*/ */
public function UserUseTotp() public function UserUseTotp()
{ {
return $this->getFieldData('usetotp') return $this->getFieldData('usetotp')
&& $this->getFieldData('seed'); && $this->getFieldData('seed');
} }
/** /**
* @param $userId * @param $userId
* @return string * @return string
*/ */
public function getSavedSecret() public function getSavedSecret()
{ {
$seed_enc = $this->getFieldData('seed'); $seed_enc = $this->getFieldData('seed');
$sPwd = Registry::getSession()->getVariable('pwdTransmit'); $sPwd = Registry::getSession()->getVariable('pwdTransmit');
if ($seed_enc && $sPwd) { if ($seed_enc && $sPwd) {
$seed = $this->decrypt($seed_enc, $sPwd); $seed = $this->decrypt($seed_enc, $sPwd);
if ($seed) { if ($seed) {
return $seed; return $seed;
} }
} }
return null; return null;
} }
/** /**
* @param $seed * @param $seed
* @return TOTP * @return TOTP
*/ */
public function getTotp($seed = null) public function getTotp($seed = null)
{ {
if (false == $this->totp) { if (false == $this->totp) {
if ($this->getTotpLibVersion() == 8) { // version 0.8 (< PHP 7.1) if ($this->getTotpLibVersion() == 8) { // version 0.8 (< PHP 7.1)
$this->totp = oxNew( $this->totp = oxNew(
TOTP::class, TOTP::class,
$this->getUser()->getFieldData('oxusername') $this->getUser()->getFieldData('oxusername')
? $this->getUser()->getFieldData('oxusername') ? $this->getUser()->getFieldData('oxusername')
: null, : null,
$seed $seed
? $seed ? $seed
: $this->getSavedSecret() : $this->getSavedSecret()
); );
} else { // version 0.9 (>= PHP 7.1) } else { // version 0.9 (>= PHP 7.1)
$this->totp = TOTP::create($seed ? $seed : $this->getSavedSecret()); $this->totp = TOTP::create($seed ? $seed : $this->getSavedSecret());
$this->totp->setLabel($this->getUser()->getFieldData('oxusername') $this->totp->setLabel($this->getUser()->getFieldData('oxusername')
? $this->getUser()->getFieldData('oxusername') ? $this->getUser()->getFieldData('oxusername')
: null : null
); );
} }
$this->totp->setIssuer(Registry::getConfig()->getActiveShop()->getFieldData('oxname')); $this->totp->setIssuer(Registry::getConfig()->getActiveShop()->getFieldData('oxname'));
} }
return $this->totp; return $this->totp;
} }
/** /**
* @return int * @return int
*/ */
public function getTotpLibVersion() public function getTotpLibVersion()
{ {
return method_exists(TOTP::class, 'create') ? return method_exists(TOTP::class, 'create') ?
9 : 9 :
8; 8;
} }
/** /**
* @return string * @return string
*/ */
public function getQrCodeUri() public function getQrCodeUri()
{ {
return $this->getTotp()->getQrCodeUri(); return $this->getTotp()->getQrCodeUri();
} }
/** /**
* @return string * @return string
*/ */
public function getSecret() public function getSecret()
{ {
return trim($this->getTotp()->getSecret()); return trim($this->getTotp()->getSecret());
} }
/** /**
* @param $seed * @param $seed
* @param $key * @param $key
*/ */
public function saveSecret($seed, $key) public function saveSecret($seed, $key)
{ {
$this->assign( $this->assign(
array( array(
'seed' => $this->encrypt($seed, $key) 'seed' => $this->encrypt($seed, $key)
) )
); );
} }
/** /**
* @param $totp * @param $totp
* @param $seed * @param $seed
* @return string * @return string
* @throws d3totp_wrongOtpException * @throws d3totp_wrongOtpException
*/ */
public function verify($totp, $seed = null) public function verify($totp, $seed = null)
{ {
$blVerify = $this->getTotp($seed)->verify($totp, null, 2); $blVerify = $this->getTotp($seed)->verify($totp, null, 2);
if (false == $blVerify) { if (false == $blVerify) {
$oException = oxNew(d3totp_wrongOtpException::class, 'D3_TOTP_ERROR_UNVALID'); $oException = oxNew(d3totp_wrongOtpException::class);
throw $oException; throw $oException;
} }
return $blVerify; return $blVerify;
} }
/** /**
* $key should have previously been generated in a cryptographically secure manner, e.g. via openssl_random_pseudo_bytes * $key should have previously been generated in a cryptographically secure manner, e.g. via openssl_random_pseudo_bytes
* *
* @param $plaintext * @param $plaintext
* @param $key * @param $key
* @return string * @return string
*/ */
public function encrypt($plaintext, $key) public function encrypt($plaintext, $key)
{ {
$ivlen = openssl_cipher_iv_length($cipher="AES-128-CBC"); $ivlen = openssl_cipher_iv_length($cipher="AES-128-CBC");
$iv = openssl_random_pseudo_bytes($ivlen); $iv = openssl_random_pseudo_bytes($ivlen);
$ciphertext_raw = openssl_encrypt($plaintext, $cipher, $key, $options=OPENSSL_RAW_DATA, $iv); $ciphertext_raw = openssl_encrypt($plaintext, $cipher, $key, $options=OPENSSL_RAW_DATA, $iv);
$hmac = hash_hmac('sha256', $ciphertext_raw, $key, $as_binary=true); $hmac = hash_hmac('sha256', $ciphertext_raw, $key, $as_binary=true);
return base64_encode($iv.$hmac.$ciphertext_raw); return base64_encode($iv.$hmac.$ciphertext_raw);
} }
/** /**
* $key should have previously been generated in a cryptographically secure manner, e.g. via openssl_random_pseudo_bytes * $key should have previously been generated in a cryptographically secure manner, e.g. via openssl_random_pseudo_bytes
* *
* @param $ciphertext * @param $ciphertext
* @param $key * @param $key
* @return bool|string * @return bool|string
*/ */
public function decrypt($ciphertext, $key) public function decrypt($ciphertext, $key)
{ {
$c = base64_decode($ciphertext); $c = base64_decode($ciphertext);
$ivlen = openssl_cipher_iv_length($cipher="AES-128-CBC"); $ivlen = openssl_cipher_iv_length($cipher="AES-128-CBC");
$iv = substr($c, 0, $ivlen); $iv = substr($c, 0, $ivlen);
$hmac = substr($c, $ivlen, $sha2len=32); $hmac = substr($c, $ivlen, $sha2len=32);
$ciphertext_raw = substr($c, $ivlen+$sha2len); $ciphertext_raw = substr($c, $ivlen+$sha2len);
$original_plaintext = openssl_decrypt($ciphertext_raw, $cipher, $key, $options=OPENSSL_RAW_DATA, $iv); $original_plaintext = openssl_decrypt($ciphertext_raw, $cipher, $key, $options=OPENSSL_RAW_DATA, $iv);
$calcmac = hash_hmac('sha256', $ciphertext_raw, $key, $as_binary=true); $calcmac = hash_hmac('sha256', $ciphertext_raw, $key, $as_binary=true);
if (hash_equals($hmac, $calcmac)) { // PHP 5.6+ compute attack-safe comparison if (hash_equals($hmac, $calcmac)) { // PHP 5.6+ compute attack-safe comparison
return $original_plaintext; return $original_plaintext;
} }
return false; return false;
} }
} }