Your IP : 3.135.195.180


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

<?php
namespace Bitrix\Main\Security\Sign;

use Bitrix\Main\ArgumentTypeException;
use Bitrix\Main\Config;

/**
 * Class Signer
 * @since 14.0.7
 * @package Bitrix\Main\Security\Sign
 */
class Signer
{
	/** @var SigningAlgorithm Signing algorithm */
	protected $algorithm = null;
	protected $separator = '.';
	/** @var string Secret key */
	protected $key = null;

	/**
	 * Creates new Signer object. If you want use your own signing algorithm - you can this
	 *
	 * @param SigningAlgorithm|null $algorithm Custom signing algorithm.
	 */
	public function __construct(SigningAlgorithm $algorithm = null)
	{
		if ($algorithm !== null)
			$this->algorithm = $algorithm;
		else
			$this->algorithm = new HmacAlgorithm();
	}

	/**
	 * Set key for signing
	 *
	 * @param string $value Key.
	 * @return $this
	 * @throws ArgumentTypeException
	 */
	public function setKey($value)
	{
		if (!is_string($value))
			throw new ArgumentTypeException('value', 'string');

		$this->key = $value;
		return $this;
	}

	/**
	 * Return separator, used for packing/unpacking
	 *
	 * @return string
	 */
	public function getSeparator()
	{
		return $this->separator;
	}

	/**
	 * Set separator, used for packing/unpacking
	 *
	 * @param string $value Separator.
	 * @return $this
	 * @throws ArgumentTypeException
	 */
	public function setSeparator($value)
	{
		if (!is_string($value))
			throw new ArgumentTypeException('value', 'string');

		$this->separator = $value;
		return $this;
	}

	/**
	 * Return message signature
	 *
	 * @param string $value Message.
	 * @param string|null $salt Salt.
	 * @return string
	 * @throws ArgumentTypeException
	 */
	public function getSignature($value, $salt = null)
	{
		if (!is_string($value))
			throw new ArgumentTypeException('value', 'string');

		$key = $this->getKey($salt);
		$signature = $this->algorithm->getSignature($value, $key);
		$signature = $this->encodeSignature($signature);
		return $signature;
	}

	/**
	 * Sign message, return string in format "{message}{separator}{signature}"
	 *
	 * Simple example:
	 * <code>
	 *  // If salt needed
	 *  $foo = (new Signer)->sign('test', 'my_salt');
	 *
	 *  // Otherwise
	 *  $bar = (new Signer)->sign('test');
	 * </code>
	 *
	 * @param string $value Message for signing.
	 * @param string|null $salt Salt, if needed.
	 * @return string
	 * @throws ArgumentTypeException
	 */
	public function sign($value, $salt = null)
	{
		if (!is_string($value))
			throw new ArgumentTypeException('value', 'string');

		$signature = $this->getSignature($value, $salt);
		return $this->pack(array($value, $signature));
	}

	/**
	 * Check message signature and return original message.
	 *
	 * Simple example:
	 * <code>
	 *  $signer = new Signer;
	 *
	 *  // Sing message
	 *  $signedValue = $signer->sign('test');
	 *
	 *  // Get original message with checking
	 *  echo $signer->unsign($signedValue);
	 *  // Output: 'test'
	 *
	 *  // Try to unsigning not signed value
	 *  echo $signer->unsign('test');
	 *  //throw BadSignatureException with message 'Separator not found in value'
	 *
	 *  // Or with invalid sign
	 *  echo $signer->unsign('test.invalid_sign');
	 *
	 *  // Or invalid salt
	 *  //throw BadSignatureException with message 'Signature does not match'
	 *  echo $signer->unsign($signedValue, 'invalid_salt');
	 *
	 * </code>
	 *
	 * @param string $signedValue Signed value, must be in format "{message}{separator}{signature}".
	 * @param string|null $salt Salt, if used while signing.
	 * @return string
	 * @throws BadSignatureException
	 * @throws ArgumentTypeException
	 */
	public function unsign($signedValue, $salt = null)
	{
		if (!is_string($signedValue))
			throw new ArgumentTypeException('signedValue', 'string');

		list($value, $signature) = $this->unpack($signedValue);
		if (!$this->verifySignature($value, $signature, $salt))
			throw new BadSignatureException('Signature does not match');

		return $value;
	}

