<?php
/**
 * OpenPGP_Crypt_RSA.php is a wrapper for using the classes from OpenPGP.php with Crypt_RSA
 *
 * @package OpenPGP
 */

namespace Leenooks\OpenPGP\Crypt;

use Leenooks\OpenPGP;

// From http://phpseclib.sourceforge.net/
use phpseclib\Crypt\RSA as Crypt_RSA;
use phpseclib\Math\BigInteger as Math_BigInteger;

define('CRYPT_RSA_ENCRYPTION_PKCS1', Crypt_RSA::ENCRYPTION_PKCS1);
define('CRYPT_RSA_SIGNATURE_PKCS1', Crypt_RSA::SIGNATURE_PKCS1);

class RSA
{
	protected static $DEBUG = FALSE;
	protected $key,$message;

	// Construct a wrapper object from a key or a message packet
	function __construct($packet)
	{
		if (!is_object($packet))
			$packet = OpenPGP\Message::parse($packet);

		// If it's a key (other keys are subclasses of this one)
		if ($packet instanceof OpenPGP\PublicKeyPacket || $packet[0] instanceof OpenPGP\PublicKeyPacket) {
			$this->key = $packet;
		} else {
			$this->message = $packet;
		}
	}

	function key($keyid=NULL)
	{
		// No key
		if (! $this->key)
			return NULL;

		if ($this->key instanceof OpenPGP\Message) {
			foreach ($this->key as $p) {
				if ($p instanceof OpenPGP\PublicKeyPacket) {
					if (!$keyid || strtoupper(substr($p->fingerprint,strlen($keyid)*-1)) == strtoupper($keyid))
						return $p;
				}
			}
		}

		return $this->key;
	}

	// Get Crypt_RSA for the public key
	function public_key($keyid=NULL)
	{
		return self::convert_public_key($this->key($keyid));
	}

	// Get Crypt_RSA for the private key
	function private_key($keyid=NULL)
	{
		return self::convert_private_key($this->key($keyid));
	}

	// Pass a message to verify with this key, or a key (OpenPGP or Crypt_RSA) to check this message with
	// Second optional parameter to specify which signature to verify (if there is more than one)
	function verify($packet)
	{
		if (static::$DEBUG)
			dump(['In METHOD: '=>__METHOD__,'packet'=>$packet]);

		$self = $this; // For old PHP
		if (! is_object($packet))
			$packet = OpenPGP\Message::parse($packet);

		if (! $this->message) {
			$m = $packet;
			$verifier = function($m, $s) use($self) {
			$key = $self->public_key($s->issuer());
			if(!$key) return false;
			$key->setHash(strtolower($s->hash_algorithm_name()));
			return $key->verify($m, reset($s->data));
			};
		} else {
			if(!($packet instanceof Crypt_RSA)) {
			$packet = new self($packet);
			}

			$m = $this->message;
			$verifier = function($m, $s) use($self, $packet) {
			if(!($packet instanceof Crypt_RSA)) {
				$key = $packet->public_key($s->issuer());
			}
			if(!$key) return false;
			$key->setHash(strtolower($s->hash_algorithm_name()));
			return $key->verify($m, reset($s->data));
			};
		}

		return $m->verified_signatures(array('RSA' => array(
			'MD5'		=> $verifier,
			'SHA1'	 => $verifier,
			'SHA224' => $verifier,
			'SHA256' => $verifier,
			'SHA384' => $verifier,
			'SHA512' => $verifier
		)));
	}

	// Pass a message to sign with this key, or a secret key to sign this message with
	// Second parameter is hash algorithm to use (default SHA256)
	// Third parameter is the 16-digit key ID to use... defaults to the key id in the key packet
	function sign($packet,$hash='SHA256',$keyid=NULL)
	{
		if (! is_object($packet)) {
			if($this->key) {
				$packet = new OpenPGP\LiteralDataPacket($packet);
			} else {
				$packet = OpenPGP\Message::parse($packet);
			}
		}

		if ($packet instanceof OpenPGP\SecretKeyPacket
			|| $packet instanceof Crypt_RSA
			|| ($packet instanceof ArrayAccess && $packet[0] instanceof OpenPGP\SecretKeyPacket))
		{
			$key = $packet;
			$message = $this->message;

		} else {
			$key = $this->key;
			$message = $packet;
		}

		// Missing some data
		if (! $key || !$message)
			return NULL;

		if ($message instanceof OpenPGP\Message) {
			$sign = $message->signatures();
			$message = $sign[0][0];
		}

		if (!($key instanceof Crypt_RSA)) {
			$key = new self($key);

			if (! $keyid)
				$keyid = substr($key->key()->fingerprint,-16,16);

			$key = $key->private_key($keyid);
		}

		$key->setHash(strtolower($hash));

		$sig = new SignaturePacket($message,'RSA',strtoupper($hash));
		$sig->hashed_subpackets[] = new OpenPGP\SignaturePacket\IssuerPacket($keyid);
		$sig->sign_data(['RSA'=>[$hash => function($data) use($key) {return [$key->sign($data)];}]]);

		return new OpenPGP\Message(array($sig, $message));
	}

