Your IP : 3.15.4.70


Current Path : /var/www/www-root/data/www/monolith-realty.ru/bitrix/modules/main/lib/security/mfa/
Upload File :
Current File : /var/www/www-root/data/www/monolith-realty.ru/bitrix/modules/main/lib/security/mfa/otpalgorithm.php

<?php

namespace Bitrix\Main\Security\Mfa;

use Bitrix\Main\ArgumentTypeException;
use Bitrix\Main\Security\OtpException;
use Bitrix\Main\Text\Base32;

abstract class OtpAlgorithm
{
	protected static $type = 'undefined';
	protected $digest = 'sha1';
	protected $digits = 6;
	protected $secret = null;
	protected $appScheme = 'otpauth';
	protected $requireTwoCode = true;

	/**
	 * Verify provided input
	 *
	 * @param string $input Input received from user.
	 * @param string $params Synchronized user params, saved for this algorithm (see getSyncParameters).
	 * @return array [
	 *  bool isSuccess (Valid input or not),
	 *  string newParams (Updated user params for this OtpAlgorithm)
	 * ]
	 */
	abstract public function verify($input, $params = null);

	/**
	 * Return synchronized user params for provided inputs
	 *
	 * @param string $inputA First code.
	 * @param string|null $inputB Second code. Must be provided if current OtpAlgorithm required it (see isTwoCodeRequired).
	 * @return string
	 * @throws OtpException
	 */
	abstract public function getSyncParameters($inputA, $inputB);

	/**
	 * Require or not _two_ code for synchronize parameters
	 *
	 * @return bool
	 */
	public function isTwoCodeRequired()
	{
		return $this->requireTwoCode;
	}

	/**
	 * Set new secret
	 *
	 * @param string $secret Secret (binary).
	 * @return $this
	 */
	public function setSecret($secret)
	{
		$this->secret = $secret;

		// Backward compatibility. Use sha256 for eToken with 256bits key
		if (strlen($this->secret) > 25)
		{
			$this->digest = 'sha256';
		}

		return $this;
	}

	/**
	 * Return used secret (binary)
	 *
	 * @return string
	 */
	public function getSecret()
	{
		return $this->secret;
	}

	/**
	 * Generate provision URI according to KeyUriFormat
	 *
	 * @link https://code.google.com/p/google-authenticator/wiki/KeyUriFormat
	 * @param string $label User label.
	 * @param array $opts Additional URI parameters, e.g. ['image' => 'http://example.com/my_logo.png'] .
	 * @throws ArgumentTypeException
	 * @return string
	 */
	public function generateUri($label, array $opts = [])
	{
		$positionalOpts = [
			// Don't change order!
			'secret' => Base32::encode($this->getSecret()),
		];

		$opts['algorithm'] = $this->getDigest();
		// Digest must be in upper case for some OTP apps (e.g. Google Authenticator for iOS)
		$opts['algorithm'] = mb_strtoupper($opts['algorithm']);
		$opts['digits'] = $this->getDigits();

		ksort($opts);

		// Some devices require a specific order for some parameters (e.g. Microsoft Authenticator require "secret" at first place %) )
		$opts = array_merge(
			$positionalOpts,
			$opts
		);

		$params = http_build_query($opts, '', '&', PHP_QUERY_RFC3986);

		return sprintf(
			'%s://%s/%s?%s',
			$this->getAppScheme(),
			$this->getType(),
			rawurlencode($label),
			$params
		);
	}

	/**
	 * Main method, generate OTP value for provided counter
	 *
	 * @param string|int $counter Counter.
	 * @return string
	 */
	public function generateOTP($counter)
	{
		$hash = hash_hmac($this->getDigest(), static::toByte($counter), $this->getSecret());
		$hmac = [];
		foreach (str_split($hash, 2) as $hex)
		{
			$hmac[] = hexdec($hex);
		}

		$offset = $hmac[count($hmac) - 1] & 0xf;
		$code = ($hmac[$offset] & 0x7F) << 24;
		$code |= ($hmac[$offset + 1] & 0xFF) << 16;
		$code |= ($hmac[$offset + 2] & 0xFF) << 8;
		$code |= ($hmac[$offset + 3] & 0xFF);

		$otp = $code % pow(10, $this->getDigits());
		return str_pad($otp, $this->getDigits(), '0', STR_PAD_LEFT);
	}

	/**
	 * Convert value to byte string with padding
	 *
	 * @param string|int $value Value for convert. Must be unsigned integer, e.g. 123, '123', '0x7b', etc.
	 * @return string
	 */
	protected static function toByte($value)
	{
		$result = [];
		while ($value > 0)
		{
			$result[] = chr($value & 0xFF);
			$value >>= 8;
		}

		return str_pad(implode(array_reverse($result)), 8, "\000", STR_PAD_LEFT);
	}

	/**
	 * A timing safe comparison method
	 *
	 * @param string $expected Expected string (e.g. input from user).
	 * @param string $actual Actual string (e.g. generated password).
	 * @return bool
	 * @throws ArgumentTypeException
	 */
	protected function isStringsEqual($expected, $actual)
	{
		if (!is_string($expected))
		{
			throw new ArgumentTypeException('expected', 'string');
		}

		if (!is_string($actual))
		{
			throw new ArgumentTypeException('actual', 'string');
		}

		$lenExpected = strlen($expected);
		$lenActual = strlen($actual);

		$status = $lenExpected ^ $lenActual;
		$len = min($lenExpected, $lenActual);
		for ($i = 0; $i < $len; $i++)
		{
			$status |= ord($expected[$i]) ^ ord($actual[$i]);
		}

		return $status === 0;
	}

	/**
	 * Returns digest algorithm used to calculate the OTP.
	 * Mostly used for generate provision URI
	 *
	 * @return string
	 */
	public function getDigest()
	{
		return $this->digest;
	}

	/**
	 * Return digits (password length)
	 *
	 * @return int
	 */
	public function getDigits()
	{
		return $this->digits;
	}

	/**
	 * Return OtpAlgorithm type
	 *
	 * @return string
	 */
	public function getType()
	{
		return static::$type;
	}

	/**
	 * Return algorithm scheme
	 * Mostly used for generate provision URI
	 *
	 * @return string
	 */
	public function getAppScheme()
	{
		return $this->appScheme;
	}
}