	/**
	 * Simply validation of message signature
	 *
	 * @param string $value Message.
	 * @param string $signature Signature.
	 * @param string|null $salt Salt, if used while signing.
	 * @return bool True if OK, otherwise - false.
	 */
	public function validate($value, $signature, $salt = null)
	{
		return $this->verifySignature($value, $signature, $salt);
	}

	/**
	 * Verify message signature provided by hashing algorithm
	 *
	 * @param string $value Message.
	 * @param string $sig Signature.
	 * @param string|null $salt Salt, if used while signing.
	 * @return bool
	 */
	protected function verifySignature($value, $sig, $salt = null)
	{
		$key = $this->getKey($salt);
		$signature = $this->decodeSignature($sig);
		return $this->algorithm->verify($value, $key, $signature);
	}

	/**
	 * Return salted key for signing.
	 * If key was set by setKey - use it
	 * Otherwise - used default (if default key does not exists - automatically generate it)
	 *
	 * @param string|null $salt Salt, if needed.
	 * @throws BadSignatureException
	 * @return string
	 */
	protected function getKey($salt = null)
	{
		if ($salt !== null && !preg_match('#^[a-zA-Z0-9_.-]{3,50}$#D', $salt))
			throw new BadSignatureException('Malformed salt, only [a-zA-Z0-9_.-]{3,50} characters are acceptable');

		if ($this->key !== null)
			$key = $this->key;
		else
			$key = $this->getDefaultKey();

		return strval($salt).$key;
	}

	/**
	 * Return default (system) key for signing or generate if it does not exists
	 *
	 * @return string
	 */
	protected function getDefaultKey()
	{
		static $defaultKey = null;
		if ($defaultKey === null)
		{
			$defaultKey = Config\Option::get('main', 'signer_default_key', false);
			if (!$defaultKey)
			{
				$defaultKey = hash('sha512', \Bitrix\Main\Security\Random::getString(64));
				Config\Option::set('main', 'signer_default_key', $defaultKey);
			}

			$options = Config\Configuration::getValue("crypto");
			if(isset($options["crypto_key"]))
			{
				$defaultKey .= $options["crypto_key"];
			}
		}

		return $defaultKey;
	}


	/**
	 * Pack array values to single string:
	 * pack(['test', 'all', 'values']) -> 'test.all.values'
	 *
	 * @param array $values Values for packing.
	 * @return string
	 */
	public function pack(array $values)
	{
		return join($this->separator, $values);
	}

	/**
	 * Unpack values from string (something like rsplit).
	 * Simple example for separator ".":
	 * <code>
	 *  // Unpack all values:
	 *  unpack('test.all.values', 0) -> ['test', 'all', 'values']
	 *
	 *  // Unpack 2 values (by default). First element containing the rest of string.
	 *  unpack('test.all.values') -> ['test.all', 'values']
	 *
	 *  // Exception if separator is missing
	 *  unpack('test.all values', 3) -> throws BadSignatureException
	 * </code>
	 *
	 * @param string $value String for unpacking.
	 * @param int $limit If $limit === 0 - unpack all values, default - 2.
	 * @return array
	 * @throws BadSignatureException
	 */
	public function unpack($value, $limit = 2)
	{
		// Some kind of optimization
		if ($limit === 0)
		{
			if (strpos($value, $this->separator) === false)
				throw new BadSignatureException('Separator not found in value');

			return explode($this->separator, $value);
		}

		$result = array();
		while(--$limit > 0)
		{
			$pos = bxstrrpos($value, $this->separator);
			if ($pos === false)
				throw new BadSignatureException('Separator not found in value');

			$result[] = mb_substr($value, $pos + 1);
			$value = mb_substr($value, 0, $pos);
		}
		$result[] = $value;

		return array_reverse($result);
	}

	/**
	 * Return encoded signature
	 *
	 * @param string $value Signature in binary representation.
	 * @return string Encoded signature
	 */
	protected function encodeSignature($value)
	{
		return bin2hex($value);
	}

	/**
	 * Return decoded signature
	 *
	 * @param string $value Encoded signature.
	 * @return string Signature in binary representation
	 * @throws BadSignatureException
	 */
	protected function decodeSignature($value)
	{
		if (preg_match('#[^[:xdigit:]]#', $value))
			throw new BadSignatureException('Signature must be hexadecimal string');

		// ToDo: use hex2bin instead pack for PHP > 5.4.0
		return pack('H*', $value);
	}
}