	/** Pass a message with a key and userid packet to sign */
	// TODO: merge this with the normal sign function
	function sign_key_userid($packet,$hash='SHA256',$keyid=NULL)
	{
		if (is_array($packet)) {
			$packet = new OpenPGP\Message($packet);
		} else if(!is_object($packet)) {
			$packet = OpenPGP\Message::parse($packet);
		}

		$key = $this->private_key($keyid);

		// Missing some data
		if (! $key || ! $packet)
			return NULL;

		if (! $keyid)
			$keyid = substr($this->key->fingerprint,-16);
		$key->setHash(strtolower($hash));

		$sig = NULL;
		foreach($packet as $p) {
			if ($p instanceof OpenPGP\SignaturePacket)
				$sig = $p;
		}

		if (! $sig) {
			$sig = new OpenPGP\SignaturePacket($packet,'RSA',strtoupper($hash));
			$sig->signature_type = 0x13;
			$sig->hashed_subpackets[] = new OpenPGP\SignaturePacket\KeyFlagsPacket(array(0x01|0x02));
			$sig->hashed_subpackets[] = new OpenPGP\SignaturePacket\IssuerPacket($keyid);
			$packet[] = $sig;
		}

		$sig->sign_data(['RSA'=>[$hash => function($data) use($key) {return [$key->sign($data)];}]]);

		return $packet;
	}

	function decrypt($packet)
	{
		if (! is_object($packet))
			$packet = OpenPGP\Message::parse($packet);

		if ($packet instanceof OpenPGP\SecretKeyPacket
			|| $packet instanceof Crypt_RSA
			|| ($packet instanceof \ArrayAccess && $packet[0] instanceof OpenPGP\SecretKeyPacket))
		{
			$keys = $packet;
			$message = $this->message;

		} else {
			$keys = $this->key;
			$message = $packet;
		}

		// Missing some data
		if (! $keys || ! $message)
			return NULL;

		if (! ($keys instanceof Crypt_RSA)) {
			$keys = new self($keys);
		}

		if (static::$DEBUG)
			dump([__METHOD__=>['keys'=>$keys,'message'=>$message]]);

		foreach ($message as $p) {
			if (static::$DEBUG)
				dump(['p'=>$p,'test'=> ($p instanceof OpenPGP\AsymmetricSessionKeyPacket) ]);

			if ($p instanceof OpenPGP\AsymmetricSessionKeyPacket) {
				if (static::$DEBUG)
					dump(['keys'=>$keys,'test'=>($keys instanceof Crypt_RSA)]);

				if($keys instanceof Crypt_RSA) {
					$sk = self::try_decrypt_session($keys,substr($p->encrypted_data,2));

				} elseif (strlen(str_replace('0','',$p->keyid)) < 1) {
					foreach($keys->key as $k) {
						$sk = self::try_decrypt_session(self::convert_private_key($k), substr($p->encrypted_data, 2));

						if ($sk)
							break;
					}

				} else {
					$key = $keys->private_key($p->keyid);
					$sk = self::try_decrypt_session($key,substr($p->encrypted_data,2));

					if (static::$DEBUG)
						dump(['c'=>$p->keyid,'key'=>$key,'sk'=>$sk]);
				}

				if (! $sk)
					continue;

				$r = Symmetric::decryptPacket(Symmetric::getEncryptedData($message),$sk[0],$sk[1]);

				if ($r)
					return $r;
			}
		}

		return NULL; /* Failed */
	}

	static function try_decrypt_session($key,$edata)
	{
		$key->setEncryptionMode(CRYPT_RSA_ENCRYPTION_PKCS1);
		$data = @$key->decrypt($edata);
		if (! $data)
			return NULL;

		$sk = substr($data,1,strlen($data)-3);
		$chk = unpack('n',substr($data,-2));
		$chk = reset($chk);

		$sk_chk = 0;
		for ($i=0;$i<strlen($sk);$i++) {
			$sk_chk = ($sk_chk+ord($sk{$i}))%65536;
		}

		if ($sk_chk != $chk)
			return NULL;

		return array(ord($data{0}),$sk);
	}

	static function crypt_rsa_key($mod,$exp,$hash='SHA256')
	{
		$rsa = new Crypt_RSA();

		$rsa->setSignatureMode(CRYPT_RSA_SIGNATURE_PKCS1);
		$rsa->setHash(strtolower($hash));
		$rsa->modulus = new Math_BigInteger($mod,256);
		$rsa->k = strlen($rsa->modulus->toBytes());
		$rsa->exponent = new Math_BigInteger($exp,256);
		$rsa->setPublicKey();

		return $rsa;
	}

	static function convert_key($packet,$private=false)
	{
		if (! is_object($packet))
			$packet = OpenPGP\Message::parse($packet);

		if ($packet instanceof OpenPGP\Message)
			$packet = $packet[0];

		$mod = $packet->key['n'];
		$exp = $packet->key['e'];

		if ($private)
			$exp = $packet->key['d'];

		// Packet doesn't have needed data
		if (! $exp)
			return NULL;

		$rsa = self::crypt_rsa_key($mod,$exp);

		if ($private) {
			/**
			 * @see https://github.com/phpseclib/phpseclib/issues/1113
			 * Primes and coefficients now use BigIntegers.
			 **/
			//set the primes
			if ($packet->key['p'] && $packet->key['q'])
				$rsa->primes = [
					1 => new Math_BigInteger($packet->key['p'], 256),
					2 => new Math_BigInteger($packet->key['q'], 256)
				];
			// set the coefficients
			if ($packet->key['u'])
				$rsa->coefficients = [2=>new Math_BigInteger($packet->key['u'],256)];
		}

		return $rsa;
	}

	static function convert_public_key($packet)
	{
		return self::convert_key($packet, false);
	}

	static function convert_private_key($packet)
	{
		return self::convert_key($packet, true);
	}
}