diff --git a/src/Application/Model/Exceptions/d3totp_wrongOtpException.php b/src/Application/Model/Exceptions/d3totp_wrongOtpException.php index ad7beb7..55d07a1 100644 --- a/src/Application/Model/Exceptions/d3totp_wrongOtpException.php +++ b/src/Application/Model/Exceptions/d3totp_wrongOtpException.php @@ -1,32 +1,42 @@ - - * @link http://www.oxidmodule.com - */ - -namespace D3\Totp\Application\Model\Exceptions; - -use D3\ModCfg\Application\Model\DependencyInjectionContainer\d3DicHandler; -use D3\ModCfg\Application\Model\Exception\d3_cfg_mod_exception; -use D3\ModCfg\Application\Model\Exception\d3ShopCompatibilityAdapterException; -use D3\ModCfg\Application\Model\Log\d3log; -use Doctrine\DBAL\DBALException; -use OxidEsales\Eshop\Core\Exception\DatabaseConnectionException; -use OxidEsales\Eshop\Core\Exception\DatabaseErrorException; -use OxidEsales\Eshop\Core\Exception\StandardException; - -class d3totp_wrongOtpException extends StandardException -{ - -} + + * @link http://www.oxidmodule.com + */ + +namespace D3\Totp\Application\Model\Exceptions; + +use D3\ModCfg\Application\Model\DependencyInjectionContainer\d3DicHandler; +use D3\ModCfg\Application\Model\Exception\d3_cfg_mod_exception; +use D3\ModCfg\Application\Model\Exception\d3ShopCompatibilityAdapterException; +use D3\ModCfg\Application\Model\Log\d3log; +use Doctrine\DBAL\DBALException; +use OxidEsales\Eshop\Core\Exception\DatabaseConnectionException; +use OxidEsales\Eshop\Core\Exception\DatabaseErrorException; +use OxidEsales\Eshop\Core\Exception\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); + } +} diff --git a/src/Application/Model/d3totp.php b/src/Application/Model/d3totp.php index 81db88a..79ae550 100644 --- a/src/Application/Model/d3totp.php +++ b/src/Application/Model/d3totp.php @@ -1,237 +1,237 @@ - - * @link http://www.oxidmodule.com - */ - -namespace D3\Totp\Application\Model; - -use D3\ModCfg\Application\Model\d3database; -use D3\Totp\Application\Model\Exceptions\d3totp_wrongOtpException; -use Doctrine\DBAL\DBALException; -use OTPHP\TOTP; -use OxidEsales\Eshop\Application\Model\User; -use OxidEsales\Eshop\Core\DatabaseProvider; -use OxidEsales\Eshop\Core\Exception\DatabaseConnectionException; -use OxidEsales\Eshop\Core\Model\BaseModel; -use OxidEsales\Eshop\Core\Registry; - -class d3totp extends BaseModel -{ - const TOTP_SESSION_VARNAME = 'totp_auth'; - - public $tableName = 'd3totp'; - public $userId; - public $totp; - - /** - * d3totp constructor. - */ - public function __construct() - { - $this->init($this->tableName); - - return parent::__construct(); - } - - /** - * @param $userId - * @return bool - * @throws DBALException - * @throws DatabaseConnectionException - */ - public function loadByUserId($userId) - { - $this->userId = $userId; - $oQB = d3database::getInstance()->getQueryBuilder(); - - if (DatabaseProvider::getDb(DatabaseProvider::FETCH_MODE_ASSOC)->getOne("SHOW TABLES LIKE 'd3totp'")) { - $oQB->select('oxid') - ->from($this->getViewName()) - ->where("oxuserid = " . $oQB->createNamedParameter($userId)) - ->setMaxResults(1); - - return $this->load(DatabaseProvider::getDb(DatabaseProvider::FETCH_MODE_ASSOC)->getOne($oQB->getSQL(), $oQB->getParameters())); - } - - return false; - } - - /** - * @return User - */ - public function getUser() - { - $userId = $this->userId ? $this->userId : $this->getFieldData('oxuserid'); - - $user = oxNew(User::class); - $user->load($userId); - return $user; - } - - - - /** - * @param $userId - * @return bool - */ - public function UserUseTotp() - { - return $this->getFieldData('usetotp') - && $this->getFieldData('seed'); - } - - /** - * @param $userId - * @return string - */ - public function getSavedSecret() - { - $seed_enc = $this->getFieldData('seed'); - $sPwd = Registry::getSession()->getVariable('pwdTransmit'); - - if ($seed_enc && $sPwd) { - $seed = $this->decrypt($seed_enc, $sPwd); - if ($seed) { - return $seed; - } - } - - return null; - } - - /** - * @param $seed - * @return TOTP - */ - public function getTotp($seed = null) - { - if (false == $this->totp) { - - if ($this->getTotpLibVersion() == 8) { // version 0.8 (< PHP 7.1) - $this->totp = oxNew( - TOTP::class, - $this->getUser()->getFieldData('oxusername') - ? $this->getUser()->getFieldData('oxusername') - : null, - $seed - ? $seed - : $this->getSavedSecret() - ); - } else { // version 0.9 (>= PHP 7.1) - $this->totp = TOTP::create($seed ? $seed : $this->getSavedSecret()); - $this->totp->setLabel($this->getUser()->getFieldData('oxusername') - ? $this->getUser()->getFieldData('oxusername') - : null - ); - } - $this->totp->setIssuer(Registry::getConfig()->getActiveShop()->getFieldData('oxname')); - } - - return $this->totp; - } - - /** - * @return int - */ - public function getTotpLibVersion() - { - return method_exists(TOTP::class, 'create') ? - 9 : - 8; - } - - /** - * @return string - */ - public function getQrCodeUri() - { - return $this->getTotp()->getQrCodeUri(); - } - - /** - * @return string - */ - public function getSecret() - { - return trim($this->getTotp()->getSecret()); - } - - /** - * @param $seed - * @param $key - */ - public function saveSecret($seed, $key) - { - $this->assign( - array( - 'seed' => $this->encrypt($seed, $key) - ) - ); - } - - /** - * @param $totp - * @param $seed - * @return string - * @throws d3totp_wrongOtpException - */ - public function verify($totp, $seed = null) - { - $blVerify = $this->getTotp($seed)->verify($totp, null, 2); - if (false == $blVerify) { - $oException = oxNew(d3totp_wrongOtpException::class, 'D3_TOTP_ERROR_UNVALID'); - throw $oException; - } - - return $blVerify; - } - - /** - * $key should have previously been generated in a cryptographically secure manner, e.g. via openssl_random_pseudo_bytes - * - * @param $plaintext - * @param $key - * @return string - */ - public function encrypt($plaintext, $key) - { - $ivlen = openssl_cipher_iv_length($cipher="AES-128-CBC"); - $iv = openssl_random_pseudo_bytes($ivlen); - $ciphertext_raw = openssl_encrypt($plaintext, $cipher, $key, $options=OPENSSL_RAW_DATA, $iv); - $hmac = hash_hmac('sha256', $ciphertext_raw, $key, $as_binary=true); - 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 - * - * @param $ciphertext - * @param $key - * @return bool|string - */ - public function decrypt($ciphertext, $key) - { - $c = base64_decode($ciphertext); - $ivlen = openssl_cipher_iv_length($cipher="AES-128-CBC"); - $iv = substr($c, 0, $ivlen); - $hmac = substr($c, $ivlen, $sha2len=32); - $ciphertext_raw = substr($c, $ivlen+$sha2len); - $original_plaintext = openssl_decrypt($ciphertext_raw, $cipher, $key, $options=OPENSSL_RAW_DATA, $iv); - $calcmac = hash_hmac('sha256', $ciphertext_raw, $key, $as_binary=true); - if (hash_equals($hmac, $calcmac)) { // PHP 5.6+ compute attack-safe comparison - return $original_plaintext; - } - - return false; - } + + * @link http://www.oxidmodule.com + */ + +namespace D3\Totp\Application\Model; + +use D3\ModCfg\Application\Model\d3database; +use D3\Totp\Application\Model\Exceptions\d3totp_wrongOtpException; +use Doctrine\DBAL\DBALException; +use OTPHP\TOTP; +use OxidEsales\Eshop\Application\Model\User; +use OxidEsales\Eshop\Core\DatabaseProvider; +use OxidEsales\Eshop\Core\Exception\DatabaseConnectionException; +use OxidEsales\Eshop\Core\Model\BaseModel; +use OxidEsales\Eshop\Core\Registry; + +class d3totp extends BaseModel +{ + const TOTP_SESSION_VARNAME = 'totp_auth'; + + public $tableName = 'd3totp'; + public $userId; + public $totp; + + /** + * d3totp constructor. + */ + public function __construct() + { + $this->init($this->tableName); + + return parent::__construct(); + } + + /** + * @param $userId + * @return bool + * @throws DBALException + * @throws DatabaseConnectionException + */ + public function loadByUserId($userId) + { + $this->userId = $userId; + $oQB = d3database::getInstance()->getQueryBuilder(); + + if (DatabaseProvider::getDb(DatabaseProvider::FETCH_MODE_ASSOC)->getOne("SHOW TABLES LIKE '".$this->tableName."'")) { + $oQB->select('oxid') + ->from($this->getViewName()) + ->where("oxuserid = " . $oQB->createNamedParameter($userId)) + ->setMaxResults(1); + + return $this->load(DatabaseProvider::getDb(DatabaseProvider::FETCH_MODE_ASSOC)->getOne($oQB->getSQL(), $oQB->getParameters())); + } + + return false; + } + + /** + * @return User + */ + public function getUser() + { + $userId = $this->userId ? $this->userId : $this->getFieldData('oxuserid'); + + $user = oxNew(User::class); + $user->load($userId); + return $user; + } + + + + /** + * @param $userId + * @return bool + */ + public function UserUseTotp() + { + return $this->getFieldData('usetotp') + && $this->getFieldData('seed'); + } + + /** + * @param $userId + * @return string + */ + public function getSavedSecret() + { + $seed_enc = $this->getFieldData('seed'); + $sPwd = Registry::getSession()->getVariable('pwdTransmit'); + + if ($seed_enc && $sPwd) { + $seed = $this->decrypt($seed_enc, $sPwd); + if ($seed) { + return $seed; + } + } + + return null; + } + + /** + * @param $seed + * @return TOTP + */ + public function getTotp($seed = null) + { + if (false == $this->totp) { + + if ($this->getTotpLibVersion() == 8) { // version 0.8 (< PHP 7.1) + $this->totp = oxNew( + TOTP::class, + $this->getUser()->getFieldData('oxusername') + ? $this->getUser()->getFieldData('oxusername') + : null, + $seed + ? $seed + : $this->getSavedSecret() + ); + } else { // version 0.9 (>= PHP 7.1) + $this->totp = TOTP::create($seed ? $seed : $this->getSavedSecret()); + $this->totp->setLabel($this->getUser()->getFieldData('oxusername') + ? $this->getUser()->getFieldData('oxusername') + : null + ); + } + $this->totp->setIssuer(Registry::getConfig()->getActiveShop()->getFieldData('oxname')); + } + + return $this->totp; + } + + /** + * @return int + */ + public function getTotpLibVersion() + { + return method_exists(TOTP::class, 'create') ? + 9 : + 8; + } + + /** + * @return string + */ + public function getQrCodeUri() + { + return $this->getTotp()->getQrCodeUri(); + } + + /** + * @return string + */ + public function getSecret() + { + return trim($this->getTotp()->getSecret()); + } + + /** + * @param $seed + * @param $key + */ + public function saveSecret($seed, $key) + { + $this->assign( + array( + 'seed' => $this->encrypt($seed, $key) + ) + ); + } + + /** + * @param $totp + * @param $seed + * @return string + * @throws d3totp_wrongOtpException + */ + public function verify($totp, $seed = null) + { + $blVerify = $this->getTotp($seed)->verify($totp, null, 2); + if (false == $blVerify) { + $oException = oxNew(d3totp_wrongOtpException::class); + throw $oException; + } + + return $blVerify; + } + + /** + * $key should have previously been generated in a cryptographically secure manner, e.g. via openssl_random_pseudo_bytes + * + * @param $plaintext + * @param $key + * @return string + */ + public function encrypt($plaintext, $key) + { + $ivlen = openssl_cipher_iv_length($cipher="AES-128-CBC"); + $iv = openssl_random_pseudo_bytes($ivlen); + $ciphertext_raw = openssl_encrypt($plaintext, $cipher, $key, $options=OPENSSL_RAW_DATA, $iv); + $hmac = hash_hmac('sha256', $ciphertext_raw, $key, $as_binary=true); + 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 + * + * @param $ciphertext + * @param $key + * @return bool|string + */ + public function decrypt($ciphertext, $key) + { + $c = base64_decode($ciphertext); + $ivlen = openssl_cipher_iv_length($cipher="AES-128-CBC"); + $iv = substr($c, 0, $ivlen); + $hmac = substr($c, $ivlen, $sha2len=32); + $ciphertext_raw = substr($c, $ivlen+$sha2len); + $original_plaintext = openssl_decrypt($ciphertext_raw, $cipher, $key, $options=OPENSSL_RAW_DATA, $iv); + $calcmac = hash_hmac('sha256', $ciphertext_raw, $key, $as_binary=true); + if (hash_equals($hmac, $calcmac)) { // PHP 5.6+ compute attack-safe comparison + return $original_plaintext; + } + + return false; + } } \ No newline at end of file