<?php
/**
 * @link http://www.yiiframework.com/
 * @copyright Copyright (c) 2008 Yii Software LLC
 * @license http://www.yiiframework.com/license/
 */

namespace yii\authclient\signature;

use yii\base\InvalidConfigException;
use yii\base\NotSupportedException;

/**
 * RsaSha1 represents 'RSA-SHA1' signature method.
 *
 * Note: This class require PHP "OpenSSL" extension({@link http://php.net/manual/en/book.openssl.php}).
 *
 * @property string $privateCertificate OpenSSL private key certificate content.
 * @property string $publicCertificate OpenSSL public key certificate content.
 *
 * @author Paul Klimov <klimov.paul@gmail.com>
 * @since 2.0
 */
class RsaSha1 extends BaseMethod
{
	/**
	 * @var string OpenSSL private key certificate content.
	 * This value can be fetched from file specified by {@link privateCertificateFile}.
	 */
	protected $_privateCertificate;
	/**
	 * @var string OpenSSL public key certificate content.
	 * This value can be fetched from file specified by {@link publicCertificateFile}.
	 */
	protected $_publicCertificate;
	/**
	 * @var string path to the file, which holds private key certificate.
	 */
	public $privateCertificateFile = '';
	/**
	 * @var string path to the file, which holds public key certificate.
	 */
	public $publicCertificateFile = '';

	/**
	 * @inheritdoc
	 */
	public function init()
	{
		if (!function_exists('openssl_sign')) {
			throw new NotSupportedException('PHP "OpenSSL" extension is required.');
		}
	}

	/**
	 * @param string $publicCertificate public key certificate content.
	 */
	public function setPublicCertificate($publicCertificate)
	{
		$this->_publicCertificate = $publicCertificate;
	}

	/**
	 * @return string public key certificate content.
	 */
	public function getPublicCertificate()
	{
		if ($this->_publicCertificate === null) {
			$this->_publicCertificate = $this->initPublicCertificate();
		}
		return $this->_publicCertificate;
	}

	/**
	 * @param string $privateCertificate private key certificate content.
	 */
	public function setPrivateCertificate($privateCertificate)
	{
		$this->_privateCertificate = $privateCertificate;
	}

	/**
	 * @return string private key certificate content.
	 */
	public function getPrivateCertificate()
	{
		if ($this->_privateCertificate === null) {
			$this->_privateCertificate = $this->initPrivateCertificate();
		}
		return $this->_privateCertificate;
	}

	/**
	 * @inheritdoc
	 */
	public function getName()
	{
		return 'RSA-SHA1';
	}

	/**
	 * Creates initial value for {@link publicCertificate}.
	 * This method will attempt to fetch the certificate value from {@link publicCertificateFile} file.
	 * @throws InvalidConfigException on failure.
	 * @return string public certificate content.
	 */
	protected function initPublicCertificate()
	{
		if (!empty($this->publicCertificateFile)) {
			if (!file_exists($this->publicCertificateFile)) {
				throw new InvalidConfigException("Public certificate file '{$this->publicCertificateFile}' does not exist!");
			}
			return file_get_contents($this->publicCertificateFile);
		} else {
			return '';
		}
	}

	/**
	 * Creates initial value for {@link privateCertificate}.
	 * This method will attempt to fetch the certificate value from {@link privateCertificateFile} file.
	 * @throws InvalidConfigException on failure.
	 * @return string private certificate content.
	 */
	protected function initPrivateCertificate()
	{
		if (!empty($this->privateCertificateFile)) {
			if (!file_exists($this->privateCertificateFile)) {
				throw new InvalidConfigException("Private certificate file '{$this->privateCertificateFile}' does not exist!");
			}
			return file_get_contents($this->privateCertificateFile);
		} else {
			return '';
		}
	}

	/**
	 * @inheritdoc
	 */
	public function generateSignature($baseString, $key)
	{
		$privateCertificateContent = $this->getPrivateCertificate();
		// Pull the private key ID from the certificate
		$privateKeyId = openssl_pkey_get_private($privateCertificateContent);
		// Sign using the key
		openssl_sign($baseString, $signature, $privateKeyId);
		// Release the key resource
		openssl_free_key($privateKeyId);
		return base64_encode($signature);
	}

	/**
	 * @inheritdoc
	 */
	public function verify($signature, $baseString, $key)
	{
		$decodedSignature = base64_decode($signature);
		// Fetch the public key cert based on the request
		$publicCertificate = $this->getPublicCertificate();
		// Pull the public key ID from the certificate
		$publicKeyId = openssl_pkey_get_public($publicCertificate);
		// Check the computed signature against the one passed in the query
		$verificationResult = openssl_verify($baseString, $decodedSignature, $publicKeyId);
		// Release the key resource
		openssl_free_key($publicKeyId);
		return ($verificationResult == 1);
	}
}